diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile new file mode 100644 index 0000000000..3d51f0a5a0 --- /dev/null +++ b/.devcontainer/Dockerfile @@ -0,0 +1,18 @@ +# arm + amd compatible Dockerfile +FROM ghcr.io/findy-network/findy-base:indy-1.16.ubuntu-18.04 AS indy-base + +FROM ubuntu:18.04 + +# install indy deps and files from base +RUN apt-get update && apt-get install -y libsodium23 libssl1.1 libzmq5 git zsh + +COPY --from=indy-base /usr/include/indy /usr/include/indy +COPY --from=indy-base /usr/lib/libindy.a /usr/lib/libindy.a +COPY --from=indy-base /usr/lib/libindy.so /usr/lib/libindy.so + +RUN apt-get install -y curl python3 build-essential ca-certificates && \ + curl https://raw.githubusercontent.com/creationix/nvm/master/install.sh | bash && \ + export NVM_DIR="$HOME/.nvm" && \ + [ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" && \ + nvm install v16 && \ + npm install yarn -g diff --git a/.devcontainer/devcontainer.env b/.devcontainer/devcontainer.env new file mode 100644 index 0000000000..1b31a5ab62 --- /dev/null +++ b/.devcontainer/devcontainer.env @@ -0,0 +1,7 @@ +# +# Any environment variables that the container needs +# go in here. +# +# Example(s) +# GENESIS_TXN_PATH=/work/network/genesis/local-genesis.txn +# diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 0000000000..1728c1a7cc --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,8 @@ +{ + "build": { + "dockerfile": "Dockerfile" + }, + "runArgs": ["--env-file", ".devcontainer/devcontainer.env"], + "workspaceMount": "source=${localWorkspaceFolder},target=/work,type=bind", + "workspaceFolder": "/work" +} diff --git a/.eslintrc.js b/.eslintrc.js index e45d4d2cad..c669beed73 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -5,8 +5,10 @@ module.exports = { 'plugin:import/recommended', 'plugin:import/typescript', 'plugin:@typescript-eslint/recommended', + 'plugin:workspaces/recommended', 'plugin:prettier/recommended', // Enables eslint-plugin-prettier and displays prettier errors as ESLint errors. Make sure this is always the last configuration in the extends array. ], + plugins: ['workspaces'], parserOptions: { tsconfigRootDir: __dirname, project: ['./tsconfig.eslint.json'], @@ -87,10 +89,14 @@ module.exports = { }, }, { - files: ['jest.config.ts', '.eslintrc.js'], + files: ['jest.config.ts', '.eslintrc.js', './scripts/**'], env: { node: true, }, + rules: { + '@typescript-eslint/no-var-requires': 'off', + 'no-undef': 'off', + }, }, { files: ['demo/**'], @@ -122,5 +128,13 @@ module.exports = { ], }, }, + { + files: ['*.test.ts', '**/__tests__/**', '**/tests/**', '**/tests/**'], + rules: { + 'workspaces/no-relative-imports': 'off', + 'workspaces/require-dependency': 'off', + 'workspaces/no-absolute-imports': 'off', + }, + }, ], } diff --git a/.github/actions/setup-cheqd/action.yml b/.github/actions/setup-cheqd/action.yml new file mode 100644 index 0000000000..e8a64207e7 --- /dev/null +++ b/.github/actions/setup-cheqd/action.yml @@ -0,0 +1,14 @@ +name: Setup cheqd +description: Setup a cheqd network to perform tests +author: 'daev@cheqd.io' + +runs: + using: composite + steps: + - name: Start cheqd localnet + run: docker run --rm -d -p 26657:26657 ghcr.io/cheqd/cheqd-testnet:latest + shell: bash + +branding: + icon: scissors + color: purple diff --git a/.github/actions/setup-indy-pool/action.yml b/.github/actions/setup-indy-pool/action.yml index 791548b809..23e1ebd8cf 100644 --- a/.github/actions/setup-indy-pool/action.yml +++ b/.github/actions/setup-indy-pool/action.yml @@ -6,6 +6,9 @@ inputs: seed: description: Seed to register on the ledger required: true + endorserSeed: + description: Endorser seed to register on the ledger + required: true runs: using: composite @@ -20,10 +23,14 @@ runs: run: docker exec indy-pool indy-cli-setup shell: bash - - name: Register DID on ledger + - name: Register Trustee DID on ledger run: docker exec indy-pool add-did-from-seed ${{ inputs.seed }} TRUSTEE shell: bash + - name: Register Endorser DID on ledger + run: docker exec indy-pool add-did-from-seed ${{ inputs.endorserSeed }} ENDORSER + shell: bash + branding: icon: scissors color: purple diff --git a/.github/workflows/continuous-deployment.yml b/.github/workflows/continuous-deployment.yml index 94c44825b4..82d66fb8f7 100644 --- a/.github/workflows/continuous-deployment.yml +++ b/.github/workflows/continuous-deployment.yml @@ -7,7 +7,7 @@ on: jobs: release-canary: - runs-on: ubuntu-20.04 + runs-on: aries-ubuntu-2004 name: Release Canary if: "!startsWith(github.event.head_commit.message, 'chore(release): v')" steps: @@ -56,7 +56,7 @@ jobs: git push origin v${{ steps.get-version.outputs.version }} --no-verify release-stable: - runs-on: ubuntu-20.04 + runs-on: aries-ubuntu-2004 name: Create Stable Release # Only run if the last pushed commit is a release commit if: "startsWith(github.event.head_commit.message, 'chore(release): v')" diff --git a/.github/workflows/continuous-integration.yml b/.github/workflows/continuous-integration.yml index 4134d8c5f1..19ea4923ba 100644 --- a/.github/workflows/continuous-integration.yml +++ b/.github/workflows/continuous-integration.yml @@ -10,8 +10,10 @@ on: env: TEST_AGENT_PUBLIC_DID_SEED: 000000000000000000000000Trustee9 + ENDORSER_AGENT_PUBLIC_DID_SEED: 00000000000000000000000Endorser9 GENESIS_TXN_PATH: network/genesis/local-genesis.txn LIB_INDY_STRG_POSTGRES: /home/runner/work/aries-framework-javascript/indy-sdk/experimental/plugins/postgres_storage/target/release # for Linux + NODE_OPTIONS: --max_old_space_size=4096 # Make sure we're not running multiple release steps at the same time as this can give issues with determining the next npm version to release. # Ideally we only add this to the 'release' job so it doesn't limit PR runs, but github can't guarantee the job order in that case: @@ -25,7 +27,7 @@ jobs: # validation scripts. To still be able to run the CI we can manually trigger it by adding the 'ci-test' # label to the pull request ci-trigger: - runs-on: ubuntu-20.04 + runs-on: aries-ubuntu-2004 outputs: triggered: ${{ steps.check.outputs.triggered }} steps: @@ -36,7 +38,7 @@ jobs: export SHOULD_RUN='true' elif [[ "${{ github.event.action }}" == "labeled" && "${{ github.event.label.name }}" != "ci-test" ]]; then export SHOULD_RUN='false' - else + else export SHOULD_RUN='true' fi @@ -44,7 +46,7 @@ jobs: echo "::set-output name=triggered::${SHOULD_RUN}" validate: - runs-on: ubuntu-20.04 + runs-on: aries-ubuntu-2004 name: Validate steps: - name: Checkout aries-framework-javascript @@ -75,24 +77,30 @@ jobs: run: yarn build integration-test: - runs-on: ubuntu-20.04 + runs-on: aries-ubuntu-2004 name: Integration Tests strategy: matrix: - node-version: [14.x, 16.x, 17.x, 18.x] + node-version: [16.x, 18.x] steps: - name: Checkout aries-framework-javascript uses: actions/checkout@v2 # setup dependencies + - name: Setup Libindy uses: ./.github/actions/setup-libindy + - name: Setup Indy Pool uses: ./.github/actions/setup-indy-pool with: seed: ${TEST_AGENT_PUBLIC_DID_SEED} + endorserSeed: ${ENDORSER_AGENT_PUBLIC_DID_SEED} + + - name: Setup Cheqd + uses: ./.github/actions/setup-cheqd - name: Setup Postgres uses: ./.github/actions/setup-postgres @@ -104,17 +112,22 @@ jobs: uses: ./.github/actions/setup-node with: node-version: ${{ matrix.node-version }} + + - name: Add ref-napi resolution in Node18 + run: node ./scripts/add-ref-napi-resolution.js + if: matrix.node-version == '18.x' + - name: Install dependencies run: yarn install - name: Run tests - run: TEST_AGENT_PUBLIC_DID_SEED=${TEST_AGENT_PUBLIC_DID_SEED} GENESIS_TXN_PATH=${GENESIS_TXN_PATH} yarn test --coverage + run: TEST_AGENT_PUBLIC_DID_SEED=${TEST_AGENT_PUBLIC_DID_SEED} ENDORSER_AGENT_PUBLIC_DID_SEED=${ENDORSER_AGENT_PUBLIC_DID_SEED} GENESIS_TXN_PATH=${GENESIS_TXN_PATH} yarn test --coverage --forceExit --bail - uses: codecov/codecov-action@v1 if: always() version-stable: - runs-on: ubuntu-20.04 + runs-on: aries-ubuntu-2004 name: Release stable needs: [integration-test, validate] if: github.ref == 'refs/heads/main' && github.event_name == 'workflow_dispatch' diff --git a/.husky/pre-push b/.husky/pre-push deleted file mode 100755 index b969aaaf8d..0000000000 --- a/.husky/pre-push +++ /dev/null @@ -1 +0,0 @@ -yarn validate \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 50748153df..0d2a624f28 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,21 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [0.3.3](https://github.com/hyperledger/aries-framework-javascript/compare/v0.3.2...v0.3.3) (2023-01-18) + +### Bug Fixes + +- fix typing issues with typescript 4.9 ([#1214](https://github.com/hyperledger/aries-framework-javascript/issues/1214)) ([087980f](https://github.com/hyperledger/aries-framework-javascript/commit/087980f1adf3ee0bc434ca9782243a62c6124444)) +- **openid4vc-client:** set package to private ([#1210](https://github.com/hyperledger/aries-framework-javascript/issues/1210)) ([c697716](https://github.com/hyperledger/aries-framework-javascript/commit/c697716bf1837b9fef307f60ff97f01d3d926728)) + +### Features + +- add anoncreds package ([#1118](https://github.com/hyperledger/aries-framework-javascript/issues/1118)) ([adba83d](https://github.com/hyperledger/aries-framework-javascript/commit/adba83d8df176288083969f2c3f975bbfc1acd9c)) +- add minimal oidc-client package ([#1197](https://github.com/hyperledger/aries-framework-javascript/issues/1197)) ([b6f89f9](https://github.com/hyperledger/aries-framework-javascript/commit/b6f89f943dc4417626f868ac9f43a3d890ab62c6)) +- adding trust ping events and trust ping command ([#1182](https://github.com/hyperledger/aries-framework-javascript/issues/1182)) ([fd006f2](https://github.com/hyperledger/aries-framework-javascript/commit/fd006f262a91f901e7f8a9c6e6882ea178230005)) +- **anoncreds:** add anoncreds registry service ([#1204](https://github.com/hyperledger/aries-framework-javascript/issues/1204)) ([86647e7](https://github.com/hyperledger/aries-framework-javascript/commit/86647e7f55c9a362f6ab500538c4de2112e42206)) +- **indy-sdk:** add indy-sdk package ([#1200](https://github.com/hyperledger/aries-framework-javascript/issues/1200)) ([9933b35](https://github.com/hyperledger/aries-framework-javascript/commit/9933b35a6aa4524caef8a885e71b742cd0d7186b)) + ## [0.3.2](https://github.com/hyperledger/aries-framework-javascript/compare/v0.3.1...v0.3.2) (2023-01-04) ### Bug Fixes diff --git a/DEVREADME.md b/DEVREADME.md index cea95a96da..595f7bae5f 100644 --- a/DEVREADME.md +++ b/DEVREADME.md @@ -2,9 +2,23 @@ This file is intended for developers working on the internals of the framework. If you're just looking how to get started with the framework, see the [docs](./docs) +# Environment Setup + +## VSCode devContainer + +This project comes with a [.devcontainer](./devcontainer) to make it as easy as possible to setup your dev environment and begin contributing to this project. + +All the [environment variables](https://code.visualstudio.com/remote/advancedcontainers/environment-variables) noted below can be added to [devcontainer.env](./devcontainer.env) and exposed to the development docker container. + +When running in a container your project root directory will be `/work`. Use this to correctly path any environment variables, for example: + +```console +GENESIS_TXN_PATH=/work/network/genesis/local-genesis.txn +``` + ## Running tests -Test are executed using jest. Some test require either the **mediator agents** or the **ledger** to be running. When running tests that require a connection to the ledger pool, you need to set the `TEST_AGENT_PUBLIC_DID_SEED` and `GENESIS_TXN_PATH` environment variables. +Test are executed using jest. Some test require either the **mediator agents** or the **ledger** to be running. When running tests that require a connection to the ledger pool, you need to set the `TEST_AGENT_PUBLIC_DID_SEED`, `ENDORSER_AGENT_PUBLIC_DID_SEED` and `GENESIS_TXN_PATH` environment variables. ### Setting environment variables @@ -17,6 +31,9 @@ If you're using the setup as described in this document, you don't need to provi - `TEST_AGENT_PUBLIC_DID_SEED`: The seed to use for the public DID. This will be used to do public write operations to the ledger. You should use a seed for a DID that is already registered on the ledger. - If using the local or default genesis, use the same seed you used for the `add-did-from-seed` command from the [ledger setup](#setup-ledger) in the previous step. (default is `000000000000000000000000Trustee9`) - If using the BuilderNet genesis, make sure your seed is registered on the BuilderNet using [selfserve.sovrin.org](https://selfserve.sovrin.org/) and you have read and accepted the associated [Transaction Author Agreement](https://github.com/sovrin-foundation/sovrin/blob/master/TAA/TAA.md). We are not responsible for any unwanted consequences of using the BuilderNet. +- `ENDORSER_AGENT_PUBLIC_DID_SEED`: The seed to use for the public Endorser DID. This will be used to endorse transactions. You should use a seed for a DID that is already registered on the ledger. + - If using the local or default genesis, use the same seed you used for the `add-did-from-seed` command from the [ledger setup](#setup-ledger) in the previous step. (default is `00000000000000000000000Endorser9`) + - If using the BuilderNet genesis, make sure your seed is registered on the BuilderNet using [selfserve.sovrin.org](https://selfserve.sovrin.org/) and you have read and accepted the associated [Transaction Author Agreement](https://github.com/sovrin-foundation/sovrin/blob/master/TAA/TAA.md). We are not responsible for any unwanted consequences of using the BuilderNet. ### Setup Postgres @@ -30,7 +47,7 @@ docker pull postgres docker run --name postgres -e POSTGRES_USER=postgres -e POSTGRES_PASSWORD=postgres -p 5432:5432 -d postgres ``` -### Setup Ledger +### Setup Indy Ledger For testing we've added a setup to this repo that allows you to quickly setup an indy ledger. @@ -47,13 +64,30 @@ docker run -d --rm --name indy-pool -p 9701-9708:9701-9708 indy-pool # Setup CLI. This creates a wallet, connects to the ledger and sets the Transaction Author Agreement docker exec indy-pool indy-cli-setup -# DID and Verkey from seed. Set 'Trustee' role in order to be able to register public DIDs +# DID and Verkey from seed. Set 'ENDORSER' role in order to be able to register public DIDs +docker exec indy-pool add-did-from-seed 00000000000000000000000Endorser9 ENDORSER + +# DID and Verkey from seed. Set 'Trustee' docker exec indy-pool add-did-from-seed 000000000000000000000000Trustee9 TRUSTEE # If you want to register using the DID/Verkey you can use # docker exec indy-pool add-did "NkGXDEPgpFGjQKMYmz6SyF" "CrSA1WbYYWLJoHm16Xw1VEeWxFvXtWjtsfEzMsjB5vDT" ``` +### Setup Cheqd Ledger + +In addition, there's also a docker command to run a cheqd test network. + +```sh +docker run --rm -d -p 26657:26657 ghcr.io/cheqd/cheqd-testnet:latest +``` + +If you want to run tests without the cheqd ledger, you can use the following ignore pattern: + +```sh +yarn test --testPathIgnorePatterns packages/cheqd +``` + ### Run all tests You can run the tests using the following command. @@ -65,19 +99,19 @@ yarn test If you're not using the ledger setup from above, make sure you pass the correct environment variables from [Setting environment variables](#setting-environment-variables) for connecting to the indy **ledger** pool. ```sh -GENESIS_TXN_PATH=network/genesis/local-genesis.txn TEST_AGENT_PUBLIC_DID_SEED=000000000000000000000000Trustee9 yarn test +GENESIS_TXN_PATH=network/genesis/local-genesis.txn TEST_AGENT_PUBLIC_DID_SEED=000000000000000000000000Trustee9 ENDORSER_AGENT_PUBLIC_DID_SEED=00000000000000000000000Endorser9 yarn test ``` Locally, you might want to run the tests without postgres tests. You can do that by ignoring the tests: ```sh -yarn test --testPathIgnorePatterns ./packages/core/tests/postgres.e2e.test.ts -u +yarn test --testPathIgnorePatterns postgres.e2e.test.ts ``` -In case you run into trouble running the tests, e.g. complaining about snapshots not being up-to-date, you can try and remove the data stored for the indy-client. On a Unix system with default setup you achieve this by running: +In case you run into trouble running the tests, e.g. complaining about snapshots not being up-to-date, you can try and remove the data stored for the indy-client or AFJ. Note this removes all wallets and data, so make sure you're okay with all data being removed. On a Unix system with default setup you achieve this by running: ```sh -rm -rf ~/.indy-client +rm -rf ~/.indy-client ~/.afj ``` ## Usage with Docker diff --git a/Dockerfile b/Dockerfile index 91ccda0363..cd68166f9e 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM ubuntu:18.04 as base +FROM ubuntu:20.04 as base ENV DEBIAN_FRONTEND noninteractive diff --git a/README.md b/README.md index 074c7a0127..26794e54ba 100644 --- a/README.md +++ b/README.md @@ -12,12 +12,6 @@ alt="Pipeline Status" src="https://github.com/hyperledger/aries-framework-javascript/workflows/Continuous%20Integration/badge.svg?branch=main" /> - Language grade: JavaScript Codecov Coverage + + @aries-framework/indy-sdk + + + @aries-framework/indy-sdk version + + + + + @aries-framework/indy-vdr + + + @aries-framework/indy-vdr version + + + + + @aries-framework/cheqd + + + @aries-framework/cheqd version + + + + + @aries-framework/askar + + + @aries-framework/askar version + + + + + @aries-framework/anoncreds + + + @aries-framework/anoncreds version + + + + + @aries-framework/anoncreds-rs + + + @aries-framework/anoncreds-rs version + + + + + @aries-framework/openid4vc-client + + + @aries-framework/openid4vc-client version + + + + + @aries-framework/action-menu + + + @aries-framework/action-menu version + + + + @aries-framework/question-answer + + + @aries-framework/question-answer version + + + + + @aries-framework/tenants + + + @aries-framework/tenants version + + + ## Getting Started diff --git a/demo/README.md b/demo/README.md index 7f4d71df0d..93f14ee99f 100644 --- a/demo/README.md +++ b/demo/README.md @@ -17,7 +17,7 @@ Alice, a former student of Faber College, connects with the College, is issued a In order to use Aries Framework JavaScript some platform specific dependencies and setup is required. See our guides below to quickly set up you project with Aries Framework JavaScript for NodeJS, React Native and Electron. -- [NodeJS](https:/aries.js.org/guides/getting-started/prerequisites/nodejs) +- [NodeJS](https://aries.js.org/guides/getting-started/installation/nodejs) ### Run the demo @@ -57,8 +57,8 @@ yarn faber To set up a connection: -- Select 'setup connection' in both Agents -- Alice will print a invitation link which you then copy and paste to Faber +- Select 'receive connection invitation' in Alice and 'create connection invitation' in Faber +- Faber will print a invitation link which you then copy and paste to Alice - You have now set up a connection! To offer a credential: @@ -66,7 +66,7 @@ To offer a credential: - Select 'offer credential' in Faber - Faber will start with registering a schema and the credential definition accordingly - You have now send a credential offer to Alice! -- Go to Alice to accept the incoming credential offer +- Go to Alice to accept the incoming credential offer by selecting 'yes'. To request a proof: diff --git a/demo/package.json b/demo/package.json index 5c58fa4d63..16ae0447fe 100644 --- a/demo/package.json +++ b/demo/package.json @@ -13,15 +13,25 @@ "faber": "ts-node src/FaberInquirer.ts", "refresh": "rm -rf ./node_modules ./yarn.lock && yarn" }, + "dependencies": { + "@hyperledger/indy-vdr-nodejs": "^0.1.0-dev.14", + "@hyperledger/anoncreds-nodejs": "^0.1.0-dev.15", + "@hyperledger/aries-askar-nodejs": "^0.1.0-dev.8", + "inquirer": "^8.2.5" + }, "devDependencies": { + "@aries-framework/anoncreds": "*", + "@aries-framework/anoncreds-rs": "*", + "@aries-framework/askar": "*", "@aries-framework/core": "*", + "@aries-framework/indy-sdk": "*", + "@aries-framework/indy-vdr": "*", + "@aries-framework/cheqd": "*", "@aries-framework/node": "*", - "@aries-framework/didcomm-v2": "*", "@types/figlet": "^1.5.4", - "@types/inquirer": "^8.1.3", + "@types/indy-sdk": "^1.16.26", + "@types/inquirer": "^8.2.6", "clear": "^0.1.0", - "commander": "^8.3.0", - "didcomm-node": "0.3.4", "figlet": "^1.5.2", "ts-node": "^10.4.0" } diff --git a/demo/src/Alice.ts b/demo/src/Alice.ts index e19a513c00..86b09fc6a1 100644 --- a/demo/src/Alice.ts +++ b/demo/src/Alice.ts @@ -8,7 +8,7 @@ export class Alice extends BaseAgent { public connectionRecordFaberId?: string public constructor(port: number, name: string) { - super(port, name) + super({ port, name, useLegacyIndySdk: true }) this.connected = false } @@ -46,17 +46,19 @@ export class Alice extends BaseAgent { } public async acceptCredentialOffer(credentialRecord: CredentialExchangeRecord) { + const linkSecretIds = await this.agent.modules.anoncreds.getLinkSecretIds() + if (linkSecretIds.length === 0) { + await this.agent.modules.anoncreds.createLinkSecret() + } + await this.agent.credentials.acceptOffer({ credentialRecordId: credentialRecord.id, }) } public async acceptProofRequest(proofRecord: ProofExchangeRecord) { - const requestedCredentials = await this.agent.proofs.autoSelectCredentialsForProofRequest({ + const requestedCredentials = await this.agent.proofs.selectCredentialsForRequest({ proofRecordId: proofRecord.id, - config: { - filterByPresentationPreview: true, - }, }) await this.agent.proofs.acceptRequest({ @@ -73,7 +75,7 @@ export class Alice extends BaseAgent { public async ping() { const connectionRecord = await this.getConnectionRecord() - await this.agent.connections.sendPing(connectionRecord.id) + await this.agent.connections.sendPing(connectionRecord.id, {}) } public async exit() { diff --git a/demo/src/AliceInquirer.ts b/demo/src/AliceInquirer.ts index 71a56736ed..9c68af67c8 100644 --- a/demo/src/AliceInquirer.ts +++ b/demo/src/AliceInquirer.ts @@ -86,7 +86,7 @@ export class AliceInquirer extends BaseInquirer { public async acceptProofRequest(proofRecord: ProofExchangeRecord) { const confirm = await prompt([this.inquireConfirmation(Title.ProofRequestTitle)]) if (confirm.options === ConfirmOptions.No) { - await this.alice.agent.proofs.declineRequest(proofRecord.id) + await this.alice.agent.proofs.declineRequest({ proofRecordId: proofRecord.id }) } else if (confirm.options === ConfirmOptions.Yes) { await this.alice.acceptProofRequest(proofRecord) } diff --git a/demo/src/BaseAgent.ts b/demo/src/BaseAgent.ts index 65d9d9dd96..c7b9902686 100644 --- a/demo/src/BaseAgent.ts +++ b/demo/src/BaseAgent.ts @@ -1,9 +1,43 @@ import type { InitConfig } from '@aries-framework/core' -import { Agent, AutoAcceptCredential, AutoAcceptProof, HttpOutboundTransport } from '@aries-framework/core' -import { DidCommV2Module } from '@aries-framework/didcomm-v2' +import { + AnonCredsCredentialFormatService, + AnonCredsModule, + AnonCredsProofFormatService, + LegacyIndyCredentialFormatService, + LegacyIndyProofFormatService, + V1CredentialProtocol, + V1ProofProtocol, +} from '@aries-framework/anoncreds' +import { AnonCredsRsModule } from '@aries-framework/anoncreds-rs' +import { AskarModule } from '@aries-framework/askar' +import { + CheqdAnonCredsRegistry, + CheqdDidRegistrar, + CheqdDidResolver, + CheqdModule, + CheqdModuleConfig, +} from '@aries-framework/cheqd' +import { + ConnectionsModule, + DidsModule, + V2ProofProtocol, + V2CredentialProtocol, + ProofsModule, + AutoAcceptProof, + AutoAcceptCredential, + CredentialsModule, + Agent, + HttpOutboundTransport, +} from '@aries-framework/core' +import { IndySdkAnonCredsRegistry, IndySdkModule, IndySdkSovDidResolver } from '@aries-framework/indy-sdk' +import { IndyVdrIndyDidResolver, IndyVdrAnonCredsRegistry, IndyVdrModule } from '@aries-framework/indy-vdr' import { agentDependencies, HttpInboundTransport } from '@aries-framework/node' -import * as didcomm from 'didcomm-node' +import { anoncreds } from '@hyperledger/anoncreds-nodejs' +import { ariesAskar } from '@hyperledger/aries-askar-nodejs' +import { indyVdr } from '@hyperledger/indy-vdr-nodejs' +import { randomUUID } from 'crypto' +import indySdk from 'indy-sdk' import { greenText } from './OutputClass' @@ -12,51 +46,165 @@ const bcovrin = `{"reqSignature":{},"txn":{"data":{"data":{"alias":"Node1","blsk {"reqSignature":{},"txn":{"data":{"data":{"alias":"Node3","blskey":"3WFpdbg7C5cnLYZwFZevJqhubkFALBfCBBok15GdrKMUhUjGsk3jV6QKj6MZgEubF7oqCafxNdkm7eswgA4sdKTRc82tLGzZBd6vNqU8dupzup6uYUf32KTHTPQbuUM8Yk4QFXjEf2Usu2TJcNkdgpyeUSX42u5LqdDDpNSWUK5deC5","blskey_pop":"QwDeb2CkNSx6r8QC8vGQK3GRv7Yndn84TGNijX8YXHPiagXajyfTjoR87rXUu4G4QLk2cF8NNyqWiYMus1623dELWwx57rLCFqGh7N4ZRbGDRP4fnVcaKg1BcUxQ866Ven4gw8y4N56S5HzxXNBZtLYmhGHvDtk6PFkFwCvxYrNYjh","client_ip":"138.197.138.255","client_port":9706,"node_ip":"138.197.138.255","node_port":9705,"services":["VALIDATOR"]},"dest":"DKVxG2fXXTU8yT5N7hGEbXB3dfdAnYv1JczDUHpmDxya"},"metadata":{"from":"4cU41vWW82ArfxJxHkzXPG"},"type":"0"},"txnMetadata":{"seqNo":3,"txnId":"7e9f355dffa78ed24668f0e0e369fd8c224076571c51e2ea8be5f26479edebe4"},"ver":"1"} {"reqSignature":{},"txn":{"data":{"data":{"alias":"Node4","blskey":"2zN3bHM1m4rLz54MJHYSwvqzPchYp8jkHswveCLAEJVcX6Mm1wHQD1SkPYMzUDTZvWvhuE6VNAkK3KxVeEmsanSmvjVkReDeBEMxeDaayjcZjFGPydyey1qxBHmTvAnBKoPydvuTAqx5f7YNNRAdeLmUi99gERUU7TD8KfAa6MpQ9bw","blskey_pop":"RPLagxaR5xdimFzwmzYnz4ZhWtYQEj8iR5ZU53T2gitPCyCHQneUn2Huc4oeLd2B2HzkGnjAff4hWTJT6C7qHYB1Mv2wU5iHHGFWkhnTX9WsEAbunJCV2qcaXScKj4tTfvdDKfLiVuU2av6hbsMztirRze7LvYBkRHV3tGwyCptsrP","client_ip":"138.197.138.255","client_port":9708,"node_ip":"138.197.138.255","node_port":9707,"services":["VALIDATOR"]},"dest":"4PS3EDQ3dW1tci1Bp6543CfuuebjFrg36kLAUcskGfaA"},"metadata":{"from":"TWwCRQRZ2ZHMJFn9TzLp7W"},"type":"0"},"txnMetadata":{"seqNo":4,"txnId":"aa5e817d7cc626170eca175822029339a444eb0ee8f0bd20d3b0b76e566fb008"},"ver":"1"}` +export const indyNetworkConfig = { + // Need unique network id as we will have multiple agent processes in the agent + id: randomUUID(), + genesisTransactions: bcovrin, + indyNamespace: 'bcovrin:test', + isProduction: false, + connectOnStartup: true, +} + +type DemoAgent = Agent> + export class BaseAgent { public port: number public name: string public config: InitConfig - public agent: Agent + public agent: DemoAgent + public useLegacyIndySdk: boolean - public constructor(port: number, name: string) { + public constructor({ + port, + name, + useLegacyIndySdk = false, + }: { + port: number + name: string + useLegacyIndySdk?: boolean + }) { this.name = name this.port = port - const config: InitConfig = { + const config = { label: name, walletConfig: { id: name, key: name, }, - publicDidSeed: '6b8b882e2618fa5d45ee7229ca880083', - indyLedgers: [ - { - genesisTransactions: bcovrin, - id: 'greenlights' + name, - indyNamespace: 'greenlights' + name, - isProduction: false, - }, - ], endpoints: [`http://localhost:${this.port}`], - autoAcceptConnections: true, - autoAcceptCredentials: AutoAcceptCredential.ContentApproved, - autoAcceptProofs: AutoAcceptProof.ContentApproved, } this.config = config - // Enable also didcomm-v2 module - const didCommV2Module = new DidCommV2Module({ didcomm }) - const modules = { didCommV2: didCommV2Module } - - this.agent = new Agent({ config, modules, dependencies: agentDependencies }) + this.useLegacyIndySdk = useLegacyIndySdk + this.agent = new Agent({ + config, + dependencies: agentDependencies, + modules: getAskarAnonCredsIndyModules(), + }) this.agent.registerInboundTransport(new HttpInboundTransport({ port })) this.agent.registerOutboundTransport(new HttpOutboundTransport()) } public async initializeAgent() { await this.agent.initialize() + console.log(greenText(`\nAgent ${this.name} created!\n`)) } } + +function getAskarAnonCredsIndyModules() { + const legacyIndyCredentialFormatService = new LegacyIndyCredentialFormatService() + const legacyIndyProofFormatService = new LegacyIndyProofFormatService() + + return { + connections: new ConnectionsModule({ + autoAcceptConnections: true, + }), + credentials: new CredentialsModule({ + autoAcceptCredentials: AutoAcceptCredential.ContentApproved, + credentialProtocols: [ + new V1CredentialProtocol({ + indyCredentialFormat: legacyIndyCredentialFormatService, + }), + new V2CredentialProtocol({ + credentialFormats: [legacyIndyCredentialFormatService, new AnonCredsCredentialFormatService()], + }), + ], + }), + proofs: new ProofsModule({ + autoAcceptProofs: AutoAcceptProof.ContentApproved, + proofProtocols: [ + new V1ProofProtocol({ + indyProofFormat: legacyIndyProofFormatService, + }), + new V2ProofProtocol({ + proofFormats: [legacyIndyProofFormatService, new AnonCredsProofFormatService()], + }), + ], + }), + anoncreds: new AnonCredsModule({ + registries: [new IndyVdrAnonCredsRegistry(), new CheqdAnonCredsRegistry()], + }), + anoncredsRs: new AnonCredsRsModule({ + anoncreds, + }), + indyVdr: new IndyVdrModule({ + indyVdr, + networks: [indyNetworkConfig], + }), + cheqd: new CheqdModule( + new CheqdModuleConfig({ + networks: [ + { + network: 'testnet', + cosmosPayerSeed: + 'robust across amount corn curve panther opera wish toe ring bleak empower wreck party abstract glad average muffin picnic jar squeeze annual long aunt', + }, + ], + }) + ), + dids: new DidsModule({ + resolvers: [new IndyVdrIndyDidResolver(), new CheqdDidResolver()], + registrars: [new CheqdDidRegistrar()], + }), + askar: new AskarModule({ + ariesAskar, + }), + } as const +} + +function getLegacyIndySdkModules() { + const legacyIndyCredentialFormatService = new LegacyIndyCredentialFormatService() + const legacyIndyProofFormatService = new LegacyIndyProofFormatService() + + return { + connections: new ConnectionsModule({ + autoAcceptConnections: true, + }), + credentials: new CredentialsModule({ + autoAcceptCredentials: AutoAcceptCredential.ContentApproved, + credentialProtocols: [ + new V1CredentialProtocol({ + indyCredentialFormat: legacyIndyCredentialFormatService, + }), + new V2CredentialProtocol({ + credentialFormats: [legacyIndyCredentialFormatService], + }), + ], + }), + proofs: new ProofsModule({ + autoAcceptProofs: AutoAcceptProof.ContentApproved, + proofProtocols: [ + new V1ProofProtocol({ + indyProofFormat: legacyIndyProofFormatService, + }), + new V2ProofProtocol({ + proofFormats: [legacyIndyProofFormatService], + }), + ], + }), + anoncreds: new AnonCredsModule({ + registries: [new IndySdkAnonCredsRegistry()], + }), + indySdk: new IndySdkModule({ + indySdk, + networks: [indyNetworkConfig], + }), + dids: new DidsModule({ + resolvers: [new IndySdkSovDidResolver()], + }), + } as const +} diff --git a/demo/src/Faber.ts b/demo/src/Faber.ts index 0c727cc952..a4a0d46e9e 100644 --- a/demo/src/Faber.ts +++ b/demo/src/Faber.ts @@ -1,26 +1,26 @@ +import type { RegisterCredentialDefinitionReturnStateFinished } from '@aries-framework/anoncreds' import type { ConnectionRecord, ConnectionStateChangedEvent } from '@aries-framework/core' -import type { CredDef, Schema } from 'indy-sdk' import type BottomBar from 'inquirer/lib/ui/bottom-bar' -import { - AttributeFilter, - ProofAttributeInfo, - utils, - V1CredentialPreview, - ConnectionEventTypes, -} from '@aries-framework/core' +import { KeyType, TypedArrayEncoder, utils, ConnectionEventTypes } from '@aries-framework/core' import { ui } from 'inquirer' -import { BaseAgent } from './BaseAgent' +import { BaseAgent, indyNetworkConfig } from './BaseAgent' import { Color, greenText, Output, purpleText, redText } from './OutputClass' +export enum RegistryOptions { + indy = 'did:indy', + cheqd = 'did:cheqd', +} + export class Faber extends BaseAgent { public outOfBandId?: string - public credentialDefinition?: CredDef + public credentialDefinition?: RegisterCredentialDefinitionReturnStateFinished + public anonCredsIssuerId?: string public ui: BottomBar public constructor(port: number, name: string) { - super(port, name) + super({ port, name, useLegacyIndySdk: true }) this.ui = new ui.BottomBar() } @@ -30,6 +30,28 @@ export class Faber extends BaseAgent { return faber } + public async importDid(registry: string) { + // NOTE: we assume the did is already registered on the ledger, we just store the private key in the wallet + // and store the existing did in the wallet + // indy did is based on private key (seed) + const unqualifiedIndyDid = '2jEvRuKmfBJTRa7QowDpNN' + const cheqdDid = 'did:cheqd:testnet:d37eba59-513d-42d3-8f9f-d1df0548b675' + const indyDid = `did:indy:${indyNetworkConfig.indyNamespace}:${unqualifiedIndyDid}` + + const did = registry === RegistryOptions.indy ? indyDid : cheqdDid + await this.agent.dids.import({ + did, + overwrite: true, + privateKeys: [ + { + keyType: KeyType.Ed25519, + privateKey: TypedArrayEncoder.fromString('afjdemoverysercure00000000000000'), + }, + ], + }) + this.anonCredsIssuerId = did + } + private async getConnectionRecord() { if (!this.outOfBandId) { throw Error(redText(Output.MissingConnectionRecord)) @@ -107,53 +129,87 @@ export class Faber extends BaseAgent { } private async registerSchema() { + if (!this.anonCredsIssuerId) { + throw new Error(redText('Missing anoncreds issuerId')) + } const schemaTemplate = { name: 'Faber College' + utils.uuid(), version: '1.0.0', - attributes: ['name', 'degree', 'date'], + attrNames: ['name', 'degree', 'date'], + issuerId: this.anonCredsIssuerId, } - this.printSchema(schemaTemplate.name, schemaTemplate.version, schemaTemplate.attributes) + this.printSchema(schemaTemplate.name, schemaTemplate.version, schemaTemplate.attrNames) this.ui.updateBottomBar(greenText('\nRegistering schema...\n', false)) - const schema = await this.agent.ledger.registerSchema(schemaTemplate) + + const { schemaState } = await this.agent.modules.anoncreds.registerSchema({ + schema: schemaTemplate, + options: {}, + }) + + if (schemaState.state !== 'finished') { + throw new Error( + `Error registering schema: ${schemaState.state === 'failed' ? schemaState.reason : 'Not Finished'}` + ) + } this.ui.updateBottomBar('\nSchema registered!\n') - return schema + return schemaState } - private async registerCredentialDefinition(schema: Schema) { + private async registerCredentialDefinition(schemaId: string) { + if (!this.anonCredsIssuerId) { + throw new Error(redText('Missing anoncreds issuerId')) + } + this.ui.updateBottomBar('\nRegistering credential definition...\n') - this.credentialDefinition = await this.agent.ledger.registerCredentialDefinition({ - schema, - tag: 'latest', - supportRevocation: false, + const { credentialDefinitionState } = await this.agent.modules.anoncreds.registerCredentialDefinition({ + credentialDefinition: { + schemaId, + issuerId: this.anonCredsIssuerId, + tag: 'latest', + }, + options: {}, }) + + if (credentialDefinitionState.state !== 'finished') { + throw new Error( + `Error registering credential definition: ${ + credentialDefinitionState.state === 'failed' ? credentialDefinitionState.reason : 'Not Finished' + }}` + ) + } + + this.credentialDefinition = credentialDefinitionState this.ui.updateBottomBar('\nCredential definition registered!!\n') return this.credentialDefinition } - private getCredentialPreview() { - const credentialPreview = V1CredentialPreview.fromRecord({ - name: 'Alice Smith', - degree: 'Computer Science', - date: '01/01/2022', - }) - return credentialPreview - } - public async issueCredential() { const schema = await this.registerSchema() - const credDef = await this.registerCredentialDefinition(schema) - const credentialPreview = this.getCredentialPreview() + const credentialDefinition = await this.registerCredentialDefinition(schema.schemaId) const connectionRecord = await this.getConnectionRecord() this.ui.updateBottomBar('\nSending credential offer...\n') await this.agent.credentials.offerCredential({ connectionId: connectionRecord.id, - protocolVersion: 'v1', + protocolVersion: 'v2', credentialFormats: { - indy: { - attributes: credentialPreview.attributes, - credentialDefinitionId: credDef.id, + anoncreds: { + attributes: [ + { + name: 'name', + value: 'Alice Smith', + }, + { + name: 'degree', + value: 'Computer Science', + }, + { + name: 'date', + value: '01/01/2022', + }, + ], + credentialDefinitionId: credentialDefinition.credentialDefinitionId, }, }, }) @@ -170,15 +226,16 @@ export class Faber extends BaseAgent { private async newProofAttribute() { await this.printProofFlow(greenText(`Creating new proof attribute for 'name' ...\n`)) const proofAttribute = { - name: new ProofAttributeInfo({ + name: { name: 'name', restrictions: [ - new AttributeFilter({ - credentialDefinitionId: this.credentialDefinition?.id, - }), + { + cred_def_id: this.credentialDefinition?.credentialDefinitionId, + }, ], - }), + }, } + return proofAttribute } @@ -188,14 +245,13 @@ export class Faber extends BaseAgent { await this.printProofFlow(greenText('\nRequesting proof...\n', false)) await this.agent.proofs.requestProof({ - protocolVersion: 'v1', + protocolVersion: 'v2', connectionId: connectionRecord.id, proofFormats: { - indy: { + anoncreds: { name: 'proof-request', version: '1.0', - nonce: '1298236324864', - requestedAttributes: proofAttribute, + requested_attributes: proofAttribute, }, }, }) diff --git a/demo/src/FaberInquirer.ts b/demo/src/FaberInquirer.ts index ae51f5bc43..5d358022fd 100644 --- a/demo/src/FaberInquirer.ts +++ b/demo/src/FaberInquirer.ts @@ -3,7 +3,7 @@ import { textSync } from 'figlet' import { prompt } from 'inquirer' import { BaseInquirer, ConfirmOptions } from './BaseInquirer' -import { Faber } from './Faber' +import { Faber, RegistryOptions } from './Faber' import { Listener } from './Listener' import { Title } from './OutputClass' @@ -92,6 +92,8 @@ export class FaberInquirer extends BaseInquirer { } public async credential() { + const registry = await prompt([this.inquireOptions([RegistryOptions.indy, RegistryOptions.cheqd])]) + await this.faber.importDid(registry.options) await this.faber.issueCredential() const title = 'Is the credential offer accepted?' await this.listener.newAcceptedPrompt(title, this) diff --git a/demo/src/Listener.ts b/demo/src/Listener.ts index 251de5c5f1..4a4c7e383a 100644 --- a/demo/src/Listener.ts +++ b/demo/src/Listener.ts @@ -7,8 +7,10 @@ import type { BasicMessageStateChangedEvent, CredentialExchangeRecord, CredentialStateChangedEvent, - PingReceivedEvent, - PingResponseReceivedEvent, + TrustPingReceivedEvent, + TrustPingResponseReceivedEvent, + V2TrustPingReceivedEvent, + V2TrustPingResponseReceivedEvent, ProofExchangeRecord, ProofStateChangedEvent, } from '@aries-framework/core' @@ -82,12 +84,30 @@ export class Listener { } public pingListener(agent: Agent, name: string) { - agent.events.on(TrustPingEventTypes.PingReceived, async (event: PingReceivedEvent) => { - this.ui.updateBottomBar(purpleText(`\n${name} received ping message from ${event.payload.from}\n`)) + agent.events.on(TrustPingEventTypes.TrustPingReceivedEvent, async (event: TrustPingReceivedEvent) => { + this.ui.updateBottomBar( + purpleText(`\n${name} received ping message from ${event.payload.connectionRecord?.theirDid}\n`) + ) }) - agent.events.on(TrustPingEventTypes.PingResponseReceived, async (event: PingResponseReceivedEvent) => { - this.ui.updateBottomBar(purpleText(`\n${name} received ping response message from ${event.payload.from}\n`)) + agent.events.on( + TrustPingEventTypes.TrustPingResponseReceivedEvent, + async (event: TrustPingResponseReceivedEvent) => { + this.ui.updateBottomBar( + purpleText(`\n${name} received ping response message from ${event.payload.connectionRecord?.theirDid}\n`) + ) + } + ) + agent.events.on(TrustPingEventTypes.V2TrustPingReceivedEvent, async (event: V2TrustPingReceivedEvent) => { + this.ui.updateBottomBar(purpleText(`\n${name} received ping message from ${event.payload.message.from}\n`)) }) + agent.events.on( + TrustPingEventTypes.V2TrustPingResponseReceivedEvent, + async (event: V2TrustPingResponseReceivedEvent) => { + this.ui.updateBottomBar( + purpleText(`\n${name} received ping response message from ${event.payload.message.from}\n`) + ) + } + ) } private async newProofRequestPrompt(proofRecord: ProofExchangeRecord, aliceInquirer: AliceInquirer) { diff --git a/jest.config.base.ts b/jest.config.base.ts index 5848093da6..09cb2d5760 100644 --- a/jest.config.base.ts +++ b/jest.config.base.ts @@ -3,9 +3,6 @@ import type { Config } from '@jest/types' const config: Config.InitialOptions = { preset: 'ts-jest', testEnvironment: 'node', - coveragePathIgnorePatterns: ['/build/', '/node_modules/', '/__tests__/', 'tests'], - coverageDirectory: '/coverage/', - verbose: true, testMatch: ['**/?(*.)+(spec|test).[tj]s?(x)'], moduleNameMapper: { '@aries-framework/(.+)': [ @@ -14,10 +11,13 @@ const config: Config.InitialOptions = { '/packages/$1/src', ], }, - globals: { - 'ts-jest': { - isolatedModules: true, - }, + transform: { + '^.+\\.tsx?$': [ + 'ts-jest', + { + isolatedModules: true, + }, + ], }, } diff --git a/jest.config.ts b/jest.config.ts index c4f6c766dc..49fdadb66e 100644 --- a/jest.config.ts +++ b/jest.config.ts @@ -5,6 +5,9 @@ import base from './jest.config.base' const config: Config.InitialOptions = { ...base, roots: [''], + verbose: true, + coveragePathIgnorePatterns: ['/build/', '/node_modules/', '/__tests__/', 'tests'], + coverageDirectory: '/coverage/', projects: [ '/packages/*/jest.config.ts', '/tests/jest.config.ts', diff --git a/lerna.json b/lerna.json index 56d3a038a2..b8106b0a8a 100644 --- a/lerna.json +++ b/lerna.json @@ -1,6 +1,6 @@ { "packages": ["packages/*"], - "version": "0.3.2", + "version": "0.3.3", "useWorkspaces": true, "npmClient": "yarn", "command": { diff --git a/package.json b/package.json index 139ef64ae7..222fb1d497 100644 --- a/package.json +++ b/package.json @@ -23,48 +23,46 @@ "test": "jest", "lint": "eslint --ignore-path .gitignore .", "validate": "yarn lint && yarn check-types && yarn check-format", - "prepare": "husky install", "run-mediator": "ts-node ./samples/mediator.ts", "next-version-bump": "ts-node ./scripts/get-next-bump.ts" }, "devDependencies": { "@types/cors": "^2.8.10", - "@types/eslint": "^7.2.13", + "@types/eslint": "^8.21.2", "@types/express": "^4.17.13", - "@types/jest": "^26.0.23", - "@types/node": "^15.14.4", - "@types/uuid": "^8.3.1", + "@types/jest": "^29.5.0", + "@types/node": "^16.11.7", + "@types/uuid": "^9.0.1", "@types/varint": "^6.0.0", - "@types/ws": "^7.4.6", - "@typescript-eslint/eslint-plugin": "^4.26.1", - "@typescript-eslint/parser": "^4.26.1", + "@types/ws": "^8.5.4", + "@typescript-eslint/eslint-plugin": "^5.48.1", + "@typescript-eslint/parser": "^5.48.1", "conventional-changelog-conventionalcommits": "^5.0.0", "conventional-recommended-bump": "^6.1.0", "cors": "^2.8.5", - "dotenv": "^10.0.0", - "eslint": "^7.28.0", + "eslint": "^8.36.0", "eslint-config-prettier": "^8.3.0", - "eslint-import-resolver-typescript": "^2.4.0", + "eslint-import-resolver-typescript": "^3.5.3", "eslint-plugin-import": "^2.23.4", - "eslint-plugin-prettier": "^3.4.0", + "eslint-plugin-prettier": "^4.2.1", + "eslint-plugin-workspaces": "^0.8.0", "express": "^4.17.1", - "husky": "^7.0.1", - "indy-sdk": "^1.16.0-dev-1636", - "jest": "^27.0.4", - "lerna": "^4.0.0", + "indy-sdk": "^1.16.0-dev-1655", + "jest": "^29.5.0", + "lerna": "^6.5.1", "prettier": "^2.3.1", - "rxjs": "^7.2.0", - "ts-jest": "^27.0.3", + "rxjs": "^7.8.0", + "ts-jest": "^29.0.5", "ts-node": "^10.0.0", - "tsconfig-paths": "^3.9.0", + "tsconfig-paths": "^4.1.2", "tsyringe": "^4.7.0", - "typescript": "~4.3.0", - "ws": "^7.4.6" + "typescript": "~4.9.5", + "ws": "^8.13.0" }, "resolutions": { - "@types/node": "^15.14.4" + "@types/node": "^16.11.7" }, "engines": { - "node": ">= 14" + "node": "^16 || ^18" } } diff --git a/packages/action-menu/CHANGELOG.md b/packages/action-menu/CHANGELOG.md index c3167eb95d..a18e55cd11 100644 --- a/packages/action-menu/CHANGELOG.md +++ b/packages/action-menu/CHANGELOG.md @@ -3,6 +3,16 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [0.3.3](https://github.com/hyperledger/aries-framework-javascript/compare/v0.3.2...v0.3.3) (2023-01-18) + +### Bug Fixes + +- fix typing issues with typescript 4.9 ([#1214](https://github.com/hyperledger/aries-framework-javascript/issues/1214)) ([087980f](https://github.com/hyperledger/aries-framework-javascript/commit/087980f1adf3ee0bc434ca9782243a62c6124444)) + +### Features + +- **indy-sdk:** add indy-sdk package ([#1200](https://github.com/hyperledger/aries-framework-javascript/issues/1200)) ([9933b35](https://github.com/hyperledger/aries-framework-javascript/commit/9933b35a6aa4524caef8a885e71b742cd0d7186b)) + ## [0.3.2](https://github.com/hyperledger/aries-framework-javascript/compare/v0.3.1...v0.3.2) (2023-01-04) **Note:** Version bump only for package @aries-framework/action-menu diff --git a/packages/action-menu/README.md b/packages/action-menu/README.md index ffd98caf55..c47c6a4ac7 100644 --- a/packages/action-menu/README.md +++ b/packages/action-menu/README.md @@ -6,7 +6,7 @@ height="250px" />

-

Aries Framework JavaScript Action Menu Plugin

+

Aries Framework JavaScript Action Menu Module


-Action Menu plugin for [Aries Framework JavaScript](https://github.com/hyperledger/aries-framework-javascript.git). Implements [Aries RFC 0509](https://github.com/hyperledger/aries-rfcs/blob/1795d5c2d36f664f88f5e8045042ace8e573808c/features/0509-action-menu/README.md). +Action Menu module for [Aries Framework JavaScript](https://github.com/hyperledger/aries-framework-javascript.git). Implements [Aries RFC 0509](https://github.com/hyperledger/aries-rfcs/blob/1795d5c2d36f664f88f5e8045042ace8e573808c/features/0509-action-menu/README.md). ### Installation @@ -38,7 +38,7 @@ Make sure you have set up the correct version of Aries Framework JavaScript acco npm info "@aries-framework/action-menu" peerDependencies ``` -Then add the action-menu plugin to your project. +Then add the action-menu module to your project. ```sh yarn add @aries-framework/action-menu @@ -46,7 +46,7 @@ yarn add @aries-framework/action-menu ### Quick start -In order for this plugin to work, we have to inject it into the agent to access agent functionality. See the example for more information. +In order for this module to work, we have to inject it into the agent to access agent functionality. See the example for more information. ### Example of usage diff --git a/packages/action-menu/jest.config.ts b/packages/action-menu/jest.config.ts index 55c67d70a6..93c0197296 100644 --- a/packages/action-menu/jest.config.ts +++ b/packages/action-menu/jest.config.ts @@ -6,7 +6,6 @@ import packageJson from './package.json' const config: Config.InitialOptions = { ...base, - name: packageJson.name, displayName: packageJson.name, setupFilesAfterEnv: ['./tests/setup.ts'], } diff --git a/packages/action-menu/package.json b/packages/action-menu/package.json index 795c85f463..1a07638d9b 100644 --- a/packages/action-menu/package.json +++ b/packages/action-menu/package.json @@ -2,7 +2,7 @@ "name": "@aries-framework/action-menu", "main": "build/index", "types": "build/index", - "version": "0.3.2", + "version": "0.3.3", "files": [ "build" ], @@ -18,24 +18,20 @@ }, "scripts": { "build": "yarn run clean && yarn run compile", - "clean": "rimraf -rf ./build", + "clean": "rimraf ./build", "compile": "tsc -p tsconfig.build.json", "prepublishOnly": "yarn run build", "test": "jest" }, "dependencies": { + "@aries-framework/core": "0.3.3", "class-transformer": "0.5.1", - "class-validator": "0.13.1", + "class-validator": "0.14.0", "rxjs": "^7.2.0" }, - "peerDependencies": { - "@aries-framework/core": "0.2.5" - }, "devDependencies": { - "@aries-framework/core": "0.3.2", - "@aries-framework/node": "0.3.2", "reflect-metadata": "^0.1.13", - "rimraf": "~3.0.2", - "typescript": "~4.3.0" + "rimraf": "^4.4.0", + "typescript": "~4.9.5" } } diff --git a/packages/action-menu/src/ActionMenuApi.ts b/packages/action-menu/src/ActionMenuApi.ts index bb6f3cd4f3..6abe1b3fac 100644 --- a/packages/action-menu/src/ActionMenuApi.ts +++ b/packages/action-menu/src/ActionMenuApi.ts @@ -10,7 +10,6 @@ import { AgentContext, AriesFrameworkError, ConnectionService, - Dispatcher, MessageSender, OutboundMessageContext, injectable, @@ -36,7 +35,6 @@ export class ActionMenuApi { private agentContext: AgentContext public constructor( - dispatcher: Dispatcher, connectionService: ConnectionService, messageSender: MessageSender, actionMenuService: ActionMenuService, @@ -46,7 +44,13 @@ export class ActionMenuApi { this.messageSender = messageSender this.actionMenuService = actionMenuService this.agentContext = agentContext - this.registerMessageHandlers(dispatcher) + + this.agentContext.dependencyManager.registerMessageHandlers([ + new ActionMenuProblemReportHandler(this.actionMenuService), + new MenuMessageHandler(this.actionMenuService), + new MenuRequestMessageHandler(this.actionMenuService), + new PerformMessageHandler(this.actionMenuService), + ]) } /** @@ -160,11 +164,4 @@ export class ActionMenuApi { return actionMenuRecord ? await this.actionMenuService.clearMenu(this.agentContext, { actionMenuRecord }) : null } - - private registerMessageHandlers(dispatcher: Dispatcher) { - dispatcher.registerMessageHandler(new ActionMenuProblemReportHandler(this.actionMenuService)) - dispatcher.registerMessageHandler(new MenuMessageHandler(this.actionMenuService)) - dispatcher.registerMessageHandler(new MenuRequestMessageHandler(this.actionMenuService)) - dispatcher.registerMessageHandler(new PerformMessageHandler(this.actionMenuService)) - } } diff --git a/packages/action-menu/src/index.ts b/packages/action-menu/src/index.ts index 204d9dc359..97c9a70ea7 100644 --- a/packages/action-menu/src/index.ts +++ b/packages/action-menu/src/index.ts @@ -5,4 +5,5 @@ export * from './ActionMenuEvents' export * from './ActionMenuRole' export * from './ActionMenuState' export * from './models' -export * from './repository/ActionMenuRecord' +export * from './repository' +export * from './messages' diff --git a/packages/action-menu/src/services/ActionMenuService.ts b/packages/action-menu/src/services/ActionMenuService.ts index 89c27f54a4..42da57e3ad 100644 --- a/packages/action-menu/src/services/ActionMenuService.ts +++ b/packages/action-menu/src/services/ActionMenuService.ts @@ -1,5 +1,3 @@ -import type { ActionMenuStateChangedEvent } from '../ActionMenuEvents' -import type { ActionMenuProblemReportMessage } from '../messages' import type { ClearMenuOptions, CreateMenuOptions, @@ -7,6 +5,8 @@ import type { CreateRequestOptions, FindMenuOptions, } from './ActionMenuServiceOptions' +import type { ActionMenuStateChangedEvent } from '../ActionMenuEvents' +import type { ActionMenuProblemReportMessage } from '../messages' import type { AgentContext, InboundMessageContext, Logger, Query } from '@aries-framework/core' import { AgentConfig, EventEmitter, AriesFrameworkError, injectable, JsonTransformer } from '@aries-framework/core' @@ -63,7 +63,7 @@ export class ActionMenuService { connectionId: options.connection.id, role: ActionMenuRole.Requester, state: ActionMenuState.AwaitingRootMenu, - threadId: menuRequestMessage.id, + threadId: menuRequestMessage.threadId, }) await this.actionMenuRepository.save(agentContext, actionMenuRecord) @@ -102,7 +102,7 @@ export class ActionMenuService { connectionId: connection.id, role: ActionMenuRole.Responder, state: ActionMenuState.PreparingRootMenu, - threadId: menuRequestMessage.id, + threadId: menuRequestMessage.threadId, }) await this.actionMenuRepository.save(agentContext, actionMenuRecord) @@ -157,7 +157,7 @@ export class ActionMenuService { role: ActionMenuRole.Responder, state: ActionMenuState.AwaitingSelection, menu: options.menu, - threadId: menuMessage.id, + threadId: menuMessage.threadId, }) await this.actionMenuRepository.save(agentContext, actionMenuRecord) @@ -203,7 +203,7 @@ export class ActionMenuService { connectionId: connection.id, role: ActionMenuRole.Requester, state: ActionMenuState.PreparingSelection, - threadId: menuMessage.id, + threadId: menuMessage.threadId, menu: new ActionMenu({ title: menuMessage.title, description: menuMessage.description, diff --git a/packages/action-menu/tests/action-menu.e2e.test.ts b/packages/action-menu/tests/action-menu.e2e.test.ts index b15524fd93..a32b13df49 100644 --- a/packages/action-menu/tests/action-menu.e2e.test.ts +++ b/packages/action-menu/tests/action-menu.e2e.test.ts @@ -1,13 +1,9 @@ -import type { SubjectMessage } from '../../../tests/transport/SubjectInboundTransport' import type { ConnectionRecord } from '@aries-framework/core' import { Agent } from '@aries-framework/core' -import { Subject } from 'rxjs' -import { SubjectInboundTransport } from '../../../tests/transport/SubjectInboundTransport' -import { SubjectOutboundTransport } from '../../../tests/transport/SubjectOutboundTransport' -import { getAgentOptions, makeConnection } from '../../core/tests/helpers' -import testLogger from '../../core/tests/logger' +import { getAgentOptions, makeConnection, testLogger, setupSubjectTransports, indySdk } from '../../core/tests' +import { IndySdkModule } from '../../indy-sdk/src' import { waitForActionMenuRecord } from './helpers' @@ -19,14 +15,19 @@ import { ActionMenuState, } from '@aries-framework/action-menu' +const modules = { + actionMenu: new ActionMenuModule(), + indySdk: new IndySdkModule({ + indySdk, + }), +} + const faberAgentOptions = getAgentOptions( 'Faber Action Menu', { endpoints: ['rxjs:faber'], }, - { - actionMenu: new ActionMenuModule(), - } + modules ) const aliceAgentOptions = getAgentOptions( @@ -34,18 +35,12 @@ const aliceAgentOptions = getAgentOptions( { endpoints: ['rxjs:alice'], }, - { - actionMenu: new ActionMenuModule(), - } + modules ) describe('Action Menu', () => { - let faberAgent: Agent<{ - actionMenu: ActionMenuModule - }> - let aliceAgent: Agent<{ - actionMenu: ActionMenuModule - }> + let faberAgent: Agent + let aliceAgent: Agent let faberConnection: ConnectionRecord let aliceConnection: ConnectionRecord @@ -84,21 +79,12 @@ describe('Action Menu', () => { }) beforeEach(async () => { - const faberMessages = new Subject() - const aliceMessages = new Subject() - const subjectMap = { - 'rxjs:faber': faberMessages, - 'rxjs:alice': aliceMessages, - } - faberAgent = new Agent(faberAgentOptions) - faberAgent.registerInboundTransport(new SubjectInboundTransport(faberMessages)) - faberAgent.registerOutboundTransport(new SubjectOutboundTransport(subjectMap)) - await faberAgent.initialize() - aliceAgent = new Agent(aliceAgentOptions) - aliceAgent.registerInboundTransport(new SubjectInboundTransport(aliceMessages)) - aliceAgent.registerOutboundTransport(new SubjectOutboundTransport(subjectMap)) + + setupSubjectTransports([faberAgent, aliceAgent]) + + await faberAgent.initialize() await aliceAgent.initialize() ;[aliceConnection, faberConnection] = await makeConnection(aliceAgent, faberAgent) }) diff --git a/packages/action-menu/tests/setup.ts b/packages/action-menu/tests/setup.ts index 4955aeb601..78143033f2 100644 --- a/packages/action-menu/tests/setup.ts +++ b/packages/action-menu/tests/setup.ts @@ -1,3 +1,3 @@ import 'reflect-metadata' -jest.setTimeout(20000) +jest.setTimeout(120000) diff --git a/packages/anoncreds-rs/README.md b/packages/anoncreds-rs/README.md new file mode 100644 index 0000000000..87f28670e7 --- /dev/null +++ b/packages/anoncreds-rs/README.md @@ -0,0 +1,31 @@ +

+
+ Hyperledger Aries logo +

+

Aries Framework JavaScript AnonCreds RS Module

+

+ License + typescript + @aries-framework/anoncreds-rs version + +

+
+ +AnonCreds RS module for [Aries Framework JavaScript](https://github.com/hyperledger/aries-framework-javascript.git). diff --git a/packages/didcomm-v2/jest.config.ts b/packages/anoncreds-rs/jest.config.ts similarity index 91% rename from packages/didcomm-v2/jest.config.ts rename to packages/anoncreds-rs/jest.config.ts index 55c67d70a6..93c0197296 100644 --- a/packages/didcomm-v2/jest.config.ts +++ b/packages/anoncreds-rs/jest.config.ts @@ -6,7 +6,6 @@ import packageJson from './package.json' const config: Config.InitialOptions = { ...base, - name: packageJson.name, displayName: packageJson.name, setupFilesAfterEnv: ['./tests/setup.ts'], } diff --git a/packages/anoncreds-rs/package.json b/packages/anoncreds-rs/package.json new file mode 100644 index 0000000000..633ea1f08e --- /dev/null +++ b/packages/anoncreds-rs/package.json @@ -0,0 +1,41 @@ +{ + "name": "@aries-framework/anoncreds-rs", + "main": "build/index", + "types": "build/index", + "version": "0.3.3", + "files": [ + "build" + ], + "license": "Apache-2.0", + "publishConfig": { + "access": "public" + }, + "homepage": "https://github.com/hyperledger/aries-framework-javascript/tree/main/packages/anoncreds-rs", + "repository": { + "type": "git", + "url": "https://github.com/hyperledger/aries-framework-javascript", + "directory": "packages/anoncreds-rs" + }, + "scripts": { + "build": "yarn run clean && yarn run compile", + "clean": "rimraf ./build", + "compile": "tsc -p tsconfig.build.json", + "prepublishOnly": "yarn run build", + "test": "jest" + }, + "dependencies": { + "@aries-framework/core": "0.3.3", + "@aries-framework/anoncreds": "0.3.3", + "@hyperledger/anoncreds-shared": "^0.1.0-dev.15", + "class-transformer": "^0.5.1", + "class-validator": "0.14.0", + "rxjs": "^7.2.0", + "tsyringe": "^4.7.0" + }, + "devDependencies": { + "@hyperledger/anoncreds-nodejs": "^0.1.0-dev.15", + "reflect-metadata": "^0.1.13", + "rimraf": "^4.4.0", + "typescript": "~4.9.5" + } +} diff --git a/packages/anoncreds-rs/src/AnonCredsRsModule.ts b/packages/anoncreds-rs/src/AnonCredsRsModule.ts new file mode 100644 index 0000000000..cca3f465fd --- /dev/null +++ b/packages/anoncreds-rs/src/AnonCredsRsModule.ts @@ -0,0 +1,28 @@ +import type { AnonCredsRsModuleConfigOptions } from './AnonCredsRsModuleConfig' +import type { DependencyManager, Module } from '@aries-framework/core' + +import { + AnonCredsHolderServiceSymbol, + AnonCredsIssuerServiceSymbol, + AnonCredsVerifierServiceSymbol, +} from '@aries-framework/anoncreds' + +import { AnonCredsRsModuleConfig } from './AnonCredsRsModuleConfig' +import { AnonCredsRsHolderService, AnonCredsRsIssuerService, AnonCredsRsVerifierService } from './services' + +export class AnonCredsRsModule implements Module { + public readonly config: AnonCredsRsModuleConfig + + public constructor(config: AnonCredsRsModuleConfigOptions) { + this.config = new AnonCredsRsModuleConfig(config) + } + + public register(dependencyManager: DependencyManager) { + dependencyManager.registerInstance(AnonCredsRsModuleConfig, this.config) + + // Register services + dependencyManager.registerSingleton(AnonCredsHolderServiceSymbol, AnonCredsRsHolderService) + dependencyManager.registerSingleton(AnonCredsIssuerServiceSymbol, AnonCredsRsIssuerService) + dependencyManager.registerSingleton(AnonCredsVerifierServiceSymbol, AnonCredsRsVerifierService) + } +} diff --git a/packages/anoncreds-rs/src/AnonCredsRsModuleConfig.ts b/packages/anoncreds-rs/src/AnonCredsRsModuleConfig.ts new file mode 100644 index 0000000000..2d676b4d52 --- /dev/null +++ b/packages/anoncreds-rs/src/AnonCredsRsModuleConfig.ts @@ -0,0 +1,58 @@ +import type { Anoncreds } from '@hyperledger/anoncreds-shared' + +/** + * @public + * AnonCredsRsModuleConfigOptions defines the interface for the options of the AnonCredsRsModuleConfig class. + */ +export interface AnonCredsRsModuleConfigOptions { + /** + * + * ## Node.JS + * + * ```ts + * import { anoncreds } from '@hyperledger/anoncreds-nodejs' + * + * const agent = new Agent({ + * config: {}, + * dependencies: agentDependencies, + * modules: { + * anoncredsRs: new AnoncredsRsModule({ + * anoncreds, + * }) + * } + * }) + * ``` + * + * ## React Native + * + * ```ts + * import { anoncreds } from '@hyperledger/anoncreds-react-native' + * + * const agent = new Agent({ + * config: {}, + * dependencies: agentDependencies, + * modules: { + * anoncredsRs: new AnoncredsRsModule({ + * anoncreds, + * }) + * } + * }) + * ``` + */ + anoncreds: Anoncreds +} + +/** + * @public + */ +export class AnonCredsRsModuleConfig { + private options: AnonCredsRsModuleConfigOptions + + public constructor(options: AnonCredsRsModuleConfigOptions) { + this.options = options + } + + public get anoncreds() { + return this.options.anoncreds + } +} diff --git a/packages/anoncreds-rs/src/errors/AnonCredsRsError.ts b/packages/anoncreds-rs/src/errors/AnonCredsRsError.ts new file mode 100644 index 0000000000..e8cdf3023d --- /dev/null +++ b/packages/anoncreds-rs/src/errors/AnonCredsRsError.ts @@ -0,0 +1,7 @@ +import { AriesFrameworkError } from '@aries-framework/core' + +export class AnonCredsRsError extends AriesFrameworkError { + public constructor(message: string, { cause }: { cause?: Error } = {}) { + super(message, { cause }) + } +} diff --git a/packages/anoncreds-rs/src/index.ts b/packages/anoncreds-rs/src/index.ts new file mode 100644 index 0000000000..5fdd9486c7 --- /dev/null +++ b/packages/anoncreds-rs/src/index.ts @@ -0,0 +1,5 @@ +// Services +export * from './services' + +// Module +export { AnonCredsRsModule } from './AnonCredsRsModule' diff --git a/packages/anoncreds-rs/src/services/AnonCredsRsHolderService.ts b/packages/anoncreds-rs/src/services/AnonCredsRsHolderService.ts new file mode 100644 index 0000000000..20b9c5a74d --- /dev/null +++ b/packages/anoncreds-rs/src/services/AnonCredsRsHolderService.ts @@ -0,0 +1,464 @@ +import type { + AnonCredsCredential, + AnonCredsCredentialInfo, + AnonCredsCredentialRequest, + AnonCredsCredentialRequestMetadata, + AnonCredsHolderService, + AnonCredsProof, + AnonCredsProofRequestRestriction, + AnonCredsRequestedAttributeMatch, + AnonCredsRequestedPredicateMatch, + CreateCredentialRequestOptions, + CreateCredentialRequestReturn, + CreateLinkSecretOptions, + CreateLinkSecretReturn, + CreateProofOptions, + GetCredentialOptions, + GetCredentialsForProofRequestOptions, + GetCredentialsForProofRequestReturn, + GetCredentialsOptions, + StoreCredentialOptions, +} from '@aries-framework/anoncreds' +import type { AgentContext, Query, SimpleQuery } from '@aries-framework/core' +import type { + CredentialEntry, + CredentialProve, + CredentialRequestMetadata, + JsonObject, +} from '@hyperledger/anoncreds-shared' + +import { + AnonCredsCredentialRecord, + AnonCredsCredentialRepository, + AnonCredsLinkSecretRepository, + AnonCredsRestrictionWrapper, + unqualifiedCredentialDefinitionIdRegex, + AnonCredsRegistryService, +} from '@aries-framework/anoncreds' +import { AriesFrameworkError, JsonTransformer, TypedArrayEncoder, injectable, utils } from '@aries-framework/core' +import { + Credential, + CredentialRequest, + CredentialRevocationState, + LinkSecret, + Presentation, + RevocationRegistryDefinition, + RevocationStatusList, + anoncreds, +} from '@hyperledger/anoncreds-shared' + +import { AnonCredsRsError } from '../errors/AnonCredsRsError' + +@injectable() +export class AnonCredsRsHolderService implements AnonCredsHolderService { + public async createLinkSecret( + agentContext: AgentContext, + options?: CreateLinkSecretOptions + ): Promise { + return { + linkSecretId: options?.linkSecretId ?? utils.uuid(), + linkSecretValue: LinkSecret.create(), + } + } + + public async createProof(agentContext: AgentContext, options: CreateProofOptions): Promise { + const { credentialDefinitions, proofRequest, selectedCredentials, schemas } = options + + let presentation: Presentation | undefined + try { + const rsCredentialDefinitions: Record = {} + for (const credDefId in credentialDefinitions) { + rsCredentialDefinitions[credDefId] = credentialDefinitions[credDefId] as unknown as JsonObject + } + + const rsSchemas: Record = {} + for (const schemaId in schemas) { + rsSchemas[schemaId] = schemas[schemaId] as unknown as JsonObject + } + + const credentialRepository = agentContext.dependencyManager.resolve(AnonCredsCredentialRepository) + + // Cache retrieved credentials in order to minimize storage calls + const retrievedCredentials = new Map() + + const credentialEntryFromAttribute = async ( + attribute: AnonCredsRequestedAttributeMatch | AnonCredsRequestedPredicateMatch + ): Promise<{ linkSecretId: string; credentialEntry: CredentialEntry }> => { + let credentialRecord = retrievedCredentials.get(attribute.credentialId) + if (!credentialRecord) { + credentialRecord = await credentialRepository.getByCredentialId(agentContext, attribute.credentialId) + retrievedCredentials.set(attribute.credentialId, credentialRecord) + } + + const revocationRegistryDefinitionId = credentialRecord.credential.rev_reg_id + const revocationRegistryIndex = credentialRecord.credentialRevocationId + + // TODO: Check if credential has a revocation registry id (check response from anoncreds-rs API, as it is + // sending back a mandatory string in Credential.revocationRegistryId) + const timestamp = attribute.timestamp + + let revocationState: CredentialRevocationState | undefined + let revocationRegistryDefinition: RevocationRegistryDefinition | undefined + try { + if (timestamp && revocationRegistryIndex && revocationRegistryDefinitionId) { + if (!options.revocationRegistries[revocationRegistryDefinitionId]) { + throw new AnonCredsRsError(`Revocation Registry ${revocationRegistryDefinitionId} not found`) + } + + const { definition, revocationStatusLists, tailsFilePath } = + options.revocationRegistries[revocationRegistryDefinitionId] + + // Extract revocation status list for the given timestamp + const revocationStatusList = revocationStatusLists[timestamp] + if (!revocationStatusList) { + throw new AriesFrameworkError( + `Revocation status list for revocation registry ${revocationRegistryDefinitionId} and timestamp ${timestamp} not found in revocation status lists. All revocation status lists must be present.` + ) + } + + revocationRegistryDefinition = RevocationRegistryDefinition.fromJson(definition as unknown as JsonObject) + revocationState = CredentialRevocationState.create({ + revocationRegistryIndex: Number(revocationRegistryIndex), + revocationRegistryDefinition, + tailsPath: tailsFilePath, + revocationStatusList: RevocationStatusList.fromJson(revocationStatusList as unknown as JsonObject), + }) + } + return { + linkSecretId: credentialRecord.linkSecretId, + credentialEntry: { + credential: credentialRecord.credential as unknown as JsonObject, + revocationState: revocationState?.toJson(), + timestamp, + }, + } + } finally { + revocationState?.handle.clear() + revocationRegistryDefinition?.handle.clear() + } + } + + const credentialsProve: CredentialProve[] = [] + const credentials: { linkSecretId: string; credentialEntry: CredentialEntry }[] = [] + + let entryIndex = 0 + for (const referent in selectedCredentials.attributes) { + const attribute = selectedCredentials.attributes[referent] + credentials.push(await credentialEntryFromAttribute(attribute)) + credentialsProve.push({ entryIndex, isPredicate: false, referent, reveal: attribute.revealed }) + entryIndex = entryIndex + 1 + } + + for (const referent in selectedCredentials.predicates) { + const predicate = selectedCredentials.predicates[referent] + credentials.push(await credentialEntryFromAttribute(predicate)) + credentialsProve.push({ entryIndex, isPredicate: true, referent, reveal: true }) + entryIndex = entryIndex + 1 + } + + // Get all requested credentials and take linkSecret. If it's not the same for every credential, throw error + const linkSecretsMatch = credentials.every((item) => item.linkSecretId === credentials[0].linkSecretId) + if (!linkSecretsMatch) { + throw new AnonCredsRsError('All credentials in a Proof should have been issued using the same Link Secret') + } + + const linkSecretRecord = await agentContext.dependencyManager + .resolve(AnonCredsLinkSecretRepository) + .getByLinkSecretId(agentContext, credentials[0].linkSecretId) + + if (!linkSecretRecord.value) { + throw new AnonCredsRsError('Link Secret value not stored') + } + + presentation = Presentation.create({ + credentialDefinitions: rsCredentialDefinitions, + schemas: rsSchemas, + presentationRequest: proofRequest as unknown as JsonObject, + credentials: credentials.map((entry) => entry.credentialEntry), + credentialsProve, + selfAttest: selectedCredentials.selfAttestedAttributes, + linkSecret: linkSecretRecord.value, + }) + + return presentation.toJson() as unknown as AnonCredsProof + } finally { + presentation?.handle.clear() + } + } + + public async createCredentialRequest( + agentContext: AgentContext, + options: CreateCredentialRequestOptions + ): Promise { + const { useLegacyProverDid, credentialDefinition, credentialOffer } = options + let createReturnObj: + | { credentialRequest: CredentialRequest; credentialRequestMetadata: CredentialRequestMetadata } + | undefined + try { + const linkSecretRepository = agentContext.dependencyManager.resolve(AnonCredsLinkSecretRepository) + + // If a link secret is specified, use it. Otherwise, attempt to use default link secret + const linkSecretRecord = options.linkSecretId + ? await linkSecretRepository.getByLinkSecretId(agentContext, options.linkSecretId) + : await linkSecretRepository.findDefault(agentContext) + + if (!linkSecretRecord) { + // No default link secret + throw new AnonCredsRsError( + 'No link secret provided to createCredentialRequest and no default link secret has been found' + ) + } + + if (!linkSecretRecord.value) { + throw new AnonCredsRsError('Link Secret value not stored') + } + + const isLegacyIdentifier = credentialOffer.cred_def_id.match(unqualifiedCredentialDefinitionIdRegex) + if (!isLegacyIdentifier && useLegacyProverDid) { + throw new AriesFrameworkError('Cannot use legacy prover_did with non-legacy identifiers') + } + createReturnObj = CredentialRequest.create({ + entropy: !useLegacyProverDid || !isLegacyIdentifier ? anoncreds.generateNonce() : undefined, + proverDid: useLegacyProverDid + ? TypedArrayEncoder.toBase58(TypedArrayEncoder.fromString(anoncreds.generateNonce().slice(0, 16))) + : undefined, + credentialDefinition: credentialDefinition as unknown as JsonObject, + credentialOffer: credentialOffer as unknown as JsonObject, + linkSecret: linkSecretRecord.value, + linkSecretId: linkSecretRecord.linkSecretId, + }) + + return { + credentialRequest: createReturnObj.credentialRequest.toJson() as unknown as AnonCredsCredentialRequest, + credentialRequestMetadata: + createReturnObj.credentialRequestMetadata.toJson() as unknown as AnonCredsCredentialRequestMetadata, + } + } finally { + createReturnObj?.credentialRequest.handle.clear() + createReturnObj?.credentialRequestMetadata.handle.clear() + } + } + + public async storeCredential(agentContext: AgentContext, options: StoreCredentialOptions): Promise { + const { credential, credentialDefinition, credentialRequestMetadata, revocationRegistry, schema } = options + + const linkSecretRecord = await agentContext.dependencyManager + .resolve(AnonCredsLinkSecretRepository) + .getByLinkSecretId(agentContext, credentialRequestMetadata.link_secret_name) + + if (!linkSecretRecord.value) { + throw new AnonCredsRsError('Link Secret value not stored') + } + + const revocationRegistryDefinition = revocationRegistry?.definition as unknown as JsonObject + + const credentialId = options.credentialId ?? utils.uuid() + + let credentialObj: Credential | undefined + let processedCredential: Credential | undefined + try { + credentialObj = Credential.fromJson(credential as unknown as JsonObject) + processedCredential = credentialObj.process({ + credentialDefinition: credentialDefinition as unknown as JsonObject, + credentialRequestMetadata: credentialRequestMetadata as unknown as JsonObject, + linkSecret: linkSecretRecord.value, + revocationRegistryDefinition, + }) + + const credentialRepository = agentContext.dependencyManager.resolve(AnonCredsCredentialRepository) + + const methodName = agentContext.dependencyManager + .resolve(AnonCredsRegistryService) + .getRegistryForIdentifier(agentContext, credential.cred_def_id).methodName + + await credentialRepository.save( + agentContext, + new AnonCredsCredentialRecord({ + credential: processedCredential.toJson() as unknown as AnonCredsCredential, + credentialId, + linkSecretId: linkSecretRecord.linkSecretId, + issuerId: options.credentialDefinition.issuerId, + schemaName: schema.name, + schemaIssuerId: schema.issuerId, + schemaVersion: schema.version, + credentialRevocationId: processedCredential.revocationRegistryIndex?.toString(), + methodName, + }) + ) + + return credentialId + } finally { + credentialObj?.handle.clear() + processedCredential?.handle.clear() + } + } + + public async getCredential( + agentContext: AgentContext, + options: GetCredentialOptions + ): Promise { + const credentialRecord = await agentContext.dependencyManager + .resolve(AnonCredsCredentialRepository) + .getByCredentialId(agentContext, options.credentialId) + + const attributes: { [key: string]: string } = {} + for (const attribute in credentialRecord.credential.values) { + attributes[attribute] = credentialRecord.credential.values[attribute].raw + } + return { + attributes, + credentialDefinitionId: credentialRecord.credential.cred_def_id, + credentialId: credentialRecord.credentialId, + schemaId: credentialRecord.credential.schema_id, + credentialRevocationId: credentialRecord.credentialRevocationId, + revocationRegistryId: credentialRecord.credential.rev_reg_id, + methodName: credentialRecord.methodName, + } + } + + public async getCredentials( + agentContext: AgentContext, + options: GetCredentialsOptions + ): Promise { + const credentialRecords = await agentContext.dependencyManager + .resolve(AnonCredsCredentialRepository) + .findByQuery(agentContext, { + credentialDefinitionId: options.credentialDefinitionId, + schemaId: options.schemaId, + issuerId: options.issuerId, + schemaName: options.schemaName, + schemaVersion: options.schemaVersion, + schemaIssuerId: options.schemaIssuerId, + methodName: options.methodName, + }) + + return credentialRecords.map((credentialRecord) => ({ + attributes: Object.fromEntries( + Object.entries(credentialRecord.credential.values).map(([key, value]) => [key, value.raw]) + ), + credentialDefinitionId: credentialRecord.credential.cred_def_id, + credentialId: credentialRecord.credentialId, + schemaId: credentialRecord.credential.schema_id, + credentialRevocationId: credentialRecord.credentialRevocationId, + revocationRegistryId: credentialRecord.credential.rev_reg_id, + methodName: credentialRecord.methodName, + })) + } + + public async deleteCredential(agentContext: AgentContext, credentialId: string): Promise { + const credentialRepository = agentContext.dependencyManager.resolve(AnonCredsCredentialRepository) + const credentialRecord = await credentialRepository.getByCredentialId(agentContext, credentialId) + await credentialRepository.delete(agentContext, credentialRecord) + } + + public async getCredentialsForProofRequest( + agentContext: AgentContext, + options: GetCredentialsForProofRequestOptions + ): Promise { + const proofRequest = options.proofRequest + const referent = options.attributeReferent + + const requestedAttribute = + proofRequest.requested_attributes[referent] ?? proofRequest.requested_predicates[referent] + + if (!requestedAttribute) { + throw new AnonCredsRsError(`Referent not found in proof request`) + } + + const $and = [] + + // Make sure the attribute(s) that are requested are present using the marker tag + const attributes = requestedAttribute.names ?? [requestedAttribute.name] + const attributeQuery: SimpleQuery = {} + for (const attribute of attributes) { + attributeQuery[`attr::${attribute}::marker`] = true + } + $and.push(attributeQuery) + + // Add query for proof request restrictions + if (requestedAttribute.restrictions) { + const restrictionQuery = this.queryFromRestrictions(requestedAttribute.restrictions) + $and.push(restrictionQuery) + } + + // Add extra query + // TODO: we're not really typing the extraQuery, and it will work differently based on the anoncreds implmentation + // We should make the allowed properties more strict + if (options.extraQuery) { + $and.push(options.extraQuery) + } + + const credentials = await agentContext.dependencyManager + .resolve(AnonCredsCredentialRepository) + .findByQuery(agentContext, { + $and, + }) + + return credentials.map((credentialRecord) => { + const attributes: { [key: string]: string } = {} + for (const attribute in credentialRecord.credential.values) { + attributes[attribute] = credentialRecord.credential.values[attribute].raw + } + return { + credentialInfo: { + attributes, + credentialDefinitionId: credentialRecord.credential.cred_def_id, + credentialId: credentialRecord.credentialId, + schemaId: credentialRecord.credential.schema_id, + credentialRevocationId: credentialRecord.credentialRevocationId, + revocationRegistryId: credentialRecord.credential.rev_reg_id, + methodName: credentialRecord.methodName, + }, + interval: proofRequest.non_revoked, + } + }) + } + + private queryFromRestrictions(restrictions: AnonCredsProofRequestRestriction[]) { + const query: Query[] = [] + + const { restrictions: parsedRestrictions } = JsonTransformer.fromJSON({ restrictions }, AnonCredsRestrictionWrapper) + + for (const restriction of parsedRestrictions) { + const queryElements: SimpleQuery = {} + + if (restriction.credentialDefinitionId) { + queryElements.credentialDefinitionId = restriction.credentialDefinitionId + } + + if (restriction.issuerId || restriction.issuerDid) { + queryElements.issuerId = restriction.issuerId ?? restriction.issuerDid + } + + if (restriction.schemaId) { + queryElements.schemaId = restriction.schemaId + } + + if (restriction.schemaIssuerId || restriction.schemaIssuerDid) { + queryElements.schemaIssuerId = restriction.schemaIssuerId ?? restriction.issuerDid + } + + if (restriction.schemaName) { + queryElements.schemaName = restriction.schemaName + } + + if (restriction.schemaVersion) { + queryElements.schemaVersion = restriction.schemaVersion + } + + for (const [attributeName, attributeValue] of Object.entries(restriction.attributeValues)) { + queryElements[`attr::${attributeName}::value`] = attributeValue + } + + for (const [attributeName, isAvailable] of Object.entries(restriction.attributeMarkers)) { + if (isAvailable) { + queryElements[`attr::${attributeName}::marker`] = isAvailable + } + } + + query.push(queryElements) + } + + return query.length === 1 ? query[0] : { $or: query } + } +} diff --git a/packages/anoncreds-rs/src/services/AnonCredsRsIssuerService.ts b/packages/anoncreds-rs/src/services/AnonCredsRsIssuerService.ts new file mode 100644 index 0000000000..383c6e94e7 --- /dev/null +++ b/packages/anoncreds-rs/src/services/AnonCredsRsIssuerService.ts @@ -0,0 +1,193 @@ +import type { + AnonCredsIssuerService, + CreateCredentialDefinitionOptions, + CreateCredentialOfferOptions, + CreateCredentialOptions, + CreateCredentialReturn, + CreateSchemaOptions, + AnonCredsCredentialOffer, + AnonCredsSchema, + AnonCredsCredentialDefinition, + CreateCredentialDefinitionReturn, + AnonCredsCredential, +} from '@aries-framework/anoncreds' +import type { AgentContext } from '@aries-framework/core' +import type { CredentialDefinitionPrivate, JsonObject, KeyCorrectnessProof } from '@hyperledger/anoncreds-shared' + +import { + parseIndyDid, + getUnqualifiedSchemaId, + parseIndySchemaId, + isUnqualifiedCredentialDefinitionId, + AnonCredsKeyCorrectnessProofRepository, + AnonCredsCredentialDefinitionPrivateRepository, + AnonCredsCredentialDefinitionRepository, +} from '@aries-framework/anoncreds' +import { injectable, AriesFrameworkError } from '@aries-framework/core' +import { Credential, CredentialDefinition, CredentialOffer, Schema } from '@hyperledger/anoncreds-shared' + +import { AnonCredsRsError } from '../errors/AnonCredsRsError' + +@injectable() +export class AnonCredsRsIssuerService implements AnonCredsIssuerService { + public async createSchema(agentContext: AgentContext, options: CreateSchemaOptions): Promise { + const { issuerId, name, version, attrNames: attributeNames } = options + + let schema: Schema | undefined + try { + const schema = Schema.create({ + issuerId, + name, + version, + attributeNames, + }) + + return schema.toJson() as unknown as AnonCredsSchema + } finally { + schema?.handle.clear() + } + } + + public async createCredentialDefinition( + agentContext: AgentContext, + options: CreateCredentialDefinitionOptions + ): Promise { + const { tag, supportRevocation, schema, issuerId, schemaId } = options + + let createReturnObj: + | { + credentialDefinition: CredentialDefinition + credentialDefinitionPrivate: CredentialDefinitionPrivate + keyCorrectnessProof: KeyCorrectnessProof + } + | undefined + try { + createReturnObj = CredentialDefinition.create({ + schema: schema as unknown as JsonObject, + issuerId, + schemaId, + tag, + supportRevocation, + signatureType: 'CL', + }) + + return { + credentialDefinition: createReturnObj.credentialDefinition.toJson() as unknown as AnonCredsCredentialDefinition, + credentialDefinitionPrivate: createReturnObj.credentialDefinitionPrivate.toJson(), + keyCorrectnessProof: createReturnObj.keyCorrectnessProof.toJson(), + } + } finally { + createReturnObj?.credentialDefinition.handle.clear() + createReturnObj?.credentialDefinitionPrivate.handle.clear() + createReturnObj?.keyCorrectnessProof.handle.clear() + } + } + + public async createCredentialOffer( + agentContext: AgentContext, + options: CreateCredentialOfferOptions + ): Promise { + const { credentialDefinitionId } = options + + let credentialOffer: CredentialOffer | undefined + try { + // The getByCredentialDefinitionId supports both qualified and unqualified identifiers, even though the + // record is always stored using the qualified identifier. + const credentialDefinitionRecord = await agentContext.dependencyManager + .resolve(AnonCredsCredentialDefinitionRepository) + .getByCredentialDefinitionId(agentContext, options.credentialDefinitionId) + + // We fetch the keyCorrectnessProof based on the credential definition record id, as the + // credential definition id passed to this module could be unqualified, and the key correctness + // proof is only stored using the qualified identifier. + const keyCorrectnessProofRecord = await agentContext.dependencyManager + .resolve(AnonCredsKeyCorrectnessProofRepository) + .getByCredentialDefinitionId(agentContext, credentialDefinitionRecord.credentialDefinitionId) + + if (!credentialDefinitionRecord) { + throw new AnonCredsRsError(`Credential Definition ${credentialDefinitionId} not found`) + } + + let schemaId = credentialDefinitionRecord.credentialDefinition.schemaId + + // if the credentialDefinitionId is not qualified, we need to transform the schemaId to also be unqualified + if (isUnqualifiedCredentialDefinitionId(options.credentialDefinitionId)) { + const { namespaceIdentifier, schemaName, schemaVersion } = parseIndySchemaId(schemaId) + schemaId = getUnqualifiedSchemaId(namespaceIdentifier, schemaName, schemaVersion) + } + + credentialOffer = CredentialOffer.create({ + credentialDefinitionId, + keyCorrectnessProof: keyCorrectnessProofRecord?.value, + schemaId, + }) + + return credentialOffer.toJson() as unknown as AnonCredsCredentialOffer + } finally { + credentialOffer?.handle.clear() + } + } + + public async createCredential( + agentContext: AgentContext, + options: CreateCredentialOptions + ): Promise { + const { tailsFilePath, credentialOffer, credentialRequest, credentialValues, revocationRegistryId } = options + + let credential: Credential | undefined + try { + if (revocationRegistryId || tailsFilePath) { + throw new AriesFrameworkError('Revocation not supported yet') + } + + const attributeRawValues: Record = {} + const attributeEncodedValues: Record = {} + + Object.keys(credentialValues).forEach((key) => { + attributeRawValues[key] = credentialValues[key].raw + attributeEncodedValues[key] = credentialValues[key].encoded + }) + + const credentialDefinitionRecord = await agentContext.dependencyManager + .resolve(AnonCredsCredentialDefinitionRepository) + .getByCredentialDefinitionId(agentContext, options.credentialRequest.cred_def_id) + + // We fetch the private record based on the cred def id from the cred def record, as the + // credential definition id passed to this module could be unqualified, and the private record + // is only stored using the qualified identifier. + const credentialDefinitionPrivateRecord = await agentContext.dependencyManager + .resolve(AnonCredsCredentialDefinitionPrivateRepository) + .getByCredentialDefinitionId(agentContext, credentialDefinitionRecord.credentialDefinitionId) + + let credentialDefinition = credentialDefinitionRecord.credentialDefinition + + if (isUnqualifiedCredentialDefinitionId(options.credentialRequest.cred_def_id)) { + const { namespaceIdentifier, schemaName, schemaVersion } = parseIndySchemaId(credentialDefinition.schemaId) + const { namespaceIdentifier: unqualifiedDid } = parseIndyDid(credentialDefinition.issuerId) + parseIndyDid + credentialDefinition = { + ...credentialDefinition, + schemaId: getUnqualifiedSchemaId(namespaceIdentifier, schemaName, schemaVersion), + issuerId: unqualifiedDid, + } + } + + credential = Credential.create({ + credentialDefinition: credentialDefinitionRecord.credentialDefinition as unknown as JsonObject, + credentialOffer: credentialOffer as unknown as JsonObject, + credentialRequest: credentialRequest as unknown as JsonObject, + revocationRegistryId, + attributeEncodedValues, + attributeRawValues, + credentialDefinitionPrivate: credentialDefinitionPrivateRecord.value, + }) + + return { + credential: credential.toJson() as unknown as AnonCredsCredential, + credentialRevocationId: credential.revocationRegistryIndex?.toString(), + } + } finally { + credential?.handle.clear() + } + } +} diff --git a/packages/anoncreds-rs/src/services/AnonCredsRsVerifierService.ts b/packages/anoncreds-rs/src/services/AnonCredsRsVerifierService.ts new file mode 100644 index 0000000000..26573309ff --- /dev/null +++ b/packages/anoncreds-rs/src/services/AnonCredsRsVerifierService.ts @@ -0,0 +1,49 @@ +import type { AnonCredsVerifierService, VerifyProofOptions } from '@aries-framework/anoncreds' +import type { AgentContext } from '@aries-framework/core' +import type { JsonObject } from '@hyperledger/anoncreds-shared' + +import { injectable } from '@aries-framework/core' +import { Presentation } from '@hyperledger/anoncreds-shared' + +@injectable() +export class AnonCredsRsVerifierService implements AnonCredsVerifierService { + public async verifyProof(agentContext: AgentContext, options: VerifyProofOptions): Promise { + const { credentialDefinitions, proof, proofRequest, revocationRegistries, schemas } = options + + let presentation: Presentation | undefined + try { + presentation = Presentation.fromJson(proof as unknown as JsonObject) + + const rsCredentialDefinitions: Record = {} + for (const credDefId in credentialDefinitions) { + rsCredentialDefinitions[credDefId] = credentialDefinitions[credDefId] as unknown as JsonObject + } + + const rsSchemas: Record = {} + for (const schemaId in schemas) { + rsSchemas[schemaId] = schemas[schemaId] as unknown as JsonObject + } + + const revocationRegistryDefinitions: Record = {} + const lists: JsonObject[] = [] + + for (const revocationRegistryDefinitionId in revocationRegistries) { + const { definition, revocationStatusLists } = options.revocationRegistries[revocationRegistryDefinitionId] + + revocationRegistryDefinitions[revocationRegistryDefinitionId] = definition as unknown as JsonObject + + lists.push(...(Object.values(revocationStatusLists) as unknown as Array)) + } + + return presentation.verify({ + presentationRequest: proofRequest as unknown as JsonObject, + credentialDefinitions: rsCredentialDefinitions, + schemas: rsSchemas, + revocationRegistryDefinitions, + revocationStatusLists: lists, + }) + } finally { + presentation?.handle.clear() + } + } +} diff --git a/packages/anoncreds-rs/src/services/__tests__/AnonCredsRsHolderService.test.ts b/packages/anoncreds-rs/src/services/__tests__/AnonCredsRsHolderService.test.ts new file mode 100644 index 0000000000..b7de80d8ae --- /dev/null +++ b/packages/anoncreds-rs/src/services/__tests__/AnonCredsRsHolderService.test.ts @@ -0,0 +1,624 @@ +import type { + AnonCredsCredentialDefinition, + AnonCredsProofRequest, + AnonCredsRevocationStatusList, + AnonCredsCredential, + AnonCredsSchema, + AnonCredsSelectedCredentials, + AnonCredsRevocationRegistryDefinition, + AnonCredsCredentialRequestMetadata, +} from '@aries-framework/anoncreds' +import type { JsonObject } from '@hyperledger/anoncreds-nodejs' + +import { + AnonCredsModuleConfig, + AnonCredsHolderServiceSymbol, + AnonCredsLinkSecretRecord, + AnonCredsCredentialRecord, +} from '@aries-framework/anoncreds' +import { anoncreds, RevocationRegistryDefinition } from '@hyperledger/anoncreds-nodejs' + +import { describeRunInNodeVersion } from '../../../../../tests/runInVersion' +import { AnonCredsCredentialDefinitionRepository } from '../../../../anoncreds/src/repository/AnonCredsCredentialDefinitionRepository' +import { AnonCredsCredentialRepository } from '../../../../anoncreds/src/repository/AnonCredsCredentialRepository' +import { AnonCredsLinkSecretRepository } from '../../../../anoncreds/src/repository/AnonCredsLinkSecretRepository' +import { InMemoryAnonCredsRegistry } from '../../../../anoncreds/tests/InMemoryAnonCredsRegistry' +import { getAgentConfig, getAgentContext, mockFunction } from '../../../../core/tests/helpers' +import { AnonCredsRsHolderService } from '../AnonCredsRsHolderService' + +import { + createCredentialDefinition, + createCredentialForHolder, + createCredentialOffer, + createLinkSecret, +} from './helpers' + +const agentConfig = getAgentConfig('AnonCredsRsHolderServiceTest') +const anonCredsHolderService = new AnonCredsRsHolderService() + +jest.mock('../../../../anoncreds/src/repository/AnonCredsCredentialDefinitionRepository') +const CredentialDefinitionRepositoryMock = + AnonCredsCredentialDefinitionRepository as jest.Mock +const credentialDefinitionRepositoryMock = new CredentialDefinitionRepositoryMock() + +jest.mock('../../../../anoncreds/src/repository/AnonCredsLinkSecretRepository') +const AnonCredsLinkSecretRepositoryMock = AnonCredsLinkSecretRepository as jest.Mock +const anoncredsLinkSecretRepositoryMock = new AnonCredsLinkSecretRepositoryMock() + +jest.mock('../../../../anoncreds/src/repository/AnonCredsCredentialRepository') +const AnonCredsCredentialRepositoryMock = AnonCredsCredentialRepository as jest.Mock +const anoncredsCredentialRepositoryMock = new AnonCredsCredentialRepositoryMock() + +const agentContext = getAgentContext({ + registerInstances: [ + [AnonCredsCredentialDefinitionRepository, credentialDefinitionRepositoryMock], + [AnonCredsLinkSecretRepository, anoncredsLinkSecretRepositoryMock], + [AnonCredsCredentialRepository, anoncredsCredentialRepositoryMock], + [AnonCredsHolderServiceSymbol, anonCredsHolderService], + [ + AnonCredsModuleConfig, + new AnonCredsModuleConfig({ + registries: [new InMemoryAnonCredsRegistry({})], + }), + ], + ], + agentConfig, +}) + +// FIXME: Re-include in tests when NodeJS wrapper performance is improved +describeRunInNodeVersion([18], 'AnonCredsRsHolderService', () => { + const getByCredentialIdMock = jest.spyOn(anoncredsCredentialRepositoryMock, 'getByCredentialId') + const findByQueryMock = jest.spyOn(anoncredsCredentialRepositoryMock, 'findByQuery') + + beforeEach(() => { + getByCredentialIdMock.mockClear() + }) + + test('createCredentialRequest', async () => { + mockFunction(anoncredsLinkSecretRepositoryMock.getByLinkSecretId).mockResolvedValue( + new AnonCredsLinkSecretRecord({ linkSecretId: 'linkSecretId', value: createLinkSecret() }) + ) + + const { credentialDefinition, keyCorrectnessProof } = createCredentialDefinition({ + attributeNames: ['phoneNumber'], + issuerId: 'issuer:uri', + }) + const credentialOffer = createCredentialOffer(keyCorrectnessProof) + + const { credentialRequest } = await anonCredsHolderService.createCredentialRequest(agentContext, { + credentialDefinition, + credentialOffer, + linkSecretId: 'linkSecretId', + }) + + expect(credentialRequest.cred_def_id).toBe('creddef:uri') + expect(credentialRequest.prover_did).toBeUndefined() + }) + + test('createLinkSecret', async () => { + let linkSecret = await anonCredsHolderService.createLinkSecret(agentContext, { + linkSecretId: 'linkSecretId', + }) + + expect(linkSecret.linkSecretId).toBe('linkSecretId') + expect(linkSecret.linkSecretValue).toBeDefined() + + linkSecret = await anonCredsHolderService.createLinkSecret(agentContext) + + expect(linkSecret.linkSecretId).toBeDefined() + expect(linkSecret.linkSecretValue).toBeDefined() + }) + + test('createProof', async () => { + const proofRequest: AnonCredsProofRequest = { + nonce: anoncreds.generateNonce(), + name: 'pres_req_1', + version: '0.1', + requested_attributes: { + attr1_referent: { + name: 'name', + restrictions: [{ issuer_did: 'issuer:uri' }], + }, + attr2_referent: { + name: 'phoneNumber', + }, + attr3_referent: { + name: 'age', + }, + attr4_referent: { + names: ['name', 'height'], + }, + attr5_referent: { + name: 'favouriteSport', + }, + }, + requested_predicates: { + predicate1_referent: { name: 'age', p_type: '>=' as const, p_value: 18 }, + }, + //non_revoked: { from: 10, to: 200 }, + } + + const { + credentialDefinition: personCredentialDefinition, + credentialDefinitionPrivate: personCredentialDefinitionPrivate, + keyCorrectnessProof: personKeyCorrectnessProof, + } = createCredentialDefinition({ + attributeNames: ['name', 'age', 'sex', 'height'], + issuerId: 'issuer:uri', + }) + + const { + credentialDefinition: phoneCredentialDefinition, + credentialDefinitionPrivate: phoneCredentialDefinitionPrivate, + keyCorrectnessProof: phoneKeyCorrectnessProof, + } = createCredentialDefinition({ + attributeNames: ['phoneNumber'], + issuerId: 'issuer:uri', + }) + + const linkSecret = createLinkSecret() + + mockFunction(anoncredsLinkSecretRepositoryMock.getByLinkSecretId).mockResolvedValue( + new AnonCredsLinkSecretRecord({ linkSecretId: 'linkSecretId', value: linkSecret }) + ) + + const { + credential: personCredential, + credentialInfo: personCredentialInfo, + revocationRegistryDefinition: personRevRegDef, + tailsPath: personTailsPath, + } = createCredentialForHolder({ + attributes: { + name: 'John', + sex: 'M', + height: '179', + age: '19', + }, + credentialDefinition: personCredentialDefinition as unknown as JsonObject, + schemaId: 'personschema:uri', + credentialDefinitionId: 'personcreddef:uri', + credentialDefinitionPrivate: personCredentialDefinitionPrivate, + keyCorrectnessProof: personKeyCorrectnessProof, + linkSecret, + linkSecretId: 'linkSecretId', + credentialId: 'personCredId', + revocationRegistryDefinitionId: 'personrevregid:uri', + }) + + const { + credential: phoneCredential, + credentialInfo: phoneCredentialInfo, + revocationRegistryDefinition: phoneRevRegDef, + tailsPath: phoneTailsPath, + } = createCredentialForHolder({ + attributes: { + phoneNumber: 'linkSecretId56', + }, + credentialDefinition: phoneCredentialDefinition as unknown as JsonObject, + schemaId: 'phoneschema:uri', + credentialDefinitionId: 'phonecreddef:uri', + credentialDefinitionPrivate: phoneCredentialDefinitionPrivate, + keyCorrectnessProof: phoneKeyCorrectnessProof, + linkSecret, + linkSecretId: 'linkSecretId', + credentialId: 'phoneCredId', + revocationRegistryDefinitionId: 'phonerevregid:uri', + }) + + const selectedCredentials: AnonCredsSelectedCredentials = { + selfAttestedAttributes: { attr5_referent: 'football' }, + attributes: { + attr1_referent: { credentialId: 'personCredId', credentialInfo: personCredentialInfo, revealed: true }, + attr2_referent: { credentialId: 'phoneCredId', credentialInfo: phoneCredentialInfo, revealed: true }, + attr3_referent: { credentialId: 'personCredId', credentialInfo: personCredentialInfo, revealed: true }, + attr4_referent: { credentialId: 'personCredId', credentialInfo: personCredentialInfo, revealed: true }, + }, + predicates: { + predicate1_referent: { credentialId: 'personCredId', credentialInfo: personCredentialInfo }, + }, + } + + getByCredentialIdMock.mockResolvedValueOnce( + new AnonCredsCredentialRecord({ + credential: personCredential, + credentialId: 'personCredId', + linkSecretId: 'linkSecretId', + issuerId: 'issuerDid', + schemaIssuerId: 'schemaIssuerDid', + schemaName: 'schemaName', + schemaVersion: 'schemaVersion', + methodName: 'inMemory', + }) + ) + getByCredentialIdMock.mockResolvedValueOnce( + new AnonCredsCredentialRecord({ + credential: phoneCredential, + credentialId: 'phoneCredId', + linkSecretId: 'linkSecretId', + issuerId: 'issuerDid', + schemaIssuerId: 'schemaIssuerDid', + schemaName: 'schemaName', + schemaVersion: 'schemaVersion', + methodName: 'inMemory', + }) + ) + + const revocationRegistries = { + 'personrevregid:uri': { + tailsFilePath: personTailsPath, + definition: JSON.parse(anoncreds.getJson({ objectHandle: personRevRegDef })), + revocationStatusLists: { '1': {} as AnonCredsRevocationStatusList }, + }, + 'phonerevregid:uri': { + tailsFilePath: phoneTailsPath, + definition: JSON.parse(anoncreds.getJson({ objectHandle: phoneRevRegDef })), + revocationStatusLists: { '1': {} as AnonCredsRevocationStatusList }, + }, + } + + await anonCredsHolderService.createProof(agentContext, { + credentialDefinitions: { + 'personcreddef:uri': personCredentialDefinition as AnonCredsCredentialDefinition, + 'phonecreddef:uri': phoneCredentialDefinition as AnonCredsCredentialDefinition, + }, + proofRequest, + selectedCredentials, + schemas: { + 'phoneschema:uri': { attrNames: ['phoneNumber'], issuerId: 'issuer:uri', name: 'phoneschema', version: '1' }, + 'personschema:uri': { + attrNames: ['name', 'sex', 'height', 'age'], + issuerId: 'issuer:uri', + name: 'personschema', + version: '1', + }, + }, + revocationRegistries, + }) + + expect(getByCredentialIdMock).toHaveBeenCalledTimes(2) + // TODO: check proof object + }) + + describe('getCredentialsForProofRequest', () => { + const findByQueryMock = jest.spyOn(anoncredsCredentialRepositoryMock, 'findByQuery') + + const proofRequest: AnonCredsProofRequest = { + nonce: anoncreds.generateNonce(), + name: 'pres_req_1', + version: '0.1', + requested_attributes: { + attr1_referent: { + name: 'name', + restrictions: [{ issuer_did: 'issuer:uri' }], + }, + attr2_referent: { + name: 'phoneNumber', + }, + attr3_referent: { + name: 'age', + restrictions: [{ schema_id: 'schemaid:uri', schema_name: 'schemaName' }, { schema_version: '1.0' }], + }, + attr4_referent: { + names: ['name', 'height'], + restrictions: [{ cred_def_id: 'crededefid:uri', issuer_id: 'issuerid:uri' }], + }, + attr5_referent: { + name: 'name', + restrictions: [{ 'attr::name::value': 'Alice', 'attr::name::marker': '1' }], + }, + }, + requested_predicates: { + predicate1_referent: { name: 'age', p_type: '>=' as const, p_value: 18 }, + }, + } + + beforeEach(() => { + findByQueryMock.mockResolvedValue([]) + }) + + afterEach(() => { + findByQueryMock.mockClear() + }) + + test('invalid referent', async () => { + await expect( + anonCredsHolderService.getCredentialsForProofRequest(agentContext, { + proofRequest, + attributeReferent: 'name', + }) + ).rejects.toThrowError() + }) + + test('referent with single restriction', async () => { + await anonCredsHolderService.getCredentialsForProofRequest(agentContext, { + proofRequest, + attributeReferent: 'attr1_referent', + }) + + expect(findByQueryMock).toHaveBeenCalledWith(agentContext, { + $and: [ + { + 'attr::name::marker': true, + }, + { + issuerId: 'issuer:uri', + }, + ], + }) + }) + + test('referent without restrictions', async () => { + await anonCredsHolderService.getCredentialsForProofRequest(agentContext, { + proofRequest, + attributeReferent: 'attr2_referent', + }) + + expect(findByQueryMock).toHaveBeenCalledWith(agentContext, { + $and: [ + { + 'attr::phoneNumber::marker': true, + }, + ], + }) + }) + + test('referent with multiple, complex restrictions', async () => { + await anonCredsHolderService.getCredentialsForProofRequest(agentContext, { + proofRequest, + attributeReferent: 'attr3_referent', + }) + + expect(findByQueryMock).toHaveBeenCalledWith(agentContext, { + $and: [ + { + 'attr::age::marker': true, + }, + { + $or: [{ schemaId: 'schemaid:uri', schemaName: 'schemaName' }, { schemaVersion: '1.0' }], + }, + ], + }) + }) + + test('referent with multiple names and restrictions', async () => { + await anonCredsHolderService.getCredentialsForProofRequest(agentContext, { + proofRequest, + attributeReferent: 'attr4_referent', + }) + + expect(findByQueryMock).toHaveBeenCalledWith(agentContext, { + $and: [ + { + 'attr::name::marker': true, + 'attr::height::marker': true, + }, + { + credentialDefinitionId: 'crededefid:uri', + issuerId: 'issuerid:uri', + }, + ], + }) + }) + + test('referent with attribute values and marker restriction', async () => { + await anonCredsHolderService.getCredentialsForProofRequest(agentContext, { + proofRequest, + attributeReferent: 'attr5_referent', + }) + + expect(findByQueryMock).toHaveBeenCalledWith(agentContext, { + $and: [ + { + 'attr::name::marker': true, + }, + { + 'attr::name::value': 'Alice', + 'attr::name::marker': true, + }, + ], + }) + }) + + test('predicate referent', async () => { + await anonCredsHolderService.getCredentialsForProofRequest(agentContext, { + proofRequest, + attributeReferent: 'predicate1_referent', + }) + + expect(findByQueryMock).toHaveBeenCalledWith(agentContext, { + $and: [ + { + 'attr::age::marker': true, + }, + ], + }) + }) + }) + + test('deleteCredential', async () => { + getByCredentialIdMock.mockRejectedValueOnce(new Error()) + getByCredentialIdMock.mockResolvedValueOnce( + new AnonCredsCredentialRecord({ + credential: {} as AnonCredsCredential, + credentialId: 'personCredId', + linkSecretId: 'linkSecretId', + issuerId: 'issuerDid', + schemaIssuerId: 'schemaIssuerDid', + schemaName: 'schemaName', + schemaVersion: 'schemaVersion', + methodName: 'inMemory', + }) + ) + + expect(anonCredsHolderService.deleteCredential(agentContext, 'credentialId')).rejects.toThrowError() + + await anonCredsHolderService.deleteCredential(agentContext, 'credentialId') + + expect(getByCredentialIdMock).toHaveBeenCalledWith(agentContext, 'credentialId') + }) + + test('getCredential', async () => { + getByCredentialIdMock.mockRejectedValueOnce(new Error()) + + getByCredentialIdMock.mockResolvedValueOnce( + new AnonCredsCredentialRecord({ + credential: { + cred_def_id: 'credDefId', + schema_id: 'schemaId', + signature: 'signature', + signature_correctness_proof: 'signatureCorrectnessProof', + values: { attr1: { raw: 'value1', encoded: 'encvalue1' }, attr2: { raw: 'value2', encoded: 'encvalue2' } }, + rev_reg_id: 'revRegId', + } as AnonCredsCredential, + credentialId: 'myCredentialId', + credentialRevocationId: 'credentialRevocationId', + linkSecretId: 'linkSecretId', + issuerId: 'issuerDid', + schemaIssuerId: 'schemaIssuerDid', + schemaName: 'schemaName', + schemaVersion: 'schemaVersion', + methodName: 'inMemory', + }) + ) + expect( + anonCredsHolderService.getCredential(agentContext, { credentialId: 'myCredentialId' }) + ).rejects.toThrowError() + + const credentialInfo = await anonCredsHolderService.getCredential(agentContext, { credentialId: 'myCredentialId' }) + + expect(credentialInfo).toMatchObject({ + attributes: { attr1: 'value1', attr2: 'value2' }, + credentialDefinitionId: 'credDefId', + credentialId: 'myCredentialId', + revocationRegistryId: 'revRegId', + schemaId: 'schemaId', + credentialRevocationId: 'credentialRevocationId', + }) + }) + + test('getCredentials', async () => { + findByQueryMock.mockResolvedValueOnce([ + new AnonCredsCredentialRecord({ + credential: { + cred_def_id: 'credDefId', + schema_id: 'schemaId', + signature: 'signature', + signature_correctness_proof: 'signatureCorrectnessProof', + values: { attr1: { raw: 'value1', encoded: 'encvalue1' }, attr2: { raw: 'value2', encoded: 'encvalue2' } }, + rev_reg_id: 'revRegId', + } as AnonCredsCredential, + credentialId: 'myCredentialId', + credentialRevocationId: 'credentialRevocationId', + linkSecretId: 'linkSecretId', + issuerId: 'issuerDid', + schemaIssuerId: 'schemaIssuerDid', + schemaName: 'schemaName', + schemaVersion: 'schemaVersion', + methodName: 'inMemory', + }), + ]) + + const credentialInfo = await anonCredsHolderService.getCredentials(agentContext, { + credentialDefinitionId: 'credDefId', + schemaId: 'schemaId', + schemaIssuerId: 'schemaIssuerDid', + schemaName: 'schemaName', + schemaVersion: 'schemaVersion', + issuerId: 'issuerDid', + methodName: 'inMemory', + }) + + expect(findByQueryMock).toHaveBeenCalledWith(agentContext, { + credentialDefinitionId: 'credDefId', + schemaId: 'schemaId', + schemaIssuerId: 'schemaIssuerDid', + schemaName: 'schemaName', + schemaVersion: 'schemaVersion', + issuerId: 'issuerDid', + methodName: 'inMemory', + }) + expect(credentialInfo).toMatchObject([ + { + attributes: { attr1: 'value1', attr2: 'value2' }, + credentialDefinitionId: 'credDefId', + credentialId: 'myCredentialId', + revocationRegistryId: 'revRegId', + schemaId: 'schemaId', + credentialRevocationId: 'credentialRevocationId', + }, + ]) + }) + + test('storeCredential', async () => { + const { credentialDefinition, credentialDefinitionPrivate, keyCorrectnessProof } = createCredentialDefinition({ + attributeNames: ['name', 'age', 'sex', 'height'], + issuerId: 'issuer:uri', + }) + + const linkSecret = createLinkSecret() + + mockFunction(anoncredsLinkSecretRepositoryMock.getByLinkSecretId).mockResolvedValue( + new AnonCredsLinkSecretRecord({ linkSecretId: 'linkSecretId', value: linkSecret }) + ) + + const schema: AnonCredsSchema = { + attrNames: ['name', 'sex', 'height', 'age'], + issuerId: 'issuerId', + name: 'schemaName', + version: '1', + } + + const { credential, revocationRegistryDefinition, credentialRequestMetadata } = createCredentialForHolder({ + attributes: { + name: 'John', + sex: 'M', + height: '179', + age: '19', + }, + credentialDefinition: credentialDefinition as unknown as JsonObject, + schemaId: 'personschema:uri', + credentialDefinitionId: 'personcreddef:uri', + credentialDefinitionPrivate, + keyCorrectnessProof, + linkSecret, + linkSecretId: 'linkSecretId', + credentialId: 'personCredId', + revocationRegistryDefinitionId: 'personrevregid:uri', + }) + + const saveCredentialMock = jest.spyOn(anoncredsCredentialRepositoryMock, 'save') + + saveCredentialMock.mockResolvedValue() + + const credentialId = await anonCredsHolderService.storeCredential(agentContext, { + credential, + credentialDefinition, + schema, + credentialDefinitionId: 'personcreddefid:uri', + credentialRequestMetadata: credentialRequestMetadata.toJson() as unknown as AnonCredsCredentialRequestMetadata, + credentialId: 'personCredId', + revocationRegistry: { + id: 'personrevregid:uri', + definition: new RevocationRegistryDefinition( + revocationRegistryDefinition.handle + ).toJson() as unknown as AnonCredsRevocationRegistryDefinition, + }, + }) + + expect(credentialId).toBe('personCredId') + expect(saveCredentialMock).toHaveBeenCalledWith( + agentContext, + expect.objectContaining({ + // The stored credential is different from the one received originally + credentialId: 'personCredId', + linkSecretId: 'linkSecretId', + _tags: expect.objectContaining({ + issuerId: credentialDefinition.issuerId, + schemaName: 'schemaName', + schemaIssuerId: 'issuerId', + schemaVersion: '1', + }), + }) + ) + }) +}) diff --git a/packages/anoncreds-rs/src/services/__tests__/AnonCredsRsServices.test.ts b/packages/anoncreds-rs/src/services/__tests__/AnonCredsRsServices.test.ts new file mode 100644 index 0000000000..1b5d3db10d --- /dev/null +++ b/packages/anoncreds-rs/src/services/__tests__/AnonCredsRsServices.test.ts @@ -0,0 +1,451 @@ +import type { AnonCredsProofRequest } from '@aries-framework/anoncreds' + +import { + getUnqualifiedSchemaId, + parseIndySchemaId, + getUnqualifiedCredentialDefinitionId, + parseIndyCredentialDefinitionId, + AnonCredsModuleConfig, + AnonCredsHolderServiceSymbol, + AnonCredsIssuerServiceSymbol, + AnonCredsVerifierServiceSymbol, + AnonCredsSchemaRepository, + AnonCredsSchemaRecord, + AnonCredsCredentialDefinitionRecord, + AnonCredsCredentialDefinitionRepository, + AnonCredsCredentialDefinitionPrivateRecord, + AnonCredsCredentialDefinitionPrivateRepository, + AnonCredsKeyCorrectnessProofRepository, + AnonCredsKeyCorrectnessProofRecord, + AnonCredsLinkSecretRepository, + AnonCredsLinkSecretRecord, +} from '@aries-framework/anoncreds' +import { InjectionSymbols } from '@aries-framework/core' +import { anoncreds } from '@hyperledger/anoncreds-nodejs' +import { Subject } from 'rxjs' + +import { InMemoryStorageService } from '../../../../../tests/InMemoryStorageService' +import { describeRunInNodeVersion } from '../../../../../tests/runInVersion' +import { encodeCredentialValue } from '../../../../anoncreds/src/utils/credential' +import { InMemoryAnonCredsRegistry } from '../../../../anoncreds/tests/InMemoryAnonCredsRegistry' +import { agentDependencies, getAgentConfig, getAgentContext } from '../../../../core/tests/helpers' +import { AnonCredsRsHolderService } from '../AnonCredsRsHolderService' +import { AnonCredsRsIssuerService } from '../AnonCredsRsIssuerService' +import { AnonCredsRsVerifierService } from '../AnonCredsRsVerifierService' + +const agentConfig = getAgentConfig('AnonCredsCredentialFormatServiceTest') +const anonCredsVerifierService = new AnonCredsRsVerifierService() +const anonCredsHolderService = new AnonCredsRsHolderService() +const anonCredsIssuerService = new AnonCredsRsIssuerService() +const storageService = new InMemoryStorageService() +const registry = new InMemoryAnonCredsRegistry() + +const agentContext = getAgentContext({ + registerInstances: [ + [InjectionSymbols.Stop$, new Subject()], + [InjectionSymbols.AgentDependencies, agentDependencies], + [InjectionSymbols.StorageService, storageService], + [AnonCredsIssuerServiceSymbol, anonCredsIssuerService], + [AnonCredsHolderServiceSymbol, anonCredsHolderService], + [AnonCredsVerifierServiceSymbol, anonCredsVerifierService], + [ + AnonCredsModuleConfig, + new AnonCredsModuleConfig({ + registries: [registry], + }), + ], + ], + agentConfig, +}) + +// FIXME: Re-include in tests when NodeJS wrapper performance is improved +describeRunInNodeVersion([18], 'AnonCredsRsServices', () => { + test('issuance flow without revocation', async () => { + const issuerId = 'did:indy:pool:localtest:TL1EaPFCZ8Si5aUrqScBDt' + + const schema = await anonCredsIssuerService.createSchema(agentContext, { + attrNames: ['name', 'age'], + issuerId, + name: 'Employee Credential', + version: '1.0.0', + }) + + const { schemaState } = await registry.registerSchema(agentContext, { + schema, + options: {}, + }) + + const { credentialDefinition, credentialDefinitionPrivate, keyCorrectnessProof } = + await anonCredsIssuerService.createCredentialDefinition(agentContext, { + issuerId, + schemaId: schemaState.schemaId as string, + schema, + tag: 'Employee Credential', + supportRevocation: false, + }) + + const { credentialDefinitionState } = await registry.registerCredentialDefinition(agentContext, { + credentialDefinition, + options: {}, + }) + + if ( + !credentialDefinitionState.credentialDefinition || + !credentialDefinitionState.credentialDefinitionId || + !schemaState.schema || + !schemaState.schemaId + ) { + throw new Error('Failed to create schema or credential definition') + } + + if (!credentialDefinitionPrivate || !keyCorrectnessProof) { + throw new Error('Failed to get private part of credential definition') + } + + await agentContext.dependencyManager.resolve(AnonCredsSchemaRepository).save( + agentContext, + new AnonCredsSchemaRecord({ + schema: schemaState.schema, + schemaId: schemaState.schemaId, + methodName: 'inMemory', + }) + ) + + await agentContext.dependencyManager.resolve(AnonCredsCredentialDefinitionRepository).save( + agentContext, + new AnonCredsCredentialDefinitionRecord({ + credentialDefinition: credentialDefinitionState.credentialDefinition, + credentialDefinitionId: credentialDefinitionState.credentialDefinitionId, + methodName: 'inMemory', + }) + ) + + await agentContext.dependencyManager.resolve(AnonCredsCredentialDefinitionPrivateRepository).save( + agentContext, + new AnonCredsCredentialDefinitionPrivateRecord({ + value: credentialDefinitionPrivate, + credentialDefinitionId: credentialDefinitionState.credentialDefinitionId, + }) + ) + + await agentContext.dependencyManager.resolve(AnonCredsKeyCorrectnessProofRepository).save( + agentContext, + new AnonCredsKeyCorrectnessProofRecord({ + value: keyCorrectnessProof, + credentialDefinitionId: credentialDefinitionState.credentialDefinitionId, + }) + ) + + const credentialOffer = await anonCredsIssuerService.createCredentialOffer(agentContext, { + credentialDefinitionId: credentialDefinitionState.credentialDefinitionId, + }) + + const linkSecret = await anonCredsHolderService.createLinkSecret(agentContext, { linkSecretId: 'linkSecretId' }) + expect(linkSecret.linkSecretId).toBe('linkSecretId') + + await agentContext.dependencyManager.resolve(AnonCredsLinkSecretRepository).save( + agentContext, + new AnonCredsLinkSecretRecord({ + value: linkSecret.linkSecretValue, + linkSecretId: linkSecret.linkSecretId, + }) + ) + + const credentialRequestState = await anonCredsHolderService.createCredentialRequest(agentContext, { + credentialDefinition: credentialDefinitionState.credentialDefinition, + credentialOffer, + linkSecretId: linkSecret.linkSecretId, + }) + + const { credential } = await anonCredsIssuerService.createCredential(agentContext, { + credentialOffer, + credentialRequest: credentialRequestState.credentialRequest, + credentialValues: { + name: { raw: 'John', encoded: encodeCredentialValue('John') }, + age: { raw: '25', encoded: encodeCredentialValue('25') }, + }, + }) + + const credentialId = 'holderCredentialId' + + const storedId = await anonCredsHolderService.storeCredential(agentContext, { + credential, + credentialDefinition, + schema, + credentialDefinitionId: credentialDefinitionState.credentialDefinitionId, + credentialRequestMetadata: credentialRequestState.credentialRequestMetadata, + credentialId, + }) + + expect(storedId).toEqual(credentialId) + + const credentialInfo = await anonCredsHolderService.getCredential(agentContext, { + credentialId, + }) + + expect(credentialInfo).toEqual({ + credentialId, + attributes: { + age: '25', + name: 'John', + }, + schemaId: schemaState.schemaId, + credentialDefinitionId: credentialDefinitionState.credentialDefinitionId, + revocationRegistryId: null, + credentialRevocationId: undefined, // Should it be null in this case? + methodName: 'inMemory', + }) + + const proofRequest: AnonCredsProofRequest = { + nonce: anoncreds.generateNonce(), + name: 'pres_req_1', + version: '0.1', + requested_attributes: { + attr1_referent: { + name: 'name', + }, + attr2_referent: { + name: 'age', + }, + }, + requested_predicates: { + predicate1_referent: { name: 'age', p_type: '>=' as const, p_value: 18 }, + }, + } + + const proof = await anonCredsHolderService.createProof(agentContext, { + credentialDefinitions: { [credentialDefinitionState.credentialDefinitionId]: credentialDefinition }, + proofRequest, + selectedCredentials: { + attributes: { + attr1_referent: { credentialId, credentialInfo, revealed: true }, + attr2_referent: { credentialId, credentialInfo, revealed: true }, + }, + predicates: { + predicate1_referent: { credentialId, credentialInfo }, + }, + selfAttestedAttributes: {}, + }, + schemas: { [schemaState.schemaId]: schema }, + revocationRegistries: {}, + }) + + const verifiedProof = await anonCredsVerifierService.verifyProof(agentContext, { + credentialDefinitions: { [credentialDefinitionState.credentialDefinitionId]: credentialDefinition }, + proof, + proofRequest, + schemas: { [schemaState.schemaId]: schema }, + revocationRegistries: {}, + }) + + expect(verifiedProof).toBeTruthy() + }) + + test('issuance flow with unqualified identifiers', async () => { + // Use qualified identifiers to create schema and credential definition (we only support qualified identifiers for these) + const issuerId = 'did:indy:pool:localtest:A4CYPASJYRZRt98YWrac3H' + + const schema = await anonCredsIssuerService.createSchema(agentContext, { + attrNames: ['name', 'age'], + issuerId, + name: 'Employee Credential', + version: '1.0.0', + }) + + const { schemaState } = await registry.registerSchema(agentContext, { + schema, + options: {}, + }) + + const { credentialDefinition, credentialDefinitionPrivate, keyCorrectnessProof } = + await anonCredsIssuerService.createCredentialDefinition(agentContext, { + issuerId, + schemaId: schemaState.schemaId as string, + schema, + tag: 'Employee Credential', + supportRevocation: false, + }) + + const { credentialDefinitionState } = await registry.registerCredentialDefinition(agentContext, { + credentialDefinition, + options: {}, + }) + + if ( + !credentialDefinitionState.credentialDefinition || + !credentialDefinitionState.credentialDefinitionId || + !schemaState.schema || + !schemaState.schemaId + ) { + throw new Error('Failed to create schema or credential definition') + } + + if (!credentialDefinitionPrivate || !keyCorrectnessProof) { + throw new Error('Failed to get private part of credential definition') + } + + await agentContext.dependencyManager.resolve(AnonCredsSchemaRepository).save( + agentContext, + new AnonCredsSchemaRecord({ + schema: schemaState.schema, + schemaId: schemaState.schemaId, + methodName: 'inMemory', + }) + ) + + await agentContext.dependencyManager.resolve(AnonCredsCredentialDefinitionRepository).save( + agentContext, + new AnonCredsCredentialDefinitionRecord({ + credentialDefinition: credentialDefinitionState.credentialDefinition, + credentialDefinitionId: credentialDefinitionState.credentialDefinitionId, + methodName: 'inMemory', + }) + ) + + await agentContext.dependencyManager.resolve(AnonCredsCredentialDefinitionPrivateRepository).save( + agentContext, + new AnonCredsCredentialDefinitionPrivateRecord({ + value: credentialDefinitionPrivate, + credentialDefinitionId: credentialDefinitionState.credentialDefinitionId, + }) + ) + + await agentContext.dependencyManager.resolve(AnonCredsKeyCorrectnessProofRepository).save( + agentContext, + new AnonCredsKeyCorrectnessProofRecord({ + value: keyCorrectnessProof, + credentialDefinitionId: credentialDefinitionState.credentialDefinitionId, + }) + ) + + const { namespaceIdentifier, schemaSeqNo, tag } = parseIndyCredentialDefinitionId( + credentialDefinitionState.credentialDefinitionId + ) + const unqualifiedCredentialDefinitionId = getUnqualifiedCredentialDefinitionId( + namespaceIdentifier, + schemaSeqNo, + tag + ) + + const parsedSchema = parseIndySchemaId(schemaState.schemaId) + const unqualifiedSchemaId = getUnqualifiedSchemaId( + parsedSchema.namespaceIdentifier, + parsedSchema.schemaName, + parsedSchema.schemaVersion + ) + + // Create offer with unqualified credential definition id + const credentialOffer = await anonCredsIssuerService.createCredentialOffer(agentContext, { + credentialDefinitionId: unqualifiedCredentialDefinitionId, + }) + + const linkSecret = await anonCredsHolderService.createLinkSecret(agentContext, { linkSecretId: 'someLinkSecretId' }) + expect(linkSecret.linkSecretId).toBe('someLinkSecretId') + + await agentContext.dependencyManager.resolve(AnonCredsLinkSecretRepository).save( + agentContext, + new AnonCredsLinkSecretRecord({ + value: linkSecret.linkSecretValue, + linkSecretId: linkSecret.linkSecretId, + }) + ) + + const unqualifiedCredentialDefinition = await registry.getCredentialDefinition( + agentContext, + credentialOffer.cred_def_id + ) + const unqualifiedSchema = await registry.getSchema(agentContext, credentialOffer.schema_id) + if (!unqualifiedCredentialDefinition.credentialDefinition || !unqualifiedSchema.schema) { + throw new Error('unable to fetch credential definition or schema') + } + + const credentialRequestState = await anonCredsHolderService.createCredentialRequest(agentContext, { + credentialDefinition: unqualifiedCredentialDefinition.credentialDefinition, + credentialOffer, + linkSecretId: linkSecret.linkSecretId, + }) + + const { credential } = await anonCredsIssuerService.createCredential(agentContext, { + credentialOffer, + credentialRequest: credentialRequestState.credentialRequest, + credentialValues: { + name: { raw: 'John', encoded: encodeCredentialValue('John') }, + age: { raw: '25', encoded: encodeCredentialValue('25') }, + }, + }) + + const credentialId = 'holderCredentialId2' + + const storedId = await anonCredsHolderService.storeCredential(agentContext, { + credential, + credentialDefinition: unqualifiedCredentialDefinition.credentialDefinition, + schema: unqualifiedSchema.schema, + credentialDefinitionId: credentialOffer.cred_def_id, + credentialRequestMetadata: credentialRequestState.credentialRequestMetadata, + credentialId, + }) + + expect(storedId).toEqual(credentialId) + + const credentialInfo = await anonCredsHolderService.getCredential(agentContext, { + credentialId, + }) + + expect(credentialInfo).toEqual({ + credentialId, + attributes: { + age: '25', + name: 'John', + }, + schemaId: unqualifiedSchemaId, + credentialDefinitionId: unqualifiedCredentialDefinitionId, + revocationRegistryId: null, + credentialRevocationId: undefined, // Should it be null in this case? + methodName: 'inMemory', + }) + + const proofRequest: AnonCredsProofRequest = { + nonce: anoncreds.generateNonce(), + name: 'pres_req_1', + version: '0.1', + requested_attributes: { + attr1_referent: { + name: 'name', + }, + attr2_referent: { + name: 'age', + }, + }, + requested_predicates: { + predicate1_referent: { name: 'age', p_type: '>=' as const, p_value: 18 }, + }, + } + + const proof = await anonCredsHolderService.createProof(agentContext, { + credentialDefinitions: { [unqualifiedCredentialDefinitionId]: credentialDefinition }, + proofRequest, + selectedCredentials: { + attributes: { + attr1_referent: { credentialId, credentialInfo, revealed: true }, + attr2_referent: { credentialId, credentialInfo, revealed: true }, + }, + predicates: { + predicate1_referent: { credentialId, credentialInfo }, + }, + selfAttestedAttributes: {}, + }, + schemas: { [unqualifiedSchemaId]: schema }, + revocationRegistries: {}, + }) + + const verifiedProof = await anonCredsVerifierService.verifyProof(agentContext, { + credentialDefinitions: { [unqualifiedCredentialDefinitionId]: credentialDefinition }, + proof, + proofRequest, + schemas: { [unqualifiedSchemaId]: schema }, + revocationRegistries: {}, + }) + + expect(verifiedProof).toBeTruthy() + }) +}) diff --git a/packages/anoncreds-rs/src/services/__tests__/helpers.ts b/packages/anoncreds-rs/src/services/__tests__/helpers.ts new file mode 100644 index 0000000000..fefc63d9c1 --- /dev/null +++ b/packages/anoncreds-rs/src/services/__tests__/helpers.ts @@ -0,0 +1,198 @@ +import type { + AnonCredsCredential, + AnonCredsCredentialDefinition, + AnonCredsCredentialInfo, + AnonCredsCredentialOffer, +} from '@aries-framework/anoncreds' +import type { JsonObject } from '@hyperledger/anoncreds-nodejs' + +import { + anoncreds, + Credential, + CredentialDefinition, + CredentialOffer, + CredentialRequest, + CredentialRevocationConfig, + LinkSecret, + RevocationRegistryDefinition, + RevocationRegistryDefinitionPrivate, + RevocationStatusList, + Schema, +} from '@hyperledger/anoncreds-shared' + +/** + * Creates a valid credential definition and returns its public and + * private part, including its key correctness proof + */ +export function createCredentialDefinition(options: { attributeNames: string[]; issuerId: string }) { + const { attributeNames, issuerId } = options + + const schema = Schema.create({ + issuerId, + attributeNames, + name: 'schema1', + version: '1', + }) + + const { credentialDefinition, credentialDefinitionPrivate, keyCorrectnessProof } = CredentialDefinition.create({ + issuerId, + schema, + schemaId: 'schema:uri', + signatureType: 'CL', + supportRevocation: true, // FIXME: Revocation should not be mandatory but current anoncreds-rs is requiring it + tag: 'TAG', + }) + + const returnObj = { + credentialDefinition: credentialDefinition.toJson() as unknown as AnonCredsCredentialDefinition, + credentialDefinitionPrivate: credentialDefinitionPrivate.toJson() as unknown as JsonObject, + keyCorrectnessProof: keyCorrectnessProof.toJson() as unknown as JsonObject, + schema: schema.toJson() as unknown as Schema, + } + + credentialDefinition.handle.clear() + credentialDefinitionPrivate.handle.clear() + keyCorrectnessProof.handle.clear() + schema.handle.clear() + + return returnObj +} + +/** + * Creates a valid credential offer and returns itsf + */ +export function createCredentialOffer(keyCorrectnessProof: Record) { + const credentialOffer = CredentialOffer.create({ + credentialDefinitionId: 'creddef:uri', + keyCorrectnessProof, + schemaId: 'schema:uri', + }) + const credentialOfferJson = credentialOffer.toJson() as unknown as AnonCredsCredentialOffer + credentialOffer.handle.clear() + return credentialOfferJson +} + +/** + * + * @returns Creates a valid link secret value for anoncreds-rs + */ +export function createLinkSecret() { + return LinkSecret.create() +} + +export function createCredentialForHolder(options: { + credentialDefinition: JsonObject + credentialDefinitionPrivate: JsonObject + keyCorrectnessProof: JsonObject + schemaId: string + credentialDefinitionId: string + attributes: Record + linkSecret: string + linkSecretId: string + credentialId: string + revocationRegistryDefinitionId: string +}) { + const { + credentialDefinition, + credentialDefinitionPrivate, + keyCorrectnessProof, + schemaId, + credentialDefinitionId, + attributes, + linkSecret, + linkSecretId, + credentialId, + revocationRegistryDefinitionId, + } = options + + const credentialOffer = CredentialOffer.create({ + credentialDefinitionId, + keyCorrectnessProof, + schemaId, + }) + + const { credentialRequest, credentialRequestMetadata } = CredentialRequest.create({ + entropy: 'some-entropy', + credentialDefinition, + credentialOffer, + linkSecret, + linkSecretId: linkSecretId, + }) + + const { revocationRegistryDefinition, revocationRegistryDefinitionPrivate, tailsPath } = + createRevocationRegistryDefinition({ + credentialDefinitionId, + credentialDefinition, + }) + + const timeCreateRevStatusList = 12 + const revocationStatusList = RevocationStatusList.create({ + issuerId: credentialDefinition.issuerId as string, + timestamp: timeCreateRevStatusList, + issuanceByDefault: true, + revocationRegistryDefinition: new RevocationRegistryDefinition(revocationRegistryDefinition.handle), + revocationRegistryDefinitionId: 'mock:uri', + }) + + const credentialObj = Credential.create({ + credentialDefinition, + credentialDefinitionPrivate, + credentialOffer, + credentialRequest, + attributeRawValues: attributes, + revocationRegistryId: revocationRegistryDefinitionId, + revocationStatusList, + revocationConfiguration: new CredentialRevocationConfig({ + registryDefinition: new RevocationRegistryDefinition(revocationRegistryDefinition.handle), + registryDefinitionPrivate: new RevocationRegistryDefinitionPrivate(revocationRegistryDefinitionPrivate.handle), + registryIndex: 9, + tailsPath, + }), + }) + + const credentialInfo: AnonCredsCredentialInfo = { + attributes, + credentialDefinitionId, + credentialId, + schemaId, + methodName: 'inMemory', + } + const returnObj = { + credential: credentialObj.toJson() as unknown as AnonCredsCredential, + credentialInfo, + revocationRegistryDefinition, + tailsPath, + credentialRequestMetadata, + } + + credentialObj.handle.clear() + credentialOffer.handle.clear() + credentialRequest.handle.clear() + revocationRegistryDefinitionPrivate.clear() + revocationStatusList.handle.clear() + + return returnObj +} + +export function createRevocationRegistryDefinition(options: { + credentialDefinitionId: string + credentialDefinition: Record +}) { + const { credentialDefinitionId, credentialDefinition } = options + const { revocationRegistryDefinition, revocationRegistryDefinitionPrivate } = + anoncreds.createRevocationRegistryDefinition({ + credentialDefinitionId, + credentialDefinition: CredentialDefinition.fromJson(credentialDefinition).handle, + issuerId: credentialDefinition.issuerId as string, + tag: 'some_tag', + revocationRegistryType: 'CL_ACCUM', + maximumCredentialNumber: 10, + }) + + const tailsPath = anoncreds.revocationRegistryDefinitionGetAttribute({ + objectHandle: revocationRegistryDefinition, + name: 'tails_location', + }) + + return { revocationRegistryDefinition, revocationRegistryDefinitionPrivate, tailsPath } +} diff --git a/packages/anoncreds-rs/src/services/index.ts b/packages/anoncreds-rs/src/services/index.ts new file mode 100644 index 0000000000..b675ab0025 --- /dev/null +++ b/packages/anoncreds-rs/src/services/index.ts @@ -0,0 +1,3 @@ +export { AnonCredsRsHolderService } from './AnonCredsRsHolderService' +export { AnonCredsRsIssuerService } from './AnonCredsRsIssuerService' +export { AnonCredsRsVerifierService } from './AnonCredsRsVerifierService' diff --git a/packages/anoncreds-rs/tests/anoncreds-flow.test.ts b/packages/anoncreds-rs/tests/anoncreds-flow.test.ts new file mode 100644 index 0000000000..b3465a543d --- /dev/null +++ b/packages/anoncreds-rs/tests/anoncreds-flow.test.ts @@ -0,0 +1,361 @@ +import type { AnonCredsCredentialRequest } from '@aries-framework/anoncreds' +import type { Wallet } from '@aries-framework/core' + +import { + AnonCredsModuleConfig, + AnonCredsHolderServiceSymbol, + AnonCredsIssuerServiceSymbol, + AnonCredsVerifierServiceSymbol, + AnonCredsSchemaRecord, + AnonCredsSchemaRepository, + AnonCredsCredentialDefinitionRepository, + AnonCredsCredentialDefinitionRecord, + AnonCredsCredentialDefinitionPrivateRepository, + AnonCredsCredentialDefinitionPrivateRecord, + AnonCredsKeyCorrectnessProofRepository, + AnonCredsKeyCorrectnessProofRecord, + AnonCredsLinkSecretRepository, + AnonCredsLinkSecretRecord, + AnonCredsProofFormatService, + AnonCredsCredentialFormatService, +} from '@aries-framework/anoncreds' +import { + CredentialState, + CredentialExchangeRecord, + CredentialPreviewAttribute, + InjectionSymbols, + ProofState, + ProofExchangeRecord, +} from '@aries-framework/core' +import { Subject } from 'rxjs' + +import { InMemoryStorageService } from '../../../tests/InMemoryStorageService' +import { describeRunInNodeVersion } from '../../../tests/runInVersion' +import { AnonCredsRegistryService } from '../../anoncreds/src/services/registry/AnonCredsRegistryService' +import { InMemoryAnonCredsRegistry } from '../../anoncreds/tests/InMemoryAnonCredsRegistry' +import { agentDependencies, getAgentConfig, getAgentContext } from '../../core/tests/helpers' +import { AnonCredsRsHolderService } from '../src/services/AnonCredsRsHolderService' +import { AnonCredsRsIssuerService } from '../src/services/AnonCredsRsIssuerService' +import { AnonCredsRsVerifierService } from '../src/services/AnonCredsRsVerifierService' + +const registry = new InMemoryAnonCredsRegistry() +const anonCredsModuleConfig = new AnonCredsModuleConfig({ + registries: [registry], +}) + +const agentConfig = getAgentConfig('AnonCreds format services using anoncreds-rs') +const anonCredsVerifierService = new AnonCredsRsVerifierService() +const anonCredsHolderService = new AnonCredsRsHolderService() +const anonCredsIssuerService = new AnonCredsRsIssuerService() + +const wallet = { generateNonce: () => Promise.resolve('947121108704767252195123') } as Wallet + +const inMemoryStorageService = new InMemoryStorageService() +const agentContext = getAgentContext({ + registerInstances: [ + [InjectionSymbols.Stop$, new Subject()], + [InjectionSymbols.AgentDependencies, agentDependencies], + [InjectionSymbols.StorageService, inMemoryStorageService], + [AnonCredsIssuerServiceSymbol, anonCredsIssuerService], + [AnonCredsHolderServiceSymbol, anonCredsHolderService], + [AnonCredsVerifierServiceSymbol, anonCredsVerifierService], + [AnonCredsRegistryService, new AnonCredsRegistryService()], + [AnonCredsModuleConfig, anonCredsModuleConfig], + ], + agentConfig, + wallet, +}) + +const anoncredsCredentialFormatService = new AnonCredsCredentialFormatService() +const anoncredsProofFormatService = new AnonCredsProofFormatService() + +const indyDid = 'did:indy:local:LjgpST2rjsoxYegQDRm7EL' + +// FIXME: Re-include in tests when NodeJS wrapper performance is improved +describeRunInNodeVersion([18], 'AnonCreds format services using anoncreds-rs', () => { + test('issuance and verification flow starting from proposal without negotiation and without revocation', async () => { + const schema = await anonCredsIssuerService.createSchema(agentContext, { + attrNames: ['name', 'age'], + issuerId: indyDid, + name: 'Employee Credential', + version: '1.0.0', + }) + + const { schemaState } = await registry.registerSchema(agentContext, { + schema, + options: {}, + }) + + const { credentialDefinition, credentialDefinitionPrivate, keyCorrectnessProof } = + await anonCredsIssuerService.createCredentialDefinition(agentContext, { + issuerId: indyDid, + schemaId: schemaState.schemaId as string, + schema, + tag: 'Employee Credential', + supportRevocation: false, + }) + + const { credentialDefinitionState } = await registry.registerCredentialDefinition(agentContext, { + credentialDefinition, + options: {}, + }) + + if ( + !credentialDefinitionState.credentialDefinition || + !credentialDefinitionState.credentialDefinitionId || + !schemaState.schema || + !schemaState.schemaId + ) { + throw new Error('Failed to create schema or credential definition') + } + + if ( + !credentialDefinitionState.credentialDefinition || + !credentialDefinitionState.credentialDefinitionId || + !schemaState.schema || + !schemaState.schemaId + ) { + throw new Error('Failed to create schema or credential definition') + } + + if (!credentialDefinitionPrivate || !keyCorrectnessProof) { + throw new Error('Failed to get private part of credential definition') + } + + await agentContext.dependencyManager.resolve(AnonCredsSchemaRepository).save( + agentContext, + new AnonCredsSchemaRecord({ + schema: schemaState.schema, + schemaId: schemaState.schemaId, + methodName: 'inMemory', + }) + ) + + await agentContext.dependencyManager.resolve(AnonCredsCredentialDefinitionRepository).save( + agentContext, + new AnonCredsCredentialDefinitionRecord({ + credentialDefinition: credentialDefinitionState.credentialDefinition, + credentialDefinitionId: credentialDefinitionState.credentialDefinitionId, + methodName: 'inMemory', + }) + ) + + await agentContext.dependencyManager.resolve(AnonCredsCredentialDefinitionPrivateRepository).save( + agentContext, + new AnonCredsCredentialDefinitionPrivateRecord({ + value: credentialDefinitionPrivate, + credentialDefinitionId: credentialDefinitionState.credentialDefinitionId, + }) + ) + + await agentContext.dependencyManager.resolve(AnonCredsKeyCorrectnessProofRepository).save( + agentContext, + new AnonCredsKeyCorrectnessProofRecord({ + value: keyCorrectnessProof, + credentialDefinitionId: credentialDefinitionState.credentialDefinitionId, + }) + ) + + const linkSecret = await anonCredsHolderService.createLinkSecret(agentContext, { linkSecretId: 'linkSecretId' }) + expect(linkSecret.linkSecretId).toBe('linkSecretId') + + await agentContext.dependencyManager.resolve(AnonCredsLinkSecretRepository).save( + agentContext, + new AnonCredsLinkSecretRecord({ + value: linkSecret.linkSecretValue, + linkSecretId: linkSecret.linkSecretId, + }) + ) + + const holderCredentialRecord = new CredentialExchangeRecord({ + protocolVersion: 'v1', + state: CredentialState.ProposalSent, + threadId: 'f365c1a5-2baf-4873-9432-fa87c888a0aa', + }) + + const issuerCredentialRecord = new CredentialExchangeRecord({ + protocolVersion: 'v1', + state: CredentialState.ProposalReceived, + threadId: 'f365c1a5-2baf-4873-9432-fa87c888a0aa', + }) + + const credentialAttributes = [ + new CredentialPreviewAttribute({ + name: 'name', + value: 'John', + }), + new CredentialPreviewAttribute({ + name: 'age', + value: '25', + }), + ] + + // Holder creates proposal + holderCredentialRecord.credentialAttributes = credentialAttributes + const { attachment: proposalAttachment } = await anoncredsCredentialFormatService.createProposal(agentContext, { + credentialRecord: holderCredentialRecord, + credentialFormats: { + anoncreds: { + attributes: credentialAttributes, + credentialDefinitionId: credentialDefinitionState.credentialDefinitionId, + }, + }, + }) + + // Issuer processes and accepts proposal + await anoncredsCredentialFormatService.processProposal(agentContext, { + credentialRecord: issuerCredentialRecord, + attachment: proposalAttachment, + }) + // Set attributes on the credential record, this is normally done by the protocol service + issuerCredentialRecord.credentialAttributes = credentialAttributes + const { attachment: offerAttachment } = await anoncredsCredentialFormatService.acceptProposal(agentContext, { + credentialRecord: issuerCredentialRecord, + proposalAttachment: proposalAttachment, + }) + + // Holder processes and accepts offer + await anoncredsCredentialFormatService.processOffer(agentContext, { + credentialRecord: holderCredentialRecord, + attachment: offerAttachment, + }) + const { attachment: requestAttachment } = await anoncredsCredentialFormatService.acceptOffer(agentContext, { + credentialRecord: holderCredentialRecord, + offerAttachment, + credentialFormats: { + anoncreds: { + linkSecretId: linkSecret.linkSecretId, + }, + }, + }) + + // Make sure the request contains an entropy and does not contain a prover_did field + expect((requestAttachment.getDataAsJson() as AnonCredsCredentialRequest).entropy).toBeDefined() + expect((requestAttachment.getDataAsJson() as AnonCredsCredentialRequest).prover_did).toBeUndefined() + + // Issuer processes and accepts request + await anoncredsCredentialFormatService.processRequest(agentContext, { + credentialRecord: issuerCredentialRecord, + attachment: requestAttachment, + }) + const { attachment: credentialAttachment } = await anoncredsCredentialFormatService.acceptRequest(agentContext, { + credentialRecord: issuerCredentialRecord, + requestAttachment, + offerAttachment, + }) + + // Holder processes and accepts credential + await anoncredsCredentialFormatService.processCredential(agentContext, { + credentialRecord: holderCredentialRecord, + attachment: credentialAttachment, + requestAttachment, + }) + + expect(holderCredentialRecord.credentials).toEqual([ + { credentialRecordType: 'anoncreds', credentialRecordId: expect.any(String) }, + ]) + + const credentialId = holderCredentialRecord.credentials[0].credentialRecordId + const anonCredsCredential = await anonCredsHolderService.getCredential(agentContext, { + credentialId, + }) + + expect(anonCredsCredential).toEqual({ + credentialId, + attributes: { + age: '25', + name: 'John', + }, + schemaId: schemaState.schemaId, + credentialDefinitionId: credentialDefinitionState.credentialDefinitionId, + revocationRegistryId: null, + credentialRevocationId: undefined, // FIXME: should be null? + methodName: 'inMemory', + }) + + expect(holderCredentialRecord.metadata.data).toEqual({ + '_anoncreds/credential': { + schemaId: schemaState.schemaId, + credentialDefinitionId: credentialDefinitionState.credentialDefinitionId, + }, + '_anoncreds/credentialRequest': { + link_secret_blinding_data: expect.any(Object), + link_secret_name: expect.any(String), + nonce: expect.any(String), + }, + }) + + expect(issuerCredentialRecord.metadata.data).toEqual({ + '_anoncreds/credential': { + schemaId: schemaState.schemaId, + credentialDefinitionId: credentialDefinitionState.credentialDefinitionId, + }, + }) + + const holderProofRecord = new ProofExchangeRecord({ + protocolVersion: 'v1', + state: ProofState.ProposalSent, + threadId: '4f5659a4-1aea-4f42-8c22-9a9985b35e38', + }) + const verifierProofRecord = new ProofExchangeRecord({ + protocolVersion: 'v1', + state: ProofState.ProposalReceived, + threadId: '4f5659a4-1aea-4f42-8c22-9a9985b35e38', + }) + + const { attachment: proofProposalAttachment } = await anoncredsProofFormatService.createProposal(agentContext, { + proofFormats: { + anoncreds: { + attributes: [ + { + name: 'name', + credentialDefinitionId: credentialDefinitionState.credentialDefinitionId, + value: 'John', + referent: '1', + }, + ], + predicates: [ + { + credentialDefinitionId: credentialDefinitionState.credentialDefinitionId, + name: 'age', + predicate: '>=', + threshold: 18, + }, + ], + name: 'Proof Request', + version: '1.0', + }, + }, + proofRecord: holderProofRecord, + }) + + await anoncredsProofFormatService.processProposal(agentContext, { + attachment: proofProposalAttachment, + proofRecord: verifierProofRecord, + }) + + const { attachment: proofRequestAttachment } = await anoncredsProofFormatService.acceptProposal(agentContext, { + proofRecord: verifierProofRecord, + proposalAttachment: proofProposalAttachment, + }) + + await anoncredsProofFormatService.processRequest(agentContext, { + attachment: proofRequestAttachment, + proofRecord: holderProofRecord, + }) + + const { attachment: proofAttachment } = await anoncredsProofFormatService.acceptRequest(agentContext, { + proofRecord: holderProofRecord, + requestAttachment: proofRequestAttachment, + proposalAttachment: proofProposalAttachment, + }) + + const isValid = await anoncredsProofFormatService.processPresentation(agentContext, { + attachment: proofAttachment, + proofRecord: verifierProofRecord, + requestAttachment: proofRequestAttachment, + }) + + expect(isValid).toBe(true) + }) +}) diff --git a/packages/anoncreds-rs/tests/indy-flow.test.ts b/packages/anoncreds-rs/tests/indy-flow.test.ts new file mode 100644 index 0000000000..e254ee7dc6 --- /dev/null +++ b/packages/anoncreds-rs/tests/indy-flow.test.ts @@ -0,0 +1,379 @@ +import type { AnonCredsCredentialRequest } from '@aries-framework/anoncreds' +import type { Wallet } from '@aries-framework/core' + +import { + getUnqualifiedSchemaId, + parseIndySchemaId, + getUnqualifiedCredentialDefinitionId, + parseIndyCredentialDefinitionId, + AnonCredsModuleConfig, + LegacyIndyCredentialFormatService, + AnonCredsHolderServiceSymbol, + AnonCredsIssuerServiceSymbol, + AnonCredsVerifierServiceSymbol, + AnonCredsSchemaRecord, + AnonCredsSchemaRepository, + AnonCredsCredentialDefinitionRepository, + AnonCredsCredentialDefinitionRecord, + AnonCredsCredentialDefinitionPrivateRepository, + AnonCredsCredentialDefinitionPrivateRecord, + AnonCredsKeyCorrectnessProofRepository, + AnonCredsKeyCorrectnessProofRecord, + AnonCredsLinkSecretRepository, + AnonCredsLinkSecretRecord, + LegacyIndyProofFormatService, +} from '@aries-framework/anoncreds' +import { + CredentialState, + CredentialExchangeRecord, + CredentialPreviewAttribute, + InjectionSymbols, + ProofState, + ProofExchangeRecord, +} from '@aries-framework/core' +import { Subject } from 'rxjs' + +import { InMemoryStorageService } from '../../../tests/InMemoryStorageService' +import { describeRunInNodeVersion } from '../../../tests/runInVersion' +import { AnonCredsRegistryService } from '../../anoncreds/src/services/registry/AnonCredsRegistryService' +import { InMemoryAnonCredsRegistry } from '../../anoncreds/tests/InMemoryAnonCredsRegistry' +import { agentDependencies, getAgentConfig, getAgentContext } from '../../core/tests/helpers' +import { AnonCredsRsHolderService } from '../src/services/AnonCredsRsHolderService' +import { AnonCredsRsIssuerService } from '../src/services/AnonCredsRsIssuerService' +import { AnonCredsRsVerifierService } from '../src/services/AnonCredsRsVerifierService' + +const registry = new InMemoryAnonCredsRegistry() +const anonCredsModuleConfig = new AnonCredsModuleConfig({ + registries: [registry], +}) + +const agentConfig = getAgentConfig('LegacyIndyCredentialFormatService using anoncreds-rs') +const anonCredsVerifierService = new AnonCredsRsVerifierService() +const anonCredsHolderService = new AnonCredsRsHolderService() +const anonCredsIssuerService = new AnonCredsRsIssuerService() + +const wallet = { generateNonce: () => Promise.resolve('947121108704767252195123') } as Wallet + +const inMemoryStorageService = new InMemoryStorageService() +const agentContext = getAgentContext({ + registerInstances: [ + [InjectionSymbols.Stop$, new Subject()], + [InjectionSymbols.AgentDependencies, agentDependencies], + [InjectionSymbols.StorageService, inMemoryStorageService], + [AnonCredsIssuerServiceSymbol, anonCredsIssuerService], + [AnonCredsHolderServiceSymbol, anonCredsHolderService], + [AnonCredsVerifierServiceSymbol, anonCredsVerifierService], + [AnonCredsRegistryService, new AnonCredsRegistryService()], + [AnonCredsModuleConfig, anonCredsModuleConfig], + ], + agentConfig, + wallet, +}) + +const legacyIndyCredentialFormatService = new LegacyIndyCredentialFormatService() +const legacyIndyProofFormatService = new LegacyIndyProofFormatService() + +// This is just so we don't have to register an actually indy did (as we don't have the indy did registrar configured) +const indyDid = 'did:indy:bcovrin:test:LjgpST2rjsoxYegQDRm7EL' + +// FIXME: Re-include in tests when NodeJS wrapper performance is improved +describeRunInNodeVersion([18], 'Legacy indy format services using anoncreds-rs', () => { + test('issuance and verification flow starting from proposal without negotiation and without revocation', async () => { + const schema = await anonCredsIssuerService.createSchema(agentContext, { + attrNames: ['name', 'age'], + issuerId: indyDid, + name: 'Employee Credential', + version: '1.0.0', + }) + + const { schemaState } = await registry.registerSchema(agentContext, { + schema, + options: {}, + }) + + const { credentialDefinition, credentialDefinitionPrivate, keyCorrectnessProof } = + await anonCredsIssuerService.createCredentialDefinition(agentContext, { + issuerId: indyDid, + schemaId: schemaState.schemaId as string, + schema, + tag: 'Employee Credential', + supportRevocation: false, + }) + + const { credentialDefinitionState } = await registry.registerCredentialDefinition(agentContext, { + credentialDefinition, + options: {}, + }) + + if ( + !credentialDefinitionState.credentialDefinition || + !credentialDefinitionState.credentialDefinitionId || + !schemaState.schema || + !schemaState.schemaId + ) { + throw new Error('Failed to create schema or credential definition') + } + + if ( + !credentialDefinitionState.credentialDefinition || + !credentialDefinitionState.credentialDefinitionId || + !schemaState.schema || + !schemaState.schemaId + ) { + throw new Error('Failed to create schema or credential definition') + } + + if (!credentialDefinitionPrivate || !keyCorrectnessProof) { + throw new Error('Failed to get private part of credential definition') + } + + await agentContext.dependencyManager.resolve(AnonCredsSchemaRepository).save( + agentContext, + new AnonCredsSchemaRecord({ + schema: schemaState.schema, + schemaId: schemaState.schemaId, + methodName: 'inMemory', + }) + ) + + await agentContext.dependencyManager.resolve(AnonCredsCredentialDefinitionRepository).save( + agentContext, + new AnonCredsCredentialDefinitionRecord({ + credentialDefinition: credentialDefinitionState.credentialDefinition, + credentialDefinitionId: credentialDefinitionState.credentialDefinitionId, + methodName: 'inMemory', + }) + ) + + await agentContext.dependencyManager.resolve(AnonCredsCredentialDefinitionPrivateRepository).save( + agentContext, + new AnonCredsCredentialDefinitionPrivateRecord({ + value: credentialDefinitionPrivate, + credentialDefinitionId: credentialDefinitionState.credentialDefinitionId, + }) + ) + + await agentContext.dependencyManager.resolve(AnonCredsKeyCorrectnessProofRepository).save( + agentContext, + new AnonCredsKeyCorrectnessProofRecord({ + value: keyCorrectnessProof, + credentialDefinitionId: credentialDefinitionState.credentialDefinitionId, + }) + ) + + const linkSecret = await anonCredsHolderService.createLinkSecret(agentContext, { linkSecretId: 'linkSecretId' }) + expect(linkSecret.linkSecretId).toBe('linkSecretId') + + await agentContext.dependencyManager.resolve(AnonCredsLinkSecretRepository).save( + agentContext, + new AnonCredsLinkSecretRecord({ + value: linkSecret.linkSecretValue, + linkSecretId: linkSecret.linkSecretId, + }) + ) + + const holderCredentialRecord = new CredentialExchangeRecord({ + protocolVersion: 'v1', + state: CredentialState.ProposalSent, + threadId: 'f365c1a5-2baf-4873-9432-fa87c888a0aa', + }) + + const issuerCredentialRecord = new CredentialExchangeRecord({ + protocolVersion: 'v1', + state: CredentialState.ProposalReceived, + threadId: 'f365c1a5-2baf-4873-9432-fa87c888a0aa', + }) + + const credentialAttributes = [ + new CredentialPreviewAttribute({ + name: 'name', + value: 'John', + }), + new CredentialPreviewAttribute({ + name: 'age', + value: '25', + }), + ] + + const parsedCredentialDefinition = parseIndyCredentialDefinitionId(credentialDefinitionState.credentialDefinitionId) + const unqualifiedCredentialDefinitionId = getUnqualifiedCredentialDefinitionId( + parsedCredentialDefinition.namespaceIdentifier, + parsedCredentialDefinition.schemaSeqNo, + parsedCredentialDefinition.tag + ) + + const parsedSchemaId = parseIndySchemaId(schemaState.schemaId) + const unqualifiedSchemaId = getUnqualifiedSchemaId( + parsedSchemaId.namespaceIdentifier, + parsedSchemaId.schemaName, + parsedSchemaId.schemaVersion + ) + + // Holder creates proposal + holderCredentialRecord.credentialAttributes = credentialAttributes + const { attachment: proposalAttachment } = await legacyIndyCredentialFormatService.createProposal(agentContext, { + credentialRecord: holderCredentialRecord, + credentialFormats: { + indy: { + attributes: credentialAttributes, + credentialDefinitionId: unqualifiedCredentialDefinitionId, + }, + }, + }) + + // Issuer processes and accepts proposal + await legacyIndyCredentialFormatService.processProposal(agentContext, { + credentialRecord: issuerCredentialRecord, + attachment: proposalAttachment, + }) + // Set attributes on the credential record, this is normally done by the protocol service + issuerCredentialRecord.credentialAttributes = credentialAttributes + const { attachment: offerAttachment } = await legacyIndyCredentialFormatService.acceptProposal(agentContext, { + credentialRecord: issuerCredentialRecord, + proposalAttachment: proposalAttachment, + }) + + // Holder processes and accepts offer + await legacyIndyCredentialFormatService.processOffer(agentContext, { + credentialRecord: holderCredentialRecord, + attachment: offerAttachment, + }) + const { attachment: requestAttachment } = await legacyIndyCredentialFormatService.acceptOffer(agentContext, { + credentialRecord: holderCredentialRecord, + offerAttachment, + credentialFormats: { + indy: { + linkSecretId: linkSecret.linkSecretId, + }, + }, + }) + + // Make sure the request contains a prover_did field + expect((requestAttachment.getDataAsJson() as AnonCredsCredentialRequest).prover_did).toBeDefined() + + // Issuer processes and accepts request + await legacyIndyCredentialFormatService.processRequest(agentContext, { + credentialRecord: issuerCredentialRecord, + attachment: requestAttachment, + }) + const { attachment: credentialAttachment } = await legacyIndyCredentialFormatService.acceptRequest(agentContext, { + credentialRecord: issuerCredentialRecord, + requestAttachment, + offerAttachment, + }) + + // Holder processes and accepts credential + await legacyIndyCredentialFormatService.processCredential(agentContext, { + credentialRecord: holderCredentialRecord, + attachment: credentialAttachment, + requestAttachment, + }) + + expect(holderCredentialRecord.credentials).toEqual([ + { credentialRecordType: 'anoncreds', credentialRecordId: expect.any(String) }, + ]) + + const credentialId = holderCredentialRecord.credentials[0].credentialRecordId + const anonCredsCredential = await anonCredsHolderService.getCredential(agentContext, { + credentialId, + }) + + expect(anonCredsCredential).toEqual({ + credentialId, + attributes: { + age: '25', + name: 'John', + }, + schemaId: unqualifiedSchemaId, + credentialDefinitionId: unqualifiedCredentialDefinitionId, + revocationRegistryId: null, + credentialRevocationId: undefined, // FIXME: should be null? + methodName: 'inMemory', + }) + + expect(holderCredentialRecord.metadata.data).toEqual({ + '_anoncreds/credential': { + schemaId: unqualifiedSchemaId, + credentialDefinitionId: unqualifiedCredentialDefinitionId, + }, + '_anoncreds/credentialRequest': { + link_secret_blinding_data: expect.any(Object), + link_secret_name: expect.any(String), + nonce: expect.any(String), + }, + }) + + expect(issuerCredentialRecord.metadata.data).toEqual({ + '_anoncreds/credential': { + schemaId: unqualifiedSchemaId, + credentialDefinitionId: unqualifiedCredentialDefinitionId, + }, + }) + + const holderProofRecord = new ProofExchangeRecord({ + protocolVersion: 'v1', + state: ProofState.ProposalSent, + threadId: '4f5659a4-1aea-4f42-8c22-9a9985b35e38', + }) + const verifierProofRecord = new ProofExchangeRecord({ + protocolVersion: 'v1', + state: ProofState.ProposalReceived, + threadId: '4f5659a4-1aea-4f42-8c22-9a9985b35e38', + }) + + const { attachment: proofProposalAttachment } = await legacyIndyProofFormatService.createProposal(agentContext, { + proofFormats: { + indy: { + attributes: [ + { + name: 'name', + credentialDefinitionId: unqualifiedCredentialDefinitionId, + value: 'John', + referent: '1', + }, + ], + predicates: [ + { + credentialDefinitionId: unqualifiedCredentialDefinitionId, + name: 'age', + predicate: '>=', + threshold: 18, + }, + ], + name: 'Proof Request', + version: '1.0', + }, + }, + proofRecord: holderProofRecord, + }) + + await legacyIndyProofFormatService.processProposal(agentContext, { + attachment: proofProposalAttachment, + proofRecord: verifierProofRecord, + }) + + const { attachment: proofRequestAttachment } = await legacyIndyProofFormatService.acceptProposal(agentContext, { + proofRecord: verifierProofRecord, + proposalAttachment: proofProposalAttachment, + }) + + await legacyIndyProofFormatService.processRequest(agentContext, { + attachment: proofRequestAttachment, + proofRecord: holderProofRecord, + }) + + const { attachment: proofAttachment } = await legacyIndyProofFormatService.acceptRequest(agentContext, { + proofRecord: holderProofRecord, + requestAttachment: proofRequestAttachment, + proposalAttachment: proofProposalAttachment, + }) + + const isValid = await legacyIndyProofFormatService.processPresentation(agentContext, { + attachment: proofAttachment, + proofRecord: verifierProofRecord, + requestAttachment: proofRequestAttachment, + }) + + expect(isValid).toBe(true) + }) +}) diff --git a/packages/anoncreds-rs/tests/setup.ts b/packages/anoncreds-rs/tests/setup.ts new file mode 100644 index 0000000000..4760c40357 --- /dev/null +++ b/packages/anoncreds-rs/tests/setup.ts @@ -0,0 +1,4 @@ +import '@hyperledger/anoncreds-nodejs' +import 'reflect-metadata' + +jest.setTimeout(120000) diff --git a/packages/didcomm-v2/tsconfig.build.json b/packages/anoncreds-rs/tsconfig.build.json similarity index 98% rename from packages/didcomm-v2/tsconfig.build.json rename to packages/anoncreds-rs/tsconfig.build.json index 9c30e30bd2..2b75d0adab 100644 --- a/packages/didcomm-v2/tsconfig.build.json +++ b/packages/anoncreds-rs/tsconfig.build.json @@ -1,9 +1,7 @@ { "extends": "../../tsconfig.build.json", - "compilerOptions": { "outDir": "./build" }, - "include": ["src/**/*"] } diff --git a/packages/didcomm-v2/tsconfig.json b/packages/anoncreds-rs/tsconfig.json similarity index 100% rename from packages/didcomm-v2/tsconfig.json rename to packages/anoncreds-rs/tsconfig.json diff --git a/packages/anoncreds/README.md b/packages/anoncreds/README.md new file mode 100644 index 0000000000..5bf5e5fbb0 --- /dev/null +++ b/packages/anoncreds/README.md @@ -0,0 +1,35 @@ +

+
+ Hyperledger Aries logo +

+

Aries Framework JavaScript AnonCreds Interfaces

+

+ License + typescript + @aries-framework/anoncreds version + +

+
+ +### Installation + +### Quick start + +### Example of usage diff --git a/packages/anoncreds/jest.config.ts b/packages/anoncreds/jest.config.ts new file mode 100644 index 0000000000..93c0197296 --- /dev/null +++ b/packages/anoncreds/jest.config.ts @@ -0,0 +1,13 @@ +import type { Config } from '@jest/types' + +import base from '../../jest.config.base' + +import packageJson from './package.json' + +const config: Config.InitialOptions = { + ...base, + displayName: packageJson.name, + setupFilesAfterEnv: ['./tests/setup.ts'], +} + +export default config diff --git a/packages/anoncreds/package.json b/packages/anoncreds/package.json new file mode 100644 index 0000000000..fabc377594 --- /dev/null +++ b/packages/anoncreds/package.json @@ -0,0 +1,41 @@ +{ + "name": "@aries-framework/anoncreds", + "main": "build/index", + "types": "build/index", + "version": "0.3.3", + "files": [ + "build" + ], + "license": "Apache-2.0", + "publishConfig": { + "access": "public" + }, + "homepage": "https://github.com/hyperledger/aries-framework-javascript/tree/main/packages/anoncreds", + "repository": { + "type": "git", + "url": "https://github.com/hyperledger/aries-framework-javascript", + "directory": "packages/anoncreds" + }, + "scripts": { + "build": "yarn run clean && yarn run compile", + "clean": "rimraf ./build", + "compile": "tsc -p tsconfig.build.json", + "prepublishOnly": "yarn run build", + "test": "jest" + }, + "dependencies": { + "@aries-framework/core": "0.3.3", + "bn.js": "^5.2.1", + "class-transformer": "0.5.1", + "class-validator": "0.14.0", + "reflect-metadata": "^0.1.13" + }, + "devDependencies": { + "@aries-framework/node": "0.3.3", + "@hyperledger/anoncreds-nodejs": "^0.1.0-dev.15", + "indy-sdk": "^1.16.0-dev-1636", + "rimraf": "^4.4.0", + "rxjs": "^7.8.0", + "typescript": "~4.9.5" + } +} diff --git a/packages/anoncreds/src/AnonCredsApi.ts b/packages/anoncreds/src/AnonCredsApi.ts new file mode 100644 index 0000000000..ede2e691e3 --- /dev/null +++ b/packages/anoncreds/src/AnonCredsApi.ts @@ -0,0 +1,452 @@ +import type { + AnonCredsCreateLinkSecretOptions, + AnonCredsRegisterCredentialDefinitionOptions, +} from './AnonCredsApiOptions' +import type { + GetCredentialDefinitionReturn, + GetRevocationStatusListReturn, + GetRevocationRegistryDefinitionReturn, + GetSchemaReturn, + RegisterCredentialDefinitionReturn, + RegisterSchemaOptions, + RegisterSchemaReturn, + AnonCredsRegistry, + GetCredentialsOptions, +} from './services' +import type { Extensible } from './services/registry/base' +import type { SimpleQuery } from '@aries-framework/core' + +import { AgentContext, inject, injectable } from '@aries-framework/core' + +import { AnonCredsModuleConfig } from './AnonCredsModuleConfig' +import { AnonCredsStoreRecordError } from './error' +import { + AnonCredsCredentialDefinitionPrivateRecord, + AnonCredsCredentialDefinitionPrivateRepository, + AnonCredsKeyCorrectnessProofRecord, + AnonCredsKeyCorrectnessProofRepository, + AnonCredsLinkSecretRecord, + AnonCredsLinkSecretRepository, +} from './repository' +import { AnonCredsCredentialDefinitionRecord } from './repository/AnonCredsCredentialDefinitionRecord' +import { AnonCredsCredentialDefinitionRepository } from './repository/AnonCredsCredentialDefinitionRepository' +import { AnonCredsSchemaRecord } from './repository/AnonCredsSchemaRecord' +import { AnonCredsSchemaRepository } from './repository/AnonCredsSchemaRepository' +import { AnonCredsCredentialDefinitionRecordMetadataKeys } from './repository/anonCredsCredentialDefinitionRecordMetadataTypes' +import { + AnonCredsHolderServiceSymbol, + AnonCredsIssuerServiceSymbol, + AnonCredsIssuerService, + AnonCredsHolderService, +} from './services' +import { AnonCredsRegistryService } from './services/registry/AnonCredsRegistryService' + +@injectable() +export class AnonCredsApi { + public config: AnonCredsModuleConfig + + private agentContext: AgentContext + private anonCredsRegistryService: AnonCredsRegistryService + private anonCredsSchemaRepository: AnonCredsSchemaRepository + private anonCredsCredentialDefinitionRepository: AnonCredsCredentialDefinitionRepository + private anonCredsCredentialDefinitionPrivateRepository: AnonCredsCredentialDefinitionPrivateRepository + private anonCredsKeyCorrectnessProofRepository: AnonCredsKeyCorrectnessProofRepository + private anonCredsLinkSecretRepository: AnonCredsLinkSecretRepository + private anonCredsIssuerService: AnonCredsIssuerService + private anonCredsHolderService: AnonCredsHolderService + + public constructor( + agentContext: AgentContext, + anonCredsRegistryService: AnonCredsRegistryService, + config: AnonCredsModuleConfig, + @inject(AnonCredsIssuerServiceSymbol) anonCredsIssuerService: AnonCredsIssuerService, + @inject(AnonCredsHolderServiceSymbol) anonCredsHolderService: AnonCredsHolderService, + anonCredsSchemaRepository: AnonCredsSchemaRepository, + anonCredsCredentialDefinitionRepository: AnonCredsCredentialDefinitionRepository, + anonCredsCredentialDefinitionPrivateRepository: AnonCredsCredentialDefinitionPrivateRepository, + anonCredsKeyCorrectnessProofRepository: AnonCredsKeyCorrectnessProofRepository, + anonCredsLinkSecretRepository: AnonCredsLinkSecretRepository + ) { + this.agentContext = agentContext + this.anonCredsRegistryService = anonCredsRegistryService + this.config = config + this.anonCredsIssuerService = anonCredsIssuerService + this.anonCredsHolderService = anonCredsHolderService + this.anonCredsSchemaRepository = anonCredsSchemaRepository + this.anonCredsCredentialDefinitionRepository = anonCredsCredentialDefinitionRepository + this.anonCredsCredentialDefinitionPrivateRepository = anonCredsCredentialDefinitionPrivateRepository + this.anonCredsKeyCorrectnessProofRepository = anonCredsKeyCorrectnessProofRepository + this.anonCredsLinkSecretRepository = anonCredsLinkSecretRepository + } + + /** + * Create a Link Secret, optionally indicating its ID and if it will be the default one + * If there is no default Link Secret, this will be set as default (even if setAsDefault is true). + * + */ + public async createLinkSecret(options?: AnonCredsCreateLinkSecretOptions) { + const { linkSecretId, linkSecretValue } = await this.anonCredsHolderService.createLinkSecret(this.agentContext, { + linkSecretId: options?.linkSecretId, + }) + + // In some cases we don't have the linkSecretValue. However we still want a record so we know which link secret ids are valid + const linkSecretRecord = new AnonCredsLinkSecretRecord({ linkSecretId, value: linkSecretValue }) + + // If it is the first link secret registered, set as default + const defaultLinkSecretRecord = await this.anonCredsLinkSecretRepository.findDefault(this.agentContext) + if (!defaultLinkSecretRecord || options?.setAsDefault) { + linkSecretRecord.setTag('isDefault', true) + } + + // Set the current default link secret as not default + if (defaultLinkSecretRecord && options?.setAsDefault) { + defaultLinkSecretRecord.setTag('isDefault', false) + await this.anonCredsLinkSecretRepository.update(this.agentContext, defaultLinkSecretRecord) + } + + await this.anonCredsLinkSecretRepository.save(this.agentContext, linkSecretRecord) + } + + /** + * Get a list of ids for the created link secrets + */ + public async getLinkSecretIds(): Promise { + const linkSecrets = await this.anonCredsLinkSecretRepository.getAll(this.agentContext) + + return linkSecrets.map((linkSecret) => linkSecret.linkSecretId) + } + + /** + * Retrieve a {@link AnonCredsSchema} from the registry associated + * with the {@link schemaId} + */ + public async getSchema(schemaId: string): Promise { + const failedReturnBase = { + resolutionMetadata: { + error: 'error', + message: `Unable to resolve schema ${schemaId}`, + }, + schemaId, + schemaMetadata: {}, + } + + const registry = this.findRegistryForIdentifier(schemaId) + if (!registry) { + failedReturnBase.resolutionMetadata.error = 'unsupportedAnonCredsMethod' + failedReturnBase.resolutionMetadata.message = `Unable to resolve schema ${schemaId}: No registry found for identifier ${schemaId}` + return failedReturnBase + } + + try { + const result = await registry.getSchema(this.agentContext, schemaId) + return result + } catch (error) { + failedReturnBase.resolutionMetadata.message = `Unable to resolve schema ${schemaId}: ${error.message}` + return failedReturnBase + } + } + + public async registerSchema(options: RegisterSchemaOptions): Promise { + const failedReturnBase = { + schemaState: { + state: 'failed' as const, + schema: options.schema, + reason: `Error registering schema for issuerId ${options.schema.issuerId}`, + }, + registrationMetadata: {}, + schemaMetadata: {}, + } + + const registry = this.findRegistryForIdentifier(options.schema.issuerId) + if (!registry) { + failedReturnBase.schemaState.reason = `Unable to register schema. No registry found for issuerId ${options.schema.issuerId}` + return failedReturnBase + } + + try { + const result = await registry.registerSchema(this.agentContext, options) + await this.storeSchemaRecord(registry, result) + + return result + } catch (error) { + // Storage failed + if (error instanceof AnonCredsStoreRecordError) { + failedReturnBase.schemaState.reason = `Error storing schema record: ${error.message}` + return failedReturnBase + } + + // In theory registerSchema SHOULD NOT throw, but we can't know for sure + failedReturnBase.schemaState.reason = `Error registering schema: ${error.message}` + return failedReturnBase + } + } + + public async getCreatedSchemas(query: SimpleQuery) { + return this.anonCredsSchemaRepository.findByQuery(this.agentContext, query) + } + + /** + * Retrieve a {@link AnonCredsCredentialDefinition} from the registry associated + * with the {@link credentialDefinitionId} + */ + public async getCredentialDefinition(credentialDefinitionId: string): Promise { + const failedReturnBase = { + resolutionMetadata: { + error: 'error', + message: `Unable to resolve credential definition ${credentialDefinitionId}`, + }, + credentialDefinitionId, + credentialDefinitionMetadata: {}, + } + + const registry = this.findRegistryForIdentifier(credentialDefinitionId) + if (!registry) { + failedReturnBase.resolutionMetadata.error = 'unsupportedAnonCredsMethod' + failedReturnBase.resolutionMetadata.message = `Unable to resolve credential definition ${credentialDefinitionId}: No registry found for identifier ${credentialDefinitionId}` + return failedReturnBase + } + + try { + const result = await registry.getCredentialDefinition(this.agentContext, credentialDefinitionId) + return result + } catch (error) { + failedReturnBase.resolutionMetadata.message = `Unable to resolve credential definition ${credentialDefinitionId}: ${error.message}` + return failedReturnBase + } + } + + public async registerCredentialDefinition(options: { + credentialDefinition: AnonCredsRegisterCredentialDefinitionOptions + // TODO: options should support supportsRevocation at some points + options: Extensible + }): Promise { + const failedReturnBase = { + credentialDefinitionState: { + state: 'failed' as const, + reason: `Error registering credential definition for issuerId ${options.credentialDefinition.issuerId}`, + }, + registrationMetadata: {}, + credentialDefinitionMetadata: {}, + } + + const registry = this.findRegistryForIdentifier(options.credentialDefinition.issuerId) + if (!registry) { + failedReturnBase.credentialDefinitionState.reason = `Unable to register credential definition. No registry found for issuerId ${options.credentialDefinition.issuerId}` + return failedReturnBase + } + + const schemaRegistry = this.findRegistryForIdentifier(options.credentialDefinition.schemaId) + if (!schemaRegistry) { + failedReturnBase.credentialDefinitionState.reason = `Unable to register credential definition. No registry found for schemaId ${options.credentialDefinition.schemaId}` + return failedReturnBase + } + + try { + const schemaResult = await schemaRegistry.getSchema(this.agentContext, options.credentialDefinition.schemaId) + + if (!schemaResult.schema) { + failedReturnBase.credentialDefinitionState.reason = `error resolving schema with id ${options.credentialDefinition.schemaId}: ${schemaResult.resolutionMetadata.error} ${schemaResult.resolutionMetadata.message}` + return failedReturnBase + } + + const { credentialDefinition, credentialDefinitionPrivate, keyCorrectnessProof } = + await this.anonCredsIssuerService.createCredentialDefinition( + this.agentContext, + { + issuerId: options.credentialDefinition.issuerId, + schemaId: options.credentialDefinition.schemaId, + tag: options.credentialDefinition.tag, + supportRevocation: false, + schema: schemaResult.schema, + }, + // FIXME: Indy SDK requires the schema seq no to be passed in here. This is not ideal. + { + indyLedgerSchemaSeqNo: schemaResult.schemaMetadata.indyLedgerSeqNo, + } + ) + + const result = await registry.registerCredentialDefinition(this.agentContext, { + credentialDefinition, + options: options.options, + }) + + await this.storeCredentialDefinitionRecord(registry, result, credentialDefinitionPrivate, keyCorrectnessProof) + + return result + } catch (error) { + // Storage failed + if (error instanceof AnonCredsStoreRecordError) { + failedReturnBase.credentialDefinitionState.reason = `Error storing credential definition records: ${error.message}` + return failedReturnBase + } + + // In theory registerCredentialDefinition SHOULD NOT throw, but we can't know for sure + failedReturnBase.credentialDefinitionState.reason = `Error registering credential definition: ${error.message}` + return failedReturnBase + } + } + + public async getCreatedCredentialDefinitions(query: SimpleQuery) { + return this.anonCredsCredentialDefinitionRepository.findByQuery(this.agentContext, query) + } + + /** + * Retrieve a {@link AnonCredsRevocationRegistryDefinition} from the registry associated + * with the {@link revocationRegistryDefinitionId} + */ + public async getRevocationRegistryDefinition( + revocationRegistryDefinitionId: string + ): Promise { + const failedReturnBase = { + resolutionMetadata: { + error: 'error', + message: `Unable to resolve revocation registry ${revocationRegistryDefinitionId}`, + }, + revocationRegistryDefinitionId, + revocationRegistryDefinitionMetadata: {}, + } + + const registry = this.findRegistryForIdentifier(revocationRegistryDefinitionId) + if (!registry) { + failedReturnBase.resolutionMetadata.error = 'unsupportedAnonCredsMethod' + failedReturnBase.resolutionMetadata.message = `Unable to resolve revocation registry ${revocationRegistryDefinitionId}: No registry found for identifier ${revocationRegistryDefinitionId}` + return failedReturnBase + } + + try { + const result = await registry.getRevocationRegistryDefinition(this.agentContext, revocationRegistryDefinitionId) + return result + } catch (error) { + failedReturnBase.resolutionMetadata.message = `Unable to resolve revocation registry ${revocationRegistryDefinitionId}: ${error.message}` + return failedReturnBase + } + } + + /** + * Retrieve the {@link AnonCredsRevocationStatusList} for the given {@link timestamp} from the registry associated + * with the {@link revocationRegistryDefinitionId} + */ + public async getRevocationStatusList( + revocationRegistryDefinitionId: string, + timestamp: number + ): Promise { + const failedReturnBase = { + resolutionMetadata: { + error: 'error', + message: `Unable to resolve revocation status list for revocation registry ${revocationRegistryDefinitionId}`, + }, + revocationStatusListMetadata: {}, + } + + const registry = this.findRegistryForIdentifier(revocationRegistryDefinitionId) + if (!registry) { + failedReturnBase.resolutionMetadata.error = 'unsupportedAnonCredsMethod' + failedReturnBase.resolutionMetadata.message = `Unable to resolve revocation status list for revocation registry ${revocationRegistryDefinitionId}: No registry found for identifier ${revocationRegistryDefinitionId}` + return failedReturnBase + } + + try { + const result = await registry.getRevocationStatusList( + this.agentContext, + revocationRegistryDefinitionId, + timestamp + ) + return result + } catch (error) { + failedReturnBase.resolutionMetadata.message = `Unable to resolve revocation status list for revocation registry ${revocationRegistryDefinitionId}: ${error.message}` + return failedReturnBase + } + } + + public async getCredential(credentialId: string) { + return this.anonCredsHolderService.getCredential(this.agentContext, { credentialId }) + } + + public async getCredentials(options: GetCredentialsOptions) { + return this.anonCredsHolderService.getCredentials(this.agentContext, options) + } + + private async storeCredentialDefinitionRecord( + registry: AnonCredsRegistry, + result: RegisterCredentialDefinitionReturn, + credentialDefinitionPrivate?: Record, + keyCorrectnessProof?: Record + ): Promise { + try { + // If we have both the credentialDefinition and the credentialDefinitionId we will store a copy of the credential definition. We may need to handle an + // edge case in the future where we e.g. don't have the id yet, and it is registered through a different channel + if ( + result.credentialDefinitionState.credentialDefinition && + result.credentialDefinitionState.credentialDefinitionId + ) { + const credentialDefinitionRecord = new AnonCredsCredentialDefinitionRecord({ + credentialDefinitionId: result.credentialDefinitionState.credentialDefinitionId, + credentialDefinition: result.credentialDefinitionState.credentialDefinition, + methodName: registry.methodName, + }) + + // TODO: do we need to store this metadata? For indy, the registration metadata contains e.g. + // the indyLedgerSeqNo and the didIndyNamespace, but it can get quite big if complete transactions + // are stored in the metadata + credentialDefinitionRecord.metadata.set( + AnonCredsCredentialDefinitionRecordMetadataKeys.CredentialDefinitionMetadata, + result.credentialDefinitionMetadata + ) + credentialDefinitionRecord.metadata.set( + AnonCredsCredentialDefinitionRecordMetadataKeys.CredentialDefinitionRegistrationMetadata, + result.registrationMetadata + ) + + await this.anonCredsCredentialDefinitionRepository.save(this.agentContext, credentialDefinitionRecord) + + // Store Credential Definition private data (if provided by issuer service) + if (credentialDefinitionPrivate) { + const credentialDefinitionPrivateRecord = new AnonCredsCredentialDefinitionPrivateRecord({ + credentialDefinitionId: result.credentialDefinitionState.credentialDefinitionId, + value: credentialDefinitionPrivate, + }) + await this.anonCredsCredentialDefinitionPrivateRepository.save( + this.agentContext, + credentialDefinitionPrivateRecord + ) + } + + if (keyCorrectnessProof) { + const keyCorrectnessProofRecord = new AnonCredsKeyCorrectnessProofRecord({ + credentialDefinitionId: result.credentialDefinitionState.credentialDefinitionId, + value: keyCorrectnessProof, + }) + await this.anonCredsKeyCorrectnessProofRepository.save(this.agentContext, keyCorrectnessProofRecord) + } + } + } catch (error) { + throw new AnonCredsStoreRecordError(`Error storing credential definition records`, { cause: error }) + } + } + + private async storeSchemaRecord(registry: AnonCredsRegistry, result: RegisterSchemaReturn): Promise { + try { + // If we have both the schema and the schemaId we will store a copy of the schema. We may need to handle an + // edge case in the future where we e.g. don't have the id yet, and it is registered through a different channel + if (result.schemaState.schema && result.schemaState.schemaId) { + const schemaRecord = new AnonCredsSchemaRecord({ + schemaId: result.schemaState.schemaId, + schema: result.schemaState.schema, + methodName: registry.methodName, + }) + + await this.anonCredsSchemaRepository.save(this.agentContext, schemaRecord) + } + } catch (error) { + throw new AnonCredsStoreRecordError(`Error storing schema record`, { cause: error }) + } + } + + private findRegistryForIdentifier(identifier: string) { + try { + return this.anonCredsRegistryService.getRegistryForIdentifier(this.agentContext, identifier) + } catch { + return null + } + } +} diff --git a/packages/anoncreds/src/AnonCredsApiOptions.ts b/packages/anoncreds/src/AnonCredsApiOptions.ts new file mode 100644 index 0000000000..860ea059df --- /dev/null +++ b/packages/anoncreds/src/AnonCredsApiOptions.ts @@ -0,0 +1,8 @@ +import type { AnonCredsCredentialDefinition } from './models' + +export interface AnonCredsCreateLinkSecretOptions { + linkSecretId?: string + setAsDefault?: boolean +} + +export type AnonCredsRegisterCredentialDefinitionOptions = Omit diff --git a/packages/anoncreds/src/AnonCredsModule.ts b/packages/anoncreds/src/AnonCredsModule.ts new file mode 100644 index 0000000000..11b923e093 --- /dev/null +++ b/packages/anoncreds/src/AnonCredsModule.ts @@ -0,0 +1,48 @@ +import type { AnonCredsModuleConfigOptions } from './AnonCredsModuleConfig' +import type { DependencyManager, Module, Update } from '@aries-framework/core' + +import { AnonCredsApi } from './AnonCredsApi' +import { AnonCredsModuleConfig } from './AnonCredsModuleConfig' +import { + AnonCredsCredentialDefinitionPrivateRepository, + AnonCredsKeyCorrectnessProofRepository, + AnonCredsLinkSecretRepository, +} from './repository' +import { AnonCredsCredentialDefinitionRepository } from './repository/AnonCredsCredentialDefinitionRepository' +import { AnonCredsSchemaRepository } from './repository/AnonCredsSchemaRepository' +import { AnonCredsRegistryService } from './services/registry/AnonCredsRegistryService' +import { updateAnonCredsModuleV0_3_1ToV0_4 } from './updates/0.3.1-0.4' + +/** + * @public + */ +export class AnonCredsModule implements Module { + public readonly config: AnonCredsModuleConfig + public api = AnonCredsApi + + public constructor(config: AnonCredsModuleConfigOptions) { + this.config = new AnonCredsModuleConfig(config) + } + + public register(dependencyManager: DependencyManager) { + // Config + dependencyManager.registerInstance(AnonCredsModuleConfig, this.config) + + dependencyManager.registerSingleton(AnonCredsRegistryService) + + // Repositories + dependencyManager.registerSingleton(AnonCredsSchemaRepository) + dependencyManager.registerSingleton(AnonCredsCredentialDefinitionRepository) + dependencyManager.registerSingleton(AnonCredsCredentialDefinitionPrivateRepository) + dependencyManager.registerSingleton(AnonCredsKeyCorrectnessProofRepository) + dependencyManager.registerSingleton(AnonCredsLinkSecretRepository) + } + + public updates: Update[] = [ + { + fromVersion: '0.3.1', + toVersion: '0.4', + doUpdate: updateAnonCredsModuleV0_3_1ToV0_4, + }, + ] +} diff --git a/packages/anoncreds/src/AnonCredsModuleConfig.ts b/packages/anoncreds/src/AnonCredsModuleConfig.ts new file mode 100644 index 0000000000..9f7b971aab --- /dev/null +++ b/packages/anoncreds/src/AnonCredsModuleConfig.ts @@ -0,0 +1,28 @@ +import type { AnonCredsRegistry } from './services' + +/** + * @public + * AnonCredsModuleConfigOptions defines the interface for the options of the AnonCredsModuleConfig class. + */ +export interface AnonCredsModuleConfigOptions { + /** + * A list of AnonCreds registries to make available to the AnonCreds module. + */ + registries: [AnonCredsRegistry, ...AnonCredsRegistry[]] +} + +/** + * @public + */ +export class AnonCredsModuleConfig { + private options: AnonCredsModuleConfigOptions + + public constructor(options: AnonCredsModuleConfigOptions) { + this.options = options + } + + /** See {@link AnonCredsModuleConfigOptions.registries} */ + public get registries() { + return this.options.registries + } +} diff --git a/packages/anoncreds/src/__tests__/AnonCredsModule.test.ts b/packages/anoncreds/src/__tests__/AnonCredsModule.test.ts new file mode 100644 index 0000000000..f9c868c14c --- /dev/null +++ b/packages/anoncreds/src/__tests__/AnonCredsModule.test.ts @@ -0,0 +1,40 @@ +import type { AnonCredsRegistry } from '../services' +import type { DependencyManager } from '@aries-framework/core' + +import { AnonCredsModule } from '../AnonCredsModule' +import { AnonCredsModuleConfig } from '../AnonCredsModuleConfig' +import { + AnonCredsSchemaRepository, + AnonCredsCredentialDefinitionRepository, + AnonCredsCredentialDefinitionPrivateRepository, + AnonCredsKeyCorrectnessProofRepository, + AnonCredsLinkSecretRepository, +} from '../repository' +import { AnonCredsRegistryService } from '../services/registry/AnonCredsRegistryService' + +const dependencyManager = { + registerInstance: jest.fn(), + registerSingleton: jest.fn(), +} as unknown as DependencyManager + +const registry = {} as AnonCredsRegistry + +describe('AnonCredsModule', () => { + test('registers dependencies on the dependency manager', () => { + const anonCredsModule = new AnonCredsModule({ + registries: [registry], + }) + anonCredsModule.register(dependencyManager) + + expect(dependencyManager.registerSingleton).toHaveBeenCalledTimes(6) + expect(dependencyManager.registerSingleton).toHaveBeenCalledWith(AnonCredsRegistryService) + expect(dependencyManager.registerSingleton).toHaveBeenCalledWith(AnonCredsSchemaRepository) + expect(dependencyManager.registerSingleton).toHaveBeenCalledWith(AnonCredsCredentialDefinitionRepository) + expect(dependencyManager.registerSingleton).toHaveBeenCalledWith(AnonCredsCredentialDefinitionPrivateRepository) + expect(dependencyManager.registerSingleton).toHaveBeenCalledWith(AnonCredsKeyCorrectnessProofRepository) + expect(dependencyManager.registerSingleton).toHaveBeenCalledWith(AnonCredsLinkSecretRepository) + + expect(dependencyManager.registerInstance).toHaveBeenCalledTimes(1) + expect(dependencyManager.registerInstance).toHaveBeenCalledWith(AnonCredsModuleConfig, anonCredsModule.config) + }) +}) diff --git a/packages/anoncreds/src/__tests__/AnonCredsModuleConfig.test.ts b/packages/anoncreds/src/__tests__/AnonCredsModuleConfig.test.ts new file mode 100644 index 0000000000..beaca8bf53 --- /dev/null +++ b/packages/anoncreds/src/__tests__/AnonCredsModuleConfig.test.ts @@ -0,0 +1,15 @@ +import type { AnonCredsRegistry } from '../services' + +import { AnonCredsModuleConfig } from '../AnonCredsModuleConfig' + +describe('AnonCredsModuleConfig', () => { + test('sets values', () => { + const registry = {} as AnonCredsRegistry + + const config = new AnonCredsModuleConfig({ + registries: [registry], + }) + + expect(config.registries).toEqual([registry]) + }) +}) diff --git a/packages/anoncreds/src/error/AnonCredsError.ts b/packages/anoncreds/src/error/AnonCredsError.ts new file mode 100644 index 0000000000..eb6d250a4a --- /dev/null +++ b/packages/anoncreds/src/error/AnonCredsError.ts @@ -0,0 +1,7 @@ +import { AriesFrameworkError } from '@aries-framework/core' + +export class AnonCredsError extends AriesFrameworkError { + public constructor(message: string, { cause }: { cause?: Error } = {}) { + super(message, { cause }) + } +} diff --git a/packages/anoncreds/src/error/AnonCredsStoreRecordError.ts b/packages/anoncreds/src/error/AnonCredsStoreRecordError.ts new file mode 100644 index 0000000000..11437d7b64 --- /dev/null +++ b/packages/anoncreds/src/error/AnonCredsStoreRecordError.ts @@ -0,0 +1,7 @@ +import { AnonCredsError } from './AnonCredsError' + +export class AnonCredsStoreRecordError extends AnonCredsError { + public constructor(message: string, { cause }: { cause?: Error } = {}) { + super(message, { cause }) + } +} diff --git a/packages/anoncreds/src/error/index.ts b/packages/anoncreds/src/error/index.ts new file mode 100644 index 0000000000..6d25bc4dbb --- /dev/null +++ b/packages/anoncreds/src/error/index.ts @@ -0,0 +1,2 @@ +export * from './AnonCredsError' +export * from './AnonCredsStoreRecordError' diff --git a/packages/anoncreds/src/formats/AnonCredsCredentialFormat.ts b/packages/anoncreds/src/formats/AnonCredsCredentialFormat.ts new file mode 100644 index 0000000000..dba5361a41 --- /dev/null +++ b/packages/anoncreds/src/formats/AnonCredsCredentialFormat.ts @@ -0,0 +1,93 @@ +import type { AnonCredsCredential, AnonCredsCredentialOffer, AnonCredsCredentialRequest } from '../models' +import type { CredentialPreviewAttributeOptions, CredentialFormat, LinkedAttachment } from '@aries-framework/core' + +export interface AnonCredsCredentialProposalFormat { + schema_issuer_id?: string + schema_name?: string + schema_version?: string + schema_id?: string + + cred_def_id?: string + issuer_id?: string + + // TODO: we don't necessarily need to include these in the AnonCreds Format RFC + // as it's a new one and we can just forbid the use of legacy properties + schema_issuer_did?: string + issuer_did?: string +} + +/** + * This defines the module payload for calling CredentialsApi.createProposal + * or CredentialsApi.negotiateOffer + */ +export interface AnonCredsProposeCredentialFormat { + schemaIssuerId?: string + schemaId?: string + schemaName?: string + schemaVersion?: string + + credentialDefinitionId?: string + issuerId?: string + + attributes?: CredentialPreviewAttributeOptions[] + linkedAttachments?: LinkedAttachment[] + + // Kept for backwards compatibility + schemaIssuerDid?: string + issuerDid?: string +} + +/** + * This defines the module payload for calling CredentialsApi.acceptProposal + */ +export interface AnonCredsAcceptProposalFormat { + credentialDefinitionId?: string + attributes?: CredentialPreviewAttributeOptions[] + linkedAttachments?: LinkedAttachment[] +} + +/** + * This defines the module payload for calling CredentialsApi.acceptOffer. No options are available for this + * method, so it's an empty object + */ +export interface AnonCredsAcceptOfferFormat { + linkSecretId?: string +} + +/** + * This defines the module payload for calling CredentialsApi.offerCredential + * or CredentialsApi.negotiateProposal + */ +export interface AnonCredsOfferCredentialFormat { + credentialDefinitionId: string + attributes: CredentialPreviewAttributeOptions[] + linkedAttachments?: LinkedAttachment[] +} + +/** + * This defines the module payload for calling CredentialsApi.acceptRequest. No options are available for this + * method, so it's an empty object + */ +export type AnonCredsAcceptRequestFormat = Record + +export interface AnonCredsCredentialFormat extends CredentialFormat { + formatKey: 'anoncreds' + credentialRecordType: 'anoncreds' + credentialFormats: { + createProposal: AnonCredsProposeCredentialFormat + acceptProposal: AnonCredsAcceptProposalFormat + createOffer: AnonCredsOfferCredentialFormat + acceptOffer: AnonCredsAcceptOfferFormat + createRequest: never // cannot start from createRequest + acceptRequest: AnonCredsAcceptRequestFormat + } + // TODO: update to new RFC once available + // Format data is based on RFC 0592 + // https://github.com/hyperledger/aries-rfcs/tree/main/features/0592-indy-attachments + formatData: { + proposal: AnonCredsCredentialProposalFormat + offer: AnonCredsCredentialOffer + request: AnonCredsCredentialRequest + credential: AnonCredsCredential + } +} diff --git a/packages/anoncreds/src/formats/AnonCredsCredentialFormatService.ts b/packages/anoncreds/src/formats/AnonCredsCredentialFormatService.ts new file mode 100644 index 0000000000..f9627cd0aa --- /dev/null +++ b/packages/anoncreds/src/formats/AnonCredsCredentialFormatService.ts @@ -0,0 +1,636 @@ +import type { AnonCredsCredentialFormat, AnonCredsCredentialProposalFormat } from './AnonCredsCredentialFormat' +import type { + AnonCredsCredential, + AnonCredsCredentialOffer, + AnonCredsCredentialRequest, + AnonCredsCredentialRequestMetadata, +} from '../models' +import type { AnonCredsIssuerService, AnonCredsHolderService, GetRevocationRegistryDefinitionReturn } from '../services' +import type { AnonCredsCredentialMetadata } from '../utils/metadata' +import type { + CredentialFormatService, + AgentContext, + CredentialFormatCreateProposalOptions, + CredentialFormatCreateProposalReturn, + CredentialFormatProcessOptions, + CredentialFormatAcceptProposalOptions, + CredentialFormatCreateOfferReturn, + CredentialFormatCreateOfferOptions, + CredentialFormatAcceptOfferOptions, + CredentialFormatCreateReturn, + CredentialFormatAcceptRequestOptions, + CredentialFormatProcessCredentialOptions, + CredentialFormatAutoRespondProposalOptions, + CredentialFormatAutoRespondOfferOptions, + CredentialFormatAutoRespondRequestOptions, + CredentialFormatAutoRespondCredentialOptions, + CredentialExchangeRecord, + CredentialPreviewAttributeOptions, + LinkedAttachment, +} from '@aries-framework/core' + +import { + ProblemReportError, + MessageValidator, + CredentialFormatSpec, + AriesFrameworkError, + JsonEncoder, + utils, + CredentialProblemReportReason, + JsonTransformer, + V1Attachment, + V1AttachmentData, +} from '@aries-framework/core' + +import { AnonCredsError } from '../error' +import { AnonCredsCredentialProposal } from '../models/AnonCredsCredentialProposal' +import { AnonCredsIssuerServiceSymbol, AnonCredsHolderServiceSymbol } from '../services' +import { AnonCredsRegistryService } from '../services/registry/AnonCredsRegistryService' +import { + convertAttributesToCredentialValues, + assertCredentialValuesMatch, + checkCredentialValuesMatch, + assertAttributesMatch, + createAndLinkAttachmentsToPreview, +} from '../utils/credential' +import { AnonCredsCredentialMetadataKey, AnonCredsCredentialRequestMetadataKey } from '../utils/metadata' + +const ANONCREDS_CREDENTIAL_OFFER = 'anoncreds/credential-offer@v1.0' +const ANONCREDS_CREDENTIAL_REQUEST = 'anoncreds/credential-request@v1.0' +const ANONCREDS_CREDENTIAL_FILTER = 'anoncreds/credential-filter@v1.0' +const ANONCREDS_CREDENTIAL = 'anoncreds/credential@v1.0' + +export class AnonCredsCredentialFormatService implements CredentialFormatService { + /** formatKey is the key used when calling agent.credentials.xxx with credentialFormats.anoncreds */ + public readonly formatKey = 'anoncreds' as const + + /** + * credentialRecordType is the type of record that stores the credential. It is stored in the credential + * record binding in the credential exchange record. + */ + public readonly credentialRecordType = 'anoncreds' as const + + /** + * Create a {@link AttachmentFormats} object dependent on the message type. + * + * @param options The object containing all the options for the proposed credential + * @returns object containing associated attachment, format and optionally the credential preview + * + */ + public async createProposal( + agentContext: AgentContext, + { credentialFormats, credentialRecord }: CredentialFormatCreateProposalOptions + ): Promise { + const format = new CredentialFormatSpec({ + format: ANONCREDS_CREDENTIAL_FILTER, + }) + + const anoncredsFormat = credentialFormats.anoncreds + + if (!anoncredsFormat) { + throw new AriesFrameworkError('Missing anoncreds payload in createProposal') + } + + // We want all properties except for `attributes` and `linkedAttachments` attributes. + // The easiest way is to destructure and use the spread operator. But that leaves the other properties unused + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const { attributes, linkedAttachments, ...anoncredsCredentialProposal } = anoncredsFormat + const proposal = new AnonCredsCredentialProposal(anoncredsCredentialProposal) + + try { + MessageValidator.validateSync(proposal) + } catch (error) { + throw new AriesFrameworkError( + `Invalid proposal supplied: ${anoncredsCredentialProposal} in AnonCredsFormatService` + ) + } + + const attachment = this.getFormatData(JsonTransformer.toJSON(proposal), format.attachmentId) + + const { previewAttributes } = this.getCredentialLinkedAttachments( + anoncredsFormat.attributes, + anoncredsFormat.linkedAttachments + ) + + // Set the metadata + credentialRecord.metadata.set(AnonCredsCredentialMetadataKey, { + schemaId: proposal.schemaId, + credentialDefinitionId: proposal.credentialDefinitionId, + }) + + return { format, attachment, previewAttributes } + } + + public async processProposal( + agentContext: AgentContext, + { attachment }: CredentialFormatProcessOptions + ): Promise { + const proposalJson = attachment.getDataAsJson() + + JsonTransformer.fromJSON(proposalJson, AnonCredsCredentialProposal) + } + + public async acceptProposal( + agentContext: AgentContext, + { + attachmentId, + credentialFormats, + credentialRecord, + proposalAttachment, + }: CredentialFormatAcceptProposalOptions + ): Promise { + const anoncredsFormat = credentialFormats?.anoncreds + + const proposalJson = proposalAttachment.getDataAsJson() + const credentialDefinitionId = anoncredsFormat?.credentialDefinitionId ?? proposalJson.cred_def_id + + const attributes = anoncredsFormat?.attributes ?? credentialRecord.credentialAttributes + + if (!credentialDefinitionId) { + throw new AriesFrameworkError( + 'No credential definition id in proposal or provided as input to accept proposal method.' + ) + } + + if (!attributes) { + throw new AriesFrameworkError('No attributes in proposal or provided as input to accept proposal method.') + } + + const { format, attachment, previewAttributes } = await this.createAnonCredsOffer(agentContext, { + credentialRecord, + attachmentId, + attributes, + credentialDefinitionId, + linkedAttachments: anoncredsFormat?.linkedAttachments, + }) + + return { format, attachment, previewAttributes } + } + + /** + * Create a credential attachment format for a credential request. + * + * @param options The object containing all the options for the credential offer + * @returns object containing associated attachment, formats and offersAttach elements + * + */ + public async createOffer( + agentContext: AgentContext, + { credentialFormats, credentialRecord, attachmentId }: CredentialFormatCreateOfferOptions + ): Promise { + const anoncredsFormat = credentialFormats.anoncreds + + if (!anoncredsFormat) { + throw new AriesFrameworkError('Missing anoncreds credential format data') + } + + const { format, attachment, previewAttributes } = await this.createAnonCredsOffer(agentContext, { + credentialRecord, + attachmentId, + attributes: anoncredsFormat.attributes, + credentialDefinitionId: anoncredsFormat.credentialDefinitionId, + linkedAttachments: anoncredsFormat.linkedAttachments, + }) + + return { format, attachment, previewAttributes } + } + + public async processOffer( + agentContext: AgentContext, + { attachment, credentialRecord }: CredentialFormatProcessOptions + ) { + agentContext.config.logger.debug( + `Processing anoncreds credential offer for credential record ${credentialRecord.id}` + ) + + const credOffer = attachment.getDataAsJson() + + if (!credOffer.schema_id || !credOffer.cred_def_id) { + throw new ProblemReportError('Invalid credential offer', { + problemCode: CredentialProblemReportReason.IssuanceAbandoned, + }) + } + } + + public async acceptOffer( + agentContext: AgentContext, + { + credentialRecord, + attachmentId, + offerAttachment, + credentialFormats, + }: CredentialFormatAcceptOfferOptions + ): Promise { + const registryService = agentContext.dependencyManager.resolve(AnonCredsRegistryService) + const holderService = agentContext.dependencyManager.resolve(AnonCredsHolderServiceSymbol) + + const credentialOffer = offerAttachment.getDataAsJson() + + // Get credential definition + const registry = registryService.getRegistryForIdentifier(agentContext, credentialOffer.cred_def_id) + const { credentialDefinition, resolutionMetadata } = await registry.getCredentialDefinition( + agentContext, + credentialOffer.cred_def_id + ) + + if (!credentialDefinition) { + throw new AnonCredsError( + `Unable to retrieve credential definition with id ${credentialOffer.cred_def_id}: ${resolutionMetadata.error} ${resolutionMetadata.message}` + ) + } + + const { credentialRequest, credentialRequestMetadata } = await holderService.createCredentialRequest(agentContext, { + credentialOffer, + credentialDefinition, + linkSecretId: credentialFormats?.anoncreds?.linkSecretId, + }) + + credentialRecord.metadata.set( + AnonCredsCredentialRequestMetadataKey, + credentialRequestMetadata + ) + credentialRecord.metadata.set(AnonCredsCredentialMetadataKey, { + credentialDefinitionId: credentialOffer.cred_def_id, + schemaId: credentialOffer.schema_id, + }) + + const format = new CredentialFormatSpec({ + attachmentId, + format: ANONCREDS_CREDENTIAL_REQUEST, + }) + + const attachment = this.getFormatData(credentialRequest, format.attachmentId) + return { format, attachment } + } + + /** + * Starting from a request is not supported for anoncreds credentials, this method only throws an error. + */ + public async createRequest(): Promise { + throw new AriesFrameworkError('Starting from a request is not supported for anoncreds credentials') + } + + /** + * We don't have any models to validate an anoncreds request object, for now this method does nothing + */ + // eslint-disable-next-line @typescript-eslint/no-unused-vars + public async processRequest(agentContext: AgentContext, options: CredentialFormatProcessOptions): Promise { + // not needed for anoncreds + } + + public async acceptRequest( + agentContext: AgentContext, + { + credentialRecord, + attachmentId, + offerAttachment, + requestAttachment, + }: CredentialFormatAcceptRequestOptions + ): Promise { + // Assert credential attributes + const credentialAttributes = credentialRecord.credentialAttributes + if (!credentialAttributes) { + throw new AriesFrameworkError( + `Missing required credential attribute values on credential record with id ${credentialRecord.id}` + ) + } + + const anonCredsIssuerService = + agentContext.dependencyManager.resolve(AnonCredsIssuerServiceSymbol) + + const credentialOffer = offerAttachment?.getDataAsJson() + if (!credentialOffer) throw new AriesFrameworkError('Missing anoncreds credential offer in createCredential') + + const credentialRequest = requestAttachment.getDataAsJson() + if (!credentialRequest) throw new AriesFrameworkError('Missing anoncreds credential request in createCredential') + + const { credential, credentialRevocationId } = await anonCredsIssuerService.createCredential(agentContext, { + credentialOffer, + credentialRequest, + credentialValues: convertAttributesToCredentialValues(credentialAttributes), + }) + + if (credential.rev_reg_id) { + credentialRecord.metadata.add(AnonCredsCredentialMetadataKey, { + credentialRevocationId: credentialRevocationId, + revocationRegistryId: credential.rev_reg_id, + }) + credentialRecord.setTags({ + anonCredsRevocationRegistryId: credential.rev_reg_id, + anonCredsCredentialRevocationId: credentialRevocationId, + }) + } + + const format = new CredentialFormatSpec({ + attachmentId, + format: ANONCREDS_CREDENTIAL, + }) + + const attachment = this.getFormatData(credential, format.attachmentId) + return { format, attachment } + } + + /** + * Processes an incoming credential - retrieve metadata, retrieve payload and store it in wallet + * @param options the issue credential message wrapped inside this object + * @param credentialRecord the credential exchange record for this credential + */ + public async processCredential( + agentContext: AgentContext, + { credentialRecord, attachment }: CredentialFormatProcessCredentialOptions + ): Promise { + const credentialRequestMetadata = credentialRecord.metadata.get( + AnonCredsCredentialRequestMetadataKey + ) + + const registryService = agentContext.dependencyManager.resolve(AnonCredsRegistryService) + const anonCredsHolderService = + agentContext.dependencyManager.resolve(AnonCredsHolderServiceSymbol) + + if (!credentialRequestMetadata) { + throw new AriesFrameworkError( + `Missing required request metadata for credential exchange with thread id with id ${credentialRecord.id}` + ) + } + + if (!credentialRecord.credentialAttributes) { + throw new AriesFrameworkError( + 'Missing credential attributes on credential record. Unable to check credential attributes' + ) + } + + const anonCredsCredential = attachment.getDataAsJson() + + const credentialDefinitionResult = await registryService + .getRegistryForIdentifier(agentContext, anonCredsCredential.cred_def_id) + .getCredentialDefinition(agentContext, anonCredsCredential.cred_def_id) + if (!credentialDefinitionResult.credentialDefinition) { + throw new AriesFrameworkError( + `Unable to resolve credential definition ${anonCredsCredential.cred_def_id}: ${credentialDefinitionResult.resolutionMetadata.error} ${credentialDefinitionResult.resolutionMetadata.message}` + ) + } + + const schemaResult = await registryService + .getRegistryForIdentifier(agentContext, anonCredsCredential.cred_def_id) + .getSchema(agentContext, anonCredsCredential.schema_id) + if (!schemaResult.schema) { + throw new AriesFrameworkError( + `Unable to resolve schema ${anonCredsCredential.schema_id}: ${schemaResult.resolutionMetadata.error} ${schemaResult.resolutionMetadata.message}` + ) + } + + // Resolve revocation registry if credential is revocable + let revocationRegistryResult: null | GetRevocationRegistryDefinitionReturn = null + if (anonCredsCredential.rev_reg_id) { + revocationRegistryResult = await registryService + .getRegistryForIdentifier(agentContext, anonCredsCredential.rev_reg_id) + .getRevocationRegistryDefinition(agentContext, anonCredsCredential.rev_reg_id) + + if (!revocationRegistryResult.revocationRegistryDefinition) { + throw new AriesFrameworkError( + `Unable to resolve revocation registry definition ${anonCredsCredential.rev_reg_id}: ${revocationRegistryResult.resolutionMetadata.error} ${revocationRegistryResult.resolutionMetadata.message}` + ) + } + } + + // assert the credential values match the offer values + const recordCredentialValues = convertAttributesToCredentialValues(credentialRecord.credentialAttributes) + assertCredentialValuesMatch(anonCredsCredential.values, recordCredentialValues) + + const credentialId = await anonCredsHolderService.storeCredential(agentContext, { + credentialId: utils.uuid(), + credentialRequestMetadata, + credential: anonCredsCredential, + credentialDefinitionId: credentialDefinitionResult.credentialDefinitionId, + credentialDefinition: credentialDefinitionResult.credentialDefinition, + schema: schemaResult.schema, + revocationRegistry: revocationRegistryResult?.revocationRegistryDefinition + ? { + definition: revocationRegistryResult.revocationRegistryDefinition, + id: revocationRegistryResult.revocationRegistryDefinitionId, + } + : undefined, + }) + + // If the credential is revocable, store the revocation identifiers in the credential record + if (anonCredsCredential.rev_reg_id) { + const credential = await anonCredsHolderService.getCredential(agentContext, { credentialId }) + + credentialRecord.metadata.add(AnonCredsCredentialMetadataKey, { + credentialRevocationId: credential.credentialRevocationId, + revocationRegistryId: credential.revocationRegistryId, + }) + credentialRecord.setTags({ + anonCredsRevocationRegistryId: credential.revocationRegistryId, + anonCredsCredentialRevocationId: credential.credentialRevocationId, + }) + } + + credentialRecord.credentials.push({ + credentialRecordType: this.credentialRecordType, + credentialRecordId: credentialId, + }) + } + + public supportsFormat(format: string): boolean { + const supportedFormats = [ + ANONCREDS_CREDENTIAL_REQUEST, + ANONCREDS_CREDENTIAL_OFFER, + ANONCREDS_CREDENTIAL_FILTER, + ANONCREDS_CREDENTIAL, + ] + + return supportedFormats.includes(format) + } + + /** + * Gets the attachment object for a given attachmentId. We need to get out the correct attachmentId for + * anoncreds and then find the corresponding attachment (if there is one) + * @param formats the formats object containing the attachmentId + * @param messageAttachments the attachments containing the payload + * @returns The Attachment if found or undefined + * + */ + public getAttachment(formats: CredentialFormatSpec[], messageAttachments: V1Attachment[]): V1Attachment | undefined { + const supportedAttachmentIds = formats.filter((f) => this.supportsFormat(f.format)).map((f) => f.attachmentId) + const supportedAttachment = messageAttachments.find((attachment) => supportedAttachmentIds.includes(attachment.id)) + + return supportedAttachment + } + + public async deleteCredentialById(agentContext: AgentContext, credentialRecordId: string): Promise { + const anonCredsHolderService = + agentContext.dependencyManager.resolve(AnonCredsHolderServiceSymbol) + + await anonCredsHolderService.deleteCredential(agentContext, credentialRecordId) + } + + public async shouldAutoRespondToProposal( + agentContext: AgentContext, + { offerAttachment, proposalAttachment }: CredentialFormatAutoRespondProposalOptions + ) { + const proposalJson = proposalAttachment.getDataAsJson() + const offerJson = offerAttachment.getDataAsJson() + + // We want to make sure the credential definition matches. + // TODO: If no credential definition is present on the proposal, we could check whether the other fields + // of the proposal match with the credential definition id. + return proposalJson.cred_def_id === offerJson.cred_def_id + } + + public async shouldAutoRespondToOffer( + agentContext: AgentContext, + { offerAttachment, proposalAttachment }: CredentialFormatAutoRespondOfferOptions + ) { + const proposalJson = proposalAttachment.getDataAsJson() + const offerJson = offerAttachment.getDataAsJson() + + // We want to make sure the credential definition matches. + // TODO: If no credential definition is present on the proposal, we could check whether the other fields + // of the proposal match with the credential definition id. + return proposalJson.cred_def_id === offerJson.cred_def_id + } + + public async shouldAutoRespondToRequest( + agentContext: AgentContext, + { offerAttachment, requestAttachment }: CredentialFormatAutoRespondRequestOptions + ) { + const credentialOfferJson = offerAttachment.getDataAsJson() + const credentialRequestJson = requestAttachment.getDataAsJson() + + return credentialOfferJson.cred_def_id === credentialRequestJson.cred_def_id + } + + public async shouldAutoRespondToCredential( + agentContext: AgentContext, + { credentialRecord, requestAttachment, credentialAttachment }: CredentialFormatAutoRespondCredentialOptions + ) { + const credentialJson = credentialAttachment.getDataAsJson() + const credentialRequestJson = requestAttachment.getDataAsJson() + + // make sure the credential definition matches + if (credentialJson.cred_def_id !== credentialRequestJson.cred_def_id) return false + + // If we don't have any attributes stored we can't compare so always return false. + if (!credentialRecord.credentialAttributes) return false + const attributeValues = convertAttributesToCredentialValues(credentialRecord.credentialAttributes) + + // check whether the values match the values in the record + return checkCredentialValuesMatch(attributeValues, credentialJson.values) + } + + private async createAnonCredsOffer( + agentContext: AgentContext, + { + credentialRecord, + attachmentId, + credentialDefinitionId, + attributes, + linkedAttachments, + }: { + credentialDefinitionId: string + credentialRecord: CredentialExchangeRecord + attachmentId?: string + attributes: CredentialPreviewAttributeOptions[] + linkedAttachments?: LinkedAttachment[] + } + ): Promise { + const anonCredsIssuerService = + agentContext.dependencyManager.resolve(AnonCredsIssuerServiceSymbol) + + // if the proposal has an attachment Id use that, otherwise the generated id of the formats object + const format = new CredentialFormatSpec({ + attachmentId: attachmentId, + format: ANONCREDS_CREDENTIAL, + }) + + const offer = await anonCredsIssuerService.createCredentialOffer(agentContext, { + credentialDefinitionId, + }) + + const { previewAttributes } = this.getCredentialLinkedAttachments(attributes, linkedAttachments) + if (!previewAttributes) { + throw new AriesFrameworkError('Missing required preview attributes for anoncreds offer') + } + + await this.assertPreviewAttributesMatchSchemaAttributes(agentContext, offer, previewAttributes) + + credentialRecord.metadata.set(AnonCredsCredentialMetadataKey, { + schemaId: offer.schema_id, + credentialDefinitionId: offer.cred_def_id, + }) + + const attachment = this.getFormatData(offer, format.attachmentId) + + return { format, attachment, previewAttributes } + } + + private async assertPreviewAttributesMatchSchemaAttributes( + agentContext: AgentContext, + offer: AnonCredsCredentialOffer, + attributes: CredentialPreviewAttributeOptions[] + ): Promise { + const registryService = agentContext.dependencyManager.resolve(AnonCredsRegistryService) + const registry = registryService.getRegistryForIdentifier(agentContext, offer.schema_id) + + const schemaResult = await registry.getSchema(agentContext, offer.schema_id) + + if (!schemaResult.schema) { + throw new AriesFrameworkError( + `Unable to resolve schema ${offer.schema_id} from registry: ${schemaResult.resolutionMetadata.error} ${schemaResult.resolutionMetadata.message}` + ) + } + + assertAttributesMatch(schemaResult.schema, attributes) + } + + /** + * Get linked attachments for anoncreds format from a proposal message. This allows attachments + * to be copied across to old style credential records + * + * @param options ProposeCredentialOptions object containing (optionally) the linked attachments + * @return array of linked attachments or undefined if none present + */ + private getCredentialLinkedAttachments( + attributes?: CredentialPreviewAttributeOptions[], + linkedAttachments?: LinkedAttachment[] + ): { + attachments?: V1Attachment[] + previewAttributes?: CredentialPreviewAttributeOptions[] + } { + if (!linkedAttachments && !attributes) { + return {} + } + + let previewAttributes = attributes ?? [] + let attachments: V1Attachment[] | undefined + + if (linkedAttachments) { + // there are linked attachments so transform into the attribute field of the CredentialPreview object for + // this proposal + previewAttributes = createAndLinkAttachmentsToPreview(linkedAttachments, previewAttributes) + attachments = linkedAttachments.map((linkedAttachment) => linkedAttachment.attachment) + } + + return { attachments, previewAttributes } + } + + /** + * Returns an object of type {@link Attachment} for use in credential exchange messages. + * It looks up the correct format identifier and encodes the data as a base64 attachment. + * + * @param data The data to include in the attach object + * @param id the attach id from the formats component of the message + */ + public getFormatData(data: unknown, id: string): V1Attachment { + const attachment = new V1Attachment({ + id, + mimeType: 'application/json', + data: new V1AttachmentData({ + base64: JsonEncoder.toBase64(data), + }), + }) + + return attachment + } +} diff --git a/packages/anoncreds/src/formats/AnonCredsProofFormat.ts b/packages/anoncreds/src/formats/AnonCredsProofFormat.ts new file mode 100644 index 0000000000..0c326943f8 --- /dev/null +++ b/packages/anoncreds/src/formats/AnonCredsProofFormat.ts @@ -0,0 +1,89 @@ +import type { + AnonCredsNonRevokedInterval, + AnonCredsPredicateType, + AnonCredsProof, + AnonCredsProofRequest, + AnonCredsRequestedAttribute, + AnonCredsRequestedAttributeMatch, + AnonCredsRequestedPredicate, + AnonCredsRequestedPredicateMatch, + AnonCredsSelectedCredentials, +} from '../models' +import type { ProofFormat } from '@aries-framework/core' + +export interface AnonCredsPresentationPreviewAttribute { + name: string + credentialDefinitionId?: string + mimeType?: string + value?: string + referent?: string +} + +export interface AnonCredsPresentationPreviewPredicate { + name: string + credentialDefinitionId: string + predicate: AnonCredsPredicateType + threshold: number +} + +/** + * Interface for creating an anoncreds proof proposal. + */ +export interface AnonCredsProposeProofFormat { + name?: string + version?: string + attributes?: AnonCredsPresentationPreviewAttribute[] + predicates?: AnonCredsPresentationPreviewPredicate[] +} + +/** + * Interface for creating an anoncreds proof request. + */ +export interface AnonCredsRequestProofFormat { + name: string + version: string + non_revoked?: AnonCredsNonRevokedInterval + requested_attributes?: Record + requested_predicates?: Record +} + +/** + * Interface for getting credentials for an indy proof request. + */ +export interface AnonCredsCredentialsForProofRequest { + attributes: Record + predicates: Record +} + +export interface AnonCredsGetCredentialsForProofRequestOptions { + filterByNonRevocationRequirements?: boolean +} + +export interface AnonCredsProofFormat extends ProofFormat { + formatKey: 'anoncreds' + + proofFormats: { + createProposal: AnonCredsProposeProofFormat + acceptProposal: { + name?: string + version?: string + } + createRequest: AnonCredsRequestProofFormat + acceptRequest: AnonCredsSelectedCredentials + + getCredentialsForRequest: { + input: AnonCredsGetCredentialsForProofRequestOptions + output: AnonCredsCredentialsForProofRequest + } + selectCredentialsForRequest: { + input: AnonCredsGetCredentialsForProofRequestOptions + output: AnonCredsSelectedCredentials + } + } + + formatData: { + proposal: AnonCredsProofRequest + request: AnonCredsProofRequest + presentation: AnonCredsProof + } +} diff --git a/packages/anoncreds/src/formats/AnonCredsProofFormatService.ts b/packages/anoncreds/src/formats/AnonCredsProofFormatService.ts new file mode 100644 index 0000000000..138876195c --- /dev/null +++ b/packages/anoncreds/src/formats/AnonCredsProofFormatService.ts @@ -0,0 +1,627 @@ +import type { + AnonCredsProofFormat, + AnonCredsCredentialsForProofRequest, + AnonCredsGetCredentialsForProofRequestOptions, +} from './AnonCredsProofFormat' +import type { + AnonCredsCredentialDefinition, + AnonCredsCredentialInfo, + AnonCredsProof, + AnonCredsRequestedAttribute, + AnonCredsRequestedPredicate, + AnonCredsSchema, + AnonCredsSelectedCredentials, + AnonCredsProofRequest, +} from '../models' +import type { AnonCredsHolderService, AnonCredsVerifierService, GetCredentialsForProofRequestReturn } from '../services' +import type { + ProofFormatService, + AgentContext, + ProofFormatCreateReturn, + FormatCreateRequestOptions, + ProofFormatCreateProposalOptions, + ProofFormatProcessOptions, + ProofFormatAcceptProposalOptions, + ProofFormatAcceptRequestOptions, + ProofFormatProcessPresentationOptions, + ProofFormatGetCredentialsForRequestOptions, + ProofFormatGetCredentialsForRequestReturn, + ProofFormatSelectCredentialsForRequestOptions, + ProofFormatSelectCredentialsForRequestReturn, + ProofFormatAutoRespondProposalOptions, + ProofFormatAutoRespondRequestOptions, + ProofFormatAutoRespondPresentationOptions, +} from '@aries-framework/core' + +import { + AriesFrameworkError, + V1Attachment, + V1AttachmentData, + JsonEncoder, + ProofFormatSpec, + JsonTransformer, +} from '@aries-framework/core' + +import { AnonCredsProofRequest as AnonCredsProofRequestClass } from '../models/AnonCredsProofRequest' +import { AnonCredsVerifierServiceSymbol, AnonCredsHolderServiceSymbol } from '../services' +import { AnonCredsRegistryService } from '../services/registry/AnonCredsRegistryService' +import { + sortRequestedCredentialsMatches, + createRequestFromPreview, + areAnonCredsProofRequestsEqual, + assertBestPracticeRevocationInterval, + checkValidCredentialValueEncoding, + encodeCredentialValue, + assertNoDuplicateGroupsNamesInProofRequest, + getRevocationRegistriesForRequest, + getRevocationRegistriesForProof, +} from '../utils' + +const ANONCREDS_PRESENTATION_PROPOSAL = 'anoncreds/proof-request@v1.0' +const ANONCREDS_PRESENTATION_REQUEST = 'anoncreds/proof-request@v1.0' +const ANONCREDS_PRESENTATION = 'anoncreds/proof@v1.0' + +export class AnonCredsProofFormatService implements ProofFormatService { + public readonly formatKey = 'anoncreds' as const + + public async createProposal( + agentContext: AgentContext, + { attachmentId, proofFormats }: ProofFormatCreateProposalOptions + ): Promise { + const format = new ProofFormatSpec({ + format: ANONCREDS_PRESENTATION_PROPOSAL, + attachmentId, + }) + + const anoncredsFormat = proofFormats.anoncreds + if (!anoncredsFormat) { + throw Error('Missing anoncreds format to create proposal attachment format') + } + + const proofRequest = createRequestFromPreview({ + attributes: anoncredsFormat.attributes ?? [], + predicates: anoncredsFormat.predicates ?? [], + name: anoncredsFormat.name ?? 'Proof request', + version: anoncredsFormat.version ?? '1.0', + nonce: await agentContext.wallet.generateNonce(), + }) + const attachment = this.getFormatData(proofRequest, format.attachmentId) + + return { attachment, format } + } + + public async processProposal(agentContext: AgentContext, { attachment }: ProofFormatProcessOptions): Promise { + const proposalJson = attachment.getDataAsJson() + + // fromJson also validates + JsonTransformer.fromJSON(proposalJson, AnonCredsProofRequestClass) + + // Assert attribute and predicate (group) names do not match + assertNoDuplicateGroupsNamesInProofRequest(proposalJson) + } + + public async acceptProposal( + agentContext: AgentContext, + { proposalAttachment, attachmentId }: ProofFormatAcceptProposalOptions + ): Promise { + const format = new ProofFormatSpec({ + format: ANONCREDS_PRESENTATION_REQUEST, + attachmentId, + }) + + const proposalJson = proposalAttachment.getDataAsJson() + + const request = { + ...proposalJson, + // We never want to reuse the nonce from the proposal, as this will allow replay attacks + nonce: await agentContext.wallet.generateNonce(), + } + + const attachment = this.getFormatData(request, format.attachmentId) + + return { attachment, format } + } + + public async createRequest( + agentContext: AgentContext, + { attachmentId, proofFormats }: FormatCreateRequestOptions + ): Promise { + const format = new ProofFormatSpec({ + format: ANONCREDS_PRESENTATION_REQUEST, + attachmentId, + }) + + const anoncredsFormat = proofFormats.anoncreds + if (!anoncredsFormat) { + throw Error('Missing anoncreds format in create request attachment format') + } + + const request = { + name: anoncredsFormat.name, + version: anoncredsFormat.version, + nonce: await agentContext.wallet.generateNonce(), + requested_attributes: anoncredsFormat.requested_attributes ?? {}, + requested_predicates: anoncredsFormat.requested_predicates ?? {}, + non_revoked: anoncredsFormat.non_revoked, + } + + // Assert attribute and predicate (group) names do not match + assertNoDuplicateGroupsNamesInProofRequest(request) + + const attachment = this.getFormatData(request, format.attachmentId) + + return { attachment, format } + } + + public async processRequest(agentContext: AgentContext, { attachment }: ProofFormatProcessOptions): Promise { + const requestJson = attachment.getDataAsJson() + + // fromJson also validates + JsonTransformer.fromJSON(requestJson, AnonCredsProofRequestClass) + + // Assert attribute and predicate (group) names do not match + assertNoDuplicateGroupsNamesInProofRequest(requestJson) + } + + public async acceptRequest( + agentContext: AgentContext, + { proofFormats, requestAttachment, attachmentId }: ProofFormatAcceptRequestOptions + ): Promise { + const format = new ProofFormatSpec({ + format: ANONCREDS_PRESENTATION, + attachmentId, + }) + const requestJson = requestAttachment.getDataAsJson() + + const anoncredsFormat = proofFormats?.anoncreds + + const selectedCredentials = + anoncredsFormat ?? + (await this._selectCredentialsForRequest(agentContext, requestJson, { + filterByNonRevocationRequirements: true, + })) + + const proof = await this.createProof(agentContext, requestJson, selectedCredentials) + const attachment = this.getFormatData(proof, format.attachmentId) + + return { + attachment, + format, + } + } + + public async processPresentation( + agentContext: AgentContext, + { requestAttachment, attachment }: ProofFormatProcessPresentationOptions + ): Promise { + const verifierService = + agentContext.dependencyManager.resolve(AnonCredsVerifierServiceSymbol) + + const proofRequestJson = requestAttachment.getDataAsJson() + + // NOTE: we don't do validation here, as this is handled by the AnonCreds implementation, however + // this can lead to confusing error messages. We should consider doing validation here as well. + // Defining a class-transformer/class-validator class seems a bit overkill, and the usage of interfaces + // for the anoncreds package keeps things simple. Maybe we can try to use something like zod to validate + const proofJson = attachment.getDataAsJson() + + for (const [referent, attribute] of Object.entries(proofJson.requested_proof.revealed_attrs)) { + if (!checkValidCredentialValueEncoding(attribute.raw, attribute.encoded)) { + throw new AriesFrameworkError( + `The encoded value for '${referent}' is invalid. ` + + `Expected '${encodeCredentialValue(attribute.raw)}'. ` + + `Actual '${attribute.encoded}'` + ) + } + } + + for (const [, attributeGroup] of Object.entries(proofJson.requested_proof.revealed_attr_groups ?? {})) { + for (const [attributeName, attribute] of Object.entries(attributeGroup.values)) { + if (!checkValidCredentialValueEncoding(attribute.raw, attribute.encoded)) { + throw new AriesFrameworkError( + `The encoded value for '${attributeName}' is invalid. ` + + `Expected '${encodeCredentialValue(attribute.raw)}'. ` + + `Actual '${attribute.encoded}'` + ) + } + } + } + + const schemas = await this.getSchemas(agentContext, new Set(proofJson.identifiers.map((i) => i.schema_id))) + const credentialDefinitions = await this.getCredentialDefinitions( + agentContext, + new Set(proofJson.identifiers.map((i) => i.cred_def_id)) + ) + + const revocationRegistries = await getRevocationRegistriesForProof(agentContext, proofJson) + + return await verifierService.verifyProof(agentContext, { + proofRequest: proofRequestJson, + proof: proofJson, + schemas, + credentialDefinitions, + revocationRegistries, + }) + } + + public async getCredentialsForRequest( + agentContext: AgentContext, + { requestAttachment, proofFormats }: ProofFormatGetCredentialsForRequestOptions + ): Promise> { + const proofRequestJson = requestAttachment.getDataAsJson() + + // Set default values + const { filterByNonRevocationRequirements = true } = proofFormats?.anoncreds ?? {} + + const credentialsForRequest = await this._getCredentialsForRequest(agentContext, proofRequestJson, { + filterByNonRevocationRequirements, + }) + + return credentialsForRequest + } + + public async selectCredentialsForRequest( + agentContext: AgentContext, + { requestAttachment, proofFormats }: ProofFormatSelectCredentialsForRequestOptions + ): Promise> { + const proofRequestJson = requestAttachment.getDataAsJson() + + // Set default values + const { filterByNonRevocationRequirements = true } = proofFormats?.anoncreds ?? {} + + const selectedCredentials = this._selectCredentialsForRequest(agentContext, proofRequestJson, { + filterByNonRevocationRequirements, + }) + + return selectedCredentials + } + + public async shouldAutoRespondToProposal( + agentContext: AgentContext, + { proposalAttachment, requestAttachment }: ProofFormatAutoRespondProposalOptions + ): Promise { + const proposalJson = proposalAttachment.getDataAsJson() + const requestJson = requestAttachment.getDataAsJson() + + const areRequestsEqual = areAnonCredsProofRequestsEqual(proposalJson, requestJson) + agentContext.config.logger.debug(`AnonCreds request and proposal are are equal: ${areRequestsEqual}`, { + proposalJson, + requestJson, + }) + + return areRequestsEqual + } + + public async shouldAutoRespondToRequest( + agentContext: AgentContext, + { proposalAttachment, requestAttachment }: ProofFormatAutoRespondRequestOptions + ): Promise { + const proposalJson = proposalAttachment.getDataAsJson() + const requestJson = requestAttachment.getDataAsJson() + + return areAnonCredsProofRequestsEqual(proposalJson, requestJson) + } + + public async shouldAutoRespondToPresentation( + // eslint-disable-next-line @typescript-eslint/no-unused-vars + _agentContext: AgentContext, + // eslint-disable-next-line @typescript-eslint/no-unused-vars + _options: ProofFormatAutoRespondPresentationOptions + ): Promise { + // The presentation is already verified in processPresentation, so we can just return true here. + // It's only an ack, so it's just that we received the presentation. + return true + } + + public supportsFormat(formatIdentifier: string): boolean { + const supportedFormats = [ANONCREDS_PRESENTATION_PROPOSAL, ANONCREDS_PRESENTATION_REQUEST, ANONCREDS_PRESENTATION] + return supportedFormats.includes(formatIdentifier) + } + + private async _getCredentialsForRequest( + agentContext: AgentContext, + proofRequest: AnonCredsProofRequest, + options: AnonCredsGetCredentialsForProofRequestOptions + ): Promise { + const credentialsForProofRequest: AnonCredsCredentialsForProofRequest = { + attributes: {}, + predicates: {}, + } + + for (const [referent, requestedAttribute] of Object.entries(proofRequest.requested_attributes)) { + const credentials = await this.getCredentialsForProofRequestReferent(agentContext, proofRequest, referent) + + credentialsForProofRequest.attributes[referent] = sortRequestedCredentialsMatches( + await Promise.all( + credentials.map(async (credential) => { + const { isRevoked, timestamp } = await this.getRevocationStatus( + agentContext, + proofRequest, + requestedAttribute, + credential.credentialInfo + ) + + return { + credentialId: credential.credentialInfo.credentialId, + revealed: true, + credentialInfo: credential.credentialInfo, + timestamp, + revoked: isRevoked, + } + }) + ) + ) + + // We only attach revoked state if non-revocation is requested. So if revoked is true it means + // the credential is not applicable to the proof request + if (options.filterByNonRevocationRequirements) { + credentialsForProofRequest.attributes[referent] = credentialsForProofRequest.attributes[referent].filter( + (r) => !r.revoked + ) + } + } + + for (const [referent, requestedPredicate] of Object.entries(proofRequest.requested_predicates)) { + const credentials = await this.getCredentialsForProofRequestReferent(agentContext, proofRequest, referent) + + credentialsForProofRequest.predicates[referent] = sortRequestedCredentialsMatches( + await Promise.all( + credentials.map(async (credential) => { + const { isRevoked, timestamp } = await this.getRevocationStatus( + agentContext, + proofRequest, + requestedPredicate, + credential.credentialInfo + ) + + return { + credentialId: credential.credentialInfo.credentialId, + credentialInfo: credential.credentialInfo, + timestamp, + revoked: isRevoked, + } + }) + ) + ) + + // We only attach revoked state if non-revocation is requested. So if revoked is true it means + // the credential is not applicable to the proof request + if (options.filterByNonRevocationRequirements) { + credentialsForProofRequest.predicates[referent] = credentialsForProofRequest.predicates[referent].filter( + (r) => !r.revoked + ) + } + } + + return credentialsForProofRequest + } + + private async _selectCredentialsForRequest( + agentContext: AgentContext, + proofRequest: AnonCredsProofRequest, + options: AnonCredsGetCredentialsForProofRequestOptions + ): Promise { + const credentialsForRequest = await this._getCredentialsForRequest(agentContext, proofRequest, options) + + const selectedCredentials: AnonCredsSelectedCredentials = { + attributes: {}, + predicates: {}, + selfAttestedAttributes: {}, + } + + Object.keys(credentialsForRequest.attributes).forEach((attributeName) => { + const attributeArray = credentialsForRequest.attributes[attributeName] + + if (attributeArray.length === 0) { + throw new AriesFrameworkError('Unable to automatically select requested attributes.') + } + + selectedCredentials.attributes[attributeName] = attributeArray[0] + }) + + Object.keys(credentialsForRequest.predicates).forEach((attributeName) => { + if (credentialsForRequest.predicates[attributeName].length === 0) { + throw new AriesFrameworkError('Unable to automatically select requested predicates.') + } else { + selectedCredentials.predicates[attributeName] = credentialsForRequest.predicates[attributeName][0] + } + }) + + return selectedCredentials + } + + private async getCredentialsForProofRequestReferent( + agentContext: AgentContext, + proofRequest: AnonCredsProofRequest, + attributeReferent: string + ): Promise { + const holderService = agentContext.dependencyManager.resolve(AnonCredsHolderServiceSymbol) + + const credentials = await holderService.getCredentialsForProofRequest(agentContext, { + proofRequest, + attributeReferent, + }) + + return credentials + } + + /** + * Build schemas object needed to create and verify proof objects. + * + * Creates object with `{ schemaId: AnonCredsSchema }` mapping + * + * @param schemaIds List of schema ids + * @returns Object containing schemas for specified schema ids + * + */ + private async getSchemas(agentContext: AgentContext, schemaIds: Set) { + const registryService = agentContext.dependencyManager.resolve(AnonCredsRegistryService) + + const schemas: { [key: string]: AnonCredsSchema } = {} + + for (const schemaId of schemaIds) { + const schemaRegistry = registryService.getRegistryForIdentifier(agentContext, schemaId) + const schemaResult = await schemaRegistry.getSchema(agentContext, schemaId) + + if (!schemaResult.schema) { + throw new AriesFrameworkError(`Schema not found for id ${schemaId}: ${schemaResult.resolutionMetadata.message}`) + } + + schemas[schemaId] = schemaResult.schema + } + + return schemas + } + + /** + * Build credential definitions object needed to create and verify proof objects. + * + * Creates object with `{ credentialDefinitionId: AnonCredsCredentialDefinition }` mapping + * + * @param credentialDefinitionIds List of credential definition ids + * @returns Object containing credential definitions for specified credential definition ids + * + */ + private async getCredentialDefinitions(agentContext: AgentContext, credentialDefinitionIds: Set) { + const registryService = agentContext.dependencyManager.resolve(AnonCredsRegistryService) + + const credentialDefinitions: { [key: string]: AnonCredsCredentialDefinition } = {} + + for (const credentialDefinitionId of credentialDefinitionIds) { + const credentialDefinitionRegistry = registryService.getRegistryForIdentifier( + agentContext, + credentialDefinitionId + ) + + const credentialDefinitionResult = await credentialDefinitionRegistry.getCredentialDefinition( + agentContext, + credentialDefinitionId + ) + + if (!credentialDefinitionResult.credentialDefinition) { + throw new AriesFrameworkError( + `Credential definition not found for id ${credentialDefinitionId}: ${credentialDefinitionResult.resolutionMetadata.message}` + ) + } + + credentialDefinitions[credentialDefinitionId] = credentialDefinitionResult.credentialDefinition + } + + return credentialDefinitions + } + + private async getRevocationStatus( + agentContext: AgentContext, + proofRequest: AnonCredsProofRequest, + requestedItem: AnonCredsRequestedAttribute | AnonCredsRequestedPredicate, + credentialInfo: AnonCredsCredentialInfo + ) { + const requestNonRevoked = requestedItem.non_revoked ?? proofRequest.non_revoked + const credentialRevocationId = credentialInfo.credentialRevocationId + const revocationRegistryId = credentialInfo.revocationRegistryId + + // If revocation interval is not present or the credential is not revocable then we + // don't need to fetch the revocation status + if (!requestNonRevoked || !credentialRevocationId || !revocationRegistryId) { + return { isRevoked: undefined, timestamp: undefined } + } + + agentContext.config.logger.trace( + `Fetching credential revocation status for credential revocation id '${credentialRevocationId}' with revocation interval with from '${requestNonRevoked.from}' and to '${requestNonRevoked.to}'` + ) + + // Make sure the revocation interval follows best practices from Aries RFC 0441 + assertBestPracticeRevocationInterval(requestNonRevoked) + + const registryService = agentContext.dependencyManager.resolve(AnonCredsRegistryService) + const registry = registryService.getRegistryForIdentifier(agentContext, revocationRegistryId) + + const revocationStatusResult = await registry.getRevocationStatusList( + agentContext, + revocationRegistryId, + requestNonRevoked.to ?? Date.now() + ) + + if (!revocationStatusResult.revocationStatusList) { + throw new AriesFrameworkError( + `Could not retrieve revocation status list for revocation registry ${revocationRegistryId}: ${revocationStatusResult.resolutionMetadata.message}` + ) + } + + // Item is revoked when the value at the index is 1 + const isRevoked = revocationStatusResult.revocationStatusList.revocationList[parseInt(credentialRevocationId)] === 1 + + agentContext.config.logger.trace( + `Credential with credential revocation index '${credentialRevocationId}' is ${ + isRevoked ? '' : 'not ' + }revoked with revocation interval with to '${requestNonRevoked.to}' & from '${requestNonRevoked.from}'` + ) + + return { + isRevoked, + timestamp: revocationStatusResult.revocationStatusList.timestamp, + } + } + + /** + * Create anoncreds proof from a given proof request and requested credential object. + * + * @param proofRequest The proof request to create the proof for + * @param requestedCredentials The requested credentials object specifying which credentials to use for the proof + * @returns anoncreds proof object + */ + private async createProof( + agentContext: AgentContext, + proofRequest: AnonCredsProofRequest, + selectedCredentials: AnonCredsSelectedCredentials + ): Promise { + const holderService = agentContext.dependencyManager.resolve(AnonCredsHolderServiceSymbol) + + const credentialObjects = await Promise.all( + [...Object.values(selectedCredentials.attributes), ...Object.values(selectedCredentials.predicates)].map( + async (c) => c.credentialInfo ?? holderService.getCredential(agentContext, { credentialId: c.credentialId }) + ) + ) + + const schemas = await this.getSchemas(agentContext, new Set(credentialObjects.map((c) => c.schemaId))) + const credentialDefinitions = await this.getCredentialDefinitions( + agentContext, + new Set(credentialObjects.map((c) => c.credentialDefinitionId)) + ) + + // selectedCredentials are overridden with specified timestamps of the revocation status list that + // should be used for the selected credentials. + const { revocationRegistries, updatedSelectedCredentials } = await getRevocationRegistriesForRequest( + agentContext, + proofRequest, + selectedCredentials + ) + + return await holderService.createProof(agentContext, { + proofRequest, + selectedCredentials: updatedSelectedCredentials, + schemas, + credentialDefinitions, + revocationRegistries, + }) + } + + /** + * Returns an object of type {@link Attachment} for use in credential exchange messages. + * It looks up the correct format identifier and encodes the data as a base64 attachment. + * + * @param data The data to include in the attach object + * @param id the attach id from the formats component of the message + */ + private getFormatData(data: unknown, id: string): V1Attachment { + const attachment = new V1Attachment({ + id, + mimeType: 'application/json', + data: new V1AttachmentData({ + base64: JsonEncoder.toBase64(data), + }), + }) + + return attachment + } +} diff --git a/packages/anoncreds/src/formats/LegacyIndyCredentialFormat.ts b/packages/anoncreds/src/formats/LegacyIndyCredentialFormat.ts new file mode 100644 index 0000000000..f4a6f2a0d2 --- /dev/null +++ b/packages/anoncreds/src/formats/LegacyIndyCredentialFormat.ts @@ -0,0 +1,56 @@ +import type { + AnonCredsAcceptOfferFormat, + AnonCredsAcceptProposalFormat, + AnonCredsAcceptRequestFormat, + AnonCredsCredentialProposalFormat, + AnonCredsOfferCredentialFormat, + AnonCredsProposeCredentialFormat, +} from './AnonCredsCredentialFormat' +import type { AnonCredsCredential, AnonCredsCredentialOffer, AnonCredsCredentialRequest } from '../models' +import type { CredentialFormat } from '@aries-framework/core' + +// Legacy indy credential proposal doesn't support _id properties +export type LegacyIndyCredentialProposalFormat = Omit< + AnonCredsCredentialProposalFormat, + 'schema_issuer_id' | 'issuer_id' +> + +/** + * This defines the module payload for calling CredentialsApi.createProposal + * or CredentialsApi.negotiateOffer + * + * NOTE: This doesn't include the `issuerId` and `schemaIssuerId` properties that are present in the newer format. + */ +export type LegacyIndyProposeCredentialFormat = Omit + +export interface LegacyIndyCredentialRequest extends AnonCredsCredentialRequest { + // prover_did is optional in AnonCreds credential request, but required in legacy format + prover_did: string +} + +export interface LegacyIndyCredentialFormat extends CredentialFormat { + formatKey: 'indy' + + // The stored type is the same as the anoncreds credential service + credentialRecordType: 'anoncreds' + + // credential formats are the same as the AnonCreds credential format + credentialFormats: { + // The createProposal interface is different between the interfaces + createProposal: LegacyIndyProposeCredentialFormat + acceptProposal: AnonCredsAcceptProposalFormat + createOffer: AnonCredsOfferCredentialFormat + acceptOffer: AnonCredsAcceptOfferFormat + createRequest: never // cannot start from createRequest + acceptRequest: AnonCredsAcceptRequestFormat + } + + // Format data is based on RFC 0592 + // https://github.com/hyperledger/aries-rfcs/tree/main/features/0592-indy-attachments + formatData: { + proposal: LegacyIndyCredentialProposalFormat + offer: AnonCredsCredentialOffer + request: LegacyIndyCredentialRequest + credential: AnonCredsCredential + } +} diff --git a/packages/anoncreds/src/formats/LegacyIndyCredentialFormatService.ts b/packages/anoncreds/src/formats/LegacyIndyCredentialFormatService.ts new file mode 100644 index 0000000000..9bc9e2a000 --- /dev/null +++ b/packages/anoncreds/src/formats/LegacyIndyCredentialFormatService.ts @@ -0,0 +1,649 @@ +import type { LegacyIndyCredentialFormat, LegacyIndyCredentialProposalFormat } from './LegacyIndyCredentialFormat' +import type { + AnonCredsCredential, + AnonCredsCredentialOffer, + AnonCredsCredentialRequest, + AnonCredsCredentialRequestMetadata, +} from '../models' +import type { AnonCredsIssuerService, AnonCredsHolderService, GetRevocationRegistryDefinitionReturn } from '../services' +import type { AnonCredsCredentialMetadata } from '../utils/metadata' +import type { + CredentialFormatService, + AgentContext, + CredentialFormatCreateProposalOptions, + CredentialFormatCreateProposalReturn, + CredentialFormatProcessOptions, + CredentialFormatAcceptProposalOptions, + CredentialFormatCreateOfferReturn, + CredentialFormatCreateOfferOptions, + CredentialFormatAcceptOfferOptions, + CredentialFormatCreateReturn, + CredentialFormatAcceptRequestOptions, + CredentialFormatProcessCredentialOptions, + CredentialFormatAutoRespondProposalOptions, + CredentialFormatAutoRespondOfferOptions, + CredentialFormatAutoRespondRequestOptions, + CredentialFormatAutoRespondCredentialOptions, + CredentialExchangeRecord, + CredentialPreviewAttributeOptions, + LinkedAttachment, +} from '@aries-framework/core' + +import { + ProblemReportError, + MessageValidator, + CredentialFormatSpec, + AriesFrameworkError, + JsonEncoder, + utils, + CredentialProblemReportReason, + JsonTransformer, + V1Attachment, + V1AttachmentData, +} from '@aries-framework/core' + +import { AnonCredsError } from '../error' +import { AnonCredsCredentialProposal } from '../models/AnonCredsCredentialProposal' +import { AnonCredsIssuerServiceSymbol, AnonCredsHolderServiceSymbol } from '../services' +import { AnonCredsRegistryService } from '../services/registry/AnonCredsRegistryService' +import { + convertAttributesToCredentialValues, + assertCredentialValuesMatch, + checkCredentialValuesMatch, + assertAttributesMatch, + createAndLinkAttachmentsToPreview, +} from '../utils/credential' +import { isUnqualifiedCredentialDefinitionId, isUnqualifiedSchemaId } from '../utils/indyIdentifiers' +import { AnonCredsCredentialMetadataKey, AnonCredsCredentialRequestMetadataKey } from '../utils/metadata' +import { generateLegacyProverDidLikeString } from '../utils/proverDid' + +const INDY_CRED_ABSTRACT = 'hlindy/cred-abstract@v2.0' +const INDY_CRED_REQUEST = 'hlindy/cred-req@v2.0' +const INDY_CRED_FILTER = 'hlindy/cred-filter@v2.0' +const INDY_CRED = 'hlindy/cred@v2.0' + +export class LegacyIndyCredentialFormatService implements CredentialFormatService { + /** formatKey is the key used when calling agent.credentials.xxx with credentialFormats.indy */ + public readonly formatKey = 'indy' as const + + /** + * credentialRecordType is the type of record that stores the credential. It is stored in the credential + * record binding in the credential exchange record. + */ + public readonly credentialRecordType = 'anoncreds' as const + + /** + * Create a {@link AttachmentFormats} object dependent on the message type. + * + * @param options The object containing all the options for the proposed credential + * @returns object containing associated attachment, format and optionally the credential preview + * + */ + public async createProposal( + agentContext: AgentContext, + { credentialFormats, credentialRecord }: CredentialFormatCreateProposalOptions + ): Promise { + const format = new CredentialFormatSpec({ + format: INDY_CRED_FILTER, + }) + + const indyFormat = credentialFormats.indy + + if (!indyFormat) { + throw new AriesFrameworkError('Missing indy payload in createProposal') + } + + // We want all properties except for `attributes` and `linkedAttachments` attributes. + // The easiest way is to destructure and use the spread operator. But that leaves the other properties unused + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const { attributes, linkedAttachments, ...indyCredentialProposal } = indyFormat + const proposal = new AnonCredsCredentialProposal(indyCredentialProposal) + + try { + MessageValidator.validateSync(proposal) + } catch (error) { + throw new AriesFrameworkError(`Invalid proposal supplied: ${indyCredentialProposal} in Indy Format Service`) + } + + const attachment = this.getFormatData(JsonTransformer.toJSON(proposal), format.attachmentId) + + const { previewAttributes } = this.getCredentialLinkedAttachments( + indyFormat.attributes, + indyFormat.linkedAttachments + ) + + // Set the metadata + credentialRecord.metadata.set(AnonCredsCredentialMetadataKey, { + schemaId: proposal.schemaId, + credentialDefinitionId: proposal.credentialDefinitionId, + }) + + return { format, attachment, previewAttributes } + } + + public async processProposal( + agentContext: AgentContext, + { attachment }: CredentialFormatProcessOptions + ): Promise { + const proposalJson = attachment.getDataAsJson() + + JsonTransformer.fromJSON(proposalJson, AnonCredsCredentialProposal) + } + + public async acceptProposal( + agentContext: AgentContext, + { + attachmentId, + credentialFormats, + credentialRecord, + proposalAttachment, + }: CredentialFormatAcceptProposalOptions + ): Promise { + const indyFormat = credentialFormats?.indy + + const proposalJson = proposalAttachment.getDataAsJson() + const credentialDefinitionId = indyFormat?.credentialDefinitionId ?? proposalJson.cred_def_id + + const attributes = indyFormat?.attributes ?? credentialRecord.credentialAttributes + + if (!credentialDefinitionId) { + throw new AriesFrameworkError( + 'No credential definition id in proposal or provided as input to accept proposal method.' + ) + } + + if (!isUnqualifiedCredentialDefinitionId(credentialDefinitionId)) { + throw new AriesFrameworkError(`${credentialDefinitionId} is not a valid legacy indy credential definition id`) + } + + if (!attributes) { + throw new AriesFrameworkError('No attributes in proposal or provided as input to accept proposal method.') + } + + const { format, attachment, previewAttributes } = await this.createIndyOffer(agentContext, { + credentialRecord, + attachmentId, + attributes, + credentialDefinitionId, + linkedAttachments: indyFormat?.linkedAttachments, + }) + + return { format, attachment, previewAttributes } + } + + /** + * Create a credential attachment format for a credential request. + * + * @param options The object containing all the options for the credential offer + * @returns object containing associated attachment, formats and offersAttach elements + * + */ + public async createOffer( + agentContext: AgentContext, + { + credentialFormats, + credentialRecord, + attachmentId, + }: CredentialFormatCreateOfferOptions + ): Promise { + const indyFormat = credentialFormats.indy + + if (!indyFormat) { + throw new AriesFrameworkError('Missing indy credentialFormat data') + } + + const { format, attachment, previewAttributes } = await this.createIndyOffer(agentContext, { + credentialRecord, + attachmentId, + attributes: indyFormat.attributes, + credentialDefinitionId: indyFormat.credentialDefinitionId, + linkedAttachments: indyFormat.linkedAttachments, + }) + + return { format, attachment, previewAttributes } + } + + public async processOffer( + agentContext: AgentContext, + { attachment, credentialRecord }: CredentialFormatProcessOptions + ) { + agentContext.config.logger.debug(`Processing indy credential offer for credential record ${credentialRecord.id}`) + + const credOffer = attachment.getDataAsJson() + + if (!isUnqualifiedSchemaId(credOffer.schema_id) || !isUnqualifiedCredentialDefinitionId(credOffer.cred_def_id)) { + throw new ProblemReportError('Invalid credential offer', { + problemCode: CredentialProblemReportReason.IssuanceAbandoned, + }) + } + } + + public async acceptOffer( + agentContext: AgentContext, + { + credentialRecord, + attachmentId, + offerAttachment, + credentialFormats, + }: CredentialFormatAcceptOfferOptions + ): Promise { + const registryService = agentContext.dependencyManager.resolve(AnonCredsRegistryService) + const holderService = agentContext.dependencyManager.resolve(AnonCredsHolderServiceSymbol) + + const credentialOffer = offerAttachment.getDataAsJson() + + if (!isUnqualifiedCredentialDefinitionId(credentialOffer.cred_def_id)) { + throw new AriesFrameworkError( + `${credentialOffer.cred_def_id} is not a valid legacy indy credential definition id` + ) + } + // Get credential definition + const registry = registryService.getRegistryForIdentifier(agentContext, credentialOffer.cred_def_id) + const { credentialDefinition, resolutionMetadata } = await registry.getCredentialDefinition( + agentContext, + credentialOffer.cred_def_id + ) + + if (!credentialDefinition) { + throw new AnonCredsError( + `Unable to retrieve credential definition with id ${credentialOffer.cred_def_id}: ${resolutionMetadata.error} ${resolutionMetadata.message}` + ) + } + + const { credentialRequest, credentialRequestMetadata } = await holderService.createCredentialRequest(agentContext, { + credentialOffer, + credentialDefinition, + linkSecretId: credentialFormats?.indy?.linkSecretId, + useLegacyProverDid: true, + }) + + if (!credentialRequest.prover_did) { + // We just generate a prover did like string, as it's not used for anything and we don't need + // to prove ownership of the did. It's deprecated in AnonCreds v1, but kept for backwards compatibility + credentialRequest.prover_did = generateLegacyProverDidLikeString() + } + + credentialRecord.metadata.set( + AnonCredsCredentialRequestMetadataKey, + credentialRequestMetadata + ) + credentialRecord.metadata.set(AnonCredsCredentialMetadataKey, { + credentialDefinitionId: credentialOffer.cred_def_id, + schemaId: credentialOffer.schema_id, + }) + + const format = new CredentialFormatSpec({ + attachmentId, + format: INDY_CRED_REQUEST, + }) + + const attachment = this.getFormatData(credentialRequest, format.attachmentId) + return { format, attachment } + } + + /** + * Starting from a request is not supported for indy credentials, this method only throws an error. + */ + public async createRequest(): Promise { + throw new AriesFrameworkError('Starting from a request is not supported for indy credentials') + } + + /** + * We don't have any models to validate an indy request object, for now this method does nothing + */ + // eslint-disable-next-line @typescript-eslint/no-unused-vars + public async processRequest(agentContext: AgentContext, options: CredentialFormatProcessOptions): Promise { + // not needed for Indy + } + + public async acceptRequest( + agentContext: AgentContext, + { + credentialRecord, + attachmentId, + offerAttachment, + requestAttachment, + }: CredentialFormatAcceptRequestOptions + ): Promise { + // Assert credential attributes + const credentialAttributes = credentialRecord.credentialAttributes + if (!credentialAttributes) { + throw new AriesFrameworkError( + `Missing required credential attribute values on credential record with id ${credentialRecord.id}` + ) + } + + const anonCredsIssuerService = + agentContext.dependencyManager.resolve(AnonCredsIssuerServiceSymbol) + + const credentialOffer = offerAttachment?.getDataAsJson() + if (!credentialOffer) throw new AriesFrameworkError('Missing indy credential offer in createCredential') + + const credentialRequest = requestAttachment.getDataAsJson() + if (!credentialRequest) throw new AriesFrameworkError('Missing indy credential request in createCredential') + + const { credential, credentialRevocationId } = await anonCredsIssuerService.createCredential(agentContext, { + credentialOffer, + credentialRequest, + credentialValues: convertAttributesToCredentialValues(credentialAttributes), + }) + + if (credential.rev_reg_id) { + credentialRecord.metadata.add(AnonCredsCredentialMetadataKey, { + credentialRevocationId: credentialRevocationId, + revocationRegistryId: credential.rev_reg_id, + }) + credentialRecord.setTags({ + anonCredsRevocationRegistryId: credential.rev_reg_id, + anonCredsCredentialRevocationId: credentialRevocationId, + }) + } + + const format = new CredentialFormatSpec({ + attachmentId, + format: INDY_CRED, + }) + + const attachment = this.getFormatData(credential, format.attachmentId) + return { format, attachment } + } + + /** + * Processes an incoming credential - retrieve metadata, retrieve payload and store it in the Indy wallet + * @param options the issue credential message wrapped inside this object + * @param credentialRecord the credential exchange record for this credential + */ + public async processCredential( + agentContext: AgentContext, + { credentialRecord, attachment }: CredentialFormatProcessCredentialOptions + ): Promise { + const credentialRequestMetadata = credentialRecord.metadata.get( + AnonCredsCredentialRequestMetadataKey + ) + + const registryService = agentContext.dependencyManager.resolve(AnonCredsRegistryService) + const anonCredsHolderService = + agentContext.dependencyManager.resolve(AnonCredsHolderServiceSymbol) + + if (!credentialRequestMetadata) { + throw new AriesFrameworkError( + `Missing required request metadata for credential exchange with thread id with id ${credentialRecord.id}` + ) + } + + if (!credentialRecord.credentialAttributes) { + throw new AriesFrameworkError( + 'Missing credential attributes on credential record. Unable to check credential attributes' + ) + } + + const anonCredsCredential = attachment.getDataAsJson() + + const credentialDefinitionResult = await registryService + .getRegistryForIdentifier(agentContext, anonCredsCredential.cred_def_id) + .getCredentialDefinition(agentContext, anonCredsCredential.cred_def_id) + if (!credentialDefinitionResult.credentialDefinition) { + throw new AriesFrameworkError( + `Unable to resolve credential definition ${anonCredsCredential.cred_def_id}: ${credentialDefinitionResult.resolutionMetadata.error} ${credentialDefinitionResult.resolutionMetadata.message}` + ) + } + + const schemaResult = await registryService + .getRegistryForIdentifier(agentContext, anonCredsCredential.cred_def_id) + .getSchema(agentContext, anonCredsCredential.schema_id) + if (!schemaResult.schema) { + throw new AriesFrameworkError( + `Unable to resolve schema ${anonCredsCredential.schema_id}: ${schemaResult.resolutionMetadata.error} ${schemaResult.resolutionMetadata.message}` + ) + } + + // Resolve revocation registry if credential is revocable + let revocationRegistryResult: null | GetRevocationRegistryDefinitionReturn = null + if (anonCredsCredential.rev_reg_id) { + revocationRegistryResult = await registryService + .getRegistryForIdentifier(agentContext, anonCredsCredential.rev_reg_id) + .getRevocationRegistryDefinition(agentContext, anonCredsCredential.rev_reg_id) + + if (!revocationRegistryResult.revocationRegistryDefinition) { + throw new AriesFrameworkError( + `Unable to resolve revocation registry definition ${anonCredsCredential.rev_reg_id}: ${revocationRegistryResult.resolutionMetadata.error} ${revocationRegistryResult.resolutionMetadata.message}` + ) + } + } + + // assert the credential values match the offer values + const recordCredentialValues = convertAttributesToCredentialValues(credentialRecord.credentialAttributes) + assertCredentialValuesMatch(anonCredsCredential.values, recordCredentialValues) + + const credentialId = await anonCredsHolderService.storeCredential(agentContext, { + credentialId: utils.uuid(), + credentialRequestMetadata, + credential: anonCredsCredential, + credentialDefinitionId: credentialDefinitionResult.credentialDefinitionId, + credentialDefinition: credentialDefinitionResult.credentialDefinition, + schema: schemaResult.schema, + revocationRegistry: revocationRegistryResult?.revocationRegistryDefinition + ? { + definition: revocationRegistryResult.revocationRegistryDefinition, + id: revocationRegistryResult.revocationRegistryDefinitionId, + } + : undefined, + }) + + // If the credential is revocable, store the revocation identifiers in the credential record + if (anonCredsCredential.rev_reg_id) { + const credential = await anonCredsHolderService.getCredential(agentContext, { credentialId }) + + credentialRecord.metadata.add(AnonCredsCredentialMetadataKey, { + credentialRevocationId: credential.credentialRevocationId, + revocationRegistryId: credential.revocationRegistryId, + }) + credentialRecord.setTags({ + anonCredsRevocationRegistryId: credential.revocationRegistryId, + anonCredsCredentialRevocationId: credential.credentialRevocationId, + }) + } + + credentialRecord.credentials.push({ + credentialRecordType: this.credentialRecordType, + credentialRecordId: credentialId, + }) + } + + public supportsFormat(format: string): boolean { + const supportedFormats = [INDY_CRED_ABSTRACT, INDY_CRED_REQUEST, INDY_CRED_FILTER, INDY_CRED] + + return supportedFormats.includes(format) + } + + /** + * Gets the attachment object for a given attachmentId. We need to get out the correct attachmentId for + * indy and then find the corresponding attachment (if there is one) + * @param formats the formats object containing the attachmentId + * @param messageAttachments the attachments containing the payload + * @returns The Attachment if found or undefined + * + */ + public getAttachment(formats: CredentialFormatSpec[], messageAttachments: V1Attachment[]): V1Attachment | undefined { + const supportedAttachmentIds = formats.filter((f) => this.supportsFormat(f.format)).map((f) => f.attachmentId) + const supportedAttachment = messageAttachments.find((attachment) => supportedAttachmentIds.includes(attachment.id)) + + return supportedAttachment + } + + public async deleteCredentialById(agentContext: AgentContext, credentialRecordId: string): Promise { + const anonCredsHolderService = + agentContext.dependencyManager.resolve(AnonCredsHolderServiceSymbol) + + await anonCredsHolderService.deleteCredential(agentContext, credentialRecordId) + } + + public async shouldAutoRespondToProposal( + agentContext: AgentContext, + { offerAttachment, proposalAttachment }: CredentialFormatAutoRespondProposalOptions + ) { + const proposalJson = proposalAttachment.getDataAsJson() + const offerJson = offerAttachment.getDataAsJson() + + // We want to make sure the credential definition matches. + // TODO: If no credential definition is present on the proposal, we could check whether the other fields + // of the proposal match with the credential definition id. + return proposalJson.cred_def_id === offerJson.cred_def_id + } + + public async shouldAutoRespondToOffer( + agentContext: AgentContext, + { offerAttachment, proposalAttachment }: CredentialFormatAutoRespondOfferOptions + ) { + const proposalJson = proposalAttachment.getDataAsJson() + const offerJson = offerAttachment.getDataAsJson() + + // We want to make sure the credential definition matches. + // TODO: If no credential definition is present on the proposal, we could check whether the other fields + // of the proposal match with the credential definition id. + return proposalJson.cred_def_id === offerJson.cred_def_id + } + + public async shouldAutoRespondToRequest( + agentContext: AgentContext, + { offerAttachment, requestAttachment }: CredentialFormatAutoRespondRequestOptions + ) { + const credentialOfferJson = offerAttachment.getDataAsJson() + const credentialRequestJson = requestAttachment.getDataAsJson() + + return credentialOfferJson.cred_def_id === credentialRequestJson.cred_def_id + } + + public async shouldAutoRespondToCredential( + agentContext: AgentContext, + { credentialRecord, requestAttachment, credentialAttachment }: CredentialFormatAutoRespondCredentialOptions + ) { + const credentialJson = credentialAttachment.getDataAsJson() + const credentialRequestJson = requestAttachment.getDataAsJson() + + // make sure the credential definition matches + if (credentialJson.cred_def_id !== credentialRequestJson.cred_def_id) return false + + // If we don't have any attributes stored we can't compare so always return false. + if (!credentialRecord.credentialAttributes) return false + const attributeValues = convertAttributesToCredentialValues(credentialRecord.credentialAttributes) + + // check whether the values match the values in the record + return checkCredentialValuesMatch(attributeValues, credentialJson.values) + } + + private async createIndyOffer( + agentContext: AgentContext, + { + credentialRecord, + attachmentId, + credentialDefinitionId, + attributes, + linkedAttachments, + }: { + credentialDefinitionId: string + credentialRecord: CredentialExchangeRecord + attachmentId?: string + attributes: CredentialPreviewAttributeOptions[] + linkedAttachments?: LinkedAttachment[] + } + ): Promise { + const anonCredsIssuerService = + agentContext.dependencyManager.resolve(AnonCredsIssuerServiceSymbol) + + // if the proposal has an attachment Id use that, otherwise the generated id of the formats object + const format = new CredentialFormatSpec({ + attachmentId: attachmentId, + format: INDY_CRED_ABSTRACT, + }) + + const offer = await anonCredsIssuerService.createCredentialOffer(agentContext, { + credentialDefinitionId, + }) + + const { previewAttributes } = this.getCredentialLinkedAttachments(attributes, linkedAttachments) + if (!previewAttributes) { + throw new AriesFrameworkError('Missing required preview attributes for indy offer') + } + + await this.assertPreviewAttributesMatchSchemaAttributes(agentContext, offer, previewAttributes) + + credentialRecord.metadata.set(AnonCredsCredentialMetadataKey, { + schemaId: offer.schema_id, + credentialDefinitionId: offer.cred_def_id, + }) + + const attachment = this.getFormatData(offer, format.attachmentId) + + return { format, attachment, previewAttributes } + } + + private async assertPreviewAttributesMatchSchemaAttributes( + agentContext: AgentContext, + offer: AnonCredsCredentialOffer, + attributes: CredentialPreviewAttributeOptions[] + ): Promise { + const registryService = agentContext.dependencyManager.resolve(AnonCredsRegistryService) + const registry = registryService.getRegistryForIdentifier(agentContext, offer.schema_id) + + const schemaResult = await registry.getSchema(agentContext, offer.schema_id) + + if (!schemaResult.schema) { + throw new AriesFrameworkError( + `Unable to resolve schema ${offer.schema_id} from registry: ${schemaResult.resolutionMetadata.error} ${schemaResult.resolutionMetadata.message}` + ) + } + + assertAttributesMatch(schemaResult.schema, attributes) + } + + /** + * Get linked attachments for indy format from a proposal message. This allows attachments + * to be copied across to old style credential records + * + * @param options ProposeCredentialOptions object containing (optionally) the linked attachments + * @return array of linked attachments or undefined if none present + */ + private getCredentialLinkedAttachments( + attributes?: CredentialPreviewAttributeOptions[], + linkedAttachments?: LinkedAttachment[] + ): { + attachments?: V1Attachment[] + previewAttributes?: CredentialPreviewAttributeOptions[] + } { + if (!linkedAttachments && !attributes) { + return {} + } + + let previewAttributes = attributes ?? [] + let attachments: V1Attachment[] | undefined + + if (linkedAttachments) { + // there are linked attachments so transform into the attribute field of the CredentialPreview object for + // this proposal + previewAttributes = createAndLinkAttachmentsToPreview(linkedAttachments, previewAttributes) + attachments = linkedAttachments.map((linkedAttachment) => linkedAttachment.attachment) + } + + return { attachments, previewAttributes } + } + + /** + * Returns an object of type {@link Attachment} for use in credential exchange messages. + * It looks up the correct format identifier and encodes the data as a base64 attachment. + * + * @param data The data to include in the attach object + * @param id the attach id from the formats component of the message + */ + public getFormatData(data: unknown, id: string): V1Attachment { + const attachment = new V1Attachment({ + id, + mimeType: 'application/json', + data: new V1AttachmentData({ + base64: JsonEncoder.toBase64(data), + }), + }) + + return attachment + } +} diff --git a/packages/anoncreds/src/formats/LegacyIndyProofFormat.ts b/packages/anoncreds/src/formats/LegacyIndyProofFormat.ts new file mode 100644 index 0000000000..a586e77b10 --- /dev/null +++ b/packages/anoncreds/src/formats/LegacyIndyProofFormat.ts @@ -0,0 +1,40 @@ +import type { + AnonCredsProposeProofFormat, + AnonCredsRequestProofFormat, + AnonCredsGetCredentialsForProofRequestOptions, + AnonCredsCredentialsForProofRequest, +} from './AnonCredsProofFormat' +import type { AnonCredsProof, AnonCredsProofRequest, AnonCredsSelectedCredentials } from '../models' +import type { ProofFormat } from '@aries-framework/core' + +// TODO: Custom restrictions to remove `_id` from restrictions? +export type LegacyIndyProofRequest = AnonCredsProofRequest + +export interface LegacyIndyProofFormat extends ProofFormat { + formatKey: 'indy' + + proofFormats: { + createProposal: AnonCredsProposeProofFormat + acceptProposal: { + name?: string + version?: string + } + createRequest: AnonCredsRequestProofFormat + acceptRequest: AnonCredsSelectedCredentials + + getCredentialsForRequest: { + input: AnonCredsGetCredentialsForProofRequestOptions + output: AnonCredsCredentialsForProofRequest + } + selectCredentialsForRequest: { + input: AnonCredsGetCredentialsForProofRequestOptions + output: AnonCredsSelectedCredentials + } + } + + formatData: { + proposal: LegacyIndyProofRequest + request: LegacyIndyProofRequest + presentation: AnonCredsProof + } +} diff --git a/packages/anoncreds/src/formats/LegacyIndyProofFormatService.ts b/packages/anoncreds/src/formats/LegacyIndyProofFormatService.ts new file mode 100644 index 0000000000..1890acc59e --- /dev/null +++ b/packages/anoncreds/src/formats/LegacyIndyProofFormatService.ts @@ -0,0 +1,640 @@ +import type { + AnonCredsCredentialsForProofRequest, + AnonCredsGetCredentialsForProofRequestOptions, +} from './AnonCredsProofFormat' +import type { LegacyIndyProofFormat } from './LegacyIndyProofFormat' +import type { + AnonCredsCredentialDefinition, + AnonCredsCredentialInfo, + AnonCredsProof, + AnonCredsRequestedAttribute, + AnonCredsRequestedPredicate, + AnonCredsSchema, + AnonCredsSelectedCredentials, + AnonCredsProofRequest, +} from '../models' +import type { AnonCredsHolderService, AnonCredsVerifierService, GetCredentialsForProofRequestReturn } from '../services' +import type { + ProofFormatService, + AgentContext, + ProofFormatCreateReturn, + FormatCreateRequestOptions, + ProofFormatCreateProposalOptions, + ProofFormatProcessOptions, + ProofFormatAcceptProposalOptions, + ProofFormatAcceptRequestOptions, + ProofFormatProcessPresentationOptions, + ProofFormatGetCredentialsForRequestOptions, + ProofFormatGetCredentialsForRequestReturn, + ProofFormatSelectCredentialsForRequestOptions, + ProofFormatSelectCredentialsForRequestReturn, + ProofFormatAutoRespondProposalOptions, + ProofFormatAutoRespondRequestOptions, + ProofFormatAutoRespondPresentationOptions, +} from '@aries-framework/core' + +import { + AriesFrameworkError, + V1Attachment, + V1AttachmentData, + JsonEncoder, + ProofFormatSpec, + JsonTransformer, +} from '@aries-framework/core' + +import { AnonCredsProofRequest as AnonCredsProofRequestClass } from '../models/AnonCredsProofRequest' +import { AnonCredsVerifierServiceSymbol, AnonCredsHolderServiceSymbol } from '../services' +import { AnonCredsRegistryService } from '../services/registry/AnonCredsRegistryService' +import { + sortRequestedCredentialsMatches, + createRequestFromPreview, + areAnonCredsProofRequestsEqual, + assertBestPracticeRevocationInterval, + checkValidCredentialValueEncoding, + encodeCredentialValue, + assertNoDuplicateGroupsNamesInProofRequest, + getRevocationRegistriesForRequest, + getRevocationRegistriesForProof, +} from '../utils' +import { isUnqualifiedCredentialDefinitionId, isUnqualifiedSchemaId } from '../utils/indyIdentifiers' + +const V2_INDY_PRESENTATION_PROPOSAL = 'hlindy/proof-req@v2.0' +const V2_INDY_PRESENTATION_REQUEST = 'hlindy/proof-req@v2.0' +const V2_INDY_PRESENTATION = 'hlindy/proof@v2.0' + +export class LegacyIndyProofFormatService implements ProofFormatService { + public readonly formatKey = 'indy' as const + + public async createProposal( + agentContext: AgentContext, + { attachmentId, proofFormats }: ProofFormatCreateProposalOptions + ): Promise { + const format = new ProofFormatSpec({ + format: V2_INDY_PRESENTATION_PROPOSAL, + attachmentId, + }) + + const indyFormat = proofFormats.indy + if (!indyFormat) { + throw Error('Missing indy format to create proposal attachment format') + } + + const proofRequest = createRequestFromPreview({ + attributes: indyFormat.attributes ?? [], + predicates: indyFormat.predicates ?? [], + name: indyFormat.name ?? 'Proof request', + version: indyFormat.version ?? '1.0', + nonce: await agentContext.wallet.generateNonce(), + }) + const attachment = this.getFormatData(proofRequest, format.attachmentId) + + return { attachment, format } + } + + public async processProposal(agentContext: AgentContext, { attachment }: ProofFormatProcessOptions): Promise { + const proposalJson = attachment.getDataAsJson() + + // fromJson also validates + JsonTransformer.fromJSON(proposalJson, AnonCredsProofRequestClass) + + // Assert attribute and predicate (group) names do not match + assertNoDuplicateGroupsNamesInProofRequest(proposalJson) + } + + public async acceptProposal( + agentContext: AgentContext, + { proposalAttachment, attachmentId }: ProofFormatAcceptProposalOptions + ): Promise { + const format = new ProofFormatSpec({ + format: V2_INDY_PRESENTATION_REQUEST, + attachmentId, + }) + + const proposalJson = proposalAttachment.getDataAsJson() + + const request = { + ...proposalJson, + // We never want to reuse the nonce from the proposal, as this will allow replay attacks + nonce: await agentContext.wallet.generateNonce(), + } + + const attachment = this.getFormatData(request, format.attachmentId) + + return { attachment, format } + } + + public async createRequest( + agentContext: AgentContext, + { attachmentId, proofFormats }: FormatCreateRequestOptions + ): Promise { + const format = new ProofFormatSpec({ + format: V2_INDY_PRESENTATION_REQUEST, + attachmentId, + }) + + const indyFormat = proofFormats.indy + if (!indyFormat) { + throw Error('Missing indy format in create request attachment format') + } + + const request = { + name: indyFormat.name, + version: indyFormat.version, + nonce: await agentContext.wallet.generateNonce(), + requested_attributes: indyFormat.requested_attributes ?? {}, + requested_predicates: indyFormat.requested_predicates ?? {}, + non_revoked: indyFormat.non_revoked, + } + + // Assert attribute and predicate (group) names do not match + assertNoDuplicateGroupsNamesInProofRequest(request) + + const attachment = this.getFormatData(request, format.attachmentId) + + return { attachment, format } + } + + public async processRequest(agentContext: AgentContext, { attachment }: ProofFormatProcessOptions): Promise { + const requestJson = attachment.getDataAsJson() + + // fromJson also validates + JsonTransformer.fromJSON(requestJson, AnonCredsProofRequestClass) + + // Assert attribute and predicate (group) names do not match + assertNoDuplicateGroupsNamesInProofRequest(requestJson) + } + + public async acceptRequest( + agentContext: AgentContext, + { proofFormats, requestAttachment, attachmentId }: ProofFormatAcceptRequestOptions + ): Promise { + const format = new ProofFormatSpec({ + format: V2_INDY_PRESENTATION, + attachmentId, + }) + const requestJson = requestAttachment.getDataAsJson() + + const indyFormat = proofFormats?.indy + + const selectedCredentials = + indyFormat ?? + (await this._selectCredentialsForRequest(agentContext, requestJson, { + filterByNonRevocationRequirements: true, + })) + + const proof = await this.createProof(agentContext, requestJson, selectedCredentials) + const attachment = this.getFormatData(proof, format.attachmentId) + + return { + attachment, + format, + } + } + + public async processPresentation( + agentContext: AgentContext, + { requestAttachment, attachment }: ProofFormatProcessPresentationOptions + ): Promise { + const verifierService = + agentContext.dependencyManager.resolve(AnonCredsVerifierServiceSymbol) + + const proofRequestJson = requestAttachment.getDataAsJson() + + // NOTE: we don't do validation here, as this is handled by the AnonCreds implementation, however + // this can lead to confusing error messages. We should consider doing validation here as well. + // Defining a class-transformer/class-validator class seems a bit overkill, and the usage of interfaces + // for the anoncreds package keeps things simple. Maybe we can try to use something like zod to validate + const proofJson = attachment.getDataAsJson() + + for (const [referent, attribute] of Object.entries(proofJson.requested_proof.revealed_attrs)) { + if (!checkValidCredentialValueEncoding(attribute.raw, attribute.encoded)) { + throw new AriesFrameworkError( + `The encoded value for '${referent}' is invalid. ` + + `Expected '${encodeCredentialValue(attribute.raw)}'. ` + + `Actual '${attribute.encoded}'` + ) + } + } + + for (const [, attributeGroup] of Object.entries(proofJson.requested_proof.revealed_attr_groups ?? {})) { + for (const [attributeName, attribute] of Object.entries(attributeGroup.values)) { + if (!checkValidCredentialValueEncoding(attribute.raw, attribute.encoded)) { + throw new AriesFrameworkError( + `The encoded value for '${attributeName}' is invalid. ` + + `Expected '${encodeCredentialValue(attribute.raw)}'. ` + + `Actual '${attribute.encoded}'` + ) + } + } + } + + // TODO: pre verify proof json + // I'm not 100% sure how much indy does. Also if it checks whether the proof requests matches the proof + // @see https://github.com/hyperledger/aries-cloudagent-python/blob/master/aries_cloudagent/indy/sdk/verifier.py#L79-L164 + + const schemas = await this.getSchemas(agentContext, new Set(proofJson.identifiers.map((i) => i.schema_id))) + const credentialDefinitions = await this.getCredentialDefinitions( + agentContext, + new Set(proofJson.identifiers.map((i) => i.cred_def_id)) + ) + + const revocationRegistries = await getRevocationRegistriesForProof(agentContext, proofJson) + + return await verifierService.verifyProof(agentContext, { + proofRequest: proofRequestJson, + proof: proofJson, + schemas, + credentialDefinitions, + revocationRegistries, + }) + } + + public async getCredentialsForRequest( + agentContext: AgentContext, + { requestAttachment, proofFormats }: ProofFormatGetCredentialsForRequestOptions + ): Promise> { + const proofRequestJson = requestAttachment.getDataAsJson() + + // Set default values + const { filterByNonRevocationRequirements = true } = proofFormats?.indy ?? {} + + const credentialsForRequest = await this._getCredentialsForRequest(agentContext, proofRequestJson, { + filterByNonRevocationRequirements, + }) + + return credentialsForRequest + } + + public async selectCredentialsForRequest( + agentContext: AgentContext, + { requestAttachment, proofFormats }: ProofFormatSelectCredentialsForRequestOptions + ): Promise> { + const proofRequestJson = requestAttachment.getDataAsJson() + + // Set default values + const { filterByNonRevocationRequirements = true } = proofFormats?.indy ?? {} + + const selectedCredentials = this._selectCredentialsForRequest(agentContext, proofRequestJson, { + filterByNonRevocationRequirements, + }) + + return selectedCredentials + } + + public async shouldAutoRespondToProposal( + agentContext: AgentContext, + { proposalAttachment, requestAttachment }: ProofFormatAutoRespondProposalOptions + ): Promise { + const proposalJson = proposalAttachment.getDataAsJson() + const requestJson = requestAttachment.getDataAsJson() + + const areRequestsEqual = areAnonCredsProofRequestsEqual(proposalJson, requestJson) + agentContext.config.logger.debug(`AnonCreds request and proposal are are equal: ${areRequestsEqual}`, { + proposalJson, + requestJson, + }) + + return areRequestsEqual + } + + public async shouldAutoRespondToRequest( + agentContext: AgentContext, + { proposalAttachment, requestAttachment }: ProofFormatAutoRespondRequestOptions + ): Promise { + const proposalJson = proposalAttachment.getDataAsJson() + const requestJson = requestAttachment.getDataAsJson() + + return areAnonCredsProofRequestsEqual(proposalJson, requestJson) + } + + public async shouldAutoRespondToPresentation( + // eslint-disable-next-line @typescript-eslint/no-unused-vars + _agentContext: AgentContext, + // eslint-disable-next-line @typescript-eslint/no-unused-vars + _options: ProofFormatAutoRespondPresentationOptions + ): Promise { + // The presentation is already verified in processPresentation, so we can just return true here. + // It's only an ack, so it's just that we received the presentation. + return true + } + + public supportsFormat(formatIdentifier: string): boolean { + const supportedFormats = [V2_INDY_PRESENTATION_PROPOSAL, V2_INDY_PRESENTATION_REQUEST, V2_INDY_PRESENTATION] + return supportedFormats.includes(formatIdentifier) + } + + private async _getCredentialsForRequest( + agentContext: AgentContext, + proofRequest: AnonCredsProofRequest, + options: AnonCredsGetCredentialsForProofRequestOptions + ): Promise { + const credentialsForProofRequest: AnonCredsCredentialsForProofRequest = { + attributes: {}, + predicates: {}, + } + + for (const [referent, requestedAttribute] of Object.entries(proofRequest.requested_attributes)) { + const credentials = await this.getCredentialsForProofRequestReferent(agentContext, proofRequest, referent) + + credentialsForProofRequest.attributes[referent] = sortRequestedCredentialsMatches( + await Promise.all( + credentials.map(async (credential) => { + const { isRevoked, timestamp } = await this.getRevocationStatus( + agentContext, + proofRequest, + requestedAttribute, + credential.credentialInfo + ) + + return { + credentialId: credential.credentialInfo.credentialId, + revealed: true, + credentialInfo: credential.credentialInfo, + timestamp, + revoked: isRevoked, + } + }) + ) + ) + + // We only attach revoked state if non-revocation is requested. So if revoked is true it means + // the credential is not applicable to the proof request + if (options.filterByNonRevocationRequirements) { + credentialsForProofRequest.attributes[referent] = credentialsForProofRequest.attributes[referent].filter( + (r) => !r.revoked + ) + } + } + + for (const [referent, requestedPredicate] of Object.entries(proofRequest.requested_predicates)) { + const credentials = await this.getCredentialsForProofRequestReferent(agentContext, proofRequest, referent) + + credentialsForProofRequest.predicates[referent] = sortRequestedCredentialsMatches( + await Promise.all( + credentials.map(async (credential) => { + const { isRevoked, timestamp } = await this.getRevocationStatus( + agentContext, + proofRequest, + requestedPredicate, + credential.credentialInfo + ) + + return { + credentialId: credential.credentialInfo.credentialId, + credentialInfo: credential.credentialInfo, + timestamp, + revoked: isRevoked, + } + }) + ) + ) + + // We only attach revoked state if non-revocation is requested. So if revoked is true it means + // the credential is not applicable to the proof request + if (options.filterByNonRevocationRequirements) { + credentialsForProofRequest.predicates[referent] = credentialsForProofRequest.predicates[referent].filter( + (r) => !r.revoked + ) + } + } + + return credentialsForProofRequest + } + + private async _selectCredentialsForRequest( + agentContext: AgentContext, + proofRequest: AnonCredsProofRequest, + options: AnonCredsGetCredentialsForProofRequestOptions + ): Promise { + const credentialsForRequest = await this._getCredentialsForRequest(agentContext, proofRequest, options) + + const selectedCredentials: AnonCredsSelectedCredentials = { + attributes: {}, + predicates: {}, + selfAttestedAttributes: {}, + } + + Object.keys(credentialsForRequest.attributes).forEach((attributeName) => { + const attributeArray = credentialsForRequest.attributes[attributeName] + + if (attributeArray.length === 0) { + throw new AriesFrameworkError('Unable to automatically select requested attributes.') + } + + selectedCredentials.attributes[attributeName] = attributeArray[0] + }) + + Object.keys(credentialsForRequest.predicates).forEach((attributeName) => { + if (credentialsForRequest.predicates[attributeName].length === 0) { + throw new AriesFrameworkError('Unable to automatically select requested predicates.') + } else { + selectedCredentials.predicates[attributeName] = credentialsForRequest.predicates[attributeName][0] + } + }) + + return selectedCredentials + } + + private async getCredentialsForProofRequestReferent( + agentContext: AgentContext, + proofRequest: AnonCredsProofRequest, + attributeReferent: string + ): Promise { + const holderService = agentContext.dependencyManager.resolve(AnonCredsHolderServiceSymbol) + + const credentials = await holderService.getCredentialsForProofRequest(agentContext, { + proofRequest, + attributeReferent, + }) + + return credentials + } + + /** + * Build schemas object needed to create and verify proof objects. + * + * Creates object with `{ schemaId: AnonCredsSchema }` mapping + * + * @param schemaIds List of schema ids + * @returns Object containing schemas for specified schema ids + * + */ + private async getSchemas(agentContext: AgentContext, schemaIds: Set) { + const registryService = agentContext.dependencyManager.resolve(AnonCredsRegistryService) + + const schemas: { [key: string]: AnonCredsSchema } = {} + + for (const schemaId of schemaIds) { + if (!isUnqualifiedSchemaId(schemaId)) { + throw new AriesFrameworkError(`${schemaId} is not a valid legacy indy schema id`) + } + + const schemaRegistry = registryService.getRegistryForIdentifier(agentContext, schemaId) + const schemaResult = await schemaRegistry.getSchema(agentContext, schemaId) + + if (!schemaResult.schema) { + throw new AriesFrameworkError(`Schema not found for id ${schemaId}: ${schemaResult.resolutionMetadata.message}`) + } + + schemas[schemaId] = schemaResult.schema + } + + return schemas + } + + /** + * Build credential definitions object needed to create and verify proof objects. + * + * Creates object with `{ credentialDefinitionId: AnonCredsCredentialDefinition }` mapping + * + * @param credentialDefinitionIds List of credential definition ids + * @returns Object containing credential definitions for specified credential definition ids + * + */ + private async getCredentialDefinitions(agentContext: AgentContext, credentialDefinitionIds: Set) { + const registryService = agentContext.dependencyManager.resolve(AnonCredsRegistryService) + + const credentialDefinitions: { [key: string]: AnonCredsCredentialDefinition } = {} + + for (const credentialDefinitionId of credentialDefinitionIds) { + if (!isUnqualifiedCredentialDefinitionId(credentialDefinitionId)) { + throw new AriesFrameworkError(`${credentialDefinitionId} is not a valid legacy indy credential definition id`) + } + + const credentialDefinitionRegistry = registryService.getRegistryForIdentifier( + agentContext, + credentialDefinitionId + ) + + const credentialDefinitionResult = await credentialDefinitionRegistry.getCredentialDefinition( + agentContext, + credentialDefinitionId + ) + + if (!credentialDefinitionResult.credentialDefinition) { + throw new AriesFrameworkError( + `Credential definition not found for id ${credentialDefinitionId}: ${credentialDefinitionResult.resolutionMetadata.message}` + ) + } + + credentialDefinitions[credentialDefinitionId] = credentialDefinitionResult.credentialDefinition + } + + return credentialDefinitions + } + + private async getRevocationStatus( + agentContext: AgentContext, + proofRequest: AnonCredsProofRequest, + requestedItem: AnonCredsRequestedAttribute | AnonCredsRequestedPredicate, + credentialInfo: AnonCredsCredentialInfo + ) { + const requestNonRevoked = requestedItem.non_revoked ?? proofRequest.non_revoked + const credentialRevocationId = credentialInfo.credentialRevocationId + const revocationRegistryId = credentialInfo.revocationRegistryId + + // If revocation interval is not present or the credential is not revocable then we + // don't need to fetch the revocation status + if (!requestNonRevoked || !credentialRevocationId || !revocationRegistryId) { + return { isRevoked: undefined, timestamp: undefined } + } + + agentContext.config.logger.trace( + `Fetching credential revocation status for credential revocation id '${credentialRevocationId}' with revocation interval with from '${requestNonRevoked.from}' and to '${requestNonRevoked.to}'` + ) + + // Make sure the revocation interval follows best practices from Aries RFC 0441 + assertBestPracticeRevocationInterval(requestNonRevoked) + + const registryService = agentContext.dependencyManager.resolve(AnonCredsRegistryService) + const registry = registryService.getRegistryForIdentifier(agentContext, revocationRegistryId) + + const revocationStatusResult = await registry.getRevocationStatusList( + agentContext, + revocationRegistryId, + requestNonRevoked.to ?? Date.now() + ) + + if (!revocationStatusResult.revocationStatusList) { + throw new AriesFrameworkError( + `Could not retrieve revocation status list for revocation registry ${revocationRegistryId}: ${revocationStatusResult.resolutionMetadata.message}` + ) + } + + // Item is revoked when the value at the index is 1 + const isRevoked = revocationStatusResult.revocationStatusList.revocationList[parseInt(credentialRevocationId)] === 1 + + agentContext.config.logger.trace( + `Credential with credential revocation index '${credentialRevocationId}' is ${ + isRevoked ? '' : 'not ' + }revoked with revocation interval with to '${requestNonRevoked.to}' & from '${requestNonRevoked.from}'` + ) + + return { + isRevoked, + timestamp: revocationStatusResult.revocationStatusList.timestamp, + } + } + + /** + * Create indy proof from a given proof request and requested credential object. + * + * @param proofRequest The proof request to create the proof for + * @param requestedCredentials The requested credentials object specifying which credentials to use for the proof + * @returns indy proof object + */ + private async createProof( + agentContext: AgentContext, + proofRequest: AnonCredsProofRequest, + selectedCredentials: AnonCredsSelectedCredentials + ): Promise { + const holderService = agentContext.dependencyManager.resolve(AnonCredsHolderServiceSymbol) + + const credentialObjects = await Promise.all( + [...Object.values(selectedCredentials.attributes), ...Object.values(selectedCredentials.predicates)].map( + async (c) => c.credentialInfo ?? holderService.getCredential(agentContext, { credentialId: c.credentialId }) + ) + ) + + const schemas = await this.getSchemas(agentContext, new Set(credentialObjects.map((c) => c.schemaId))) + const credentialDefinitions = await this.getCredentialDefinitions( + agentContext, + new Set(credentialObjects.map((c) => c.credentialDefinitionId)) + ) + + // selectedCredentials are overridden with specified timestamps of the revocation status list that + // should be used for the selected credentials. + const { revocationRegistries, updatedSelectedCredentials } = await getRevocationRegistriesForRequest( + agentContext, + proofRequest, + selectedCredentials + ) + + return await holderService.createProof(agentContext, { + proofRequest, + selectedCredentials: updatedSelectedCredentials, + schemas, + credentialDefinitions, + revocationRegistries, + }) + } + + /** + * Returns an object of type {@link Attachment} for use in credential exchange messages. + * It looks up the correct format identifier and encodes the data as a base64 attachment. + * + * @param data The data to include in the attach object + * @param id the attach id from the formats component of the message + */ + private getFormatData(data: unknown, id: string): V1Attachment { + const attachment = new V1Attachment({ + id, + mimeType: 'application/json', + data: new V1AttachmentData({ + base64: JsonEncoder.toBase64(data), + }), + }) + + return attachment + } +} diff --git a/packages/anoncreds/src/formats/__tests__/legacy-indy-format-services.test.ts b/packages/anoncreds/src/formats/__tests__/legacy-indy-format-services.test.ts new file mode 100644 index 0000000000..ebda7bf2ca --- /dev/null +++ b/packages/anoncreds/src/formats/__tests__/legacy-indy-format-services.test.ts @@ -0,0 +1,337 @@ +import type { AnonCredsCredentialRequest } from '../../models' + +import { + CredentialState, + CredentialExchangeRecord, + KeyProviderRegistry, + KeyType, + CredentialPreviewAttribute, + ProofExchangeRecord, + ProofState, + EventEmitter, +} from '@aries-framework/core' +import * as indySdk from 'indy-sdk' +import { Subject } from 'rxjs' + +import { agentDependencies, getAgentConfig, getAgentContext } from '../../../../core/tests/helpers' +import { + IndySdkHolderService, + IndySdkIssuerService, + IndySdkStorageService, + IndySdkVerifierService, + IndySdkWallet, +} from '../../../../indy-sdk/src' +import { IndySdkRevocationService } from '../../../../indy-sdk/src/anoncreds/services/IndySdkRevocationService' +import { legacyIndyDidFromPublicKeyBase58 } from '../../../../indy-sdk/src/utils/did' +import { InMemoryAnonCredsRegistry } from '../../../tests/InMemoryAnonCredsRegistry' +import { AnonCredsModuleConfig } from '../../AnonCredsModuleConfig' +import { AnonCredsLinkSecretRecord, AnonCredsLinkSecretRepository } from '../../repository' +import { + AnonCredsHolderServiceSymbol, + AnonCredsIssuerServiceSymbol, + AnonCredsVerifierServiceSymbol, +} from '../../services' +import { AnonCredsRegistryService } from '../../services/registry/AnonCredsRegistryService' +import { + getUnqualifiedCredentialDefinitionId, + getUnqualifiedSchemaId, + parseIndyCredentialDefinitionId, + parseIndySchemaId, +} from '../../utils/indyIdentifiers' +import { LegacyIndyCredentialFormatService } from '../LegacyIndyCredentialFormatService' +import { LegacyIndyProofFormatService } from '../LegacyIndyProofFormatService' + +const registry = new InMemoryAnonCredsRegistry() +const anonCredsModuleConfig = new AnonCredsModuleConfig({ + registries: [registry], +}) + +const agentConfig = getAgentConfig('LegacyIndyFormatServicesTest') +const anonCredsRevocationService = new IndySdkRevocationService(indySdk) +const anonCredsVerifierService = new IndySdkVerifierService(indySdk) +const anonCredsHolderService = new IndySdkHolderService(anonCredsRevocationService, indySdk) +const anonCredsIssuerService = new IndySdkIssuerService(indySdk) +const wallet = new IndySdkWallet(indySdk, agentConfig.logger, new KeyProviderRegistry([])) +const storageService = new IndySdkStorageService(indySdk) +const eventEmitter = new EventEmitter(agentDependencies, new Subject()) +const anonCredsLinkSecretRepository = new AnonCredsLinkSecretRepository(storageService, eventEmitter) +const agentContext = getAgentContext({ + registerInstances: [ + [AnonCredsIssuerServiceSymbol, anonCredsIssuerService], + [AnonCredsHolderServiceSymbol, anonCredsHolderService], + [AnonCredsVerifierServiceSymbol, anonCredsVerifierService], + [AnonCredsRegistryService, new AnonCredsRegistryService()], + [AnonCredsModuleConfig, anonCredsModuleConfig], + [AnonCredsLinkSecretRepository, anonCredsLinkSecretRepository], + ], + agentConfig, + wallet, +}) + +const indyCredentialFormatService = new LegacyIndyCredentialFormatService() +const indyProofFormatService = new LegacyIndyProofFormatService() + +// We can split up these tests when we can use AnonCredsRS as a backend, but currently +// we need to have the link secrets etc in the wallet which is not so easy to do with Indy +describe('Legacy indy format services', () => { + beforeEach(async () => { + await wallet.createAndOpen(agentConfig.walletConfig) + }) + + afterEach(async () => { + await wallet.delete() + }) + + test('issuance and verification flow starting from proposal without negotiation and without revocation', async () => { + // This is just so we don't have to register an actual indy did (as we don't have the indy did registrar configured) + const key = await wallet.createKey({ keyType: KeyType.Ed25519 }) + const unqualifiedIndyDid = legacyIndyDidFromPublicKeyBase58(key.publicKeyBase58) + const indyDid = `did:indy:pool1:${unqualifiedIndyDid}` + + // Create link secret + await anonCredsHolderService.createLinkSecret(agentContext, { + linkSecretId: 'link-secret-id', + }) + const anonCredsLinkSecret = new AnonCredsLinkSecretRecord({ + linkSecretId: 'link-secret-id', + }) + anonCredsLinkSecret.setTag('isDefault', true) + await anonCredsLinkSecretRepository.save(agentContext, anonCredsLinkSecret) + + const schema = await anonCredsIssuerService.createSchema(agentContext, { + attrNames: ['name', 'age'], + issuerId: indyDid, + name: 'Employee Credential', + version: '1.0.0', + }) + + const { schemaState, schemaMetadata } = await registry.registerSchema(agentContext, { + schema, + options: {}, + }) + + const { credentialDefinition } = await anonCredsIssuerService.createCredentialDefinition( + agentContext, + { + issuerId: indyDid, + schemaId: schemaState.schemaId as string, + schema, + tag: 'Employee Credential', + supportRevocation: false, + }, + { + // Need to pass this as the indy-sdk MUST have the seqNo + indyLedgerSchemaSeqNo: schemaMetadata.indyLedgerSeqNo as number, + } + ) + + const { credentialDefinitionState } = await registry.registerCredentialDefinition(agentContext, { + credentialDefinition, + options: {}, + }) + + if ( + !credentialDefinitionState.credentialDefinition || + !credentialDefinitionState.credentialDefinitionId || + !schemaState.schema || + !schemaState.schemaId + ) { + throw new Error('Failed to create schema or credential definition') + } + + const holderCredentialRecord = new CredentialExchangeRecord({ + protocolVersion: 'v1', + state: CredentialState.ProposalSent, + threadId: 'f365c1a5-2baf-4873-9432-fa87c888a0aa', + }) + + const issuerCredentialRecord = new CredentialExchangeRecord({ + protocolVersion: 'v1', + state: CredentialState.ProposalReceived, + threadId: 'f365c1a5-2baf-4873-9432-fa87c888a0aa', + }) + + const credentialAttributes = [ + new CredentialPreviewAttribute({ + name: 'name', + value: 'John', + }), + new CredentialPreviewAttribute({ + name: 'age', + value: '25', + }), + ] + + const cd = parseIndyCredentialDefinitionId(credentialDefinitionState.credentialDefinitionId) + const legacyCredentialDefinitionId = getUnqualifiedCredentialDefinitionId( + cd.namespaceIdentifier, + cd.schemaSeqNo, + cd.tag + ) + + const s = parseIndySchemaId(schemaState.schemaId) + const legacySchemaId = getUnqualifiedSchemaId(s.namespaceIdentifier, s.schemaName, s.schemaVersion) + + // Holder creates proposal + holderCredentialRecord.credentialAttributes = credentialAttributes + const { attachment: proposalAttachment } = await indyCredentialFormatService.createProposal(agentContext, { + credentialRecord: holderCredentialRecord, + credentialFormats: { + indy: { + attributes: credentialAttributes, + credentialDefinitionId: legacyCredentialDefinitionId, + }, + }, + }) + + // Issuer processes and accepts proposal + await indyCredentialFormatService.processProposal(agentContext, { + credentialRecord: issuerCredentialRecord, + attachment: proposalAttachment, + }) + // Set attributes on the credential record, this is normally done by the protocol service + issuerCredentialRecord.credentialAttributes = credentialAttributes + const { attachment: offerAttachment } = await indyCredentialFormatService.acceptProposal(agentContext, { + credentialRecord: issuerCredentialRecord, + proposalAttachment: proposalAttachment, + }) + + // Holder processes and accepts offer + await indyCredentialFormatService.processOffer(agentContext, { + credentialRecord: holderCredentialRecord, + attachment: offerAttachment, + }) + const { attachment: requestAttachment } = await indyCredentialFormatService.acceptOffer(agentContext, { + credentialRecord: holderCredentialRecord, + offerAttachment, + }) + + // Make sure the request contains a prover_did field + expect((requestAttachment.getDataAsJson() as AnonCredsCredentialRequest).prover_did).toBeDefined() + + // Issuer processes and accepts request + await indyCredentialFormatService.processRequest(agentContext, { + credentialRecord: issuerCredentialRecord, + attachment: requestAttachment, + }) + const { attachment: credentialAttachment } = await indyCredentialFormatService.acceptRequest(agentContext, { + credentialRecord: issuerCredentialRecord, + requestAttachment, + offerAttachment, + }) + + // Holder processes and accepts credential + await indyCredentialFormatService.processCredential(agentContext, { + credentialRecord: holderCredentialRecord, + attachment: credentialAttachment, + requestAttachment, + }) + + expect(holderCredentialRecord.credentials).toEqual([ + { credentialRecordType: 'anoncreds', credentialRecordId: expect.any(String) }, + ]) + + const credentialId = holderCredentialRecord.credentials[0].credentialRecordId + const anonCredsCredential = await anonCredsHolderService.getCredential(agentContext, { + credentialId, + }) + + expect(anonCredsCredential).toEqual({ + credentialId, + attributes: { + age: '25', + name: 'John', + }, + schemaId: legacySchemaId, + credentialDefinitionId: legacyCredentialDefinitionId, + revocationRegistryId: null, + credentialRevocationId: null, + methodName: 'indy', + }) + + expect(holderCredentialRecord.metadata.data).toEqual({ + '_anoncreds/credential': { + schemaId: legacySchemaId, + credentialDefinitionId: legacyCredentialDefinitionId, + }, + '_anoncreds/credentialRequest': { + link_secret_blinding_data: expect.any(Object), + link_secret_name: expect.any(String), + nonce: expect.any(String), + }, + }) + + expect(issuerCredentialRecord.metadata.data).toEqual({ + '_anoncreds/credential': { + schemaId: legacySchemaId, + credentialDefinitionId: legacyCredentialDefinitionId, + }, + }) + + const holderProofRecord = new ProofExchangeRecord({ + protocolVersion: 'v1', + state: ProofState.ProposalSent, + threadId: '4f5659a4-1aea-4f42-8c22-9a9985b35e38', + }) + const verifierProofRecord = new ProofExchangeRecord({ + protocolVersion: 'v1', + state: ProofState.ProposalReceived, + threadId: '4f5659a4-1aea-4f42-8c22-9a9985b35e38', + }) + + const { attachment: proofProposalAttachment } = await indyProofFormatService.createProposal(agentContext, { + proofFormats: { + indy: { + attributes: [ + { + name: 'name', + credentialDefinitionId: legacyCredentialDefinitionId, + value: 'John', + referent: '1', + }, + ], + predicates: [ + { + credentialDefinitionId: legacyCredentialDefinitionId, + name: 'age', + predicate: '>=', + threshold: 18, + }, + ], + name: 'Proof Request', + version: '1.0', + }, + }, + proofRecord: holderProofRecord, + }) + + await indyProofFormatService.processProposal(agentContext, { + attachment: proofProposalAttachment, + proofRecord: verifierProofRecord, + }) + + const { attachment: proofRequestAttachment } = await indyProofFormatService.acceptProposal(agentContext, { + proofRecord: verifierProofRecord, + proposalAttachment: proofProposalAttachment, + }) + + await indyProofFormatService.processRequest(agentContext, { + attachment: proofRequestAttachment, + proofRecord: holderProofRecord, + }) + + const { attachment: proofAttachment } = await indyProofFormatService.acceptRequest(agentContext, { + proofRecord: holderProofRecord, + requestAttachment: proofRequestAttachment, + proposalAttachment: proofProposalAttachment, + }) + + const isValid = await indyProofFormatService.processPresentation(agentContext, { + attachment: proofAttachment, + proofRecord: verifierProofRecord, + requestAttachment: proofRequestAttachment, + }) + + expect(isValid).toBe(true) + }) +}) diff --git a/packages/anoncreds/src/formats/index.ts b/packages/anoncreds/src/formats/index.ts new file mode 100644 index 0000000000..07f76522ba --- /dev/null +++ b/packages/anoncreds/src/formats/index.ts @@ -0,0 +1,9 @@ +export * from './AnonCredsCredentialFormat' +export * from './LegacyIndyCredentialFormat' +export { AnonCredsCredentialFormatService } from './AnonCredsCredentialFormatService' +export { LegacyIndyCredentialFormatService } from './LegacyIndyCredentialFormatService' + +export * from './AnonCredsProofFormat' +export * from './LegacyIndyProofFormat' +export { AnonCredsProofFormatService } from './AnonCredsProofFormatService' +export { LegacyIndyProofFormatService } from './LegacyIndyProofFormatService' diff --git a/packages/anoncreds/src/index.ts b/packages/anoncreds/src/index.ts new file mode 100644 index 0000000000..edc9883578 --- /dev/null +++ b/packages/anoncreds/src/index.ts @@ -0,0 +1,16 @@ +import 'reflect-metadata' + +export * from './models' +export * from './services' +export * from './error' +export * from './repository' +export * from './formats' +export * from './protocols' + +export { AnonCredsModule } from './AnonCredsModule' +export { AnonCredsModuleConfig, AnonCredsModuleConfigOptions } from './AnonCredsModuleConfig' +export { AnonCredsApi } from './AnonCredsApi' +export * from './AnonCredsApiOptions' +export { generateLegacyProverDidLikeString } from './utils/proverDid' +export * from './utils/indyIdentifiers' +export { assertBestPracticeRevocationInterval } from './utils/revocationInterval' diff --git a/packages/core/src/modules/credentials/formats/indy/models/IndyCredPropose.ts b/packages/anoncreds/src/models/AnonCredsCredentialProposal.ts similarity index 58% rename from packages/core/src/modules/credentials/formats/indy/models/IndyCredPropose.ts rename to packages/anoncreds/src/models/AnonCredsCredentialProposal.ts index 3ddfff7542..928c26b5d5 100644 --- a/packages/core/src/modules/credentials/formats/indy/models/IndyCredPropose.ts +++ b/packages/anoncreds/src/models/AnonCredsCredentialProposal.ts @@ -1,44 +1,62 @@ import { Expose } from 'class-transformer' import { IsOptional, IsString } from 'class-validator' -export interface IndyCredProposeOptions { +export interface AnonCredsCredentialProposalOptions { + /** + * @deprecated Use `schemaIssuerId` instead. Only valid for legacy indy identifiers. + */ schemaIssuerDid?: string + schemaIssuerId?: string + schemaId?: string schemaName?: string schemaVersion?: string credentialDefinitionId?: string + + /** + * @deprecated Use `issuerId` instead. Only valid for legacy indy identifiers. + */ issuerDid?: string + issuerId?: string } /** - * Class providing validation for the V2 credential proposal payload. - * - * The v1 message contains the properties directly in the message, which means they are - * validated using the class validator decorators. In v2 the attachments content is not transformed - * when transforming the message to a class instance so the content is not verified anymore, hence this - * class. - * + * Class representing an AnonCreds credential proposal as defined in Aries RFC 0592 (and soon the new AnonCreds RFC) */ -export class IndyCredPropose { - public constructor(options: IndyCredProposeOptions) { +export class AnonCredsCredentialProposal { + public constructor(options: AnonCredsCredentialProposalOptions) { if (options) { this.schemaIssuerDid = options.schemaIssuerDid + this.schemaIssuerId = options.schemaIssuerId this.schemaId = options.schemaId this.schemaName = options.schemaName this.schemaVersion = options.schemaVersion this.credentialDefinitionId = options.credentialDefinitionId this.issuerDid = options.issuerDid + this.issuerId = options.issuerId } } /** * Filter to request credential based on a particular Schema issuer DID. + * + * May only be used with legacy indy identifiers + * + * @deprecated Use schemaIssuerId instead */ @Expose({ name: 'schema_issuer_did' }) @IsString() @IsOptional() public schemaIssuerDid?: string + /** + * Filter to request credential based on a particular Schema issuer DID. + */ + @Expose({ name: 'schema_issuer_id' }) + @IsString() + @IsOptional() + public schemaIssuerId?: string + /** * Filter to request credential based on a particular Schema. */ @@ -73,9 +91,21 @@ export class IndyCredPropose { /** * Filter to request a credential issued by the owner of a particular DID. + * + * May only be used with legacy indy identifiers + * + * @deprecated Use issuerId instead */ @Expose({ name: 'issuer_did' }) @IsString() @IsOptional() public issuerDid?: string + + /** + * Filter to request a credential issued by the owner of a particular DID. + */ + @Expose({ name: 'issuer_id' }) + @IsString() + @IsOptional() + public issuerId?: string } diff --git a/packages/anoncreds/src/models/AnonCredsProofRequest.ts b/packages/anoncreds/src/models/AnonCredsProofRequest.ts new file mode 100644 index 0000000000..3448b71570 --- /dev/null +++ b/packages/anoncreds/src/models/AnonCredsProofRequest.ts @@ -0,0 +1,83 @@ +import type { AnonCredsRequestedAttributeOptions } from './AnonCredsRequestedAttribute' +import type { AnonCredsRequestedPredicateOptions } from './AnonCredsRequestedPredicate' + +import { Expose, Type } from 'class-transformer' +import { IsIn, IsInstance, IsOptional, IsString, ValidateNested } from 'class-validator' + +import { IsMap } from '../utils' + +import { AnonCredsRequestedAttribute } from './AnonCredsRequestedAttribute' +import { AnonCredsRequestedPredicate } from './AnonCredsRequestedPredicate' +import { AnonCredsRevocationInterval } from './AnonCredsRevocationInterval' + +export interface AnonCredsProofRequestOptions { + name: string + version: string + nonce: string + nonRevoked?: AnonCredsRevocationInterval + ver?: '1.0' | '2.0' + requestedAttributes?: Record + requestedPredicates?: Record +} + +/** + * Proof Request for AnonCreds based proof format + */ +export class AnonCredsProofRequest { + public constructor(options: AnonCredsProofRequestOptions) { + if (options) { + this.name = options.name + this.version = options.version + this.nonce = options.nonce + + this.requestedAttributes = new Map( + Object.entries(options.requestedAttributes ?? {}).map(([key, attribute]) => [ + key, + new AnonCredsRequestedAttribute(attribute), + ]) + ) + + this.requestedPredicates = new Map( + Object.entries(options.requestedPredicates ?? {}).map(([key, predicate]) => [ + key, + new AnonCredsRequestedPredicate(predicate), + ]) + ) + + this.nonRevoked = options.nonRevoked ? new AnonCredsRevocationInterval(options.nonRevoked) : undefined + this.ver = options.ver + } + } + + @IsString() + public name!: string + + @IsString() + public version!: string + + @IsString() + public nonce!: string + + @Expose({ name: 'requested_attributes' }) + @IsMap() + @ValidateNested({ each: true }) + @Type(() => AnonCredsRequestedAttribute) + public requestedAttributes!: Map + + @Expose({ name: 'requested_predicates' }) + @IsMap() + @ValidateNested({ each: true }) + @Type(() => AnonCredsRequestedPredicate) + public requestedPredicates!: Map + + @Expose({ name: 'non_revoked' }) + @ValidateNested() + @Type(() => AnonCredsRevocationInterval) + @IsOptional() + @IsInstance(AnonCredsRevocationInterval) + public nonRevoked?: AnonCredsRevocationInterval + + @IsIn(['1.0', '2.0']) + @IsOptional() + public ver?: '1.0' | '2.0' +} diff --git a/packages/anoncreds/src/models/AnonCredsRequestedAttribute.ts b/packages/anoncreds/src/models/AnonCredsRequestedAttribute.ts new file mode 100644 index 0000000000..7e2df55c8f --- /dev/null +++ b/packages/anoncreds/src/models/AnonCredsRequestedAttribute.ts @@ -0,0 +1,48 @@ +import type { AnonCredsRestrictionOptions } from './AnonCredsRestriction' + +import { Expose, Type } from 'class-transformer' +import { ArrayNotEmpty, IsArray, IsInstance, IsOptional, IsString, ValidateIf, ValidateNested } from 'class-validator' + +import { AnonCredsRestriction, AnonCredsRestrictionTransformer } from './AnonCredsRestriction' +import { AnonCredsRevocationInterval } from './AnonCredsRevocationInterval' + +export interface AnonCredsRequestedAttributeOptions { + name?: string + names?: string[] + nonRevoked?: AnonCredsRevocationInterval + restrictions?: AnonCredsRestrictionOptions[] +} + +export class AnonCredsRequestedAttribute { + public constructor(options: AnonCredsRequestedAttributeOptions) { + if (options) { + this.name = options.name + this.names = options.names + this.nonRevoked = options.nonRevoked ? new AnonCredsRevocationInterval(options.nonRevoked) : undefined + this.restrictions = options.restrictions?.map((r) => new AnonCredsRestriction(r)) + } + } + + @IsString() + @ValidateIf((o: AnonCredsRequestedAttribute) => o.names === undefined) + public name?: string + + @IsArray() + @IsString({ each: true }) + @ValidateIf((o: AnonCredsRequestedAttribute) => o.name === undefined) + @ArrayNotEmpty() + public names?: string[] + + @Expose({ name: 'non_revoked' }) + @ValidateNested() + @IsInstance(AnonCredsRevocationInterval) + @Type(() => AnonCredsRevocationInterval) + @IsOptional() + public nonRevoked?: AnonCredsRevocationInterval + + @ValidateNested({ each: true }) + @Type(() => AnonCredsRestriction) + @IsOptional() + @AnonCredsRestrictionTransformer() + public restrictions?: AnonCredsRestriction[] +} diff --git a/packages/anoncreds/src/models/AnonCredsRequestedPredicate.ts b/packages/anoncreds/src/models/AnonCredsRequestedPredicate.ts new file mode 100644 index 0000000000..9df0bcd698 --- /dev/null +++ b/packages/anoncreds/src/models/AnonCredsRequestedPredicate.ts @@ -0,0 +1,55 @@ +import type { AnonCredsRestrictionOptions } from './AnonCredsRestriction' + +import { Expose, Type } from 'class-transformer' +import { IsArray, IsIn, IsInstance, IsInt, IsOptional, IsString, ValidateNested } from 'class-validator' + +import { AnonCredsPredicateType, anonCredsPredicateType } from '../models' + +import { AnonCredsRestriction, AnonCredsRestrictionTransformer } from './AnonCredsRestriction' +import { AnonCredsRevocationInterval } from './AnonCredsRevocationInterval' + +export interface AnonCredsRequestedPredicateOptions { + name: string + // Also allow string value of the enum as input, to make it easier to use in the API + predicateType: AnonCredsPredicateType + predicateValue: number + nonRevoked?: AnonCredsRevocationInterval + restrictions?: AnonCredsRestrictionOptions[] +} + +export class AnonCredsRequestedPredicate { + public constructor(options: AnonCredsRequestedPredicateOptions) { + if (options) { + this.name = options.name + this.nonRevoked = options.nonRevoked ? new AnonCredsRevocationInterval(options.nonRevoked) : undefined + this.restrictions = options.restrictions?.map((r) => new AnonCredsRestriction(r)) + this.predicateType = options.predicateType as AnonCredsPredicateType + this.predicateValue = options.predicateValue + } + } + + @IsString() + public name!: string + + @Expose({ name: 'p_type' }) + @IsIn(anonCredsPredicateType) + public predicateType!: AnonCredsPredicateType + + @Expose({ name: 'p_value' }) + @IsInt() + public predicateValue!: number + + @Expose({ name: 'non_revoked' }) + @ValidateNested() + @Type(() => AnonCredsRevocationInterval) + @IsOptional() + @IsInstance(AnonCredsRevocationInterval) + public nonRevoked?: AnonCredsRevocationInterval + + @ValidateNested({ each: true }) + @Type(() => AnonCredsRestriction) + @IsOptional() + @IsArray() + @AnonCredsRestrictionTransformer() + public restrictions?: AnonCredsRestriction[] +} diff --git a/packages/anoncreds/src/models/AnonCredsRestriction.ts b/packages/anoncreds/src/models/AnonCredsRestriction.ts new file mode 100644 index 0000000000..c3f8ce843c --- /dev/null +++ b/packages/anoncreds/src/models/AnonCredsRestriction.ts @@ -0,0 +1,152 @@ +import { Exclude, Expose, Transform, TransformationType } from 'class-transformer' +import { IsOptional, IsString } from 'class-validator' + +export interface AnonCredsRestrictionOptions { + schemaId?: string + schemaIssuerDid?: string + schemaIssuerId?: string + schemaName?: string + schemaVersion?: string + issuerDid?: string + issuerId?: string + credentialDefinitionId?: string + attributeMarkers?: Record + attributeValues?: Record +} + +export class AnonCredsRestriction { + public constructor(options: AnonCredsRestrictionOptions) { + if (options) { + this.schemaId = options.schemaId + this.schemaIssuerDid = options.schemaIssuerDid + this.schemaIssuerId = options.schemaIssuerId + this.schemaName = options.schemaName + this.schemaVersion = options.schemaVersion + this.issuerDid = options.issuerDid + this.issuerId = options.issuerId + this.credentialDefinitionId = options.credentialDefinitionId + this.attributeMarkers = options.attributeMarkers ?? {} + this.attributeValues = options.attributeValues ?? {} + } + } + + @Expose({ name: 'schema_id' }) + @IsOptional() + @IsString() + public schemaId?: string + + @Expose({ name: 'schema_issuer_did' }) + @IsOptional() + @IsString() + public schemaIssuerDid?: string + + @Expose({ name: 'schema_issuer_id' }) + @IsOptional() + @IsString() + public schemaIssuerId?: string + + @Expose({ name: 'schema_name' }) + @IsOptional() + @IsString() + public schemaName?: string + + @Expose({ name: 'schema_version' }) + @IsOptional() + @IsString() + public schemaVersion?: string + + @Expose({ name: 'issuer_did' }) + @IsOptional() + @IsString() + public issuerDid?: string + + @Expose({ name: 'issuer_id' }) + @IsOptional() + @IsString() + public issuerId?: string + + @Expose({ name: 'cred_def_id' }) + @IsOptional() + @IsString() + public credentialDefinitionId?: string + + @Exclude() + public attributeMarkers: Record = {} + + @Exclude() + public attributeValues: Record = {} +} + +/** + * Decorator that transforms attribute values and attribute markers. + * + * It will transform between the following JSON structure: + * ```json + * { + * "attr::test_prop::value": "test_value" + * "attr::test_prop::marker": "1 + * } + * ``` + * + * And the following AnonCredsRestriction: + * ```json + * { + * "attributeValues": { + * "test_prop": "test_value" + * }, + * "attributeMarkers": { + * "test_prop": true + * } + * } + * ``` + * + * @example + * class Example { + * AttributeFilterTransformer() + * public restrictions!: AnonCredsRestriction[] + * } + */ +export function AnonCredsRestrictionTransformer() { + return Transform(({ value: restrictions, type }) => { + switch (type) { + case TransformationType.CLASS_TO_PLAIN: + if (restrictions && Array.isArray(restrictions)) { + for (const restriction of restrictions) { + const r = restriction as AnonCredsRestriction + + for (const [attributeName, attributeValue] of Object.entries(r.attributeValues)) { + restriction[`attr::${attributeName}::value`] = attributeValue + } + + for (const [attributeName] of Object.entries(r.attributeMarkers)) { + restriction[`attr::${attributeName}::marker`] = '1' + } + } + } + + return restrictions + + case TransformationType.PLAIN_TO_CLASS: + if (restrictions && Array.isArray(restrictions)) { + for (const restriction of restrictions) { + const r = restriction as AnonCredsRestriction + + for (const [attributeName, attributeValue] of Object.entries(r)) { + const match = new RegExp('^attr::([^:]+)::(value|marker)$').exec(attributeName) + + if (match && match[2] === 'marker' && attributeValue === '1') { + r.attributeMarkers[match[1]] = true + delete restriction[attributeName] + } else if (match && match[2] === 'value') { + r.attributeValues[match[1]] = attributeValue + delete restriction[attributeName] + } + } + } + } + return restrictions + default: + return restrictions + } + }) +} diff --git a/packages/anoncreds/src/models/AnonCredsRestrictionWrapper.ts b/packages/anoncreds/src/models/AnonCredsRestrictionWrapper.ts new file mode 100644 index 0000000000..a701c9e6ec --- /dev/null +++ b/packages/anoncreds/src/models/AnonCredsRestrictionWrapper.ts @@ -0,0 +1,11 @@ +import { Type } from 'class-transformer' +import { ValidateNested } from 'class-validator' + +import { AnonCredsRestrictionTransformer, AnonCredsRestriction } from './AnonCredsRestriction' + +export class AnonCredsRestrictionWrapper { + @ValidateNested({ each: true }) + @Type(() => AnonCredsRestriction) + @AnonCredsRestrictionTransformer() + public restrictions!: AnonCredsRestriction[] +} diff --git a/packages/core/src/modules/credentials/formats/indy/models/IndyRevocationInterval.ts b/packages/anoncreds/src/models/AnonCredsRevocationInterval.ts similarity index 69% rename from packages/core/src/modules/credentials/formats/indy/models/IndyRevocationInterval.ts rename to packages/anoncreds/src/models/AnonCredsRevocationInterval.ts index 6057153aaa..0ae0160616 100644 --- a/packages/core/src/modules/credentials/formats/indy/models/IndyRevocationInterval.ts +++ b/packages/anoncreds/src/models/AnonCredsRevocationInterval.ts @@ -1,7 +1,7 @@ import { IsInt, IsOptional } from 'class-validator' -export class IndyRevocationInterval { - public constructor(options: { from?: number; to?: number }) { +export class AnonCredsRevocationInterval { + public constructor(options: AnonCredsRevocationInterval) { if (options) { this.from = options.from this.to = options.to diff --git a/packages/anoncreds/src/models/__tests__/AnonCredsRestriction.test.ts b/packages/anoncreds/src/models/__tests__/AnonCredsRestriction.test.ts new file mode 100644 index 0000000000..33884d09a8 --- /dev/null +++ b/packages/anoncreds/src/models/__tests__/AnonCredsRestriction.test.ts @@ -0,0 +1,145 @@ +import { JsonTransformer } from '@aries-framework/core' +import { Type } from 'class-transformer' +import { IsArray } from 'class-validator' + +import { AnonCredsRestriction, AnonCredsRestrictionTransformer } from '../AnonCredsRestriction' + +// We need to add the transformer class to the wrapper +class Wrapper { + public constructor(options: Wrapper) { + if (options) { + this.restrictions = options.restrictions + } + } + + @Type(() => AnonCredsRestriction) + @IsArray() + @AnonCredsRestrictionTransformer() + public restrictions!: AnonCredsRestriction[] +} + +describe('AnonCredsRestriction', () => { + test('parses attribute values and markers', () => { + const anonCredsRestrictions = JsonTransformer.fromJSON( + { + restrictions: [ + { + 'attr::test_prop::value': 'test_value', + 'attr::test_prop2::value': 'test_value2', + 'attr::test_prop::marker': '1', + 'attr::test_prop2::marker': '1', + }, + ], + }, + Wrapper + ) + + expect(anonCredsRestrictions).toEqual({ + restrictions: [ + { + attributeValues: { + test_prop: 'test_value', + test_prop2: 'test_value2', + }, + attributeMarkers: { + test_prop: true, + test_prop2: true, + }, + }, + ], + }) + }) + + test('transforms attributeValues and attributeMarkers to json', () => { + const restrictions = new Wrapper({ + restrictions: [ + new AnonCredsRestriction({ + attributeMarkers: { + test_prop: true, + test_prop2: true, + }, + attributeValues: { + test_prop: 'test_value', + test_prop2: 'test_value2', + }, + }), + ], + }) + + expect(JsonTransformer.toJSON(restrictions)).toMatchObject({ + restrictions: [ + { + 'attr::test_prop::value': 'test_value', + 'attr::test_prop2::value': 'test_value2', + 'attr::test_prop::marker': '1', + 'attr::test_prop2::marker': '1', + }, + ], + }) + }) + + test('transforms properties from and to json with correct casing', () => { + const restrictions = new Wrapper({ + restrictions: [ + new AnonCredsRestriction({ + credentialDefinitionId: 'credentialDefinitionId', + issuerDid: 'issuerDid', + issuerId: 'issuerId', + schemaName: 'schemaName', + schemaVersion: 'schemaVersion', + schemaId: 'schemaId', + schemaIssuerDid: 'schemaIssuerDid', + schemaIssuerId: 'schemaIssuerId', + }), + ], + }) + + expect(JsonTransformer.toJSON(restrictions)).toMatchObject({ + restrictions: [ + { + cred_def_id: 'credentialDefinitionId', + issuer_did: 'issuerDid', + issuer_id: 'issuerId', + schema_name: 'schemaName', + schema_version: 'schemaVersion', + schema_id: 'schemaId', + schema_issuer_did: 'schemaIssuerDid', + schema_issuer_id: 'schemaIssuerId', + }, + ], + }) + + expect( + JsonTransformer.fromJSON( + { + restrictions: [ + { + cred_def_id: 'credentialDefinitionId', + issuer_did: 'issuerDid', + issuer_id: 'issuerId', + schema_name: 'schemaName', + schema_version: 'schemaVersion', + schema_id: 'schemaId', + schema_issuer_did: 'schemaIssuerDid', + schema_issuer_id: 'schemaIssuerId', + }, + ], + }, + Wrapper + ) + ).toMatchObject({ + restrictions: [ + { + credentialDefinitionId: 'credentialDefinitionId', + issuerDid: 'issuerDid', + issuerId: 'issuerId', + schemaName: 'schemaName', + schemaVersion: 'schemaVersion', + schemaId: 'schemaId', + schemaIssuerDid: 'schemaIssuerDid', + schemaIssuerId: 'schemaIssuerId', + }, + ], + }) + }) +}) diff --git a/packages/anoncreds/src/models/exchange.ts b/packages/anoncreds/src/models/exchange.ts new file mode 100644 index 0000000000..0e0ae355c9 --- /dev/null +++ b/packages/anoncreds/src/models/exchange.ts @@ -0,0 +1,125 @@ +export const anonCredsPredicateType = ['>=', '>', '<=', '<'] as const +export type AnonCredsPredicateType = (typeof anonCredsPredicateType)[number] + +export interface AnonCredsProofRequestRestriction { + schema_id?: string + schema_issuer_id?: string + schema_name?: string + schema_version?: string + issuer_id?: string + cred_def_id?: string + rev_reg_id?: string + + // Deprecated, but kept for backwards compatibility with legacy indy anoncreds implementations + schema_issuer_did?: string + issuer_did?: string + + // the following keys can be used for every `attribute name` in credential. + [key: `attr::${string}::marker`]: '1' | '0' + [key: `attr::${string}::value`]: string +} + +export interface AnonCredsNonRevokedInterval { + from?: number + to?: number +} + +export interface AnonCredsCredentialOffer { + schema_id: string + cred_def_id: string + nonce: string + key_correctness_proof: Record +} + +export interface AnonCredsCredentialRequest { + // prover_did is deprecated, however it is kept for backwards compatibility with legacy anoncreds implementations + prover_did?: string + entropy?: string + cred_def_id: string + blinded_ms: Record + blinded_ms_correctness_proof: Record + nonce: string +} + +export type AnonCredsCredentialValues = Record +export interface AnonCredsCredentialValue { + raw: string + encoded: string // Raw value as number in string +} + +export interface AnonCredsCredential { + schema_id: string + cred_def_id: string + rev_reg_id?: string + values: Record + signature: unknown + signature_correctness_proof: unknown +} + +export interface AnonCredsProof { + requested_proof: { + revealed_attrs: Record< + string, + { + sub_proof_index: number + raw: string + encoded: string + } + > + // revealed_attr_groups is only defined if there's a requested attribute using `names` + revealed_attr_groups?: Record< + string, + { + sub_proof_index: number + values: { + [key: string]: { + raw: string + encoded: string + } + } + } + > + unrevealed_attrs: Record< + string, + { + sub_proof_index: number + } + > + self_attested_attrs: Record + + requested_predicates: Record + } + // TODO: extend types for proof property + proof: any + identifiers: Array<{ + schema_id: string + cred_def_id: string + rev_reg_id?: string + timestamp?: number + }> +} + +export interface AnonCredsRequestedAttribute { + name?: string + names?: string[] + restrictions?: AnonCredsProofRequestRestriction[] + non_revoked?: AnonCredsNonRevokedInterval +} + +export interface AnonCredsRequestedPredicate { + name: string + p_type: AnonCredsPredicateType + p_value: number + restrictions?: AnonCredsProofRequestRestriction[] + non_revoked?: AnonCredsNonRevokedInterval +} + +export interface AnonCredsProofRequest { + name: string + version: string + nonce: string + requested_attributes: Record + requested_predicates: Record + non_revoked?: AnonCredsNonRevokedInterval + ver?: '1.0' | '2.0' +} diff --git a/packages/anoncreds/src/models/index.ts b/packages/anoncreds/src/models/index.ts new file mode 100644 index 0000000000..3ad7724723 --- /dev/null +++ b/packages/anoncreds/src/models/index.ts @@ -0,0 +1,4 @@ +export * from './internal' +export * from './exchange' +export * from './registry' +export * from './AnonCredsRestrictionWrapper' diff --git a/packages/anoncreds/src/models/internal.ts b/packages/anoncreds/src/models/internal.ts new file mode 100644 index 0000000000..8aacc72a52 --- /dev/null +++ b/packages/anoncreds/src/models/internal.ts @@ -0,0 +1,43 @@ +export interface AnonCredsCredentialInfo { + credentialId: string + attributes: { + [key: string]: string + } + schemaId: string + credentialDefinitionId: string + revocationRegistryId?: string | undefined + credentialRevocationId?: string | undefined + methodName: string +} + +export interface AnonCredsRequestedAttributeMatch { + credentialId: string + timestamp?: number + revealed: boolean + credentialInfo: AnonCredsCredentialInfo + revoked?: boolean +} + +export interface AnonCredsRequestedPredicateMatch { + credentialId: string + timestamp?: number + credentialInfo: AnonCredsCredentialInfo + revoked?: boolean +} + +export interface AnonCredsSelectedCredentials { + attributes: Record + predicates: Record + selfAttestedAttributes: Record +} + +export interface AnonCredsLinkSecretBlindingData { + v_prime: string + vr_prime: string | null +} + +export interface AnonCredsCredentialRequestMetadata { + link_secret_blinding_data: AnonCredsLinkSecretBlindingData + link_secret_name: string + nonce: string +} diff --git a/packages/anoncreds/src/models/registry.ts b/packages/anoncreds/src/models/registry.ts new file mode 100644 index 0000000000..bc4ad8a152 --- /dev/null +++ b/packages/anoncreds/src/models/registry.ts @@ -0,0 +1,43 @@ +export interface AnonCredsSchema { + issuerId: string + name: string + version: string + attrNames: string[] +} + +export interface AnonCredsCredentialDefinition { + issuerId: string + schemaId: string + type: 'CL' + tag: string + // TODO: work out in more detail + value: { + primary: Record + revocation?: unknown + } +} + +export interface AnonCredsRevocationRegistryDefinition { + issuerId: string + revocDefType: 'CL_ACCUM' + credDefId: string + tag: string + value: { + publicKeys: { + accumKey: { + z: string + } + } + maxCredNum: number + tailsLocation: string + tailsHash: string + } +} + +export interface AnonCredsRevocationStatusList { + issuerId: string + revRegDefId: string + revocationList: number[] + currentAccumulator: string + timestamp: number +} diff --git a/packages/core/src/modules/credentials/protocol/v1/V1CredentialProtocol.ts b/packages/anoncreds/src/protocols/credentials/v1/V1CredentialProtocol.ts similarity index 85% rename from packages/core/src/modules/credentials/protocol/v1/V1CredentialProtocol.ts rename to packages/anoncreds/src/protocols/credentials/v1/V1CredentialProtocol.ts index 0886de7e3e..ab7ba09cff 100644 --- a/packages/core/src/modules/credentials/protocol/v1/V1CredentialProtocol.ts +++ b/packages/anoncreds/src/protocols/credentials/v1/V1CredentialProtocol.ts @@ -1,83 +1,80 @@ -import type { AgentContext } from '../../../../agent' -import type { FeatureRegistry } from '../../../../agent/FeatureRegistry' -import type { InboundMessageContext } from '../../../../agent/models/InboundMessageContext' -import type { DidCommV1Message } from '../../../../didcomm' -import type { DependencyManager } from '../../../../plugins' -import type { ProblemReportMessage } from '../../../problem-reports' +import type { LegacyIndyCredentialFormatService } from '../../../formats' import type { - AcceptCredentialOptions, - AcceptOfferOptions, - AcceptProposalOptions, - AcceptRequestOptions, - CreateOfferOptions, - CreateProblemReportOptions, - CreateProposalOptions, - CredentialProtocolMsgReturnType, - NegotiateOfferOptions, - NegotiateProposalOptions, -} from '../../CredentialProtocolOptions' -import type { GetFormatDataReturn } from '../../CredentialsApiOptions' -import type { CredentialFormatService, ExtractCredentialFormats, IndyCredentialFormat } from '../../formats' - -import { Protocol } from '../../../../agent/models/features' -import { V1Attachment, V1AttachmentData } from '../../../../decorators/attachment/V1Attachment' -import { AriesFrameworkError } from '../../../../error' -import { injectable } from '../../../../plugins' -import { DidCommMessageRepository, DidCommMessageRole } from '../../../../storage' -import { JsonTransformer } from '../../../../utils' -import { isLinkedAttachment } from '../../../../utils/attachment' -import { uuid } from '../../../../utils/uuid' -import { AckStatus } from '../../../common' -import { ConnectionService } from '../../../connections/services' -import { CredentialsModuleConfig } from '../../CredentialsModuleConfig' -import { CredentialProblemReportReason } from '../../errors' -import { IndyCredPropose } from '../../formats/indy/models' -import { AutoAcceptCredential } from '../../models/CredentialAutoAcceptType' -import { CredentialState } from '../../models/CredentialState' -import { CredentialExchangeRecord, CredentialRepository } from '../../repository' -import { composeAutoAccept } from '../../util/composeAutoAccept' -import { arePreviewAttributesEqual } from '../../util/previewAttributes' -import { BaseCredentialProtocol } from '../BaseCredentialProtocol' + AgentContext, + DependencyManager, + FeatureRegistry, + CredentialProtocolOptions, + InboundMessageContext, + ProblemReportMessage, + ExtractCredentialFormats, + CredentialProtocol, + DidCommV1Message, +} from '@aries-framework/core' + +import { + Protocol, + CredentialRepository, + AriesFrameworkError, + CredentialExchangeRecord, + CredentialState, + JsonTransformer, + ConnectionService, + V1Attachment, + V1AttachmentData, + AckStatus, + CredentialProblemReportReason, + CredentialsModuleConfig, + AutoAcceptCredential, + utils, + DidCommMessageRepository, + DidCommMessageRole, + BaseCredentialProtocol, + isLinkedAttachment, +} from '@aries-framework/core' + +import { AnonCredsCredentialProposal } from '../../../models/AnonCredsCredentialProposal' +import { composeCredentialAutoAccept, areCredentialPreviewAttributesEqual } from '../../../utils' import { - V1CredentialAckHandler, - V1CredentialProblemReportHandler, - V1IssueCredentialHandler, - V1OfferCredentialHandler, V1ProposeCredentialHandler, + V1OfferCredentialHandler, V1RequestCredentialHandler, + V1IssueCredentialHandler, + V1CredentialAckHandler, + V1CredentialProblemReportHandler, } from './handlers' import { - INDY_CREDENTIAL_ATTACHMENT_ID, + V1CredentialPreview, + V1ProposeCredentialMessage, + V1OfferCredentialMessage, INDY_CREDENTIAL_OFFER_ATTACHMENT_ID, + V1RequestCredentialMessage, INDY_CREDENTIAL_REQUEST_ATTACHMENT_ID, + V1IssueCredentialMessage, + INDY_CREDENTIAL_ATTACHMENT_ID, V1CredentialAckMessage, V1CredentialProblemReportMessage, - V1IssueCredentialMessage, - V1OfferCredentialMessage, - V1ProposeCredentialMessage, - V1RequestCredentialMessage, } from './messages' -import { V1CredentialPreview } from './messages/V1CredentialPreview' export interface V1CredentialProtocolConfig { - // indyCredentialFormat must be a service that implements the `IndyCredentialFormat` interface, however it doesn't - // have to be the IndyCredentialFormatService implementation per se. - indyCredentialFormat: CredentialFormatService + indyCredentialFormat: LegacyIndyCredentialFormatService } -@injectable() -export class V1CredentialProtocol extends BaseCredentialProtocol<[CredentialFormatService]> { - private indyCredentialFormat: CredentialFormatService +export class V1CredentialProtocol + extends BaseCredentialProtocol<[LegacyIndyCredentialFormatService]> + implements CredentialProtocol<[LegacyIndyCredentialFormatService]> +{ + private indyCredentialFormat: LegacyIndyCredentialFormatService public constructor({ indyCredentialFormat }: V1CredentialProtocolConfig) { super() + // TODO: just create a new instance of LegacyIndyCredentialFormatService here so it makes the setup easier this.indyCredentialFormat = indyCredentialFormat } /** - * The version of the issue credential protocol this service supports + * The version of the issue credential protocol this protocol supports */ public readonly version = 'v1' @@ -115,12 +112,12 @@ export class V1CredentialProtocol extends BaseCredentialProtocol<[CredentialForm public async createProposal( agentContext: AgentContext, { - connection, + connectionRecord, credentialFormats, comment, autoAcceptCredential, - }: CreateProposalOptions<[CredentialFormatService]> - ): Promise> { + }: CredentialProtocolOptions.CreateCredentialProposalOptions<[LegacyIndyCredentialFormatService]> + ): Promise> { this.assertOnlyIndyFormat(credentialFormats) const credentialRepository = agentContext.dependencyManager.resolve(CredentialRepository) @@ -136,11 +133,11 @@ export class V1CredentialProtocol extends BaseCredentialProtocol<[CredentialForm // Create record const credentialRecord = new CredentialExchangeRecord({ - connectionId: connection.id, - threadId: uuid(), + connectionId: connectionRecord.id, + threadId: utils.uuid(), state: CredentialState.ProposalSent, linkedAttachments: linkedAttachments?.map((linkedAttachment) => linkedAttachment.attachment), - autoAcceptCredential: autoAcceptCredential, + autoAcceptCredential, protocolVersion: 'v1', }) @@ -151,7 +148,7 @@ export class V1CredentialProtocol extends BaseCredentialProtocol<[CredentialForm }) // Transform the attachment into the attachment payload and use that to construct the v1 message - const indyCredentialProposal = JsonTransformer.fromJSON(attachment.getDataAsJson(), IndyCredPropose) + const indyCredentialProposal = JsonTransformer.fromJSON(attachment.getDataAsJson(), AnonCredsCredentialProposal) const credentialProposal = previewAttributes ? new V1CredentialPreview({ @@ -173,7 +170,7 @@ export class V1CredentialProtocol extends BaseCredentialProtocol<[CredentialForm associatedRecordId: credentialRecord.id, }) - credentialRecord.credentialAttributes = previewAttributes + credentialRecord.credentialAttributes = credentialProposal?.attributes await credentialRepository.save(agentContext, credentialRecord) this.emitStateChangedEvent(agentContext, credentialRecord, null) @@ -218,18 +215,18 @@ export class V1CredentialProtocol extends BaseCredentialProtocol<[CredentialForm credentialRecord.assertProtocolVersion('v1') credentialRecord.assertState(CredentialState.OfferSent) - const proposalCredentialMessage = await didCommMessageRepository.findAgentMessage(messageContext.agentContext, { + const previousReceivedMessage = await didCommMessageRepository.findAgentMessage(messageContext.agentContext, { associatedRecordId: credentialRecord.id, messageClass: V1ProposeCredentialMessage, }) - const offerCredentialMessage = await didCommMessageRepository.findAgentMessage(messageContext.agentContext, { + const previousSentMessage = await didCommMessageRepository.getAgentMessage(messageContext.agentContext, { associatedRecordId: credentialRecord.id, messageClass: V1OfferCredentialMessage, }) connectionService.assertConnectionOrServiceDecorator(messageContext, { - previousReceivedMessage: proposalCredentialMessage ?? undefined, - previousSentMessage: offerCredentialMessage ?? undefined, + previousReceivedMessage, + previousSentMessage, }) await this.indyCredentialFormat.processProposal(messageContext.agentContext, { @@ -287,8 +284,8 @@ export class V1CredentialProtocol extends BaseCredentialProtocol<[CredentialForm credentialFormats, comment, autoAcceptCredential, - }: AcceptProposalOptions<[CredentialFormatService]> - ): Promise> { + }: CredentialProtocolOptions.AcceptCredentialProposalOptions<[LegacyIndyCredentialFormatService]> + ): Promise> { // Assert credentialRecord.assertProtocolVersion('v1') credentialRecord.assertState(CredentialState.ProposalReceived) @@ -307,7 +304,7 @@ export class V1CredentialProtocol extends BaseCredentialProtocol<[CredentialForm credentialRecord.credentialAttributes = proposalMessage.credentialPreview?.attributes const { attachment, previewAttributes } = await this.indyCredentialFormat.acceptProposal(agentContext, { - attachId: INDY_CREDENTIAL_OFFER_ATTACHMENT_ID, + attachmentId: INDY_CREDENTIAL_OFFER_ATTACHMENT_ID, credentialFormats, credentialRecord, proposalAttachment: new V1Attachment({ @@ -332,7 +329,7 @@ export class V1CredentialProtocol extends BaseCredentialProtocol<[CredentialForm message.setThread({ threadId: credentialRecord.threadId }) - credentialRecord.credentialAttributes = previewAttributes + credentialRecord.credentialAttributes = message.credentialPreview.attributes credentialRecord.autoAcceptCredential = autoAcceptCredential ?? credentialRecord.autoAcceptCredential await this.updateState(agentContext, credentialRecord, CredentialState.OfferSent) @@ -349,7 +346,7 @@ export class V1CredentialProtocol extends BaseCredentialProtocol<[CredentialForm * Negotiate a credential proposal as issuer (by sending a credential offer message) to the connection * associated with the credential record. * - * @param options configuration for the offer see {@link NegotiateProposalOptions} + * @param options configuration for the offer see {@link NegotiateCredentialProposalOptions} * @returns Credential record associated with the credential offer and the corresponding new offer message * */ @@ -360,17 +357,17 @@ export class V1CredentialProtocol extends BaseCredentialProtocol<[CredentialForm credentialRecord, comment, autoAcceptCredential, - }: NegotiateProposalOptions<[CredentialFormatService]> - ): Promise> { + }: CredentialProtocolOptions.NegotiateCredentialProposalOptions<[LegacyIndyCredentialFormatService]> + ): Promise> { // Assert credentialRecord.assertProtocolVersion('v1') credentialRecord.assertState(CredentialState.ProposalReceived) - if (credentialFormats) this.assertOnlyIndyFormat(credentialFormats) + this.assertOnlyIndyFormat(credentialFormats) const didCommMessageRepository = agentContext.dependencyManager.resolve(DidCommMessageRepository) const { attachment, previewAttributes } = await this.indyCredentialFormat.createOffer(agentContext, { - attachId: INDY_CREDENTIAL_OFFER_ATTACHMENT_ID, + attachmentId: INDY_CREDENTIAL_OFFER_ATTACHMENT_ID, credentialFormats, credentialRecord, }) @@ -389,7 +386,7 @@ export class V1CredentialProtocol extends BaseCredentialProtocol<[CredentialForm }) message.setThread({ threadId: credentialRecord.threadId }) - credentialRecord.credentialAttributes = previewAttributes + credentialRecord.credentialAttributes = message.credentialPreview.attributes credentialRecord.autoAcceptCredential = autoAcceptCredential ?? credentialRecord.autoAcceptCredential await this.updateState(agentContext, credentialRecord, CredentialState.OfferSent) @@ -416,11 +413,11 @@ export class V1CredentialProtocol extends BaseCredentialProtocol<[CredentialForm credentialFormats, autoAcceptCredential, comment, - connection, - }: CreateOfferOptions<[CredentialFormatService]> - ): Promise> { + connectionRecord, + }: CredentialProtocolOptions.CreateCredentialOfferOptions<[LegacyIndyCredentialFormatService]> + ): Promise> { // Assert - if (credentialFormats) this.assertOnlyIndyFormat(credentialFormats) + this.assertOnlyIndyFormat(credentialFormats) const credentialRepository = agentContext.dependencyManager.resolve(CredentialRepository) const didCommMessageRepository = agentContext.dependencyManager.resolve(DidCommMessageRepository) @@ -431,8 +428,8 @@ export class V1CredentialProtocol extends BaseCredentialProtocol<[CredentialForm // Create record const credentialRecord = new CredentialExchangeRecord({ - connectionId: connection?.id, - threadId: uuid(), + connectionId: connectionRecord?.id, + threadId: utils.uuid(), linkedAttachments: credentialFormats.indy.linkedAttachments?.map( (linkedAttachments) => linkedAttachments.attachment ), @@ -442,7 +439,7 @@ export class V1CredentialProtocol extends BaseCredentialProtocol<[CredentialForm }) const { attachment, previewAttributes } = await this.indyCredentialFormat.createOffer(agentContext, { - attachId: INDY_CREDENTIAL_OFFER_ATTACHMENT_ID, + attachmentId: INDY_CREDENTIAL_OFFER_ATTACHMENT_ID, credentialFormats, credentialRecord, }) @@ -468,7 +465,7 @@ export class V1CredentialProtocol extends BaseCredentialProtocol<[CredentialForm role: DidCommMessageRole.Sender, }) - credentialRecord.credentialAttributes = previewAttributes + credentialRecord.credentialAttributes = message.credentialPreview.attributes await credentialRepository.save(agentContext, credentialRecord) this.emitStateChangedEvent(agentContext, credentialRecord, null) @@ -499,11 +496,7 @@ export class V1CredentialProtocol extends BaseCredentialProtocol<[CredentialForm agentContext.config.logger.debug(`Processing credential offer with id ${offerMessage.id}`) - let credentialRecord = await this.findByThreadAndConnectionId( - messageContext.agentContext, - offerMessage.threadId, - connection?.id - ) + let credentialRecord = await this.findByThreadAndConnectionId(agentContext, offerMessage.threadId, connection?.id) const offerAttachment = offerMessage.getOfferAttachmentById(INDY_CREDENTIAL_OFFER_ATTACHMENT_ID) if (!offerAttachment) { @@ -513,11 +506,11 @@ export class V1CredentialProtocol extends BaseCredentialProtocol<[CredentialForm } if (credentialRecord) { - const proposalCredentialMessage = await didCommMessageRepository.findAgentMessage(messageContext.agentContext, { + const previousSentMessage = await didCommMessageRepository.getAgentMessage(messageContext.agentContext, { associatedRecordId: credentialRecord.id, messageClass: V1ProposeCredentialMessage, }) - const offerCredentialMessage = await didCommMessageRepository.findAgentMessage(messageContext.agentContext, { + const previousReceivedMessage = await didCommMessageRepository.findAgentMessage(messageContext.agentContext, { associatedRecordId: credentialRecord.id, messageClass: V1OfferCredentialMessage, }) @@ -526,8 +519,8 @@ export class V1CredentialProtocol extends BaseCredentialProtocol<[CredentialForm credentialRecord.assertProtocolVersion('v1') credentialRecord.assertState(CredentialState.ProposalSent) connectionService.assertConnectionOrServiceDecorator(messageContext, { - previousReceivedMessage: offerCredentialMessage ?? undefined, - previousSentMessage: proposalCredentialMessage ?? undefined, + previousReceivedMessage, + previousSentMessage, }) await this.indyCredentialFormat.processOffer(messageContext.agentContext, { @@ -547,7 +540,7 @@ export class V1CredentialProtocol extends BaseCredentialProtocol<[CredentialForm // No credential record exists with thread id credentialRecord = new CredentialExchangeRecord({ connectionId: connection?.id, - threadId: offerMessage.id, + threadId: offerMessage.threadId, state: CredentialState.OfferReceived, protocolVersion: 'v1', }) @@ -587,8 +580,8 @@ export class V1CredentialProtocol extends BaseCredentialProtocol<[CredentialForm credentialFormats, comment, autoAcceptCredential, - }: AcceptOfferOptions<[CredentialFormatService]> - ): Promise> { + }: CredentialProtocolOptions.AcceptCredentialOfferOptions<[LegacyIndyCredentialFormatService]> + ): Promise> { // Assert credential credentialRecord.assertProtocolVersion('v1') credentialRecord.assertState(CredentialState.OfferReceived) @@ -610,7 +603,7 @@ export class V1CredentialProtocol extends BaseCredentialProtocol<[CredentialForm const { attachment } = await this.indyCredentialFormat.acceptOffer(agentContext, { credentialRecord, credentialFormats, - attachId: INDY_CREDENTIAL_REQUEST_ATTACHMENT_ID, + attachmentId: INDY_CREDENTIAL_REQUEST_ATTACHMENT_ID, offerAttachment, }) @@ -654,8 +647,8 @@ export class V1CredentialProtocol extends BaseCredentialProtocol<[CredentialForm credentialRecord, autoAcceptCredential, comment, - }: NegotiateOfferOptions<[CredentialFormatService]> - ): Promise> { + }: CredentialProtocolOptions.NegotiateCredentialOfferOptions<[LegacyIndyCredentialFormatService]> + ): Promise> { // Assert credentialRecord.assertProtocolVersion('v1') credentialRecord.assertState(CredentialState.OfferReceived) @@ -670,7 +663,7 @@ export class V1CredentialProtocol extends BaseCredentialProtocol<[CredentialForm } if (!credentialFormats.indy) { - throw new AriesFrameworkError('Missing indy credential format in v1 create proposal call.') + throw new AriesFrameworkError('Missing indy credential format in v1 negotiate proposal call.') } const { linkedAttachments } = credentialFormats.indy @@ -683,7 +676,7 @@ export class V1CredentialProtocol extends BaseCredentialProtocol<[CredentialForm }) // Transform the attachment into the attachment payload and use that to construct the v1 message - const indyCredentialProposal = JsonTransformer.fromJSON(attachment.getDataAsJson(), IndyCredPropose) + const indyCredentialProposal = JsonTransformer.fromJSON(attachment.getDataAsJson(), AnonCredsCredentialProposal) const credentialProposal = previewAttributes ? new V1CredentialPreview({ @@ -707,7 +700,7 @@ export class V1CredentialProtocol extends BaseCredentialProtocol<[CredentialForm }) // Update record - credentialRecord.credentialAttributes = previewAttributes + credentialRecord.credentialAttributes = message.credentialPreview?.attributes credentialRecord.linkedAttachments = linkedAttachments?.map((attachment) => attachment.attachment) credentialRecord.autoAcceptCredential = autoAcceptCredential ?? credentialRecord.autoAcceptCredential await this.updateState(agentContext, credentialRecord, CredentialState.ProposalSent) @@ -719,21 +712,12 @@ export class V1CredentialProtocol extends BaseCredentialProtocol<[CredentialForm * Starting from a request is not supported in v1 of the issue credential protocol * because indy doesn't allow to start from a request */ - public async createRequest(): Promise> { + public async createRequest(): Promise< + CredentialProtocolOptions.CredentialProtocolMsgReturnType + > { throw new AriesFrameworkError('Starting from a request is not supported for v1 issue credential protocol') } - /** - * Process a received {@link IssueCredentialMessage}. This will not accept the credential - * or send a credential acknowledgement. It will only update the existing credential record with - * the information from the issue credential message. Use {@link createAck} - * after calling this method to create a credential acknowledgement. - * - * @param messageContext The message context containing an issue credential message - * - * @returns credential record associated with the issue credential message - * - */ public async processRequest( messageContext: InboundMessageContext ): Promise { @@ -796,7 +780,7 @@ export class V1CredentialProtocol extends BaseCredentialProtocol<[CredentialForm } /** - * Create a {@link IssueCredentialMessage} as response to a received credential request. + * Create a {@link V1IssueCredentialMessage} as response to a received credential request. * * @returns Object containing issue credential message and associated credential record * @@ -808,11 +792,12 @@ export class V1CredentialProtocol extends BaseCredentialProtocol<[CredentialForm credentialFormats, comment, autoAcceptCredential, - }: AcceptRequestOptions<[CredentialFormatService]> - ): Promise> { + }: CredentialProtocolOptions.AcceptCredentialRequestOptions<[LegacyIndyCredentialFormatService]> + ): Promise> { // Assert credentialRecord.assertProtocolVersion('v1') credentialRecord.assertState(CredentialState.RequestReceived) + if (credentialFormats) this.assertOnlyIndyFormat(credentialFormats) const didCommMessageRepository = agentContext.dependencyManager.resolve(DidCommMessageRepository) @@ -834,17 +819,17 @@ export class V1CredentialProtocol extends BaseCredentialProtocol<[CredentialForm ) } - const { attachment: credentialsAttach } = await this.indyCredentialFormat.acceptRequest(agentContext, { + const { attachment } = await this.indyCredentialFormat.acceptRequest(agentContext, { credentialRecord, requestAttachment, offerAttachment, - attachId: INDY_CREDENTIAL_ATTACHMENT_ID, + attachmentId: INDY_CREDENTIAL_ATTACHMENT_ID, credentialFormats, }) const issueMessage = new V1IssueCredentialMessage({ comment, - credentialAttachments: [credentialsAttach], + credentialAttachments: [attachment], attachments: credentialRecord.linkedAttachments, }) @@ -864,7 +849,7 @@ export class V1CredentialProtocol extends BaseCredentialProtocol<[CredentialForm } /** - * Process an incoming {@link IssueCredentialMessage} + * Process an incoming {@link V1IssueCredentialMessage} * * @param messageContext The message context containing a credential acknowledgement message * @returns credential record associated with the credential acknowledgement message @@ -902,8 +887,8 @@ export class V1CredentialProtocol extends BaseCredentialProtocol<[CredentialForm credentialRecord.assertProtocolVersion('v1') credentialRecord.assertState(CredentialState.RequestSent) connectionService.assertConnectionOrServiceDecorator(messageContext, { - previousReceivedMessage: offerCredentialMessage ?? undefined, - previousSentMessage: requestCredentialMessage ?? undefined, + previousReceivedMessage: offerCredentialMessage, + previousSentMessage: requestCredentialMessage, }) const issueAttachment = issueMessage.getCredentialAttachmentById(INDY_CREDENTIAL_ATTACHMENT_ID) @@ -942,8 +927,8 @@ export class V1CredentialProtocol extends BaseCredentialProtocol<[CredentialForm */ public async acceptCredential( agentContext: AgentContext, - { credentialRecord }: AcceptCredentialOptions - ): Promise> { + { credentialRecord }: CredentialProtocolOptions.AcceptCredentialOptions + ): Promise> { credentialRecord.assertProtocolVersion('v1') credentialRecord.assertState(CredentialState.CredentialReceived) @@ -1014,13 +999,18 @@ export class V1CredentialProtocol extends BaseCredentialProtocol<[CredentialForm * @returns a {@link V1CredentialProblemReportMessage} * */ - public createProblemReport(agentContext: AgentContext, options: CreateProblemReportOptions): ProblemReportMessage { - return new V1CredentialProblemReportMessage({ + public async createProblemReport( + agentContext: AgentContext, + { credentialRecord, description }: CredentialProtocolOptions.CreateCredentialProblemReportOptions + ): Promise> { + const message = new V1CredentialProblemReportMessage({ description: { - en: options.message, + en: description, code: CredentialProblemReportReason.IssuanceAbandoned, }, }) + + return { message, credentialRecord } } // AUTO RESPOND METHODS @@ -1035,7 +1025,7 @@ export class V1CredentialProtocol extends BaseCredentialProtocol<[CredentialForm const credentialsModuleConfig = agentContext.dependencyManager.resolve(CredentialsModuleConfig) - const autoAccept = composeAutoAccept( + const autoAccept = composeCredentialAutoAccept( credentialRecord.autoAcceptCredential, credentialsModuleConfig.autoAcceptCredentials ) @@ -1057,7 +1047,7 @@ export class V1CredentialProtocol extends BaseCredentialProtocol<[CredentialForm if (credentialOfferJson.cred_def_id !== proposalMessage.credentialDefinitionId) return false // Check if preview values match - return arePreviewAttributesEqual( + return areCredentialPreviewAttributesEqual( proposalMessage.credentialPreview.attributes, offerMessage.credentialPreview.attributes ) @@ -1074,7 +1064,7 @@ export class V1CredentialProtocol extends BaseCredentialProtocol<[CredentialForm const credentialsModuleConfig = agentContext.dependencyManager.resolve(CredentialsModuleConfig) - const autoAccept = composeAutoAccept( + const autoAccept = composeCredentialAutoAccept( credentialRecord.autoAcceptCredential, credentialsModuleConfig.autoAcceptCredentials ) @@ -1096,7 +1086,7 @@ export class V1CredentialProtocol extends BaseCredentialProtocol<[CredentialForm if (credentialOfferJson.cred_def_id !== proposalMessage.credentialDefinitionId) return false // Check if preview values match - return arePreviewAttributesEqual( + return areCredentialPreviewAttributesEqual( proposalMessage.credentialPreview.attributes, offerMessage.credentialPreview.attributes ) @@ -1113,7 +1103,7 @@ export class V1CredentialProtocol extends BaseCredentialProtocol<[CredentialForm const credentialsModuleConfig = agentContext.dependencyManager.resolve(CredentialsModuleConfig) - const autoAccept = composeAutoAccept( + const autoAccept = composeCredentialAutoAccept( credentialRecord.autoAcceptCredential, credentialsModuleConfig.autoAcceptCredentials ) @@ -1148,7 +1138,7 @@ export class V1CredentialProtocol extends BaseCredentialProtocol<[CredentialForm const credentialsModuleConfig = agentContext.dependencyManager.resolve(CredentialsModuleConfig) - const autoAccept = composeAutoAccept( + const autoAccept = composeCredentialAutoAccept( credentialRecord.autoAcceptCredential, credentialsModuleConfig.autoAcceptCredentials ) @@ -1215,7 +1205,11 @@ export class V1CredentialProtocol extends BaseCredentialProtocol<[CredentialForm public async getFormatData( agentContext: AgentContext, credentialExchangeId: string - ): Promise]>>> { + ): Promise< + CredentialProtocolOptions.GetCredentialFormatDataReturn< + ExtractCredentialFormats<[LegacyIndyCredentialFormatService]> + > + > { // TODO: we could looking at fetching all record using a single query and then filtering based on the type of the message. const [proposalMessage, offerMessage, requestMessage, credentialMessage] = await Promise.all([ this.findProposalMessage(agentContext, credentialExchangeId), @@ -1259,7 +1253,7 @@ export class V1CredentialProtocol extends BaseCredentialProtocol<[CredentialForm } private rfc0592ProposalFromV1ProposeMessage(proposalMessage: V1ProposeCredentialMessage) { - const indyCredentialProposal = new IndyCredPropose({ + const indyCredentialProposal = new AnonCredsCredentialProposal({ credentialDefinitionId: proposalMessage.credentialDefinitionId, schemaId: proposalMessage.schemaId, issuerDid: proposalMessage.issuerDid, diff --git a/packages/core/src/modules/credentials/protocol/v1/__tests__/V1CredentialProtocolCred.test.ts b/packages/anoncreds/src/protocols/credentials/v1/__tests__/V1CredentialProtocolCred.test.ts similarity index 75% rename from packages/core/src/modules/credentials/protocol/v1/__tests__/V1CredentialProtocolCred.test.ts rename to packages/anoncreds/src/protocols/credentials/v1/__tests__/V1CredentialProtocolCred.test.ts index 405da5b405..5bbd6a698f 100644 --- a/packages/core/src/modules/credentials/protocol/v1/__tests__/V1CredentialProtocolCred.test.ts +++ b/packages/anoncreds/src/protocols/credentials/v1/__tests__/V1CredentialProtocolCred.test.ts @@ -1,39 +1,38 @@ -import type { AgentContext } from '../../../../../agent' -import type { AgentConfig } from '../../../../../agent/AgentConfig' -import type { GetAgentMessageOptions } from '../../../../../storage/didcomm/DidCommMessageRepository' -import type { CredentialStateChangedEvent } from '../../../CredentialEvents' -import type { IndyCredentialViewMetadata } from '../../../formats/indy/models' -import type { CredentialPreviewAttribute } from '../../../models' -import type { CustomCredentialTags } from '../../../repository/CredentialExchangeRecord' +import type { + AgentContext, + CustomCredentialTags, + CredentialPreviewAttribute, + AgentConfig, + CredentialStateChangedEvent, +} from '@aries-framework/core' +import { + EventEmitter, + DidExchangeState, + JsonEncoder, + DidCommMessageRecord, + DidCommMessageRole, + AriesFrameworkError, + CredentialState, + CredentialExchangeRecord, + CredentialFormatSpec, + AutoAcceptCredential, + JsonTransformer, + InboundMessageContext, + CredentialEventTypes, + AckStatus, + CredentialProblemReportReason, + V1Attachment, + V1AttachmentData, +} from '@aries-framework/core' import { Subject } from 'rxjs' -import { getAgentConfig, getAgentContext, getMockConnection, mockFunction } from '../../../../../../tests/helpers' -import { Dispatcher } from '../../../../../agent/Dispatcher' -import { EventEmitter } from '../../../../../agent/EventEmitter' -import { InboundMessageContext } from '../../../../../agent/models/InboundMessageContext' -import { V1Attachment, V1AttachmentData } from '../../../../../decorators/attachment/V1Attachment' -import { AriesFrameworkError } from '../../../../../error' -import { DidCommMessageRecord, DidCommMessageRole } from '../../../../../storage' -import { DidCommMessageRepository } from '../../../../../storage/didcomm/DidCommMessageRepository' -import { JsonTransformer } from '../../../../../utils' -import { JsonEncoder } from '../../../../../utils/JsonEncoder' -import { uuid } from '../../../../../utils/uuid' -import { AckStatus } from '../../../../common' -import { DidExchangeState } from '../../../../connections' -import { ConnectionService } from '../../../../connections/services/ConnectionService' -import { RoutingService } from '../../../../routing/services/RoutingService' -import { CredentialEventTypes } from '../../../CredentialEvents' -import { credDef, credReq } from '../../../__tests__/fixtures' -import { CredentialProblemReportReason } from '../../../errors/CredentialProblemReportReason' -import { IndyCredentialFormatService } from '../../../formats/indy/IndyCredentialFormatService' -import { IndyCredentialUtils } from '../../../formats/indy/IndyCredentialUtils' -import { AutoAcceptCredential } from '../../../models/CredentialAutoAcceptType' -import { CredentialFormatSpec } from '../../../models/CredentialFormatSpec' -import { CredentialState } from '../../../models/CredentialState' -import { CredentialExchangeRecord } from '../../../repository/CredentialExchangeRecord' -import { CredentialMetadataKeys } from '../../../repository/CredentialMetadataTypes' -import { CredentialRepository } from '../../../repository/CredentialRepository' +import { ConnectionService } from '../../../../../../core/src/modules/connections/services/ConnectionService' +import { CredentialRepository } from '../../../../../../core/src/modules/credentials/repository/CredentialRepository' +import { DidCommMessageRepository } from '../../../../../../core/src/storage/didcomm/DidCommMessageRepository' +import { getMockConnection, getAgentConfig, getAgentContext, mockFunction } from '../../../../../../core/tests/helpers' +import { LegacyIndyCredentialFormatService } from '../../../../formats/LegacyIndyCredentialFormatService' +import { convertAttributesToCredentialValues } from '../../../../utils/credential' import { V1CredentialProtocol } from '../V1CredentialProtocol' import { INDY_CREDENTIAL_ATTACHMENT_ID, @@ -49,31 +48,26 @@ import { } from '../messages' // Mock classes -jest.mock('../../../repository/CredentialRepository') -jest.mock('../../../formats/indy/IndyCredentialFormatService') -jest.mock('../../../../../storage/didcomm/DidCommMessageRepository') -jest.mock('../../../../routing/services/RoutingService') -jest.mock('../../../../connections/services/ConnectionService') -jest.mock('../../../../../agent/Dispatcher') +jest.mock('../../../../../../core/src/modules/credentials/repository/CredentialRepository') +jest.mock('../../../../formats/LegacyIndyCredentialFormatService') +jest.mock('../../../../../../core/src/storage/didcomm/DidCommMessageRepository') +jest.mock('../../../../../../core/src/modules/connections/services/ConnectionService') // Mock typed object const CredentialRepositoryMock = CredentialRepository as jest.Mock -const IndyCredentialFormatServiceMock = IndyCredentialFormatService as jest.Mock +const LegacyIndyCredentialFormatServiceMock = + LegacyIndyCredentialFormatService as jest.Mock const DidCommMessageRepositoryMock = DidCommMessageRepository as jest.Mock -const RoutingServiceMock = RoutingService as jest.Mock const ConnectionServiceMock = ConnectionService as jest.Mock -const DispatcherMock = Dispatcher as jest.Mock const credentialRepository = new CredentialRepositoryMock() const didCommMessageRepository = new DidCommMessageRepositoryMock() -const routingService = new RoutingServiceMock() -const indyCredentialFormatService = new IndyCredentialFormatServiceMock() -const dispatcher = new DispatcherMock() +const legacyIndyCredentialFormatService = new LegacyIndyCredentialFormatServiceMock() const connectionService = new ConnectionServiceMock() // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore -indyCredentialFormatService.credentialRecordType = 'indy' +legacyIndyCredentialFormatService.credentialRecordType = 'anoncreds' const connection = getMockConnection({ id: '123', @@ -98,7 +92,7 @@ const requestAttachment = new V1Attachment({ id: INDY_CREDENTIAL_REQUEST_ATTACHMENT_ID, mimeType: 'application/json', data: new V1AttachmentData({ - base64: JsonEncoder.toBase64(credReq), + base64: JsonEncoder.toBase64({}), }), }) @@ -107,14 +101,14 @@ const credentialAttachment = new V1Attachment({ mimeType: 'application/json', data: new V1AttachmentData({ base64: JsonEncoder.toBase64({ - values: IndyCredentialUtils.convertAttributesToValues(credentialPreview.attributes), + values: convertAttributesToCredentialValues(credentialPreview.attributes), }), }), }) const credentialProposalMessage = new V1ProposeCredentialMessage({ comment: 'comment', - credentialDefinitionId: credDef.id, + credentialDefinitionId: 'Th7MpTaRZVRYnPiabds81Y:3:CL:17:TAG', }) const credentialRequestMessage = new V1RequestCredentialMessage({ comment: 'abcd', @@ -137,7 +131,7 @@ const didCommMessageRecord = new DidCommMessageRecord({ }) // eslint-disable-next-line @typescript-eslint/no-explicit-any -const getAgentMessageMock = async (agentContext: AgentContext, options: GetAgentMessageOptions) => { +const getAgentMessageMock = async (agentContext: AgentContext, options: { messageClass: any }) => { if (options.messageClass === V1ProposeCredentialMessage) { return credentialProposalMessage } @@ -158,35 +152,29 @@ const getAgentMessageMock = async (agentContext: AgentContext, options: GetAgent // object to test our service would behave correctly. We use type assertion for `offer` attribute to `any`. const mockCredentialRecord = ({ state, - metadata, threadId, connectionId, tags, id, credentialAttributes, - indyRevocationRegistryId, - indyCredentialRevocationId, }: { state?: CredentialState - metadata?: IndyCredentialViewMetadata & { indyRequest: Record } tags?: CustomCredentialTags threadId?: string connectionId?: string credentialId?: string id?: string credentialAttributes?: CredentialPreviewAttribute[] - indyRevocationRegistryId?: string - indyCredentialRevocationId?: string } = {}) => { const credentialRecord = new CredentialExchangeRecord({ id, credentialAttributes: credentialAttributes || credentialPreview.attributes, state: state || CredentialState.OfferSent, - threadId: threadId ?? uuid(), + threadId: threadId ?? '809dd7ec-f0e7-4b97-9231-7a3615af6139', connectionId: connectionId ?? '123', credentials: [ { - credentialRecordType: 'indy', + credentialRecordType: 'anoncreds', credentialRecordId: '123456', }, ], @@ -194,29 +182,6 @@ const mockCredentialRecord = ({ protocolVersion: 'v1', }) - if (metadata?.indyRequest) { - credentialRecord.metadata.set(CredentialMetadataKeys.IndyRequest, { ...metadata.indyRequest }) - } - - if (metadata?.schemaId) { - credentialRecord.metadata.add(CredentialMetadataKeys.IndyCredential, { - schemaId: metadata.schemaId, - }) - } - - if (metadata?.credentialDefinitionId) { - credentialRecord.metadata.add(CredentialMetadataKeys.IndyCredential, { - credentialDefinitionId: metadata.credentialDefinitionId, - }) - } - - if (indyCredentialRevocationId || indyRevocationRegistryId) { - credentialRecord.metadata.add(CredentialMetadataKeys.IndyCredential, { - indyCredentialRevocationId, - indyRevocationRegistryId, - }) - } - return credentialRecord } @@ -235,10 +200,8 @@ describe('V1CredentialProtocol', () => { registerInstances: [ [CredentialRepository, credentialRepository], [DidCommMessageRepository, didCommMessageRepository], - [RoutingService, routingService], - [Dispatcher, dispatcher], - [ConnectionService, connectionService], [EventEmitter, eventEmitter], + [ConnectionService, connectionService], ], agentConfig, }) @@ -253,7 +216,7 @@ describe('V1CredentialProtocol', () => { didCommMessageRecord, ]) - credentialProtocol = new V1CredentialProtocol({ indyCredentialFormat: indyCredentialFormatService }) + credentialProtocol = new V1CredentialProtocol({ indyCredentialFormat: legacyIndyCredentialFormatService }) }) afterEach(() => { @@ -269,18 +232,12 @@ describe('V1CredentialProtocol', () => { connectionId: 'b1e2f039-aa39-40be-8643-6ce2797b5190', }) - const credentialFormats = { - indy: { - holderDid: 'did:sov:123456789abcdefghi', - }, - } - // mock resolved format call - mockFunction(indyCredentialFormatService.acceptOffer).mockResolvedValue({ + mockFunction(legacyIndyCredentialFormatService.acceptOffer).mockResolvedValue({ attachment: requestAttachment, format: new CredentialFormatSpec({ format: 'indy', - attachId: INDY_CREDENTIAL_REQUEST_ATTACHMENT_ID, + attachmentId: INDY_CREDENTIAL_REQUEST_ATTACHMENT_ID, }), }) @@ -289,7 +246,6 @@ describe('V1CredentialProtocol', () => { comment: 'hello', autoAcceptCredential: AutoAcceptCredential.Never, credentialRecord, - credentialFormats, }) // then @@ -308,15 +264,10 @@ describe('V1CredentialProtocol', () => { 'requests~attach': [JsonTransformer.toJSON(requestAttachment)], }) expect(credentialRepository.update).toHaveBeenCalledTimes(1) - expect(indyCredentialFormatService.acceptOffer).toHaveBeenCalledWith(agentContext, { + expect(legacyIndyCredentialFormatService.acceptOffer).toHaveBeenCalledWith(agentContext, { credentialRecord, - attachId: INDY_CREDENTIAL_REQUEST_ATTACHMENT_ID, + attachmentId: INDY_CREDENTIAL_REQUEST_ATTACHMENT_ID, offerAttachment, - credentialFormats: { - indy: { - holderDid: 'did:sov:123456789abcdefghi', - }, - }, }) expect(didCommMessageRepository.saveOrUpdateAgentMessage).toHaveBeenCalledWith(agentContext, { agentMessage: message, @@ -333,11 +284,11 @@ describe('V1CredentialProtocol', () => { const updateStateSpy = jest.spyOn(credentialProtocol, 'updateState') // mock resolved format call - mockFunction(indyCredentialFormatService.acceptOffer).mockResolvedValue({ + mockFunction(legacyIndyCredentialFormatService.acceptOffer).mockResolvedValue({ attachment: requestAttachment, format: new CredentialFormatSpec({ format: 'indy', - attachId: INDY_CREDENTIAL_REQUEST_ATTACHMENT_ID, + attachmentId: INDY_CREDENTIAL_REQUEST_ATTACHMENT_ID, }), }) @@ -439,11 +390,11 @@ describe('V1CredentialProtocol', () => { connectionId: 'b1e2f039-aa39-40be-8643-6ce2797b5190', }) - mockFunction(indyCredentialFormatService.acceptRequest).mockResolvedValue({ + mockFunction(legacyIndyCredentialFormatService.acceptRequest).mockResolvedValue({ attachment: credentialAttachment, format: new CredentialFormatSpec({ format: 'the-format', - attachId: 'the-attach-id', + attachmentId: 'the-attach-id', }), }) @@ -469,11 +420,11 @@ describe('V1CredentialProtocol', () => { mockFunction(credentialRepository.getById).mockResolvedValue(credentialRecord) - mockFunction(indyCredentialFormatService.acceptRequest).mockResolvedValue({ + mockFunction(legacyIndyCredentialFormatService.acceptRequest).mockResolvedValue({ attachment: credentialAttachment, format: new CredentialFormatSpec({ format: 'the-format', - attachId: 'the-attach-id', + attachmentId: 'the-attach-id', }), }) @@ -509,11 +460,11 @@ describe('V1CredentialProtocol', () => { mockFunction(credentialRepository.getById).mockResolvedValue(credentialRecord) const comment = 'credential response comment' - mockFunction(indyCredentialFormatService.acceptRequest).mockResolvedValue({ + mockFunction(legacyIndyCredentialFormatService.acceptRequest).mockResolvedValue({ attachment: credentialAttachment, format: new CredentialFormatSpec({ format: 'the-format', - attachId: 'the-attach-id', + attachmentId: 'the-attach-id', }), }) @@ -532,11 +483,11 @@ describe('V1CredentialProtocol', () => { '~please_ack': expect.any(Object), }) - expect(indyCredentialFormatService.acceptRequest).toHaveBeenCalledWith(agentContext, { + expect(legacyIndyCredentialFormatService.acceptRequest).toHaveBeenCalledWith(agentContext, { credentialRecord, requestAttachment, offerAttachment, - attachId: INDY_CREDENTIAL_ATTACHMENT_ID, + attachmentId: INDY_CREDENTIAL_ATTACHMENT_ID, }) }) }) @@ -571,7 +522,7 @@ describe('V1CredentialProtocol', () => { associatedRecordId: credentialRecord.id, }) - expect(indyCredentialFormatService.processCredential).toHaveBeenNthCalledWith(1, agentContext, { + expect(legacyIndyCredentialFormatService.processCredential).toHaveBeenNthCalledWith(1, agentContext, { attachment: credentialAttachment, credentialRecord, requestAttachment: expect.any(V1Attachment), @@ -708,7 +659,6 @@ describe('V1CredentialProtocol', () => { describe('createProblemReport', () => { const threadId = 'fd9c5ddb-ec11-4acd-bc32-540736249746' - const message = 'Indy error' let credential: CredentialExchangeRecord beforeEach(() => { @@ -719,16 +669,19 @@ describe('V1CredentialProtocol', () => { }) }) - test('returns problem report message base once get error', () => { + test('returns problem report message base once get error', async () => { // given mockFunction(credentialRepository.getById).mockReturnValue(Promise.resolve(credential)) // when - const credentialProblemReportMessage = credentialProtocol.createProblemReport(agentContext, { message }) + const { message } = await credentialProtocol.createProblemReport(agentContext, { + description: 'Indy error', + credentialRecord: credential, + }) - credentialProblemReportMessage.setThread({ threadId }) + message.setThread({ threadId }) // then - expect(credentialProblemReportMessage.toJSON()).toMatchObject({ + expect(message.toJSON()).toMatchObject({ '@id': expect.any(String), '@type': 'https://didcomm.org/issue-credential/1.0/problem-report', '~thread': { @@ -736,7 +689,7 @@ describe('V1CredentialProtocol', () => { }, description: { code: CredentialProblemReportReason.IssuanceAbandoned, - en: message, + en: 'Indy error', }, }) }) @@ -848,7 +801,7 @@ describe('V1CredentialProtocol', () => { }) it('should call deleteCredentialById in indyCredentialFormatService if deleteAssociatedCredential is true', async () => { - const deleteCredentialMock = mockFunction(indyCredentialFormatService.deleteCredentialById) + const deleteCredentialMock = mockFunction(legacyIndyCredentialFormatService.deleteCredentialById) const credentialRecord = mockCredentialRecord() mockFunction(credentialRepository.getById).mockResolvedValue(credentialRecord) @@ -866,7 +819,7 @@ describe('V1CredentialProtocol', () => { }) it('should not call deleteCredentialById in indyCredentialFormatService if deleteAssociatedCredential is false', async () => { - const deleteCredentialMock = mockFunction(indyCredentialFormatService.deleteCredentialById) + const deleteCredentialMock = mockFunction(legacyIndyCredentialFormatService.deleteCredentialById) const credentialRecord = mockCredentialRecord() mockFunction(credentialRepository.getById).mockResolvedValue(credentialRecord) @@ -880,7 +833,7 @@ describe('V1CredentialProtocol', () => { }) it('deleteAssociatedCredentials should default to true', async () => { - const deleteCredentialMock = mockFunction(indyCredentialFormatService.deleteCredentialById) + const deleteCredentialMock = mockFunction(legacyIndyCredentialFormatService.deleteCredentialById) const credentialRecord = mockCredentialRecord() mockFunction(credentialRepository.getById).mockResolvedValue(credentialRecord) @@ -894,7 +847,7 @@ describe('V1CredentialProtocol', () => { ) }) it('deleteAssociatedDidCommMessages should default to true', async () => { - const deleteCredentialMock = mockFunction(indyCredentialFormatService.deleteCredentialById) + const deleteCredentialMock = mockFunction(legacyIndyCredentialFormatService.deleteCredentialById) const credentialRecord = mockCredentialRecord() mockFunction(credentialRepository.getById).mockResolvedValue(credentialRecord) @@ -909,74 +862,4 @@ describe('V1CredentialProtocol', () => { expect(didCommMessageRepository.delete).toHaveBeenCalledTimes(3) }) }) - - describe('declineOffer', () => { - const threadId = 'fd9c5ddb-ec11-4acd-bc32-540736249754' - let credential: CredentialExchangeRecord - - beforeEach(() => { - credential = mockCredentialRecord({ - state: CredentialState.OfferReceived, - tags: { threadId }, - }) - }) - - test(`updates state to ${CredentialState.Declined}`, async () => { - // given - const repositoryUpdateSpy = jest.spyOn(credentialRepository, 'update') - - // when - await credentialProtocol.declineOffer(agentContext, credential) - - // then - const expectedCredentialState = { - state: CredentialState.Declined, - } - expect(repositoryUpdateSpy).toHaveBeenCalledTimes(1) - expect(repositoryUpdateSpy).toHaveBeenNthCalledWith( - 1, - agentContext, - expect.objectContaining(expectedCredentialState) - ) - }) - - test(`emits stateChange event from ${CredentialState.OfferReceived} to ${CredentialState.Declined}`, async () => { - const eventListenerMock = jest.fn() - eventEmitter.on(CredentialEventTypes.CredentialStateChanged, eventListenerMock) - - // given - mockFunction(credentialRepository.getSingleByQuery).mockReturnValue(Promise.resolve(credential)) - - // when - await credentialProtocol.declineOffer(agentContext, credential) - - // then - expect(eventListenerMock).toHaveBeenCalledTimes(1) - const [[event]] = eventListenerMock.mock.calls - expect(event).toMatchObject({ - type: 'CredentialStateChanged', - metadata: { - contextCorrelationId: 'mock', - }, - payload: { - previousState: CredentialState.OfferReceived, - credentialRecord: expect.objectContaining({ - state: CredentialState.Declined, - }), - }, - }) - }) - - const validState = CredentialState.OfferReceived - const invalidCredentialStates = Object.values(CredentialState).filter((state) => state !== validState) - test(`throws an error when state transition is invalid`, async () => { - await Promise.all( - invalidCredentialStates.map(async (state) => { - await expect( - credentialProtocol.declineOffer(agentContext, mockCredentialRecord({ state, tags: { threadId } })) - ).rejects.toThrowError(`Credential record is in invalid state ${state}. Valid states are: ${validState}.`) - }) - ) - }) - }) }) diff --git a/packages/core/src/modules/credentials/protocol/v1/__tests__/V1CredentialProtocolProposeOffer.test.ts b/packages/anoncreds/src/protocols/credentials/v1/__tests__/V1CredentialProtocolProposeOffer.test.ts similarity index 76% rename from packages/core/src/modules/credentials/protocol/v1/__tests__/V1CredentialProtocolProposeOffer.test.ts rename to packages/anoncreds/src/protocols/credentials/v1/__tests__/V1CredentialProtocolProposeOffer.test.ts index 523f84051d..836be6371c 100644 --- a/packages/core/src/modules/credentials/protocol/v1/__tests__/V1CredentialProtocolProposeOffer.test.ts +++ b/packages/anoncreds/src/protocols/credentials/v1/__tests__/V1CredentialProtocolProposeOffer.test.ts @@ -1,55 +1,44 @@ -import type { CredentialStateChangedEvent } from '../../../CredentialEvents' -import type { CreateOfferOptions, CreateProposalOptions } from '../../../CredentialProtocolOptions' - +import type { CredentialProtocolOptions, CredentialStateChangedEvent } from '@aries-framework/core' + +import { + EventEmitter, + DidExchangeState, + V1Attachment, + V1AttachmentData, + CredentialState, + CredentialFormatSpec, + CredentialExchangeRecord, + CredentialEventTypes, + JsonTransformer, + InboundMessageContext, +} from '@aries-framework/core' import { Subject } from 'rxjs' -import { getAgentConfig, getAgentContext, getMockConnection, mockFunction } from '../../../../../../tests/helpers' -import { Dispatcher } from '../../../../../agent/Dispatcher' -import { EventEmitter } from '../../../../../agent/EventEmitter' -import { InboundMessageContext } from '../../../../../agent/models/InboundMessageContext' -import { V1Attachment, V1AttachmentData } from '../../../../../decorators/attachment/V1Attachment' -import { DidCommMessageRepository } from '../../../../../storage' -import { JsonTransformer } from '../../../../../utils' -import { DidExchangeState } from '../../../../connections' -import { ConnectionService } from '../../../../connections/services/ConnectionService' -import { IndyLedgerService } from '../../../../ledger/services' -import { RoutingService } from '../../../../routing/services/RoutingService' -import { CredentialEventTypes } from '../../../CredentialEvents' -import { schema, credDef } from '../../../__tests__/fixtures' -import { IndyCredentialFormatService } from '../../../formats' -import { CredentialFormatSpec } from '../../../models' -import { CredentialState } from '../../../models/CredentialState' -import { CredentialExchangeRecord } from '../../../repository/CredentialExchangeRecord' -import { CredentialRepository } from '../../../repository/CredentialRepository' +import { ConnectionService } from '../../../../../../core/src/modules/connections/services/ConnectionService' +import { CredentialRepository } from '../../../../../../core/src/modules/credentials/repository/CredentialRepository' +import { DidCommMessageRepository } from '../../../../../../core/src/storage/didcomm/DidCommMessageRepository' +import { getAgentConfig, getAgentContext, getMockConnection, mockFunction } from '../../../../../../core/tests/helpers' +import { LegacyIndyCredentialFormatService } from '../../../../formats/LegacyIndyCredentialFormatService' import { V1CredentialProtocol } from '../V1CredentialProtocol' -import { INDY_CREDENTIAL_OFFER_ATTACHMENT_ID, V1OfferCredentialMessage } from '../messages' -import { V1CredentialPreview } from '../messages/V1CredentialPreview' +import { V1CredentialPreview, INDY_CREDENTIAL_OFFER_ATTACHMENT_ID, V1OfferCredentialMessage } from '../messages' // Mock classes -jest.mock('../../../repository/CredentialRepository') -jest.mock('../../../../ledger/services/IndyLedgerService') -jest.mock('../../../formats/indy/IndyCredentialFormatService') -jest.mock('../../../../../storage/didcomm/DidCommMessageRepository') -jest.mock('../../../../routing/services/RoutingService') -jest.mock('../../../../connections/services/ConnectionService') -jest.mock('../../../../../agent/Dispatcher') +jest.mock('../../../../../../core/src/modules/credentials/repository/CredentialRepository') +jest.mock('../../../../formats/LegacyIndyCredentialFormatService') +jest.mock('../../../../../../core/src/storage/didcomm/DidCommMessageRepository') +jest.mock('../../../../../../core/src/modules/connections/services/ConnectionService') // Mock typed object const CredentialRepositoryMock = CredentialRepository as jest.Mock -const IndyLedgerServiceMock = IndyLedgerService as jest.Mock -const IndyCredentialFormatServiceMock = IndyCredentialFormatService as jest.Mock const DidCommMessageRepositoryMock = DidCommMessageRepository as jest.Mock -const RoutingServiceMock = RoutingService as jest.Mock const ConnectionServiceMock = ConnectionService as jest.Mock -const DispatcherMock = Dispatcher as jest.Mock +const LegacyIndyCredentialFormatServiceMock = + LegacyIndyCredentialFormatService as jest.Mock const credentialRepository = new CredentialRepositoryMock() const didCommMessageRepository = new DidCommMessageRepositoryMock() -const routingService = new RoutingServiceMock() -const indyLedgerService = new IndyLedgerServiceMock() -const indyCredentialFormatService = new IndyCredentialFormatServiceMock() -const dispatcher = new DispatcherMock() const connectionService = new ConnectionServiceMock() +const indyCredentialFormatService = new LegacyIndyCredentialFormatServiceMock() const agentConfig = getAgentConfig('V1CredentialProtocolProposeOfferTest') const eventEmitter = new EventEmitter(agentConfig.agentDependencies, new Subject()) @@ -58,8 +47,6 @@ const agentContext = getAgentContext({ registerInstances: [ [CredentialRepository, credentialRepository], [DidCommMessageRepository, didCommMessageRepository], - [RoutingService, routingService], - [Dispatcher, dispatcher], [ConnectionService, connectionService], [EventEmitter, eventEmitter], ], @@ -68,9 +55,9 @@ const agentContext = getAgentContext({ // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore -indyCredentialFormatService.credentialRecordType = 'indy' +indyCredentialFormatService.credentialRecordType = 'anoncreds' -const connection = getMockConnection({ +const connectionRecord = getMockConnection({ id: '123', state: DidExchangeState.Completed, }) @@ -107,9 +94,7 @@ describe('V1CredentialProtocolProposeOffer', () => { beforeEach(async () => { // mock function implementations - mockFunction(connectionService.getById).mockResolvedValue(connection) - mockFunction(indyLedgerService.getCredentialDefinition).mockResolvedValue(credDef) - mockFunction(indyLedgerService.getSchema).mockResolvedValue(schema) + mockFunction(connectionService.getById).mockResolvedValue(connectionRecord) credentialProtocol = new V1CredentialProtocol({ indyCredentialFormat: indyCredentialFormatService, @@ -121,8 +106,10 @@ describe('V1CredentialProtocolProposeOffer', () => { }) describe('createProposal', () => { - const proposeOptions: CreateProposalOptions<[IndyCredentialFormatService]> = { - connection, + const proposeOptions: CredentialProtocolOptions.CreateCredentialProposalOptions< + [LegacyIndyCredentialFormatService] + > = { + connectionRecord: connectionRecord, credentialFormats: { indy: { credentialDefinitionId: 'Th7MpTaRZVRYnPiabds81Y:3:CL:17:TAG', @@ -136,6 +123,7 @@ describe('V1CredentialProtocolProposeOffer', () => { }, comment: 'v1 propose credential test', } + test(`creates credential record in ${CredentialState.OfferSent} state with offer, thread id`, async () => { const repositorySaveSpy = jest.spyOn(credentialRepository, 'save') @@ -143,7 +131,7 @@ describe('V1CredentialProtocolProposeOffer', () => { attachment: proposalAttachment, format: new CredentialFormatSpec({ format: 'indy', - attachId: 'indy-proposal', + attachmentId: 'indy-proposal', }), }) @@ -157,7 +145,7 @@ describe('V1CredentialProtocolProposeOffer', () => { type: CredentialExchangeRecord.type, id: expect.any(String), createdAt: expect.any(Date), - connectionId: connection.id, + connectionId: connectionRecord.id, state: CredentialState.ProposalSent, }) ) @@ -171,7 +159,7 @@ describe('V1CredentialProtocolProposeOffer', () => { attachment: proposalAttachment, format: new CredentialFormatSpec({ format: 'indy', - attachId: 'indy-proposal', + attachmentId: 'indy-proposal', }), }) @@ -196,7 +184,7 @@ describe('V1CredentialProtocolProposeOffer', () => { attachment: proposalAttachment, format: new CredentialFormatSpec({ format: 'indy', - attachId: 'indy-proposal', + attachmentId: 'indy-proposal', }), previewAttributes: credentialPreview.attributes, }) @@ -233,9 +221,9 @@ describe('V1CredentialProtocolProposeOffer', () => { }) describe('createOffer', () => { - const offerOptions: CreateOfferOptions<[IndyCredentialFormatService]> = { + const offerOptions: CredentialProtocolOptions.CreateCredentialOfferOptions<[LegacyIndyCredentialFormatService]> = { comment: 'some comment', - connection, + connectionRecord, credentialFormats: { indy: { attributes: credentialPreview.attributes, @@ -249,7 +237,7 @@ describe('V1CredentialProtocolProposeOffer', () => { attachment: offerAttachment, format: new CredentialFormatSpec({ format: 'indy', - attachId: 'indy-offer', + attachmentId: 'indy-offer', }), previewAttributes: credentialPreview.attributes, }) @@ -267,7 +255,7 @@ describe('V1CredentialProtocolProposeOffer', () => { id: expect.any(String), createdAt: expect.any(Date), threadId: createdCredentialRecord.threadId, - connectionId: connection.id, + connectionId: connectionRecord.id, state: CredentialState.OfferSent, }) }) @@ -280,7 +268,7 @@ describe('V1CredentialProtocolProposeOffer', () => { attachment: offerAttachment, format: new CredentialFormatSpec({ format: 'indy', - attachId: 'indy-offer', + attachmentId: 'indy-offer', }), previewAttributes: credentialPreview.attributes, }) @@ -306,7 +294,7 @@ describe('V1CredentialProtocolProposeOffer', () => { attachment: offerAttachment, format: new CredentialFormatSpec({ format: 'indy', - attachId: 'indy-offer', + attachmentId: 'indy-offer', }), }) @@ -320,7 +308,7 @@ describe('V1CredentialProtocolProposeOffer', () => { attachment: offerAttachment, format: new CredentialFormatSpec({ format: 'indy', - attachId: 'indy-offer', + attachmentId: 'indy-offer', }), previewAttributes: credentialPreview.attributes, }) @@ -356,7 +344,10 @@ describe('V1CredentialProtocolProposeOffer', () => { credentialPreview: credentialPreview, offerAttachments: [offerAttachment], }) - const messageContext = new InboundMessageContext(credentialOfferMessage, { agentContext, connection }) + const messageContext = new InboundMessageContext(credentialOfferMessage, { + agentContext, + connection: connectionRecord, + }) test(`creates and return credential record in ${CredentialState.OfferReceived} state with offer, thread ID`, async () => { // when @@ -371,7 +362,7 @@ describe('V1CredentialProtocolProposeOffer', () => { id: expect.any(String), createdAt: expect.any(Date), threadId: credentialOfferMessage.id, - connectionId: connection.id, + connectionId: connectionRecord.id, state: CredentialState.OfferReceived, credentialAttributes: undefined, }) diff --git a/packages/core/src/modules/credentials/protocol/v1/__tests__/v1-connectionless-credentials.e2e.test.ts b/packages/anoncreds/src/protocols/credentials/v1/__tests__/v1-connectionless-credentials.e2e.test.ts similarity index 66% rename from packages/core/src/modules/credentials/protocol/v1/__tests__/v1-connectionless-credentials.e2e.test.ts rename to packages/anoncreds/src/protocols/credentials/v1/__tests__/v1-connectionless-credentials.e2e.test.ts index a3fff6612e..5825f0610c 100644 --- a/packages/core/src/modules/credentials/protocol/v1/__tests__/v1-connectionless-credentials.e2e.test.ts +++ b/packages/anoncreds/src/protocols/credentials/v1/__tests__/v1-connectionless-credentials.e2e.test.ts @@ -1,27 +1,12 @@ -import type { SubjectMessage } from '../../../../../../../../tests/transport/SubjectInboundTransport' -import type { CredentialStateChangedEvent } from '../../../CredentialEvents' -import type { AcceptCredentialOfferOptions, AcceptCredentialRequestOptions } from '../../../CredentialsApiOptions' - -import { ReplaySubject, Subject } from 'rxjs' - -import { SubjectInboundTransport } from '../../../../../../../../tests/transport/SubjectInboundTransport' -import { SubjectOutboundTransport } from '../../../../../../../../tests/transport/SubjectOutboundTransport' -import { prepareForIssuance, waitForCredentialRecordSubject, getAgentOptions } from '../../../../../../tests/helpers' -import testLogger from '../../../../../../tests/logger' -import { Agent } from '../../../../../agent/Agent' -import { CredentialEventTypes } from '../../../CredentialEvents' -import { AutoAcceptCredential } from '../../../models/CredentialAutoAcceptType' -import { CredentialState } from '../../../models/CredentialState' -import { CredentialExchangeRecord } from '../../../repository/CredentialExchangeRecord' -import { V1CredentialPreview } from '../messages/V1CredentialPreview' - -const faberAgentOptions = getAgentOptions('Faber connection-less Credentials V1', { - endpoints: ['rxjs:faber'], -}) +import type { EventReplaySubject } from '../../../../../../core/tests' +import type { AnonCredsTestsAgent } from '../../../../../tests/legacyAnonCredsSetup' +import type { AcceptCredentialOfferOptions, AcceptCredentialRequestOptions } from '@aries-framework/core' -const aliceAgentOptions = getAgentOptions('Alice connection-less Credentials V1', { - endpoints: ['rxjs:alice'], -}) +import { AutoAcceptCredential, CredentialExchangeRecord, CredentialState } from '@aries-framework/core' + +import { waitForCredentialRecordSubject, testLogger } from '../../../../../../core/tests' +import { setupAnonCredsTests } from '../../../../../tests/legacyAnonCredsSetup' +import { V1CredentialPreview } from '../messages' const credentialPreview = V1CredentialPreview.fromRecord({ name: 'John', @@ -29,42 +14,27 @@ const credentialPreview = V1CredentialPreview.fromRecord({ }) describe('V1 Connectionless Credentials', () => { - let faberAgent: Agent - let aliceAgent: Agent - let faberReplay: ReplaySubject - let aliceReplay: ReplaySubject + let faberAgent: AnonCredsTestsAgent + let aliceAgent: AnonCredsTestsAgent + let faberReplay: EventReplaySubject + let aliceReplay: EventReplaySubject let credentialDefinitionId: string + let schemaId: string beforeEach(async () => { - const faberMessages = new Subject() - const aliceMessages = new Subject() - - const subjectMap = { - 'rxjs:faber': faberMessages, - 'rxjs:alice': aliceMessages, - } - faberAgent = new Agent(faberAgentOptions) - faberAgent.registerInboundTransport(new SubjectInboundTransport(faberMessages)) - faberAgent.registerOutboundTransport(new SubjectOutboundTransport(subjectMap)) - await faberAgent.initialize() - - aliceAgent = new Agent(aliceAgentOptions) - aliceAgent.registerInboundTransport(new SubjectInboundTransport(aliceMessages)) - aliceAgent.registerOutboundTransport(new SubjectOutboundTransport(subjectMap)) - await aliceAgent.initialize() - - const { definition } = await prepareForIssuance(faberAgent, ['name', 'age']) - credentialDefinitionId = definition.id - - faberReplay = new ReplaySubject() - aliceReplay = new ReplaySubject() - - faberAgent.events - .observable(CredentialEventTypes.CredentialStateChanged) - .subscribe(faberReplay) - aliceAgent.events - .observable(CredentialEventTypes.CredentialStateChanged) - .subscribe(aliceReplay) + ;({ + issuerAgent: faberAgent, + issuerReplay: faberReplay, + holderAgent: aliceAgent, + holderReplay: aliceReplay, + credentialDefinitionId, + schemaId, + } = await setupAnonCredsTests({ + issuerName: 'Faber connection-less Credentials V1', + holderName: 'Alice connection-less Credentials V1', + attributeNames: ['name', 'age'], + createConnections: false, + })) }) afterEach(async () => { @@ -144,14 +114,15 @@ describe('V1 Connectionless Credentials', () => { createdAt: expect.any(Date), metadata: { data: { - '_internal/indyCredential': { + '_anoncreds/credential': { + schemaId, credentialDefinitionId, }, }, }, credentials: [ { - credentialRecordType: 'indy', + credentialRecordType: 'anoncreds', credentialRecordId: expect.any(String), }, ], @@ -165,7 +136,8 @@ describe('V1 Connectionless Credentials', () => { createdAt: expect.any(Date), metadata: { data: { - '_internal/indyCredential': { + '_anoncreds/credential': { + schemaId, credentialDefinitionId, }, }, @@ -225,14 +197,15 @@ describe('V1 Connectionless Credentials', () => { createdAt: expect.any(Date), metadata: { data: { - '_internal/indyCredential': { + '_anoncreds/credential': { + schemaId, credentialDefinitionId: credentialDefinitionId, }, }, }, credentials: [ { - credentialRecordType: 'indy', + credentialRecordType: 'anoncreds', credentialRecordId: expect.any(String), }, ], diff --git a/packages/core/src/modules/credentials/protocol/v1/__tests__/v1-credentials-auto-accept.e2e.test.ts b/packages/anoncreds/src/protocols/credentials/v1/__tests__/v1-credentials-auto-accept.e2e.test.ts similarity index 58% rename from packages/core/src/modules/credentials/protocol/v1/__tests__/v1-credentials-auto-accept.e2e.test.ts rename to packages/anoncreds/src/protocols/credentials/v1/__tests__/v1-credentials-auto-accept.e2e.test.ts index 42da4ed4da..852d0cc116 100644 --- a/packages/core/src/modules/credentials/protocol/v1/__tests__/v1-credentials-auto-accept.e2e.test.ts +++ b/packages/anoncreds/src/protocols/credentials/v1/__tests__/v1-credentials-auto-accept.e2e.test.ts @@ -1,44 +1,52 @@ -import type { Agent } from '../../../../../agent/Agent' -import type { ConnectionRecord } from '../../../../connections' -import type { AcceptCredentialOfferOptions, AcceptCredentialProposalOptions } from '../../../CredentialsApiOptions' -import type { Schema } from 'indy-sdk' - -import { setupCredentialTests, waitForCredentialRecord } from '../../../../../../tests/helpers' -import testLogger from '../../../../../../tests/logger' -import { AriesFrameworkError } from '../../../../../error/AriesFrameworkError' -import { JsonTransformer } from '../../../../../utils/JsonTransformer' -import { AutoAcceptCredential } from '../../../models/CredentialAutoAcceptType' -import { CredentialState } from '../../../models/CredentialState' -import { CredentialExchangeRecord } from '../../../repository/CredentialExchangeRecord' -import { V1CredentialPreview } from '../messages/V1CredentialPreview' - -describe('credentials', () => { - let faberAgent: Agent - let aliceAgent: Agent - let credDefId: string - let schema: Schema - let faberConnection: ConnectionRecord - let aliceConnection: ConnectionRecord - const credentialPreview = V1CredentialPreview.fromRecord({ - name: 'John', - age: '99', - 'x-ray': 'some x-ray', - profile_picture: 'profile picture', - }) - const newCredentialPreview = V1CredentialPreview.fromRecord({ - name: 'John', - age: '99', - 'x-ray': 'another x-ray value', - profile_picture: 'another profile picture', - }) +import type { EventReplaySubject } from '../../../../../../core/tests' +import type { AnonCredsTestsAgent } from '../../../../../tests/legacyAnonCredsSetup' + +import { AutoAcceptCredential, CredentialState, CredentialExchangeRecord, JsonTransformer } from '@aries-framework/core' + +import { waitForCredentialRecord, waitForCredentialRecordSubject, testLogger } from '../../../../../../core/tests' +import { setupAnonCredsTests } from '../../../../../tests/legacyAnonCredsSetup' +import { V1CredentialPreview } from '../messages' + +const credentialPreview = V1CredentialPreview.fromRecord({ + name: 'John', + age: '99', + 'x-ray': 'some x-ray', + profile_picture: 'profile picture', +}) +const newCredentialPreview = V1CredentialPreview.fromRecord({ + name: 'John', + age: '99', + 'x-ray': 'another x-ray value', + profile_picture: 'another profile picture', +}) - describe('Auto accept on `always`', () => { +describe('V1 Credentials Auto Accept', () => { + let faberAgent: AnonCredsTestsAgent + let faberReplay: EventReplaySubject + let aliceAgent: AnonCredsTestsAgent + let aliceReplay: EventReplaySubject + let credentialDefinitionId: string + let schemaId: string + let faberConnectionId: string + let aliceConnectionId: string + + describe("Auto accept on 'always'", () => { beforeAll(async () => { - ;({ faberAgent, aliceAgent, credDefId, schema, faberConnection, aliceConnection } = await setupCredentialTests( - 'faber agent: always v1', - 'alice agent: always v1', - AutoAcceptCredential.Always - )) + ;({ + issuerAgent: faberAgent, + issuerReplay: faberReplay, + holderAgent: aliceAgent, + holderReplay: aliceReplay, + credentialDefinitionId, + schemaId, + issuerHolderConnectionId: faberConnectionId, + holderIssuerConnectionId: aliceConnectionId, + } = await setupAnonCredsTests({ + issuerName: 'Faber Credentials Auto Accept V1', + holderName: 'Alice Credentials Auto Accept V1', + attributeNames: ['name', 'age', 'x-ray', 'profile_picture'], + autoAcceptCredentials: AutoAcceptCredential.Always, + })) }) afterAll(async () => { @@ -48,16 +56,16 @@ describe('credentials', () => { await aliceAgent.wallet.delete() }) - test('Alice starts with V1 credential proposal to Faber, both with autoAcceptCredential on `always`', async () => { + test("Alice starts with V1 credential proposal to Faber, both with autoAcceptCredential on 'always'", async () => { testLogger.test('Alice sends credential proposal to Faber') const aliceCredentialExchangeRecord = await aliceAgent.credentials.proposeCredential({ - connectionId: aliceConnection.id, + connectionId: aliceConnectionId, protocolVersion: 'v1', credentialFormats: { indy: { attributes: credentialPreview.attributes, - credentialDefinitionId: credDefId, + credentialDefinitionId: credentialDefinitionId, }, }, comment: 'v1 propose credential test', @@ -81,9 +89,9 @@ describe('credentials', () => { createdAt: expect.any(Date), metadata: { data: { - '_internal/indyCredential': { - schemaId: schema.id, - credentialDefinitionId: credDefId, + '_anoncreds/credential': { + schemaId: schemaId, + credentialDefinitionId: credentialDefinitionId, }, }, }, @@ -91,27 +99,26 @@ describe('credentials', () => { }) }) - test('Faber starts with V1 credential offer to Alice, both with autoAcceptCredential on `always`', async () => { + test("Faber starts with V1 credential offer to Alice, both with autoAcceptCredential on 'always'", async () => { testLogger.test('Faber sends credential offer to Alice') - const schemaId = schema.id const faberCredentialExchangeRecord = await faberAgent.credentials.offerCredential({ comment: 'some comment about credential', - connectionId: faberConnection.id, + connectionId: faberConnectionId, credentialFormats: { indy: { attributes: credentialPreview.attributes, - credentialDefinitionId: credDefId, + credentialDefinitionId: credentialDefinitionId, }, }, protocolVersion: 'v1', }) testLogger.test('Alice waits for credential from Faber') - const aliceCredentialRecord = await waitForCredentialRecord(aliceAgent, { + const aliceCredentialRecord = await waitForCredentialRecordSubject(aliceReplay, { threadId: faberCredentialExchangeRecord.threadId, state: CredentialState.CredentialReceived, }) testLogger.test('Faber waits for credential ack from Alice') - const faberCredentialRecord: CredentialExchangeRecord = await waitForCredentialRecord(faberAgent, { + const faberCredentialRecord = await waitForCredentialRecordSubject(faberReplay, { threadId: faberCredentialExchangeRecord.threadId, state: CredentialState.Done, }) @@ -121,16 +128,16 @@ describe('credentials', () => { createdAt: expect.any(Date), metadata: { data: { - '_internal/indyRequest': expect.any(Object), - '_internal/indyCredential': { + '_anoncreds/credentialRequest': expect.any(Object), + '_anoncreds/credential': { schemaId, - credentialDefinitionId: credDefId, + credentialDefinitionId, }, }, }, credentials: [ { - credentialRecordType: 'indy', + credentialRecordType: 'anoncreds', credentialRecordId: expect.any(String), }, ], @@ -145,13 +152,23 @@ describe('credentials', () => { }) }) - describe('Auto accept on `contentApproved`', () => { + describe("Auto accept on 'contentApproved'", () => { beforeAll(async () => { - ;({ faberAgent, aliceAgent, credDefId, schema, faberConnection, aliceConnection } = await setupCredentialTests( - 'faber agent: contentApproved v1', - 'alice agent: contentApproved v1', - AutoAcceptCredential.ContentApproved - )) + ;({ + issuerAgent: faberAgent, + issuerReplay: faberReplay, + holderAgent: aliceAgent, + holderReplay: aliceReplay, + credentialDefinitionId, + schemaId, + issuerHolderConnectionId: faberConnectionId, + holderIssuerConnectionId: aliceConnectionId, + } = await setupAnonCredsTests({ + issuerName: 'faber agent: contentApproved v1', + holderName: 'alice agent: contentApproved v1', + attributeNames: ['name', 'age', 'x-ray', 'profile_picture'], + autoAcceptCredentials: AutoAcceptCredential.ContentApproved, + })) }) afterAll(async () => { @@ -164,51 +181,45 @@ describe('credentials', () => { // ============================== // TESTS v1 BEGIN // ========================== - test('Alice starts with V1 credential proposal to Faber, both with autoAcceptCredential on `contentApproved`', async () => { + test("Alice starts with V1 credential proposal to Faber, both with autoAcceptCredential on 'contentApproved'", async () => { testLogger.test('Alice sends credential proposal to Faber') - const schemaId = schema.id - let faberCredentialExchangeRecord: CredentialExchangeRecord - let aliceCredentialExchangeRecord: CredentialExchangeRecord - - aliceCredentialExchangeRecord = await aliceAgent.credentials.proposeCredential({ - connectionId: aliceConnection.id, + let aliceCredentialExchangeRecord = await aliceAgent.credentials.proposeCredential({ + connectionId: aliceConnectionId, protocolVersion: 'v1', credentialFormats: { indy: { attributes: credentialPreview.attributes, - credentialDefinitionId: credDefId, + credentialDefinitionId: credentialDefinitionId, }, }, }) testLogger.test('Faber waits for credential proposal from Alice') - faberCredentialExchangeRecord = await waitForCredentialRecord(faberAgent, { + let faberCredentialExchangeRecord = await waitForCredentialRecordSubject(faberReplay, { threadId: aliceCredentialExchangeRecord.threadId, state: CredentialState.ProposalReceived, }) - const options: AcceptCredentialProposalOptions = { + testLogger.test('Faber sends credential offer to Alice') + faberCredentialExchangeRecord = await faberAgent.credentials.acceptProposal({ credentialRecordId: faberCredentialExchangeRecord.id, comment: 'V1 Indy Offer', credentialFormats: { indy: { - credentialDefinitionId: credDefId, + credentialDefinitionId: credentialDefinitionId, attributes: credentialPreview.attributes, }, }, - } - testLogger.test('Faber sends credential offer to Alice') - options.credentialRecordId = faberCredentialExchangeRecord.id - faberCredentialExchangeRecord = await faberAgent.credentials.acceptProposal(options) + }) testLogger.test('Alice waits for credential from Faber') - aliceCredentialExchangeRecord = await waitForCredentialRecord(aliceAgent, { + aliceCredentialExchangeRecord = await waitForCredentialRecordSubject(aliceReplay, { threadId: faberCredentialExchangeRecord.threadId, state: CredentialState.CredentialReceived, }) testLogger.test('Faber waits for credential ack from Alice') - faberCredentialExchangeRecord = await waitForCredentialRecord(faberAgent, { + faberCredentialExchangeRecord = await waitForCredentialRecordSubject(faberReplay, { threadId: faberCredentialExchangeRecord.threadId, state: CredentialState.Done, }) @@ -219,16 +230,16 @@ describe('credentials', () => { createdAt: expect.any(Date), metadata: { data: { - '_internal/indyRequest': expect.any(Object), - '_internal/indyCredential': { + '_anoncreds/credentialRequest': expect.any(Object), + '_anoncreds/credential': { schemaId, - credentialDefinitionId: credDefId, + credentialDefinitionId: credentialDefinitionId, }, }, }, credentials: [ { - credentialRecordType: 'indy', + credentialRecordType: 'anoncreds', credentialRecordId: expect.any(String), }, ], @@ -241,9 +252,9 @@ describe('credentials', () => { createdAt: expect.any(Date), metadata: { data: { - '_internal/indyCredential': { + '_anoncreds/credential': { schemaId, - credentialDefinitionId: credDefId, + credentialDefinitionId: credentialDefinitionId, }, }, }, @@ -251,26 +262,22 @@ describe('credentials', () => { }) }) - test('Faber starts with V1 credential offer to Alice, both with autoAcceptCredential on `contentApproved`', async () => { + test("Faber starts with V1 credential offer to Alice, both with autoAcceptCredential on 'contentApproved'", async () => { testLogger.test('Faber sends credential offer to Alice') - const schemaId = schema.id - let aliceCredentialExchangeRecord: CredentialExchangeRecord - let faberCredentialExchangeRecord: CredentialExchangeRecord - - faberCredentialExchangeRecord = await faberAgent.credentials.offerCredential({ + let faberCredentialExchangeRecord = await faberAgent.credentials.offerCredential({ comment: 'some comment about credential', - connectionId: faberConnection.id, + connectionId: faberConnectionId, credentialFormats: { indy: { attributes: credentialPreview.attributes, - credentialDefinitionId: credDefId, + credentialDefinitionId: credentialDefinitionId, }, }, protocolVersion: 'v1', }) testLogger.test('Alice waits for credential offer from Faber') - aliceCredentialExchangeRecord = await waitForCredentialRecord(aliceAgent, { + let aliceCredentialExchangeRecord = await waitForCredentialRecordSubject(aliceReplay, { threadId: faberCredentialExchangeRecord.threadId, state: CredentialState.OfferReceived, }) @@ -284,78 +291,73 @@ describe('credentials', () => { expect(aliceCredentialExchangeRecord.getTags()).toEqual({ threadId: aliceCredentialExchangeRecord.threadId, state: aliceCredentialExchangeRecord.state, - connectionId: aliceConnection.id, + connectionId: aliceConnectionId, credentialIds: [], }) - if (aliceCredentialExchangeRecord.connectionId) { - const acceptOfferOptions: AcceptCredentialOfferOptions = { - credentialRecordId: aliceCredentialExchangeRecord.id, - } - testLogger.test('alice sends credential request to faber') - faberCredentialExchangeRecord = await aliceAgent.credentials.acceptOffer(acceptOfferOptions) - - testLogger.test('Alice waits for credential from Faber') - aliceCredentialExchangeRecord = await waitForCredentialRecord(aliceAgent, { - threadId: faberCredentialExchangeRecord.threadId, - state: CredentialState.CredentialReceived, - }) - - testLogger.test('Faber waits for credential ack from Alice') - faberCredentialExchangeRecord = await waitForCredentialRecord(faberAgent, { - threadId: faberCredentialExchangeRecord.threadId, - state: CredentialState.Done, - }) - - expect(aliceCredentialExchangeRecord).toMatchObject({ - type: CredentialExchangeRecord.type, - id: expect.any(String), - createdAt: expect.any(Date), - metadata: { - data: { - '_internal/indyRequest': expect.any(Object), - '_internal/indyCredential': { - schemaId, - credentialDefinitionId: credDefId, - }, + testLogger.test('alice sends credential request to faber') + faberCredentialExchangeRecord = await aliceAgent.credentials.acceptOffer({ + credentialRecordId: aliceCredentialExchangeRecord.id, + }) + + testLogger.test('Alice waits for credential from Faber') + aliceCredentialExchangeRecord = await waitForCredentialRecordSubject(aliceReplay, { + threadId: faberCredentialExchangeRecord.threadId, + state: CredentialState.CredentialReceived, + }) + + testLogger.test('Faber waits for credential ack from Alice') + faberCredentialExchangeRecord = await waitForCredentialRecordSubject(faberReplay, { + threadId: faberCredentialExchangeRecord.threadId, + state: CredentialState.Done, + }) + + expect(aliceCredentialExchangeRecord).toMatchObject({ + type: CredentialExchangeRecord.type, + id: expect.any(String), + createdAt: expect.any(Date), + metadata: { + data: { + '_anoncreds/credentialRequest': expect.any(Object), + '_anoncreds/credential': { + schemaId, + credentialDefinitionId: credentialDefinitionId, }, }, - credentials: [ - { - credentialRecordType: 'indy', - credentialRecordId: expect.any(String), - }, - ], - state: CredentialState.CredentialReceived, - }) - - expect(faberCredentialExchangeRecord).toMatchObject({ - type: CredentialExchangeRecord.type, - id: expect.any(String), - createdAt: expect.any(Date), - state: CredentialState.Done, - }) - } else { - throw new AriesFrameworkError('missing alice connection id') - } + }, + credentials: [ + { + credentialRecordType: 'anoncreds', + credentialRecordId: expect.any(String), + }, + ], + state: CredentialState.CredentialReceived, + }) + + expect(faberCredentialExchangeRecord).toMatchObject({ + type: CredentialExchangeRecord.type, + id: expect.any(String), + createdAt: expect.any(Date), + state: CredentialState.Done, + }) }) - test('Faber starts with V1 credential offer to Alice, both have autoAcceptCredential on `contentApproved` and attributes did change', async () => { + test("Faber starts with V1 credential offer to Alice, both have autoAcceptCredential on 'contentApproved' and attributes did change", async () => { testLogger.test('Faber sends credential offer to Alice') let faberCredentialExchangeRecord = await faberAgent.credentials.offerCredential({ comment: 'some comment about credential', - connectionId: faberConnection.id, + connectionId: faberConnectionId, credentialFormats: { indy: { attributes: credentialPreview.attributes, - credentialDefinitionId: credDefId, + credentialDefinitionId: credentialDefinitionId, }, }, protocolVersion: 'v1', }) testLogger.test('Alice waits for credential offer from Faber') - let aliceCredentialExchangeRecord = await waitForCredentialRecord(aliceAgent, { + let aliceCredentialExchangeRecord = await waitForCredentialRecordSubject(aliceReplay, { threadId: faberCredentialExchangeRecord.threadId, state: CredentialState.OfferReceived, }) @@ -365,7 +367,7 @@ describe('credentials', () => { expect(aliceCredentialExchangeRecord.getTags()).toEqual({ threadId: aliceCredentialExchangeRecord.threadId, state: aliceCredentialExchangeRecord.state, - connectionId: aliceConnection.id, + connectionId: aliceConnectionId, credentialIds: [], }) @@ -375,7 +377,7 @@ describe('credentials', () => { credentialFormats: { indy: { attributes: newCredentialPreview.attributes, - credentialDefinitionId: credDefId, + credentialDefinitionId: credentialDefinitionId, }, }, comment: 'v1 propose credential test', @@ -395,22 +397,22 @@ describe('credentials', () => { aliceCredentialExchangeRecord.assertState(CredentialState.ProposalSent) }) - test('Alice starts with V1 credential proposal to Faber, both have autoAcceptCredential on `contentApproved` and attributes did change', async () => { + test("Alice starts with V1 credential proposal to Faber, both have autoAcceptCredential on 'contentApproved' and attributes did change", async () => { testLogger.test('Alice sends credential proposal to Faber') const aliceCredentialExchangeRecord = await aliceAgent.credentials.proposeCredential({ - connectionId: aliceConnection.id, + connectionId: aliceConnectionId, protocolVersion: 'v1', credentialFormats: { indy: { attributes: credentialPreview.attributes, - credentialDefinitionId: credDefId, + credentialDefinitionId: credentialDefinitionId, }, }, comment: 'v1 propose credential test', }) testLogger.test('Faber waits for credential proposal from Alice') - let faberCredentialExchangeRecord = await waitForCredentialRecord(faberAgent, { + let faberCredentialExchangeRecord = await waitForCredentialRecordSubject(faberReplay, { threadId: aliceCredentialExchangeRecord.threadId, state: CredentialState.ProposalReceived, }) @@ -419,7 +421,7 @@ describe('credentials', () => { credentialRecordId: faberCredentialExchangeRecord.id, credentialFormats: { indy: { - credentialDefinitionId: credDefId, + credentialDefinitionId: credentialDefinitionId, attributes: newCredentialPreview.attributes, }, }, @@ -427,7 +429,7 @@ describe('credentials', () => { testLogger.test('Alice waits for credential offer from Faber') - const record = await waitForCredentialRecord(aliceAgent, { + const record = await waitForCredentialRecordSubject(aliceReplay, { threadId: faberCredentialExchangeRecord.threadId, state: CredentialState.OfferReceived, }) @@ -437,7 +439,7 @@ describe('credentials', () => { expect(record.getTags()).toEqual({ threadId: record.threadId, state: record.state, - connectionId: aliceConnection.id, + connectionId: aliceConnectionId, credentialIds: [], }) diff --git a/packages/core/src/modules/credentials/protocol/v1/__tests__/v1-credentials.e2e.test.ts b/packages/anoncreds/src/protocols/credentials/v1/__tests__/v1-credentials.e2e.test.ts similarity index 86% rename from packages/core/src/modules/credentials/protocol/v1/__tests__/v1-credentials.e2e.test.ts rename to packages/anoncreds/src/protocols/credentials/v1/__tests__/v1-credentials.e2e.test.ts index 1d25498a3a..78a6f2f852 100644 --- a/packages/core/src/modules/credentials/protocol/v1/__tests__/v1-credentials.e2e.test.ts +++ b/packages/anoncreds/src/protocols/credentials/v1/__tests__/v1-credentials.e2e.test.ts @@ -1,12 +1,15 @@ -import type { Agent } from '../../../../../agent/Agent' -import type { ConnectionRecord } from '../../../../connections' +import type { AnonCredsTestsAgent } from '../../../../../tests/legacyAnonCredsSetup' -import { setupCredentialTests, waitForCredentialRecord } from '../../../../../../tests/helpers' -import testLogger from '../../../../../../tests/logger' -import { DidCommMessageRepository } from '../../../../../storage' -import { JsonTransformer } from '../../../../../utils' -import { CredentialState } from '../../../models/CredentialState' -import { CredentialExchangeRecord } from '../../../repository/CredentialExchangeRecord' +import { + CredentialExchangeRecord, + CredentialState, + DidCommMessageRepository, + JsonTransformer, +} from '@aries-framework/core' + +import { waitForCredentialRecord } from '../../../../../../core/tests/helpers' +import testLogger from '../../../../../../core/tests/logger' +import { setupAnonCredsTests } from '../../../../../tests/legacyAnonCredsSetup' import { V1ProposeCredentialMessage, V1RequestCredentialMessage, @@ -15,19 +18,23 @@ import { V1CredentialPreview, } from '../messages' -describe('v1 credentials', () => { - let faberAgent: Agent - let aliceAgent: Agent - let credDefId: string - let aliceConnection: ConnectionRecord - let aliceCredentialRecord: CredentialExchangeRecord - let faberCredentialRecord: CredentialExchangeRecord +describe('V1 Credentials', () => { + let faberAgent: AnonCredsTestsAgent + let aliceAgent: AnonCredsTestsAgent + let credentialDefinitionId: string + let aliceConnectionId: string beforeAll(async () => { - ;({ faberAgent, aliceAgent, credDefId, aliceConnection } = await setupCredentialTests( - 'Faber Agent Credentials v1', - 'Alice Agent Credentials v1' - )) + ;({ + issuerAgent: faberAgent, + holderAgent: aliceAgent, + credentialDefinitionId, + holderIssuerConnectionId: aliceConnectionId, + } = await setupAnonCredsTests({ + issuerName: 'Faber Agent Credentials V1', + holderName: 'Alice Agent Credentials V1', + attributeNames: ['name', 'age', 'x-ray', 'profile_picture'], + })) }) afterAll(async () => { @@ -48,7 +55,7 @@ describe('v1 credentials', () => { testLogger.test('Alice sends (v1) credential proposal to Faber') const credentialExchangeRecord = await aliceAgent.credentials.proposeCredential({ - connectionId: aliceConnection.id, + connectionId: aliceConnectionId, protocolVersion: 'v1', credentialFormats: { indy: { @@ -65,14 +72,14 @@ describe('v1 credentials', () => { }) expect(credentialExchangeRecord).toMatchObject({ - connectionId: aliceConnection.id, + connectionId: aliceConnectionId, protocolVersion: 'v1', state: CredentialState.ProposalSent, threadId: expect.any(String), }) testLogger.test('Faber waits for credential proposal from Alice') - faberCredentialRecord = await waitForCredentialRecord(faberAgent, { + let faberCredentialRecord = await waitForCredentialRecord(faberAgent, { threadId: credentialExchangeRecord.threadId, state: CredentialState.ProposalReceived, }) @@ -83,14 +90,14 @@ describe('v1 credentials', () => { comment: 'V1 Indy Proposal', credentialFormats: { indy: { - credentialDefinitionId: credDefId, + credentialDefinitionId: credentialDefinitionId, attributes: credentialPreview.attributes, }, }, }) testLogger.test('Alice waits for credential offer from Faber') - aliceCredentialRecord = await waitForCredentialRecord(aliceAgent, { + let aliceCredentialRecord = await waitForCredentialRecord(aliceAgent, { threadId: faberCredentialRecord.threadId, state: CredentialState.OfferReceived, }) @@ -152,7 +159,7 @@ describe('v1 credentials', () => { }) expect(offerCredentialExchangeRecord).toMatchObject({ - connectionId: aliceConnection.id, + connectionId: aliceConnectionId, protocolVersion: 'v1', state: CredentialState.RequestSent, threadId: expect.any(String), diff --git a/packages/anoncreds/src/protocols/credentials/v1/errors/V1CredentialProblemReportError.ts b/packages/anoncreds/src/protocols/credentials/v1/errors/V1CredentialProblemReportError.ts new file mode 100644 index 0000000000..113a8ac6f2 --- /dev/null +++ b/packages/anoncreds/src/protocols/credentials/v1/errors/V1CredentialProblemReportError.ts @@ -0,0 +1,23 @@ +import type { ProblemReportErrorOptions, CredentialProblemReportReason } from '@aries-framework/core' + +import { ProblemReportError } from '@aries-framework/core' + +import { V1CredentialProblemReportMessage } from '../messages' + +export interface V1CredentialProblemReportErrorOptions extends ProblemReportErrorOptions { + problemCode: CredentialProblemReportReason +} + +export class V1CredentialProblemReportError extends ProblemReportError { + public problemReport: V1CredentialProblemReportMessage + + public constructor(message: string, { problemCode }: V1CredentialProblemReportErrorOptions) { + super(message, { problemCode }) + this.problemReport = new V1CredentialProblemReportMessage({ + description: { + en: message, + code: problemCode, + }, + }) + } +} diff --git a/packages/anoncreds/src/protocols/credentials/v1/errors/index.ts b/packages/anoncreds/src/protocols/credentials/v1/errors/index.ts new file mode 100644 index 0000000000..5d2b6fc15e --- /dev/null +++ b/packages/anoncreds/src/protocols/credentials/v1/errors/index.ts @@ -0,0 +1 @@ +export { V1CredentialProblemReportError, V1CredentialProblemReportErrorOptions } from './V1CredentialProblemReportError' diff --git a/packages/core/src/modules/credentials/protocol/v1/handlers/V1CredentialAckHandler.ts b/packages/anoncreds/src/protocols/credentials/v1/handlers/V1CredentialAckHandler.ts similarity index 94% rename from packages/core/src/modules/credentials/protocol/v1/handlers/V1CredentialAckHandler.ts rename to packages/anoncreds/src/protocols/credentials/v1/handlers/V1CredentialAckHandler.ts index e34a95d2bb..b4d3384ed9 100644 --- a/packages/core/src/modules/credentials/protocol/v1/handlers/V1CredentialAckHandler.ts +++ b/packages/anoncreds/src/protocols/credentials/v1/handlers/V1CredentialAckHandler.ts @@ -1,5 +1,5 @@ -import type { MessageHandler, MessageHandlerInboundMessage } from '../../../../../agent/MessageHandler' import type { V1CredentialProtocol } from '../V1CredentialProtocol' +import type { MessageHandler, MessageHandlerInboundMessage } from '@aries-framework/core' import { V1CredentialAckMessage } from '../messages' diff --git a/packages/core/src/modules/credentials/protocol/v1/handlers/V1CredentialProblemReportHandler.ts b/packages/anoncreds/src/protocols/credentials/v1/handlers/V1CredentialProblemReportHandler.ts similarity index 94% rename from packages/core/src/modules/credentials/protocol/v1/handlers/V1CredentialProblemReportHandler.ts rename to packages/anoncreds/src/protocols/credentials/v1/handlers/V1CredentialProblemReportHandler.ts index 06769cf1bb..b2e599577d 100644 --- a/packages/core/src/modules/credentials/protocol/v1/handlers/V1CredentialProblemReportHandler.ts +++ b/packages/anoncreds/src/protocols/credentials/v1/handlers/V1CredentialProblemReportHandler.ts @@ -1,5 +1,5 @@ -import type { MessageHandler, MessageHandlerInboundMessage } from '../../../../../agent/MessageHandler' import type { V1CredentialProtocol } from '../V1CredentialProtocol' +import type { MessageHandler, MessageHandlerInboundMessage } from '@aries-framework/core' import { V1CredentialProblemReportMessage } from '../messages' diff --git a/packages/core/src/modules/credentials/protocol/v1/handlers/V1IssueCredentialHandler.ts b/packages/anoncreds/src/protocols/credentials/v1/handlers/V1IssueCredentialHandler.ts similarity index 88% rename from packages/core/src/modules/credentials/protocol/v1/handlers/V1IssueCredentialHandler.ts rename to packages/anoncreds/src/protocols/credentials/v1/handlers/V1IssueCredentialHandler.ts index 82e7dea44a..e828fb2258 100644 --- a/packages/core/src/modules/credentials/protocol/v1/handlers/V1IssueCredentialHandler.ts +++ b/packages/anoncreds/src/protocols/credentials/v1/handlers/V1IssueCredentialHandler.ts @@ -1,9 +1,8 @@ -import type { MessageHandler, MessageHandlerInboundMessage } from '../../../../../agent/MessageHandler' -import type { CredentialExchangeRecord } from '../../../repository/CredentialExchangeRecord' import type { V1CredentialProtocol } from '../V1CredentialProtocol' +import type { MessageHandler, MessageHandlerInboundMessage, CredentialExchangeRecord } from '@aries-framework/core' + +import { DidCommMessageRepository, OutboundMessageContext } from '@aries-framework/core' -import { OutboundMessageContext } from '../../../../../agent/models' -import { DidCommMessageRepository } from '../../../../../storage' import { V1IssueCredentialMessage, V1RequestCredentialMessage } from '../messages' export class V1IssueCredentialHandler implements MessageHandler { diff --git a/packages/core/src/modules/credentials/protocol/v1/handlers/V1OfferCredentialHandler.ts b/packages/anoncreds/src/protocols/credentials/v1/handlers/V1OfferCredentialHandler.ts similarity index 82% rename from packages/core/src/modules/credentials/protocol/v1/handlers/V1OfferCredentialHandler.ts rename to packages/anoncreds/src/protocols/credentials/v1/handlers/V1OfferCredentialHandler.ts index 5e7731b72f..8d2d847e96 100644 --- a/packages/core/src/modules/credentials/protocol/v1/handlers/V1OfferCredentialHandler.ts +++ b/packages/anoncreds/src/protocols/credentials/v1/handlers/V1OfferCredentialHandler.ts @@ -1,11 +1,14 @@ -import type { MessageHandler, MessageHandlerInboundMessage } from '../../../../../agent/MessageHandler' -import type { CredentialExchangeRecord } from '../../../repository/CredentialExchangeRecord' import type { V1CredentialProtocol } from '../V1CredentialProtocol' +import type { MessageHandler, MessageHandlerInboundMessage, CredentialExchangeRecord } from '@aries-framework/core' + +import { + OutboundMessageContext, + RoutingService, + DidCommMessageRepository, + DidCommMessageRole, + ServiceDecorator, +} from '@aries-framework/core' -import { OutboundMessageContext } from '../../../../../agent/models' -import { ServiceDecorator } from '../../../../../decorators/service/ServiceDecorator' -import { DidCommMessageRepository, DidCommMessageRole } from '../../../../../storage' -import { RoutingService } from '../../../../routing/services/RoutingService' import { V1OfferCredentialMessage } from '../messages' export class V1OfferCredentialHandler implements MessageHandler { @@ -54,11 +57,6 @@ export class V1OfferCredentialHandler implements MessageHandler { const { message } = await this.credentialProtocol.acceptOffer(messageContext.agentContext, { credentialRecord, - credentialFormats: { - indy: { - holderDid: ourService.recipientKeys[0], - }, - }, }) // Set and save ~service decorator to record (to remember our verkey) diff --git a/packages/core/src/modules/credentials/protocol/v1/handlers/V1ProposeCredentialHandler.ts b/packages/anoncreds/src/protocols/credentials/v1/handlers/V1ProposeCredentialHandler.ts similarity index 86% rename from packages/core/src/modules/credentials/protocol/v1/handlers/V1ProposeCredentialHandler.ts rename to packages/anoncreds/src/protocols/credentials/v1/handlers/V1ProposeCredentialHandler.ts index 998d3940fc..d4fdbec98f 100644 --- a/packages/core/src/modules/credentials/protocol/v1/handlers/V1ProposeCredentialHandler.ts +++ b/packages/anoncreds/src/protocols/credentials/v1/handlers/V1ProposeCredentialHandler.ts @@ -1,8 +1,8 @@ -import type { MessageHandler, MessageHandlerInboundMessage } from '../../../../../agent/MessageHandler' -import type { CredentialExchangeRecord } from '../../../repository/CredentialExchangeRecord' import type { V1CredentialProtocol } from '../V1CredentialProtocol' +import type { CredentialExchangeRecord, MessageHandler, MessageHandlerInboundMessage } from '@aries-framework/core' + +import { OutboundMessageContext } from '@aries-framework/core' -import { OutboundMessageContext } from '../../../../../agent/models' import { V1ProposeCredentialMessage } from '../messages' export class V1ProposeCredentialHandler implements MessageHandler { diff --git a/packages/core/src/modules/credentials/protocol/v1/handlers/V1RequestCredentialHandler.ts b/packages/anoncreds/src/protocols/credentials/v1/handlers/V1RequestCredentialHandler.ts similarity index 88% rename from packages/core/src/modules/credentials/protocol/v1/handlers/V1RequestCredentialHandler.ts rename to packages/anoncreds/src/protocols/credentials/v1/handlers/V1RequestCredentialHandler.ts index 2b831566b2..00154fc8a4 100644 --- a/packages/core/src/modules/credentials/protocol/v1/handlers/V1RequestCredentialHandler.ts +++ b/packages/anoncreds/src/protocols/credentials/v1/handlers/V1RequestCredentialHandler.ts @@ -1,9 +1,8 @@ -import type { MessageHandler, MessageHandlerInboundMessage } from '../../../../../agent/MessageHandler' -import type { CredentialExchangeRecord } from '../../../repository/CredentialExchangeRecord' import type { V1CredentialProtocol } from '../V1CredentialProtocol' +import type { CredentialExchangeRecord, MessageHandler, MessageHandlerInboundMessage } from '@aries-framework/core' + +import { DidCommMessageRepository, DidCommMessageRole, OutboundMessageContext } from '@aries-framework/core' -import { OutboundMessageContext } from '../../../../../agent/models' -import { DidCommMessageRepository, DidCommMessageRole } from '../../../../../storage' import { V1RequestCredentialMessage } from '../messages' export class V1RequestCredentialHandler implements MessageHandler { diff --git a/packages/core/src/modules/credentials/protocol/v1/handlers/index.ts b/packages/anoncreds/src/protocols/credentials/v1/handlers/index.ts similarity index 75% rename from packages/core/src/modules/credentials/protocol/v1/handlers/index.ts rename to packages/anoncreds/src/protocols/credentials/v1/handlers/index.ts index dc0528f7c8..8566870084 100644 --- a/packages/core/src/modules/credentials/protocol/v1/handlers/index.ts +++ b/packages/anoncreds/src/protocols/credentials/v1/handlers/index.ts @@ -4,4 +4,3 @@ export * from './V1OfferCredentialHandler' export * from './V1ProposeCredentialHandler' export * from './V1RequestCredentialHandler' export * from './V1CredentialProblemReportHandler' -export * from '../../revocation-notification/handlers/V1RevocationNotificationHandler' diff --git a/packages/core/src/modules/credentials/protocol/v1/index.ts b/packages/anoncreds/src/protocols/credentials/v1/index.ts similarity index 100% rename from packages/core/src/modules/credentials/protocol/v1/index.ts rename to packages/anoncreds/src/protocols/credentials/v1/index.ts diff --git a/packages/core/src/modules/credentials/protocol/v1/messages/V1CredentialAckMessage.ts b/packages/anoncreds/src/protocols/credentials/v1/messages/V1CredentialAckMessage.ts similarity index 75% rename from packages/core/src/modules/credentials/protocol/v1/messages/V1CredentialAckMessage.ts rename to packages/anoncreds/src/protocols/credentials/v1/messages/V1CredentialAckMessage.ts index 857ea12bc0..540123584b 100644 --- a/packages/core/src/modules/credentials/protocol/v1/messages/V1CredentialAckMessage.ts +++ b/packages/anoncreds/src/protocols/credentials/v1/messages/V1CredentialAckMessage.ts @@ -1,7 +1,6 @@ -import type { AckMessageOptions } from '../../../../common' +import type { AckMessageOptions } from '@aries-framework/core' -import { IsValidMessageType, parseMessageType } from '../../../../../utils/messageType' -import { AckMessage } from '../../../../common' +import { AckMessage, IsValidMessageType, parseMessageType } from '@aries-framework/core' export type V1CredentialAckMessageOptions = AckMessageOptions @@ -9,6 +8,8 @@ export type V1CredentialAckMessageOptions = AckMessageOptions * @see https://github.com/hyperledger/aries-rfcs/blob/master/features/0015-acks/README.md#explicit-acks */ export class V1CredentialAckMessage extends AckMessage { + public readonly allowDidSovPrefix = true + /** * Create new CredentialAckMessage instance. * @param options diff --git a/packages/core/src/modules/credentials/protocol/v1/messages/V1CredentialPreview.ts b/packages/anoncreds/src/protocols/credentials/v1/messages/V1CredentialPreview.ts similarity index 78% rename from packages/core/src/modules/credentials/protocol/v1/messages/V1CredentialPreview.ts rename to packages/anoncreds/src/protocols/credentials/v1/messages/V1CredentialPreview.ts index 9fe8aa5fc3..a5e1344bb8 100644 --- a/packages/core/src/modules/credentials/protocol/v1/messages/V1CredentialPreview.ts +++ b/packages/anoncreds/src/protocols/credentials/v1/messages/V1CredentialPreview.ts @@ -1,11 +1,14 @@ -import type { CredentialPreviewOptions } from '../../../models/CredentialPreviewAttribute' +import type { CredentialPreviewOptions } from '@aries-framework/core' +import { + CredentialPreviewAttribute, + IsValidMessageType, + parseMessageType, + JsonTransformer, + replaceLegacyDidSovPrefix, +} from '@aries-framework/core' import { Expose, Transform, Type } from 'class-transformer' -import { IsInstance, ValidateNested } from 'class-validator' - -import { JsonTransformer } from '../../../../../utils/JsonTransformer' -import { IsValidMessageType, parseMessageType, replaceLegacyDidSovPrefix } from '../../../../../utils/messageType' -import { CredentialPreviewAttribute } from '../../../models/CredentialPreviewAttribute' +import { ValidateNested, IsInstance } from 'class-validator' /** * Credential preview inner message class. @@ -17,7 +20,7 @@ import { CredentialPreviewAttribute } from '../../../models/CredentialPreviewAtt export class V1CredentialPreview { public constructor(options: CredentialPreviewOptions) { if (options) { - this.attributes = options.attributes + this.attributes = options.attributes.map((a) => new CredentialPreviewAttribute(a)) } } diff --git a/packages/core/src/modules/credentials/protocol/v1/messages/V1CredentialProblemReportMessage.ts b/packages/anoncreds/src/protocols/credentials/v1/messages/V1CredentialProblemReportMessage.ts similarity index 68% rename from packages/core/src/modules/credentials/protocol/v1/messages/V1CredentialProblemReportMessage.ts rename to packages/anoncreds/src/protocols/credentials/v1/messages/V1CredentialProblemReportMessage.ts index b8d8722829..5e36d71381 100644 --- a/packages/core/src/modules/credentials/protocol/v1/messages/V1CredentialProblemReportMessage.ts +++ b/packages/anoncreds/src/protocols/credentials/v1/messages/V1CredentialProblemReportMessage.ts @@ -1,7 +1,6 @@ -import type { ProblemReportMessageOptions } from '../../../../problem-reports/versions/v1/messages/ProblemReportMessage' +import type { ProblemReportMessageOptions } from '@aries-framework/core' -import { IsValidMessageType, parseMessageType } from '../../../../../utils/messageType' -import { ProblemReportMessage } from '../../../../problem-reports/versions/v1/messages/ProblemReportMessage' +import { ProblemReportMessage, IsValidMessageType, parseMessageType } from '@aries-framework/core' export type V1CredentialProblemReportMessageOptions = ProblemReportMessageOptions @@ -9,6 +8,8 @@ export type V1CredentialProblemReportMessageOptions = ProblemReportMessageOption * @see https://github.com/hyperledger/aries-rfcs/blob/main/features/0035-report-problem/README.md */ export class V1CredentialProblemReportMessage extends ProblemReportMessage { + public readonly allowDidSovPrefix = true + /** * Create new CredentialProblemReportMessage instance. * @param options diff --git a/packages/core/src/modules/credentials/protocol/v1/messages/V1IssueCredentialMessage.ts b/packages/anoncreds/src/protocols/credentials/v1/messages/V1IssueCredentialMessage.ts similarity index 76% rename from packages/core/src/modules/credentials/protocol/v1/messages/V1IssueCredentialMessage.ts rename to packages/anoncreds/src/protocols/credentials/v1/messages/V1IssueCredentialMessage.ts index 0979254e6a..c50278f911 100644 --- a/packages/core/src/modules/credentials/protocol/v1/messages/V1IssueCredentialMessage.ts +++ b/packages/anoncreds/src/protocols/credentials/v1/messages/V1IssueCredentialMessage.ts @@ -1,11 +1,8 @@ -import type { Cred } from 'indy-sdk' +import type { AnonCredsCredential } from '../../../../models' +import { DidCommV1Message, IsValidMessageType, parseMessageType, V1Attachment } from '@aries-framework/core' import { Expose, Type } from 'class-transformer' -import { IsArray, IsInstance, IsOptional, IsString, ValidateNested } from 'class-validator' - -import { V1Attachment } from '../../../../../decorators/attachment/V1Attachment' -import { DidCommV1Message } from '../../../../../didcomm' -import { IsValidMessageType, parseMessageType } from '../../../../../utils/messageType' +import { IsString, IsOptional, IsArray, ValidateNested, IsInstance } from 'class-validator' export const INDY_CREDENTIAL_ATTACHMENT_ID = 'libindy-cred-0' @@ -17,6 +14,8 @@ export interface V1IssueCredentialMessageOptions { } export class V1IssueCredentialMessage extends DidCommV1Message { + public readonly allowDidSovPrefix = true + public constructor(options: V1IssueCredentialMessageOptions) { super() @@ -45,11 +44,11 @@ export class V1IssueCredentialMessage extends DidCommV1Message { @IsInstance(V1Attachment, { each: true }) public credentialAttachments!: V1Attachment[] - public get indyCredential(): Cred | null { + public get indyCredential(): AnonCredsCredential | null { const attachment = this.credentialAttachments.find((attachment) => attachment.id === INDY_CREDENTIAL_ATTACHMENT_ID) // Extract credential from attachment - const credentialJson = attachment?.getDataAsJson() ?? null + const credentialJson = attachment?.getDataAsJson() ?? null return credentialJson } diff --git a/packages/core/src/modules/credentials/protocol/v1/messages/V1OfferCredentialMessage.ts b/packages/anoncreds/src/protocols/credentials/v1/messages/V1OfferCredentialMessage.ts similarity index 84% rename from packages/core/src/modules/credentials/protocol/v1/messages/V1OfferCredentialMessage.ts rename to packages/anoncreds/src/protocols/credentials/v1/messages/V1OfferCredentialMessage.ts index c9c7946e4a..3145291321 100644 --- a/packages/core/src/modules/credentials/protocol/v1/messages/V1OfferCredentialMessage.ts +++ b/packages/anoncreds/src/protocols/credentials/v1/messages/V1OfferCredentialMessage.ts @@ -1,12 +1,9 @@ -import type { CredOffer } from 'indy-sdk' +import type { AnonCredsCredentialOffer } from '../../../../models' +import { DidCommV1Message, IsValidMessageType, parseMessageType, V1Attachment } from '@aries-framework/core' import { Expose, Type } from 'class-transformer' import { IsArray, IsInstance, IsOptional, IsString, ValidateNested } from 'class-validator' -import { V1Attachment } from '../../../../../decorators/attachment/V1Attachment' -import { DidCommV1Message } from '../../../../../didcomm' -import { IsValidMessageType, parseMessageType } from '../../../../../utils/messageType' - import { V1CredentialPreview } from './V1CredentialPreview' export const INDY_CREDENTIAL_OFFER_ATTACHMENT_ID = 'libindy-cred-offer-0' @@ -25,6 +22,8 @@ export interface V1OfferCredentialMessageOptions { * @see https://github.com/hyperledger/aries-rfcs/blob/master/features/0036-issue-credential/README.md#offer-credential */ export class V1OfferCredentialMessage extends DidCommV1Message { + public readonly allowDidSovPrefix = true + public constructor(options: V1OfferCredentialMessageOptions) { super() @@ -60,11 +59,11 @@ export class V1OfferCredentialMessage extends DidCommV1Message { @IsInstance(V1Attachment, { each: true }) public offerAttachments!: V1Attachment[] - public get indyCredentialOffer(): CredOffer | null { + public get indyCredentialOffer(): AnonCredsCredentialOffer | null { const attachment = this.offerAttachments.find((attachment) => attachment.id === INDY_CREDENTIAL_OFFER_ATTACHMENT_ID) // Extract credential offer from attachment - const credentialOfferJson = attachment?.getDataAsJson() ?? null + const credentialOfferJson = attachment?.getDataAsJson() ?? null return credentialOfferJson } diff --git a/packages/core/src/modules/credentials/protocol/v1/messages/V1ProposeCredentialMessage.ts b/packages/anoncreds/src/protocols/credentials/v1/messages/V1ProposeCredentialMessage.ts similarity index 85% rename from packages/core/src/modules/credentials/protocol/v1/messages/V1ProposeCredentialMessage.ts rename to packages/anoncreds/src/protocols/credentials/v1/messages/V1ProposeCredentialMessage.ts index 85042d17cc..62e8613a1e 100644 --- a/packages/core/src/modules/credentials/protocol/v1/messages/V1ProposeCredentialMessage.ts +++ b/packages/anoncreds/src/protocols/credentials/v1/messages/V1ProposeCredentialMessage.ts @@ -1,11 +1,15 @@ -import type { V1Attachment } from '../../../../../decorators/attachment/V1Attachment' +import type { V1Attachment } from '@aries-framework/core' +import { DidCommV1Message, IsValidMessageType, parseMessageType } from '@aries-framework/core' import { Expose, Type } from 'class-transformer' import { IsInstance, IsOptional, IsString, Matches, ValidateNested } from 'class-validator' -import { DidCommV1Message } from '../../../../../didcomm' -import { indyDidRegex, schemaIdRegex, schemaVersionRegex, credDefIdRegex } from '../../../../../utils' -import { IsValidMessageType, parseMessageType } from '../../../../../utils/messageType' +import { + unqualifiedCredentialDefinitionIdRegex, + unqualifiedIndyDidRegex, + unqualifiedSchemaIdRegex, + unqualifiedSchemaVersionRegex, +} from '../../../../utils' import { V1CredentialPreview } from './V1CredentialPreview' @@ -28,6 +32,8 @@ export interface V1ProposeCredentialMessageOptions { * @see https://github.com/hyperledger/aries-rfcs/blob/master/features/0036-issue-credential/README.md#propose-credential */ export class V1ProposeCredentialMessage extends DidCommV1Message { + public readonly allowDidSovPrefix = true + public constructor(options: V1ProposeCredentialMessageOptions) { super() @@ -73,7 +79,7 @@ export class V1ProposeCredentialMessage extends DidCommV1Message { @Expose({ name: 'schema_issuer_did' }) @IsString() @IsOptional() - @Matches(indyDidRegex) + @Matches(unqualifiedIndyDidRegex) public schemaIssuerDid?: string /** @@ -82,7 +88,7 @@ export class V1ProposeCredentialMessage extends DidCommV1Message { @Expose({ name: 'schema_id' }) @IsString() @IsOptional() - @Matches(schemaIdRegex) + @Matches(unqualifiedSchemaIdRegex) public schemaId?: string /** @@ -99,7 +105,7 @@ export class V1ProposeCredentialMessage extends DidCommV1Message { @Expose({ name: 'schema_version' }) @IsString() @IsOptional() - @Matches(schemaVersionRegex, { + @Matches(unqualifiedSchemaVersionRegex, { message: 'Version must be X.X or X.X.X', }) public schemaVersion?: string @@ -110,7 +116,7 @@ export class V1ProposeCredentialMessage extends DidCommV1Message { @Expose({ name: 'cred_def_id' }) @IsString() @IsOptional() - @Matches(credDefIdRegex) + @Matches(unqualifiedCredentialDefinitionIdRegex) public credentialDefinitionId?: string /** @@ -119,7 +125,7 @@ export class V1ProposeCredentialMessage extends DidCommV1Message { @Expose({ name: 'issuer_did' }) @IsString() @IsOptional() - @Matches(indyDidRegex) + @Matches(unqualifiedIndyDidRegex) public issuerDid?: string public getAttachment(): V1Attachment | undefined { diff --git a/packages/core/src/modules/credentials/protocol/v1/messages/V1RequestCredentialMessage.ts b/packages/anoncreds/src/protocols/credentials/v1/messages/V1RequestCredentialMessage.ts similarity index 80% rename from packages/core/src/modules/credentials/protocol/v1/messages/V1RequestCredentialMessage.ts rename to packages/anoncreds/src/protocols/credentials/v1/messages/V1RequestCredentialMessage.ts index 494524b475..edc09a6049 100644 --- a/packages/core/src/modules/credentials/protocol/v1/messages/V1RequestCredentialMessage.ts +++ b/packages/anoncreds/src/protocols/credentials/v1/messages/V1RequestCredentialMessage.ts @@ -1,12 +1,9 @@ -import type { CredReq } from 'indy-sdk' +import type { LegacyIndyCredentialRequest } from '../../../../formats' +import { DidCommV1Message, IsValidMessageType, parseMessageType, V1Attachment } from '@aries-framework/core' import { Expose, Type } from 'class-transformer' import { IsArray, IsInstance, IsOptional, IsString, ValidateNested } from 'class-validator' -import { V1Attachment } from '../../../../../decorators/attachment/V1Attachment' -import { DidCommV1Message } from '../../../../../didcomm' -import { IsValidMessageType, parseMessageType } from '../../../../../utils/messageType' - export const INDY_CREDENTIAL_REQUEST_ATTACHMENT_ID = 'libindy-cred-request-0' export interface V1RequestCredentialMessageOptions { @@ -17,6 +14,8 @@ export interface V1RequestCredentialMessageOptions { } export class V1RequestCredentialMessage extends DidCommV1Message { + public readonly allowDidSovPrefix = true + public constructor(options: V1RequestCredentialMessageOptions) { super() @@ -45,12 +44,12 @@ export class V1RequestCredentialMessage extends DidCommV1Message { @IsInstance(V1Attachment, { each: true }) public requestAttachments!: V1Attachment[] - public get indyCredentialRequest(): CredReq | null { + public get indyCredentialRequest(): LegacyIndyCredentialRequest | null { const attachment = this.requestAttachments.find( (attachment) => attachment.id === INDY_CREDENTIAL_REQUEST_ATTACHMENT_ID ) // Extract proof request from attachment - const credentialReqJson = attachment?.getDataAsJson() ?? null + const credentialReqJson = attachment?.getDataAsJson() ?? null return credentialReqJson } diff --git a/packages/core/src/modules/credentials/protocol/v1/messages/index.ts b/packages/anoncreds/src/protocols/credentials/v1/messages/index.ts similarity index 100% rename from packages/core/src/modules/credentials/protocol/v1/messages/index.ts rename to packages/anoncreds/src/protocols/credentials/v1/messages/index.ts diff --git a/packages/anoncreds/src/protocols/index.ts b/packages/anoncreds/src/protocols/index.ts new file mode 100644 index 0000000000..d5a3d13f6c --- /dev/null +++ b/packages/anoncreds/src/protocols/index.ts @@ -0,0 +1,2 @@ +export * from './credentials/v1' +export * from './proofs/v1' diff --git a/packages/anoncreds/src/protocols/proofs/v1/V1ProofProtocol.ts b/packages/anoncreds/src/protocols/proofs/v1/V1ProofProtocol.ts new file mode 100644 index 0000000000..c7c88c15ae --- /dev/null +++ b/packages/anoncreds/src/protocols/proofs/v1/V1ProofProtocol.ts @@ -0,0 +1,1121 @@ +import type { LegacyIndyProofFormatService } from '../../../formats' +import type { + ProofProtocol, + DependencyManager, + FeatureRegistry, + AgentContext, + ProofProtocolOptions, + InboundMessageContext, + ProblemReportMessage, + GetProofFormatDataReturn, + ProofFormat, + DidCommV1Message, +} from '@aries-framework/core' + +import { + BaseProofProtocol, + Protocol, + ProofRepository, + DidCommMessageRepository, + AriesFrameworkError, + MessageValidator, + ProofExchangeRecord, + ProofState, + DidCommMessageRole, + ConnectionService, + V1Attachment, + JsonTransformer, + PresentationProblemReportReason, + AckStatus, + ProofsModuleConfig, + AutoAcceptProof, + JsonEncoder, + utils, +} from '@aries-framework/core' + +import { composeProofAutoAccept, createRequestFromPreview } from '../../../utils' + +import { V1PresentationProblemReportError } from './errors' +import { + V1PresentationAckHandler, + V1PresentationHandler, + V1PresentationProblemReportHandler, + V1ProposePresentationHandler, + V1RequestPresentationHandler, +} from './handlers' +import { + INDY_PROOF_ATTACHMENT_ID, + INDY_PROOF_REQUEST_ATTACHMENT_ID, + V1PresentationAckMessage, + V1PresentationMessage, + V1ProposePresentationMessage, + V1RequestPresentationMessage, +} from './messages' +import { V1PresentationProblemReportMessage } from './messages/V1PresentationProblemReportMessage' +import { V1PresentationPreview } from './models/V1PresentationPreview' + +export interface V1ProofProtocolConfig { + indyProofFormat: LegacyIndyProofFormatService +} + +export class V1ProofProtocol extends BaseProofProtocol implements ProofProtocol<[LegacyIndyProofFormatService]> { + private indyProofFormat: LegacyIndyProofFormatService + + public constructor({ indyProofFormat }: V1ProofProtocolConfig) { + super() + + // TODO: just create a new instance of LegacyIndyProofFormatService here so it makes the setup easier + this.indyProofFormat = indyProofFormat + } + + /** + * The version of the present proof protocol this protocol supports + */ + public readonly version = 'v1' as const + + /** + * Registers the protocol implementation (handlers, feature registry) on the agent. + */ + public register(dependencyManager: DependencyManager, featureRegistry: FeatureRegistry) { + // Register message handlers for the Issue Credential V1 Protocol + dependencyManager.registerMessageHandlers([ + new V1ProposePresentationHandler(this), + new V1RequestPresentationHandler(this), + new V1PresentationHandler(this), + new V1PresentationAckHandler(this), + new V1PresentationProblemReportHandler(this), + ]) + + // Register Present Proof V1 in feature registry, with supported roles + featureRegistry.register( + new Protocol({ + id: 'https://didcomm.org/present-proof/1.0', + roles: ['prover', 'verifier'], + }) + ) + } + + public async createProposal( + agentContext: AgentContext, + { + proofFormats, + connectionRecord, + comment, + parentThreadId, + autoAcceptProof, + }: ProofProtocolOptions.CreateProofProposalOptions<[LegacyIndyProofFormatService]> + ): Promise> { + this.assertOnlyIndyFormat(proofFormats) + + const proofRepository = agentContext.dependencyManager.resolve(ProofRepository) + const didCommMessageRepository = agentContext.dependencyManager.resolve(DidCommMessageRepository) + + if (!proofFormats.indy) { + throw new AriesFrameworkError('Missing indy proof format in v1 create proposal call.') + } + + const presentationProposal = new V1PresentationPreview({ + attributes: proofFormats.indy?.attributes, + predicates: proofFormats.indy?.predicates, + }) + + // validate input data from user + MessageValidator.validateSync(presentationProposal) + + // Create message + const message = new V1ProposePresentationMessage({ + presentationProposal, + comment, + }) + + if (parentThreadId) + message.setThread({ + parentThreadId, + }) + + // Create record + const proofRecord = new ProofExchangeRecord({ + connectionId: connectionRecord.id, + threadId: message.threadId, + parentThreadId: message.thread?.parentThreadId, + state: ProofState.ProposalSent, + autoAcceptProof, + protocolVersion: 'v1', + }) + + await didCommMessageRepository.saveAgentMessage(agentContext, { + agentMessage: message, + associatedRecordId: proofRecord.id, + role: DidCommMessageRole.Sender, + }) + + await proofRepository.save(agentContext, proofRecord) + this.emitStateChangedEvent(agentContext, proofRecord, null) + + return { proofRecord, message } + } + + public async processProposal( + messageContext: InboundMessageContext + ): Promise { + const { message: proposalMessage, connection, agentContext } = messageContext + + const proofRepository = agentContext.dependencyManager.resolve(ProofRepository) + const didCommMessageRepository = agentContext.dependencyManager.resolve(DidCommMessageRepository) + + // TODO: with this method, we should update the credential protocol to use the ConnectionApi, so it + // only depends on the public api, rather than the internal API (this helps with breaking changes) + const connectionService = agentContext.dependencyManager.resolve(ConnectionService) + + agentContext.config.logger.debug(`Processing presentation proposal with message id ${proposalMessage.id}`) + + let proofRecord = await this.findByThreadAndConnectionId(agentContext, proposalMessage.threadId, connection?.id) + + // Proof record already exists, this is a response to an earlier message sent by us + if (proofRecord) { + agentContext.config.logger.debug('Proof record already exists for incoming proposal') + + // Assert + proofRecord.assertState(ProofState.RequestSent) + proofRecord.assertProtocolVersion('v1') + + const previousReceivedMessage = await didCommMessageRepository.findAgentMessage(agentContext, { + associatedRecordId: proofRecord.id, + messageClass: V1ProposePresentationMessage, + }) + const previousSentMessage = await didCommMessageRepository.getAgentMessage(agentContext, { + associatedRecordId: proofRecord.id, + messageClass: V1RequestPresentationMessage, + }) + connectionService.assertConnectionOrServiceDecorator(messageContext, { + previousReceivedMessage, + previousSentMessage, + }) + + // Update record + await didCommMessageRepository.saveOrUpdateAgentMessage(agentContext, { + agentMessage: proposalMessage, + associatedRecordId: proofRecord.id, + role: DidCommMessageRole.Receiver, + }) + await this.updateState(agentContext, proofRecord, ProofState.ProposalReceived) + } else { + agentContext.config.logger.debug('Proof record does not exists yet for incoming proposal') + + // No proof record exists with thread id + proofRecord = new ProofExchangeRecord({ + connectionId: connection?.id, + threadId: proposalMessage.threadId, + parentThreadId: proposalMessage.thread?.parentThreadId, + state: ProofState.ProposalReceived, + protocolVersion: 'v1', + }) + + // Assert + connectionService.assertConnectionOrServiceDecorator(messageContext) + + await didCommMessageRepository.saveOrUpdateAgentMessage(agentContext, { + agentMessage: proposalMessage, + associatedRecordId: proofRecord.id, + role: DidCommMessageRole.Sender, + }) + + // Save record + await proofRepository.save(agentContext, proofRecord) + this.emitStateChangedEvent(agentContext, proofRecord, null) + } + + return proofRecord + } + + public async acceptProposal( + agentContext: AgentContext, + { + proofRecord, + proofFormats, + comment, + autoAcceptProof, + }: ProofProtocolOptions.AcceptProofProposalOptions<[LegacyIndyProofFormatService]> + ): Promise> { + // Assert + proofRecord.assertProtocolVersion('v1') + proofRecord.assertState(ProofState.ProposalReceived) + if (proofFormats) this.assertOnlyIndyFormat(proofFormats) + + const didCommMessageRepository = agentContext.dependencyManager.resolve(DidCommMessageRepository) + + const proposalMessage = await didCommMessageRepository.getAgentMessage(agentContext, { + associatedRecordId: proofRecord.id, + messageClass: V1ProposePresentationMessage, + }) + + const indyFormat = proofFormats?.indy + + // Create a proof request from the preview, so we can let the messages + // be handled using the indy proof format which supports RFC0592 + const requestFromPreview = createRequestFromPreview({ + attributes: proposalMessage.presentationProposal.attributes, + predicates: proposalMessage.presentationProposal.predicates, + name: indyFormat?.name ?? 'Proof Request', + version: indyFormat?.version ?? '1.0', + nonce: await agentContext.wallet.generateNonce(), + }) + + const proposalAttachment = new V1Attachment({ + data: { + json: JsonTransformer.toJSON(requestFromPreview), + }, + }) + + // Create message + const { attachment } = await this.indyProofFormat.acceptProposal(agentContext, { + attachmentId: INDY_PROOF_REQUEST_ATTACHMENT_ID, + proofRecord, + proposalAttachment, + }) + + const requestPresentationMessage = new V1RequestPresentationMessage({ + comment, + requestAttachments: [attachment], + }) + + requestPresentationMessage.setThread({ + threadId: proofRecord.threadId, + parentThreadId: proofRecord.parentThreadId, + }) + + await didCommMessageRepository.saveOrUpdateAgentMessage(agentContext, { + agentMessage: requestPresentationMessage, + associatedRecordId: proofRecord.id, + role: DidCommMessageRole.Sender, + }) + + // Update record + proofRecord.autoAcceptProof = autoAcceptProof ?? proofRecord.autoAcceptProof + await this.updateState(agentContext, proofRecord, ProofState.RequestSent) + + return { message: requestPresentationMessage, proofRecord } + } + + public async negotiateProposal( + agentContext: AgentContext, + { + proofFormats, + proofRecord, + comment, + autoAcceptProof, + }: ProofProtocolOptions.NegotiateProofProposalOptions<[LegacyIndyProofFormatService]> + ): Promise> { + // Assert + proofRecord.assertProtocolVersion('v1') + proofRecord.assertState(ProofState.ProposalReceived) + this.assertOnlyIndyFormat(proofFormats) + + const didCommMessageRepository = agentContext.dependencyManager.resolve(DidCommMessageRepository) + + // Create message + const { attachment } = await this.indyProofFormat.createRequest(agentContext, { + attachmentId: INDY_PROOF_REQUEST_ATTACHMENT_ID, + proofFormats, + proofRecord, + }) + + const requestPresentationMessage = new V1RequestPresentationMessage({ + comment, + requestAttachments: [attachment], + }) + requestPresentationMessage.setThread({ + threadId: proofRecord.threadId, + }) + + await didCommMessageRepository.saveOrUpdateAgentMessage(agentContext, { + agentMessage: requestPresentationMessage, + associatedRecordId: proofRecord.id, + role: DidCommMessageRole.Sender, + }) + + proofRecord.autoAcceptProof = autoAcceptProof ?? proofRecord.autoAcceptProof + await this.updateState(agentContext, proofRecord, ProofState.RequestSent) + + return { message: requestPresentationMessage, proofRecord } + } + + public async createRequest( + agentContext: AgentContext, + { + proofFormats, + connectionRecord, + comment, + parentThreadId, + autoAcceptProof, + }: ProofProtocolOptions.CreateProofRequestOptions<[LegacyIndyProofFormatService]> + ): Promise> { + this.assertOnlyIndyFormat(proofFormats) + + const proofRepository = agentContext.dependencyManager.resolve(ProofRepository) + const didCommMessageRepository = agentContext.dependencyManager.resolve(DidCommMessageRepository) + + if (!proofFormats.indy) { + throw new AriesFrameworkError('Missing indy proof request data for v1 create request') + } + + // Create record + const proofRecord = new ProofExchangeRecord({ + connectionId: connectionRecord?.id, + threadId: utils.uuid(), + parentThreadId, + state: ProofState.RequestSent, + autoAcceptProof, + protocolVersion: 'v1', + }) + + // Create message + const { attachment } = await this.indyProofFormat.createRequest(agentContext, { + attachmentId: INDY_PROOF_REQUEST_ATTACHMENT_ID, + proofFormats, + proofRecord, + }) + + // Construct request message + const message = new V1RequestPresentationMessage({ + id: proofRecord.threadId, + comment, + requestAttachments: [attachment], + }) + + message.setThread({ + threadId: proofRecord.threadId, + parentThreadId: proofRecord.parentThreadId, + }) + + await didCommMessageRepository.saveAgentMessage(agentContext, { + agentMessage: message, + associatedRecordId: proofRecord.id, + role: DidCommMessageRole.Sender, + }) + + await proofRepository.save(agentContext, proofRecord) + this.emitStateChangedEvent(agentContext, proofRecord, null) + + return { message, proofRecord } + } + + public async processRequest( + messageContext: InboundMessageContext + ): Promise { + const { message: proofRequestMessage, connection, agentContext } = messageContext + + const proofRepository = agentContext.dependencyManager.resolve(ProofRepository) + const didCommMessageRepository = agentContext.dependencyManager.resolve(DidCommMessageRepository) + + // TODO: with this method, we should update the credential protocol to use the ConnectionApi, so it + // only depends on the public api, rather than the internal API (this helps with breaking changes) + const connectionService = agentContext.dependencyManager.resolve(ConnectionService) + + agentContext.config.logger.debug(`Processing presentation request with id ${proofRequestMessage.id}`) + + let proofRecord = await this.findByThreadAndConnectionId(agentContext, proofRequestMessage.threadId, connection?.id) + + const requestAttachment = proofRequestMessage.getRequestAttachmentById(INDY_PROOF_REQUEST_ATTACHMENT_ID) + if (!requestAttachment) { + throw new AriesFrameworkError( + `Indy attachment with id ${INDY_PROOF_REQUEST_ATTACHMENT_ID} not found in request message` + ) + } + + // proof record already exists, this means we are the message is sent as reply to a proposal we sent + if (proofRecord) { + const previousReceivedMessage = await didCommMessageRepository.findAgentMessage(agentContext, { + associatedRecordId: proofRecord.id, + messageClass: V1RequestPresentationMessage, + }) + const previousSentMessage = await didCommMessageRepository.getAgentMessage(agentContext, { + associatedRecordId: proofRecord.id, + messageClass: V1ProposePresentationMessage, + }) + + // Assert + proofRecord.assertProtocolVersion('v1') + proofRecord.assertState(ProofState.ProposalSent) + connectionService.assertConnectionOrServiceDecorator(messageContext, { + previousReceivedMessage, + previousSentMessage, + }) + + await this.indyProofFormat.processRequest(agentContext, { + attachment: requestAttachment, + proofRecord, + }) + + await didCommMessageRepository.saveOrUpdateAgentMessage(agentContext, { + agentMessage: proofRequestMessage, + associatedRecordId: proofRecord.id, + role: DidCommMessageRole.Receiver, + }) + await this.updateState(agentContext, proofRecord, ProofState.RequestReceived) + } else { + // No proof record exists with thread id + proofRecord = new ProofExchangeRecord({ + connectionId: connection?.id, + threadId: proofRequestMessage.threadId, + parentThreadId: proofRequestMessage.thread?.parentThreadId, + state: ProofState.RequestReceived, + protocolVersion: 'v1', + }) + + await this.indyProofFormat.processRequest(agentContext, { + attachment: requestAttachment, + proofRecord, + }) + + await didCommMessageRepository.saveAgentMessage(agentContext, { + agentMessage: proofRequestMessage, + associatedRecordId: proofRecord.id, + role: DidCommMessageRole.Receiver, + }) + + // Assert + connectionService.assertConnectionOrServiceDecorator(messageContext) + + // Save in repository + await proofRepository.save(agentContext, proofRecord) + this.emitStateChangedEvent(agentContext, proofRecord, null) + } + + return proofRecord + } + + public async negotiateRequest( + agentContext: AgentContext, + { + proofFormats, + proofRecord, + comment, + autoAcceptProof, + }: ProofProtocolOptions.NegotiateProofRequestOptions<[LegacyIndyProofFormatService]> + ): Promise> { + // Assert + proofRecord.assertProtocolVersion('v1') + proofRecord.assertState(ProofState.RequestReceived) + this.assertOnlyIndyFormat(proofFormats) + + const didCommMessageRepository = agentContext.dependencyManager.resolve(DidCommMessageRepository) + + if (!proofRecord.connectionId) { + throw new AriesFrameworkError( + `No connectionId found for proof record '${proofRecord.id}'. Connection-less verification does not support negotiation.` + ) + } + + if (!proofFormats.indy) { + throw new AriesFrameworkError('Missing indy proof format in v1 negotiate request call.') + } + + const presentationProposal = new V1PresentationPreview({ + attributes: proofFormats.indy?.attributes, + predicates: proofFormats.indy?.predicates, + }) + + // validate input data from user + MessageValidator.validateSync(presentationProposal) + + const message = new V1ProposePresentationMessage({ + comment, + presentationProposal, + }) + message.setThread({ threadId: proofRecord.threadId }) + + await didCommMessageRepository.saveOrUpdateAgentMessage(agentContext, { + agentMessage: message, + associatedRecordId: proofRecord.id, + role: DidCommMessageRole.Sender, + }) + + // Update record + proofRecord.autoAcceptProof = autoAcceptProof ?? proofRecord.autoAcceptProof + await this.updateState(agentContext, proofRecord, ProofState.ProposalSent) + + return { proofRecord, message: message } + } + + public async acceptRequest( + agentContext: AgentContext, + { + proofRecord, + proofFormats, + autoAcceptProof, + comment, + }: ProofProtocolOptions.AcceptProofRequestOptions<[LegacyIndyProofFormatService]> + ): Promise> { + // Assert + proofRecord.assertProtocolVersion('v1') + proofRecord.assertState(ProofState.RequestReceived) + + const didCommMessageRepository = agentContext.dependencyManager.resolve(DidCommMessageRepository) + + const requestMessage = await didCommMessageRepository.getAgentMessage(agentContext, { + associatedRecordId: proofRecord.id, + messageClass: V1RequestPresentationMessage, + }) + const proposalMessage = await didCommMessageRepository.findAgentMessage(agentContext, { + associatedRecordId: proofRecord.id, + messageClass: V1ProposePresentationMessage, + }) + + const requestAttachment = requestMessage.getRequestAttachmentById(INDY_PROOF_REQUEST_ATTACHMENT_ID) + const indyProofRequest = requestMessage.indyProofRequest + + if (!requestAttachment || !indyProofRequest) { + throw new V1PresentationProblemReportError( + `Missing indy attachment in request message for presentation with thread id ${proofRecord.threadId}`, + { problemCode: PresentationProblemReportReason.Abandoned } + ) + } + + const proposalAttachment = proposalMessage + ? new V1Attachment({ + data: { + json: JsonTransformer.toJSON( + createRequestFromPreview({ + attributes: proposalMessage.presentationProposal?.attributes, + predicates: proposalMessage.presentationProposal?.predicates, + name: indyProofRequest.name, + nonce: indyProofRequest.nonce, + version: indyProofRequest.nonce, + }) + ), + }, + }) + : undefined + + const { attachment } = await this.indyProofFormat.acceptRequest(agentContext, { + attachmentId: INDY_PROOF_ATTACHMENT_ID, + requestAttachment, + proposalAttachment, + proofFormats, + proofRecord, + }) + + const message = new V1PresentationMessage({ + comment, + presentationAttachments: [attachment], + }) + message.setThread({ threadId: proofRecord.threadId }) + + await didCommMessageRepository.saveAgentMessage(agentContext, { + agentMessage: message, + associatedRecordId: proofRecord.id, + role: DidCommMessageRole.Sender, + }) + + // Update record + proofRecord.autoAcceptProof = autoAcceptProof ?? proofRecord.autoAcceptProof + await this.updateState(agentContext, proofRecord, ProofState.PresentationSent) + + return { message, proofRecord } + } + + public async getCredentialsForRequest( + agentContext: AgentContext, + { proofRecord, proofFormats }: ProofProtocolOptions.GetCredentialsForRequestOptions<[LegacyIndyProofFormatService]> + ): Promise> { + if (proofFormats) this.assertOnlyIndyFormat(proofFormats) + + const didCommMessageRepository = agentContext.dependencyManager.resolve(DidCommMessageRepository) + + const requestMessage = await didCommMessageRepository.getAgentMessage(agentContext, { + associatedRecordId: proofRecord.id, + messageClass: V1RequestPresentationMessage, + }) + + const proposalMessage = await didCommMessageRepository.findAgentMessage(agentContext, { + associatedRecordId: proofRecord.id, + messageClass: V1ProposePresentationMessage, + }) + + const requestAttachment = requestMessage.getRequestAttachmentById(INDY_PROOF_REQUEST_ATTACHMENT_ID) + const indyProofRequest = requestMessage.indyProofRequest + + if (!requestAttachment || !indyProofRequest) { + throw new AriesFrameworkError( + `Missing indy attachment in request message for presentation with thread id ${proofRecord.threadId}` + ) + } + + const proposalAttachment = proposalMessage + ? new V1Attachment({ + data: { + json: JsonTransformer.toJSON( + createRequestFromPreview({ + attributes: proposalMessage.presentationProposal?.attributes, + predicates: proposalMessage.presentationProposal?.predicates, + name: indyProofRequest.name, + nonce: indyProofRequest.nonce, + version: indyProofRequest.nonce, + }) + ), + }, + }) + : undefined + + const credentialForRequest = await this.indyProofFormat.getCredentialsForRequest(agentContext, { + proofRecord, + requestAttachment, + proofFormats, + proposalAttachment, + }) + + return { + proofFormats: { + indy: credentialForRequest, + }, + } + } + + public async selectCredentialsForRequest( + agentContext: AgentContext, + { + proofRecord, + proofFormats, + }: ProofProtocolOptions.SelectCredentialsForRequestOptions<[LegacyIndyProofFormatService]> + ): Promise> { + if (proofFormats) this.assertOnlyIndyFormat(proofFormats) + + const didCommMessageRepository = agentContext.dependencyManager.resolve(DidCommMessageRepository) + + const requestMessage = await didCommMessageRepository.getAgentMessage(agentContext, { + associatedRecordId: proofRecord.id, + messageClass: V1RequestPresentationMessage, + }) + + const proposalMessage = await didCommMessageRepository.findAgentMessage(agentContext, { + associatedRecordId: proofRecord.id, + messageClass: V1ProposePresentationMessage, + }) + + const requestAttachment = requestMessage.getRequestAttachmentById(INDY_PROOF_REQUEST_ATTACHMENT_ID) + const indyProofRequest = requestMessage.indyProofRequest + + if (!requestAttachment || !indyProofRequest) { + throw new AriesFrameworkError( + `Missing indy attachment in request message for presentation with thread id ${proofRecord.threadId}` + ) + } + + const proposalAttachment = proposalMessage + ? new V1Attachment({ + data: { + json: JsonTransformer.toJSON( + createRequestFromPreview({ + attributes: proposalMessage.presentationProposal?.attributes, + predicates: proposalMessage.presentationProposal?.predicates, + name: indyProofRequest.name, + nonce: indyProofRequest.nonce, + version: indyProofRequest.nonce, + }) + ), + }, + }) + : undefined + + const selectedCredentials = await this.indyProofFormat.selectCredentialsForRequest(agentContext, { + proofFormats, + proofRecord, + requestAttachment, + proposalAttachment, + }) + + return { + proofFormats: { + indy: selectedCredentials, + }, + } + } + + public async processPresentation( + messageContext: InboundMessageContext + ): Promise { + const { message: presentationMessage, connection, agentContext } = messageContext + + agentContext.config.logger.debug(`Processing presentation with message id ${presentationMessage.id}`) + + const didCommMessageRepository = agentContext.dependencyManager.resolve(DidCommMessageRepository) + + // TODO: with this method, we should update the credential protocol to use the ConnectionApi, so it + // only depends on the public api, rather than the internal API (this helps with breaking changes) + const connectionService = agentContext.dependencyManager.resolve(ConnectionService) + + const proofRecord = await this.getByThreadAndConnectionId( + agentContext, + presentationMessage.threadId, + connection?.id + ) + + const proposalMessage = await didCommMessageRepository.findAgentMessage(agentContext, { + associatedRecordId: proofRecord.id, + messageClass: V1ProposePresentationMessage, + }) + + const requestMessage = await didCommMessageRepository.getAgentMessage(agentContext, { + associatedRecordId: proofRecord.id, + messageClass: V1RequestPresentationMessage, + }) + + // Assert + proofRecord.assertState(ProofState.RequestSent) + proofRecord.assertProtocolVersion('v1') + connectionService.assertConnectionOrServiceDecorator(messageContext, { + previousReceivedMessage: proposalMessage, + previousSentMessage: requestMessage, + }) + + const presentationAttachment = presentationMessage.getPresentationAttachmentById(INDY_PROOF_ATTACHMENT_ID) + if (!presentationAttachment) { + throw new AriesFrameworkError('Missing indy proof attachment in processPresentation') + } + + const requestAttachment = requestMessage.getRequestAttachmentById(INDY_PROOF_REQUEST_ATTACHMENT_ID) + if (!requestAttachment) { + throw new AriesFrameworkError('Missing indy proof request attachment in processPresentation') + } + + const isValid = await this.indyProofFormat.processPresentation(agentContext, { + proofRecord, + attachment: presentationAttachment, + requestAttachment, + }) + + await didCommMessageRepository.saveAgentMessage(agentContext, { + agentMessage: presentationMessage, + associatedRecordId: proofRecord.id, + role: DidCommMessageRole.Receiver, + }) + + // Update record + proofRecord.isVerified = isValid + await this.updateState(agentContext, proofRecord, ProofState.PresentationReceived) + + return proofRecord + } + + public async acceptPresentation( + agentContext: AgentContext, + { proofRecord }: ProofProtocolOptions.AcceptPresentationOptions + ): Promise> { + agentContext.config.logger.debug(`Creating presentation ack for proof record with id ${proofRecord.id}`) + + // Assert + proofRecord.assertProtocolVersion('v1') + proofRecord.assertState(ProofState.PresentationReceived) + + // Create message + const ackMessage = new V1PresentationAckMessage({ + status: AckStatus.OK, + threadId: proofRecord.threadId, + }) + + // Update record + await this.updateState(agentContext, proofRecord, ProofState.Done) + + return { message: ackMessage, proofRecord } + } + + public async processAck( + messageContext: InboundMessageContext + ): Promise { + const { message: presentationAckMessage, connection, agentContext } = messageContext + + agentContext.config.logger.debug(`Processing presentation ack with message id ${presentationAckMessage.id}`) + + const didCommMessageRepository = agentContext.dependencyManager.resolve(DidCommMessageRepository) + + // TODO: with this method, we should update the credential protocol to use the ConnectionApi, so it + // only depends on the public api, rather than the internal API (this helps with breaking changes) + const connectionService = agentContext.dependencyManager.resolve(ConnectionService) + + const proofRecord = await this.getByThreadAndConnectionId( + agentContext, + presentationAckMessage.threadId, + connection?.id + ) + + const previousReceivedMessage = await didCommMessageRepository.getAgentMessage(agentContext, { + associatedRecordId: proofRecord.id, + messageClass: V1RequestPresentationMessage, + }) + + const previousSentMessage = await didCommMessageRepository.getAgentMessage(agentContext, { + associatedRecordId: proofRecord.id, + messageClass: V1PresentationMessage, + }) + + // Assert + proofRecord.assertProtocolVersion('v1') + proofRecord.assertState(ProofState.PresentationSent) + connectionService.assertConnectionOrServiceDecorator(messageContext, { + previousReceivedMessage, + previousSentMessage, + }) + + // Update record + await this.updateState(agentContext, proofRecord, ProofState.Done) + + return proofRecord + } + + public async createProblemReport( + agentContext: AgentContext, + { proofRecord, description }: ProofProtocolOptions.CreateProofProblemReportOptions + ): Promise> { + const message = new V1PresentationProblemReportMessage({ + description: { + code: PresentationProblemReportReason.Abandoned, + en: description, + }, + }) + + message.setThread({ + threadId: proofRecord.threadId, + parentThreadId: proofRecord.parentThreadId, + }) + + return { + proofRecord, + message, + } + } + + public async shouldAutoRespondToProposal( + agentContext: AgentContext, + options: { + proofRecord: ProofExchangeRecord + proposalMessage: V1ProposePresentationMessage + } + ): Promise { + const { proofRecord, proposalMessage } = options + + const proofsModuleConfig = agentContext.dependencyManager.resolve(ProofsModuleConfig) + + const autoAccept = composeProofAutoAccept(proofRecord.autoAcceptProof, proofsModuleConfig.autoAcceptProofs) + + // Handle always / never cases + if (autoAccept === AutoAcceptProof.Always) return true + if (autoAccept === AutoAcceptProof.Never) return false + + // We are in the ContentApproved case. We need to make sure we've sent a request, and it matches the proposal + const requestMessage = await this.findRequestMessage(agentContext, proofRecord.id) + const requestAttachment = requestMessage?.getRequestAttachmentById(INDY_PROOF_REQUEST_ATTACHMENT_ID) + if (!requestAttachment) return false + + const rfc0592Proposal = JsonTransformer.toJSON( + createRequestFromPreview({ + name: 'Proof Request', + nonce: await agentContext.wallet.generateNonce(), + version: '1.0', + attributes: proposalMessage.presentationProposal.attributes, + predicates: proposalMessage.presentationProposal.predicates, + }) + ) + + return this.indyProofFormat.shouldAutoRespondToProposal(agentContext, { + proofRecord, + proposalAttachment: new V1Attachment({ + data: { + json: rfc0592Proposal, + }, + }), + requestAttachment, + }) + } + + public async shouldAutoRespondToRequest( + agentContext: AgentContext, + options: { + proofRecord: ProofExchangeRecord + requestMessage: V1RequestPresentationMessage + } + ): Promise { + const { proofRecord, requestMessage } = options + + const proofsModuleConfig = agentContext.dependencyManager.resolve(ProofsModuleConfig) + + const autoAccept = composeProofAutoAccept(proofRecord.autoAcceptProof, proofsModuleConfig.autoAcceptProofs) + + // Handle always / never cases + if (autoAccept === AutoAcceptProof.Always) return true + if (autoAccept === AutoAcceptProof.Never) return false + + const requestAttachment = requestMessage.getRequestAttachmentById(INDY_PROOF_REQUEST_ATTACHMENT_ID) + if (!requestAttachment) return false + + // We are in the ContentApproved case. We need to make sure we've sent a proposal, and it matches the request + const proposalMessage = await this.findProposalMessage(agentContext, proofRecord.id) + if (!proposalMessage) return false + + const rfc0592Proposal = createRequestFromPreview({ + name: 'Proof Request', + nonce: await agentContext.wallet.generateNonce(), + version: '1.0', + attributes: proposalMessage.presentationProposal.attributes, + predicates: proposalMessage.presentationProposal.predicates, + }) + + return this.indyProofFormat.shouldAutoRespondToRequest(agentContext, { + proofRecord, + proposalAttachment: new V1Attachment({ + data: { + base64: JsonEncoder.toBase64(rfc0592Proposal), + }, + }), + requestAttachment, + }) + } + + public async shouldAutoRespondToPresentation( + agentContext: AgentContext, + options: { + proofRecord: ProofExchangeRecord + presentationMessage: V1PresentationMessage + } + ): Promise { + const { proofRecord, presentationMessage } = options + + const proofsModuleConfig = agentContext.dependencyManager.resolve(ProofsModuleConfig) + + const autoAccept = composeProofAutoAccept(proofRecord.autoAcceptProof, proofsModuleConfig.autoAcceptProofs) + + // Handle always / never cases + if (autoAccept === AutoAcceptProof.Always) return true + if (autoAccept === AutoAcceptProof.Never) return false + + const presentationAttachment = presentationMessage.getPresentationAttachmentById(INDY_PROOF_ATTACHMENT_ID) + if (!presentationAttachment) return false + + // We are in the ContentApproved case. We need to make sure we've sent a request, and it matches the presentation + const requestMessage = await this.findRequestMessage(agentContext, proofRecord.id) + const requestAttachment = requestMessage?.getRequestAttachmentById(INDY_PROOF_REQUEST_ATTACHMENT_ID) + if (!requestAttachment) return false + + // We are in the ContentApproved case. We need to make sure we've sent a proposal, and it matches the request + const proposalMessage = await this.findProposalMessage(agentContext, proofRecord.id) + + const rfc0592Proposal = proposalMessage + ? JsonTransformer.toJSON( + createRequestFromPreview({ + name: 'Proof Request', + nonce: await agentContext.wallet.generateNonce(), + version: '1.0', + attributes: proposalMessage.presentationProposal.attributes, + predicates: proposalMessage.presentationProposal.predicates, + }) + ) + : undefined + + return this.indyProofFormat.shouldAutoRespondToPresentation(agentContext, { + proofRecord, + requestAttachment, + presentationAttachment, + proposalAttachment: new V1Attachment({ + data: { + json: rfc0592Proposal, + }, + }), + }) + } + + public async findProposalMessage( + agentContext: AgentContext, + proofRecordId: string + ): Promise { + const didCommMessageRepository = agentContext.dependencyManager.resolve(DidCommMessageRepository) + return await didCommMessageRepository.findAgentMessage(agentContext, { + associatedRecordId: proofRecordId, + messageClass: V1ProposePresentationMessage, + }) + } + + public async findRequestMessage( + agentContext: AgentContext, + proofRecordId: string + ): Promise { + const didCommMessageRepository = agentContext.dependencyManager.resolve(DidCommMessageRepository) + return await didCommMessageRepository.findAgentMessage(agentContext, { + associatedRecordId: proofRecordId, + messageClass: V1RequestPresentationMessage, + }) + } + + public async findPresentationMessage( + agentContext: AgentContext, + proofRecordId: string + ): Promise { + const didCommMessageRepository = agentContext.dependencyManager.resolve(DidCommMessageRepository) + return await didCommMessageRepository.findAgentMessage(agentContext, { + associatedRecordId: proofRecordId, + messageClass: V1PresentationMessage, + }) + } + + public async getFormatData( + agentContext: AgentContext, + proofRecordId: string + ): Promise> { + // TODO: we could looking at fetching all record using a single query and then filtering based on the type of the message. + const [proposalMessage, requestMessage, presentationMessage] = await Promise.all([ + this.findProposalMessage(agentContext, proofRecordId), + this.findRequestMessage(agentContext, proofRecordId), + this.findPresentationMessage(agentContext, proofRecordId), + ]) + + let indyProposeProof = undefined + const indyRequestProof = requestMessage?.indyProofRequest ?? undefined + const indyPresentProof = presentationMessage?.indyProof ?? undefined + + if (proposalMessage && indyRequestProof) { + indyProposeProof = createRequestFromPreview({ + name: indyRequestProof.name, + version: indyRequestProof.version, + nonce: indyRequestProof.nonce, + attributes: proposalMessage.presentationProposal.attributes, + predicates: proposalMessage.presentationProposal.predicates, + }) + } else if (proposalMessage) { + indyProposeProof = createRequestFromPreview({ + name: 'Proof Request', + version: '1.0', + nonce: await agentContext.wallet.generateNonce(), + attributes: proposalMessage.presentationProposal.attributes, + predicates: proposalMessage.presentationProposal.predicates, + }) + } + + return { + proposal: proposalMessage + ? { + indy: indyProposeProof, + } + : undefined, + request: requestMessage + ? { + indy: indyRequestProof, + } + : undefined, + presentation: presentationMessage + ? { + indy: indyPresentProof, + } + : undefined, + } + } + + private assertOnlyIndyFormat(proofFormats: Record) { + const formatKeys = Object.keys(proofFormats) + + // It's fine to not have any formats in some cases, if indy is required the method that calls this should check for this + if (formatKeys.length === 0) return + + if (formatKeys.length !== 1 || !formatKeys.includes('indy')) { + throw new AriesFrameworkError('Only indy proof format is supported for present proof v1 protocol') + } + } +} diff --git a/packages/core/src/modules/proofs/__tests__/V1ProofService.test.ts b/packages/anoncreds/src/protocols/proofs/v1/__tests__/V1ProofProtocol.test.ts similarity index 61% rename from packages/core/src/modules/proofs/__tests__/V1ProofService.test.ts rename to packages/anoncreds/src/protocols/proofs/v1/__tests__/V1ProofProtocol.test.ts index 6aceca2bd4..09297563a0 100644 --- a/packages/core/src/modules/proofs/__tests__/V1ProofService.test.ts +++ b/packages/anoncreds/src/protocols/proofs/v1/__tests__/V1ProofProtocol.test.ts @@ -1,50 +1,43 @@ -import type { AgentContext } from '../../../agent' -import type { Wallet } from '../../../wallet/Wallet' -import type { CredentialRepository } from '../../credentials/repository' -import type { ProofStateChangedEvent } from '../ProofEvents' -import type { CustomProofTags } from './../repository/ProofExchangeRecord' +import type { CustomProofTags, AgentConfig, AgentContext, ProofStateChangedEvent } from '../../../../../../core/src' import { Subject } from 'rxjs' -import { getAgentConfig, getAgentContext, getMockConnection, mockFunction } from '../../../../tests/helpers' -import { EventEmitter } from '../../../agent/EventEmitter' -import { InboundMessageContext } from '../../../agent/models/InboundMessageContext' -import { V1Attachment, V1AttachmentData } from '../../../decorators/attachment/V1Attachment' -import { DidCommMessageRepository } from '../../../storage' -import { ConnectionService, DidExchangeState } from '../../connections' -import { IndyHolderService } from '../../indy/services/IndyHolderService' -import { IndyRevocationService } from '../../indy/services/IndyRevocationService' -import { IndyLedgerService } from '../../ledger/services' -import { ProofEventTypes } from '../ProofEvents' -import { PresentationProblemReportReason } from '../errors/PresentationProblemReportReason' -import { IndyProofFormatService } from '../formats/indy/IndyProofFormatService' -import { ProofState } from '../models/ProofState' -import { V1ProofService } from '../protocol/v1' -import { INDY_PROOF_REQUEST_ATTACHMENT_ID, V1RequestPresentationMessage } from '../protocol/v1/messages' -import { V1PresentationProblemReportMessage } from '../protocol/v1/messages/V1PresentationProblemReportMessage' -import { ProofExchangeRecord } from '../repository/ProofExchangeRecord' -import { ProofRepository } from '../repository/ProofRepository' - -import { credDef } from './fixtures' +import { + DidExchangeState, + V1Attachment, + V1AttachmentData, + ProofState, + ProofExchangeRecord, + InboundMessageContext, + ProofEventTypes, + PresentationProblemReportReason, + EventEmitter, +} from '../../../../../../core/src' +import { ConnectionService } from '../../../../../../core/src/modules/connections/services/ConnectionService' +import { ProofRepository } from '../../../../../../core/src/modules/proofs/repository/ProofRepository' +import { DidCommMessageRepository } from '../../../../../../core/src/storage/didcomm/DidCommMessageRepository' +import { getMockConnection, getAgentConfig, getAgentContext, mockFunction } from '../../../../../../core/tests' +import { LegacyIndyProofFormatService } from '../../../../formats/LegacyIndyProofFormatService' +import { V1ProofProtocol } from '../V1ProofProtocol' +import { INDY_PROOF_REQUEST_ATTACHMENT_ID, V1RequestPresentationMessage } from '../messages' +import { V1PresentationProblemReportMessage } from '../messages/V1PresentationProblemReportMessage' // Mock classes -jest.mock('../repository/ProofRepository') -jest.mock('../../../modules/ledger/services/IndyLedgerService') -jest.mock('../../indy/services/IndyHolderService') -jest.mock('../../indy/services/IndyIssuerService') -jest.mock('../../indy/services/IndyVerifierService') -jest.mock('../../indy/services/IndyRevocationService') -jest.mock('../../connections/services/ConnectionService') -jest.mock('../../../storage/Repository') +jest.mock('../../../../../../core/src/modules/proofs/repository/ProofRepository') +jest.mock('../../../../formats/LegacyIndyProofFormatService') +jest.mock('../../../../../../core/src/storage/didcomm/DidCommMessageRepository') +jest.mock('../../../../../../core/src/modules/connections/services/ConnectionService') // Mock typed object const ProofRepositoryMock = ProofRepository as jest.Mock -const IndyLedgerServiceMock = IndyLedgerService as jest.Mock -const IndyHolderServiceMock = IndyHolderService as jest.Mock -const IndyRevocationServiceMock = IndyRevocationService as jest.Mock const connectionServiceMock = ConnectionService as jest.Mock const didCommMessageRepositoryMock = DidCommMessageRepository as jest.Mock -const indyProofFormatServiceMock = IndyProofFormatService as jest.Mock +const indyProofFormatServiceMock = LegacyIndyProofFormatService as jest.Mock + +const proofRepository = new ProofRepositoryMock() +const connectionService = new connectionServiceMock() +const didCommMessageRepository = new didCommMessageRepositoryMock() +const indyProofFormatService = new indyProofFormatServiceMock() const connection = getMockConnection({ id: '123', @@ -78,7 +71,7 @@ const mockProofExchangeRecord = ({ } = {}) => { const requestPresentationMessage = new V1RequestPresentationMessage({ comment: 'some comment', - requestPresentationAttachments: [requestAttachment], + requestAttachments: [requestAttachment], }) const proofRecord = new ProofExchangeRecord({ @@ -93,58 +86,37 @@ const mockProofExchangeRecord = ({ return proofRecord } -describe('V1ProofService', () => { - let proofRepository: ProofRepository - let proofService: V1ProofService - let ledgerService: IndyLedgerService - let wallet: Wallet - let indyHolderService: IndyHolderService - let indyRevocationService: IndyRevocationService +describe('V1ProofProtocol', () => { let eventEmitter: EventEmitter - let credentialRepository: CredentialRepository - let connectionService: ConnectionService - let didCommMessageRepository: DidCommMessageRepository - let indyProofFormatService: IndyProofFormatService + let agentConfig: AgentConfig let agentContext: AgentContext + let proofProtocol: V1ProofProtocol beforeEach(() => { - const agentConfig = getAgentConfig('V1ProofServiceTest') - agentContext = getAgentContext() - proofRepository = new ProofRepositoryMock() - indyHolderService = new IndyHolderServiceMock() - indyRevocationService = new IndyRevocationServiceMock() - ledgerService = new IndyLedgerServiceMock() + // real objects + agentConfig = getAgentConfig('V1ProofProtocolTest') eventEmitter = new EventEmitter(agentConfig.agentDependencies, new Subject()) - connectionService = new connectionServiceMock() - didCommMessageRepository = new didCommMessageRepositoryMock() - indyProofFormatService = new indyProofFormatServiceMock() - agentContext = getAgentContext() - - proofService = new V1ProofService( - proofRepository, - didCommMessageRepository, - ledgerService, - wallet, + + agentContext = getAgentContext({ + registerInstances: [ + [ProofRepository, proofRepository], + [DidCommMessageRepository, didCommMessageRepository], + [EventEmitter, eventEmitter], + [ConnectionService, connectionService], + ], agentConfig, - connectionService, - eventEmitter, - credentialRepository, - indyProofFormatService, - indyHolderService, - indyRevocationService - ) - - mockFunction(ledgerService.getCredentialDefinition).mockReturnValue(Promise.resolve(credDef)) + }) + proofProtocol = new V1ProofProtocol({ indyProofFormat: indyProofFormatService }) }) - describe('processProofRequest', () => { + describe('processRequest', () => { let presentationRequest: V1RequestPresentationMessage let messageContext: InboundMessageContext beforeEach(() => { presentationRequest = new V1RequestPresentationMessage({ comment: 'abcd', - requestPresentationAttachments: [requestAttachment], + requestAttachments: [requestAttachment], }) messageContext = new InboundMessageContext(presentationRequest, { connection, @@ -156,7 +128,7 @@ describe('V1ProofService', () => { const repositorySaveSpy = jest.spyOn(proofRepository, 'save') // when - const returnedProofExchangeRecord = await proofService.processRequest(messageContext) + const returnedProofExchangeRecord = await proofProtocol.processRequest(messageContext) // then const expectedProofExchangeRecord = { @@ -178,7 +150,7 @@ describe('V1ProofService', () => { eventEmitter.on(ProofEventTypes.ProofStateChanged, eventListenerMock) // when - await proofService.processRequest(messageContext) + await proofProtocol.processRequest(messageContext) // then expect(eventListenerMock).toHaveBeenCalledWith({ @@ -261,7 +233,7 @@ describe('V1ProofService', () => { mockFunction(proofRepository.getSingleByQuery).mockReturnValue(Promise.resolve(proof)) // when - const returnedCredentialRecord = await proofService.processProblemReport(messageContext) + const returnedCredentialRecord = await proofProtocol.processProblemReport(messageContext) // then const expectedCredentialRecord = { diff --git a/packages/anoncreds/src/protocols/proofs/v1/__tests__/v1-connectionless-proofs.e2e.test.ts b/packages/anoncreds/src/protocols/proofs/v1/__tests__/v1-connectionless-proofs.e2e.test.ts new file mode 100644 index 0000000000..2b95a0efbb --- /dev/null +++ b/packages/anoncreds/src/protocols/proofs/v1/__tests__/v1-connectionless-proofs.e2e.test.ts @@ -0,0 +1,552 @@ +import type { SubjectMessage } from '../../../../../../../tests/transport/SubjectInboundTransport' +import type { AnonCredsTestsAgent } from '../../../../../tests/legacyAnonCredsSetup' + +import { Subject } from 'rxjs' + +import { SubjectInboundTransport } from '../../../../../../../tests/transport/SubjectInboundTransport' +import { SubjectOutboundTransport } from '../../../../../../../tests/transport/SubjectOutboundTransport' +import { + CredentialEventTypes, + Agent, + AutoAcceptProof, + ProofState, + HandshakeProtocol, + MediatorPickupStrategy, + LinkedAttachment, + V1Attachment, + V1AttachmentData, + ProofEventTypes, + MediatorModule, + MediationRecipientModule, +} from '../../../../../../core/src' +import { uuid } from '../../../../../../core/src/utils/uuid' +import { + testLogger, + waitForProofExchangeRecordSubject, + getAgentOptions, + makeConnection, + setupEventReplaySubjects, +} from '../../../../../../core/tests' +import { getIndySdkModules } from '../../../../../../indy-sdk/tests/setupIndySdkModule' +import { + getLegacyAnonCredsModules, + issueLegacyAnonCredsCredential, + prepareForAnonCredsIssuance, + setupAnonCredsTests, +} from '../../../../../tests/legacyAnonCredsSetup' +import { V1CredentialPreview } from '../../../credentials/v1' + +describe('V1 Proofs - Connectionless - Indy', () => { + let agents: Agent[] + + afterEach(async () => { + for (const agent of agents) { + await agent.shutdown() + await agent.wallet.delete() + } + }) + + // new method to test the return route and mediator together + const connectionlessTest = async (returnRoute?: boolean) => { + const { + holderAgent: aliceAgent, + issuerAgent: faberAgent, + holderReplay: aliceReplay, + credentialDefinitionId, + issuerReplay: faberReplay, + issuerHolderConnectionId: faberConnectionId, + } = await setupAnonCredsTests({ + issuerName: 'Faber v1 connection-less Proofs - Never', + holderName: 'Alice v1 connection-less Proofs - Never', + autoAcceptProofs: AutoAcceptProof.Never, + attributeNames: ['name', 'age'], + }) + + await issueLegacyAnonCredsCredential({ + issuerAgent: faberAgent, + holderAgent: aliceAgent, + issuerReplay: faberReplay, + holderReplay: aliceReplay, + issuerHolderConnectionId: faberConnectionId, + offer: { + credentialDefinitionId, + attributes: [ + { + name: 'name', + value: 'John', + }, + { + name: 'age', + value: '99', + }, + ], + }, + }) + + agents = [aliceAgent, faberAgent] + testLogger.test('Faber sends presentation request to Alice') + + // eslint-disable-next-line prefer-const + let { proofRecord: faberProofExchangeRecord, message } = await faberAgent.proofs.createRequest({ + protocolVersion: 'v1', + proofFormats: { + indy: { + name: 'test-proof-request', + version: '1.0', + requested_attributes: { + name: { + name: 'name', + restrictions: [ + { + cred_def_id: credentialDefinitionId, + }, + ], + }, + }, + requested_predicates: { + age: { + name: 'age', + p_type: '>=', + p_value: 50, + restrictions: [ + { + cred_def_id: credentialDefinitionId, + }, + ], + }, + }, + }, + }, + }) + + const { message: requestMessage } = await faberAgent.oob.createLegacyConnectionlessInvitation({ + recordId: faberProofExchangeRecord.id, + message, + domain: 'https://a-domain.com', + }) + await aliceAgent.receiveMessage(requestMessage.toJSON()) + + testLogger.test('Alice waits for presentation request from Faber') + let aliceProofExchangeRecord = await waitForProofExchangeRecordSubject(aliceReplay, { + state: ProofState.RequestReceived, + }) + + testLogger.test('Alice accepts presentation request from Faber') + const requestedCredentials = await aliceAgent.proofs.selectCredentialsForRequest({ + proofRecordId: aliceProofExchangeRecord.id, + }) + + await aliceAgent.proofs.acceptRequest({ + proofRecordId: aliceProofExchangeRecord.id, + useReturnRoute: returnRoute, + proofFormats: { indy: requestedCredentials.proofFormats.indy }, + }) + + testLogger.test('Faber waits for presentation from Alice') + faberProofExchangeRecord = await waitForProofExchangeRecordSubject(faberReplay, { + threadId: aliceProofExchangeRecord.threadId, + state: ProofState.PresentationReceived, + }) + + const sentPresentationMessage = aliceAgent.proofs.findPresentationMessage(aliceProofExchangeRecord.id) + // assert presentation is valid + expect(faberProofExchangeRecord.isVerified).toBe(true) + + // Faber accepts presentation + await faberAgent.proofs.acceptPresentation({ proofRecordId: faberProofExchangeRecord.id }) + + // Alice waits till it receives presentation ack + aliceProofExchangeRecord = await waitForProofExchangeRecordSubject(aliceReplay, { + threadId: aliceProofExchangeRecord.threadId, + state: ProofState.Done, + }) + return sentPresentationMessage + } + + test('Faber starts with connection-less proof requests to Alice', async () => { + await connectionlessTest() + }) + + test('Faber starts with connection-less proof requests to Alice with auto-accept enabled', async () => { + const { + holderAgent: aliceAgent, + issuerAgent: faberAgent, + holderReplay: aliceReplay, + credentialDefinitionId, + issuerReplay: faberReplay, + issuerHolderConnectionId: faberConnectionId, + } = await setupAnonCredsTests({ + issuerName: 'Faber v1 connection-less Proofs - Always', + holderName: 'Alice v1 connection-less Proofs - Always', + autoAcceptProofs: AutoAcceptProof.Always, + attributeNames: ['name', 'age'], + }) + + await issueLegacyAnonCredsCredential({ + issuerAgent: faberAgent, + holderAgent: aliceAgent, + issuerReplay: faberReplay, + holderReplay: aliceReplay, + issuerHolderConnectionId: faberConnectionId, + offer: { + credentialDefinitionId, + attributes: [ + { + name: 'name', + value: 'John', + }, + { + name: 'age', + value: '99', + }, + ], + }, + }) + + agents = [aliceAgent, faberAgent] + + const { message, proofRecord: faberProofExchangeRecord } = await faberAgent.proofs.createRequest({ + protocolVersion: 'v1', + proofFormats: { + indy: { + name: 'test-proof-request', + version: '1.0', + requested_attributes: { + name: { + name: 'name', + restrictions: [ + { + cred_def_id: credentialDefinitionId, + }, + ], + }, + }, + requested_predicates: { + age: { + name: 'age', + p_type: '>=', + p_value: 50, + restrictions: [ + { + cred_def_id: credentialDefinitionId, + }, + ], + }, + }, + }, + }, + autoAcceptProof: AutoAcceptProof.ContentApproved, + }) + + const { message: requestMessage } = await faberAgent.oob.createLegacyConnectionlessInvitation({ + recordId: faberProofExchangeRecord.id, + message, + domain: 'https://a-domain.com', + }) + + await aliceAgent.receiveMessage(requestMessage.toJSON()) + + await waitForProofExchangeRecordSubject(aliceReplay, { + state: ProofState.Done, + threadId: message.threadId, + }) + + await waitForProofExchangeRecordSubject(faberReplay, { + state: ProofState.Done, + threadId: message.threadId, + }) + }) + + test('Faber starts with connection-less proof requests to Alice with auto-accept enabled and without an outbound transport', async () => { + const { + holderAgent: aliceAgent, + issuerAgent: faberAgent, + holderReplay: aliceReplay, + credentialDefinitionId, + issuerReplay: faberReplay, + issuerHolderConnectionId: faberConnectionId, + } = await setupAnonCredsTests({ + issuerName: 'Faber v1 connection-less Proofs - Always', + holderName: 'Alice v1 connection-less Proofs - Always', + autoAcceptProofs: AutoAcceptProof.Always, + attributeNames: ['name', 'age'], + }) + + await issueLegacyAnonCredsCredential({ + issuerAgent: faberAgent, + holderAgent: aliceAgent, + issuerReplay: faberReplay, + holderReplay: aliceReplay, + issuerHolderConnectionId: faberConnectionId, + offer: { + credentialDefinitionId, + attributes: [ + { + name: 'name', + value: 'John', + }, + { + name: 'age', + value: '99', + }, + ], + }, + }) + + agents = [aliceAgent, faberAgent] + + const { message, proofRecord: faberProofExchangeRecord } = await faberAgent.proofs.createRequest({ + protocolVersion: 'v1', + proofFormats: { + indy: { + name: 'test-proof-request', + version: '1.0', + requested_attributes: { + name: { + name: 'name', + restrictions: [ + { + cred_def_id: credentialDefinitionId, + }, + ], + }, + }, + requested_predicates: { + age: { + name: 'age', + p_type: '>=', + p_value: 50, + restrictions: [ + { + cred_def_id: credentialDefinitionId, + }, + ], + }, + }, + }, + }, + autoAcceptProof: AutoAcceptProof.ContentApproved, + }) + + const { message: requestMessage } = await faberAgent.oob.createLegacyConnectionlessInvitation({ + recordId: faberProofExchangeRecord.id, + message, + domain: 'https://a-domain.com', + }) + + for (const transport of faberAgent.outboundTransports) { + await faberAgent.unregisterOutboundTransport(transport) + } + + await aliceAgent.receiveMessage(requestMessage.toJSON()) + + await waitForProofExchangeRecordSubject(aliceReplay, { + state: ProofState.Done, + threadId: requestMessage.threadId, + }) + + await waitForProofExchangeRecordSubject(faberReplay, { + state: ProofState.Done, + threadId: requestMessage.threadId, + }) + }) + + test('Faber starts with connection-less proof requests to Alice with auto-accept enabled and both agents having a mediator', async () => { + testLogger.test('Faber sends presentation request to Alice') + + const credentialPreview = V1CredentialPreview.fromRecord({ + name: 'John', + age: '99', + }) + + const unique = uuid().substring(0, 4) + + const mediatorAgentOptions = getAgentOptions( + `Connectionless proofs with mediator Mediator-${unique}`, + { + endpoints: ['rxjs:mediator'], + }, + { + ...getIndySdkModules(), + mediator: new MediatorModule({ + autoAcceptMediationRequests: true, + }), + } + ) + + const mediatorMessages = new Subject() + const subjectMap = { 'rxjs:mediator': mediatorMessages } + + // Initialize mediator + const mediatorAgent = new Agent(mediatorAgentOptions) + mediatorAgent.registerOutboundTransport(new SubjectOutboundTransport(subjectMap)) + mediatorAgent.registerInboundTransport(new SubjectInboundTransport(mediatorMessages)) + await mediatorAgent.initialize() + + const faberMediationOutOfBandRecord = await mediatorAgent.oob.createInvitation({ + label: 'faber invitation', + handshakeProtocols: [HandshakeProtocol.Connections], + }) + + const aliceMediationOutOfBandRecord = await mediatorAgent.oob.createInvitation({ + label: 'alice invitation', + handshakeProtocols: [HandshakeProtocol.Connections], + }) + + const faberAgentOptions = getAgentOptions( + `Connectionless proofs with mediator Faber-${unique}`, + {}, + { + ...getLegacyAnonCredsModules({ + autoAcceptProofs: AutoAcceptProof.Always, + }), + mediationRecipient: new MediationRecipientModule({ + mediatorInvitationUrl: faberMediationOutOfBandRecord.getOutOfBandInvitation().toUrl({ + domain: 'https://example.com', + }), + mediatorPickupStrategy: MediatorPickupStrategy.PickUpV1, + }), + } + ) + + const aliceAgentOptions = getAgentOptions( + `Connectionless proofs with mediator Alice-${unique}`, + {}, + { + ...getLegacyAnonCredsModules({ + autoAcceptProofs: AutoAcceptProof.Always, + }), + mediationRecipient: new MediationRecipientModule({ + mediatorInvitationUrl: aliceMediationOutOfBandRecord.getOutOfBandInvitation().toUrl({ + domain: 'https://example.com', + }), + mediatorPickupStrategy: MediatorPickupStrategy.PickUpV1, + }), + } + ) + + const faberAgent = new Agent(faberAgentOptions) + faberAgent.registerOutboundTransport(new SubjectOutboundTransport(subjectMap)) + await faberAgent.initialize() + + const aliceAgent = new Agent(aliceAgentOptions) + aliceAgent.registerOutboundTransport(new SubjectOutboundTransport(subjectMap)) + await aliceAgent.initialize() + + const [faberReplay, aliceReplay] = setupEventReplaySubjects( + [faberAgent, aliceAgent], + [CredentialEventTypes.CredentialStateChanged, ProofEventTypes.ProofStateChanged] + ) + + agents = [aliceAgent, faberAgent, mediatorAgent] + + const { credentialDefinition } = await prepareForAnonCredsIssuance(faberAgent, { + attributeNames: ['name', 'age', 'image_0', 'image_1'], + }) + + const [faberConnection, aliceConnection] = await makeConnection(faberAgent, aliceAgent) + expect(faberConnection.isReady).toBe(true) + expect(aliceConnection.isReady).toBe(true) + + await aliceAgent.modules.anoncreds.createLinkSecret({ + linkSecretId: 'default', + setAsDefault: true, + }) + + await issueLegacyAnonCredsCredential({ + issuerAgent: faberAgent as AnonCredsTestsAgent, + issuerReplay: faberReplay, + issuerHolderConnectionId: faberConnection.id, + holderAgent: aliceAgent as AnonCredsTestsAgent, + holderReplay: aliceReplay, + offer: { + credentialDefinitionId: credentialDefinition.credentialDefinitionId, + attributes: credentialPreview.attributes, + linkedAttachments: [ + new LinkedAttachment({ + name: 'image_0', + attachment: new V1Attachment({ + filename: 'picture-of-a-cat.png', + data: new V1AttachmentData({ base64: 'cGljdHVyZSBvZiBhIGNhdA==' }), + }), + }), + new LinkedAttachment({ + name: 'image_1', + attachment: new V1Attachment({ + filename: 'picture-of-a-dog.png', + data: new V1AttachmentData({ base64: 'UGljdHVyZSBvZiBhIGRvZw==' }), + }), + }), + ], + }, + }) + + // eslint-disable-next-line prefer-const + let { message, proofRecord: faberProofExchangeRecord } = await faberAgent.proofs.createRequest({ + protocolVersion: 'v1', + proofFormats: { + indy: { + name: 'test-proof-request', + version: '1.0', + requested_attributes: { + name: { + name: 'name', + restrictions: [ + { + cred_def_id: credentialDefinition.credentialDefinitionId, + }, + ], + }, + }, + requested_predicates: { + age: { + name: 'age', + p_type: '>=', + p_value: 50, + restrictions: [ + { + cred_def_id: credentialDefinition.credentialDefinitionId, + }, + ], + }, + }, + }, + }, + autoAcceptProof: AutoAcceptProof.ContentApproved, + }) + + const { message: requestMessage } = await faberAgent.oob.createLegacyConnectionlessInvitation({ + recordId: faberProofExchangeRecord.id, + message, + domain: 'https://a-domain.com', + }) + + const mediationRecord = await faberAgent.mediationRecipient.findDefaultMediator() + if (!mediationRecord) { + throw new Error('Faber agent has no default mediator') + } + + expect(requestMessage).toMatchObject({ + service: { + recipientKeys: [expect.any(String)], + routingKeys: mediationRecord.routingKeys, + serviceEndpoint: mediationRecord.endpoint, + }, + }) + + await aliceAgent.receiveMessage(requestMessage.toJSON()) + + await waitForProofExchangeRecordSubject(aliceReplay, { + state: ProofState.Done, + threadId: requestMessage.threadId, + }) + + await waitForProofExchangeRecordSubject(faberReplay, { + state: ProofState.Done, + threadId: requestMessage.threadId, + }) + + await aliceAgent.mediationRecipient.stopMessagePickup() + await faberAgent.mediationRecipient.stopMessagePickup() + }) +}) diff --git a/packages/anoncreds/src/protocols/proofs/v1/__tests__/v1-indy-proof-negotiation.e2e.test.ts b/packages/anoncreds/src/protocols/proofs/v1/__tests__/v1-indy-proof-negotiation.e2e.test.ts new file mode 100644 index 0000000000..917f5c805d --- /dev/null +++ b/packages/anoncreds/src/protocols/proofs/v1/__tests__/v1-indy-proof-negotiation.e2e.test.ts @@ -0,0 +1,293 @@ +import type { AcceptProofProposalOptions } from '../../../../../../core/src' +import type { AnonCredsTestsAgent } from '../../../../../tests/legacyAnonCredsSetup' +import type { V1RequestPresentationMessage } from '../messages' + +import { ProofState } from '../../../../../../core/src' +import { testLogger, waitForProofExchangeRecord } from '../../../../../../core/tests' +import { setupAnonCredsTests } from '../../../../../tests/legacyAnonCredsSetup' + +describe('Present Proof', () => { + let faberAgent: AnonCredsTestsAgent + let aliceAgent: AnonCredsTestsAgent + let aliceConnectionId: string + let credentialDefinitionId: string + + beforeAll(async () => { + testLogger.test('Initializing the agents') + ;({ + issuerAgent: faberAgent, + holderAgent: aliceAgent, + credentialDefinitionId, + holderIssuerConnectionId: aliceConnectionId, + } = await setupAnonCredsTests({ + issuerName: 'Faber - V1 Indy Proof Negotiation', + holderName: 'Alice - V1 Indy Proof Negotiation', + attributeNames: ['name', 'age'], + })) + }) + + afterAll(async () => { + testLogger.test('Shutting down both agents') + await faberAgent.shutdown() + await faberAgent.wallet.delete() + await aliceAgent.shutdown() + await aliceAgent.wallet.delete() + }) + + test(`Proof negotiation between Alice and Faber`, async () => { + testLogger.test('Alice sends proof proposal to Faber') + + let faberProofExchangeRecordPromise = waitForProofExchangeRecord(faberAgent, { + state: ProofState.ProposalReceived, + }) + + let aliceProofExchangeRecord = await aliceAgent.proofs.proposeProof({ + connectionId: aliceConnectionId, + protocolVersion: 'v1', + proofFormats: { + indy: { + name: 'proof-request', + version: '1.0', + attributes: [], + predicates: [ + { + credentialDefinitionId, + name: 'age', + predicate: '>=', + threshold: 18, + }, + ], + }, + }, + comment: 'V1 propose proof test 1', + }) + + testLogger.test('Faber waits for presentation from Alice') + let faberProofExchangeRecord = await faberProofExchangeRecordPromise + + let proposal = await faberAgent.proofs.findProposalMessage(faberProofExchangeRecord.id) + expect(proposal).toMatchObject({ + type: 'https://didcomm.org/present-proof/1.0/propose-presentation', + id: expect.any(String), + comment: 'V1 propose proof test 1', + presentationProposal: { + type: 'https://didcomm.org/present-proof/1.0/presentation-preview', + attributes: [], + predicates: [ + { + name: 'age', + credentialDefinitionId, + predicate: '>=', + threshold: 18, + }, + ], + }, + }) + expect(faberProofExchangeRecord).toMatchObject({ + id: expect.anything(), + threadId: faberProofExchangeRecord.threadId, + state: ProofState.ProposalReceived, + protocolVersion: 'v1', + }) + + let aliceProofExchangeRecordPromise = waitForProofExchangeRecord(aliceAgent, { + threadId: faberProofExchangeRecord.threadId, + state: ProofState.RequestReceived, + }) + + testLogger.test('Faber sends new proof request to Alice') + faberProofExchangeRecord = await faberAgent.proofs.negotiateProposal({ + proofRecordId: faberProofExchangeRecord.id, + proofFormats: { + indy: { + name: 'proof-request', + version: '1.0', + requested_attributes: { + something: { + name: 'name', + restrictions: [ + { + cred_def_id: credentialDefinitionId, + }, + ], + }, + }, + requested_predicates: { + somethingElse: { + name: 'age', + p_type: '>=', + p_value: 50, + restrictions: [ + { + cred_def_id: credentialDefinitionId, + }, + ], + }, + }, + }, + }, + }) + + testLogger.test('Alice waits for proof request from Faber') + aliceProofExchangeRecord = await aliceProofExchangeRecordPromise + + let request = await faberAgent.proofs.findRequestMessage(faberProofExchangeRecord.id) + expect(request).toMatchObject({ + type: 'https://didcomm.org/present-proof/1.0/request-presentation', + id: expect.any(String), + requestAttachments: [ + { + id: 'libindy-request-presentation-0', + mimeType: 'application/json', + data: { + base64: expect.any(String), + }, + }, + ], + thread: { + threadId: faberProofExchangeRecord.threadId, + }, + }) + expect(aliceProofExchangeRecord).toMatchObject({ + id: expect.anything(), + threadId: faberProofExchangeRecord.threadId, + state: ProofState.RequestReceived, + protocolVersion: 'v1', + }) + + testLogger.test('Alice sends proof proposal to Faber') + + faberProofExchangeRecordPromise = waitForProofExchangeRecord(faberAgent, { + state: ProofState.ProposalReceived, + }) + + aliceProofExchangeRecord = await aliceAgent.proofs.negotiateRequest({ + proofRecordId: aliceProofExchangeRecord.id, + proofFormats: { + indy: { + name: 'proof-request', + version: '1.0', + attributes: [], + predicates: [ + { + credentialDefinitionId, + name: 'age', + predicate: '>=', + threshold: 18, + }, + ], + }, + }, + comment: 'V1 propose proof test 2', + }) + + testLogger.test('Faber waits for presentation from Alice') + faberProofExchangeRecord = await faberProofExchangeRecordPromise + + proposal = await faberAgent.proofs.findProposalMessage(faberProofExchangeRecord.id) + expect(proposal).toMatchObject({ + type: 'https://didcomm.org/present-proof/1.0/propose-presentation', + id: expect.any(String), + comment: 'V1 propose proof test 2', + presentationProposal: { + type: 'https://didcomm.org/present-proof/1.0/presentation-preview', + attributes: [], + predicates: [ + { + name: 'age', + credentialDefinitionId, + predicate: '>=', + threshold: 18, + }, + ], + }, + }) + expect(faberProofExchangeRecord).toMatchObject({ + id: expect.anything(), + threadId: faberProofExchangeRecord.threadId, + state: ProofState.ProposalReceived, + protocolVersion: 'v1', + }) + + // Accept Proposal + const acceptProposalOptions: AcceptProofProposalOptions = { + proofRecordId: faberProofExchangeRecord.id, + } + + aliceProofExchangeRecordPromise = waitForProofExchangeRecord(aliceAgent, { + threadId: faberProofExchangeRecord.threadId, + state: ProofState.RequestReceived, + }) + + testLogger.test('Faber accepts presentation proposal from Alice') + faberProofExchangeRecord = await faberAgent.proofs.acceptProposal(acceptProposalOptions) + + testLogger.test('Alice waits for proof request from Faber') + aliceProofExchangeRecord = await aliceProofExchangeRecordPromise + + request = await faberAgent.proofs.findRequestMessage(faberProofExchangeRecord.id) + expect(request).toMatchObject({ + type: 'https://didcomm.org/present-proof/1.0/request-presentation', + id: expect.any(String), + requestAttachments: [ + { + id: 'libindy-request-presentation-0', + mimeType: 'application/json', + data: { + base64: expect.any(String), + }, + }, + ], + thread: { + threadId: faberProofExchangeRecord.threadId, + }, + }) + expect(aliceProofExchangeRecord).toMatchObject({ + id: expect.anything(), + threadId: faberProofExchangeRecord.threadId, + state: ProofState.RequestReceived, + protocolVersion: 'v1', + }) + + const proposalMessage = await aliceAgent.proofs.findProposalMessage(aliceProofExchangeRecord.id) + expect(proposalMessage).toMatchObject({ + type: 'https://didcomm.org/present-proof/1.0/propose-presentation', + id: expect.any(String), + comment: 'V1 propose proof test 2', + presentationProposal: { + type: 'https://didcomm.org/present-proof/1.0/presentation-preview', + attributes: [], + predicates: [ + { + name: 'age', + credentialDefinitionId, + predicate: '>=', + threshold: 18, + }, + ], + }, + }) + + const proofRequestMessage = (await aliceAgent.proofs.findRequestMessage( + aliceProofExchangeRecord.id + )) as V1RequestPresentationMessage + + const predicateKey = Object.keys(proofRequestMessage.indyProofRequest?.requested_predicates ?? {})[0] + expect(proofRequestMessage.indyProofRequest).toMatchObject({ + name: 'Proof Request', + version: '1.0', + requested_attributes: {}, + requested_predicates: { + [predicateKey]: { + p_type: '>=', + p_value: 18, + restrictions: [ + { + cred_def_id: credentialDefinitionId, + }, + ], + }, + }, + }) + }) +}) diff --git a/packages/core/src/modules/proofs/protocol/v1/__tests__/indy-proof-presentation.test.ts b/packages/anoncreds/src/protocols/proofs/v1/__tests__/v1-indy-proof-presentation.e2e.test.ts similarity index 57% rename from packages/core/src/modules/proofs/protocol/v1/__tests__/indy-proof-presentation.test.ts rename to packages/anoncreds/src/protocols/proofs/v1/__tests__/v1-indy-proof-presentation.e2e.test.ts index 975a6aed43..5b4c358a2f 100644 --- a/packages/core/src/modules/proofs/protocol/v1/__tests__/indy-proof-presentation.test.ts +++ b/packages/anoncreds/src/protocols/proofs/v1/__tests__/v1-indy-proof-presentation.e2e.test.ts @@ -1,29 +1,55 @@ -import type { Agent } from '../../../../../agent/Agent' -import type { ConnectionRecord } from '../../../../connections/repository/ConnectionRecord' -import type { PresentationPreview } from '../models/V1PresentationPreview' +import type { EventReplaySubject } from '../../../../../../core/tests' +import type { AnonCredsTestsAgent } from '../../../../../tests/legacyAnonCredsSetup' -import { setupProofsTest, waitForProofExchangeRecord } from '../../../../../../tests/helpers' -import testLogger from '../../../../../../tests/logger' -import { DidCommMessageRepository } from '../../../../../storage/didcomm' -import { ProofState } from '../../../models/ProofState' -import { ProofExchangeRecord } from '../../../repository/ProofExchangeRecord' -import { V1PresentationMessage, V1ProposePresentationMessage, V1RequestPresentationMessage } from '../messages' +import { ProofState, ProofExchangeRecord } from '../../../../../../core/src' +import { testLogger, waitForProofExchangeRecord } from '../../../../../../core/tests' +import { issueLegacyAnonCredsCredential, setupAnonCredsTests } from '../../../../../tests/legacyAnonCredsSetup' describe('Present Proof', () => { - let faberAgent: Agent - let aliceAgent: Agent - let aliceConnection: ConnectionRecord - let presentationPreview: PresentationPreview - let faberProofExchangeRecord: ProofExchangeRecord - let aliceProofExchangeRecord: ProofExchangeRecord - let didCommMessageRepository: DidCommMessageRepository + let faberAgent: AnonCredsTestsAgent + let faberReplay: EventReplaySubject + let aliceAgent: AnonCredsTestsAgent + let aliceReplay: EventReplaySubject + let aliceConnectionId: string + let faberConnectionId: string + let credentialDefinitionId: string beforeAll(async () => { testLogger.test('Initializing the agents') - ;({ faberAgent, aliceAgent, aliceConnection, presentationPreview } = await setupProofsTest( - 'Faber agent', - 'Alice agent' - )) + ;({ + issuerAgent: faberAgent, + issuerReplay: faberReplay, + holderAgent: aliceAgent, + holderReplay: aliceReplay, + credentialDefinitionId, + holderIssuerConnectionId: aliceConnectionId, + issuerHolderConnectionId: faberConnectionId, + } = await setupAnonCredsTests({ + issuerName: 'Faber - V1 Indy Proof', + holderName: 'Alice - V1 Indy Proof', + attributeNames: ['name', 'age'], + })) + + await issueLegacyAnonCredsCredential({ + issuerAgent: faberAgent, + issuerReplay: faberReplay, + holderAgent: aliceAgent, + holderReplay: aliceReplay, + issuerHolderConnectionId: faberConnectionId, + offer: { + credentialDefinitionId, + attributes: [ + { + name: 'name', + value: 'John', + }, + { + name: 'age', + value: '55', + }, + ], + }, + }) }) afterAll(async () => { @@ -37,20 +63,33 @@ describe('Present Proof', () => { test(`Alice Creates and sends Proof Proposal to Faber`, async () => { testLogger.test('Alice sends proof proposal to Faber') - const faberProofExchangeRecordPromise = waitForProofExchangeRecord(faberAgent, { + let faberProofExchangeRecordPromise = waitForProofExchangeRecord(faberAgent, { state: ProofState.ProposalReceived, }) - aliceProofExchangeRecord = await aliceAgent.proofs.proposeProof({ - connectionId: aliceConnection.id, + let aliceProofExchangeRecord = await aliceAgent.proofs.proposeProof({ + connectionId: aliceConnectionId, protocolVersion: 'v1', proofFormats: { indy: { name: 'ProofRequest', - nonce: '58d223e5-fc4d-4448-b74c-5eb11c6b558f', version: '1.0', - attributes: presentationPreview.attributes, - predicates: presentationPreview.predicates, + attributes: [ + { + name: 'name', + value: 'John', + credentialDefinitionId, + referent: '0', + }, + ], + predicates: [ + { + name: 'age', + predicate: '>=', + threshold: 50, + credentialDefinitionId, + }, + ], }, }, comment: 'V1 propose proof test', @@ -58,15 +97,9 @@ describe('Present Proof', () => { testLogger.test('Faber waits for presentation from Alice') - faberProofExchangeRecord = await faberProofExchangeRecordPromise - - didCommMessageRepository = faberAgent.injectionContainer.resolve(DidCommMessageRepository) - - const proposal = await didCommMessageRepository.findAgentMessage(faberAgent.context, { - associatedRecordId: faberProofExchangeRecord.id, - messageClass: V1ProposePresentationMessage, - }) + let faberProofExchangeRecord = await faberProofExchangeRecordPromise + const proposal = await faberAgent.proofs.findProposalMessage(faberProofExchangeRecord.id) expect(proposal).toMatchObject({ type: 'https://didcomm.org/present-proof/1.0/propose-presentation', id: expect.any(String), @@ -76,19 +109,15 @@ describe('Present Proof', () => { attributes: [ { name: 'name', - credentialDefinitionId: presentationPreview.attributes[0].credentialDefinitionId, + credentialDefinitionId, value: 'John', referent: '0', }, - { - name: 'image_0', - credentialDefinitionId: presentationPreview.attributes[1].credentialDefinitionId, - }, ], predicates: [ { name: 'age', - credentialDefinitionId: presentationPreview.predicates[0].credentialDefinitionId, + credentialDefinitionId, predicate: '>=', threshold: 50, }, @@ -101,11 +130,9 @@ describe('Present Proof', () => { state: ProofState.ProposalReceived, protocolVersion: 'v1', }) - }) - test(`Faber accepts the Proposal send by Alice`, async () => { // Accept Proposal - const aliceProofExchangeRecordPromise = waitForProofExchangeRecord(aliceAgent, { + let aliceProofExchangeRecordPromise = waitForProofExchangeRecord(aliceAgent, { threadId: faberProofExchangeRecord.threadId, state: ProofState.RequestReceived, }) @@ -118,17 +145,11 @@ describe('Present Proof', () => { testLogger.test('Alice waits for proof request from Faber') aliceProofExchangeRecord = await aliceProofExchangeRecordPromise - didCommMessageRepository = faberAgent.injectionContainer.resolve(DidCommMessageRepository) - - const request = await didCommMessageRepository.findAgentMessage(faberAgent.context, { - associatedRecordId: faberProofExchangeRecord.id, - messageClass: V1RequestPresentationMessage, - }) - + const request = await faberAgent.proofs.findRequestMessage(faberProofExchangeRecord.id) expect(request).toMatchObject({ type: 'https://didcomm.org/present-proof/1.0/request-presentation', id: expect.any(String), - requestPresentationAttachments: [ + requestAttachments: [ { id: 'libindy-request-presentation-0', mimeType: 'application/json', @@ -147,17 +168,12 @@ describe('Present Proof', () => { state: ProofState.RequestReceived, protocolVersion: 'v1', }) - }) - test(`Alice accepts presentation request from Faber`, async () => { - const requestedCredentials = await aliceAgent.proofs.autoSelectCredentialsForProofRequest({ + const requestedCredentials = await aliceAgent.proofs.selectCredentialsForRequest({ proofRecordId: aliceProofExchangeRecord.id, - config: { - filterByPresentationPreview: true, - }, }) - const faberProofExchangeRecordPromise = waitForProofExchangeRecord(faberAgent, { + faberProofExchangeRecordPromise = waitForProofExchangeRecord(faberAgent, { threadId: aliceProofExchangeRecord.threadId, state: ProofState.PresentationReceived, }) @@ -171,11 +187,7 @@ describe('Present Proof', () => { testLogger.test('Faber waits for presentation from Alice') faberProofExchangeRecord = await faberProofExchangeRecordPromise - const presentation = await didCommMessageRepository.findAgentMessage(faberAgent.context, { - associatedRecordId: faberProofExchangeRecord.id, - messageClass: V1PresentationMessage, - }) - + const presentation = await faberAgent.proofs.findPresentationMessage(faberProofExchangeRecord.id) expect(presentation).toMatchObject({ type: 'https://didcomm.org/present-proof/1.0/presentation', id: expect.any(String), @@ -188,15 +200,6 @@ describe('Present Proof', () => { }, }, ], - appendedAttachments: [ - { - id: expect.any(String), - filename: expect.any(String), - data: { - base64: expect.any(String), - }, - }, - ], thread: { threadId: expect.any(String), }, @@ -208,16 +211,14 @@ describe('Present Proof', () => { state: ProofState.PresentationReceived, protocolVersion: 'v1', }) - }) - test(`Faber accepts the presentation provided by Alice`, async () => { - const aliceProofExchangeRecordPromise = waitForProofExchangeRecord(aliceAgent, { + aliceProofExchangeRecordPromise = waitForProofExchangeRecord(aliceAgent, { threadId: aliceProofExchangeRecord.threadId, state: ProofState.Done, }) // Faber accepts the presentation provided by Alice - await faberAgent.proofs.acceptPresentation(faberProofExchangeRecord.id) + await faberAgent.proofs.acceptPresentation({ proofRecordId: faberProofExchangeRecord.id }) // Alice waits until she received a presentation acknowledgement testLogger.test('Alice waits until she receives a presentation acknowledgement') diff --git a/packages/anoncreds/src/protocols/proofs/v1/__tests__/v1-indy-proof-proposal.e2e.test.ts b/packages/anoncreds/src/protocols/proofs/v1/__tests__/v1-indy-proof-proposal.e2e.test.ts new file mode 100644 index 0000000000..14e9e72145 --- /dev/null +++ b/packages/anoncreds/src/protocols/proofs/v1/__tests__/v1-indy-proof-proposal.e2e.test.ts @@ -0,0 +1,106 @@ +import type { AnonCredsTestsAgent } from '../../../../../tests/legacyAnonCredsSetup' + +import { ProofState } from '../../../../../../core/src' +import { testLogger, waitForProofExchangeRecord } from '../../../../../../core/tests' +import { setupAnonCredsTests } from '../../../../../tests/legacyAnonCredsSetup' + +describe('Present Proof', () => { + let faberAgent: AnonCredsTestsAgent + let aliceAgent: AnonCredsTestsAgent + let aliceConnectionId: string + let credentialDefinitionId: string + + beforeAll(async () => { + testLogger.test('Initializing the agents') + ;({ + issuerAgent: faberAgent, + holderAgent: aliceAgent, + credentialDefinitionId, + holderIssuerConnectionId: aliceConnectionId, + } = await setupAnonCredsTests({ + issuerName: 'Faber - V1 Indy Proof Request', + holderName: 'Alice - V1 Indy Proof Request', + attributeNames: ['name', 'age'], + })) + }) + + afterAll(async () => { + testLogger.test('Shutting down both agents') + await faberAgent.shutdown() + await faberAgent.wallet.delete() + await aliceAgent.shutdown() + await aliceAgent.wallet.delete() + }) + + test(`Alice Creates and sends Proof Proposal to Faber`, async () => { + testLogger.test('Alice sends proof proposal to Faber') + + const faberProofExchangeRecordPromise = waitForProofExchangeRecord(faberAgent, { + state: ProofState.ProposalReceived, + }) + + await aliceAgent.proofs.proposeProof({ + connectionId: aliceConnectionId, + protocolVersion: 'v1', + proofFormats: { + indy: { + name: 'ProofRequest', + version: '1.0', + attributes: [ + { + name: 'name', + value: 'John', + credentialDefinitionId, + referent: '0', + }, + ], + predicates: [ + { + name: 'age', + predicate: '>=', + threshold: 50, + credentialDefinitionId, + }, + ], + }, + }, + comment: 'V1 propose proof test', + }) + + testLogger.test('Faber waits for presentation from Alice') + const faberProofExchangeRecord = await faberProofExchangeRecordPromise + + const proposal = await faberAgent.proofs.findProposalMessage(faberProofExchangeRecord.id) + expect(proposal).toMatchObject({ + type: 'https://didcomm.org/present-proof/1.0/propose-presentation', + id: expect.any(String), + comment: 'V1 propose proof test', + presentationProposal: { + type: 'https://didcomm.org/present-proof/1.0/presentation-preview', + attributes: [ + { + name: 'name', + credentialDefinitionId, + value: 'John', + referent: '0', + }, + ], + predicates: [ + { + name: 'age', + credentialDefinitionId, + predicate: '>=', + threshold: 50, + }, + ], + }, + }) + + expect(faberProofExchangeRecord).toMatchObject({ + id: expect.anything(), + threadId: faberProofExchangeRecord.threadId, + state: ProofState.ProposalReceived, + protocolVersion: 'v1', + }) + }) +}) diff --git a/packages/anoncreds/src/protocols/proofs/v1/__tests__/v1-indy-proof-request.e2e.test.ts b/packages/anoncreds/src/protocols/proofs/v1/__tests__/v1-indy-proof-request.e2e.test.ts new file mode 100644 index 0000000000..36e9203b0d --- /dev/null +++ b/packages/anoncreds/src/protocols/proofs/v1/__tests__/v1-indy-proof-request.e2e.test.ts @@ -0,0 +1,144 @@ +import type { AnonCredsTestsAgent } from '../../../../../tests/legacyAnonCredsSetup' + +import { ProofState } from '../../../../../../core/src' +import { testLogger, waitForProofExchangeRecord } from '../../../../../../core/tests' +import { setupAnonCredsTests } from '../../../../../tests/legacyAnonCredsSetup' + +describe('Present Proof | V1ProofProtocol', () => { + let faberAgent: AnonCredsTestsAgent + let aliceAgent: AnonCredsTestsAgent + let aliceConnectionId: string + let credentialDefinitionId: string + + beforeAll(async () => { + testLogger.test('Initializing the agents') + ;({ + issuerAgent: faberAgent, + holderAgent: aliceAgent, + credentialDefinitionId, + holderIssuerConnectionId: aliceConnectionId, + } = await setupAnonCredsTests({ + issuerName: 'Faber - V1 Indy Proof Request', + holderName: 'Alice - V1 Indy Proof Request', + attributeNames: ['name', 'age'], + })) + }) + + afterAll(async () => { + testLogger.test('Shutting down both agents') + await faberAgent.shutdown() + await faberAgent.wallet.delete() + await aliceAgent.shutdown() + await aliceAgent.wallet.delete() + }) + + test(`Alice Creates and sends Proof Proposal to Faber and Faber accepts the proposal`, async () => { + testLogger.test('Alice sends proof proposal to Faber') + + const faberProofExchangeRecordPromise = waitForProofExchangeRecord(faberAgent, { + state: ProofState.ProposalReceived, + }) + + let aliceProofExchangeRecord = await aliceAgent.proofs.proposeProof({ + connectionId: aliceConnectionId, + protocolVersion: 'v1', + proofFormats: { + indy: { + name: 'Proof Request', + version: '1.0', + attributes: [ + { + name: 'name', + value: 'John', + credentialDefinitionId, + referent: '0', + }, + ], + predicates: [ + { + name: 'age', + predicate: '>=', + threshold: 50, + credentialDefinitionId, + }, + ], + }, + }, + comment: 'V1 propose proof test', + }) + + testLogger.test('Faber waits for presentation from Alice') + let faberProofExchangeRecord = await faberProofExchangeRecordPromise + + const proposal = await faberAgent.proofs.findProposalMessage(faberProofExchangeRecord.id) + expect(proposal?.toJSON()).toMatchObject({ + '@type': 'https://didcomm.org/present-proof/1.0/propose-presentation', + '@id': expect.any(String), + comment: 'V1 propose proof test', + presentation_proposal: { + '@type': 'https://didcomm.org/present-proof/1.0/presentation-preview', + attributes: [ + { + name: 'name', + cred_def_id: credentialDefinitionId, + value: 'John', + referent: '0', + }, + ], + predicates: [ + { + name: 'age', + cred_def_id: credentialDefinitionId, + predicate: '>=', + threshold: 50, + }, + ], + }, + }) + expect(faberProofExchangeRecord).toMatchObject({ + id: expect.anything(), + threadId: faberProofExchangeRecord.threadId, + state: ProofState.ProposalReceived, + protocolVersion: 'v1', + }) + + const aliceProofExchangeRecordPromise = waitForProofExchangeRecord(aliceAgent, { + threadId: faberProofExchangeRecord.threadId, + state: ProofState.RequestReceived, + }) + + // Accept Proposal + testLogger.test('Faber accepts presentation proposal from Alice') + faberProofExchangeRecord = await faberAgent.proofs.acceptProposal({ + proofRecordId: faberProofExchangeRecord.id, + }) + + testLogger.test('Alice waits for proof request from Faber') + aliceProofExchangeRecord = await aliceProofExchangeRecordPromise + + const request = await faberAgent.proofs.findRequestMessage(faberProofExchangeRecord.id) + expect(request).toMatchObject({ + type: 'https://didcomm.org/present-proof/1.0/request-presentation', + id: expect.any(String), + requestAttachments: [ + { + id: 'libindy-request-presentation-0', + mimeType: 'application/json', + data: { + base64: expect.any(String), + }, + }, + ], + thread: { + threadId: faberProofExchangeRecord.threadId, + }, + }) + + expect(aliceProofExchangeRecord).toMatchObject({ + id: expect.anything(), + threadId: faberProofExchangeRecord.threadId, + state: ProofState.RequestReceived, + protocolVersion: 'v1', + }) + }) +}) diff --git a/packages/core/tests/v1-indy-proofs.test.ts b/packages/anoncreds/src/protocols/proofs/v1/__tests__/v1-indy-proofs.e2e.test.ts similarity index 61% rename from packages/core/tests/v1-indy-proofs.test.ts rename to packages/anoncreds/src/protocols/proofs/v1/__tests__/v1-indy-proofs.e2e.test.ts index 81b523d659..ff71996463 100644 --- a/packages/core/tests/v1-indy-proofs.test.ts +++ b/packages/anoncreds/src/protocols/proofs/v1/__tests__/v1-indy-proofs.e2e.test.ts @@ -1,43 +1,50 @@ -import type { Agent, ConnectionRecord } from '../src' -import type { AcceptProofProposalOptions } from '../src/modules/proofs/ProofsApiOptions' -import type { PresentationPreview } from '../src/modules/proofs/protocol/v1/models/V1PresentationPreview' -import type { CredDefId } from 'indy-sdk' - -import { ProofExchangeRecord } from '../src' -import { getGroupKeysFromIndyProofFormatData } from '../src/modules/proofs/__tests__/groupKeys' -import { - ProofAttributeInfo, - AttributeFilter, - ProofPredicateInfo, - PredicateType, -} from '../src/modules/proofs/formats/indy/models' -import { ProofState } from '../src/modules/proofs/models/ProofState' -import { - V1ProposePresentationMessage, - V1RequestPresentationMessage, - V1PresentationMessage, -} from '../src/modules/proofs/protocol/v1/messages' -import { DidCommMessageRepository } from '../src/storage/didcomm' - -import { setupProofsTest, waitForProofExchangeRecord } from './helpers' -import testLogger from './logger' +import type { EventReplaySubject } from '../../../../../../core/tests' +import type { AnonCredsTestsAgent } from '../../../../../tests/legacyAnonCredsSetup' + +import { ProofState, ProofExchangeRecord } from '../../../../../../core/src' +import { testLogger, waitForProofExchangeRecord } from '../../../../../../core/tests' +import { issueLegacyAnonCredsCredential, setupAnonCredsTests } from '../../../../../tests/legacyAnonCredsSetup' +import { V1ProposePresentationMessage, V1RequestPresentationMessage, V1PresentationMessage } from '../messages' describe('Present Proof', () => { - let faberAgent: Agent - let aliceAgent: Agent - let credDefId: CredDefId - let faberConnection: ConnectionRecord - let aliceConnection: ConnectionRecord - let faberProofExchangeRecord: ProofExchangeRecord - let aliceProofExchangeRecord: ProofExchangeRecord - let presentationPreview: PresentationPreview - let didCommMessageRepository: DidCommMessageRepository + let faberAgent: AnonCredsTestsAgent + let faberReplay: EventReplaySubject + let aliceAgent: AnonCredsTestsAgent + let aliceReplay: EventReplaySubject + let faberConnectionId: string + let aliceConnectionId: string + let credentialDefinitionId: string beforeAll(async () => { testLogger.test('Initializing the agents') - ;({ faberAgent, aliceAgent, credDefId, faberConnection, aliceConnection, presentationPreview } = - await setupProofsTest('Faber agent', 'Alice agent')) - testLogger.test('Issuing second credential') + ;({ + issuerAgent: faberAgent, + issuerReplay: faberReplay, + holderAgent: aliceAgent, + holderReplay: aliceReplay, + credentialDefinitionId, + issuerHolderConnectionId: faberConnectionId, + holderIssuerConnectionId: aliceConnectionId, + } = await setupAnonCredsTests({ + issuerName: 'Faber Proofs V1 - Full', + holderName: 'Alice Proofs V1 - Full', + attributeNames: ['name', 'age'], + })) + + await issueLegacyAnonCredsCredential({ + issuerAgent: faberAgent, + issuerReplay: faberReplay, + holderAgent: aliceAgent, + holderReplay: aliceReplay, + issuerHolderConnectionId: faberConnectionId, + offer: { + credentialDefinitionId, + attributes: [ + { name: 'name', value: 'John' }, + { name: 'age', value: '99' }, + ], + }, + }) }) afterAll(async () => { @@ -56,28 +63,35 @@ describe('Present Proof', () => { state: ProofState.ProposalReceived, }) - aliceProofExchangeRecord = await aliceAgent.proofs.proposeProof({ - connectionId: aliceConnection.id, + let aliceProofExchangeRecord = await aliceAgent.proofs.proposeProof({ + connectionId: aliceConnectionId, protocolVersion: 'v1', proofFormats: { indy: { - attributes: presentationPreview.attributes, - predicates: presentationPreview.predicates, + attributes: [ + { + name: 'name', + value: 'John', + credentialDefinitionId, + referent: '0', + }, + ], + predicates: [ + { + name: 'age', + predicate: '>=', + threshold: 50, + credentialDefinitionId, + }, + ], }, }, }) // Faber waits for a presentation proposal from Alice testLogger.test('Faber waits for a presentation proposal from Alice') - faberProofExchangeRecord = await faberProofExchangeRecordPromise - - didCommMessageRepository = faberAgent.injectionContainer.resolve(DidCommMessageRepository) - - const proposal = await didCommMessageRepository.findAgentMessage(faberAgent.context, { - associatedRecordId: faberProofExchangeRecord.id, - messageClass: V1ProposePresentationMessage, - }) - + let faberProofExchangeRecord = await faberProofExchangeRecordPromise + const proposal = await faberAgent.proofs.findProposalMessage(faberProofExchangeRecord.id) expect(proposal).toMatchObject({ type: 'https://didcomm.org/present-proof/1.0/propose-presentation', id: expect.any(String), @@ -86,19 +100,15 @@ describe('Present Proof', () => { attributes: [ { name: 'name', - credentialDefinitionId: presentationPreview.attributes[0].credentialDefinitionId, + credentialDefinitionId, value: 'John', referent: '0', }, - { - name: 'image_0', - credentialDefinitionId: presentationPreview.attributes[1].credentialDefinitionId, - }, ], predicates: [ { name: 'age', - credentialDefinitionId: presentationPreview.predicates[0].credentialDefinitionId, + credentialDefinitionId, predicate: '>=', threshold: 50, }, @@ -112,10 +122,6 @@ describe('Present Proof', () => { protocolVersion: 'v1', }) - const acceptProposalOptions: AcceptProofProposalOptions = { - proofRecordId: faberProofExchangeRecord.id, - } - let aliceProofExchangeRecordPromise = waitForProofExchangeRecord(aliceAgent, { threadId: aliceProofExchangeRecord.threadId, state: ProofState.RequestReceived, @@ -123,21 +129,19 @@ describe('Present Proof', () => { // Faber accepts the presentation proposal from Alice testLogger.test('Faber accepts presentation proposal from Alice') - faberProofExchangeRecord = await faberAgent.proofs.acceptProposal(acceptProposalOptions) + faberProofExchangeRecord = await faberAgent.proofs.acceptProposal({ + proofRecordId: faberProofExchangeRecord.id, + }) // Alice waits for presentation request from Faber testLogger.test('Alice waits for presentation request from Faber') aliceProofExchangeRecord = await aliceProofExchangeRecordPromise - const request = await didCommMessageRepository.findAgentMessage(faberAgent.context, { - associatedRecordId: faberProofExchangeRecord.id, - messageClass: V1RequestPresentationMessage, - }) - + const request = await faberAgent.proofs.findRequestMessage(faberProofExchangeRecord.id) expect(request).toMatchObject({ type: 'https://didcomm.org/present-proof/1.0/request-presentation', id: expect.any(String), - requestPresentationAttachments: [ + requestAttachments: [ { id: 'libindy-request-presentation-0', mimeType: 'application/json', @@ -154,11 +158,8 @@ describe('Present Proof', () => { // Alice retrieves the requested credentials and accepts the presentation request testLogger.test('Alice accepts presentation request from Faber') - const requestedCredentials = await aliceAgent.proofs.autoSelectCredentialsForProofRequest({ + const requestedCredentials = await aliceAgent.proofs.selectCredentialsForRequest({ proofRecordId: aliceProofExchangeRecord.id, - config: { - filterByPresentationPreview: true, - }, }) faberProofExchangeRecordPromise = waitForProofExchangeRecord(faberAgent, { @@ -175,11 +176,7 @@ describe('Present Proof', () => { testLogger.test('Faber waits for presentation from Alice') faberProofExchangeRecord = await faberProofExchangeRecordPromise - const presentation = await didCommMessageRepository.findAgentMessage(faberAgent.context, { - associatedRecordId: faberProofExchangeRecord.id, - messageClass: V1PresentationMessage, - }) - + const presentation = await faberAgent.proofs.findPresentationMessage(faberProofExchangeRecord.id) expect(presentation).toMatchObject({ type: 'https://didcomm.org/present-proof/1.0/presentation', id: expect.any(String), @@ -192,15 +189,6 @@ describe('Present Proof', () => { }, }, ], - appendedAttachments: [ - { - id: expect.any(String), - filename: expect.any(String), - data: { - base64: expect.any(String), - }, - }, - ], thread: { threadId: expect.any(String), }, @@ -220,7 +208,7 @@ describe('Present Proof', () => { // Faber accepts the presentation provided by Alice testLogger.test('Faber accepts the presentation provided by Alice') - await faberAgent.proofs.acceptPresentation(faberProofExchangeRecord.id) + await faberAgent.proofs.acceptPresentation({ proofRecordId: faberProofExchangeRecord.id }) // Alice waits until she received a presentation acknowledgement testLogger.test('Alice waits until she receives a presentation acknowledgement') @@ -255,8 +243,8 @@ describe('Present Proof', () => { const formatData = await aliceAgent.proofs.getFormatData(aliceProofExchangeRecord.id) - // eslint-disable-next-line prefer-const - let { proposeKey1, proposeKey2, requestKey1, requestKey2 } = getGroupKeysFromIndyProofFormatData(formatData) + const proposalPredicateKey = Object.keys(formatData.proposal?.indy?.requested_predicates || {})[0] + const requestPredicateKey = Object.keys(formatData.request?.indy?.requested_predicates || {})[0] expect(formatData).toMatchObject({ proposal: { @@ -268,23 +256,15 @@ describe('Present Proof', () => { 0: { name: 'name', }, - [proposeKey1]: { - name: 'image_0', - restrictions: [ - { - cred_def_id: credDefId, - }, - ], - }, }, requested_predicates: { - [proposeKey2]: { + [proposalPredicateKey]: { name: 'age', p_type: '>=', p_value: 50, restrictions: [ { - cred_def_id: credDefId, + cred_def_id: credentialDefinitionId, }, ], }, @@ -300,23 +280,15 @@ describe('Present Proof', () => { 0: { name: 'name', }, - [requestKey1]: { - name: 'image_0', - restrictions: [ - { - cred_def_id: credDefId, - }, - ], - }, }, requested_predicates: { - [requestKey2]: { + [requestPredicateKey]: { name: 'age', p_type: '>=', p_value: 50, restrictions: [ { - cred_def_id: credDefId, + cred_def_id: credentialDefinitionId, }, ], }, @@ -334,73 +306,54 @@ describe('Present Proof', () => { }) test('Faber starts with proof request to Alice', async () => { - const attributes = { - name: new ProofAttributeInfo({ - name: 'name', - restrictions: [ - new AttributeFilter({ - credentialDefinitionId: credDefId, - }), - ], - }), - image_0: new ProofAttributeInfo({ - name: 'image_0', - restrictions: [ - new AttributeFilter({ - credentialDefinitionId: credDefId, - }), - ], - }), - } - - // Sample predicates - const predicates = { - age: new ProofPredicateInfo({ - name: 'age', - predicateType: PredicateType.GreaterThanOrEqualTo, - predicateValue: 50, - restrictions: [ - new AttributeFilter({ - credentialDefinitionId: credDefId, - }), - ], - }), - } - let aliceProofExchangeRecordPromise = waitForProofExchangeRecord(aliceAgent, { state: ProofState.RequestReceived, }) // Faber sends a presentation request to Alice testLogger.test('Faber sends a presentation request to Alice') - faberProofExchangeRecord = await faberAgent.proofs.requestProof({ + let faberProofExchangeRecord = await faberAgent.proofs.requestProof({ protocolVersion: 'v1', - connectionId: faberConnection.id, + connectionId: faberConnectionId, proofFormats: { indy: { name: 'proof-request', version: '1.0', - requestedAttributes: attributes, - requestedPredicates: predicates, + requested_attributes: { + name: { + name: 'name', + restrictions: [ + { + cred_def_id: credentialDefinitionId, + }, + ], + }, + }, + requested_predicates: { + age: { + name: 'age', + p_type: '>=', + p_value: 50, + restrictions: [ + { + cred_def_id: credentialDefinitionId, + }, + ], + }, + }, }, }, }) // Alice waits for presentation request from Faber testLogger.test('Alice waits for presentation request from Faber') - aliceProofExchangeRecord = await aliceProofExchangeRecordPromise - - didCommMessageRepository = faberAgent.injectionContainer.resolve(DidCommMessageRepository) - - const request = await didCommMessageRepository.findAgentMessage(faberAgent.context, { - associatedRecordId: faberProofExchangeRecord.id, - messageClass: V1RequestPresentationMessage, - }) + let aliceProofExchangeRecord = await aliceProofExchangeRecordPromise + const request = await faberAgent.proofs.findRequestMessage(faberProofExchangeRecord.id) expect(request).toMatchObject({ type: 'https://didcomm.org/present-proof/1.0/request-presentation', id: expect.any(String), - requestPresentationAttachments: [ + requestAttachments: [ { id: 'libindy-request-presentation-0', mimeType: 'application/json', @@ -421,11 +374,8 @@ describe('Present Proof', () => { // Alice retrieves the requested credentials and accepts the presentation request testLogger.test('Alice accepts presentation request from Faber') - const requestedCredentials = await aliceAgent.proofs.autoSelectCredentialsForProofRequest({ + const requestedCredentials = await aliceAgent.proofs.selectCredentialsForRequest({ proofRecordId: aliceProofExchangeRecord.id, - config: { - filterByPresentationPreview: true, - }, }) const faberProofExchangeRecordPromise = waitForProofExchangeRecord(faberAgent, { @@ -442,11 +392,7 @@ describe('Present Proof', () => { testLogger.test('Faber waits for presentation from Alice') faberProofExchangeRecord = await faberProofExchangeRecordPromise - const presentation = await didCommMessageRepository.findAgentMessage(faberAgent.context, { - associatedRecordId: faberProofExchangeRecord.id, - messageClass: V1PresentationMessage, - }) - + const presentation = await faberAgent.proofs.findPresentationMessage(faberProofExchangeRecord.id) expect(presentation).toMatchObject({ type: 'https://didcomm.org/present-proof/1.0/presentation', id: expect.any(String), @@ -459,15 +405,6 @@ describe('Present Proof', () => { }, }, ], - appendedAttachments: [ - { - id: expect.any(String), - filename: expect.any(String), - data: { - base64: expect.any(String), - }, - }, - ], thread: { threadId: expect.any(String), }, @@ -487,7 +424,7 @@ describe('Present Proof', () => { // Faber accepts the presentation testLogger.test('Faber accept the presentation from Alice') - await faberAgent.proofs.acceptPresentation(faberProofExchangeRecord.id) + await faberAgent.proofs.acceptPresentation({ proofRecordId: faberProofExchangeRecord.id }) // Alice waits until she receives a presentation acknowledgement testLogger.test('Alice waits for acceptance by Faber') @@ -514,43 +451,36 @@ describe('Present Proof', () => { }) test('an attribute group name matches with a predicate group name so an error is thrown', async () => { - // Age attribute - const attributes = { - age: new ProofAttributeInfo({ - name: 'age', - restrictions: [ - new AttributeFilter({ - credentialDefinitionId: credDefId, - }), - ], - }), - } - - // Age predicate - const predicates = { - age: new ProofPredicateInfo({ - name: 'age', - predicateType: PredicateType.GreaterThanOrEqualTo, - predicateValue: 50, - restrictions: [ - new AttributeFilter({ - credentialDefinitionId: credDefId, - }), - ], - }), - } - await expect( faberAgent.proofs.requestProof({ protocolVersion: 'v1', - connectionId: faberConnection.id, + connectionId: faberConnectionId, proofFormats: { indy: { name: 'proof-request', version: '1.0', - nonce: '1298236324864', - requestedAttributes: attributes, - requestedPredicates: predicates, + requested_attributes: { + age: { + name: 'age', + restrictions: [ + { + cred_def_id: credentialDefinitionId, + }, + ], + }, + }, + requested_predicates: { + age: { + name: 'age', + p_type: '>=', + p_value: 50, + restrictions: [ + { + cred_def_id: credentialDefinitionId, + }, + ], + }, + }, }, }, }) @@ -558,74 +488,54 @@ describe('Present Proof', () => { }) test('Faber starts with proof request to Alice but gets Problem Reported', async () => { - const attributes = { - name: new ProofAttributeInfo({ - name: 'name', - restrictions: [ - new AttributeFilter({ - credentialDefinitionId: credDefId, - }), - ], - }), - image_0: new ProofAttributeInfo({ - name: 'image_0', - restrictions: [ - new AttributeFilter({ - credentialDefinitionId: credDefId, - }), - ], - }), - } - - // Sample predicates - const predicates = { - age: new ProofPredicateInfo({ - name: 'age', - predicateType: PredicateType.GreaterThanOrEqualTo, - predicateValue: 50, - restrictions: [ - new AttributeFilter({ - credentialDefinitionId: credDefId, - }), - ], - }), - } - const aliceProofExchangeRecordPromise = waitForProofExchangeRecord(aliceAgent, { state: ProofState.RequestReceived, }) // Faber sends a presentation request to Alice testLogger.test('Faber sends a presentation request to Alice') - faberProofExchangeRecord = await faberAgent.proofs.requestProof({ + let faberProofExchangeRecord = await faberAgent.proofs.requestProof({ protocolVersion: 'v1', - connectionId: faberConnection.id, + connectionId: faberConnectionId, proofFormats: { indy: { name: 'proof-request', version: '1.0', - nonce: '1298236324864', - requestedAttributes: attributes, - requestedPredicates: predicates, + requested_attributes: { + name: { + name: 'name', + restrictions: [ + { + cred_def_id: credentialDefinitionId, + }, + ], + }, + }, + requested_predicates: { + age: { + name: 'age', + p_type: '>=', + p_value: 50, + restrictions: [ + { + cred_def_id: credentialDefinitionId, + }, + ], + }, + }, }, }, }) // Alice waits for presentation request from Faber testLogger.test('Alice waits for presentation request from Faber') - aliceProofExchangeRecord = await aliceProofExchangeRecordPromise - - didCommMessageRepository = faberAgent.injectionContainer.resolve(DidCommMessageRepository) - - const request = await didCommMessageRepository.findAgentMessage(faberAgent.context, { - associatedRecordId: faberProofExchangeRecord.id, - messageClass: V1RequestPresentationMessage, - }) + let aliceProofExchangeRecord = await aliceProofExchangeRecordPromise + const request = await faberAgent.proofs.findRequestMessage(faberProofExchangeRecord.id) expect(request).toMatchObject({ type: 'https://didcomm.org/present-proof/1.0/request-presentation', id: expect.any(String), - requestPresentationAttachments: [ + requestAttachments: [ { id: 'libindy-request-presentation-0', mimeType: 'application/json', @@ -648,10 +558,10 @@ describe('Present Proof', () => { state: ProofState.Abandoned, }) - aliceProofExchangeRecord = await aliceAgent.proofs.sendProblemReport( - aliceProofExchangeRecord.id, - 'Problem inside proof request' - ) + aliceProofExchangeRecord = await aliceAgent.proofs.sendProblemReport({ + proofRecordId: aliceProofExchangeRecord.id, + description: 'Problem inside proof request', + }) faberProofExchangeRecord = await faberProofExchangeRecordPromise diff --git a/packages/anoncreds/src/protocols/proofs/v1/__tests__/v1-proofs-auto-accept.e2e.test.ts b/packages/anoncreds/src/protocols/proofs/v1/__tests__/v1-proofs-auto-accept.e2e.test.ts new file mode 100644 index 0000000000..407e975271 --- /dev/null +++ b/packages/anoncreds/src/protocols/proofs/v1/__tests__/v1-proofs-auto-accept.e2e.test.ts @@ -0,0 +1,272 @@ +import type { EventReplaySubject } from '../../../../../../core/tests' +import type { AnonCredsTestsAgent } from '../../../../../tests/legacyAnonCredsSetup' + +import { AutoAcceptProof, ProofState } from '../../../../../../core/src' +import { testLogger, waitForProofExchangeRecord } from '../../../../../../core/tests' +import { issueLegacyAnonCredsCredential, setupAnonCredsTests } from '../../../../../tests/legacyAnonCredsSetup' + +describe('Auto accept present proof', () => { + let faberAgent: AnonCredsTestsAgent + let faberReplay: EventReplaySubject + let aliceAgent: AnonCredsTestsAgent + let aliceReplay: EventReplaySubject + let faberConnectionId: string + let aliceConnectionId: string + let credentialDefinitionId: string + + describe("Auto accept on 'always'", () => { + beforeAll(async () => { + ;({ + issuerAgent: faberAgent, + issuerReplay: faberReplay, + holderAgent: aliceAgent, + holderReplay: aliceReplay, + credentialDefinitionId, + issuerHolderConnectionId: faberConnectionId, + holderIssuerConnectionId: aliceConnectionId, + } = await setupAnonCredsTests({ + issuerName: 'Faber Auto Accept Always Proofs', + holderName: 'Alice Auto Accept Always Proofs', + autoAcceptProofs: AutoAcceptProof.Always, + attributeNames: ['name', 'age'], + })) + + await issueLegacyAnonCredsCredential({ + issuerAgent: faberAgent, + issuerReplay: faberReplay, + holderAgent: aliceAgent, + holderReplay: aliceReplay, + issuerHolderConnectionId: faberConnectionId, + offer: { + credentialDefinitionId, + attributes: [ + { name: 'name', value: 'John' }, + { name: 'age', value: '99' }, + ], + }, + }) + }) + afterAll(async () => { + await faberAgent.shutdown() + await faberAgent.wallet.delete() + await aliceAgent.shutdown() + await aliceAgent.wallet.delete() + }) + + test("Alice starts with proof proposal to Faber, both with autoAcceptProof on 'always'", async () => { + testLogger.test('Alice sends presentation proposal to Faber') + + await aliceAgent.proofs.proposeProof({ + connectionId: aliceConnectionId, + protocolVersion: 'v1', + proofFormats: { + indy: { + name: 'abc', + version: '1.0', + attributes: [ + { + name: 'name', + value: 'John', + credentialDefinitionId, + }, + ], + predicates: [ + { + name: 'age', + predicate: '>=', + threshold: 50, + credentialDefinitionId, + }, + ], + }, + }, + }) + + testLogger.test('Faber waits for presentation from Alice') + testLogger.test('Alice waits till it receives presentation ack') + await Promise.all([ + waitForProofExchangeRecord(faberAgent, { state: ProofState.Done }), + waitForProofExchangeRecord(aliceAgent, { state: ProofState.Done }), + ]) + }) + + test("Faber starts with proof requests to Alice, both with autoAcceptProof on 'always'", async () => { + testLogger.test('Faber sends presentation request to Alice') + + await faberAgent.proofs.requestProof({ + protocolVersion: 'v1', + connectionId: faberConnectionId, + proofFormats: { + indy: { + name: 'proof-request', + version: '1.0', + requested_attributes: { + name: { + name: 'name', + restrictions: [ + { + cred_def_id: credentialDefinitionId, + }, + ], + }, + }, + requested_predicates: { + age: { + name: 'age', + p_type: '>=', + p_value: 50, + restrictions: [ + { + cred_def_id: credentialDefinitionId, + }, + ], + }, + }, + }, + }, + }) + + testLogger.test('Faber waits for presentation from Alice') + await Promise.all([ + waitForProofExchangeRecord(faberAgent, { state: ProofState.Done }), + waitForProofExchangeRecord(aliceAgent, { state: ProofState.Done }), + ]) + }) + }) + + describe("Auto accept on 'contentApproved'", () => { + beforeAll(async () => { + testLogger.test('Initializing the agents') + ;({ + issuerAgent: faberAgent, + issuerReplay: faberReplay, + holderAgent: aliceAgent, + holderReplay: aliceReplay, + credentialDefinitionId, + issuerHolderConnectionId: faberConnectionId, + holderIssuerConnectionId: aliceConnectionId, + } = await setupAnonCredsTests({ + issuerName: 'Faber Auto Accept ContentApproved Proofs', + holderName: 'Alice Auto Accept ContentApproved Proofs', + autoAcceptProofs: AutoAcceptProof.ContentApproved, + attributeNames: ['name', 'age'], + })) + + await issueLegacyAnonCredsCredential({ + issuerAgent: faberAgent, + issuerReplay: faberReplay, + holderAgent: aliceAgent, + holderReplay: aliceReplay, + issuerHolderConnectionId: faberConnectionId, + offer: { + credentialDefinitionId, + attributes: [ + { name: 'name', value: 'John' }, + { name: 'age', value: '99' }, + ], + }, + }) + }) + afterAll(async () => { + testLogger.test('Shutting down both agents') + await faberAgent.shutdown() + await faberAgent.wallet.delete() + await aliceAgent.shutdown() + await aliceAgent.wallet.delete() + }) + + test("Alice starts with proof proposal to Faber, both with autoAcceptProof on 'contentApproved'", async () => { + testLogger.test('Alice sends presentation proposal to Faber') + + const aliceProofExchangeRecord = await aliceAgent.proofs.proposeProof({ + connectionId: aliceConnectionId, + protocolVersion: 'v1', + proofFormats: { + indy: { + name: 'abc', + version: '1.0', + attributes: [ + { + name: 'name', + value: 'John', + credentialDefinitionId, + }, + ], + predicates: [ + { + name: 'age', + predicate: '>=', + threshold: 50, + credentialDefinitionId, + }, + ], + }, + }, + }) + + testLogger.test('Faber waits for presentation proposal from Alice') + const faberProofExchangeRecord = await waitForProofExchangeRecord(faberAgent, { + threadId: aliceProofExchangeRecord.threadId, + state: ProofState.ProposalReceived, + }) + + testLogger.test('Faber accepts presentation proposal from Alice') + await faberAgent.proofs.acceptProposal({ proofRecordId: faberProofExchangeRecord.id }) + + await Promise.all([ + waitForProofExchangeRecord(aliceAgent, { state: ProofState.Done }), + waitForProofExchangeRecord(faberAgent, { state: ProofState.Done }), + ]) + }) + + test("Faber starts with proof requests to Alice, both with autoAcceptProof on 'contentApproved'", async () => { + testLogger.test('Faber sends presentation request to Alice') + + await faberAgent.proofs.requestProof({ + protocolVersion: 'v1', + connectionId: faberConnectionId, + proofFormats: { + indy: { + name: 'proof-request', + version: '1.0', + requested_attributes: { + name: { + name: 'name', + restrictions: [ + { + cred_def_id: credentialDefinitionId, + }, + ], + }, + }, + requested_predicates: { + age: { + name: 'age', + p_type: '>=', + p_value: 50, + restrictions: [ + { + cred_def_id: credentialDefinitionId, + }, + ], + }, + }, + }, + }, + }) + + testLogger.test('Alice waits for request from Faber') + const { id: proofRecordId } = await waitForProofExchangeRecord(aliceAgent, { + state: ProofState.RequestReceived, + }) + + const { proofFormats } = await aliceAgent.proofs.selectCredentialsForRequest({ proofRecordId }) + await aliceAgent.proofs.acceptRequest({ proofRecordId, proofFormats }) + + await Promise.all([ + waitForProofExchangeRecord(aliceAgent, { state: ProofState.Done }), + waitForProofExchangeRecord(faberAgent, { state: ProofState.Done }), + ]) + }) + }) +}) diff --git a/packages/core/src/modules/proofs/protocol/v1/errors/V1PresentationProblemReportError.ts b/packages/anoncreds/src/protocols/proofs/v1/errors/V1PresentationProblemReportError.ts similarity index 62% rename from packages/core/src/modules/proofs/protocol/v1/errors/V1PresentationProblemReportError.ts rename to packages/anoncreds/src/protocols/proofs/v1/errors/V1PresentationProblemReportError.ts index 27c77c0f82..4ec7280eae 100644 --- a/packages/core/src/modules/proofs/protocol/v1/errors/V1PresentationProblemReportError.ts +++ b/packages/anoncreds/src/protocols/proofs/v1/errors/V1PresentationProblemReportError.ts @@ -1,8 +1,8 @@ -import type { ProblemReportErrorOptions } from '../../../../problem-reports' -import type { PresentationProblemReportReason } from '../../../errors/PresentationProblemReportReason' +import type { ProblemReportErrorOptions, PresentationProblemReportReason } from '@aries-framework/core' -import { ProblemReportError } from '../../../../problem-reports' -import { V1PresentationProblemReportMessage } from '../messages/V1PresentationProblemReportMessage' +import { ProblemReportError } from '@aries-framework/core' + +import { V1PresentationProblemReportMessage } from '../messages' interface V1PresentationProblemReportErrorOptions extends ProblemReportErrorOptions { problemCode: PresentationProblemReportReason diff --git a/packages/core/src/modules/proofs/protocol/v1/errors/index.ts b/packages/anoncreds/src/protocols/proofs/v1/errors/index.ts similarity index 100% rename from packages/core/src/modules/proofs/protocol/v1/errors/index.ts rename to packages/anoncreds/src/protocols/proofs/v1/errors/index.ts diff --git a/packages/core/src/modules/proofs/protocol/v1/handlers/V1PresentationAckHandler.ts b/packages/anoncreds/src/protocols/proofs/v1/handlers/V1PresentationAckHandler.ts similarity index 55% rename from packages/core/src/modules/proofs/protocol/v1/handlers/V1PresentationAckHandler.ts rename to packages/anoncreds/src/protocols/proofs/v1/handlers/V1PresentationAckHandler.ts index cdc9f6d797..dc087fe1af 100644 --- a/packages/core/src/modules/proofs/protocol/v1/handlers/V1PresentationAckHandler.ts +++ b/packages/anoncreds/src/protocols/proofs/v1/handlers/V1PresentationAckHandler.ts @@ -1,17 +1,17 @@ -import type { MessageHandler, MessageHandlerInboundMessage } from '../../../../../agent/MessageHandler' -import type { V1ProofService } from '../V1ProofService' +import type { V1ProofProtocol } from '../V1ProofProtocol' +import type { MessageHandler, MessageHandlerInboundMessage } from '@aries-framework/core' import { V1PresentationAckMessage } from '../messages' export class V1PresentationAckHandler implements MessageHandler { - private proofService: V1ProofService + private proofProtocol: V1ProofProtocol public supportedMessages = [V1PresentationAckMessage] - public constructor(proofService: V1ProofService) { - this.proofService = proofService + public constructor(proofProtocol: V1ProofProtocol) { + this.proofProtocol = proofProtocol } public async handle(messageContext: MessageHandlerInboundMessage) { - await this.proofService.processAck(messageContext) + await this.proofProtocol.processAck(messageContext) } } diff --git a/packages/anoncreds/src/protocols/proofs/v1/handlers/V1PresentationHandler.ts b/packages/anoncreds/src/protocols/proofs/v1/handlers/V1PresentationHandler.ts new file mode 100644 index 0000000000..41bcd9b4ae --- /dev/null +++ b/packages/anoncreds/src/protocols/proofs/v1/handlers/V1PresentationHandler.ts @@ -0,0 +1,78 @@ +import type { V1ProofProtocol } from '../V1ProofProtocol' +import type { MessageHandler, MessageHandlerInboundMessage, ProofExchangeRecord } from '@aries-framework/core' + +import { OutboundMessageContext, DidCommMessageRepository } from '@aries-framework/core' + +import { V1PresentationMessage, V1RequestPresentationMessage } from '../messages' + +export class V1PresentationHandler implements MessageHandler { + private proofProtocol: V1ProofProtocol + public supportedMessages = [V1PresentationMessage] + + public constructor(proofProtocol: V1ProofProtocol) { + this.proofProtocol = proofProtocol + } + + public async handle(messageContext: MessageHandlerInboundMessage) { + const proofRecord = await this.proofProtocol.processPresentation(messageContext) + + const shouldAutoRespond = await this.proofProtocol.shouldAutoRespondToPresentation(messageContext.agentContext, { + presentationMessage: messageContext.message, + proofRecord, + }) + + if (shouldAutoRespond) { + return await this.acceptPresentation(proofRecord, messageContext) + } + } + + private async acceptPresentation( + proofRecord: ProofExchangeRecord, + messageContext: MessageHandlerInboundMessage + ) { + messageContext.agentContext.config.logger.info(`Automatically sending acknowledgement with autoAccept`) + + if (messageContext.connection) { + const { message } = await this.proofProtocol.acceptPresentation(messageContext.agentContext, { + proofRecord, + }) + + return new OutboundMessageContext(message, { + agentContext: messageContext.agentContext, + connection: messageContext.connection, + associatedRecord: proofRecord, + }) + } else if (messageContext.message.service) { + const { message } = await this.proofProtocol.acceptPresentation(messageContext.agentContext, { + proofRecord, + }) + + const didCommMessageRepository = messageContext.agentContext.dependencyManager.resolve(DidCommMessageRepository) + const requestMessage = await didCommMessageRepository.findAgentMessage(messageContext.agentContext, { + associatedRecordId: proofRecord.id, + messageClass: V1RequestPresentationMessage, + }) + + const recipientService = messageContext.message.service + const ourService = requestMessage?.service + + if (!ourService) { + messageContext.agentContext.config.logger.error( + `Could not automatically create presentation ack. Missing ourService on request message` + ) + return + } + + return new OutboundMessageContext(message, { + agentContext: messageContext.agentContext, + serviceParams: { + service: recipientService.resolvedDidCommService, + senderKey: ourService.resolvedDidCommService.recipientKeys[0], + returnRoute: true, + }, + }) + } + + messageContext.agentContext.config.logger.error(`Could not automatically create presentation ack`) + } +} diff --git a/packages/core/src/modules/proofs/protocol/v1/handlers/V1PresentationProblemReportHandler.ts b/packages/anoncreds/src/protocols/proofs/v1/handlers/V1PresentationProblemReportHandler.ts similarity index 59% rename from packages/core/src/modules/proofs/protocol/v1/handlers/V1PresentationProblemReportHandler.ts rename to packages/anoncreds/src/protocols/proofs/v1/handlers/V1PresentationProblemReportHandler.ts index e3c7f97410..4106bbf3f9 100644 --- a/packages/core/src/modules/proofs/protocol/v1/handlers/V1PresentationProblemReportHandler.ts +++ b/packages/anoncreds/src/protocols/proofs/v1/handlers/V1PresentationProblemReportHandler.ts @@ -1,17 +1,17 @@ -import type { MessageHandler, MessageHandlerInboundMessage } from '../../../../../agent/MessageHandler' -import type { V1ProofService } from '../V1ProofService' +import type { V1ProofProtocol } from '../V1ProofProtocol' +import type { MessageHandler, MessageHandlerInboundMessage } from '@aries-framework/core' import { V1PresentationProblemReportMessage } from '../messages/V1PresentationProblemReportMessage' export class V1PresentationProblemReportHandler implements MessageHandler { - private proofService: V1ProofService + private proofProtocol: V1ProofProtocol public supportedMessages = [V1PresentationProblemReportMessage] - public constructor(proofService: V1ProofService) { - this.proofService = proofService + public constructor(proofProtocol: V1ProofProtocol) { + this.proofProtocol = proofProtocol } public async handle(messageContext: MessageHandlerInboundMessage) { - await this.proofService.processProblemReport(messageContext) + await this.proofProtocol.processProblemReport(messageContext) } } diff --git a/packages/anoncreds/src/protocols/proofs/v1/handlers/V1ProposePresentationHandler.ts b/packages/anoncreds/src/protocols/proofs/v1/handlers/V1ProposePresentationHandler.ts new file mode 100644 index 0000000000..2193f6e733 --- /dev/null +++ b/packages/anoncreds/src/protocols/proofs/v1/handlers/V1ProposePresentationHandler.ts @@ -0,0 +1,50 @@ +import type { V1ProofProtocol } from '../V1ProofProtocol' +import type { MessageHandler, MessageHandlerInboundMessage, ProofExchangeRecord } from '@aries-framework/core' + +import { OutboundMessageContext } from '@aries-framework/core' + +import { V1ProposePresentationMessage } from '../messages' + +export class V1ProposePresentationHandler implements MessageHandler { + private proofProtocol: V1ProofProtocol + public supportedMessages = [V1ProposePresentationMessage] + + public constructor(proofProtocol: V1ProofProtocol) { + this.proofProtocol = proofProtocol + } + + public async handle(messageContext: MessageHandlerInboundMessage) { + const proofRecord = await this.proofProtocol.processProposal(messageContext) + + const shouldAutoRespond = await this.proofProtocol.shouldAutoRespondToProposal(messageContext.agentContext, { + proofRecord, + proposalMessage: messageContext.message, + }) + + if (shouldAutoRespond) { + return await this.acceptProposal(proofRecord, messageContext) + } + } + + private async acceptProposal( + proofRecord: ProofExchangeRecord, + messageContext: MessageHandlerInboundMessage + ) { + messageContext.agentContext.config.logger.info(`Automatically sending request with autoAccept`) + + if (!messageContext.connection) { + messageContext.agentContext.config.logger.error('No connection on the messageContext, aborting auto accept') + return + } + + const { message } = await this.proofProtocol.acceptProposal(messageContext.agentContext, { + proofRecord, + }) + + return new OutboundMessageContext(message, { + agentContext: messageContext.agentContext, + connection: messageContext.connection, + associatedRecord: proofRecord, + }) + } +} diff --git a/packages/anoncreds/src/protocols/proofs/v1/handlers/V1RequestPresentationHandler.ts b/packages/anoncreds/src/protocols/proofs/v1/handlers/V1RequestPresentationHandler.ts new file mode 100644 index 0000000000..a697f0da90 --- /dev/null +++ b/packages/anoncreds/src/protocols/proofs/v1/handlers/V1RequestPresentationHandler.ts @@ -0,0 +1,87 @@ +import type { V1ProofProtocol } from '../V1ProofProtocol' +import type { MessageHandler, MessageHandlerInboundMessage, ProofExchangeRecord } from '@aries-framework/core' + +import { + OutboundMessageContext, + RoutingService, + ServiceDecorator, + DidCommMessageRepository, + DidCommMessageRole, +} from '@aries-framework/core' + +import { V1RequestPresentationMessage } from '../messages' + +export class V1RequestPresentationHandler implements MessageHandler { + private proofProtocol: V1ProofProtocol + public supportedMessages = [V1RequestPresentationMessage] + + public constructor(proofProtocol: V1ProofProtocol) { + this.proofProtocol = proofProtocol + } + + public async handle(messageContext: MessageHandlerInboundMessage) { + const proofRecord = await this.proofProtocol.processRequest(messageContext) + + const shouldAutoRespond = await this.proofProtocol.shouldAutoRespondToRequest(messageContext.agentContext, { + proofRecord, + requestMessage: messageContext.message, + }) + + if (shouldAutoRespond) { + return await this.acceptRequest(proofRecord, messageContext) + } + } + + private async acceptRequest( + proofRecord: ProofExchangeRecord, + messageContext: MessageHandlerInboundMessage + ) { + messageContext.agentContext.config.logger.info(`Automatically sending presentation with autoAccept on`) + + if (messageContext.connection) { + const { message } = await this.proofProtocol.acceptRequest(messageContext.agentContext, { + proofRecord, + }) + + return new OutboundMessageContext(message, { + agentContext: messageContext.agentContext, + connection: messageContext.connection, + associatedRecord: proofRecord, + }) + } else if (messageContext.message.service) { + const { message } = await this.proofProtocol.acceptRequest(messageContext.agentContext, { + proofRecord, + }) + + const routingService = messageContext.agentContext.dependencyManager.resolve(RoutingService) + const routing = await routingService.getRouting(messageContext.agentContext) + const ourService = new ServiceDecorator({ + serviceEndpoint: routing.endpoints[0], + recipientKeys: [routing.recipientKey.publicKeyBase58], + routingKeys: routing.routingKeys.map((key) => key.publicKeyBase58), + }) + const recipientService = messageContext.message.service + + // Set and save ~service decorator to record (to remember our verkey) + message.service = ourService + + const didCommMessageRepository = messageContext.agentContext.dependencyManager.resolve(DidCommMessageRepository) + await didCommMessageRepository.saveOrUpdateAgentMessage(messageContext.agentContext, { + agentMessage: message, + associatedRecordId: proofRecord.id, + role: DidCommMessageRole.Sender, + }) + + return new OutboundMessageContext(message, { + agentContext: messageContext.agentContext, + serviceParams: { + service: recipientService.resolvedDidCommService, + senderKey: message.service.resolvedDidCommService.recipientKeys[0], + returnRoute: true, + }, + }) + } + + messageContext.agentContext.config.logger.error(`Could not automatically create presentation`) + } +} diff --git a/packages/core/src/modules/proofs/protocol/v1/handlers/index.ts b/packages/anoncreds/src/protocols/proofs/v1/handlers/index.ts similarity index 100% rename from packages/core/src/modules/proofs/protocol/v1/handlers/index.ts rename to packages/anoncreds/src/protocols/proofs/v1/handlers/index.ts diff --git a/packages/core/src/modules/proofs/protocol/v1/index.ts b/packages/anoncreds/src/protocols/proofs/v1/index.ts similarity index 69% rename from packages/core/src/modules/proofs/protocol/v1/index.ts rename to packages/anoncreds/src/protocols/proofs/v1/index.ts index a7e92f64d6..e698bc7140 100644 --- a/packages/core/src/modules/proofs/protocol/v1/index.ts +++ b/packages/anoncreds/src/protocols/proofs/v1/index.ts @@ -1,4 +1,4 @@ export * from './errors' export * from './messages' export * from './models' -export * from './V1ProofService' +export * from './V1ProofProtocol' diff --git a/packages/core/src/modules/proofs/protocol/v1/messages/V1PresentationAckMessage.ts b/packages/anoncreds/src/protocols/proofs/v1/messages/V1PresentationAckMessage.ts similarity index 64% rename from packages/core/src/modules/proofs/protocol/v1/messages/V1PresentationAckMessage.ts rename to packages/anoncreds/src/protocols/proofs/v1/messages/V1PresentationAckMessage.ts index 29f12a8dd9..6c44d9db82 100644 --- a/packages/core/src/modules/proofs/protocol/v1/messages/V1PresentationAckMessage.ts +++ b/packages/anoncreds/src/protocols/proofs/v1/messages/V1PresentationAckMessage.ts @@ -1,9 +1,10 @@ -import type { AckMessageOptions } from '../../../../common' +import type { AckMessageOptions } from '@aries-framework/core' -import { IsValidMessageType, parseMessageType } from '../../../../../utils/messageType' -import { AckMessage } from '../../../../common' +import { AckMessage, IsValidMessageType, parseMessageType } from '@aries-framework/core' export class V1PresentationAckMessage extends AckMessage { + public readonly allowDidSovPrefix = true + public constructor(options: AckMessageOptions) { super(options) } diff --git a/packages/core/src/modules/proofs/protocol/v1/messages/V1PresentationMessage.ts b/packages/anoncreds/src/protocols/proofs/v1/messages/V1PresentationMessage.ts similarity index 57% rename from packages/core/src/modules/proofs/protocol/v1/messages/V1PresentationMessage.ts rename to packages/anoncreds/src/protocols/proofs/v1/messages/V1PresentationMessage.ts index 5ba6af9001..628362cc13 100644 --- a/packages/core/src/modules/proofs/protocol/v1/messages/V1PresentationMessage.ts +++ b/packages/anoncreds/src/protocols/proofs/v1/messages/V1PresentationMessage.ts @@ -1,19 +1,12 @@ -import type { ProofAttachmentFormat } from '../../../formats/models/ProofAttachmentFormat' -import type { IndyProof } from 'indy-sdk' +import type { AnonCredsProof } from '../../../../models' +import { IsValidMessageType, parseMessageType, DidCommV1Message, V1Attachment } from '@aries-framework/core' import { Expose, Type } from 'class-transformer' import { IsArray, IsString, ValidateNested, IsOptional, IsInstance } from 'class-validator' -import { V1Attachment } from '../../../../../decorators/attachment/V1Attachment' -import { DidCommV1Message } from '../../../../../didcomm' -import { AriesFrameworkError } from '../../../../../error/AriesFrameworkError' -import { IsValidMessageType, parseMessageType } from '../../../../../utils/messageType' -import { V2_INDY_PRESENTATION } from '../../../formats/ProofFormatConstants' -import { ProofFormatSpec } from '../../../models/ProofFormatSpec' - export const INDY_PROOF_ATTACHMENT_ID = 'libindy-presentation-0' -export interface PresentationOptions { +export interface V1PresentationMessageOptions { id?: string comment?: string presentationAttachments: V1Attachment[] @@ -27,7 +20,9 @@ export interface PresentationOptions { * @see https://github.com/hyperledger/aries-rfcs/blob/master/features/0037-present-proof/README.md#presentation */ export class V1PresentationMessage extends DidCommV1Message { - public constructor(options: PresentationOptions) { + public readonly allowDidSovPrefix = true + + public constructor(options: V1PresentationMessageOptions) { super() if (options) { @@ -61,30 +56,16 @@ export class V1PresentationMessage extends DidCommV1Message { @IsInstance(V1Attachment, { each: true }) public presentationAttachments!: V1Attachment[] - public getAttachmentFormats(): ProofAttachmentFormat[] { - const attachment = this.indyAttachment + public get indyProof(): AnonCredsProof | null { + const attachment = + this.presentationAttachments.find((attachment) => attachment.id === INDY_PROOF_ATTACHMENT_ID) ?? null - if (!attachment) { - throw new AriesFrameworkError(`Could not find a presentation attachment`) - } - - return [ - { - format: new ProofFormatSpec({ format: V2_INDY_PRESENTATION }), - attachment: attachment, - }, - ] - } + const proofJson = attachment?.getDataAsJson() ?? null - public get indyAttachment(): V1Attachment | null { - return this.presentationAttachments.find((attachment) => attachment.id === INDY_PROOF_ATTACHMENT_ID) ?? null + return proofJson } - public get indyProof(): IndyProof | null { - const attachment = this.indyAttachment - - const proofJson = attachment?.getDataAsJson() ?? null - - return proofJson + public getPresentationAttachmentById(id: string): V1Attachment | undefined { + return this.presentationAttachments.find((attachment) => attachment.id === id) } } diff --git a/packages/core/src/modules/proofs/protocol/v1/messages/V1PresentationProblemReportMessage.ts b/packages/anoncreds/src/protocols/proofs/v1/messages/V1PresentationProblemReportMessage.ts similarity index 71% rename from packages/core/src/modules/proofs/protocol/v1/messages/V1PresentationProblemReportMessage.ts rename to packages/anoncreds/src/protocols/proofs/v1/messages/V1PresentationProblemReportMessage.ts index 6978318eee..479aade010 100644 --- a/packages/core/src/modules/proofs/protocol/v1/messages/V1PresentationProblemReportMessage.ts +++ b/packages/anoncreds/src/protocols/proofs/v1/messages/V1PresentationProblemReportMessage.ts @@ -1,7 +1,6 @@ -import type { ProblemReportMessageOptions } from '../../../../problem-reports/versions/v1/messages/ProblemReportMessage' +import type { ProblemReportMessageOptions } from '@aries-framework/core' -import { IsValidMessageType, parseMessageType } from '../../../../../utils/messageType' -import { ProblemReportMessage } from '../../../../problem-reports/versions/v1/messages/ProblemReportMessage' +import { ProblemReportMessage, IsValidMessageType, parseMessageType } from '@aries-framework/core' export type V1PresentationProblemReportMessageOptions = ProblemReportMessageOptions @@ -9,6 +8,8 @@ export type V1PresentationProblemReportMessageOptions = ProblemReportMessageOpti * @see https://github.com/hyperledger/aries-rfcs/blob/main/features/0035-report-problem/README.md */ export class V1PresentationProblemReportMessage extends ProblemReportMessage { + public readonly allowDidSovPrefix = true + /** * Create new PresentationProblemReportMessage instance. * @param options description of error and multiple optional fields for reporting problem diff --git a/packages/core/src/modules/proofs/protocol/v1/messages/V1ProposePresentationMessage.ts b/packages/anoncreds/src/protocols/proofs/v1/messages/V1ProposePresentationMessage.ts similarity index 64% rename from packages/core/src/modules/proofs/protocol/v1/messages/V1ProposePresentationMessage.ts rename to packages/anoncreds/src/protocols/proofs/v1/messages/V1ProposePresentationMessage.ts index e246687518..1078e93aa4 100644 --- a/packages/core/src/modules/proofs/protocol/v1/messages/V1ProposePresentationMessage.ts +++ b/packages/anoncreds/src/protocols/proofs/v1/messages/V1ProposePresentationMessage.ts @@ -1,15 +1,13 @@ +import { IsValidMessageType, parseMessageType, DidCommV1Message } from '@aries-framework/core' import { Expose, Type } from 'class-transformer' import { IsInstance, IsOptional, IsString, ValidateNested } from 'class-validator' -import { DidCommV1Message } from '../../../../../didcomm' -import { IsValidMessageType, parseMessageType } from '../../../../../utils/messageType' -import { PresentationPreview } from '../models/V1PresentationPreview' +import { V1PresentationPreview } from '../models/V1PresentationPreview' -export interface ProposePresentationMessageOptions { +export interface V1ProposePresentationMessageOptions { id?: string comment?: string - parentThreadId?: string - presentationProposal: PresentationPreview + presentationProposal: V1PresentationPreview } /** @@ -18,17 +16,13 @@ export interface ProposePresentationMessageOptions { * @see https://github.com/hyperledger/aries-rfcs/blob/master/features/0037-present-proof/README.md#propose-presentation */ export class V1ProposePresentationMessage extends DidCommV1Message { - public constructor(options: ProposePresentationMessageOptions) { + public readonly allowDidSovPrefix = true + public constructor(options: V1ProposePresentationMessageOptions) { super() if (options) { this.id = options.id ?? this.generateId() this.comment = options.comment - if (options.parentThreadId) { - this.setThread({ - parentThreadId: options.parentThreadId, - }) - } this.presentationProposal = options.presentationProposal } } @@ -48,8 +42,8 @@ export class V1ProposePresentationMessage extends DidCommV1Message { * Represents the presentation example that prover wants to provide. */ @Expose({ name: 'presentation_proposal' }) - @Type(() => PresentationPreview) + @Type(() => V1PresentationPreview) @ValidateNested() - @IsInstance(PresentationPreview) - public presentationProposal!: PresentationPreview + @IsInstance(V1PresentationPreview) + public presentationProposal!: V1PresentationPreview } diff --git a/packages/anoncreds/src/protocols/proofs/v1/messages/V1RequestPresentationMessage.ts b/packages/anoncreds/src/protocols/proofs/v1/messages/V1RequestPresentationMessage.ts new file mode 100644 index 0000000000..0232f336ed --- /dev/null +++ b/packages/anoncreds/src/protocols/proofs/v1/messages/V1RequestPresentationMessage.ts @@ -0,0 +1,65 @@ +import type { LegacyIndyProofRequest } from '../../../../formats' + +import { V1Attachment, IsValidMessageType, parseMessageType, DidCommV1Message } from '@aries-framework/core' +import { Expose, Type } from 'class-transformer' +import { IsArray, IsString, ValidateNested, IsOptional, IsInstance } from 'class-validator' + +export interface V1RequestPresentationMessageOptions { + id?: string + comment?: string + requestAttachments: V1Attachment[] +} + +export const INDY_PROOF_REQUEST_ATTACHMENT_ID = 'libindy-request-presentation-0' + +/** + * Request Presentation Message part of Present Proof Protocol used to initiate request from verifier to prover. + * + * @see https://github.com/hyperledger/aries-rfcs/blob/master/features/0037-present-proof/README.md#request-presentation + */ +export class V1RequestPresentationMessage extends DidCommV1Message { + public readonly allowDidSovPrefix = true + + public constructor(options: V1RequestPresentationMessageOptions) { + super() + + if (options) { + this.id = options.id ?? this.generateId() + this.comment = options.comment + this.requestAttachments = options.requestAttachments + } + } + + @IsValidMessageType(V1RequestPresentationMessage.type) + public readonly type = V1RequestPresentationMessage.type.messageTypeUri + public static readonly type = parseMessageType('https://didcomm.org/present-proof/1.0/request-presentation') + + /** + * Provides some human readable information about this request for a presentation. + */ + @IsOptional() + @IsString() + public comment?: string + + /** + * An array of attachments defining the acceptable formats for the presentation. + */ + @Expose({ name: 'request_presentations~attach' }) + @Type(() => V1Attachment) + @IsArray() + @ValidateNested({ + each: true, + }) + @IsInstance(V1Attachment, { each: true }) + public requestAttachments!: V1Attachment[] + + public get indyProofRequest(): LegacyIndyProofRequest | null { + const attachment = this.requestAttachments.find((attachment) => attachment.id === INDY_PROOF_REQUEST_ATTACHMENT_ID) + // Extract proof request from attachment + return attachment?.getDataAsJson() ?? null + } + + public getRequestAttachmentById(id: string): V1Attachment | undefined { + return this.requestAttachments.find((attachment) => attachment.id === id) + } +} diff --git a/packages/core/src/modules/proofs/protocol/v1/messages/index.ts b/packages/anoncreds/src/protocols/proofs/v1/messages/index.ts similarity index 100% rename from packages/core/src/modules/proofs/protocol/v1/messages/index.ts rename to packages/anoncreds/src/protocols/proofs/v1/messages/index.ts diff --git a/packages/core/src/modules/proofs/protocol/v1/models/V1PresentationPreview.ts b/packages/anoncreds/src/protocols/proofs/v1/models/V1PresentationPreview.ts similarity index 55% rename from packages/core/src/modules/proofs/protocol/v1/models/V1PresentationPreview.ts rename to packages/anoncreds/src/protocols/proofs/v1/models/V1PresentationPreview.ts index 2ac71e903e..47cbf8a636 100644 --- a/packages/core/src/modules/proofs/protocol/v1/models/V1PresentationPreview.ts +++ b/packages/anoncreds/src/protocols/proofs/v1/models/V1PresentationPreview.ts @@ -1,6 +1,7 @@ +import { JsonTransformer, IsValidMessageType, replaceLegacyDidSovPrefix, parseMessageType } from '@aries-framework/core' import { Expose, Transform, Type } from 'class-transformer' import { - IsEnum, + IsIn, IsInstance, IsInt, IsMimeType, @@ -11,12 +12,10 @@ import { ValidateNested, } from 'class-validator' -import { credDefIdRegex } from '../../../../../utils' -import { JsonTransformer } from '../../../../../utils/JsonTransformer' -import { IsValidMessageType, parseMessageType, replaceLegacyDidSovPrefix } from '../../../../../utils/messageType' -import { PredicateType } from '../../../formats/indy/models/PredicateType' +import { anonCredsPredicateType, AnonCredsPredicateType } from '../../../../models' +import { unqualifiedCredentialDefinitionIdRegex } from '../../../../utils' -export interface PresentationPreviewAttributeOptions { +export interface V1PresentationPreviewAttributeOptions { name: string credentialDefinitionId?: string mimeType?: string @@ -24,8 +23,8 @@ export interface PresentationPreviewAttributeOptions { referent?: string } -export class PresentationPreviewAttribute { - public constructor(options: PresentationPreviewAttributeOptions) { +export class V1PresentationPreviewAttribute { + public constructor(options: V1PresentationPreviewAttributeOptions) { if (options) { this.name = options.name this.credentialDefinitionId = options.credentialDefinitionId @@ -39,8 +38,8 @@ export class PresentationPreviewAttribute { @Expose({ name: 'cred_def_id' }) @IsString() - @ValidateIf((o: PresentationPreviewAttribute) => o.referent !== undefined) - @Matches(credDefIdRegex) + @ValidateIf((o: V1PresentationPreviewAttribute) => o.referent !== undefined) + @Matches(unqualifiedCredentialDefinitionIdRegex) public credentialDefinitionId?: string @Expose({ name: 'mime-type' }) @@ -61,15 +60,15 @@ export class PresentationPreviewAttribute { } } -export interface PresentationPreviewPredicateOptions { +export interface V1PresentationPreviewPredicateOptions { name: string credentialDefinitionId: string - predicate: PredicateType + predicate: AnonCredsPredicateType threshold: number } -export class PresentationPreviewPredicate { - public constructor(options: PresentationPreviewPredicateOptions) { +export class V1PresentationPreviewPredicate { + public constructor(options: V1PresentationPreviewPredicateOptions) { if (options) { this.name = options.name this.credentialDefinitionId = options.credentialDefinitionId @@ -83,11 +82,11 @@ export class PresentationPreviewPredicate { @Expose({ name: 'cred_def_id' }) @IsString() - @Matches(credDefIdRegex) + @Matches(unqualifiedCredentialDefinitionIdRegex) public credentialDefinitionId!: string - @IsEnum(PredicateType) - public predicate!: PredicateType + @IsIn(anonCredsPredicateType) + public predicate!: AnonCredsPredicateType @IsInt() public threshold!: number @@ -97,9 +96,9 @@ export class PresentationPreviewPredicate { } } -export interface PresentationPreviewOptions { - attributes?: PresentationPreviewAttribute[] - predicates?: PresentationPreviewPredicate[] +export interface V1PresentationPreviewOptions { + attributes?: V1PresentationPreviewAttributeOptions[] + predicates?: V1PresentationPreviewPredicateOptions[] } /** @@ -109,31 +108,31 @@ export interface PresentationPreviewOptions { * * @see https://github.com/hyperledger/aries-rfcs/blob/master/features/0037-present-proof/README.md#presentation-preview */ -export class PresentationPreview { - public constructor(options: PresentationPreviewOptions) { +export class V1PresentationPreview { + public constructor(options: V1PresentationPreviewOptions) { if (options) { - this.attributes = options.attributes ?? [] - this.predicates = options.predicates ?? [] + this.attributes = options.attributes?.map((a) => new V1PresentationPreviewAttribute(a)) ?? [] + this.predicates = options.predicates?.map((p) => new V1PresentationPreviewPredicate(p)) ?? [] } } @Expose({ name: '@type' }) - @IsValidMessageType(PresentationPreview.type) + @IsValidMessageType(V1PresentationPreview.type) @Transform(({ value }) => replaceLegacyDidSovPrefix(value), { toClassOnly: true, }) - public readonly type = PresentationPreview.type.messageTypeUri + public readonly type = V1PresentationPreview.type.messageTypeUri public static readonly type = parseMessageType('https://didcomm.org/present-proof/1.0/presentation-preview') - @Type(() => PresentationPreviewAttribute) + @Type(() => V1PresentationPreviewAttribute) @ValidateNested({ each: true }) - @IsInstance(PresentationPreviewAttribute, { each: true }) - public attributes!: PresentationPreviewAttribute[] + @IsInstance(V1PresentationPreviewAttribute, { each: true }) + public attributes!: V1PresentationPreviewAttribute[] - @Type(() => PresentationPreviewPredicate) + @Type(() => V1PresentationPreviewPredicate) @ValidateNested({ each: true }) - @IsInstance(PresentationPreviewPredicate, { each: true }) - public predicates!: PresentationPreviewPredicate[] + @IsInstance(V1PresentationPreviewPredicate, { each: true }) + public predicates!: V1PresentationPreviewPredicate[] public toJSON(): Record { return JsonTransformer.toJSON(this) diff --git a/packages/anoncreds/src/protocols/proofs/v1/models/index.ts b/packages/anoncreds/src/protocols/proofs/v1/models/index.ts new file mode 100644 index 0000000000..56c1a4fde0 --- /dev/null +++ b/packages/anoncreds/src/protocols/proofs/v1/models/index.ts @@ -0,0 +1 @@ +export * from './V1PresentationPreview' diff --git a/packages/anoncreds/src/repository/AnonCredsCredentialDefinitionPrivateRecord.ts b/packages/anoncreds/src/repository/AnonCredsCredentialDefinitionPrivateRecord.ts new file mode 100644 index 0000000000..bc0c1c99ee --- /dev/null +++ b/packages/anoncreds/src/repository/AnonCredsCredentialDefinitionPrivateRecord.ts @@ -0,0 +1,41 @@ +import type { TagsBase } from '@aries-framework/core' + +import { BaseRecord, utils } from '@aries-framework/core' + +export interface AnonCredsCredentialDefinitionPrivateRecordProps { + id?: string + credentialDefinitionId: string + value: Record +} + +export type DefaultAnonCredsCredentialDefinitionPrivateTags = { + credentialDefinitionId: string +} + +export class AnonCredsCredentialDefinitionPrivateRecord extends BaseRecord< + DefaultAnonCredsCredentialDefinitionPrivateTags, + TagsBase +> { + public static readonly type = 'AnonCredsCredentialDefinitionPrivateRecord' + public readonly type = AnonCredsCredentialDefinitionPrivateRecord.type + + public readonly credentialDefinitionId!: string + public readonly value!: Record // TODO: Define structure + + public constructor(props: AnonCredsCredentialDefinitionPrivateRecordProps) { + super() + + if (props) { + this.id = props.id ?? utils.uuid() + this.credentialDefinitionId = props.credentialDefinitionId + this.value = props.value + } + } + + public getTags() { + return { + ...this._tags, + credentialDefinitionId: this.credentialDefinitionId, + } + } +} diff --git a/packages/anoncreds/src/repository/AnonCredsCredentialDefinitionPrivateRepository.ts b/packages/anoncreds/src/repository/AnonCredsCredentialDefinitionPrivateRepository.ts new file mode 100644 index 0000000000..31c7737143 --- /dev/null +++ b/packages/anoncreds/src/repository/AnonCredsCredentialDefinitionPrivateRepository.ts @@ -0,0 +1,23 @@ +import type { AgentContext } from '@aries-framework/core' + +import { Repository, InjectionSymbols, StorageService, EventEmitter, injectable, inject } from '@aries-framework/core' + +import { AnonCredsCredentialDefinitionPrivateRecord } from './AnonCredsCredentialDefinitionPrivateRecord' + +@injectable() +export class AnonCredsCredentialDefinitionPrivateRepository extends Repository { + public constructor( + @inject(InjectionSymbols.StorageService) storageService: StorageService, + eventEmitter: EventEmitter + ) { + super(AnonCredsCredentialDefinitionPrivateRecord, storageService, eventEmitter) + } + + public async getByCredentialDefinitionId(agentContext: AgentContext, credentialDefinitionId: string) { + return this.getSingleByQuery(agentContext, { credentialDefinitionId }) + } + + public async findByCredentialDefinitionId(agentContext: AgentContext, credentialDefinitionId: string) { + return this.findSingleByQuery(agentContext, { credentialDefinitionId }) + } +} diff --git a/packages/anoncreds/src/repository/AnonCredsCredentialDefinitionRecord.ts b/packages/anoncreds/src/repository/AnonCredsCredentialDefinitionRecord.ts new file mode 100644 index 0000000000..fe0bcc6eea --- /dev/null +++ b/packages/anoncreds/src/repository/AnonCredsCredentialDefinitionRecord.ts @@ -0,0 +1,79 @@ +import type { AnonCredsCredentialDefinitionRecordMetadata } from './anonCredsCredentialDefinitionRecordMetadataTypes' +import type { AnonCredsCredentialDefinition } from '../models' +import type { TagsBase } from '@aries-framework/core' + +import { BaseRecord, utils } from '@aries-framework/core' + +import { + getUnqualifiedCredentialDefinitionId, + isDidIndyCredentialDefinitionId, + parseIndyCredentialDefinitionId, +} from '../utils/indyIdentifiers' + +export interface AnonCredsCredentialDefinitionRecordProps { + id?: string + credentialDefinitionId: string + credentialDefinition: AnonCredsCredentialDefinition + methodName: string +} + +export type DefaultAnonCredsCredentialDefinitionTags = { + schemaId: string + credentialDefinitionId: string + issuerId: string + tag: string + methodName: string + + // Stores the unqualified variant of the credential definition id, which allows issuing credentials using the legacy + // credential definition id, even though the credential definition id is stored in the wallet as a qualified id. + // This is only added when the credential definition id is an did:indy identifier. + unqualifiedCredentialDefinitionId?: string +} + +export class AnonCredsCredentialDefinitionRecord extends BaseRecord< + DefaultAnonCredsCredentialDefinitionTags, + TagsBase, + AnonCredsCredentialDefinitionRecordMetadata +> { + public static readonly type = 'AnonCredsCredentialDefinitionRecord' + public readonly type = AnonCredsCredentialDefinitionRecord.type + + public credentialDefinitionId!: string + public credentialDefinition!: AnonCredsCredentialDefinition + + /** + * AnonCreds method name. We don't use names explicitly from the registry (there's no identifier for a registry) + * @see https://hyperledger.github.io/anoncreds-methods-registry/ + */ + public methodName!: string + + public constructor(props: AnonCredsCredentialDefinitionRecordProps) { + super() + + if (props) { + this.id = props.id ?? utils.uuid() + this.credentialDefinitionId = props.credentialDefinitionId + this.credentialDefinition = props.credentialDefinition + this.methodName = props.methodName + } + } + + public getTags() { + let unqualifiedCredentialDefinitionId: string | undefined = undefined + if (isDidIndyCredentialDefinitionId(this.credentialDefinitionId)) { + const { namespaceIdentifier, schemaSeqNo, tag } = parseIndyCredentialDefinitionId(this.credentialDefinitionId) + + unqualifiedCredentialDefinitionId = getUnqualifiedCredentialDefinitionId(namespaceIdentifier, schemaSeqNo, tag) + } + + return { + ...this._tags, + credentialDefinitionId: this.credentialDefinitionId, + schemaId: this.credentialDefinition.schemaId, + issuerId: this.credentialDefinition.issuerId, + tag: this.credentialDefinition.tag, + methodName: this.methodName, + unqualifiedCredentialDefinitionId, + } + } +} diff --git a/packages/core/src/modules/indy/repository/AnonCredsCredentialDefinitionRepository.ts b/packages/anoncreds/src/repository/AnonCredsCredentialDefinitionRepository.ts similarity index 52% rename from packages/core/src/modules/indy/repository/AnonCredsCredentialDefinitionRepository.ts rename to packages/anoncreds/src/repository/AnonCredsCredentialDefinitionRepository.ts index 706c7e2cac..88aedef82a 100644 --- a/packages/core/src/modules/indy/repository/AnonCredsCredentialDefinitionRepository.ts +++ b/packages/anoncreds/src/repository/AnonCredsCredentialDefinitionRepository.ts @@ -1,10 +1,6 @@ -import type { AgentContext } from '../../../agent/context/AgentContext' +import type { AgentContext } from '@aries-framework/core' -import { EventEmitter } from '../../../agent/EventEmitter' -import { InjectionSymbols } from '../../../constants' -import { injectable, inject } from '../../../plugins' -import { Repository } from '../../../storage/Repository' -import { StorageService } from '../../../storage/StorageService' +import { Repository, InjectionSymbols, StorageService, EventEmitter, injectable, inject } from '@aries-framework/core' import { AnonCredsCredentialDefinitionRecord } from './AnonCredsCredentialDefinitionRecord' @@ -18,10 +14,28 @@ export class AnonCredsCredentialDefinitionRepository extends Repository { + public static readonly type = 'AnonCredsCredentialRecord' + public readonly type = AnonCredsCredentialRecord.type + + public readonly credentialId!: string + public readonly credentialRevocationId?: string + public readonly linkSecretId!: string + public readonly credential!: AnonCredsCredential + + /** + * AnonCreds method name. We don't use names explicitly from the registry (there's no identifier for a registry) + * @see https://hyperledger.github.io/anoncreds-methods-registry/ + */ + public readonly methodName!: string + + public constructor(props: AnonCredsCredentialRecordProps) { + super() + + if (props) { + this.id = props.id ?? utils.uuid() + this.credentialId = props.credentialId + this.credential = props.credential + this.credentialRevocationId = props.credentialRevocationId + this.linkSecretId = props.linkSecretId + this.methodName = props.methodName + this.setTags({ + issuerId: props.issuerId, + schemaIssuerId: props.schemaIssuerId, + schemaName: props.schemaName, + schemaVersion: props.schemaVersion, + }) + } + } + + public getTags() { + const tags: Tags = { + ...this._tags, + credentialDefinitionId: this.credential.cred_def_id, + schemaId: this.credential.schema_id, + credentialId: this.credentialId, + credentialRevocationId: this.credentialRevocationId, + revocationRegistryId: this.credential.rev_reg_id, + linkSecretId: this.linkSecretId, + methodName: this.methodName, + } + + for (const [key, value] of Object.entries(this.credential.values)) { + tags[`attr::${key}::value`] = value.raw + tags[`attr::${key}::marker`] = true + } + + return tags + } +} diff --git a/packages/anoncreds/src/repository/AnonCredsCredentialRepository.ts b/packages/anoncreds/src/repository/AnonCredsCredentialRepository.ts new file mode 100644 index 0000000000..fb02878439 --- /dev/null +++ b/packages/anoncreds/src/repository/AnonCredsCredentialRepository.ts @@ -0,0 +1,31 @@ +import type { AgentContext } from '@aries-framework/core' + +import { Repository, InjectionSymbols, StorageService, EventEmitter, injectable, inject } from '@aries-framework/core' + +import { AnonCredsCredentialRecord } from './AnonCredsCredentialRecord' + +@injectable() +export class AnonCredsCredentialRepository extends Repository { + public constructor( + @inject(InjectionSymbols.StorageService) storageService: StorageService, + eventEmitter: EventEmitter + ) { + super(AnonCredsCredentialRecord, storageService, eventEmitter) + } + + public async getByCredentialDefinitionId(agentContext: AgentContext, credentialDefinitionId: string) { + return this.getSingleByQuery(agentContext, { credentialDefinitionId }) + } + + public async findByCredentialDefinitionId(agentContext: AgentContext, credentialDefinitionId: string) { + return this.findSingleByQuery(agentContext, { credentialDefinitionId }) + } + + public async getByCredentialId(agentContext: AgentContext, credentialId: string) { + return this.getSingleByQuery(agentContext, { credentialId }) + } + + public async findByCredentialId(agentContext: AgentContext, credentialId: string) { + return this.findSingleByQuery(agentContext, { credentialId }) + } +} diff --git a/packages/anoncreds/src/repository/AnonCredsKeyCorrectnessProofRecord.ts b/packages/anoncreds/src/repository/AnonCredsKeyCorrectnessProofRecord.ts new file mode 100644 index 0000000000..cac331bd6c --- /dev/null +++ b/packages/anoncreds/src/repository/AnonCredsKeyCorrectnessProofRecord.ts @@ -0,0 +1,41 @@ +import type { TagsBase } from '@aries-framework/core' + +import { BaseRecord, utils } from '@aries-framework/core' + +export interface AnonCredsKeyCorrectnessProofRecordProps { + id?: string + credentialDefinitionId: string + value: Record +} + +export type DefaultAnonCredsKeyCorrectnessProofPrivateTags = { + credentialDefinitionId: string +} + +export class AnonCredsKeyCorrectnessProofRecord extends BaseRecord< + DefaultAnonCredsKeyCorrectnessProofPrivateTags, + TagsBase +> { + public static readonly type = 'AnonCredsKeyCorrectnessProofRecord' + public readonly type = AnonCredsKeyCorrectnessProofRecord.type + + public readonly credentialDefinitionId!: string + public readonly value!: Record // TODO: Define structure + + public constructor(props: AnonCredsKeyCorrectnessProofRecordProps) { + super() + + if (props) { + this.id = props.id ?? utils.uuid() + this.credentialDefinitionId = props.credentialDefinitionId + this.value = props.value + } + } + + public getTags() { + return { + ...this._tags, + credentialDefinitionId: this.credentialDefinitionId, + } + } +} diff --git a/packages/anoncreds/src/repository/AnonCredsKeyCorrectnessProofRepository.ts b/packages/anoncreds/src/repository/AnonCredsKeyCorrectnessProofRepository.ts new file mode 100644 index 0000000000..959ba8b4a5 --- /dev/null +++ b/packages/anoncreds/src/repository/AnonCredsKeyCorrectnessProofRepository.ts @@ -0,0 +1,23 @@ +import type { AgentContext } from '@aries-framework/core' + +import { Repository, InjectionSymbols, StorageService, EventEmitter, injectable, inject } from '@aries-framework/core' + +import { AnonCredsKeyCorrectnessProofRecord } from './AnonCredsKeyCorrectnessProofRecord' + +@injectable() +export class AnonCredsKeyCorrectnessProofRepository extends Repository { + public constructor( + @inject(InjectionSymbols.StorageService) storageService: StorageService, + eventEmitter: EventEmitter + ) { + super(AnonCredsKeyCorrectnessProofRecord, storageService, eventEmitter) + } + + public async getByCredentialDefinitionId(agentContext: AgentContext, credentialDefinitionId: string) { + return this.getSingleByQuery(agentContext, { credentialDefinitionId }) + } + + public async findByCredentialDefinitionId(agentContext: AgentContext, credentialDefinitionId: string) { + return this.findSingleByQuery(agentContext, { credentialDefinitionId }) + } +} diff --git a/packages/anoncreds/src/repository/AnonCredsLinkSecretRecord.ts b/packages/anoncreds/src/repository/AnonCredsLinkSecretRecord.ts new file mode 100644 index 0000000000..ffb775526e --- /dev/null +++ b/packages/anoncreds/src/repository/AnonCredsLinkSecretRecord.ts @@ -0,0 +1,42 @@ +import type { TagsBase } from '@aries-framework/core' + +import { BaseRecord, utils } from '@aries-framework/core' + +export interface AnonCredsLinkSecretRecordProps { + id?: string + linkSecretId: string + value?: string // If value is not provided, only reference to link secret is stored in regular storage +} + +export type DefaultAnonCredsLinkSecretTags = { + linkSecretId: string +} + +export type CustomAnonCredsLinkSecretTags = TagsBase & { + isDefault?: boolean +} + +export class AnonCredsLinkSecretRecord extends BaseRecord { + public static readonly type = 'AnonCredsLinkSecretRecord' + public readonly type = AnonCredsLinkSecretRecord.type + + public readonly linkSecretId!: string + public readonly value?: string + + public constructor(props: AnonCredsLinkSecretRecordProps) { + super() + + if (props) { + this.id = props.id ?? utils.uuid() + this.linkSecretId = props.linkSecretId + this.value = props.value + } + } + + public getTags() { + return { + ...this._tags, + linkSecretId: this.linkSecretId, + } + } +} diff --git a/packages/anoncreds/src/repository/AnonCredsLinkSecretRepository.ts b/packages/anoncreds/src/repository/AnonCredsLinkSecretRepository.ts new file mode 100644 index 0000000000..a4b69b08db --- /dev/null +++ b/packages/anoncreds/src/repository/AnonCredsLinkSecretRepository.ts @@ -0,0 +1,31 @@ +import type { AgentContext } from '@aries-framework/core' + +import { Repository, InjectionSymbols, StorageService, EventEmitter, injectable, inject } from '@aries-framework/core' + +import { AnonCredsLinkSecretRecord } from './AnonCredsLinkSecretRecord' + +@injectable() +export class AnonCredsLinkSecretRepository extends Repository { + public constructor( + @inject(InjectionSymbols.StorageService) storageService: StorageService, + eventEmitter: EventEmitter + ) { + super(AnonCredsLinkSecretRecord, storageService, eventEmitter) + } + + public async getDefault(agentContext: AgentContext) { + return this.getSingleByQuery(agentContext, { isDefault: true }) + } + + public async findDefault(agentContext: AgentContext) { + return this.findSingleByQuery(agentContext, { isDefault: true }) + } + + public async getByLinkSecretId(agentContext: AgentContext, linkSecretId: string) { + return this.getSingleByQuery(agentContext, { linkSecretId }) + } + + public async findByLinkSecretId(agentContext: AgentContext, linkSecretId: string) { + return this.findSingleByQuery(agentContext, { linkSecretId }) + } +} diff --git a/packages/anoncreds/src/repository/AnonCredsSchemaRecord.ts b/packages/anoncreds/src/repository/AnonCredsSchemaRecord.ts new file mode 100644 index 0000000000..ffdbd15189 --- /dev/null +++ b/packages/anoncreds/src/repository/AnonCredsSchemaRecord.ts @@ -0,0 +1,74 @@ +import type { AnonCredsSchemaRecordMetadata } from './anonCredsSchemaRecordMetadataTypes' +import type { AnonCredsSchema } from '../models' +import type { TagsBase } from '@aries-framework/core' + +import { BaseRecord, utils } from '@aries-framework/core' + +import { getUnqualifiedSchemaId, isDidIndySchemaId, parseIndySchemaId } from '../utils/indyIdentifiers' + +export interface AnonCredsSchemaRecordProps { + id?: string + schemaId: string + schema: AnonCredsSchema + methodName: string +} + +export type DefaultAnonCredsSchemaTags = { + schemaId: string + issuerId: string + schemaName: string + schemaVersion: string + methodName: string + + // Stores the unqualified variant of the schema id, which allows issuing credentials using the legacy + // schema id, even though the schema id is stored in the wallet as a qualified id. + // This is only added when the schema id is an did:indy identifier. + unqualifiedSchemaId?: string +} + +export class AnonCredsSchemaRecord extends BaseRecord< + DefaultAnonCredsSchemaTags, + TagsBase, + AnonCredsSchemaRecordMetadata +> { + public static readonly type = 'AnonCredsSchemaRecord' + public readonly type = AnonCredsSchemaRecord.type + + public schemaId!: string + public schema!: AnonCredsSchema + + /** + * AnonCreds method name. We don't use names explicitly from the registry (there's no identifier for a registry) + * @see https://hyperledger.github.io/anoncreds-methods-registry/ + */ + public methodName!: string + + public constructor(props: AnonCredsSchemaRecordProps) { + super() + + if (props) { + this.id = props.id ?? utils.uuid() + this.schema = props.schema + this.schemaId = props.schemaId + this.methodName = props.methodName + } + } + + public getTags() { + let unqualifiedSchemaId: string | undefined = undefined + if (isDidIndySchemaId(this.schemaId)) { + const { namespaceIdentifier, schemaName, schemaVersion } = parseIndySchemaId(this.schemaId) + unqualifiedSchemaId = getUnqualifiedSchemaId(namespaceIdentifier, schemaName, schemaVersion) + } + + return { + ...this._tags, + schemaId: this.schemaId, + issuerId: this.schema.issuerId, + schemaName: this.schema.name, + schemaVersion: this.schema.version, + methodName: this.methodName, + unqualifiedSchemaId, + } + } +} diff --git a/packages/core/src/modules/indy/repository/AnonCredsSchemaRepository.ts b/packages/anoncreds/src/repository/AnonCredsSchemaRepository.ts similarity index 50% rename from packages/core/src/modules/indy/repository/AnonCredsSchemaRepository.ts rename to packages/anoncreds/src/repository/AnonCredsSchemaRepository.ts index 311931f1f6..231878d2ea 100644 --- a/packages/core/src/modules/indy/repository/AnonCredsSchemaRepository.ts +++ b/packages/anoncreds/src/repository/AnonCredsSchemaRepository.ts @@ -1,10 +1,6 @@ -import type { AgentContext } from '../../../agent/context/AgentContext' +import type { AgentContext } from '@aries-framework/core' -import { EventEmitter } from '../../../agent/EventEmitter' -import { InjectionSymbols } from '../../../constants' -import { injectable, inject } from '../../../plugins' -import { Repository } from '../../../storage/Repository' -import { StorageService } from '../../../storage/StorageService' +import { Repository, InjectionSymbols, StorageService, EventEmitter, inject, injectable } from '@aries-framework/core' import { AnonCredsSchemaRecord } from './AnonCredsSchemaRecord' @@ -18,10 +14,28 @@ export class AnonCredsSchemaRepository extends Repository } public async getBySchemaId(agentContext: AgentContext, schemaId: string) { - return this.getSingleByQuery(agentContext, { schemaId: schemaId }) + return this.getSingleByQuery(agentContext, { + $or: [ + { + schemaId, + }, + { + unqualifiedSchemaId: schemaId, + }, + ], + }) } public async findBySchemaId(agentContext: AgentContext, schemaId: string) { - return await this.findSingleByQuery(agentContext, { schemaId: schemaId }) + return await this.findSingleByQuery(agentContext, { + $or: [ + { + schemaId, + }, + { + unqualifiedSchemaId: schemaId, + }, + ], + }) } } diff --git a/packages/anoncreds/src/repository/__tests__/AnonCredsCredentialRecord.test.ts b/packages/anoncreds/src/repository/__tests__/AnonCredsCredentialRecord.test.ts new file mode 100644 index 0000000000..111ce0a291 --- /dev/null +++ b/packages/anoncreds/src/repository/__tests__/AnonCredsCredentialRecord.test.ts @@ -0,0 +1,45 @@ +import type { AnonCredsCredential } from '@aries-framework/anoncreds' + +import { AnonCredsCredentialRecord } from '../AnonCredsCredentialRecord' + +describe('AnoncredsCredentialRecords', () => { + test('Returns the correct tags from the getTags methods based on the credential record values', () => { + const anoncredsCredentialRecords = new AnonCredsCredentialRecord({ + credential: { + cred_def_id: 'credDefId', + schema_id: 'schemaId', + signature: 'signature', + signature_correctness_proof: 'signatureCorrectnessProof', + values: { attr1: { raw: 'value1', encoded: 'encvalue1' }, attr2: { raw: 'value2', encoded: 'encvalue2' } }, + rev_reg_id: 'revRegId', + } as AnonCredsCredential, + credentialId: 'myCredentialId', + credentialRevocationId: 'credentialRevocationId', + linkSecretId: 'linkSecretId', + issuerId: 'issuerDid', + schemaIssuerId: 'schemaIssuerDid', + schemaName: 'schemaName', + schemaVersion: 'schemaVersion', + methodName: 'methodName', + }) + + const tags = anoncredsCredentialRecords.getTags() + + expect(tags).toMatchObject({ + issuerId: 'issuerDid', + schemaIssuerId: 'schemaIssuerDid', + schemaName: 'schemaName', + schemaVersion: 'schemaVersion', + credentialDefinitionId: 'credDefId', + schemaId: 'schemaId', + credentialId: 'myCredentialId', + credentialRevocationId: 'credentialRevocationId', + linkSecretId: 'linkSecretId', + 'attr::attr1::value': 'value1', + 'attr::attr1::marker': true, + 'attr::attr2::value': 'value2', + 'attr::attr2::marker': true, + methodName: 'methodName', + }) + }) +}) diff --git a/packages/anoncreds/src/repository/anonCredsCredentialDefinitionRecordMetadataTypes.ts b/packages/anoncreds/src/repository/anonCredsCredentialDefinitionRecordMetadataTypes.ts new file mode 100644 index 0000000000..05806802e4 --- /dev/null +++ b/packages/anoncreds/src/repository/anonCredsCredentialDefinitionRecordMetadataTypes.ts @@ -0,0 +1,11 @@ +import type { Extensible } from '../services/registry/base' + +export enum AnonCredsCredentialDefinitionRecordMetadataKeys { + CredentialDefinitionRegistrationMetadata = '_internal/anonCredsCredentialDefinitionRegistrationMetadata', + CredentialDefinitionMetadata = '_internal/anonCredsCredentialDefinitionMetadata', +} + +export type AnonCredsCredentialDefinitionRecordMetadata = { + [AnonCredsCredentialDefinitionRecordMetadataKeys.CredentialDefinitionRegistrationMetadata]: Extensible + [AnonCredsCredentialDefinitionRecordMetadataKeys.CredentialDefinitionMetadata]: Extensible +} diff --git a/packages/anoncreds/src/repository/anonCredsSchemaRecordMetadataTypes.ts b/packages/anoncreds/src/repository/anonCredsSchemaRecordMetadataTypes.ts new file mode 100644 index 0000000000..9880a50625 --- /dev/null +++ b/packages/anoncreds/src/repository/anonCredsSchemaRecordMetadataTypes.ts @@ -0,0 +1,11 @@ +import type { Extensible } from '../services/registry/base' + +export enum AnonCredsSchemaRecordMetadataKeys { + SchemaRegistrationMetadata = '_internal/anonCredsSchemaRegistrationMetadata', + SchemaMetadata = '_internal/anonCredsSchemaMetadata', +} + +export type AnonCredsSchemaRecordMetadata = { + [AnonCredsSchemaRecordMetadataKeys.SchemaRegistrationMetadata]: Extensible + [AnonCredsSchemaRecordMetadataKeys.SchemaMetadata]: Extensible +} diff --git a/packages/anoncreds/src/repository/index.ts b/packages/anoncreds/src/repository/index.ts new file mode 100644 index 0000000000..c4fb3bbe80 --- /dev/null +++ b/packages/anoncreds/src/repository/index.ts @@ -0,0 +1,12 @@ +export * from './AnonCredsCredentialRecord' +export * from './AnonCredsCredentialRepository' +export * from './AnonCredsCredentialDefinitionRecord' +export * from './AnonCredsCredentialDefinitionRepository' +export * from './AnonCredsCredentialDefinitionPrivateRecord' +export * from './AnonCredsCredentialDefinitionPrivateRepository' +export * from './AnonCredsKeyCorrectnessProofRecord' +export * from './AnonCredsKeyCorrectnessProofRepository' +export * from './AnonCredsLinkSecretRecord' +export * from './AnonCredsLinkSecretRepository' +export * from './AnonCredsSchemaRecord' +export * from './AnonCredsSchemaRepository' diff --git a/packages/anoncreds/src/services/AnonCredsHolderService.ts b/packages/anoncreds/src/services/AnonCredsHolderService.ts new file mode 100644 index 0000000000..47cefac8e3 --- /dev/null +++ b/packages/anoncreds/src/services/AnonCredsHolderService.ts @@ -0,0 +1,45 @@ +import type { + CreateCredentialRequestOptions, + CreateCredentialRequestReturn, + CreateProofOptions, + GetCredentialOptions, + StoreCredentialOptions, + GetCredentialsForProofRequestOptions, + GetCredentialsForProofRequestReturn, + CreateLinkSecretReturn, + CreateLinkSecretOptions, + GetCredentialsOptions, +} from './AnonCredsHolderServiceOptions' +import type { AnonCredsCredentialInfo } from '../models' +import type { AnonCredsProof } from '../models/exchange' +import type { AgentContext } from '@aries-framework/core' + +export const AnonCredsHolderServiceSymbol = Symbol('AnonCredsHolderService') + +export interface AnonCredsHolderService { + createLinkSecret(agentContext: AgentContext, options: CreateLinkSecretOptions): Promise + + createProof(agentContext: AgentContext, options: CreateProofOptions): Promise + storeCredential( + agentContext: AgentContext, + options: StoreCredentialOptions, + metadata?: Record + ): Promise + + // TODO: this doesn't actually return the credential, as the indy-sdk doesn't support that + // We could come up with a hack (as we've received the credential at one point), but for + // now I think it's not that much of an issue + getCredential(agentContext: AgentContext, options: GetCredentialOptions): Promise + getCredentials(agentContext: AgentContext, options: GetCredentialsOptions): Promise + + createCredentialRequest( + agentContext: AgentContext, + options: CreateCredentialRequestOptions + ): Promise + + deleteCredential(agentContext: AgentContext, credentialId: string): Promise + getCredentialsForProofRequest( + agentContext: AgentContext, + options: GetCredentialsForProofRequestOptions + ): Promise +} diff --git a/packages/anoncreds/src/services/AnonCredsHolderServiceOptions.ts b/packages/anoncreds/src/services/AnonCredsHolderServiceOptions.ts new file mode 100644 index 0000000000..a657279715 --- /dev/null +++ b/packages/anoncreds/src/services/AnonCredsHolderServiceOptions.ts @@ -0,0 +1,111 @@ +import type { + AnonCredsCredentialInfo, + AnonCredsCredentialRequestMetadata, + AnonCredsSelectedCredentials, +} from '../models' +import type { + AnonCredsCredential, + AnonCredsCredentialOffer, + AnonCredsCredentialRequest, + AnonCredsProofRequest, + AnonCredsNonRevokedInterval, +} from '../models/exchange' +import type { + AnonCredsCredentialDefinition, + AnonCredsRevocationStatusList, + AnonCredsRevocationRegistryDefinition, + AnonCredsSchema, +} from '../models/registry' + +export interface AnonCredsAttributeInfo { + name?: string + names?: string[] +} + +export interface CreateProofOptions { + proofRequest: AnonCredsProofRequest + selectedCredentials: AnonCredsSelectedCredentials + schemas: { + [schemaId: string]: AnonCredsSchema + } + credentialDefinitions: { + [credentialDefinitionId: string]: AnonCredsCredentialDefinition + } + revocationRegistries: { + [revocationRegistryDefinitionId: string]: { + // tails file MUST already be downloaded on a higher level and stored + tailsFilePath: string + definition: AnonCredsRevocationRegistryDefinition + revocationStatusLists: { + [timestamp: number]: AnonCredsRevocationStatusList + } + } + } +} + +export interface StoreCredentialOptions { + credentialRequestMetadata: AnonCredsCredentialRequestMetadata + credential: AnonCredsCredential + credentialDefinition: AnonCredsCredentialDefinition + schema: AnonCredsSchema + credentialDefinitionId: string + credentialId?: string + revocationRegistry?: { + id: string + definition: AnonCredsRevocationRegistryDefinition + } +} + +export interface GetCredentialOptions { + credentialId: string +} + +export interface GetCredentialsOptions { + credentialDefinitionId?: string + schemaId?: string + schemaIssuerId?: string + schemaName?: string + schemaVersion?: string + issuerId?: string + methodName?: string +} + +// TODO: Maybe we can make this a bit more specific? +export type WalletQuery = Record +export interface ReferentWalletQuery { + [referent: string]: WalletQuery +} + +export interface GetCredentialsForProofRequestOptions { + proofRequest: AnonCredsProofRequest + attributeReferent: string + start?: number + limit?: number + extraQuery?: ReferentWalletQuery +} + +export type GetCredentialsForProofRequestReturn = Array<{ + credentialInfo: AnonCredsCredentialInfo + interval?: AnonCredsNonRevokedInterval +}> + +export interface CreateCredentialRequestOptions { + credentialOffer: AnonCredsCredentialOffer + credentialDefinition: AnonCredsCredentialDefinition + linkSecretId?: string + useLegacyProverDid?: boolean +} + +export interface CreateCredentialRequestReturn { + credentialRequest: AnonCredsCredentialRequest + credentialRequestMetadata: AnonCredsCredentialRequestMetadata +} + +export interface CreateLinkSecretOptions { + linkSecretId?: string +} + +export interface CreateLinkSecretReturn { + linkSecretId: string + linkSecretValue?: string +} diff --git a/packages/anoncreds/src/services/AnonCredsIssuerService.ts b/packages/anoncreds/src/services/AnonCredsIssuerService.ts new file mode 100644 index 0000000000..3090b1759b --- /dev/null +++ b/packages/anoncreds/src/services/AnonCredsIssuerService.ts @@ -0,0 +1,32 @@ +import type { + CreateSchemaOptions, + CreateCredentialDefinitionOptions, + CreateCredentialOfferOptions, + CreateCredentialReturn, + CreateCredentialOptions, + CreateCredentialDefinitionReturn, +} from './AnonCredsIssuerServiceOptions' +import type { AnonCredsCredentialOffer } from '../models/exchange' +import type { AnonCredsSchema } from '../models/registry' +import type { AgentContext } from '@aries-framework/core' + +export const AnonCredsIssuerServiceSymbol = Symbol('AnonCredsIssuerService') + +export interface AnonCredsIssuerService { + createSchema(agentContext: AgentContext, options: CreateSchemaOptions): Promise + + // This should store the private part of the credential definition as in the indy-sdk + // we don't have access to the private part of the credential definition + createCredentialDefinition( + agentContext: AgentContext, + options: CreateCredentialDefinitionOptions, + metadata?: Record + ): Promise + + createCredentialOffer( + agentContext: AgentContext, + options: CreateCredentialOfferOptions + ): Promise + + createCredential(agentContext: AgentContext, options: CreateCredentialOptions): Promise +} diff --git a/packages/anoncreds/src/services/AnonCredsIssuerServiceOptions.ts b/packages/anoncreds/src/services/AnonCredsIssuerServiceOptions.ts new file mode 100644 index 0000000000..c7da246b9b --- /dev/null +++ b/packages/anoncreds/src/services/AnonCredsIssuerServiceOptions.ts @@ -0,0 +1,47 @@ +import type { + AnonCredsCredential, + AnonCredsCredentialOffer, + AnonCredsCredentialRequest, + AnonCredsCredentialValues, +} from '../models/exchange' +import type { AnonCredsCredentialDefinition, AnonCredsSchema } from '../models/registry' + +export interface CreateSchemaOptions { + issuerId: string + name: string + version: string + attrNames: string[] +} + +export interface CreateCredentialDefinitionOptions { + issuerId: string + tag: string + supportRevocation?: boolean + + schemaId: string + schema: AnonCredsSchema +} + +export interface CreateCredentialOfferOptions { + credentialDefinitionId: string +} + +export interface CreateCredentialOptions { + credentialOffer: AnonCredsCredentialOffer + credentialRequest: AnonCredsCredentialRequest + credentialValues: AnonCredsCredentialValues + revocationRegistryId?: string + // TODO: should this just be the tails file instead of a path? + tailsFilePath?: string +} + +export interface CreateCredentialReturn { + credential: AnonCredsCredential + credentialRevocationId?: string +} + +export interface CreateCredentialDefinitionReturn { + credentialDefinition: AnonCredsCredentialDefinition + credentialDefinitionPrivate?: Record + keyCorrectnessProof?: Record +} diff --git a/packages/anoncreds/src/services/AnonCredsVerifierService.ts b/packages/anoncreds/src/services/AnonCredsVerifierService.ts new file mode 100644 index 0000000000..f0ffdf1e91 --- /dev/null +++ b/packages/anoncreds/src/services/AnonCredsVerifierService.ts @@ -0,0 +1,10 @@ +import type { VerifyProofOptions } from './AnonCredsVerifierServiceOptions' +import type { AgentContext } from '@aries-framework/core' + +export const AnonCredsVerifierServiceSymbol = Symbol('AnonCredsVerifierService') + +export interface AnonCredsVerifierService { + // TODO: do we want to extend the return type with more info besides a boolean. + // If the value is false it would be nice to have some extra contexts about why it failed + verifyProof(agentContext: AgentContext, options: VerifyProofOptions): Promise +} diff --git a/packages/anoncreds/src/services/AnonCredsVerifierServiceOptions.ts b/packages/anoncreds/src/services/AnonCredsVerifierServiceOptions.ts new file mode 100644 index 0000000000..1bdd959f15 --- /dev/null +++ b/packages/anoncreds/src/services/AnonCredsVerifierServiceOptions.ts @@ -0,0 +1,31 @@ +import type { AnonCredsProof, AnonCredsProofRequest } from '../models/exchange' +import type { + AnonCredsCredentialDefinition, + AnonCredsRevocationStatusList, + AnonCredsRevocationRegistryDefinition, + AnonCredsSchema, +} from '../models/registry' + +export interface VerifyProofOptions { + proofRequest: AnonCredsProofRequest + proof: AnonCredsProof + schemas: { + [schemaId: string]: AnonCredsSchema + } + credentialDefinitions: { + [credentialDefinitionId: string]: AnonCredsCredentialDefinition + } + revocationRegistries: { + [revocationRegistryDefinitionId: string]: { + definition: AnonCredsRevocationRegistryDefinition + // NOTE: the verifier only needs the accumulator, not the whole state of the revocation registry + // Requiring this to be the full state means we need to retrieve the full state from the ledger + // as a verifier. This is just following the data models from the AnonCreds spec, but for e.g. indy + // this means we need to retrieve _ALL_ deltas from the ledger to verify a proof. While currently we + // only need to fetch the registry. + revocationStatusLists: { + [timestamp: number]: AnonCredsRevocationStatusList + } + } + } +} diff --git a/packages/anoncreds/src/services/index.ts b/packages/anoncreds/src/services/index.ts new file mode 100644 index 0000000000..fe7b176754 --- /dev/null +++ b/packages/anoncreds/src/services/index.ts @@ -0,0 +1,7 @@ +export * from './AnonCredsHolderService' +export * from './AnonCredsHolderServiceOptions' +export * from './AnonCredsIssuerService' +export * from './AnonCredsIssuerServiceOptions' +export * from './registry' +export * from './AnonCredsVerifierService' +export * from './AnonCredsVerifierServiceOptions' diff --git a/packages/anoncreds/src/services/registry/AnonCredsRegistry.ts b/packages/anoncreds/src/services/registry/AnonCredsRegistry.ts new file mode 100644 index 0000000000..85bf72ba2b --- /dev/null +++ b/packages/anoncreds/src/services/registry/AnonCredsRegistry.ts @@ -0,0 +1,58 @@ +import type { + GetCredentialDefinitionReturn, + RegisterCredentialDefinitionOptions, + RegisterCredentialDefinitionReturn, +} from './CredentialDefinitionOptions' +import type { GetRevocationRegistryDefinitionReturn } from './RevocationRegistryDefinitionOptions' +import type { GetRevocationStatusListReturn } from './RevocationStatusListOptions' +import type { GetSchemaReturn, RegisterSchemaOptions, RegisterSchemaReturn } from './SchemaOptions' +import type { AgentContext } from '@aries-framework/core' + +/** + * @public + */ +export interface AnonCredsRegistry { + /** + * A name to identify the registry. This will be stored as part of the reigstered anoncreds objects to allow querying + * for created objects using a specific registry. Multilpe implementations can use the same name, but they should in that + * case also reference objects on the same networks. + */ + methodName: string + + supportedIdentifier: RegExp + + getSchema(agentContext: AgentContext, schemaId: string): Promise + registerSchema(agentContext: AgentContext, options: RegisterSchemaOptions): Promise + + getCredentialDefinition( + agentContext: AgentContext, + credentialDefinitionId: string + ): Promise + registerCredentialDefinition( + agentContext: AgentContext, + options: RegisterCredentialDefinitionOptions + ): Promise + + getRevocationRegistryDefinition( + agentContext: AgentContext, + revocationRegistryDefinitionId: string + ): Promise + + // TODO: issuance of revocable credentials + // registerRevocationRegistryDefinition( + // agentContext: AgentContext, + // options: RegisterRevocationRegistryDefinitionOptions + // ): Promise + + getRevocationStatusList( + agentContext: AgentContext, + revocationRegistryId: string, + timestamp: number + ): Promise + + // TODO: issuance of revocable credentials + // registerRevocationList( + // agentContext: AgentContext, + // options: RegisterRevocationListOptions + // ): Promise +} diff --git a/packages/anoncreds/src/services/registry/AnonCredsRegistryService.ts b/packages/anoncreds/src/services/registry/AnonCredsRegistryService.ts new file mode 100644 index 0000000000..23c393bb38 --- /dev/null +++ b/packages/anoncreds/src/services/registry/AnonCredsRegistryService.ts @@ -0,0 +1,28 @@ +import type { AnonCredsRegistry } from '.' +import type { AgentContext } from '@aries-framework/core' + +import { injectable } from '@aries-framework/core' + +import { AnonCredsModuleConfig } from '../../AnonCredsModuleConfig' +import { AnonCredsError } from '../../error' + +/** + * @internal + * The AnonCreds registry service manages multiple {@link AnonCredsRegistry} instances + * and returns the correct registry based on a given identifier + */ +@injectable() +export class AnonCredsRegistryService { + public getRegistryForIdentifier(agentContext: AgentContext, identifier: string): AnonCredsRegistry { + const registries = agentContext.dependencyManager.resolve(AnonCredsModuleConfig).registries + + // TODO: should we check if multiple are registered? + const registry = registries.find((registry) => registry.supportedIdentifier.test(identifier)) + + if (!registry) { + throw new AnonCredsError(`No AnonCredsRegistry registered for identifier '${identifier}'`) + } + + return registry + } +} diff --git a/packages/anoncreds/src/services/registry/CredentialDefinitionOptions.ts b/packages/anoncreds/src/services/registry/CredentialDefinitionOptions.ts new file mode 100644 index 0000000000..6e35c6ca76 --- /dev/null +++ b/packages/anoncreds/src/services/registry/CredentialDefinitionOptions.ts @@ -0,0 +1,45 @@ +import type { + AnonCredsResolutionMetadata, + Extensible, + AnonCredsOperationStateFailed, + AnonCredsOperationStateFinished, + AnonCredsOperationState, +} from './base' +import type { AnonCredsCredentialDefinition } from '../../models/registry' + +export interface GetCredentialDefinitionReturn { + credentialDefinition?: AnonCredsCredentialDefinition + credentialDefinitionId: string + resolutionMetadata: AnonCredsResolutionMetadata + credentialDefinitionMetadata: Extensible +} + +export interface RegisterCredentialDefinitionOptions { + credentialDefinition: AnonCredsCredentialDefinition + options: Extensible +} + +export interface RegisterCredentialDefinitionReturnStateFailed extends AnonCredsOperationStateFailed { + credentialDefinition?: AnonCredsCredentialDefinition + credentialDefinitionId?: string +} + +export interface RegisterCredentialDefinitionReturnStateFinished extends AnonCredsOperationStateFinished { + credentialDefinition: AnonCredsCredentialDefinition + credentialDefinitionId: string +} + +export interface RegisterCredentialDefinitionReturnState extends AnonCredsOperationState { + credentialDefinition?: AnonCredsCredentialDefinition + credentialDefinitionId?: string +} + +export interface RegisterCredentialDefinitionReturn { + jobId?: string + credentialDefinitionState: + | RegisterCredentialDefinitionReturnState + | RegisterCredentialDefinitionReturnStateFinished + | RegisterCredentialDefinitionReturnStateFailed + credentialDefinitionMetadata: Extensible + registrationMetadata: Extensible +} diff --git a/packages/anoncreds/src/services/registry/RevocationRegistryDefinitionOptions.ts b/packages/anoncreds/src/services/registry/RevocationRegistryDefinitionOptions.ts new file mode 100644 index 0000000000..6e9d1349fe --- /dev/null +++ b/packages/anoncreds/src/services/registry/RevocationRegistryDefinitionOptions.ts @@ -0,0 +1,18 @@ +import type { AnonCredsResolutionMetadata, Extensible } from './base' +import type { AnonCredsRevocationRegistryDefinition } from '../../models/registry' + +export interface GetRevocationRegistryDefinitionReturn { + revocationRegistryDefinition?: AnonCredsRevocationRegistryDefinition + revocationRegistryDefinitionId: string + resolutionMetadata: AnonCredsResolutionMetadata + revocationRegistryDefinitionMetadata: Extensible +} + +// TODO: Support for issuance of revocable credentials +// export interface RegisterRevocationRegistryDefinitionOptions { +// revocationRegistryDefinition: AnonCredsRevocationRegistryDefinition +// } + +// export interface RegisterRevocationRegistryDefinitionReturn { +// revocationRegistryDefinitionId: string +// } diff --git a/packages/anoncreds/src/services/registry/RevocationStatusListOptions.ts b/packages/anoncreds/src/services/registry/RevocationStatusListOptions.ts new file mode 100644 index 0000000000..6396fe6df0 --- /dev/null +++ b/packages/anoncreds/src/services/registry/RevocationStatusListOptions.ts @@ -0,0 +1,18 @@ +import type { AnonCredsResolutionMetadata, Extensible } from './base' +import type { AnonCredsRevocationStatusList } from '../../models/registry' + +export interface GetRevocationStatusListReturn { + revocationStatusList?: AnonCredsRevocationStatusList + resolutionMetadata: AnonCredsResolutionMetadata + revocationStatusListMetadata: Extensible +} + +// TODO: Support for issuance of revocable credentials +// export interface RegisterRevocationListOptions { +// // Timestamp is often calculated by the ledger, otherwise method should just take current time +// // Return type does include the timestamp. +// revocationList: Omit +// } +// export interface RegisterRevocationListReturn { +// timestamp: string +// } diff --git a/packages/anoncreds/src/services/registry/SchemaOptions.ts b/packages/anoncreds/src/services/registry/SchemaOptions.ts new file mode 100644 index 0000000000..9ff42c9bc4 --- /dev/null +++ b/packages/anoncreds/src/services/registry/SchemaOptions.ts @@ -0,0 +1,46 @@ +import type { + AnonCredsResolutionMetadata, + Extensible, + AnonCredsOperationStateFailed, + AnonCredsOperationStateFinished, + AnonCredsOperationState, +} from './base' +import type { AnonCredsSchema } from '../../models/registry' + +// Get Schema +export interface GetSchemaReturn { + schema?: AnonCredsSchema + schemaId: string + // Can contain e.g. the ledger transaction request/response + resolutionMetadata: AnonCredsResolutionMetadata + // Can contain additional fields + schemaMetadata: Extensible +} + +// +export interface RegisterSchemaOptions { + schema: AnonCredsSchema + options: Extensible +} + +export interface RegisterSchemaReturnStateFailed extends AnonCredsOperationStateFailed { + schema?: AnonCredsSchema + schemaId?: string +} + +export interface RegisterSchemaReturnStateFinished extends AnonCredsOperationStateFinished { + schema: AnonCredsSchema + schemaId: string +} + +export interface RegisterSchemaReturnState extends AnonCredsOperationState { + schema?: AnonCredsSchema + schemaId?: string +} + +export interface RegisterSchemaReturn { + jobId?: string + schemaState: RegisterSchemaReturnState | RegisterSchemaReturnStateFinished | RegisterSchemaReturnStateFailed + schemaMetadata: Extensible + registrationMetadata: Extensible +} diff --git a/packages/anoncreds/src/services/registry/__tests__/AnonCredsRegistryService.test.ts b/packages/anoncreds/src/services/registry/__tests__/AnonCredsRegistryService.test.ts new file mode 100644 index 0000000000..2cb39bc2e5 --- /dev/null +++ b/packages/anoncreds/src/services/registry/__tests__/AnonCredsRegistryService.test.ts @@ -0,0 +1,38 @@ +import type { AnonCredsRegistry } from '../AnonCredsRegistry' + +import { getAgentContext } from '../../../../../core/tests/helpers' +import { AnonCredsModuleConfig } from '../../../AnonCredsModuleConfig' +import { AnonCredsError } from '../../../error' +import { AnonCredsRegistryService } from '../AnonCredsRegistryService' + +const registryOne = { + supportedIdentifier: /a/, +} as AnonCredsRegistry + +const registryTwo = { + supportedIdentifier: /b/, +} as AnonCredsRegistry + +const agentContext = getAgentContext({ + registerInstances: [ + [ + AnonCredsModuleConfig, + new AnonCredsModuleConfig({ + registries: [registryOne, registryTwo], + }), + ], + ], +}) + +const anonCredsRegistryService = new AnonCredsRegistryService() + +describe('AnonCredsRegistryService', () => { + test('returns the registry for an identifier based on the supportedMethods regex', async () => { + expect(anonCredsRegistryService.getRegistryForIdentifier(agentContext, 'a')).toEqual(registryOne) + expect(anonCredsRegistryService.getRegistryForIdentifier(agentContext, 'b')).toEqual(registryTwo) + }) + + test('throws AnonCredsError if no registry is found for the given identifier', async () => { + expect(() => anonCredsRegistryService.getRegistryForIdentifier(agentContext, 'c')).toThrow(AnonCredsError) + }) +}) diff --git a/packages/anoncreds/src/services/registry/base.ts b/packages/anoncreds/src/services/registry/base.ts new file mode 100644 index 0000000000..af9b52ee43 --- /dev/null +++ b/packages/anoncreds/src/services/registry/base.ts @@ -0,0 +1,19 @@ +export type Extensible = Record + +export interface AnonCredsOperationState { + state: 'action' | 'wait' +} + +export interface AnonCredsOperationStateFinished { + state: 'finished' +} + +export interface AnonCredsOperationStateFailed { + state: 'failed' + reason: string +} + +export interface AnonCredsResolutionMetadata extends Extensible { + error?: 'invalid' | 'notFound' | 'unsupportedAnonCredsMethod' | string + message?: string +} diff --git a/packages/anoncreds/src/services/registry/index.ts b/packages/anoncreds/src/services/registry/index.ts new file mode 100644 index 0000000000..6577018992 --- /dev/null +++ b/packages/anoncreds/src/services/registry/index.ts @@ -0,0 +1,7 @@ +export * from './AnonCredsRegistry' +export * from './CredentialDefinitionOptions' +export * from './SchemaOptions' +export * from './RevocationRegistryDefinitionOptions' +export * from './RevocationStatusListOptions' +export { AnonCredsResolutionMetadata } from './base' +export * from './AnonCredsRegistryService' diff --git a/packages/anoncreds/src/updates/0.3.1-0.4/__tests__/credentialDefinition.test.ts b/packages/anoncreds/src/updates/0.3.1-0.4/__tests__/credentialDefinition.test.ts new file mode 100644 index 0000000000..65b40bcddb --- /dev/null +++ b/packages/anoncreds/src/updates/0.3.1-0.4/__tests__/credentialDefinition.test.ts @@ -0,0 +1,154 @@ +import type { AnonCredsCredentialDefinition } from '../../../models' + +import { JsonTransformer } from '../../../../../core/src' +import { Agent } from '../../../../../core/src/agent/Agent' +import { getAgentConfig, getAgentContext, mockFunction } from '../../../../../core/tests' +import { InMemoryAnonCredsRegistry } from '../../../../tests/InMemoryAnonCredsRegistry' +import { AnonCredsCredentialDefinitionRecord } from '../../../repository' +import { AnonCredsCredentialDefinitionRepository } from '../../../repository/AnonCredsCredentialDefinitionRepository' +import * as testModule from '../credentialDefinition' + +const agentConfig = getAgentConfig('AnonCreds Migration - Credential Exchange Record - 0.3.1-0.4.0') +const agentContext = getAgentContext() + +jest.mock('../../../repository/AnonCredsCredentialDefinitionRepository') +const AnonCredsCredentialDefinitionRepositoryMock = + AnonCredsCredentialDefinitionRepository as jest.Mock +const credentialDefinitionRepository = new AnonCredsCredentialDefinitionRepositoryMock() + +const inMemoryAnonCredsRegistry = new InMemoryAnonCredsRegistry({ + existingCredentialDefinitions: { + 'did:indy:bcovrin:test:SDqTzbVuCowusqGBNbNDjH/anoncreds/v0/CLAIM_DEF/104/default': { + schemaId: 'did:indy:bcovrin:test:SDqTzbVuCowusqGBNbNDjH/anoncreds/v0/SCHEMA/credentialDefinition-name/1.0', + tag: 'default', + type: 'CL', + value: { + primary: { + master_secret: '119999 00192381', + }, + }, + issuerId: 'did:indy:bcovrin:test:SDqTzbVuCowusqGBNbNDjH', + }, + }, +}) + +const registryService = { + getRegistryForIdentifier: () => inMemoryAnonCredsRegistry, +} +jest.mock('../../../../../core/src/agent/Agent', () => { + return { + Agent: jest.fn(() => ({ + config: agentConfig, + context: agentContext, + dependencyManager: { + resolve: jest.fn((injectionSymbol) => + injectionSymbol === AnonCredsCredentialDefinitionRepository ? credentialDefinitionRepository : registryService + ), + }, + })), + } +}) + +// Mock typed object +const AgentMock = Agent as jest.Mock + +describe('0.3.1-0.4.0 | AnonCreds Migration | Credential Definition Record', () => { + let agent: Agent + + beforeEach(() => { + agent = new AgentMock() + }) + + afterEach(() => { + jest.clearAllMocks() + }) + + describe('migrateAnonCredsCredentialDefinitionRecordToV0_4()', () => { + it('should fetch all records and apply the needed updates', async () => { + const records: AnonCredsCredentialDefinitionRecord[] = [ + getCredentialDefinitionRecord({ + credentialDefinition: { + id: 'did:indy:bcovrin:test:SDqTzbVuCowusqGBNbNDjH/anoncreds/v0/CLAIM_DEF/104/default', + schemaId: '104', + tag: 'default', + type: 'CL', + value: { + primary: { + master_secret: '119999 00192381', + }, + }, + ver: '1.0', + }, + }), + ] + + mockFunction(credentialDefinitionRepository.getAll).mockResolvedValue(records) + + await testModule.migrateAnonCredsCredentialDefinitionRecordToV0_4(agent) + + expect(credentialDefinitionRepository.getAll).toHaveBeenCalledTimes(1) + expect(credentialDefinitionRepository.update).toHaveBeenCalledTimes(1) + + const [, credentialDefinitionRecord] = mockFunction(credentialDefinitionRepository.update).mock.calls[0] + expect(credentialDefinitionRecord.toJSON()).toMatchObject({ + credentialDefinitionId: 'did:indy:bcovrin:test:SDqTzbVuCowusqGBNbNDjH/anoncreds/v0/CLAIM_DEF/104/default', + credentialDefinition: { + schemaId: 'did:indy:bcovrin:test:SDqTzbVuCowusqGBNbNDjH/anoncreds/v0/SCHEMA/credentialDefinition-name/1.0', + tag: 'default', + type: 'CL', + value: { + primary: { + master_secret: '119999 00192381', + }, + }, + issuerId: 'did:indy:bcovrin:test:SDqTzbVuCowusqGBNbNDjH', + }, + }) + }) + + it('should skip records that are already migrated to the 0.4.0 format', async () => { + const records: AnonCredsCredentialDefinitionRecord[] = [ + getCredentialDefinitionRecord({ + credentialDefinitionId: 'did:indy:bcovrin:test:SDqTzbVuCowusqGBNbNDjH/anoncreds/v0/CLAIM_DEF/104/default', + credentialDefinition: { + schemaId: 'did:indy:bcovrin:test:SDqTzbVuCowusqGBNbNDjH/anoncreds/v0/SCHEMA/credentialDefinition-name/1.0', + tag: 'default', + type: 'CL', + value: { + primary: { + master_secret: '119999 00192381', + }, + }, + issuerId: 'did:indy:bcovrin:test:SDqTzbVuCowusqGBNbNDjH', + }, + }), + ] + + mockFunction(credentialDefinitionRepository.getAll).mockResolvedValue(records) + + await testModule.migrateAnonCredsCredentialDefinitionRecordToV0_4(agent) + + expect(credentialDefinitionRepository.getAll).toHaveBeenCalledTimes(1) + expect(credentialDefinitionRepository.update).toHaveBeenCalledTimes(0) + }) + }) +}) + +function getCredentialDefinitionRecord({ + id, + credentialDefinition, + credentialDefinitionId, +}: { + id?: string + credentialDefinition: testModule.OldCredentialDefinition | AnonCredsCredentialDefinition + credentialDefinitionId?: string +}) { + return JsonTransformer.fromJSON( + { + id: id ?? 'credentialDefinition-record-id', + credentialDefinition, + credentialDefinitionId, + }, + AnonCredsCredentialDefinitionRecord + ) +} diff --git a/packages/anoncreds/src/updates/0.3.1-0.4/__tests__/credentialExchangeRecord.test.ts b/packages/anoncreds/src/updates/0.3.1-0.4/__tests__/credentialExchangeRecord.test.ts new file mode 100644 index 0000000000..80e5a596c9 --- /dev/null +++ b/packages/anoncreds/src/updates/0.3.1-0.4/__tests__/credentialExchangeRecord.test.ts @@ -0,0 +1,160 @@ +import type { CredentialRecordBinding } from '../../../../../core/src' + +import { CredentialExchangeRecord, JsonTransformer } from '../../../../../core/src' +import { Agent } from '../../../../../core/src/agent/Agent' +import { CredentialRepository } from '../../../../../core/src/modules/credentials/repository/CredentialRepository' +import { getAgentConfig, getAgentContext, mockFunction } from '../../../../../core/tests' +import { + migrateIndyCredentialMetadataToAnonCredsMetadata, + migrateIndyCredentialTypeToAnonCredsCredential, +} from '../credentialExchangeRecord' +import * as testModule from '../credentialExchangeRecord' + +const agentConfig = getAgentConfig('AnonCreds Migration - Credential Exchange Record - 0.3.1-0.4.0') +const agentContext = getAgentContext() + +jest.mock('../../../../../core/src/modules/credentials/repository/CredentialRepository') +const CredentialRepositoryMock = CredentialRepository as jest.Mock +const credentialRepository = new CredentialRepositoryMock() + +jest.mock('../../../../../core/src/agent/Agent', () => { + return { + Agent: jest.fn(() => ({ + config: agentConfig, + context: agentContext, + dependencyManager: { + resolve: jest.fn(() => credentialRepository), + }, + })), + } +}) + +// Mock typed object +const AgentMock = Agent as jest.Mock + +describe('0.3.1-0.4.0 | AnonCreds Migration | Credential Exchange Record', () => { + let agent: Agent + + beforeEach(() => { + agent = new AgentMock() + }) + + describe('migrateCredentialExchangeRecordToV0_4()', () => { + it('should fetch all records and apply the needed updates ', async () => { + const records: CredentialExchangeRecord[] = [ + getCredentialRecord({ + metadata: { + '_internal/indyCredential': { some: 'value' }, + '_internal/indyRequest': { nonce: 'nonce', master_secret_name: 'ms', master_secret_blinding_data: 'msbd' }, + }, + credentials: [ + { + credentialRecordId: 'credential-id', + credentialRecordType: 'indy', + }, + { + credentialRecordId: 'credential-id2', + credentialRecordType: 'jsonld', + }, + ], + }), + ] + + mockFunction(credentialRepository.getAll).mockResolvedValue(records) + + await testModule.migrateCredentialExchangeRecordToV0_4(agent) + + expect(credentialRepository.getAll).toHaveBeenCalledTimes(1) + expect(credentialRepository.update).toHaveBeenCalledTimes(1) + + const [, credentialRecord] = mockFunction(credentialRepository.update).mock.calls[0] + expect(credentialRecord.toJSON()).toMatchObject({ + metadata: { + '_anoncreds/credential': { some: 'value' }, + '_anoncreds/credentialRequest': { nonce: 'nonce', link_secret_name: 'ms', link_secret_blinding_data: 'msbd' }, + }, + credentials: [ + { + credentialRecordId: 'credential-id', + credentialRecordType: 'anoncreds', + }, + { + credentialRecordId: 'credential-id2', + credentialRecordType: 'jsonld', + }, + ], + }) + }) + }) + + describe('migrateIndyCredentialMetadataToAnonCredsMetadata()', () => { + test('updates indy metadata to anoncreds metadata', () => { + const record = getCredentialRecord({ + metadata: { + '_internal/indyCredential': { some: 'value' }, + '_internal/indyRequest': { nonce: 'nonce', master_secret_name: 'ms', master_secret_blinding_data: 'msbd' }, + }, + }) + + migrateIndyCredentialMetadataToAnonCredsMetadata(agent, record) + + expect(record.toJSON()).toMatchObject({ + metadata: { + '_anoncreds/credential': { some: 'value' }, + '_anoncreds/credentialRequest': { nonce: 'nonce', link_secret_name: 'ms', link_secret_blinding_data: 'msbd' }, + }, + }) + }) + }) + + describe('migrateIndyCredentialTypeToAnonCredsCredential()', () => { + test('updates indy credential record binding to anoncreds binding', () => { + const record = getCredentialRecord({ + credentials: [ + { + credentialRecordId: 'credential-id', + credentialRecordType: 'indy', + }, + { + credentialRecordId: 'credential-id2', + credentialRecordType: 'jsonld', + }, + ], + }) + + migrateIndyCredentialTypeToAnonCredsCredential(agent, record) + + expect(record.toJSON()).toMatchObject({ + credentials: [ + { + credentialRecordId: 'credential-id', + credentialRecordType: 'anoncreds', + }, + { + credentialRecordId: 'credential-id2', + credentialRecordType: 'jsonld', + }, + ], + }) + }) + }) +}) + +function getCredentialRecord({ + id, + metadata, + credentials, +}: { + id?: string + metadata?: Record + credentials?: CredentialRecordBinding[] +}) { + return JsonTransformer.fromJSON( + { + id: id ?? 'credential-id', + metadata, + credentials, + }, + CredentialExchangeRecord + ) +} diff --git a/packages/anoncreds/src/updates/0.3.1-0.4/__tests__/linkSecret.test.ts b/packages/anoncreds/src/updates/0.3.1-0.4/__tests__/linkSecret.test.ts new file mode 100644 index 0000000000..200e81914b --- /dev/null +++ b/packages/anoncreds/src/updates/0.3.1-0.4/__tests__/linkSecret.test.ts @@ -0,0 +1,76 @@ +import { Agent } from '../../../../../core/src/agent/Agent' +import { getAgentConfig, getAgentContext, mockFunction } from '../../../../../core/tests' +import { AnonCredsLinkSecretRecord } from '../../../repository' +import { AnonCredsLinkSecretRepository } from '../../../repository/AnonCredsLinkSecretRepository' +import * as testModule from '../linkSecret' + +const agentConfig = getAgentConfig('AnonCreds Migration - Link Secret - 0.3.1-0.4.0') +const agentContext = getAgentContext() + +jest.mock('../../../repository/AnonCredsLinkSecretRepository') +const AnonCredsLinkSecretRepositoryMock = AnonCredsLinkSecretRepository as jest.Mock +const linkSecretRepository = new AnonCredsLinkSecretRepositoryMock() + +jest.mock('../../../../../core/src/agent/Agent', () => { + return { + Agent: jest.fn(() => ({ + config: agentConfig, + context: agentContext, + wallet: { + walletConfig: { + id: 'wallet-id', + }, + }, + dependencyManager: { + resolve: jest.fn(() => linkSecretRepository), + }, + })), + } +}) + +// Mock typed object +const AgentMock = Agent as jest.Mock + +describe('0.3.1-0.4.0 | AnonCreds Migration | Link Secret', () => { + let agent: Agent + + beforeEach(() => { + agent = new AgentMock() + }) + + afterEach(() => { + jest.clearAllMocks() + }) + + describe('migrateLinkSecretToV0_4()', () => { + test('creates default link secret record based on wallet id if no default link secret exists', async () => { + mockFunction(linkSecretRepository.findDefault).mockResolvedValue(null) + + await testModule.migrateLinkSecretToV0_4(agent) + + expect(linkSecretRepository.findDefault).toHaveBeenCalledTimes(1) + expect(linkSecretRepository.save).toHaveBeenCalledTimes(1) + + const [, linkSecretRecord] = mockFunction(linkSecretRepository.save).mock.calls[0] + expect(linkSecretRecord.toJSON()).toMatchObject({ + linkSecretId: 'wallet-id', + }) + expect(linkSecretRecord.getTags()).toMatchObject({ + isDefault: true, + }) + }) + + test('does not create default link secret record if default link secret record already exists', async () => { + mockFunction(linkSecretRepository.findDefault).mockResolvedValue( + new AnonCredsLinkSecretRecord({ + linkSecretId: 'some-link-secret-id', + }) + ) + + await testModule.migrateLinkSecretToV0_4(agent) + + expect(linkSecretRepository.findDefault).toHaveBeenCalledTimes(1) + expect(linkSecretRepository.update).toHaveBeenCalledTimes(0) + }) + }) +}) diff --git a/packages/anoncreds/src/updates/0.3.1-0.4/__tests__/schema.test.ts b/packages/anoncreds/src/updates/0.3.1-0.4/__tests__/schema.test.ts new file mode 100644 index 0000000000..0c2f2fd46e --- /dev/null +++ b/packages/anoncreds/src/updates/0.3.1-0.4/__tests__/schema.test.ts @@ -0,0 +1,117 @@ +import type { AnonCredsSchema } from '../../../models' + +import { JsonTransformer } from '../../../../../core/src' +import { Agent } from '../../../../../core/src/agent/Agent' +import { getAgentConfig, getAgentContext, mockFunction } from '../../../../../core/tests' +import { AnonCredsSchemaRecord } from '../../../repository' +import { AnonCredsSchemaRepository } from '../../../repository/AnonCredsSchemaRepository' +import * as testModule from '../schema' + +const agentConfig = getAgentConfig('AnonCreds Migration - Credential Exchange Record - 0.3.1-0.4.0') +const agentContext = getAgentContext() + +jest.mock('../../../repository/AnonCredsSchemaRepository') +const AnonCredsSchemaRepositoryMock = AnonCredsSchemaRepository as jest.Mock +const schemaRepository = new AnonCredsSchemaRepositoryMock() + +jest.mock('../../../../../core/src/agent/Agent', () => { + return { + Agent: jest.fn(() => ({ + config: agentConfig, + context: agentContext, + dependencyManager: { + resolve: jest.fn(() => schemaRepository), + }, + })), + } +}) + +// Mock typed object +const AgentMock = Agent as jest.Mock + +describe('0.3.1-0.4.0 | AnonCreds Migration | Schema Record', () => { + let agent: Agent + + beforeEach(() => { + agent = new AgentMock() + }) + + afterEach(() => { + jest.clearAllMocks() + }) + + describe('migrateAnonCredsSchemaRecordToV0_4()', () => { + it('should fetch all records and apply the needed updates', async () => { + const records: AnonCredsSchemaRecord[] = [ + getSchemaRecord({ + schema: { + attrNames: ['name', 'age'], + id: 'did:indy:bcovrin:test:SDqTzbVuCowusqGBNbNDjH/anoncreds/v0/SCHEMA/schema-name/1.0', + name: 'schema-name', + seqNo: 1, + version: '1.0', + ver: '1.0', + }, + }), + ] + + mockFunction(schemaRepository.getAll).mockResolvedValue(records) + + await testModule.migrateAnonCredsSchemaRecordToV0_4(agent) + + expect(schemaRepository.getAll).toHaveBeenCalledTimes(1) + expect(schemaRepository.update).toHaveBeenCalledTimes(1) + + const [, schemaRecord] = mockFunction(schemaRepository.update).mock.calls[0] + expect(schemaRecord.toJSON()).toMatchObject({ + schemaId: 'did:indy:bcovrin:test:SDqTzbVuCowusqGBNbNDjH/anoncreds/v0/SCHEMA/schema-name/1.0', + schema: { + attrNames: ['name', 'age'], + name: 'schema-name', + version: '1.0', + issuerId: 'did:indy:bcovrin:test:SDqTzbVuCowusqGBNbNDjH', + }, + }) + }) + + it('should skip records that are already migrated to the 0.4.0 format', async () => { + const records: AnonCredsSchemaRecord[] = [ + getSchemaRecord({ + schema: { + attrNames: ['name', 'age'], + name: 'schema-name', + version: '1.0', + issuerId: 'did:indy:bcovrin:test:SDqTzbVuCowusqGBNbNDjH', + }, + schemaId: 'did:indy:bcovrin:test:SDqTzbVuCowusqGBNbNDjH/anoncreds/v0/SCHEMA/schema-name/1.0', + }), + ] + + mockFunction(schemaRepository.getAll).mockResolvedValue(records) + + await testModule.migrateAnonCredsSchemaRecordToV0_4(agent) + + expect(schemaRepository.getAll).toHaveBeenCalledTimes(1) + expect(schemaRepository.update).toHaveBeenCalledTimes(0) + }) + }) +}) + +function getSchemaRecord({ + id, + schema, + schemaId, +}: { + id?: string + schema: testModule.OldSchema | AnonCredsSchema + schemaId?: string +}) { + return JsonTransformer.fromJSON( + { + id: id ?? 'schema-record-id', + schema, + schemaId, + }, + AnonCredsSchemaRecord + ) +} diff --git a/packages/anoncreds/src/updates/0.3.1-0.4/credentialDefinition.ts b/packages/anoncreds/src/updates/0.3.1-0.4/credentialDefinition.ts new file mode 100644 index 0000000000..cff9842fb6 --- /dev/null +++ b/packages/anoncreds/src/updates/0.3.1-0.4/credentialDefinition.ts @@ -0,0 +1,93 @@ +import type { BaseAgent } from '@aries-framework/core' + +import { AriesFrameworkError } from '@aries-framework/core' + +import { AnonCredsCredentialDefinitionRepository } from '../../repository' +import { AnonCredsRegistryService } from '../../services/registry/AnonCredsRegistryService' + +/** + * Migrates the {@link AnonCredsCredentialDefinitionRecord} to 0.4 compatible format. It fetches all credential definition records from + * storage and updates the format based on the new ledger agnostic anoncreds models. After a record has been transformed, + * it is updated in storage and the next record will be transformed. + */ +export async function migrateAnonCredsCredentialDefinitionRecordToV0_4(agent: Agent) { + agent.config.logger.info('Migrating AnonCredsCredentialDefinitionRecord records to storage version 0.4') + const credentialDefinitionRepository = agent.dependencyManager.resolve(AnonCredsCredentialDefinitionRepository) + + agent.config.logger.debug(`Fetching all credential definition records from storage`) + const credentialDefinitionRecords = await credentialDefinitionRepository.getAll(agent.context) + + agent.config.logger.debug( + `Found a total of ${credentialDefinitionRecords.length} credential definition records to update.` + ) + + for (const credentialDefinitionRecord of credentialDefinitionRecords) { + const oldCredentialDefinition = + credentialDefinitionRecord.credentialDefinition as unknown as OldCredentialDefinition + + // If askar migration script is ran, it could be that the credential definition record is already in 0.4 format + if (oldCredentialDefinition.id === undefined) { + agent.config.logger.info( + `Credential definition record with id ${credentialDefinitionRecord.id} and credential definition id ${credentialDefinitionRecord.credentialDefinitionId} is already in storage version 0.4 format. Probably due to Indy SDK to Askar migration. Skipping...` + ) + continue + } + + agent.config.logger.debug( + `Migrating anoncreds credential definition record with id ${credentialDefinitionRecord.id} and credential definition id ${oldCredentialDefinition.id} to storage version 0.4` + ) + + // the schemaId is actually the ledger seqNo. We'll have to fetch the schema from the ledger to get the schemaId + // However, we can't just fetch the schema by it's seqNo, so we'll actually fetch the credential definition, + // which will contain the valid schemaId + const registryService = agent.dependencyManager.resolve(AnonCredsRegistryService) + const registry = registryService.getRegistryForIdentifier(agent.context, oldCredentialDefinition.id) + agent.config.logger.debug( + `Using registry with supportedIdentifier ${registry.supportedIdentifier} to resolve credential definition` + ) + + const { credentialDefinition } = await registry.getCredentialDefinition(agent.context, oldCredentialDefinition.id) + if (!credentialDefinition) { + agent.config.logger.error( + `Could not resolve credential definition with id ${oldCredentialDefinition.id} from ledger` + ) + throw new AriesFrameworkError(`Unable to resolve credential definition ${oldCredentialDefinition.id}`) + } + + agent.config.logger.debug(`Resolved credential definition with id ${oldCredentialDefinition.id} from ledger`, { + credentialDefinition, + }) + + const newCredentialDefinition = { + // Use the schemaId from the resolved credential definition so we get the qualified identifier + schemaId: credentialDefinition.schemaId, + tag: oldCredentialDefinition.tag, + type: oldCredentialDefinition.type, + value: oldCredentialDefinition.value, + issuerId: oldCredentialDefinition.id.split('/')[0], + } + + credentialDefinitionRecord.credentialDefinition = newCredentialDefinition + credentialDefinitionRecord.credentialDefinitionId = oldCredentialDefinition.id + credentialDefinitionRecord.methodName = 'indy' + + // Save updated credentialDefinition record + await credentialDefinitionRepository.update(agent.context, credentialDefinitionRecord) + + agent.config.logger.debug( + `Successfully migrated credential definition record with id ${credentialDefinitionRecord.id} to storage version 0.4` + ) + } +} + +export interface OldCredentialDefinition { + id: string + schemaId: string + type: 'CL' + tag: string + value: { + primary: Record + revocation?: unknown | undefined + } + ver: string +} diff --git a/packages/anoncreds/src/updates/0.3.1-0.4/credentialExchangeRecord.ts b/packages/anoncreds/src/updates/0.3.1-0.4/credentialExchangeRecord.ts new file mode 100644 index 0000000000..b181f42a61 --- /dev/null +++ b/packages/anoncreds/src/updates/0.3.1-0.4/credentialExchangeRecord.ts @@ -0,0 +1,151 @@ +import type { BaseAgent, CredentialExchangeRecord } from '@aries-framework/core' + +import { CredentialRepository } from '@aries-framework/core' + +/** + * Migrates the {@link CredentialExchangeRecord} to 0.4 compatible format. It fetches all credential exchange records from + * storage and applies the needed updates to the records. After a record has been transformed, it is updated + * in storage and the next record will be transformed. + * + * The following transformations are applied: + * - {@link migrateIndyCredentialMetadataToAnonCredsMetadata} + * - {@link migrateIndyCredentialTypeToAnonCredsCredential} + */ +export async function migrateCredentialExchangeRecordToV0_4(agent: Agent) { + agent.config.logger.info('Migrating credential exchange records to storage version 0.4') + const credentialRepository = agent.dependencyManager.resolve(CredentialRepository) + + agent.config.logger.debug(`Fetching all credential records from storage`) + const credentialRecords = await credentialRepository.getAll(agent.context) + + agent.config.logger.debug(`Found a total of ${credentialRecords.length} credential exchange records to update.`) + for (const credentialRecord of credentialRecords) { + agent.config.logger.debug( + `Migrating credential exchange record with id ${credentialRecord.id} to storage version 0.4` + ) + + migrateIndyCredentialTypeToAnonCredsCredential(agent, credentialRecord) + migrateIndyCredentialMetadataToAnonCredsMetadata(agent, credentialRecord) + + // Save updated did record + await credentialRepository.update(agent.context, credentialRecord) + + agent.config.logger.debug( + `Successfully migrated credential exchange record with id ${credentialRecord.id} to storage version 0.4` + ) + } +} + +/** + * Migrates the indy credential record binding to anoncreds credential record binding. + * + * The following 0.3.1 credential record structure (unrelated keys omitted): + * + * ```json + * { + * "credentials": [ + * { + * "credentialRecordId": "credential-id", + * "credentialRecordType": "indy" + * }, + * { + * "credentialRecordId": "credential-id2", + * "credentialRecordType": "jsonld" + * } + * ] + * } + * ``` + * + * Wil be tranformed into the following 0.4 credential record structure (unrelated keys omitted): + * ```json + * { + * "credentials": [ + * { + * "credentialRecordId": "credential-id", + * "credentialRecordType": "anoncreds" + * }, + * { + * "credentialRecordId": "credential-id2", + * "credentialRecordType": "jsonld" + * } + * ] + * } + * ``` + */ +export function migrateIndyCredentialTypeToAnonCredsCredential( + agent: Agent, + credentialRecord: CredentialExchangeRecord +) { + agent.config.logger.debug( + `Migrating credential record with id ${credentialRecord.id} to anoncreds credential binding for version 0.4` + ) + + const INDY_CREDENTIAL_RECORD_TYPE = 'indy' + const ANONCREDS_CREDENTIAL_RECORD_TYPE = 'anoncreds' + + for (const credential of credentialRecord.credentials) { + if (credential.credentialRecordType === INDY_CREDENTIAL_RECORD_TYPE) { + agent.config.logger.debug(`Updating credential binding ${credential.credentialRecordId} to anoncreds type`) + credential.credentialRecordType = ANONCREDS_CREDENTIAL_RECORD_TYPE + } + } + + agent.config.logger.debug( + `Successfully migrated credential record with id ${credentialRecord.id} to anoncreds credential binding for version 0.4` + ) +} + +/** + * Migrates the indy credential metadata type to anoncreds credential metadata type. + * + * The following 0.3.1 credential metadata structure (unrelated keys omitted): + * + * ```json + * { + * "_internal/indyRequest": {} + * "_internal/indyCredential": {} + * } + * ``` + * + * Wil be tranformed into the following 0.4 credential metadata structure (unrelated keys omitted): + * ```json + * { + * "_anoncreds/credentialRequest": {} + * "_anoncreds/credential": {} + * } + * ``` + */ +export function migrateIndyCredentialMetadataToAnonCredsMetadata( + agent: Agent, + credentialRecord: CredentialExchangeRecord +) { + agent.config.logger.debug( + `Migrating credential record with id ${credentialRecord.id} to anoncreds metadata for version 0.4` + ) + + const indyCredentialRequestMetadataKey = '_internal/indyRequest' + const indyCredentialMetadataKey = '_internal/indyCredential' + + const ANONCREDS_CREDENTIAL_REQUEST_METADATA = '_anoncreds/credentialRequest' + const ANONCREDS_CREDENTIAL_METADATA = '_anoncreds/credential' + + const indyCredentialRequestMetadata = credentialRecord.metadata.get(indyCredentialRequestMetadataKey) + if (indyCredentialRequestMetadata) { + credentialRecord.metadata.set(ANONCREDS_CREDENTIAL_REQUEST_METADATA, { + link_secret_blinding_data: indyCredentialRequestMetadata.master_secret_blinding_data, + link_secret_name: indyCredentialRequestMetadata.master_secret_name, + nonce: indyCredentialRequestMetadata.nonce, + }) + credentialRecord.metadata.delete(indyCredentialRequestMetadataKey) + } + + const indyCredentialMetadata = credentialRecord.metadata.get(indyCredentialMetadataKey) + if (indyCredentialMetadata) { + credentialRecord.metadata.set(ANONCREDS_CREDENTIAL_METADATA, indyCredentialMetadata) + credentialRecord.metadata.delete(indyCredentialMetadataKey) + } + + agent.config.logger.debug( + `Successfully migrated credential record with id ${credentialRecord.id} to anoncreds credential metadata for version 0.4` + ) +} diff --git a/packages/anoncreds/src/updates/0.3.1-0.4/index.ts b/packages/anoncreds/src/updates/0.3.1-0.4/index.ts new file mode 100644 index 0000000000..8d79e32cd1 --- /dev/null +++ b/packages/anoncreds/src/updates/0.3.1-0.4/index.ts @@ -0,0 +1,13 @@ +import type { BaseAgent } from '@aries-framework/core' + +import { migrateAnonCredsCredentialDefinitionRecordToV0_4 } from './credentialDefinition' +import { migrateCredentialExchangeRecordToV0_4 } from './credentialExchangeRecord' +import { migrateLinkSecretToV0_4 } from './linkSecret' +import { migrateAnonCredsSchemaRecordToV0_4 } from './schema' + +export async function updateAnonCredsModuleV0_3_1ToV0_4(agent: Agent): Promise { + await migrateCredentialExchangeRecordToV0_4(agent) + await migrateLinkSecretToV0_4(agent) + await migrateAnonCredsCredentialDefinitionRecordToV0_4(agent) + await migrateAnonCredsSchemaRecordToV0_4(agent) +} diff --git a/packages/anoncreds/src/updates/0.3.1-0.4/linkSecret.ts b/packages/anoncreds/src/updates/0.3.1-0.4/linkSecret.ts new file mode 100644 index 0000000000..d665084092 --- /dev/null +++ b/packages/anoncreds/src/updates/0.3.1-0.4/linkSecret.ts @@ -0,0 +1,43 @@ +import type { BaseAgent } from '@aries-framework/core' + +import { AnonCredsLinkSecretRecord, AnonCredsLinkSecretRepository } from '../../repository' + +/** + * Creates an {@link AnonCredsLinkSecretRecord} based on the wallet id. If an {@link AnonCredsLinkSecretRecord} + * already exists (which is the case when upgraded to Askar), no link secret record will be created. + */ +export async function migrateLinkSecretToV0_4(agent: Agent) { + agent.config.logger.info('Migrating link secret to storage version 0.4') + + const linkSecretRepository = agent.dependencyManager.resolve(AnonCredsLinkSecretRepository) + + agent.config.logger.debug(`Fetching default link secret record from storage`) + const defaultLinkSecret = await linkSecretRepository.findDefault(agent.context) + + if (!defaultLinkSecret) { + // If no default link secret record exists, we create one based on the wallet id and set is as default + agent.config.logger.debug(`No default link secret record found. Creating one based on wallet id.`) + + if (!agent.wallet.walletConfig?.id) { + agent.config.logger.error(`Wallet id not found. Cannot create default link secret record. Skipping...`) + return + } + + // We can't store the link secret value. This is not exposed by indy-sdk. + const linkSecret = new AnonCredsLinkSecretRecord({ + linkSecretId: agent.wallet.walletConfig?.id, + }) + linkSecret.setTag('isDefault', true) + + agent.config.logger.debug( + `Saving default link secret record with record id ${linkSecret.id} and link secret id ${linkSecret.linkSecretId} to storage` + ) + await linkSecretRepository.save(agent.context, linkSecret) + } else { + agent.config.logger.debug( + `Default link secret record with record id ${defaultLinkSecret.id} and link secret id ${defaultLinkSecret.linkSecretId} found. Skipping...` + ) + } + + agent.config.logger.debug(`Successfully migrated link secret to version 0.4`) +} diff --git a/packages/anoncreds/src/updates/0.3.1-0.4/schema.ts b/packages/anoncreds/src/updates/0.3.1-0.4/schema.ts new file mode 100644 index 0000000000..60e2b2da0c --- /dev/null +++ b/packages/anoncreds/src/updates/0.3.1-0.4/schema.ts @@ -0,0 +1,62 @@ +import type { BaseAgent } from '@aries-framework/core' + +import { AnonCredsSchemaRepository } from '../../repository' + +/** + * Migrates the {@link AnonCredsSchemaRecord} to 0.4 compatible format. It fetches all schema records from + * storage and updates the format based on the new ledger agnostic anoncreds models. After a record has been transformed, + * it is updated in storage and the next record will be transformed. + */ +export async function migrateAnonCredsSchemaRecordToV0_4(agent: Agent) { + agent.config.logger.info('Migrating AnonCredsSchemaRecord records to storage version 0.4') + const schemaRepository = agent.dependencyManager.resolve(AnonCredsSchemaRepository) + + agent.config.logger.debug(`Fetching all schema records from storage`) + const schemaRecords = await schemaRepository.getAll(agent.context) + + agent.config.logger.debug(`Found a total of ${schemaRecords.length} schema records to update.`) + for (const schemaRecord of schemaRecords) { + const oldSchema = schemaRecord.schema as unknown as OldSchema + + // If askar migration script is ran, it could be that the credential definition record is already in 0.4 format + if (oldSchema.id === undefined) { + agent.config.logger.info( + `Schema record with id ${schemaRecord.id} and schema id ${schemaRecord.schemaId} is already in storage version 0.4 format. Probably due to Indy SDK to Askar migration. Skipping...` + ) + continue + } + + agent.config.logger.debug( + `Migrating anoncreds schema record with id ${schemaRecord.id} and schema id ${oldSchema.id} to storage version 0.4` + ) + + const newSchema = { + attrNames: oldSchema.attrNames, + name: oldSchema.name, + version: oldSchema.version, + issuerId: oldSchema.id.split('/')[0], + } + + schemaRecord.schema = newSchema + schemaRecord.schemaId = oldSchema.id + schemaRecord.methodName = 'indy' + + // schemaIssuerDid was set as tag, but is now replaced by issuerId. It was also always set + // to the value `did` as it incorrectly parsed the schemaId. + schemaRecord.setTag('schemaIssuerDid', undefined) + + // Save updated schema record + await schemaRepository.update(agent.context, schemaRecord) + + agent.config.logger.debug(`Successfully migrated schema record with id ${schemaRecord.id} to storage version 0.4`) + } +} + +export interface OldSchema { + id: string + name: string + version: string + attrNames: string[] + seqNo: number + ver: string +} diff --git a/packages/anoncreds/src/updates/__tests__/0.3.test.ts b/packages/anoncreds/src/updates/__tests__/0.3.test.ts new file mode 100644 index 0000000000..7ad829c3f9 --- /dev/null +++ b/packages/anoncreds/src/updates/__tests__/0.3.test.ts @@ -0,0 +1,239 @@ +import { DependencyManager, InjectionSymbols, Agent, UpdateAssistant, utils } from '@aries-framework/core' +import { readFileSync } from 'fs' +import path from 'path' + +import { InMemoryStorageService } from '../../../../../tests/InMemoryStorageService' +import { indySdk, agentDependencies } from '../../../../core/tests' +import { IndySdkWallet } from '../../../../indy-sdk/src' +import { IndySdkSymbol } from '../../../../indy-sdk/src/types' +import { InMemoryAnonCredsRegistry } from '../../../tests/InMemoryAnonCredsRegistry' +import { AnonCredsModule } from '../../AnonCredsModule' +import { + AnonCredsHolderServiceSymbol, + AnonCredsIssuerServiceSymbol, + AnonCredsVerifierServiceSymbol, +} from '../../services' + +// Backup date / time is the unique identifier for a backup, needs to be unique for every test +const backupDate = new Date('2023-03-19T22:50:20.522Z') +jest.useFakeTimers().setSystemTime(backupDate) + +describe('UpdateAssistant | AnonCreds | v0.3.1 - v0.4', () => { + it(`should correctly update the credential exchange records for holders`, async () => { + // We need to mock the uuid generation to make sure we generate consistent uuids for the new records created. + let uuidCounter = 1 + const uuidSpy = jest.spyOn(utils, 'uuid').mockImplementation(() => `${uuidCounter++}-4e4f-41d9-94c4-f49351b811f1`) + + const holderRecordsString = readFileSync( + path.join(__dirname, '__fixtures__/holder-anoncreds-2-credentials-0.3.json'), + 'utf8' + ) + + const dependencyManager = new DependencyManager() + const storageService = new InMemoryStorageService() + dependencyManager.registerInstance(InjectionSymbols.StorageService, storageService) + // If we register the IndySdkModule it will register the storage service, but we use in memory storage here + dependencyManager.registerContextScoped(InjectionSymbols.Wallet, IndySdkWallet) + dependencyManager.registerInstance(IndySdkSymbol, indySdk) + dependencyManager.registerInstance(AnonCredsIssuerServiceSymbol, {}) + dependencyManager.registerInstance(AnonCredsHolderServiceSymbol, {}) + dependencyManager.registerInstance(AnonCredsVerifierServiceSymbol, {}) + + const agent = new Agent( + { + config: { + label: 'Test Agent', + walletConfig: { + id: `Wallet: 0.3 Update AnonCreds - Holder`, + key: `Key: 0.3 Update AnonCreds - Holder`, + }, + }, + dependencies: agentDependencies, + modules: { + // We need to include the AnonCredsModule to run the updates + anoncreds: new AnonCredsModule({ + registries: [new InMemoryAnonCredsRegistry()], + }), + }, + }, + dependencyManager + ) + + const updateAssistant = new UpdateAssistant(agent, { + v0_1ToV0_2: { + mediationRoleUpdateStrategy: 'doNotChange', + }, + }) + + await updateAssistant.initialize() + + // Set storage after initialization. This mimics as if this wallet + // is opened as an existing wallet instead of a new wallet + storageService.records = JSON.parse(holderRecordsString) + + expect(await updateAssistant.isUpToDate()).toBe(false) + expect(await updateAssistant.getNeededUpdates('0.4')).toEqual([ + { + fromVersion: '0.3.1', + toVersion: '0.4', + doUpdate: expect.any(Function), + }, + ]) + + await updateAssistant.update() + + expect(await updateAssistant.isUpToDate()).toBe(true) + expect(await updateAssistant.getNeededUpdates()).toEqual([]) + + // MEDIATOR_ROUTING_RECORD recipientKeys will be different every time, and is not what we're testing here + delete storageService.records.MEDIATOR_ROUTING_RECORD + expect(storageService.records).toMatchSnapshot() + + await agent.shutdown() + await agent.wallet.delete() + + uuidSpy.mockReset() + }) + + it(`should correctly update the schema and credential definition, and create link secret records for issuers`, async () => { + // We need to mock the uuid generation to make sure we generate consistent uuids for the new records created. + let uuidCounter = 1 + const uuidSpy = jest.spyOn(utils, 'uuid').mockImplementation(() => `${uuidCounter++}-4e4f-41d9-94c4-f49351b811f1`) + + const issuerRecordsString = readFileSync( + path.join(__dirname, '__fixtures__/issuer-anoncreds-2-schema-credential-definition-credentials-0.3.json'), + 'utf8' + ) + + const dependencyManager = new DependencyManager() + const storageService = new InMemoryStorageService() + dependencyManager.registerInstance(InjectionSymbols.StorageService, storageService) + // If we register the IndySdkModule it will register the storage service, but we use in memory storage here + dependencyManager.registerContextScoped(InjectionSymbols.Wallet, IndySdkWallet) + dependencyManager.registerInstance(IndySdkSymbol, indySdk) + dependencyManager.registerInstance(AnonCredsIssuerServiceSymbol, {}) + dependencyManager.registerInstance(AnonCredsHolderServiceSymbol, {}) + dependencyManager.registerInstance(AnonCredsVerifierServiceSymbol, {}) + + const agent = new Agent( + { + config: { + label: 'Test Agent', + walletConfig: { + id: `Wallet: 0.3 Update AnonCreds - Issuer`, + key: `Key: 0.3 Update AnonCreds - Issuer`, + }, + }, + dependencies: agentDependencies, + modules: { + // We need to include the AnonCredsModule to run the updates + anoncreds: new AnonCredsModule({ + registries: [ + // We need to be able to resolve the credential definition so we can correctly + new InMemoryAnonCredsRegistry({ + existingCredentialDefinitions: { + 'did:indy:bcovrin:test:A4CYPASJYRZRt98YWrac3H/anoncreds/v0/CLAIM_DEF/728265/TAG': { + schemaId: 'did:indy:bcovrin:test:A4CYPASJYRZRt98YWrac3H/anoncreds/v0/SCHEMA/Test Schema/5.0', + type: 'CL', + tag: 'TAG', + value: { + primary: { + n: '92212366077388130017820454980772482128748816766820141476572599854614095851660955000471493059368591899172871902601780138917819366396362308478329294184309858890996528496805316851980442998603067852135492500241351106196875782591605768921500179261268030423733287913264566336690041275292095018304899931956463465418485815424864260174164039300668997079647515281912887296402163314193409758676035183692610399804909476026418386307889108672419432084350222061008099663029495600327790438170442656903258282723208685959709427842790363181237326817713760262728130215152068903053780106153722598661062532884431955981726066921637468626277', + s: '51390585781167888666038495435187170763184923351566453067945476469346756595806461020566734704158200027078692575370502193819960413516290740555746465017482403889478846290536023708403164732218491843776868132606601025003681747438312581577370961516850128243993069117644352618102176047630881347535103984514944899145266563740618494984195198066875837169587608421653434298405108448043919659694417868161307274719186874014050768478275366248108923366328095899343801270111152240906954275776825865228792303252410200003812030838965966766135547588341334766187306815530098180130152857685278588510653805870629396608258594629734808653690', + r: { + master_secret: + '61760181601132349837705650289020474131050187135887129471275844481815813236212130783118399756778708344638568886652376797607377320325668612002653752234977886335615451602379984880071434500085608574636210148262041392898193694256008614118948399335181637372037261847305940365423773073896368876304671332779131812342778821167205383614143093932646167069176375555949468490333033638790088487176980785886865670928635382374747549737473235069853277820515331625504955674335885563904945632728269515723913822149934246500994026445014344664596837782532383727917670585587931554459150014400148586199456993200824425072825041491149065115358', + name: '26931653629593338073547610164492146524581067674323312766422801723649824593245481234130445257275008372300577748467390938672361842062002005882497002927312107798057743381013725196864084323240188855871993429346248168719358184490582297236588103100736704037766893167139178159330117766371806271005063205199099350905918805615139883380562348264630567225617537443345104841331985857206740142310735949731954114795552226430346325242557801443933408634628778255674180716568613268278944764455783252702248656985033565125477742417595184280107251126994232013125430027211388949790163391384834400043466265407965987657397646084753620067162', + age: '12830581846716232289919923091802380953776468678758115385731032778424701987000173171859986490394782070339145726689704906636521504338663443469452098276346339448054923530423862972901740020260863939784049655599141309168321131841197392728580317478651190091260391159517458959241170623799027865010022955890184958710784660242539198197998462816406524943537217991903198815091955260278449922637325465043293444707204707128649276474679898162587929569212222042385297095967670138838722149998051089657830225229881876437390119475653879155105350339634203813849831587911926503279160004910687478611349149984784835918594248713746244647783', + }, + rctxt: + '49138795132156579347604024288478735151511429635862925688354411685205551763173458098934068417340097826251030547752551543780926866551808708614689637810970695962341030571486307177314332719168625736959985286432056963760600243473038903885347227651607234887915878119362501367507071709125019506105125043394599512754034429977523734855754182754166158276654375145600716372728023694171066421047665189687655246390105632221713801254689564447819382923248801463300558408016868673087319876644152902663657524012266707505607127264589517707325298805787788577090696580253467312664036297509153665682462337661380935241888630672980409135218', + z: '60039858321231958911193979301402644724013798961769784342413248136534681852773598059805490735235936787666273383388316713664379360735859198156203333524277752965063504355175962212112042368638829236003950022345790744597825843498279654720032726822247321101635671237626308268641767351508666548662103083107416168951088459343716911392807952489009684909391952363633692353090657169830487309162716174148340837088238136793727262599036868196525437496909391247737814314203700293659965465494637540937762691328712617352605531361117679740841379808332881579693119257467828678864789270752346248637901288389165259844857126172669320275054', + }, + }, + issuerId: 'did:indy:bcovrin:test:A4CYPASJYRZRt98YWrac3H', + }, + 'did:indy:bcovrin:test:A4CYPASJYRZRt98YWrac3H/anoncreds/v0/CLAIM_DEF/728266/TAG2222': { + schemaId: 'did:indy:bcovrin:test:A4CYPASJYRZRt98YWrac3H/anoncreds/v0/SCHEMA/AnotherSchema/5.12', + type: 'CL', + tag: 'TAG2222', + value: { + primary: { + n: '92672464557302826159958381706610232890780336783477671819498833000372263812875113518039840314305532823865676182383425212337361529127538393953888294696727490398569250059424479369124987018050461872589017845243006613503064725987487445193151580781503573638936354603906684667833347097853363102011613363551325414493877438329911648643160153986822516630519571283817404410939266429143144325310144873915523634615108054232698216677813083664813055224968248142239446186423096615162232894052206134565411335121805318762068246410255953720296084525738290155785653879950387998340378428740625858243516259978797729172915362664095388670853', + s: '14126994029068124564262196574803727042317991235159231485233854758856355239996741822278406673337232628669751727662479515044513565209261235580848666630891738643990084502393352476512637677170660741636200618878417433799077613673205726221908822955109963272016538705991333626487531499501561952303907487494079241110050020874027756313402672435051524680914533743665605349121374703526870439925807395782970618162620991315112088226807823652545755186406850860290372739405126488851340032404507898084409367889215777693868794728141508635105180827151292046483128114528214465463152927678575672993454367871685772245405671312263615738674', + r: { + master_secret: + '26619502892062275386286102324954654427871501074061444846499515284182097331967223335934051936866595058991987589854477281430063143491959604612779394547177027208671151839864660333634457188140162529133121090987235146837242477233778516233683361556079466930407338673047472758762971774183683006400366713364299999136369605402942210978218705656266115751492424192940375368169431001551131077280268253962541139755004287154221749191778445668471756569604156885298127934116907544590473960073154419342138695278066485640775060389330807300193554886282756714343171543381166744147102049996134009291163457413551838522312496539196521595692', + age: '66774168049579501626527407565561158517617240253618394664527561632035323705337586053746273530704030779131642005263474574499533256973752287111528352278167213322154697290967283640418150957238004730763043665983334023181560033670971095508406493073727137576662898702804435263291473328275724172150330235410304531103984478435316648590218258879268883696376276091511367418038567366131461327869666106899795056026111553656932251156588986604454718398629113510266779047268855074155849278155719183039926867214509122089958991364786653941718444527779068428328047815224843863247382688134945397530917090461254004883032104714157971400208', + name: '86741028136853574348723360731891313985090403925160846711944073250686426070668157504590860843944722066104971819518996745252253900749842002049747953678564857190954502037349272982356665401492886602390599170831356482930058593126740772109115907363756874709445041702269262783286817223011097284796236690595266721670997137095592005971209969288260603902458413116126663192645410011918509026240763669966445865557485752253073758758805818980495379553872266089697405986128733558878942127067722757597848458411141451957344742184798866278323991155218917859626726262257431337439505881892995617030558234045945209395337282759265659447047', + height: + '36770374391380149834988196363447736840005566975684817148359676140020826239618728242171844190597784913998189387814084045750250841733745991085876913508447852492274928778550079342017977247125002133117906534740912461625630470754160325262589990928728689070499835994964192507742581994860212500470412940278375419595406129858839275229421691764136274418279944569154327695608011398611897919792595046386574831604431186160019573221025054141054966299987505071844770166968281403659227192031982497703452822527121064221030191938050276126255137769594174387744686048921264418842943478063585931864099188919773279516048122408000535396365', + }, + rctxt: + '71013751275772779114070724661642241189015436101735233481124050655632421295506098157799226697991094582116557937036881377025107827713675564553986787961039221830812177248435167562891351835998258222703796710987072076518659197627933717399137564619646356496210281862112127733957003638837075816198062819168957810762822613691407808469027306413697001991060047213339777833838291591976754857934071589843434238025803790508552421154902537027548698271140571140256835534208651964449214890690159171682094521879102663244464066621388809286987873635426369915309596945084951678722672915158041830248278889303704844284468270547467324686757', + z: '90415953543044389740703639345387867170174070770040351538453902580989033567810029650534915348296084212079064544906463014824475317557221991571331212308335167828473551776349999211544897426382305096215624787217055491736755052175278235595298571339706430785816901931128536495808042995635624112114867111658850659510246291844949806165980806847525704751270260070165853067310918184720602183083989806069386048683955313982129380729637761521928446431397104973906871109766946008926113644488012281655650467201044142029022180536946134328567554182760495139058952910079169456941591963348364521142012653606596379566245637724637892435425', + }, + revocation: { + g: '1 1864FF219549D1BC1E492955305FC5EED27C114580F206532D2F5D983A1DD3BD 1 0414758D7B6B254A9CA81E1084721A97CA312497C21BB9B16096636C59F9D105 2 095E45DDF417D05FB10933FFC63D474548B7FFFF7888802F07FFFFFF7D07A8A8', + g_dash: + '1 2327DA248E721E3935D81C5579DD3707882FFB962B518D37FB1112D96CC63611 1 164989452135CF5D840A20EE354DBF26BEEC74DE7FD53672E55224BEE0228128 1 0634D5E85C210319BFD2535AFD8F7F79590B2F5CC61AF794218CC50B43FBB8C6 1 0A63F1C0FC2C4540156C7A2E2A2DF1DDF99879C25B4F622933707DD6074A0F1B 2 095E45DDF417D05FB10933FFC63D474548B7FFFF7888802F07FFFFFF7D07A8A8 1 0000000000000000000000000000000000000000000000000000000000000000', + h: '1 0A031B1932CDFEE76C448CA0B13A7DDC81615036DA17B81DB2E5DFC7D1F6CD6F 1 06F46C9CC7D32A11C7D2A308D4C71BEE42B3BD9DD54141284D92D64D3AC2CE04 2 095E45DDF417D05FB10933FFC63D474548B7FFFF7888802F07FFFFFF7D07A8A8', + h0: '1 1C88CA353EF878B74E7F515C88E2CBF11FDC3047E3C5057B34ECC2635B4F8FA5 1 1D645261FBC6164EC493BB700B5D8D5C8BF876FD9BA034B107753C79A53B0321 2 095E45DDF417D05FB10933FFC63D474548B7FFFF7888802F07FFFFFF7D07A8A8', + h1: '1 16AC82FE7769689173EABA532E7A489DF87F81AE891C1FDA90FE9813F6761D71 1 147E45451C76CD3A9B0649B12E27EA0BF4E85E632D1B2BEC3EC9FFFA51780ACE 2 095E45DDF417D05FB10933FFC63D474548B7FFFF7888802F07FFFFFF7D07A8A8', + h2: '1 2522C4FAA35392EE9B35DAC9CD8E270364598A5ED019CB34695E9C01D43C16DC 1 21D353FB299C9E39C976055BF4555198C63F912DBE3471E930185EF5A20470E5 2 095E45DDF417D05FB10933FFC63D474548B7FFFF7888802F07FFFFFF7D07A8A8', + htilde: + '1 24D87DBC6283534AE2AA38C45E52D83CC1E70BD589C813F412CC68563F52A2CA 1 05189BC1AAEE8E2A6CB92F65A8C0A18E4125EE61E5CEF1809EF68B388844D1B1 2 095E45DDF417D05FB10933FFC63D474548B7FFFF7888802F07FFFFFF7D07A8A8', + h_cap: + '1 1E3272ABDFD9BF05DB5A7667335A48B9026C9EA2C8DB9FA6E59323BBEB955FE2 1 031BD12497C5BBD68BEA2D0D41713CDFFDCBE462D603C54E9CA5F50DE792E1AB 1 05A917EBAA7D4B321E34F37ADC0C3212CE297E67C7D7FEC4E28AD4CE863B7516 1 16780B2C5BF22F7868BF7F442987AF1382F6465A581F6824245EFB90D4BB8B62 2 095E45DDF417D05FB10933FFC63D474548B7FFFF7888802F07FFFFFF7D07A8A8 1 0000000000000000000000000000000000000000000000000000000000000000', + u: '1 1F654067166C73E14C4600C2349F0756763653A0B66F8872D99F9642F3BD2013 1 24B074FFB3EE1E5E7A17A06F4BCB4082478224BD4711619286266B59E3110777 1 001B07BEE5A1E36C0BBC31E56E039B39BB0A1BA2F491C2F674EC5CB89150FC2F 1 0F4F1E71A11EB1215DE5A081B7651E1E22C30FCCC5566E13F0A8062DB67B9E32 2 095E45DDF417D05FB10933FFC63D474548B7FFFF7888802F07FFFFFF7D07A8A8 1 0000000000000000000000000000000000000000000000000000000000000000', + pk: '1 0A165BF9A5546F44298356622C58CA29D2C8D194402CAFCAF5944BE65239474E 1 24BA0620893059732B89897F601F37EF92F9F29B4526E094DA9DC612EB5A90CD 2 095E45DDF417D05FB10933FFC63D474548B7FFFF7888802F07FFFFFF7D07A8A8', + y: '1 020240A177435C7D5B1DBDB78A5F0A34A353447991E670BA09E69CCD03FA6800 1 1501D3C784703A097EDDE368B27B85229030C2942C4874CB913C7AAB8C3EF61A 1 109DB12EF355D8A477E353970300E8C0AC2E48793D3DC13416BFF75145BAD753 1 079C6F242737A5D97AC34CDE4FDE4BEC057A399E73E4EF87E7024048163A005F 2 095E45DDF417D05FB10933FFC63D474548B7FFFF7888802F07FFFFFF7D07A8A8 1 0000000000000000000000000000000000000000000000000000000000000000', + }, + }, + issuerId: 'did:indy:bcovrin:test:A4CYPASJYRZRt98YWrac3H', + }, + }, + }), + ], + }), + }, + }, + dependencyManager + ) + + const updateAssistant = new UpdateAssistant(agent, { + v0_1ToV0_2: { + mediationRoleUpdateStrategy: 'doNotChange', + }, + }) + + await updateAssistant.initialize() + + // Set storage after initialization. This mimics as if this wallet + // is opened as an existing wallet instead of a new wallet + storageService.records = JSON.parse(issuerRecordsString) + + expect(await updateAssistant.isUpToDate()).toBe(false) + expect(await updateAssistant.getNeededUpdates()).toEqual([ + { + fromVersion: '0.3.1', + toVersion: '0.4', + doUpdate: expect.any(Function), + }, + ]) + + await updateAssistant.update() + + expect(await updateAssistant.isUpToDate()).toBe(true) + expect(await updateAssistant.getNeededUpdates()).toEqual([]) + + // MEDIATOR_ROUTING_RECORD recipientKeys will be different every time, and is not what we're testing here + delete storageService.records.MEDIATOR_ROUTING_RECORD + expect(storageService.records).toMatchSnapshot() + + await agent.shutdown() + await agent.wallet.delete() + + uuidSpy.mockReset() + }) +}) diff --git a/packages/anoncreds/src/updates/__tests__/__fixtures__/holder-anoncreds-2-credentials-0.3.json b/packages/anoncreds/src/updates/__tests__/__fixtures__/holder-anoncreds-2-credentials-0.3.json new file mode 100644 index 0000000000..0bd19082fc --- /dev/null +++ b/packages/anoncreds/src/updates/__tests__/__fixtures__/holder-anoncreds-2-credentials-0.3.json @@ -0,0 +1,304 @@ +{ + "STORAGE_VERSION_RECORD_ID": { + "value": { + "_tags": {}, + "metadata": {}, + "id": "STORAGE_VERSION_RECORD_ID", + "createdAt": "2023-03-18T18:53:44.041Z", + "storageVersion": "0.3.1", + "updatedAt": "2023-03-18T18:53:44.041Z" + }, + "id": "STORAGE_VERSION_RECORD_ID", + "type": "StorageVersionRecord", + "tags": {} + }, + "8788182f-1397-4265-9cea-10831b55f2df": { + "value": { + "_tags": {}, + "metadata": {}, + "id": "8788182f-1397-4265-9cea-10831b55f2df", + "createdAt": "2023-03-18T18:54:00.025Z", + "associatedRecordId": "2c250bf3-da8b-46ac-999d-509e4e6daafa", + "role": "receiver", + "message": { + "@type": "https://didcomm.org/issue-credential/1.0/offer-credential", + "@id": "c5fc78be-b355-4411-86f3-3d97482b9841", + "credential_preview": { + "@type": "https://didcomm.org/issue-credential/1.0/credential-preview", + "attributes": [ + { + "mime-type": "text/plain", + "name": "name", + "value": "John" + }, + { + "mime-type": "text/plain", + "name": "age", + "value": "99" + } + ] + }, + "offers~attach": [ + { + "@id": "libindy-cred-offer-0", + "mime-type": "application/json", + "data": { + "base64": "eyJzY2hlbWFfaWQiOiJBNENZUEFTSllSWlJ0OThZV3JhYzNIOjI6VGVzdCBTY2hlbWE6NS4wIiwiY3JlZF9kZWZfaWQiOiJBNENZUEFTSllSWlJ0OThZV3JhYzNIOjM6Q0w6NzI4MjY1OlRBRyIsImtleV9jb3JyZWN0bmVzc19wcm9vZiI6eyJjIjoiODUxODAxMDMyNzEzNDg5NzYxOTg5MzAzNjMzMDkzOTEyOTExMDUxNjI0OTQ0OTYzMTgzNzM2MDY3NDkwOTc2MDYxODEwMDgxODkxMzQiLCJ4el9jYXAiOiI4NDk0NDg4MjQzNTk2NTkwOTc2MjQzMjc0NDg4ODk2Mjc1NTcyODAyMTQ1ODE5NDQzNTE0NzQxMzk1NDI1NjM5MzQwMTczMDIzMTQ5NzI3MDY5NzMzMzQwODgzMTU4MzQ1NTYzOTA5OTcxNDMzNTg1MjMwODAxNTYyMTM0NjczNjM1ODg5NTA3Njg5ODQwOTgyODU5Mzg1NjA1MTc1NTkxNDYxOTkyMDExNzU2Mzg1MTI3MTQ3ODgxNDMwODEzNjYxNzY0MDU5MzE0ODk4MTc2NzQzMTQ5MjYzMDMwMDQ1NzMwMDMzMzI2NzgyMzg1OTY0NjcxMzg1ODQ2MzcxNjQ5MzQxMTg2MDM5NjE4MTQwOTIwMDUxMzg1MDAwNTYxMDcyMTc5NTEyMzc5Nzk0OTU4NjE1ODIyODI2OTExNzIwNTQyNTE0MTQ1NDc5MTAxOTUyMzM4MDMwMDY1MDk5NjcxOTU2OTMxMzE2NjE5MjM0NTQ0NTE5NTQ1ODQ1MzA4MzgxMjQyNTM0NDcyOTc3NjY0MjAwMjc2MTMyOTgxODE1ODAzNTIxOTExMzk4ODkxMjE0NjE1NzA1MDM2ODM2ODU1NDU1NzY4ODg4MTUxNDgzODAyNDcyODQyMzczNzE0MTI0NTYwMzIyNTI3NDE4MTEwNzYyMjgyNzY4NzMyNTIzMDQyMDA3MDY2OTk2ODIxMTQwMzE1NDg0NzI4NTM2NzIwNDI3MDg5MTI2NDk1NTAzMjc0ODQ4MDM3MjUzOTM3NjI3MDU2ODUzMTQ4NjE5NDA4NDYxOTI5NzEzMjM4MjEwNDc4MjcyMTIxNTUwNjQzODc4ODM1NDYwMzY1OTIwMjE3NTk5NDYyNDUzMDMyNDQ4MjYyMTM3NjE5ODY0OTU4MzA1MDE3MjA4OTYwNDc1MTQxODgwMTMiLCJ4cl9jYXAiOltbIm5hbWUiLCI1MDcyNzU2NDE2NDA2ODIxNzU1OTc0MzUxMTg0NjE1NjA4NDY2NTk3Mzk0NzA2MTY1NDg2ODAzMjc3MjMyMzQyOTk4MDA0MzY0OTU0MTczMzc0NDIwOTc5NTkwMDcyODgxNDgxNDA0MTg2OTExODg5NzQ4MTgzMzQ1OTk5NzQ0NzgxMTQ1MTMwNzEyNDIzODY0Nzc1MzQzNjAzNTk2NDM3Mzg4OTgzNTExNDAzODA0NjEyNjU1MDE5NzQ4MTI5NDk3ODY2NTcwMDQyMjcwNDQxNDQ5MjYwODY0NzgyMzI5MjAxNDEzMTc5ODU3NzA0MjM5OTMyMTg4NTc4NzE3MDczNzM3NjUyNzY5MzY5NDg4OTgxNzg2NDQwNTExODAzMjMzNDMxNzA4NDk4MTU2NTA0OTUzNzkzNjU2NjQ2NzMyNTU4MzQwNDI2MDI1MjA3NTk0OTIwMDY4OTc2OTQ4Nzg2OTUxNzM3MDIwNDQ0NTA5NzYyMDQ2MzIzNzA0MDQ3MjU1ODU3NDE5ODE3MDc5NTI3NDgzNTE1NDY2NTAyMDkzOTY1NDMzMzk3MjQ1MzA4MjQ5MDgyMTQ4Mjc4NDA1MzI5Njg1Mjc0MDYwNjk0MzI0MTI2ODgxMjkyMDIyMjY1ODczMjk5MDU0NDU1OTA5NzkyNjUwNjAyMTk0NjUzMjYxMDk0ODYwOTc2NzA4ODE1ODgwMjExMTY0MTkwMDM0NjY0MzI2MDc3NjcwNzkyMDE4NTE2MzMzNDI3NjkwODYwMjIxODEwMzk5MDgxMjc5NjAwNTYzMjk3MjI0NjM0MDM0NjcxNTIwODE5MzU3NzQ0Njk2NzU1Njg1NDI2NjIzMzAwMjQ3MDUwODE4NTQ2MDM2NjA0NjMxNjcyNzE5MjI0NDA4NTE2NDM4NTgxMDM5Njk4NzI0MSJdLFsibWFzdGVyX3NlY3JldCIsIjU2MzYzNTgyMDQ5Mjg4OTY1OTg1MDA4NzgyMzU0NjgyNjMwNDkxMzQ3MTM1NDIxNTAyMDEyMTIwMzI4MDI4ODIyMjUyMzg4NjgwNTMwNTgxMTcwMTgwNDU1MTcyNTc3ODkyMTEyMTY1OTM0Mjk5NjUyNzAxNDExMzUyNDkzMzkyODU0ODI4NzMyMDQzMDI0MDI0MzM0MzMzNzc0NjEyOTEzOTUyMjAzNjM1NDk2MDQ0ODMzMDI5NDE2NjUwOTU5NjE0ODgzNTUwOTMxNzgzNTA5MzE1Nzg4MDEyODQ0MzAwMDQwMDE5MTY5MTc3NTI1OTgxMTU3OTkwNjQzMDcyMjQyNzcxMjU0MTYyNzMxOTU4NzI2Nzc1NjYwMjkxODIzMDcyNDk1Mzg0NzM5MTcwODc4ODMxNzkxMjQzMjEzMjU5MzA5ODQxNjU3MjUwOTg1NzMxMjEyNzE2MDM2MDY3MDUxNjM2NzA0MjA1NDEzMDk2MDU3MTA2NTM2MTI2ODUyNDU0NzcwMzQzMTMwMTczMjAwNjEzMDIxOTE4MzgzMDQxOTU4MTkwOTE2NzQ0NjU4NTI0ODA1NjM4Mzk2OTY3OTA3MzIwNjY1MDU1MzcwMjY0NjAxMDczMjc5NDMyNjM5MjM3Njc1NTA0OTg1NzQyNTI4NjYwMTAyMDEzNzIxMzA2MTE4MTg0NDk1MTEyNDQ2NDYyNDc2NTkwMjYxODkxMjA0OTQxOTA4MjMyNzMzNDA3MTg4MDA3NzE2NTA2OTUzMDY0Nzc5NDk5ODExNzI0ODI5NjcyNjY2NzIyNjIzOTAxMTc1OTk0NTIyNjkwMjk1ODI0MDgyNzY5NjQ0NDYxOTAxMDk2NzI3MTE5NzAzMjUzNzI4NjY3MTU1MzA5MDYzNDUyNDY2MDY3NzU5NzIwOTgyNDA3MiJdLFsiYWdlIiwiMTM2NTQxMjE0MjM5MTcyNDQxNzQ1MjU3MjcyMDI3MTA4NDYwMzU0MjgxMTA2OTA2MzYwNDIwMDE0NjUyMDIxMDgyNDEzODM2ODEyMjk3NjY3ODk2MTYzNDkzMjM4NDIxNDI4NjMyNTMxODE0ODk4NzkwMDg4OTg2NjgyMTE2OTAyMzc4NDgwNTE4OTUxNDExNzg1OTk3NTk5MDMyNDYxNjExNjIyMDUyNjMzMDQ5ODYxMzc5MTQzNzI4MTM5MTUyMDkyMzI0ODc3MjMxMTYwNTgzNzA5NjE0MzA1NzQ1MjA5MjQwNjU2MDU4NjY3OTMwODEzNzYyNDY5MDc2ODc5MTk1Nzg0Nzg4NTE2NjI3MjgxMDY0NjE3MzgzMDc4Njc5MTkwODIwMzQwNTgwNDY2MjU3ODU3NjA1MTc2MTg4NTI3OTMxMDI4MTMzNTY5Njc0Mzg2ODAwMTA2MDE2MDg1Nzc0OTcyMzI1NTAyNDA2MTY0OTY0MjU2OTUxNDI3ODAxMTQzNTQxMzUzMzI0Nzg0MzA5OTY4MjIyOTU1NDk4Njk3NTAwMDUwMzc0MDg0NjIwNzQ4MTk0NzIyMTI2NjE2OTY3OTY3Mzc1NTM3Nzc5NTc4NTMwMDIxODExNTA2MTIxNjcxMDUwNDgzNTM2MjA3Njc3MTg5NDQwNjEwNzk0NTcyNzI5MTgzMzAyMjM1MDkxMDg4NTU2ODc5NTg3OTE3MDMzMzQyODcyMzg2NDQ5MTQ0NzgwMDYyNjc4MzA3NzE4MzU1MjQ5MTUxNjc5MDA1MzkxNDA5NDE4OTQxMjEzNDkxMjQyMjg2NTAwODcyMzQxNDI3Nzk1MjQ1ODYzODE2MDY2NDY3NDkxOTg4OTU3MDEwNDIxNDA3NDkyMDUxOTc0NTMwNjIxOTk1ODU0ODczNTM5Mjk3MyJdXX0sIm5vbmNlIjoiNzk3NjAzMjE3NzA5MzM1MzAwMTcwODI4In0=" + } + } + ], + "~service": { + "recipientKeys": ["GVbwqUMqzxKaEVWjn1aPBfjJpYQHVejinpx8GCeEuQjW"], + "routingKeys": [], + "serviceEndpoint": "http://localhost:3000" + } + }, + "updatedAt": "2023-03-18T18:54:00.025Z" + }, + "id": "8788182f-1397-4265-9cea-10831b55f2df", + "type": "DidCommMessageRecord", + "tags": { + "role": "receiver", + "associatedRecordId": "2c250bf3-da8b-46ac-999d-509e4e6daafa", + "threadId": "c5fc78be-b355-4411-86f3-3d97482b9841", + "protocolName": "issue-credential", + "messageName": "offer-credential", + "protocolMajorVersion": "1", + "protocolMinorVersion": "0", + "messageType": "https://didcomm.org/issue-credential/1.0/offer-credential", + "messageId": "c5fc78be-b355-4411-86f3-3d97482b9841" + } + }, + "2c250bf3-da8b-46ac-999d-509e4e6daafa": { + "value": { + "metadata": { + "_internal/indyRequest": { + "master_secret_blinding_data": { + "v_prime": "6088566065720309491695644944398283228337587174153857313170975821102428665682789111613194763354086540665993822078019981371868225077833338619179176775427438467982451441607103798898879602785159234518625137830139620180247716943526165654371269235270542103763086097868993123576876140373079243750364373248313759006451117374448224809216784667062369066076812328680472952148248732117690061334364498707450807760707599232005951883007442927332478453073050250159545354197772368724822531644722135760544102661829321297308144745035201971564171469931191452967102169235498946760810509797149446495254099095221645804379785022515460071863075055785600423275733199", + "vr_prime": null + }, + "nonce": "131502096406868204437821", + "master_secret_name": "walletId28c602347-3f6e-429f-93cd-d5aa7856ef3f" + }, + "_internal/indyCredential": { + "credentialDefinitionId": "A4CYPASJYRZRt98YWrac3H:3:CL:728265:TAG", + "schemaId": "A4CYPASJYRZRt98YWrac3H:2:Test Schema:5.0" + } + }, + "credentials": [ + { + "credentialRecordType": "indy", + "credentialRecordId": "f54d231b-ef4f-4da5-adad-b10a1edaeb18" + } + ], + "id": "2c250bf3-da8b-46ac-999d-509e4e6daafa", + "createdAt": "2023-03-18T18:54:00.023Z", + "state": "done", + "threadId": "c5fc78be-b355-4411-86f3-3d97482b9841", + "protocolVersion": "v1", + "credentialAttributes": [ + { + "mime-type": "text/plain", + "name": "name", + "value": "John" + }, + { + "mime-type": "text/plain", + "name": "age", + "value": "99" + } + ], + "updatedAt": "2023-03-18T18:54:01.370Z" + }, + "id": "2c250bf3-da8b-46ac-999d-509e4e6daafa", + "type": "CredentialRecord", + "tags": { + "threadId": "c5fc78be-b355-4411-86f3-3d97482b9841", + "state": "done", + "credentialIds": ["f54d231b-ef4f-4da5-adad-b10a1edaeb18"] + } + }, + "e1e7b5cb-9489-4cb5-9edd-77aa9b3edb64": { + "value": { + "metadata": {}, + "id": "e1e7b5cb-9489-4cb5-9edd-77aa9b3edb64", + "createdAt": "2023-03-18T18:54:01.098Z", + "associatedRecordId": "2c250bf3-da8b-46ac-999d-509e4e6daafa", + "role": "sender", + "message": { + "@type": "https://didcomm.org/issue-credential/1.0/request-credential", + "@id": "2a6a3dad-8838-489b-aeea-deef649b0dc1", + "requests~attach": [ + { + "@id": "libindy-cred-request-0", + "mime-type": "application/json", + "data": { + "base64": "eyJwcm92ZXJfZGlkIjoiY3F4ZW8ybzVIWmVjYWc3VHo5aTJXcTRqejQxTml4THJjZW5HV0s2QmJKVyIsImNyZWRfZGVmX2lkIjoiQTRDWVBBU0pZUlpSdDk4WVdyYWMzSDozOkNMOjcyODI2NTpUQUciLCJibGluZGVkX21zIjp7InUiOiI3OTE4NzUwNzgzMzQxMjU3ODU3OTUzMjc2OTU5MjcxOTcyMDQwMjQxMTU1MzcyODEwOTQ2NTEwMjk5MDA1ODEyMTcwNjkwMjIzMTQ2ODU0NDk1MTI1NjQ1MTg3ODQxNzk0NjA3OTUwOTQ1OTM3MDYxMjk1ODgwMjIxMzE2NTg1MTIyNDY1Mzk1MjAwNDQ3MDIwNzAxNDA0NjEwMDc4MzkyMjA4NzkxMjk5NzYwMzM4OTIxNDMzMDc1Njk2ODU0NTY3MTIzNjYxNTYwNDMwNDE3NzQwMzc5MzA4NDQzODcyOTU1NzAwNTk1MTg2NzcxODY3MzM5NzQ5NDgzODYxNTQ2NTE2MTU4NTM5MjkyNDQzNTQ3OTg3MDUwMzE4OTAyOTI5OTgyNzMzMDk1ODk4MDIyMjg2OTk1OTQwMjkzMTg3NTg5NDgwNTgwNTAwNjM0NzAyNjQxNDM0MTgxNjIwMTU4OTU3MzUyNTE1OTA4NDE2MjI4MDQ0NDA2OTU4MTk1MDg4Mjc0ODI4Njc3OTQxMDgxOTczOTg3NjU1MDEzNDUxMzA4ODQyMjYyMzY4MTQzOTExNjIxMTE0NzYyNTk3Nzg1MDczMTM4MDg3NTQ2MDIyMzc1NjQxODQ5ODI2OTg2MjYwMDE5NTAzNzE3OTk0NjM3MzIyNDExNTgzNTY0MTQ1NjcwMTM5OTQ1MjQxOTU2Njk2MDQ3MTQzNjA4NjQ0MDM5OTg2NTYwODUyMzA1MTczMjUxMTUyMDIzODI5NjI3MzQyODM2NDI3MjkwNDQ5NTA3OTY0Nzk4MTQ2NjkzOTUxMDkwNzUwOTAyNiIsInVyIjpudWxsLCJoaWRkZW5fYXR0cmlidXRlcyI6WyJtYXN0ZXJfc2VjcmV0Il0sImNvbW1pdHRlZF9hdHRyaWJ1dGVzIjp7fX0sImJsaW5kZWRfbXNfY29ycmVjdG5lc3NfcHJvb2YiOnsiYyI6Ijk4MjIzNzMzMDcwMDk1NjM0MzU5MTE2MDEyOTgzMDcyMjc5MjcxMzk1MTg0NjA0NDcxNDQ5NzA1Nzg0MTEyNDAyODMwNzIyMTUxMzIxIiwidl9kYXNoX2NhcCI6IjU5ODA0MTY4ODAxODk1NDAzMjkwMzczMTA3NzA4MzUwODc1NjY4MDcyNDY3ODAyOTcyOTIyNjUzNDE5ODU2MjMyNTIzNDI4OTUxODQ4NjEyNDE1MjM5Nzk3Mjk5ODY2MzIxNjU5NDQ1MTM1NzQ4NzU2MDY1NjgyOTY5MjY4ODI5MTYyMDA0NjQ4NzYwMzg4NTg4NDkyNjg1NDI1MTg1MDU2OTAxOTkxMjcwMzYwMDk3MDc5NjEyNTYxMzY4NzU1OTcwMjY5MjI4MDYzMjMyODU0NzI0NDkyOTA5Njg5MDMyOTg4NjYyMjk5Mzg3MDU2NDEwNjU5MDU3MDUwNjE0MDQ2NzE1NjA0NTgyMzM2NTg4MjMxMjI3MTEzMDEzMDQxMTA0NTU2NzM1MDE3ODUwNzUzODcwNjc2ODYxNDA4MjA0NzkzMDIzNTYwMDEwNTEzODAwNzA4MjAyNjAyNjQ0Mjg2NzI4NjAyOTk5MzU5MDcwODQxMTQ5MTAzMjA5MzY0ODkyMzMzMDYwMDgzMTA5NDIzOTQ5NzE4NTk5NjEzMzk2NjIyMjc4MTExMzk5ODU0MTcyMjMyNTQzOTk1Njc5NDk3Mjk1Nzc1NzA0MjA0MTQxOTU2NDI1MDc4NjYzMzgwMDA1Nzc2ODY2MTcxNTY4OTU1NjE4NjAwMTA2NzkxMjIyNDkyODA2NzI1ODU1NDY2Nzk4OTEzMTc2NDcxMDY3MTk5ODQ2ODEwNDI5MDIzMDc3ODI3OTc1OTIzMDIzNjU3MTg0NjkwNzE0MjkxNDk0MDc5MTM1NzYyOTUxNTc0MjMzNjMwMjExNDQ1Njc3NzE1Mzg3Mzc1NjkyMjAzODE3NDczNDk5NDExNzE5MzIwMjczNzExOTIzMzM3MzYzMTAyOTkwMDcyMjE2MjYzMzUxMzMxNTk4ODk1OTU3MzU1MDc1NTEzODE0NTUwNzkyMCIsIm1fY2FwcyI6eyJtYXN0ZXJfc2VjcmV0IjoiNTY0MTY1NjA2NjM0MTUwODgzMjg2MjM1NDM1MjUyODQ3MjAxMTk4NTQyNDAxMjYzNTY2MDQ2MTA3OTU3NzI4NTQ0MTMwMjgzNjUyNjQzOTI0NDc2NTU2NTIzNzg0ODI1OTgyMzMwMzc4NDI4OTM0MDkxNDcwNDM0OTAwMTM3NzkwNDkxMjM4NTA4ODk2ODQxMzkwNjQ4MTA1NzY5ODYzMzI1OTAzODI1Mjk1MjU2OTQ0NSJ9LCJyX2NhcHMiOnt9fSwibm9uY2UiOiIxMzE1MDIwOTY0MDY4NjgyMDQ0Mzc4MjEifQ==" + } + } + ], + "~thread": { + "thid": "c5fc78be-b355-4411-86f3-3d97482b9841" + }, + "~service": { + "recipientKeys": ["cqxeo2o5HZecag7Tz9i2Wq4jz41NixLrcenGWK6BbJW"], + "routingKeys": [], + "serviceEndpoint": "http://localhost:3001" + } + }, + "updatedAt": "2023-03-18T18:54:01.099Z" + }, + "id": "e1e7b5cb-9489-4cb5-9edd-77aa9b3edb64", + "type": "DidCommMessageRecord", + "tags": { + "role": "sender", + "associatedRecordId": "2c250bf3-da8b-46ac-999d-509e4e6daafa", + "threadId": "c5fc78be-b355-4411-86f3-3d97482b9841", + "protocolName": "issue-credential", + "messageName": "request-credential", + "protocolMajorVersion": "1", + "protocolMinorVersion": "0", + "messageType": "https://didcomm.org/issue-credential/1.0/request-credential", + "messageId": "2a6a3dad-8838-489b-aeea-deef649b0dc1" + } + }, + "669093c0-b1f6-437a-b285-9cef598bb748": { + "value": { + "_tags": {}, + "metadata": {}, + "id": "669093c0-b1f6-437a-b285-9cef598bb748", + "createdAt": "2023-03-18T18:54:01.134Z", + "associatedRecordId": "93a327ad-2bf3-4ec4-b01c-bdd58ba2f6e3", + "role": "receiver", + "message": { + "@type": "https://didcomm.org/issue-credential/2.0/offer-credential", + "@id": "4d2c80b7-4a25-42ac-b8cf-a68b1374b9b7", + "formats": [ + { + "attach_id": "8430ddb8-b0c3-4074-8ded-f4dcfe80303d", + "format": "hlindy/cred-abstract@v2.0" + } + ], + "credential_preview": { + "@type": "https://didcomm.org/issue-credential/2.0/credential-preview", + "attributes": [ + { + "mime-type": "text/plain", + "name": "name", + "value": "John" + }, + { + "mime-type": "text/plain", + "name": "age", + "value": "99" + }, + { + "mime-type": "text/plain", + "name": "height", + "value": "180" + } + ] + }, + "offers~attach": [ + { + "@id": "8430ddb8-b0c3-4074-8ded-f4dcfe80303d", + "mime-type": "application/json", + "data": { + "base64": "eyJzY2hlbWFfaWQiOiJBNENZUEFTSllSWlJ0OThZV3JhYzNIOjI6QW5vdGhlclNjaGVtYTo1LjEyIiwiY3JlZF9kZWZfaWQiOiJBNENZUEFTSllSWlJ0OThZV3JhYzNIOjM6Q0w6NzI4MjY2OlRBRzIyMjIiLCJrZXlfY29ycmVjdG5lc3NfcHJvb2YiOnsiYyI6IjQwMTQ0MTA0NDg3MjM0NDU2MTc1NzYwMDc1NzMxNjUyNjg1MTk0MjE5MTk5NDk3NDczNTM4NjU4ODM3OTIyODMzNTEzMDg0Nzk2MDQ5IiwieHpfY2FwIjoiMzgxOTQyMjM1Mzc3MzYwODEyNjY0MDA4MjYzNDQxMDg2MDMwOTMyMjAxNzgzMjM3ODQxODQ5NDg3ODk2ODg1MTYwODY2MTY1MDM3NzI2MTIxNjU0MjcwOTg5NDY3NjAzNDExOTAzODk4MzUwMDAzNDIwODg3MzI4NTUwMTY2MTI1ODMyMjAxOTQzMTkwNzAxMDU4NTAwMDE5ODM1NjA1ODczNDYzOTkwODg3NzQ0NjY3MzU0MjM2Njc3MzcyODg0ODQyNjE5NTEwMTUwOTA2MjI1OTMzMjc1ODEyNjg2NDg3NTg5NjY3ODI3MjAwODcwOTQ0OTIyMjk5MzI3OTI4MDQ1MTk1OTIwMDI3NTc0MDQwNDA4ODU5MzAwMzY1MDYwODc3Nzg2ODkwOTE1MDU5NTA2ODc1OTI0NzE2OTI1MDM2MTc4Njg2NDE5NTYyMzcwODI4MTMzODY2Nzg3NzkyMDcwNjAyNDQzNTkzMTk2NzEzNzcyNDM2NTYzODI0MzkwMDIyNzg4MjU2MzA4NjU4OTc0OTEzMTk1ODYxODUwMTQ3ODE1Mjg5NzQwOTA4NDk1MjQ3NTAyNjYyNDc3NzQ2NTI5ODA3Mzg0OTgxODI5MDc3NTQ4OTI2NzExMDkzNzQ5MjM1ODU4NjUwNDc5NzE5NDI4MzUwMzAwNzUyNjQ0OTg1MTQ5MTMxNjA1NjUzMDIxMDYxNzkwMjY3MzQyNTY4NTkyNTY2MTQ0MDM5NzY4OTg0NTMyNDMzNzk0MzUzNjQ2Nzg1MjA3NDgzOTk2ODQ0OTcxNTgzNzY3NDQ5ODYyODgxMjMxMjI1MzM4MzAzMTQ4NzA0ODczMDEzNDM3MDgyNzY1MTk4OTY2MzE5NTM0OTkyNjk4MzMzMDQ0MDI3MjIyNTYyNTIzNzk3ODk5Mjk2MTQ1NDU5IiwieHJfY2FwIjpbWyJtYXN0ZXJfc2VjcmV0IiwiOTE5MTc5NzQ4MTE5NTg5MTY3Njc5MjQxODk5NzY0ODIwNTk0MDc5OTQxNzIzOTgzOTYyNzQ1MTczODM0NDQxMDQ3MjU4MDcyOTE4OTUzNjIzOTQ4MDMyNzI2NDgyNzI2MTEwOTk2Mjk3MDU3NTYwNjcwNzAxOTU1MTkxNDc0NjM0MzQ0ODMxMzg3NTk4NzI2MzMxMjc0NjI4NDU3Njk5NzczMDA1NDMwMDIxNzMwMzg4MzcwMTEyMjc3MzI2MzU4OTgwMTA3ODIzNzUzODc3MTU0NjIwMDkzMjE5MjYyNjAxNDM2NzMyNTgzNDI4Nzc4NDA4OTc0NTQyNzkzMDk0NTQ5MTczOTA3MzQ3OTUxNTc1NjM5NzU2NDg5MTA0Mzk0MTY3NzExMzY1MjM3OTI1MjAwNjk4OTg5NTI5MTQ3OTIzNTYzNDMyODgyMzgwMTg0NzU0NzkzODMwMTE3MTQ1MDAwMTI0NDYxNjkzOTcxMDQ5MjgzNDk1NTE4MDQxMDc5ODUyMzAwMjk0NDM1MjYzOTIwNDU0NTU3MzUxNDQ1MDM3NDI4MDg3OTk2Mzg2NjY3NjU3Nzk5OTYyNzQzNzIyNzA3NzczOTEzMzc0NzIxODUyNTQ3MjkwMTY5MjI5NTAzMTQxOTMwODYzNTk4NTExNjc4NDEyMDE0MzE2MDM2MzYxMzczNDcwOTQwMDEyODcwMDgwMDA2MzE0NzYxNzYzNzUyNzYwODk5MTQ3NzA1MTA0NzQyNjAxNjkxMzMxNjkzMDIwMjg2MjA2NzQ2NzE0MzI3NjU2MjA2NTMzMjk3NDg4MjU2NTM2NTQ3MzY4MjM2OTQ2MDM5NzAzMzc0OTMzNTE0NTc2NDg2NjQyNTY4MjgyNTY2MjMyNDU1NTU5MDY4MzE3NzU5NDM0ODU4NTI3MDg2NjQ0Il0sWyJoZWlnaHQiLCI5MjMwMzkyNDc1NjI4ODc1MjA4OTM0NjM0NzE4MjYzNzA4MDIzOTI1MDU0NjY2NDgzMzgxMzIyMzc3MDg1MjMxMjU4MTM4MzgwOTU1NTk3NDQxNTEyOTYwNDA2MjI3MjUwODgyNjA3NjExMDkwODk3MTM1NDcxNzAwMDIzNDcwOTM2ODg4MDE3NDY5Nzk0ODYzNDk4NzUyNTI3Njc3MjMwMTEwNzg0ODQzNzI0NDUyNTUzODYyOTA2MzM5MDc0OTIzNDU4NTQ3NDYzODcwNzU3OTg5MzMxNzk4OTI2MjM4MjUxMTM2NTYzNjM2MjIyOTQwNDkwMzY3MjQ2OTg0OTU2NTE5MTAzODcwNDE0MDM5NzM2MDE2MDY5MzA2NjQ0NjQzODI4OTgxMTE3OTM3NzYyNDAzODY1Mjc1MDU5MjEyOTY2NzIxOTU3MzM0MTM2ODEyMDI0OTE0MzA4MzAxMzk5MzM4NzMyOTIzNTA0MjA5MDM5ODMxMTc5NjU1NTkyNjg0MjMyMTIzMTI2Mjc4ODQzNDMyOTUwMTk1Mjg3MzE4ODI3NTM2MTMwNDQ3NzM3MTgwMjk3MDE0ODEzNDg3NDQyOTg2NjQ1NzQyNjEyMzE5NzQxNDY2MDMyNTg5OTU0NzYwNjE4MDU0MDUxMjAzMTE1NTAxNDcxNDExMzg3NzU0NDk5MzAwNTU4MTc5NjM5NDAxOTM0NTAzMTMyMDEzMjAzOTg2NzkyMTEzMDAzNTkwODg1NTc3NjgyMzU2NDY3MjA5NTUwNjQxODQxMDYyNTkzNDYyODIwODg3NzgxNDYyODM3ODkzODcxNDM4MzM3Mjc5MTcwMTExMTQ5MTU4NDMzNDE0ODI1NTkyNjcyODU2MzM5OTM4NTgyODg2NzM3OTIwMjc1MzI0MjEwMTUzMjE5MjI2OTYiXSxbImFnZSIsIjkxNTg1ODk3NDkwNzE0ODA3OTY2MDYzOTg5MjE1NTMxNDkyOTQwMDI5NDcyMTM4MjgwNjcxNjcyMjQ0NjY5MDc5NzIyNTQyMDU0NTU3NjY0MTcxMDI1NzM1NjQ4NTIwMTM4ODQ4ODAxNzIyMTc4MTcxMTA5NTc0MTMyNTExMzM1MDEwNTc5NzExMzcyODM5MjI3MDExOTg4MTUyMTEwMzI4MTE5MjkyMjI4NjM3MDU4MDQ3NzYwODYwOTQ0NTY3MzQxMjY4MTY4Mjk3NjE5MDM2ODEwMjYwODM2NDI1NDkwMzU3NjE4NzM4NTYxNTY2MTUxODQ3MzIxNzM1MjQ5ODk1MDU5NTY2OTQxODI5MjE0Nzc0MTA0NzYyNTQwMjcyMjk2NjE1NTE3NjUwMDcyNDQyMTI0NjY5MDEzMTc1ODAyMDk5MDQxMzk3MzE5ODQ0OTA2MDgwOTYxNTcyMTcwNjg2NzgzNDM1Mjg2MDUyMzE5ODY3ODExMDE5MjAxMDYwODM2OTM3Mzc0MzM0NDM5MTQxMDAzMTI3NTcyNjgzNTgwODI0OTkwOTg3MjE5MzU4NzkzOTM2NTU4Nzk3MjI0MDQzNTM1ODA5NzMyNzgxMjE1NzEwNjI1MjQzODYwNTk4MTk0MjU2MjAwODkwOTA3ODAzMDcyMTAzNzc3MzkwODk4MDczOTgyNjY3Njc1ODg0MjI3MjU0Mzc2OTI5Mjg3ODQyNDE0MTE0MjcwNDQwMTEzNDUxNjk4NzE5Nzc5NjQyNTI4MDA4NDM3Mzk5NjI0NTE3OTM4Nzg5MDc3ODE5ODA0MDY5MzcxOTM0NzExMTIyNTQyODU0OTg4MDA0Mjc4NDkwMjAxNTk2NjE0MjUwODc3NDYxMDczNjc3NTUzNzYxMTMyMTA5Nzg3NTQ2ODE1ODk5Njc2NCJdLFsibmFtZSIsIjYyNzgwNTIwMTM3MzI3NTUzMDc3MDg4NTE4NDg1NDYyMTA0NjEzMjEyNzY3ODUwMzYwNTc3NDQ4MDUxNTk5MTMxMTM1NTI2NzQ3Nzc2NzMzMDg1MDMwODcyMDE1OTM2MTI2NzE0MTIxMDgxMzg3ODU2MTkwMTkzMzI3ODY3OTE0NTEzODM2NTQ1OTY4Mjg1NTc5ODEyODMxMDI4ODc2Nzg1NzI3OTQ2MTEwNzg5Mzc0MjcyODgzMzkyOTgwNDkwODk3NDkwMTc5MDQ0ODM0NTgwMzQ2ODY4NDI2ODc0ODU4NTY1OTg4NTUyMDcwNjI1NDczNjM4MDM3Njc5NTU1NTk2MzE5MTc3Nzc5OTcxMTIxMjQzMjgyMTIyOTQ2NjY0ODMxOTgxMTg3MzQ3MzcyMjkxMjYwOTM3MzkzNDA1ODk5OTI0NjM4MzE3ODI5MDczODMxMjI4ODc1Njg5MTcyMTg4NjIyMDI5NzcxNzM5MTQ5NDY2Mzg3NTM5NjkyNDQ5NDU5MjczNDI5NzM5MjMzNjkyNTEzNDA5OTkyNDgxNTQ4ODk0NjAzNjM3MTYzNjA4MTM0MTAzMTk3Nzc3NTM4OTYwMDcyMjcyMzYyNzM4NDM1MTM3MDcyNzIxMjExMDYxNTg4MDE3ODczODg3MTEwNDA2OTk1NDQ4ODIwMDEzMDA5MjgyMzk0OTczMDMwMDI5MTY3NjQ5NzY1OTI1MTUxMzY4NTg5OTkyNzMyMDE1ODAwNjAzNzYxOTI3MTg3MDM4MDkxNDY3MDE1MjA3MzIwNDczMDM0NDA3MDIyNDA0NjQ4MTI0NTk2NjQwNjU1NjY1MTIzMzY5Njc0ODI2NDE3MjE2ODUxNTM4Njc1NTM3NzAwOTg4MTQzNzE1NTE3NzMwMTM4NjA4NzkxMjcyMzM0MDUyMzY4OCJdXX0sIm5vbmNlIjoiNDE0MzQ4Njg0NDk2OTAxNjkyMjI2OTY0In0=" + } + } + ], + "~thread": { + "thid": "f9f79a46-a4d8-4ee7-9745-1b9cdf03676b" + }, + "~service": { + "recipientKeys": ["DXubCT3ahg6N7aASVFVei1GNUTecne8m3iRWjVNiAw31"], + "routingKeys": [], + "serviceEndpoint": "http://localhost:3000" + } + }, + "updatedAt": "2023-03-18T18:54:01.134Z" + }, + "id": "669093c0-b1f6-437a-b285-9cef598bb748", + "type": "DidCommMessageRecord", + "tags": { + "role": "receiver", + "associatedRecordId": "93a327ad-2bf3-4ec4-b01c-bdd58ba2f6e3", + "threadId": "f9f79a46-a4d8-4ee7-9745-1b9cdf03676b", + "protocolName": "issue-credential", + "messageName": "offer-credential", + "protocolMajorVersion": "2", + "protocolMinorVersion": "0", + "messageType": "https://didcomm.org/issue-credential/2.0/offer-credential", + "messageId": "4d2c80b7-4a25-42ac-b8cf-a68b1374b9b7" + } + }, + "93a327ad-2bf3-4ec4-b01c-bdd58ba2f6e3": { + "value": { + "_tags": {}, + "metadata": {}, + "credentials": [], + "id": "93a327ad-2bf3-4ec4-b01c-bdd58ba2f6e3", + "createdAt": "2023-03-18T18:54:01.133Z", + "state": "offer-received", + "threadId": "f9f79a46-a4d8-4ee7-9745-1b9cdf03676b", + "protocolVersion": "v2", + "updatedAt": "2023-03-18T18:54:01.136Z" + }, + "id": "93a327ad-2bf3-4ec4-b01c-bdd58ba2f6e3", + "type": "CredentialRecord", + "tags": { + "threadId": "f9f79a46-a4d8-4ee7-9745-1b9cdf03676b", + "state": "offer-received", + "credentialIds": [] + } + }, + "b71d455b-9437-4ef8-b4f3-b6a0dd6bbfb3": { + "value": { + "_tags": {}, + "metadata": {}, + "id": "b71d455b-9437-4ef8-b4f3-b6a0dd6bbfb3", + "createdAt": "2023-03-18T18:54:01.369Z", + "associatedRecordId": "2c250bf3-da8b-46ac-999d-509e4e6daafa", + "role": "receiver", + "message": { + "@type": "https://didcomm.org/issue-credential/1.0/issue-credential", + "@id": "578fc144-1e01-418c-b564-1523eb1e95b8", + "credentials~attach": [ + { + "@id": "libindy-cred-0", + "mime-type": "application/json", + "data": { + "base64": "eyJzY2hlbWFfaWQiOiJBNENZUEFTSllSWlJ0OThZV3JhYzNIOjI6VGVzdCBTY2hlbWE6NS4wIiwiY3JlZF9kZWZfaWQiOiJBNENZUEFTSllSWlJ0OThZV3JhYzNIOjM6Q0w6NzI4MjY1OlRBRyIsInJldl9yZWdfaWQiOm51bGwsInZhbHVlcyI6eyJhZ2UiOnsicmF3IjoiOTkiLCJlbmNvZGVkIjoiOTkifSwibmFtZSI6eyJyYXciOiJKb2huIiwiZW5jb2RlZCI6Ijc2MzU1NzEzOTAzNTYxODY1ODY2NzQxMjkyOTg4NzQ2MTkxOTcyNTIzMDE1MDk4Nzg5NDU4MjQwMDc3NDc4ODI2NTEzMTE0NzQzMjU4In19LCJzaWduYXR1cmUiOnsicF9jcmVkZW50aWFsIjp7Im1fMiI6IjMyMTIwMDQ1ODc4MzIxMjcyMzA1ODI5MTc3NzMzMTIwNzE1OTY5NDEyNjkwNjUyNDQ4OTc0MTA4NzEzNjU0ODc3NTg2MzIzMTI3ODk2IiwiYSI6IjIyMjY0MTYwNjIwODcwNDUyNTExMjcyMzE1MzMzMDA0MjQzMzY3NTM2NzM3NDMwNjM1NjExMjcwMDkwOTE4NDMwNzc0ODEzMjAzNjQwNjMxMjIyNDczMzk0MjQ4MTgzMDIzMjIyNzExNDUwMzQxMDcxOTQyNDQwMDgwMjY2Nzk1Mzg5Mzg5Njc1NjYwOTUzNTQyMDE4OTA3NjQ3NzI4OTQ4NjY1MzA2Njg0NjExNDU1NTI5NzM5OTY1NDcyMjQ2NDQxMzE1NzAxMzM1ODc1MDY3MjExMDk3NzcyOTgwMjU1NDIxMDMzMTI1MjAyMTQzNDk3NjMyOTAyMjM1NDAyMzU5OTA1MzY5MzE4MjI1NTc4MjUxNjY4NTYzNzc1NTY0MDM2MjUxNzE0Mzk3MTEzNjQ3OTg0MjcxMTE5MTU2NDQ3NjI1OTk1NjE5MjAwMDk4MTgzNzY1NjkzMTg1ODEzNjA1NDU3OTQwMzE0MDU2MDkzMTI2MzQ3OTU5MzYwODIyMzg0OTEzODg3Mjg3ODI2NjkyNDIyNDMyNDUwMDA5OTYxNjQ2MjMzNTE3MjY3NDU1OTkyMjA3MTE3Mzk5NzU1NjY3MTA3MzM1NTQ0MzEwNDQwNDE1NDE5NTk5NTA1OTgxMzkwMjk5NDUxNzQyODg4NDg0MTc0NTU5MDA5NDgwNjU5MDk2Nzg2ODI2MDgxNzc3MzcwNTk1MTU3OTg5NjQ1MDYxNDI2OTA2ODM2MDk5NTU5MDQ0MDI4ODM2MzYwOTM2MDkwOTkxNjA1OTU0NTM2OTQxMjgxODQwNzk2MjkxODc0ODk2NDEzNTM5IiwiZSI6IjI1OTM0NDcyMzA1NTA2MjA1OTkwNzAyNTQ5MTQ4MDY5NzU3MTkzODI3Nzg4OTUxNTE1MjMwNjI0OTcyODU4MzEwNTY2NTgwMDcxMzMwNjc1OTE0OTk4MTY5MDU1OTE5Mzk4NzE0MzAxMjM2NzkxMzIwNjI5OTMyMzg5OTY5Njk0MjIxMzIzNTk1Njc0MjkzMDExMzE1MTE0OTUxODg0MDQ5ODkyNjAwNTY3NTgzMTc4ODkwMyIsInYiOiI2NTE5ODc4MzYyODQ5NzExNDY4NDkyNDM1OTM3MDU4NzMzOTYxMTkxNjA3NjI4MzUzNjkxMDg1MzM5MDMwNDU0OTkyODc0ODYyODkyNDg4ODYzNTA3MDQ1MTM1MzA4ODI1NDA2NzYwMTQwNDQzNzM0NDYzODE5NTM2MzE0NzcxMTQ3MDk4MjU2ODMzNTc2MjIwNDI5ODQyNjc3NzMwMzQwODYwNjE2NTcxNzc5NjU4OTIxNDY4Mjc0NTUwOTc5NjYyMDkxNzEwNDU5MDk2MDgzMzYzNTc1Mjc0MjQzNzIyMzIzOTIxMjY5MDYyMjE0NjQyNzQyMTI0MzQ4MTY0MDUxNzE3MTk5MTkzODY3NTM3NTEzNjYzOTY1ODQzMDI5MjAxODA0OTE2MTEzNzMxODYzOTUzNjQ5MDkwNDgzNzMyMTkxNTQ2MTEwMjAxNTg0NzMxODg4NTE5NjA2MjE1OTkyNTgxNzk2MDg2NzUzOTE5NzUxMjkwMDI3MDI4NzczMTAwOTc5ODI5MzQ5NzA0MTUyMDEzNjg2MzU1MzM1MjIyNjU5MDY2NzE0NDQ2NDc4NzY3MTE5NDE4MjY3OTg5NTAyNzc4MjMzNzM3MjM4MjU1MTQxNzQyMjk4NTU3MDY2NzA2MTM0NzYwMjQwMzY3OTMzMzc5NzYzMTc5MTI1NTI4MDQwMzkxNjQwNTIyNTM5NjE5NTU0NTE0NTk4OTUxNTg0NjA3MjYwNzk1NzE1MDMyMjM4NTQ3ODMyMzA0NTY2MzQ4NjYzMTc0NzQwMDE2MzQ2NTU2MTM1ODc4MzgxNTYzODQ2NzU0MzQzMjk0NTIzNjc0NDI3NjQxNjAxNjAwNjE2NzI3NjEyMzc0MzI2NzY4ODA5NjAyNTE5MTAzOTk3NDY4OTg1NTg3Nzg4MjI3Njc5MzQ4NTgwNzk1OTkyOTkxNzMzMDg5MTUyMTg2MDg4OTU2MTg2MTQ0OTkyMDI5OTI2OTUxOTU0OTQyNjYwMDUxOTM0MDc5NzkxODI1NzA2MTExNzg0MDU2NDM2OTA2MDgxMDQ2MDQ5ODI0ODE1NDE0MTc5NTMzMDA2ODE4NzQ3NzgwNTQ5In0sInJfY3JlZGVudGlhbCI6bnVsbH0sInNpZ25hdHVyZV9jb3JyZWN0bmVzc19wcm9vZiI6eyJzZSI6IjEwMTUyMDI0OTk1MTUxMzcyOTgwNzI5NDM1MTQ5MzgyMzQxMDQ3MDIzNjI2NjA4MDc3ODg1NzQ2Mjg4MTE3MjI2OTAyNzM4OTY5OTEwMTU0NjgzNzM2MzI4MzQ2MDg0MjcwMDA3MTY2NTg3ODI5MjE2MzEzNDc4MDk3Njc0MTI0ODU2ODIyMjA4NzI0Nzk1NTE0ODgzOTYwMzE5NDc5OTg4NzAzNDUyNjI4NjYxMDc3MTg3OTIyMzA1NDc5MDE2NzQzOTk0NzYwMzE5NzI1OTExODk0MjM2NDMxMDkxMTIyNTUxNTU0NzgwODg0NjQ2MjE0MTUzMDUzMTM2NDMwMTk4OTA5MTM0OTk4OTM2NjY3MzI4ODI2MDcwNzEzMzk0NDg0NDI0ODUxNjkxMzUxNDc0NjAxMjIwODk2NTIyMDYzNDA5NzA4NDA1Njk2MzY5MjA0MzU0NzE1MDkxMzk2Mzc4Mzc3MzA0ODk3MzMwOTM0Mjc2NTQyNjE2NjAxNTk1ODI5NzgxOTg3NTMyNzkxMzIyNTgzOTE1Njk1OTY2MjM3MTc4Njg1NTMzNTE3MTQxNTAyNDE3MzQxMDIzMTA1MTczMjMwMTcwNzUzODYwMjgxNDAxODk4MDE5OTQwNjA2MzczOTYwMzYxNjA3NTE2NjgyMDg4MTc1NzU4ODA0Mzg4MTM5MTQ0MDkwMjg5MzI5NzMzNTQ1NDg4MjUyNjczNDIyODkzMzc1MzE5ODQ2OTMwOTIyNjIwNzAzMTEwMDgwODU5OTE4ODQ0MzgyOTQ3ODczMjAwNzA4MTY2MzA0NDk4ODk0MDA4NTMyIiwiYyI6IjIyNDQyNTM5MzYwMzYzNjQyODI1ODkxNTc5ODgzMDE5Mjc3Mjk0NTQ2MjUwMDEzNTM3MzI2OTY2NzM3MzE0NTUxMjEwMjU3MjU2NDU5In0sInJldl9yZWciOm51bGwsIndpdG5lc3MiOm51bGx9" + } + } + ], + "~thread": { + "thid": "c5fc78be-b355-4411-86f3-3d97482b9841" + }, + "~please_ack": { + "on": ["RECEIPT"] + }, + "~service": { + "recipientKeys": ["GVbwqUMqzxKaEVWjn1aPBfjJpYQHVejinpx8GCeEuQjW"], + "routingKeys": [], + "serviceEndpoint": "http://localhost:3000" + } + }, + "updatedAt": "2023-03-18T18:54:01.369Z" + }, + "id": "b71d455b-9437-4ef8-b4f3-b6a0dd6bbfb3", + "type": "DidCommMessageRecord", + "tags": { + "role": "receiver", + "associatedRecordId": "2c250bf3-da8b-46ac-999d-509e4e6daafa", + "threadId": "c5fc78be-b355-4411-86f3-3d97482b9841", + "protocolName": "issue-credential", + "messageName": "issue-credential", + "protocolMajorVersion": "1", + "protocolMinorVersion": "0", + "messageType": "https://didcomm.org/issue-credential/1.0/issue-credential", + "messageId": "578fc144-1e01-418c-b564-1523eb1e95b8" + } + } +} diff --git a/packages/anoncreds/src/updates/__tests__/__fixtures__/issuer-anoncreds-2-schema-credential-definition-credentials-0.3.json b/packages/anoncreds/src/updates/__tests__/__fixtures__/issuer-anoncreds-2-schema-credential-definition-credentials-0.3.json new file mode 100644 index 0000000000..b4bd2e2d08 --- /dev/null +++ b/packages/anoncreds/src/updates/__tests__/__fixtures__/issuer-anoncreds-2-schema-credential-definition-credentials-0.3.json @@ -0,0 +1,431 @@ +{ + "STORAGE_VERSION_RECORD_ID": { + "value": { + "_tags": {}, + "metadata": {}, + "id": "STORAGE_VERSION_RECORD_ID", + "createdAt": "2023-03-18T18:53:43.140Z", + "storageVersion": "0.3.1", + "updatedAt": "2023-03-18T18:53:43.140Z" + }, + "id": "STORAGE_VERSION_RECORD_ID", + "type": "StorageVersionRecord", + "tags": {} + }, + "fcdba9cd-3132-4e46-9677-f78c5a146cf0": { + "value": { + "_tags": {}, + "metadata": {}, + "id": "fcdba9cd-3132-4e46-9677-f78c5a146cf0", + "schema": { + "ver": "1.0", + "id": "did:indy:bcovrin:test:A4CYPASJYRZRt98YWrac3H/anoncreds/v0/SCHEMA/Test Schema/5.0", + "name": "Test Schema", + "version": "5.0", + "attrNames": ["name", "age"], + "seqNo": 728265 + }, + "updatedAt": "2023-03-18T18:53:45.521Z" + }, + "id": "fcdba9cd-3132-4e46-9677-f78c5a146cf0", + "type": "AnonCredsSchemaRecord", + "tags": { + "schemaId": "did:indy:bcovrin:test:A4CYPASJYRZRt98YWrac3H/anoncreds/v0/SCHEMA/Test Schema/5.0", + "schemaIssuerDid": "did", + "schemaName": "Test Schema", + "schemaVersion": "5.0" + } + }, + "de4c170b-b277-4220-b9dc-7e645ff4f041": { + "value": { + "_tags": {}, + "metadata": {}, + "id": "de4c170b-b277-4220-b9dc-7e645ff4f041", + "schema": { + "ver": "1.0", + "id": "did:indy:bcovrin:test:A4CYPASJYRZRt98YWrac3H/anoncreds/v0/SCHEMA/AnotherSchema/5.12", + "name": "AnotherSchema", + "version": "5.12", + "attrNames": ["name", "height", "age"], + "seqNo": 728266 + }, + "updatedAt": "2023-03-18T18:53:48.938Z" + }, + "id": "de4c170b-b277-4220-b9dc-7e645ff4f041", + "type": "AnonCredsSchemaRecord", + "tags": { + "schemaId": "did:indy:bcovrin:test:A4CYPASJYRZRt98YWrac3H/anoncreds/v0/SCHEMA/AnotherSchema/5.12", + "schemaIssuerDid": "did", + "schemaName": "AnotherSchema", + "schemaVersion": "5.12" + } + }, + "6ef35f59-a732-42f0-9c5e-4540cd3a672f": { + "value": { + "_tags": {}, + "metadata": {}, + "id": "6ef35f59-a732-42f0-9c5e-4540cd3a672f", + "credentialDefinition": { + "ver": "1.0", + "id": "did:indy:bcovrin:test:A4CYPASJYRZRt98YWrac3H/anoncreds/v0/CLAIM_DEF/728265/TAG", + "schemaId": "728265", + "type": "CL", + "tag": "TAG", + "value": { + "primary": { + "n": "92212366077388130017820454980772482128748816766820141476572599854614095851660955000471493059368591899172871902601780138917819366396362308478329294184309858890996528496805316851980442998603067852135492500241351106196875782591605768921500179261268030423733287913264566336690041275292095018304899931956463465418485815424864260174164039300668997079647515281912887296402163314193409758676035183692610399804909476026418386307889108672419432084350222061008099663029495600327790438170442656903258282723208685959709427842790363181237326817713760262728130215152068903053780106153722598661062532884431955981726066921637468626277", + "s": "51390585781167888666038495435187170763184923351566453067945476469346756595806461020566734704158200027078692575370502193819960413516290740555746465017482403889478846290536023708403164732218491843776868132606601025003681747438312581577370961516850128243993069117644352618102176047630881347535103984514944899145266563740618494984195198066875837169587608421653434298405108448043919659694417868161307274719186874014050768478275366248108923366328095899343801270111152240906954275776825865228792303252410200003812030838965966766135547588341334766187306815530098180130152857685278588510653805870629396608258594629734808653690", + "r": { + "master_secret": "61760181601132349837705650289020474131050187135887129471275844481815813236212130783118399756778708344638568886652376797607377320325668612002653752234977886335615451602379984880071434500085608574636210148262041392898193694256008614118948399335181637372037261847305940365423773073896368876304671332779131812342778821167205383614143093932646167069176375555949468490333033638790088487176980785886865670928635382374747549737473235069853277820515331625504955674335885563904945632728269515723913822149934246500994026445014344664596837782532383727917670585587931554459150014400148586199456993200824425072825041491149065115358", + "name": "26931653629593338073547610164492146524581067674323312766422801723649824593245481234130445257275008372300577748467390938672361842062002005882497002927312107798057743381013725196864084323240188855871993429346248168719358184490582297236588103100736704037766893167139178159330117766371806271005063205199099350905918805615139883380562348264630567225617537443345104841331985857206740142310735949731954114795552226430346325242557801443933408634628778255674180716568613268278944764455783252702248656985033565125477742417595184280107251126994232013125430027211388949790163391384834400043466265407965987657397646084753620067162", + "age": "12830581846716232289919923091802380953776468678758115385731032778424701987000173171859986490394782070339145726689704906636521504338663443469452098276346339448054923530423862972901740020260863939784049655599141309168321131841197392728580317478651190091260391159517458959241170623799027865010022955890184958710784660242539198197998462816406524943537217991903198815091955260278449922637325465043293444707204707128649276474679898162587929569212222042385297095967670138838722149998051089657830225229881876437390119475653879155105350339634203813849831587911926503279160004910687478611349149984784835918594248713746244647783" + }, + "rctxt": "49138795132156579347604024288478735151511429635862925688354411685205551763173458098934068417340097826251030547752551543780926866551808708614689637810970695962341030571486307177314332719168625736959985286432056963760600243473038903885347227651607234887915878119362501367507071709125019506105125043394599512754034429977523734855754182754166158276654375145600716372728023694171066421047665189687655246390105632221713801254689564447819382923248801463300558408016868673087319876644152902663657524012266707505607127264589517707325298805787788577090696580253467312664036297509153665682462337661380935241888630672980409135218", + "z": "60039858321231958911193979301402644724013798961769784342413248136534681852773598059805490735235936787666273383388316713664379360735859198156203333524277752965063504355175962212112042368638829236003950022345790744597825843498279654720032726822247321101635671237626308268641767351508666548662103083107416168951088459343716911392807952489009684909391952363633692353090657169830487309162716174148340837088238136793727262599036868196525437496909391247737814314203700293659965465494637540937762691328712617352605531361117679740841379808332881579693119257467828678864789270752346248637901288389165259844857126172669320275054" + } + } + }, + "updatedAt": "2023-03-18T18:53:55.036Z" + }, + "id": "6ef35f59-a732-42f0-9c5e-4540cd3a672f", + "type": "AnonCredsCredentialDefinitionRecord", + "tags": { + "credentialDefinitionId": "did:indy:bcovrin:test:A4CYPASJYRZRt98YWrac3H/anoncreds/v0/CLAIM_DEF/728265/TAG" + } + }, + "1545e17d-fc88-4020-a1f7-e6dbcf1e5266": { + "value": { + "_tags": {}, + "metadata": {}, + "id": "1545e17d-fc88-4020-a1f7-e6dbcf1e5266", + "credentialDefinition": { + "ver": "1.0", + "id": "did:indy:bcovrin:test:A4CYPASJYRZRt98YWrac3H/anoncreds/v0/CLAIM_DEF/728266/TAG2222", + "schemaId": "728266", + "type": "CL", + "tag": "TAG2222", + "value": { + "primary": { + "n": "92672464557302826159958381706610232890780336783477671819498833000372263812875113518039840314305532823865676182383425212337361529127538393953888294696727490398569250059424479369124987018050461872589017845243006613503064725987487445193151580781503573638936354603906684667833347097853363102011613363551325414493877438329911648643160153986822516630519571283817404410939266429143144325310144873915523634615108054232698216677813083664813055224968248142239446186423096615162232894052206134565411335121805318762068246410255953720296084525738290155785653879950387998340378428740625858243516259978797729172915362664095388670853", + "s": "14126994029068124564262196574803727042317991235159231485233854758856355239996741822278406673337232628669751727662479515044513565209261235580848666630891738643990084502393352476512637677170660741636200618878417433799077613673205726221908822955109963272016538705991333626487531499501561952303907487494079241110050020874027756313402672435051524680914533743665605349121374703526870439925807395782970618162620991315112088226807823652545755186406850860290372739405126488851340032404507898084409367889215777693868794728141508635105180827151292046483128114528214465463152927678575672993454367871685772245405671312263615738674", + "r": { + "master_secret": "26619502892062275386286102324954654427871501074061444846499515284182097331967223335934051936866595058991987589854477281430063143491959604612779394547177027208671151839864660333634457188140162529133121090987235146837242477233778516233683361556079466930407338673047472758762971774183683006400366713364299999136369605402942210978218705656266115751492424192940375368169431001551131077280268253962541139755004287154221749191778445668471756569604156885298127934116907544590473960073154419342138695278066485640775060389330807300193554886282756714343171543381166744147102049996134009291163457413551838522312496539196521595692", + "age": "66774168049579501626527407565561158517617240253618394664527561632035323705337586053746273530704030779131642005263474574499533256973752287111528352278167213322154697290967283640418150957238004730763043665983334023181560033670971095508406493073727137576662898702804435263291473328275724172150330235410304531103984478435316648590218258879268883696376276091511367418038567366131461327869666106899795056026111553656932251156588986604454718398629113510266779047268855074155849278155719183039926867214509122089958991364786653941718444527779068428328047815224843863247382688134945397530917090461254004883032104714157971400208", + "name": "86741028136853574348723360731891313985090403925160846711944073250686426070668157504590860843944722066104971819518996745252253900749842002049747953678564857190954502037349272982356665401492886602390599170831356482930058593126740772109115907363756874709445041702269262783286817223011097284796236690595266721670997137095592005971209969288260603902458413116126663192645410011918509026240763669966445865557485752253073758758805818980495379553872266089697405986128733558878942127067722757597848458411141451957344742184798866278323991155218917859626726262257431337439505881892995617030558234045945209395337282759265659447047", + "height": "36770374391380149834988196363447736840005566975684817148359676140020826239618728242171844190597784913998189387814084045750250841733745991085876913508447852492274928778550079342017977247125002133117906534740912461625630470754160325262589990928728689070499835994964192507742581994860212500470412940278375419595406129858839275229421691764136274418279944569154327695608011398611897919792595046386574831604431186160019573221025054141054966299987505071844770166968281403659227192031982497703452822527121064221030191938050276126255137769594174387744686048921264418842943478063585931864099188919773279516048122408000535396365" + }, + "rctxt": "71013751275772779114070724661642241189015436101735233481124050655632421295506098157799226697991094582116557937036881377025107827713675564553986787961039221830812177248435167562891351835998258222703796710987072076518659197627933717399137564619646356496210281862112127733957003638837075816198062819168957810762822613691407808469027306413697001991060047213339777833838291591976754857934071589843434238025803790508552421154902537027548698271140571140256835534208651964449214890690159171682094521879102663244464066621388809286987873635426369915309596945084951678722672915158041830248278889303704844284468270547467324686757", + "z": "90415953543044389740703639345387867170174070770040351538453902580989033567810029650534915348296084212079064544906463014824475317557221991571331212308335167828473551776349999211544897426382305096215624787217055491736755052175278235595298571339706430785816901931128536495808042995635624112114867111658850659510246291844949806165980806847525704751270260070165853067310918184720602183083989806069386048683955313982129380729637761521928446431397104973906871109766946008926113644488012281655650467201044142029022180536946134328567554182760495139058952910079169456941591963348364521142012653606596379566245637724637892435425" + }, + "revocation": { + "g": "1 1864FF219549D1BC1E492955305FC5EED27C114580F206532D2F5D983A1DD3BD 1 0414758D7B6B254A9CA81E1084721A97CA312497C21BB9B16096636C59F9D105 2 095E45DDF417D05FB10933FFC63D474548B7FFFF7888802F07FFFFFF7D07A8A8", + "g_dash": "1 2327DA248E721E3935D81C5579DD3707882FFB962B518D37FB1112D96CC63611 1 164989452135CF5D840A20EE354DBF26BEEC74DE7FD53672E55224BEE0228128 1 0634D5E85C210319BFD2535AFD8F7F79590B2F5CC61AF794218CC50B43FBB8C6 1 0A63F1C0FC2C4540156C7A2E2A2DF1DDF99879C25B4F622933707DD6074A0F1B 2 095E45DDF417D05FB10933FFC63D474548B7FFFF7888802F07FFFFFF7D07A8A8 1 0000000000000000000000000000000000000000000000000000000000000000", + "h": "1 0A031B1932CDFEE76C448CA0B13A7DDC81615036DA17B81DB2E5DFC7D1F6CD6F 1 06F46C9CC7D32A11C7D2A308D4C71BEE42B3BD9DD54141284D92D64D3AC2CE04 2 095E45DDF417D05FB10933FFC63D474548B7FFFF7888802F07FFFFFF7D07A8A8", + "h0": "1 1C88CA353EF878B74E7F515C88E2CBF11FDC3047E3C5057B34ECC2635B4F8FA5 1 1D645261FBC6164EC493BB700B5D8D5C8BF876FD9BA034B107753C79A53B0321 2 095E45DDF417D05FB10933FFC63D474548B7FFFF7888802F07FFFFFF7D07A8A8", + "h1": "1 16AC82FE7769689173EABA532E7A489DF87F81AE891C1FDA90FE9813F6761D71 1 147E45451C76CD3A9B0649B12E27EA0BF4E85E632D1B2BEC3EC9FFFA51780ACE 2 095E45DDF417D05FB10933FFC63D474548B7FFFF7888802F07FFFFFF7D07A8A8", + "h2": "1 2522C4FAA35392EE9B35DAC9CD8E270364598A5ED019CB34695E9C01D43C16DC 1 21D353FB299C9E39C976055BF4555198C63F912DBE3471E930185EF5A20470E5 2 095E45DDF417D05FB10933FFC63D474548B7FFFF7888802F07FFFFFF7D07A8A8", + "htilde": "1 24D87DBC6283534AE2AA38C45E52D83CC1E70BD589C813F412CC68563F52A2CA 1 05189BC1AAEE8E2A6CB92F65A8C0A18E4125EE61E5CEF1809EF68B388844D1B1 2 095E45DDF417D05FB10933FFC63D474548B7FFFF7888802F07FFFFFF7D07A8A8", + "h_cap": "1 1E3272ABDFD9BF05DB5A7667335A48B9026C9EA2C8DB9FA6E59323BBEB955FE2 1 031BD12497C5BBD68BEA2D0D41713CDFFDCBE462D603C54E9CA5F50DE792E1AB 1 05A917EBAA7D4B321E34F37ADC0C3212CE297E67C7D7FEC4E28AD4CE863B7516 1 16780B2C5BF22F7868BF7F442987AF1382F6465A581F6824245EFB90D4BB8B62 2 095E45DDF417D05FB10933FFC63D474548B7FFFF7888802F07FFFFFF7D07A8A8 1 0000000000000000000000000000000000000000000000000000000000000000", + "u": "1 1F654067166C73E14C4600C2349F0756763653A0B66F8872D99F9642F3BD2013 1 24B074FFB3EE1E5E7A17A06F4BCB4082478224BD4711619286266B59E3110777 1 001B07BEE5A1E36C0BBC31E56E039B39BB0A1BA2F491C2F674EC5CB89150FC2F 1 0F4F1E71A11EB1215DE5A081B7651E1E22C30FCCC5566E13F0A8062DB67B9E32 2 095E45DDF417D05FB10933FFC63D474548B7FFFF7888802F07FFFFFF7D07A8A8 1 0000000000000000000000000000000000000000000000000000000000000000", + "pk": "1 0A165BF9A5546F44298356622C58CA29D2C8D194402CAFCAF5944BE65239474E 1 24BA0620893059732B89897F601F37EF92F9F29B4526E094DA9DC612EB5A90CD 2 095E45DDF417D05FB10933FFC63D474548B7FFFF7888802F07FFFFFF7D07A8A8", + "y": "1 020240A177435C7D5B1DBDB78A5F0A34A353447991E670BA09E69CCD03FA6800 1 1501D3C784703A097EDDE368B27B85229030C2942C4874CB913C7AAB8C3EF61A 1 109DB12EF355D8A477E353970300E8C0AC2E48793D3DC13416BFF75145BAD753 1 079C6F242737A5D97AC34CDE4FDE4BEC057A399E73E4EF87E7024048163A005F 2 095E45DDF417D05FB10933FFC63D474548B7FFFF7888802F07FFFFFF7D07A8A8 1 0000000000000000000000000000000000000000000000000000000000000000" + } + } + }, + "updatedAt": "2023-03-18T18:53:59.067Z" + }, + "id": "1545e17d-fc88-4020-a1f7-e6dbcf1e5266", + "type": "AnonCredsCredentialDefinitionRecord", + "tags": { + "credentialDefinitionId": "did:indy:bcovrin:test:A4CYPASJYRZRt98YWrac3H/anoncreds/v0/CLAIM_DEF/728266/TAG2222" + } + }, + "d7353d4a-24fc-405f-9bf5-f99fae726349": { + "value": { + "metadata": {}, + "id": "d7353d4a-24fc-405f-9bf5-f99fae726349", + "createdAt": "2023-03-18T18:53:59.857Z", + "associatedRecordId": "be76cfbf-111b-4332-b1fe-7a1fea272188", + "role": "sender", + "message": { + "@type": "https://didcomm.org/issue-credential/1.0/offer-credential", + "@id": "c5fc78be-b355-4411-86f3-3d97482b9841", + "credential_preview": { + "@type": "https://didcomm.org/issue-credential/1.0/credential-preview", + "attributes": [ + { + "name": "name", + "value": "John" + }, + { + "name": "age", + "value": "99" + } + ] + }, + "offers~attach": [ + { + "@id": "libindy-cred-offer-0", + "mime-type": "application/json", + "data": { + "base64": "eyJzY2hlbWFfaWQiOiJBNENZUEFTSllSWlJ0OThZV3JhYzNIOjI6VGVzdCBTY2hlbWE6NS4wIiwiY3JlZF9kZWZfaWQiOiJBNENZUEFTSllSWlJ0OThZV3JhYzNIOjM6Q0w6NzI4MjY1OlRBRyIsImtleV9jb3JyZWN0bmVzc19wcm9vZiI6eyJjIjoiODUxODAxMDMyNzEzNDg5NzYxOTg5MzAzNjMzMDkzOTEyOTExMDUxNjI0OTQ0OTYzMTgzNzM2MDY3NDkwOTc2MDYxODEwMDgxODkxMzQiLCJ4el9jYXAiOiI4NDk0NDg4MjQzNTk2NTkwOTc2MjQzMjc0NDg4ODk2Mjc1NTcyODAyMTQ1ODE5NDQzNTE0NzQxMzk1NDI1NjM5MzQwMTczMDIzMTQ5NzI3MDY5NzMzMzQwODgzMTU4MzQ1NTYzOTA5OTcxNDMzNTg1MjMwODAxNTYyMTM0NjczNjM1ODg5NTA3Njg5ODQwOTgyODU5Mzg1NjA1MTc1NTkxNDYxOTkyMDExNzU2Mzg1MTI3MTQ3ODgxNDMwODEzNjYxNzY0MDU5MzE0ODk4MTc2NzQzMTQ5MjYzMDMwMDQ1NzMwMDMzMzI2NzgyMzg1OTY0NjcxMzg1ODQ2MzcxNjQ5MzQxMTg2MDM5NjE4MTQwOTIwMDUxMzg1MDAwNTYxMDcyMTc5NTEyMzc5Nzk0OTU4NjE1ODIyODI2OTExNzIwNTQyNTE0MTQ1NDc5MTAxOTUyMzM4MDMwMDY1MDk5NjcxOTU2OTMxMzE2NjE5MjM0NTQ0NTE5NTQ1ODQ1MzA4MzgxMjQyNTM0NDcyOTc3NjY0MjAwMjc2MTMyOTgxODE1ODAzNTIxOTExMzk4ODkxMjE0NjE1NzA1MDM2ODM2ODU1NDU1NzY4ODg4MTUxNDgzODAyNDcyODQyMzczNzE0MTI0NTYwMzIyNTI3NDE4MTEwNzYyMjgyNzY4NzMyNTIzMDQyMDA3MDY2OTk2ODIxMTQwMzE1NDg0NzI4NTM2NzIwNDI3MDg5MTI2NDk1NTAzMjc0ODQ4MDM3MjUzOTM3NjI3MDU2ODUzMTQ4NjE5NDA4NDYxOTI5NzEzMjM4MjEwNDc4MjcyMTIxNTUwNjQzODc4ODM1NDYwMzY1OTIwMjE3NTk5NDYyNDUzMDMyNDQ4MjYyMTM3NjE5ODY0OTU4MzA1MDE3MjA4OTYwNDc1MTQxODgwMTMiLCJ4cl9jYXAiOltbIm5hbWUiLCI1MDcyNzU2NDE2NDA2ODIxNzU1OTc0MzUxMTg0NjE1NjA4NDY2NTk3Mzk0NzA2MTY1NDg2ODAzMjc3MjMyMzQyOTk4MDA0MzY0OTU0MTczMzc0NDIwOTc5NTkwMDcyODgxNDgxNDA0MTg2OTExODg5NzQ4MTgzMzQ1OTk5NzQ0NzgxMTQ1MTMwNzEyNDIzODY0Nzc1MzQzNjAzNTk2NDM3Mzg4OTgzNTExNDAzODA0NjEyNjU1MDE5NzQ4MTI5NDk3ODY2NTcwMDQyMjcwNDQxNDQ5MjYwODY0NzgyMzI5MjAxNDEzMTc5ODU3NzA0MjM5OTMyMTg4NTc4NzE3MDczNzM3NjUyNzY5MzY5NDg4OTgxNzg2NDQwNTExODAzMjMzNDMxNzA4NDk4MTU2NTA0OTUzNzkzNjU2NjQ2NzMyNTU4MzQwNDI2MDI1MjA3NTk0OTIwMDY4OTc2OTQ4Nzg2OTUxNzM3MDIwNDQ0NTA5NzYyMDQ2MzIzNzA0MDQ3MjU1ODU3NDE5ODE3MDc5NTI3NDgzNTE1NDY2NTAyMDkzOTY1NDMzMzk3MjQ1MzA4MjQ5MDgyMTQ4Mjc4NDA1MzI5Njg1Mjc0MDYwNjk0MzI0MTI2ODgxMjkyMDIyMjY1ODczMjk5MDU0NDU1OTA5NzkyNjUwNjAyMTk0NjUzMjYxMDk0ODYwOTc2NzA4ODE1ODgwMjExMTY0MTkwMDM0NjY0MzI2MDc3NjcwNzkyMDE4NTE2MzMzNDI3NjkwODYwMjIxODEwMzk5MDgxMjc5NjAwNTYzMjk3MjI0NjM0MDM0NjcxNTIwODE5MzU3NzQ0Njk2NzU1Njg1NDI2NjIzMzAwMjQ3MDUwODE4NTQ2MDM2NjA0NjMxNjcyNzE5MjI0NDA4NTE2NDM4NTgxMDM5Njk4NzI0MSJdLFsibWFzdGVyX3NlY3JldCIsIjU2MzYzNTgyMDQ5Mjg4OTY1OTg1MDA4NzgyMzU0NjgyNjMwNDkxMzQ3MTM1NDIxNTAyMDEyMTIwMzI4MDI4ODIyMjUyMzg4NjgwNTMwNTgxMTcwMTgwNDU1MTcyNTc3ODkyMTEyMTY1OTM0Mjk5NjUyNzAxNDExMzUyNDkzMzkyODU0ODI4NzMyMDQzMDI0MDI0MzM0MzMzNzc0NjEyOTEzOTUyMjAzNjM1NDk2MDQ0ODMzMDI5NDE2NjUwOTU5NjE0ODgzNTUwOTMxNzgzNTA5MzE1Nzg4MDEyODQ0MzAwMDQwMDE5MTY5MTc3NTI1OTgxMTU3OTkwNjQzMDcyMjQyNzcxMjU0MTYyNzMxOTU4NzI2Nzc1NjYwMjkxODIzMDcyNDk1Mzg0NzM5MTcwODc4ODMxNzkxMjQzMjEzMjU5MzA5ODQxNjU3MjUwOTg1NzMxMjEyNzE2MDM2MDY3MDUxNjM2NzA0MjA1NDEzMDk2MDU3MTA2NTM2MTI2ODUyNDU0NzcwMzQzMTMwMTczMjAwNjEzMDIxOTE4MzgzMDQxOTU4MTkwOTE2NzQ0NjU4NTI0ODA1NjM4Mzk2OTY3OTA3MzIwNjY1MDU1MzcwMjY0NjAxMDczMjc5NDMyNjM5MjM3Njc1NTA0OTg1NzQyNTI4NjYwMTAyMDEzNzIxMzA2MTE4MTg0NDk1MTEyNDQ2NDYyNDc2NTkwMjYxODkxMjA0OTQxOTA4MjMyNzMzNDA3MTg4MDA3NzE2NTA2OTUzMDY0Nzc5NDk5ODExNzI0ODI5NjcyNjY2NzIyNjIzOTAxMTc1OTk0NTIyNjkwMjk1ODI0MDgyNzY5NjQ0NDYxOTAxMDk2NzI3MTE5NzAzMjUzNzI4NjY3MTU1MzA5MDYzNDUyNDY2MDY3NzU5NzIwOTgyNDA3MiJdLFsiYWdlIiwiMTM2NTQxMjE0MjM5MTcyNDQxNzQ1MjU3MjcyMDI3MTA4NDYwMzU0MjgxMTA2OTA2MzYwNDIwMDE0NjUyMDIxMDgyNDEzODM2ODEyMjk3NjY3ODk2MTYzNDkzMjM4NDIxNDI4NjMyNTMxODE0ODk4NzkwMDg4OTg2NjgyMTE2OTAyMzc4NDgwNTE4OTUxNDExNzg1OTk3NTk5MDMyNDYxNjExNjIyMDUyNjMzMDQ5ODYxMzc5MTQzNzI4MTM5MTUyMDkyMzI0ODc3MjMxMTYwNTgzNzA5NjE0MzA1NzQ1MjA5MjQwNjU2MDU4NjY3OTMwODEzNzYyNDY5MDc2ODc5MTk1Nzg0Nzg4NTE2NjI3MjgxMDY0NjE3MzgzMDc4Njc5MTkwODIwMzQwNTgwNDY2MjU3ODU3NjA1MTc2MTg4NTI3OTMxMDI4MTMzNTY5Njc0Mzg2ODAwMTA2MDE2MDg1Nzc0OTcyMzI1NTAyNDA2MTY0OTY0MjU2OTUxNDI3ODAxMTQzNTQxMzUzMzI0Nzg0MzA5OTY4MjIyOTU1NDk4Njk3NTAwMDUwMzc0MDg0NjIwNzQ4MTk0NzIyMTI2NjE2OTY3OTY3Mzc1NTM3Nzc5NTc4NTMwMDIxODExNTA2MTIxNjcxMDUwNDgzNTM2MjA3Njc3MTg5NDQwNjEwNzk0NTcyNzI5MTgzMzAyMjM1MDkxMDg4NTU2ODc5NTg3OTE3MDMzMzQyODcyMzg2NDQ5MTQ0NzgwMDYyNjc4MzA3NzE4MzU1MjQ5MTUxNjc5MDA1MzkxNDA5NDE4OTQxMjEzNDkxMjQyMjg2NTAwODcyMzQxNDI3Nzk1MjQ1ODYzODE2MDY2NDY3NDkxOTg4OTU3MDEwNDIxNDA3NDkyMDUxOTc0NTMwNjIxOTk1ODU0ODczNTM5Mjk3MyJdXX0sIm5vbmNlIjoiNzk3NjAzMjE3NzA5MzM1MzAwMTcwODI4In0=" + } + } + ], + "~service": { + "recipientKeys": ["GVbwqUMqzxKaEVWjn1aPBfjJpYQHVejinpx8GCeEuQjW"], + "routingKeys": [], + "serviceEndpoint": "http://localhost:3000" + } + }, + "updatedAt": "2023-03-18T18:54:00.011Z" + }, + "id": "d7353d4a-24fc-405f-9bf5-f99fae726349", + "type": "DidCommMessageRecord", + "tags": { + "role": "sender", + "associatedRecordId": "be76cfbf-111b-4332-b1fe-7a1fea272188", + "threadId": "c5fc78be-b355-4411-86f3-3d97482b9841", + "protocolName": "issue-credential", + "messageName": "offer-credential", + "protocolMajorVersion": "1", + "protocolMinorVersion": "0", + "messageType": "https://didcomm.org/issue-credential/1.0/offer-credential", + "messageId": "c5fc78be-b355-4411-86f3-3d97482b9841" + } + }, + "be76cfbf-111b-4332-b1fe-7a1fea272188": { + "value": { + "metadata": { + "_internal/indyCredential": { + "schemaId": "A4CYPASJYRZRt98YWrac3H:2:Test Schema:5.0", + "credentialDefinitionId": "A4CYPASJYRZRt98YWrac3H:3:CL:728265:TAG" + } + }, + "credentials": [], + "id": "be76cfbf-111b-4332-b1fe-7a1fea272188", + "createdAt": "2023-03-18T18:53:59.068Z", + "state": "done", + "threadId": "c5fc78be-b355-4411-86f3-3d97482b9841", + "protocolVersion": "v1", + "credentialAttributes": [ + { + "mime-type": "text/plain", + "name": "name", + "value": "John" + }, + { + "mime-type": "text/plain", + "name": "age", + "value": "99" + } + ], + "updatedAt": "2023-03-18T18:54:01.378Z" + }, + "id": "be76cfbf-111b-4332-b1fe-7a1fea272188", + "type": "CredentialRecord", + "tags": { + "threadId": "c5fc78be-b355-4411-86f3-3d97482b9841", + "state": "done", + "credentialIds": [] + } + }, + "e531476a-8147-44db-9e3f-2c8f97fa8f94": { + "value": { + "metadata": {}, + "id": "e531476a-8147-44db-9e3f-2c8f97fa8f94", + "createdAt": "2023-03-18T18:54:00.005Z", + "associatedRecordId": "a56d83c5-2427-4f06-9a90-585623cf854a", + "role": "sender", + "message": { + "@type": "https://didcomm.org/issue-credential/2.0/offer-credential", + "@id": "4d2c80b7-4a25-42ac-b8cf-a68b1374b9b7", + "formats": [ + { + "attach_id": "8430ddb8-b0c3-4074-8ded-f4dcfe80303d", + "format": "hlindy/cred-abstract@v2.0" + } + ], + "credential_preview": { + "@type": "https://didcomm.org/issue-credential/2.0/credential-preview", + "attributes": [ + { + "name": "name", + "value": "John" + }, + { + "name": "age", + "value": "99" + }, + { + "name": "height", + "value": "180" + } + ] + }, + "offers~attach": [ + { + "@id": "8430ddb8-b0c3-4074-8ded-f4dcfe80303d", + "mime-type": "application/json", + "data": { + "base64": "eyJzY2hlbWFfaWQiOiJBNENZUEFTSllSWlJ0OThZV3JhYzNIOjI6QW5vdGhlclNjaGVtYTo1LjEyIiwiY3JlZF9kZWZfaWQiOiJBNENZUEFTSllSWlJ0OThZV3JhYzNIOjM6Q0w6NzI4MjY2OlRBRzIyMjIiLCJrZXlfY29ycmVjdG5lc3NfcHJvb2YiOnsiYyI6IjQwMTQ0MTA0NDg3MjM0NDU2MTc1NzYwMDc1NzMxNjUyNjg1MTk0MjE5MTk5NDk3NDczNTM4NjU4ODM3OTIyODMzNTEzMDg0Nzk2MDQ5IiwieHpfY2FwIjoiMzgxOTQyMjM1Mzc3MzYwODEyNjY0MDA4MjYzNDQxMDg2MDMwOTMyMjAxNzgzMjM3ODQxODQ5NDg3ODk2ODg1MTYwODY2MTY1MDM3NzI2MTIxNjU0MjcwOTg5NDY3NjAzNDExOTAzODk4MzUwMDAzNDIwODg3MzI4NTUwMTY2MTI1ODMyMjAxOTQzMTkwNzAxMDU4NTAwMDE5ODM1NjA1ODczNDYzOTkwODg3NzQ0NjY3MzU0MjM2Njc3MzcyODg0ODQyNjE5NTEwMTUwOTA2MjI1OTMzMjc1ODEyNjg2NDg3NTg5NjY3ODI3MjAwODcwOTQ0OTIyMjk5MzI3OTI4MDQ1MTk1OTIwMDI3NTc0MDQwNDA4ODU5MzAwMzY1MDYwODc3Nzg2ODkwOTE1MDU5NTA2ODc1OTI0NzE2OTI1MDM2MTc4Njg2NDE5NTYyMzcwODI4MTMzODY2Nzg3NzkyMDcwNjAyNDQzNTkzMTk2NzEzNzcyNDM2NTYzODI0MzkwMDIyNzg4MjU2MzA4NjU4OTc0OTEzMTk1ODYxODUwMTQ3ODE1Mjg5NzQwOTA4NDk1MjQ3NTAyNjYyNDc3NzQ2NTI5ODA3Mzg0OTgxODI5MDc3NTQ4OTI2NzExMDkzNzQ5MjM1ODU4NjUwNDc5NzE5NDI4MzUwMzAwNzUyNjQ0OTg1MTQ5MTMxNjA1NjUzMDIxMDYxNzkwMjY3MzQyNTY4NTkyNTY2MTQ0MDM5NzY4OTg0NTMyNDMzNzk0MzUzNjQ2Nzg1MjA3NDgzOTk2ODQ0OTcxNTgzNzY3NDQ5ODYyODgxMjMxMjI1MzM4MzAzMTQ4NzA0ODczMDEzNDM3MDgyNzY1MTk4OTY2MzE5NTM0OTkyNjk4MzMzMDQ0MDI3MjIyNTYyNTIzNzk3ODk5Mjk2MTQ1NDU5IiwieHJfY2FwIjpbWyJtYXN0ZXJfc2VjcmV0IiwiOTE5MTc5NzQ4MTE5NTg5MTY3Njc5MjQxODk5NzY0ODIwNTk0MDc5OTQxNzIzOTgzOTYyNzQ1MTczODM0NDQxMDQ3MjU4MDcyOTE4OTUzNjIzOTQ4MDMyNzI2NDgyNzI2MTEwOTk2Mjk3MDU3NTYwNjcwNzAxOTU1MTkxNDc0NjM0MzQ0ODMxMzg3NTk4NzI2MzMxMjc0NjI4NDU3Njk5NzczMDA1NDMwMDIxNzMwMzg4MzcwMTEyMjc3MzI2MzU4OTgwMTA3ODIzNzUzODc3MTU0NjIwMDkzMjE5MjYyNjAxNDM2NzMyNTgzNDI4Nzc4NDA4OTc0NTQyNzkzMDk0NTQ5MTczOTA3MzQ3OTUxNTc1NjM5NzU2NDg5MTA0Mzk0MTY3NzExMzY1MjM3OTI1MjAwNjk4OTg5NTI5MTQ3OTIzNTYzNDMyODgyMzgwMTg0NzU0NzkzODMwMTE3MTQ1MDAwMTI0NDYxNjkzOTcxMDQ5MjgzNDk1NTE4MDQxMDc5ODUyMzAwMjk0NDM1MjYzOTIwNDU0NTU3MzUxNDQ1MDM3NDI4MDg3OTk2Mzg2NjY3NjU3Nzk5OTYyNzQzNzIyNzA3NzczOTEzMzc0NzIxODUyNTQ3MjkwMTY5MjI5NTAzMTQxOTMwODYzNTk4NTExNjc4NDEyMDE0MzE2MDM2MzYxMzczNDcwOTQwMDEyODcwMDgwMDA2MzE0NzYxNzYzNzUyNzYwODk5MTQ3NzA1MTA0NzQyNjAxNjkxMzMxNjkzMDIwMjg2MjA2NzQ2NzE0MzI3NjU2MjA2NTMzMjk3NDg4MjU2NTM2NTQ3MzY4MjM2OTQ2MDM5NzAzMzc0OTMzNTE0NTc2NDg2NjQyNTY4MjgyNTY2MjMyNDU1NTU5MDY4MzE3NzU5NDM0ODU4NTI3MDg2NjQ0Il0sWyJoZWlnaHQiLCI5MjMwMzkyNDc1NjI4ODc1MjA4OTM0NjM0NzE4MjYzNzA4MDIzOTI1MDU0NjY2NDgzMzgxMzIyMzc3MDg1MjMxMjU4MTM4MzgwOTU1NTk3NDQxNTEyOTYwNDA2MjI3MjUwODgyNjA3NjExMDkwODk3MTM1NDcxNzAwMDIzNDcwOTM2ODg4MDE3NDY5Nzk0ODYzNDk4NzUyNTI3Njc3MjMwMTEwNzg0ODQzNzI0NDUyNTUzODYyOTA2MzM5MDc0OTIzNDU4NTQ3NDYzODcwNzU3OTg5MzMxNzk4OTI2MjM4MjUxMTM2NTYzNjM2MjIyOTQwNDkwMzY3MjQ2OTg0OTU2NTE5MTAzODcwNDE0MDM5NzM2MDE2MDY5MzA2NjQ0NjQzODI4OTgxMTE3OTM3NzYyNDAzODY1Mjc1MDU5MjEyOTY2NzIxOTU3MzM0MTM2ODEyMDI0OTE0MzA4MzAxMzk5MzM4NzMyOTIzNTA0MjA5MDM5ODMxMTc5NjU1NTkyNjg0MjMyMTIzMTI2Mjc4ODQzNDMyOTUwMTk1Mjg3MzE4ODI3NTM2MTMwNDQ3NzM3MTgwMjk3MDE0ODEzNDg3NDQyOTg2NjQ1NzQyNjEyMzE5NzQxNDY2MDMyNTg5OTU0NzYwNjE4MDU0MDUxMjAzMTE1NTAxNDcxNDExMzg3NzU0NDk5MzAwNTU4MTc5NjM5NDAxOTM0NTAzMTMyMDEzMjAzOTg2NzkyMTEzMDAzNTkwODg1NTc3NjgyMzU2NDY3MjA5NTUwNjQxODQxMDYyNTkzNDYyODIwODg3NzgxNDYyODM3ODkzODcxNDM4MzM3Mjc5MTcwMTExMTQ5MTU4NDMzNDE0ODI1NTkyNjcyODU2MzM5OTM4NTgyODg2NzM3OTIwMjc1MzI0MjEwMTUzMjE5MjI2OTYiXSxbImFnZSIsIjkxNTg1ODk3NDkwNzE0ODA3OTY2MDYzOTg5MjE1NTMxNDkyOTQwMDI5NDcyMTM4MjgwNjcxNjcyMjQ0NjY5MDc5NzIyNTQyMDU0NTU3NjY0MTcxMDI1NzM1NjQ4NTIwMTM4ODQ4ODAxNzIyMTc4MTcxMTA5NTc0MTMyNTExMzM1MDEwNTc5NzExMzcyODM5MjI3MDExOTg4MTUyMTEwMzI4MTE5MjkyMjI4NjM3MDU4MDQ3NzYwODYwOTQ0NTY3MzQxMjY4MTY4Mjk3NjE5MDM2ODEwMjYwODM2NDI1NDkwMzU3NjE4NzM4NTYxNTY2MTUxODQ3MzIxNzM1MjQ5ODk1MDU5NTY2OTQxODI5MjE0Nzc0MTA0NzYyNTQwMjcyMjk2NjE1NTE3NjUwMDcyNDQyMTI0NjY5MDEzMTc1ODAyMDk5MDQxMzk3MzE5ODQ0OTA2MDgwOTYxNTcyMTcwNjg2NzgzNDM1Mjg2MDUyMzE5ODY3ODExMDE5MjAxMDYwODM2OTM3Mzc0MzM0NDM5MTQxMDAzMTI3NTcyNjgzNTgwODI0OTkwOTg3MjE5MzU4NzkzOTM2NTU4Nzk3MjI0MDQzNTM1ODA5NzMyNzgxMjE1NzEwNjI1MjQzODYwNTk4MTk0MjU2MjAwODkwOTA3ODAzMDcyMTAzNzc3MzkwODk4MDczOTgyNjY3Njc1ODg0MjI3MjU0Mzc2OTI5Mjg3ODQyNDE0MTE0MjcwNDQwMTEzNDUxNjk4NzE5Nzc5NjQyNTI4MDA4NDM3Mzk5NjI0NTE3OTM4Nzg5MDc3ODE5ODA0MDY5MzcxOTM0NzExMTIyNTQyODU0OTg4MDA0Mjc4NDkwMjAxNTk2NjE0MjUwODc3NDYxMDczNjc3NTUzNzYxMTMyMTA5Nzg3NTQ2ODE1ODk5Njc2NCJdLFsibmFtZSIsIjYyNzgwNTIwMTM3MzI3NTUzMDc3MDg4NTE4NDg1NDYyMTA0NjEzMjEyNzY3ODUwMzYwNTc3NDQ4MDUxNTk5MTMxMTM1NTI2NzQ3Nzc2NzMzMDg1MDMwODcyMDE1OTM2MTI2NzE0MTIxMDgxMzg3ODU2MTkwMTkzMzI3ODY3OTE0NTEzODM2NTQ1OTY4Mjg1NTc5ODEyODMxMDI4ODc2Nzg1NzI3OTQ2MTEwNzg5Mzc0MjcyODgzMzkyOTgwNDkwODk3NDkwMTc5MDQ0ODM0NTgwMzQ2ODY4NDI2ODc0ODU4NTY1OTg4NTUyMDcwNjI1NDczNjM4MDM3Njc5NTU1NTk2MzE5MTc3Nzc5OTcxMTIxMjQzMjgyMTIyOTQ2NjY0ODMxOTgxMTg3MzQ3MzcyMjkxMjYwOTM3MzkzNDA1ODk5OTI0NjM4MzE3ODI5MDczODMxMjI4ODc1Njg5MTcyMTg4NjIyMDI5NzcxNzM5MTQ5NDY2Mzg3NTM5NjkyNDQ5NDU5MjczNDI5NzM5MjMzNjkyNTEzNDA5OTkyNDgxNTQ4ODk0NjAzNjM3MTYzNjA4MTM0MTAzMTk3Nzc3NTM4OTYwMDcyMjcyMzYyNzM4NDM1MTM3MDcyNzIxMjExMDYxNTg4MDE3ODczODg3MTEwNDA2OTk1NDQ4ODIwMDEzMDA5MjgyMzk0OTczMDMwMDI5MTY3NjQ5NzY1OTI1MTUxMzY4NTg5OTkyNzMyMDE1ODAwNjAzNzYxOTI3MTg3MDM4MDkxNDY3MDE1MjA3MzIwNDczMDM0NDA3MDIyNDA0NjQ4MTI0NTk2NjQwNjU1NjY1MTIzMzY5Njc0ODI2NDE3MjE2ODUxNTM4Njc1NTM3NzAwOTg4MTQzNzE1NTE3NzMwMTM4NjA4NzkxMjcyMzM0MDUyMzY4OCJdXX0sIm5vbmNlIjoiNDE0MzQ4Njg0NDk2OTAxNjkyMjI2OTY0In0=" + } + } + ], + "~thread": { + "thid": "f9f79a46-a4d8-4ee7-9745-1b9cdf03676b" + }, + "~service": { + "recipientKeys": ["DXubCT3ahg6N7aASVFVei1GNUTecne8m3iRWjVNiAw31"], + "routingKeys": [], + "serviceEndpoint": "http://localhost:3000" + } + }, + "updatedAt": "2023-03-18T18:54:00.014Z" + }, + "id": "e531476a-8147-44db-9e3f-2c8f97fa8f94", + "type": "DidCommMessageRecord", + "tags": { + "role": "sender", + "associatedRecordId": "a56d83c5-2427-4f06-9a90-585623cf854a", + "threadId": "f9f79a46-a4d8-4ee7-9745-1b9cdf03676b", + "protocolName": "issue-credential", + "messageName": "offer-credential", + "protocolMajorVersion": "2", + "protocolMinorVersion": "0", + "messageType": "https://didcomm.org/issue-credential/2.0/offer-credential", + "messageId": "4d2c80b7-4a25-42ac-b8cf-a68b1374b9b7" + } + }, + "a56d83c5-2427-4f06-9a90-585623cf854a": { + "value": { + "_tags": {}, + "metadata": { + "_internal/indyCredential": { + "schemaId": "A4CYPASJYRZRt98YWrac3H:2:AnotherSchema:5.12", + "credentialDefinitionId": "A4CYPASJYRZRt98YWrac3H:3:CL:728266:TAG2222" + } + }, + "credentials": [], + "id": "a56d83c5-2427-4f06-9a90-585623cf854a", + "createdAt": "2023-03-18T18:53:59.859Z", + "state": "offer-sent", + "threadId": "f9f79a46-a4d8-4ee7-9745-1b9cdf03676b", + "protocolVersion": "v2", + "credentialAttributes": [ + { + "name": "name", + "value": "John" + }, + { + "name": "age", + "value": "99" + }, + { + "name": "height", + "value": "180" + } + ], + "updatedAt": "2023-03-18T18:54:00.007Z" + }, + "id": "a56d83c5-2427-4f06-9a90-585623cf854a", + "type": "CredentialRecord", + "tags": { + "threadId": "f9f79a46-a4d8-4ee7-9745-1b9cdf03676b", + "state": "offer-sent", + "credentialIds": [] + } + }, + "598dbcc3-5272-4503-9c67-b0cb69a9d3d6": { + "value": { + "_tags": {}, + "metadata": {}, + "id": "598dbcc3-5272-4503-9c67-b0cb69a9d3d6", + "createdAt": "2023-03-18T18:54:01.126Z", + "associatedRecordId": "be76cfbf-111b-4332-b1fe-7a1fea272188", + "role": "receiver", + "message": { + "@type": "https://didcomm.org/issue-credential/1.0/request-credential", + "@id": "2a6a3dad-8838-489b-aeea-deef649b0dc1", + "requests~attach": [ + { + "@id": "libindy-cred-request-0", + "mime-type": "application/json", + "data": { + "base64": "eyJwcm92ZXJfZGlkIjoiY3F4ZW8ybzVIWmVjYWc3VHo5aTJXcTRqejQxTml4THJjZW5HV0s2QmJKVyIsImNyZWRfZGVmX2lkIjoiQTRDWVBBU0pZUlpSdDk4WVdyYWMzSDozOkNMOjcyODI2NTpUQUciLCJibGluZGVkX21zIjp7InUiOiI3OTE4NzUwNzgzMzQxMjU3ODU3OTUzMjc2OTU5MjcxOTcyMDQwMjQxMTU1MzcyODEwOTQ2NTEwMjk5MDA1ODEyMTcwNjkwMjIzMTQ2ODU0NDk1MTI1NjQ1MTg3ODQxNzk0NjA3OTUwOTQ1OTM3MDYxMjk1ODgwMjIxMzE2NTg1MTIyNDY1Mzk1MjAwNDQ3MDIwNzAxNDA0NjEwMDc4MzkyMjA4NzkxMjk5NzYwMzM4OTIxNDMzMDc1Njk2ODU0NTY3MTIzNjYxNTYwNDMwNDE3NzQwMzc5MzA4NDQzODcyOTU1NzAwNTk1MTg2NzcxODY3MzM5NzQ5NDgzODYxNTQ2NTE2MTU4NTM5MjkyNDQzNTQ3OTg3MDUwMzE4OTAyOTI5OTgyNzMzMDk1ODk4MDIyMjg2OTk1OTQwMjkzMTg3NTg5NDgwNTgwNTAwNjM0NzAyNjQxNDM0MTgxNjIwMTU4OTU3MzUyNTE1OTA4NDE2MjI4MDQ0NDA2OTU4MTk1MDg4Mjc0ODI4Njc3OTQxMDgxOTczOTg3NjU1MDEzNDUxMzA4ODQyMjYyMzY4MTQzOTExNjIxMTE0NzYyNTk3Nzg1MDczMTM4MDg3NTQ2MDIyMzc1NjQxODQ5ODI2OTg2MjYwMDE5NTAzNzE3OTk0NjM3MzIyNDExNTgzNTY0MTQ1NjcwMTM5OTQ1MjQxOTU2Njk2MDQ3MTQzNjA4NjQ0MDM5OTg2NTYwODUyMzA1MTczMjUxMTUyMDIzODI5NjI3MzQyODM2NDI3MjkwNDQ5NTA3OTY0Nzk4MTQ2NjkzOTUxMDkwNzUwOTAyNiIsInVyIjpudWxsLCJoaWRkZW5fYXR0cmlidXRlcyI6WyJtYXN0ZXJfc2VjcmV0Il0sImNvbW1pdHRlZF9hdHRyaWJ1dGVzIjp7fX0sImJsaW5kZWRfbXNfY29ycmVjdG5lc3NfcHJvb2YiOnsiYyI6Ijk4MjIzNzMzMDcwMDk1NjM0MzU5MTE2MDEyOTgzMDcyMjc5MjcxMzk1MTg0NjA0NDcxNDQ5NzA1Nzg0MTEyNDAyODMwNzIyMTUxMzIxIiwidl9kYXNoX2NhcCI6IjU5ODA0MTY4ODAxODk1NDAzMjkwMzczMTA3NzA4MzUwODc1NjY4MDcyNDY3ODAyOTcyOTIyNjUzNDE5ODU2MjMyNTIzNDI4OTUxODQ4NjEyNDE1MjM5Nzk3Mjk5ODY2MzIxNjU5NDQ1MTM1NzQ4NzU2MDY1NjgyOTY5MjY4ODI5MTYyMDA0NjQ4NzYwMzg4NTg4NDkyNjg1NDI1MTg1MDU2OTAxOTkxMjcwMzYwMDk3MDc5NjEyNTYxMzY4NzU1OTcwMjY5MjI4MDYzMjMyODU0NzI0NDkyOTA5Njg5MDMyOTg4NjYyMjk5Mzg3MDU2NDEwNjU5MDU3MDUwNjE0MDQ2NzE1NjA0NTgyMzM2NTg4MjMxMjI3MTEzMDEzMDQxMTA0NTU2NzM1MDE3ODUwNzUzODcwNjc2ODYxNDA4MjA0NzkzMDIzNTYwMDEwNTEzODAwNzA4MjAyNjAyNjQ0Mjg2NzI4NjAyOTk5MzU5MDcwODQxMTQ5MTAzMjA5MzY0ODkyMzMzMDYwMDgzMTA5NDIzOTQ5NzE4NTk5NjEzMzk2NjIyMjc4MTExMzk5ODU0MTcyMjMyNTQzOTk1Njc5NDk3Mjk1Nzc1NzA0MjA0MTQxOTU2NDI1MDc4NjYzMzgwMDA1Nzc2ODY2MTcxNTY4OTU1NjE4NjAwMTA2NzkxMjIyNDkyODA2NzI1ODU1NDY2Nzk4OTEzMTc2NDcxMDY3MTk5ODQ2ODEwNDI5MDIzMDc3ODI3OTc1OTIzMDIzNjU3MTg0NjkwNzE0MjkxNDk0MDc5MTM1NzYyOTUxNTc0MjMzNjMwMjExNDQ1Njc3NzE1Mzg3Mzc1NjkyMjAzODE3NDczNDk5NDExNzE5MzIwMjczNzExOTIzMzM3MzYzMTAyOTkwMDcyMjE2MjYzMzUxMzMxNTk4ODk1OTU3MzU1MDc1NTEzODE0NTUwNzkyMCIsIm1fY2FwcyI6eyJtYXN0ZXJfc2VjcmV0IjoiNTY0MTY1NjA2NjM0MTUwODgzMjg2MjM1NDM1MjUyODQ3MjAxMTk4NTQyNDAxMjYzNTY2MDQ2MTA3OTU3NzI4NTQ0MTMwMjgzNjUyNjQzOTI0NDc2NTU2NTIzNzg0ODI1OTgyMzMwMzc4NDI4OTM0MDkxNDcwNDM0OTAwMTM3NzkwNDkxMjM4NTA4ODk2ODQxMzkwNjQ4MTA1NzY5ODYzMzI1OTAzODI1Mjk1MjU2OTQ0NSJ9LCJyX2NhcHMiOnt9fSwibm9uY2UiOiIxMzE1MDIwOTY0MDY4NjgyMDQ0Mzc4MjEifQ==" + } + } + ], + "~thread": { + "thid": "c5fc78be-b355-4411-86f3-3d97482b9841" + }, + "~service": { + "recipientKeys": ["cqxeo2o5HZecag7Tz9i2Wq4jz41NixLrcenGWK6BbJW"], + "routingKeys": [], + "serviceEndpoint": "http://localhost:3001" + } + }, + "updatedAt": "2023-03-18T18:54:01.126Z" + }, + "id": "598dbcc3-5272-4503-9c67-b0cb69a9d3d6", + "type": "DidCommMessageRecord", + "tags": { + "role": "receiver", + "associatedRecordId": "be76cfbf-111b-4332-b1fe-7a1fea272188", + "threadId": "c5fc78be-b355-4411-86f3-3d97482b9841", + "protocolName": "issue-credential", + "messageName": "request-credential", + "protocolMajorVersion": "1", + "protocolMinorVersion": "0", + "messageType": "https://didcomm.org/issue-credential/1.0/request-credential", + "messageId": "2a6a3dad-8838-489b-aeea-deef649b0dc1" + } + }, + "b88030f7-dc33-4e7e-bf4d-cdfaa6e51ebf": { + "value": { + "metadata": {}, + "id": "b88030f7-dc33-4e7e-bf4d-cdfaa6e51ebf", + "createdAt": "2023-03-18T18:54:01.192Z", + "associatedRecordId": "be76cfbf-111b-4332-b1fe-7a1fea272188", + "role": "sender", + "message": { + "@type": "https://didcomm.org/issue-credential/1.0/issue-credential", + "@id": "578fc144-1e01-418c-b564-1523eb1e95b8", + "credentials~attach": [ + { + "@id": "libindy-cred-0", + "mime-type": "application/json", + "data": { + "base64": "eyJzY2hlbWFfaWQiOiJBNENZUEFTSllSWlJ0OThZV3JhYzNIOjI6VGVzdCBTY2hlbWE6NS4wIiwiY3JlZF9kZWZfaWQiOiJBNENZUEFTSllSWlJ0OThZV3JhYzNIOjM6Q0w6NzI4MjY1OlRBRyIsInJldl9yZWdfaWQiOm51bGwsInZhbHVlcyI6eyJhZ2UiOnsicmF3IjoiOTkiLCJlbmNvZGVkIjoiOTkifSwibmFtZSI6eyJyYXciOiJKb2huIiwiZW5jb2RlZCI6Ijc2MzU1NzEzOTAzNTYxODY1ODY2NzQxMjkyOTg4NzQ2MTkxOTcyNTIzMDE1MDk4Nzg5NDU4MjQwMDc3NDc4ODI2NTEzMTE0NzQzMjU4In19LCJzaWduYXR1cmUiOnsicF9jcmVkZW50aWFsIjp7Im1fMiI6IjMyMTIwMDQ1ODc4MzIxMjcyMzA1ODI5MTc3NzMzMTIwNzE1OTY5NDEyNjkwNjUyNDQ4OTc0MTA4NzEzNjU0ODc3NTg2MzIzMTI3ODk2IiwiYSI6IjIyMjY0MTYwNjIwODcwNDUyNTExMjcyMzE1MzMzMDA0MjQzMzY3NTM2NzM3NDMwNjM1NjExMjcwMDkwOTE4NDMwNzc0ODEzMjAzNjQwNjMxMjIyNDczMzk0MjQ4MTgzMDIzMjIyNzExNDUwMzQxMDcxOTQyNDQwMDgwMjY2Nzk1Mzg5Mzg5Njc1NjYwOTUzNTQyMDE4OTA3NjQ3NzI4OTQ4NjY1MzA2Njg0NjExNDU1NTI5NzM5OTY1NDcyMjQ2NDQxMzE1NzAxMzM1ODc1MDY3MjExMDk3NzcyOTgwMjU1NDIxMDMzMTI1MjAyMTQzNDk3NjMyOTAyMjM1NDAyMzU5OTA1MzY5MzE4MjI1NTc4MjUxNjY4NTYzNzc1NTY0MDM2MjUxNzE0Mzk3MTEzNjQ3OTg0MjcxMTE5MTU2NDQ3NjI1OTk1NjE5MjAwMDk4MTgzNzY1NjkzMTg1ODEzNjA1NDU3OTQwMzE0MDU2MDkzMTI2MzQ3OTU5MzYwODIyMzg0OTEzODg3Mjg3ODI2NjkyNDIyNDMyNDUwMDA5OTYxNjQ2MjMzNTE3MjY3NDU1OTkyMjA3MTE3Mzk5NzU1NjY3MTA3MzM1NTQ0MzEwNDQwNDE1NDE5NTk5NTA1OTgxMzkwMjk5NDUxNzQyODg4NDg0MTc0NTU5MDA5NDgwNjU5MDk2Nzg2ODI2MDgxNzc3MzcwNTk1MTU3OTg5NjQ1MDYxNDI2OTA2ODM2MDk5NTU5MDQ0MDI4ODM2MzYwOTM2MDkwOTkxNjA1OTU0NTM2OTQxMjgxODQwNzk2MjkxODc0ODk2NDEzNTM5IiwiZSI6IjI1OTM0NDcyMzA1NTA2MjA1OTkwNzAyNTQ5MTQ4MDY5NzU3MTkzODI3Nzg4OTUxNTE1MjMwNjI0OTcyODU4MzEwNTY2NTgwMDcxMzMwNjc1OTE0OTk4MTY5MDU1OTE5Mzk4NzE0MzAxMjM2NzkxMzIwNjI5OTMyMzg5OTY5Njk0MjIxMzIzNTk1Njc0MjkzMDExMzE1MTE0OTUxODg0MDQ5ODkyNjAwNTY3NTgzMTc4ODkwMyIsInYiOiI2NTE5ODc4MzYyODQ5NzExNDY4NDkyNDM1OTM3MDU4NzMzOTYxMTkxNjA3NjI4MzUzNjkxMDg1MzM5MDMwNDU0OTkyODc0ODYyODkyNDg4ODYzNTA3MDQ1MTM1MzA4ODI1NDA2NzYwMTQwNDQzNzM0NDYzODE5NTM2MzE0NzcxMTQ3MDk4MjU2ODMzNTc2MjIwNDI5ODQyNjc3NzMwMzQwODYwNjE2NTcxNzc5NjU4OTIxNDY4Mjc0NTUwOTc5NjYyMDkxNzEwNDU5MDk2MDgzMzYzNTc1Mjc0MjQzNzIyMzIzOTIxMjY5MDYyMjE0NjQyNzQyMTI0MzQ4MTY0MDUxNzE3MTk5MTkzODY3NTM3NTEzNjYzOTY1ODQzMDI5MjAxODA0OTE2MTEzNzMxODYzOTUzNjQ5MDkwNDgzNzMyMTkxNTQ2MTEwMjAxNTg0NzMxODg4NTE5NjA2MjE1OTkyNTgxNzk2MDg2NzUzOTE5NzUxMjkwMDI3MDI4NzczMTAwOTc5ODI5MzQ5NzA0MTUyMDEzNjg2MzU1MzM1MjIyNjU5MDY2NzE0NDQ2NDc4NzY3MTE5NDE4MjY3OTg5NTAyNzc4MjMzNzM3MjM4MjU1MTQxNzQyMjk4NTU3MDY2NzA2MTM0NzYwMjQwMzY3OTMzMzc5NzYzMTc5MTI1NTI4MDQwMzkxNjQwNTIyNTM5NjE5NTU0NTE0NTk4OTUxNTg0NjA3MjYwNzk1NzE1MDMyMjM4NTQ3ODMyMzA0NTY2MzQ4NjYzMTc0NzQwMDE2MzQ2NTU2MTM1ODc4MzgxNTYzODQ2NzU0MzQzMjk0NTIzNjc0NDI3NjQxNjAxNjAwNjE2NzI3NjEyMzc0MzI2NzY4ODA5NjAyNTE5MTAzOTk3NDY4OTg1NTg3Nzg4MjI3Njc5MzQ4NTgwNzk1OTkyOTkxNzMzMDg5MTUyMTg2MDg4OTU2MTg2MTQ0OTkyMDI5OTI2OTUxOTU0OTQyNjYwMDUxOTM0MDc5NzkxODI1NzA2MTExNzg0MDU2NDM2OTA2MDgxMDQ2MDQ5ODI0ODE1NDE0MTc5NTMzMDA2ODE4NzQ3NzgwNTQ5In0sInJfY3JlZGVudGlhbCI6bnVsbH0sInNpZ25hdHVyZV9jb3JyZWN0bmVzc19wcm9vZiI6eyJzZSI6IjEwMTUyMDI0OTk1MTUxMzcyOTgwNzI5NDM1MTQ5MzgyMzQxMDQ3MDIzNjI2NjA4MDc3ODg1NzQ2Mjg4MTE3MjI2OTAyNzM4OTY5OTEwMTU0NjgzNzM2MzI4MzQ2MDg0MjcwMDA3MTY2NTg3ODI5MjE2MzEzNDc4MDk3Njc0MTI0ODU2ODIyMjA4NzI0Nzk1NTE0ODgzOTYwMzE5NDc5OTg4NzAzNDUyNjI4NjYxMDc3MTg3OTIyMzA1NDc5MDE2NzQzOTk0NzYwMzE5NzI1OTExODk0MjM2NDMxMDkxMTIyNTUxNTU0NzgwODg0NjQ2MjE0MTUzMDUzMTM2NDMwMTk4OTA5MTM0OTk4OTM2NjY3MzI4ODI2MDcwNzEzMzk0NDg0NDI0ODUxNjkxMzUxNDc0NjAxMjIwODk2NTIyMDYzNDA5NzA4NDA1Njk2MzY5MjA0MzU0NzE1MDkxMzk2Mzc4Mzc3MzA0ODk3MzMwOTM0Mjc2NTQyNjE2NjAxNTk1ODI5NzgxOTg3NTMyNzkxMzIyNTgzOTE1Njk1OTY2MjM3MTc4Njg1NTMzNTE3MTQxNTAyNDE3MzQxMDIzMTA1MTczMjMwMTcwNzUzODYwMjgxNDAxODk4MDE5OTQwNjA2MzczOTYwMzYxNjA3NTE2NjgyMDg4MTc1NzU4ODA0Mzg4MTM5MTQ0MDkwMjg5MzI5NzMzNTQ1NDg4MjUyNjczNDIyODkzMzc1MzE5ODQ2OTMwOTIyNjIwNzAzMTEwMDgwODU5OTE4ODQ0MzgyOTQ3ODczMjAwNzA4MTY2MzA0NDk4ODk0MDA4NTMyIiwiYyI6IjIyNDQyNTM5MzYwMzYzNjQyODI1ODkxNTc5ODgzMDE5Mjc3Mjk0NTQ2MjUwMDEzNTM3MzI2OTY2NzM3MzE0NTUxMjEwMjU3MjU2NDU5In0sInJldl9yZWciOm51bGwsIndpdG5lc3MiOm51bGx9" + } + } + ], + "~thread": { + "thid": "c5fc78be-b355-4411-86f3-3d97482b9841" + }, + "~please_ack": { + "on": ["RECEIPT"] + }, + "~service": { + "recipientKeys": ["GVbwqUMqzxKaEVWjn1aPBfjJpYQHVejinpx8GCeEuQjW"], + "routingKeys": [], + "serviceEndpoint": "http://localhost:3000" + } + }, + "updatedAt": "2023-03-18T18:54:01.192Z" + }, + "id": "b88030f7-dc33-4e7e-bf4d-cdfaa6e51ebf", + "type": "DidCommMessageRecord", + "tags": { + "role": "sender", + "associatedRecordId": "be76cfbf-111b-4332-b1fe-7a1fea272188", + "threadId": "c5fc78be-b355-4411-86f3-3d97482b9841", + "protocolName": "issue-credential", + "messageName": "issue-credential", + "protocolMajorVersion": "1", + "protocolMinorVersion": "0", + "messageType": "https://didcomm.org/issue-credential/1.0/issue-credential", + "messageId": "578fc144-1e01-418c-b564-1523eb1e95b8" + } + } +} diff --git a/packages/anoncreds/src/updates/__tests__/__snapshots__/0.3.test.ts.snap b/packages/anoncreds/src/updates/__tests__/__snapshots__/0.3.test.ts.snap new file mode 100644 index 0000000000..1aacd46acc --- /dev/null +++ b/packages/anoncreds/src/updates/__tests__/__snapshots__/0.3.test.ts.snap @@ -0,0 +1,824 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`UpdateAssistant | AnonCreds | v0.3.1 - v0.4 should correctly update the credential exchange records for holders 1`] = ` +{ + "1-4e4f-41d9-94c4-f49351b811f1": { + "id": "1-4e4f-41d9-94c4-f49351b811f1", + "tags": { + "isDefault": true, + "linkSecretId": "Wallet: 0.3 Update AnonCreds - Holder", + }, + "type": "AnonCredsLinkSecretRecord", + "value": { + "_tags": { + "isDefault": true, + }, + "id": "1-4e4f-41d9-94c4-f49351b811f1", + "linkSecretId": "Wallet: 0.3 Update AnonCreds - Holder", + "metadata": {}, + "updatedAt": "2023-03-19T22:50:20.522Z", + "value": undefined, + }, + }, + "2c250bf3-da8b-46ac-999d-509e4e6daafa": { + "id": "2c250bf3-da8b-46ac-999d-509e4e6daafa", + "tags": { + "connectionId": undefined, + "credentialIds": [ + "f54d231b-ef4f-4da5-adad-b10a1edaeb18", + ], + "state": "done", + "threadId": "c5fc78be-b355-4411-86f3-3d97482b9841", + }, + "type": "CredentialRecord", + "value": { + "createdAt": "2023-03-18T18:54:00.023Z", + "credentialAttributes": [ + { + "mime-type": "text/plain", + "name": "name", + "value": "John", + }, + { + "mime-type": "text/plain", + "name": "age", + "value": "99", + }, + ], + "credentials": [ + { + "credentialRecordId": "f54d231b-ef4f-4da5-adad-b10a1edaeb18", + "credentialRecordType": "anoncreds", + }, + ], + "id": "2c250bf3-da8b-46ac-999d-509e4e6daafa", + "metadata": { + "_anoncreds/credential": { + "credentialDefinitionId": "A4CYPASJYRZRt98YWrac3H:3:CL:728265:TAG", + "schemaId": "A4CYPASJYRZRt98YWrac3H:2:Test Schema:5.0", + }, + "_anoncreds/credentialRequest": { + "link_secret_blinding_data": { + "v_prime": "6088566065720309491695644944398283228337587174153857313170975821102428665682789111613194763354086540665993822078019981371868225077833338619179176775427438467982451441607103798898879602785159234518625137830139620180247716943526165654371269235270542103763086097868993123576876140373079243750364373248313759006451117374448224809216784667062369066076812328680472952148248732117690061334364498707450807760707599232005951883007442927332478453073050250159545354197772368724822531644722135760544102661829321297308144745035201971564171469931191452967102169235498946760810509797149446495254099095221645804379785022515460071863075055785600423275733199", + "vr_prime": null, + }, + "link_secret_name": "walletId28c602347-3f6e-429f-93cd-d5aa7856ef3f", + "nonce": "131502096406868204437821", + }, + }, + "protocolVersion": "v1", + "state": "done", + "threadId": "c5fc78be-b355-4411-86f3-3d97482b9841", + "updatedAt": "2023-03-19T22:50:20.522Z", + }, + }, + "669093c0-b1f6-437a-b285-9cef598bb748": { + "id": "669093c0-b1f6-437a-b285-9cef598bb748", + "tags": { + "associatedRecordId": "93a327ad-2bf3-4ec4-b01c-bdd58ba2f6e3", + "messageId": "4d2c80b7-4a25-42ac-b8cf-a68b1374b9b7", + "messageName": "offer-credential", + "messageType": "https://didcomm.org/issue-credential/2.0/offer-credential", + "protocolMajorVersion": "2", + "protocolMinorVersion": "0", + "protocolName": "issue-credential", + "role": "receiver", + "threadId": "f9f79a46-a4d8-4ee7-9745-1b9cdf03676b", + }, + "type": "DidCommMessageRecord", + "value": { + "_tags": {}, + "associatedRecordId": "93a327ad-2bf3-4ec4-b01c-bdd58ba2f6e3", + "createdAt": "2023-03-18T18:54:01.134Z", + "id": "669093c0-b1f6-437a-b285-9cef598bb748", + "message": { + "@id": "4d2c80b7-4a25-42ac-b8cf-a68b1374b9b7", + "@type": "https://didcomm.org/issue-credential/2.0/offer-credential", + "credential_preview": { + "@type": "https://didcomm.org/issue-credential/2.0/credential-preview", + "attributes": [ + { + "mime-type": "text/plain", + "name": "name", + "value": "John", + }, + { + "mime-type": "text/plain", + "name": "age", + "value": "99", + }, + { + "mime-type": "text/plain", + "name": "height", + "value": "180", + }, + ], + }, + "formats": [ + { + "attach_id": "8430ddb8-b0c3-4074-8ded-f4dcfe80303d", + "format": "hlindy/cred-abstract@v2.0", + }, + ], + "offers~attach": [ + { + "@id": "8430ddb8-b0c3-4074-8ded-f4dcfe80303d", + "data": { + "base64": "eyJzY2hlbWFfaWQiOiJBNENZUEFTSllSWlJ0OThZV3JhYzNIOjI6QW5vdGhlclNjaGVtYTo1LjEyIiwiY3JlZF9kZWZfaWQiOiJBNENZUEFTSllSWlJ0OThZV3JhYzNIOjM6Q0w6NzI4MjY2OlRBRzIyMjIiLCJrZXlfY29ycmVjdG5lc3NfcHJvb2YiOnsiYyI6IjQwMTQ0MTA0NDg3MjM0NDU2MTc1NzYwMDc1NzMxNjUyNjg1MTk0MjE5MTk5NDk3NDczNTM4NjU4ODM3OTIyODMzNTEzMDg0Nzk2MDQ5IiwieHpfY2FwIjoiMzgxOTQyMjM1Mzc3MzYwODEyNjY0MDA4MjYzNDQxMDg2MDMwOTMyMjAxNzgzMjM3ODQxODQ5NDg3ODk2ODg1MTYwODY2MTY1MDM3NzI2MTIxNjU0MjcwOTg5NDY3NjAzNDExOTAzODk4MzUwMDAzNDIwODg3MzI4NTUwMTY2MTI1ODMyMjAxOTQzMTkwNzAxMDU4NTAwMDE5ODM1NjA1ODczNDYzOTkwODg3NzQ0NjY3MzU0MjM2Njc3MzcyODg0ODQyNjE5NTEwMTUwOTA2MjI1OTMzMjc1ODEyNjg2NDg3NTg5NjY3ODI3MjAwODcwOTQ0OTIyMjk5MzI3OTI4MDQ1MTk1OTIwMDI3NTc0MDQwNDA4ODU5MzAwMzY1MDYwODc3Nzg2ODkwOTE1MDU5NTA2ODc1OTI0NzE2OTI1MDM2MTc4Njg2NDE5NTYyMzcwODI4MTMzODY2Nzg3NzkyMDcwNjAyNDQzNTkzMTk2NzEzNzcyNDM2NTYzODI0MzkwMDIyNzg4MjU2MzA4NjU4OTc0OTEzMTk1ODYxODUwMTQ3ODE1Mjg5NzQwOTA4NDk1MjQ3NTAyNjYyNDc3NzQ2NTI5ODA3Mzg0OTgxODI5MDc3NTQ4OTI2NzExMDkzNzQ5MjM1ODU4NjUwNDc5NzE5NDI4MzUwMzAwNzUyNjQ0OTg1MTQ5MTMxNjA1NjUzMDIxMDYxNzkwMjY3MzQyNTY4NTkyNTY2MTQ0MDM5NzY4OTg0NTMyNDMzNzk0MzUzNjQ2Nzg1MjA3NDgzOTk2ODQ0OTcxNTgzNzY3NDQ5ODYyODgxMjMxMjI1MzM4MzAzMTQ4NzA0ODczMDEzNDM3MDgyNzY1MTk4OTY2MzE5NTM0OTkyNjk4MzMzMDQ0MDI3MjIyNTYyNTIzNzk3ODk5Mjk2MTQ1NDU5IiwieHJfY2FwIjpbWyJtYXN0ZXJfc2VjcmV0IiwiOTE5MTc5NzQ4MTE5NTg5MTY3Njc5MjQxODk5NzY0ODIwNTk0MDc5OTQxNzIzOTgzOTYyNzQ1MTczODM0NDQxMDQ3MjU4MDcyOTE4OTUzNjIzOTQ4MDMyNzI2NDgyNzI2MTEwOTk2Mjk3MDU3NTYwNjcwNzAxOTU1MTkxNDc0NjM0MzQ0ODMxMzg3NTk4NzI2MzMxMjc0NjI4NDU3Njk5NzczMDA1NDMwMDIxNzMwMzg4MzcwMTEyMjc3MzI2MzU4OTgwMTA3ODIzNzUzODc3MTU0NjIwMDkzMjE5MjYyNjAxNDM2NzMyNTgzNDI4Nzc4NDA4OTc0NTQyNzkzMDk0NTQ5MTczOTA3MzQ3OTUxNTc1NjM5NzU2NDg5MTA0Mzk0MTY3NzExMzY1MjM3OTI1MjAwNjk4OTg5NTI5MTQ3OTIzNTYzNDMyODgyMzgwMTg0NzU0NzkzODMwMTE3MTQ1MDAwMTI0NDYxNjkzOTcxMDQ5MjgzNDk1NTE4MDQxMDc5ODUyMzAwMjk0NDM1MjYzOTIwNDU0NTU3MzUxNDQ1MDM3NDI4MDg3OTk2Mzg2NjY3NjU3Nzk5OTYyNzQzNzIyNzA3NzczOTEzMzc0NzIxODUyNTQ3MjkwMTY5MjI5NTAzMTQxOTMwODYzNTk4NTExNjc4NDEyMDE0MzE2MDM2MzYxMzczNDcwOTQwMDEyODcwMDgwMDA2MzE0NzYxNzYzNzUyNzYwODk5MTQ3NzA1MTA0NzQyNjAxNjkxMzMxNjkzMDIwMjg2MjA2NzQ2NzE0MzI3NjU2MjA2NTMzMjk3NDg4MjU2NTM2NTQ3MzY4MjM2OTQ2MDM5NzAzMzc0OTMzNTE0NTc2NDg2NjQyNTY4MjgyNTY2MjMyNDU1NTU5MDY4MzE3NzU5NDM0ODU4NTI3MDg2NjQ0Il0sWyJoZWlnaHQiLCI5MjMwMzkyNDc1NjI4ODc1MjA4OTM0NjM0NzE4MjYzNzA4MDIzOTI1MDU0NjY2NDgzMzgxMzIyMzc3MDg1MjMxMjU4MTM4MzgwOTU1NTk3NDQxNTEyOTYwNDA2MjI3MjUwODgyNjA3NjExMDkwODk3MTM1NDcxNzAwMDIzNDcwOTM2ODg4MDE3NDY5Nzk0ODYzNDk4NzUyNTI3Njc3MjMwMTEwNzg0ODQzNzI0NDUyNTUzODYyOTA2MzM5MDc0OTIzNDU4NTQ3NDYzODcwNzU3OTg5MzMxNzk4OTI2MjM4MjUxMTM2NTYzNjM2MjIyOTQwNDkwMzY3MjQ2OTg0OTU2NTE5MTAzODcwNDE0MDM5NzM2MDE2MDY5MzA2NjQ0NjQzODI4OTgxMTE3OTM3NzYyNDAzODY1Mjc1MDU5MjEyOTY2NzIxOTU3MzM0MTM2ODEyMDI0OTE0MzA4MzAxMzk5MzM4NzMyOTIzNTA0MjA5MDM5ODMxMTc5NjU1NTkyNjg0MjMyMTIzMTI2Mjc4ODQzNDMyOTUwMTk1Mjg3MzE4ODI3NTM2MTMwNDQ3NzM3MTgwMjk3MDE0ODEzNDg3NDQyOTg2NjQ1NzQyNjEyMzE5NzQxNDY2MDMyNTg5OTU0NzYwNjE4MDU0MDUxMjAzMTE1NTAxNDcxNDExMzg3NzU0NDk5MzAwNTU4MTc5NjM5NDAxOTM0NTAzMTMyMDEzMjAzOTg2NzkyMTEzMDAzNTkwODg1NTc3NjgyMzU2NDY3MjA5NTUwNjQxODQxMDYyNTkzNDYyODIwODg3NzgxNDYyODM3ODkzODcxNDM4MzM3Mjc5MTcwMTExMTQ5MTU4NDMzNDE0ODI1NTkyNjcyODU2MzM5OTM4NTgyODg2NzM3OTIwMjc1MzI0MjEwMTUzMjE5MjI2OTYiXSxbImFnZSIsIjkxNTg1ODk3NDkwNzE0ODA3OTY2MDYzOTg5MjE1NTMxNDkyOTQwMDI5NDcyMTM4MjgwNjcxNjcyMjQ0NjY5MDc5NzIyNTQyMDU0NTU3NjY0MTcxMDI1NzM1NjQ4NTIwMTM4ODQ4ODAxNzIyMTc4MTcxMTA5NTc0MTMyNTExMzM1MDEwNTc5NzExMzcyODM5MjI3MDExOTg4MTUyMTEwMzI4MTE5MjkyMjI4NjM3MDU4MDQ3NzYwODYwOTQ0NTY3MzQxMjY4MTY4Mjk3NjE5MDM2ODEwMjYwODM2NDI1NDkwMzU3NjE4NzM4NTYxNTY2MTUxODQ3MzIxNzM1MjQ5ODk1MDU5NTY2OTQxODI5MjE0Nzc0MTA0NzYyNTQwMjcyMjk2NjE1NTE3NjUwMDcyNDQyMTI0NjY5MDEzMTc1ODAyMDk5MDQxMzk3MzE5ODQ0OTA2MDgwOTYxNTcyMTcwNjg2NzgzNDM1Mjg2MDUyMzE5ODY3ODExMDE5MjAxMDYwODM2OTM3Mzc0MzM0NDM5MTQxMDAzMTI3NTcyNjgzNTgwODI0OTkwOTg3MjE5MzU4NzkzOTM2NTU4Nzk3MjI0MDQzNTM1ODA5NzMyNzgxMjE1NzEwNjI1MjQzODYwNTk4MTk0MjU2MjAwODkwOTA3ODAzMDcyMTAzNzc3MzkwODk4MDczOTgyNjY3Njc1ODg0MjI3MjU0Mzc2OTI5Mjg3ODQyNDE0MTE0MjcwNDQwMTEzNDUxNjk4NzE5Nzc5NjQyNTI4MDA4NDM3Mzk5NjI0NTE3OTM4Nzg5MDc3ODE5ODA0MDY5MzcxOTM0NzExMTIyNTQyODU0OTg4MDA0Mjc4NDkwMjAxNTk2NjE0MjUwODc3NDYxMDczNjc3NTUzNzYxMTMyMTA5Nzg3NTQ2ODE1ODk5Njc2NCJdLFsibmFtZSIsIjYyNzgwNTIwMTM3MzI3NTUzMDc3MDg4NTE4NDg1NDYyMTA0NjEzMjEyNzY3ODUwMzYwNTc3NDQ4MDUxNTk5MTMxMTM1NTI2NzQ3Nzc2NzMzMDg1MDMwODcyMDE1OTM2MTI2NzE0MTIxMDgxMzg3ODU2MTkwMTkzMzI3ODY3OTE0NTEzODM2NTQ1OTY4Mjg1NTc5ODEyODMxMDI4ODc2Nzg1NzI3OTQ2MTEwNzg5Mzc0MjcyODgzMzkyOTgwNDkwODk3NDkwMTc5MDQ0ODM0NTgwMzQ2ODY4NDI2ODc0ODU4NTY1OTg4NTUyMDcwNjI1NDczNjM4MDM3Njc5NTU1NTk2MzE5MTc3Nzc5OTcxMTIxMjQzMjgyMTIyOTQ2NjY0ODMxOTgxMTg3MzQ3MzcyMjkxMjYwOTM3MzkzNDA1ODk5OTI0NjM4MzE3ODI5MDczODMxMjI4ODc1Njg5MTcyMTg4NjIyMDI5NzcxNzM5MTQ5NDY2Mzg3NTM5NjkyNDQ5NDU5MjczNDI5NzM5MjMzNjkyNTEzNDA5OTkyNDgxNTQ4ODk0NjAzNjM3MTYzNjA4MTM0MTAzMTk3Nzc3NTM4OTYwMDcyMjcyMzYyNzM4NDM1MTM3MDcyNzIxMjExMDYxNTg4MDE3ODczODg3MTEwNDA2OTk1NDQ4ODIwMDEzMDA5MjgyMzk0OTczMDMwMDI5MTY3NjQ5NzY1OTI1MTUxMzY4NTg5OTkyNzMyMDE1ODAwNjAzNzYxOTI3MTg3MDM4MDkxNDY3MDE1MjA3MzIwNDczMDM0NDA3MDIyNDA0NjQ4MTI0NTk2NjQwNjU1NjY1MTIzMzY5Njc0ODI2NDE3MjE2ODUxNTM4Njc1NTM3NzAwOTg4MTQzNzE1NTE3NzMwMTM4NjA4NzkxMjcyMzM0MDUyMzY4OCJdXX0sIm5vbmNlIjoiNDE0MzQ4Njg0NDk2OTAxNjkyMjI2OTY0In0=", + }, + "mime-type": "application/json", + }, + ], + "~service": { + "recipientKeys": [ + "DXubCT3ahg6N7aASVFVei1GNUTecne8m3iRWjVNiAw31", + ], + "routingKeys": [], + "serviceEndpoint": "http://localhost:3000", + }, + "~thread": { + "thid": "f9f79a46-a4d8-4ee7-9745-1b9cdf03676b", + }, + }, + "metadata": {}, + "role": "receiver", + "updatedAt": "2023-03-18T18:54:01.134Z", + }, + }, + "8788182f-1397-4265-9cea-10831b55f2df": { + "id": "8788182f-1397-4265-9cea-10831b55f2df", + "tags": { + "associatedRecordId": "2c250bf3-da8b-46ac-999d-509e4e6daafa", + "messageId": "c5fc78be-b355-4411-86f3-3d97482b9841", + "messageName": "offer-credential", + "messageType": "https://didcomm.org/issue-credential/1.0/offer-credential", + "protocolMajorVersion": "1", + "protocolMinorVersion": "0", + "protocolName": "issue-credential", + "role": "receiver", + "threadId": "c5fc78be-b355-4411-86f3-3d97482b9841", + }, + "type": "DidCommMessageRecord", + "value": { + "_tags": {}, + "associatedRecordId": "2c250bf3-da8b-46ac-999d-509e4e6daafa", + "createdAt": "2023-03-18T18:54:00.025Z", + "id": "8788182f-1397-4265-9cea-10831b55f2df", + "message": { + "@id": "c5fc78be-b355-4411-86f3-3d97482b9841", + "@type": "https://didcomm.org/issue-credential/1.0/offer-credential", + "credential_preview": { + "@type": "https://didcomm.org/issue-credential/1.0/credential-preview", + "attributes": [ + { + "mime-type": "text/plain", + "name": "name", + "value": "John", + }, + { + "mime-type": "text/plain", + "name": "age", + "value": "99", + }, + ], + }, + "offers~attach": [ + { + "@id": "libindy-cred-offer-0", + "data": { + "base64": "eyJzY2hlbWFfaWQiOiJBNENZUEFTSllSWlJ0OThZV3JhYzNIOjI6VGVzdCBTY2hlbWE6NS4wIiwiY3JlZF9kZWZfaWQiOiJBNENZUEFTSllSWlJ0OThZV3JhYzNIOjM6Q0w6NzI4MjY1OlRBRyIsImtleV9jb3JyZWN0bmVzc19wcm9vZiI6eyJjIjoiODUxODAxMDMyNzEzNDg5NzYxOTg5MzAzNjMzMDkzOTEyOTExMDUxNjI0OTQ0OTYzMTgzNzM2MDY3NDkwOTc2MDYxODEwMDgxODkxMzQiLCJ4el9jYXAiOiI4NDk0NDg4MjQzNTk2NTkwOTc2MjQzMjc0NDg4ODk2Mjc1NTcyODAyMTQ1ODE5NDQzNTE0NzQxMzk1NDI1NjM5MzQwMTczMDIzMTQ5NzI3MDY5NzMzMzQwODgzMTU4MzQ1NTYzOTA5OTcxNDMzNTg1MjMwODAxNTYyMTM0NjczNjM1ODg5NTA3Njg5ODQwOTgyODU5Mzg1NjA1MTc1NTkxNDYxOTkyMDExNzU2Mzg1MTI3MTQ3ODgxNDMwODEzNjYxNzY0MDU5MzE0ODk4MTc2NzQzMTQ5MjYzMDMwMDQ1NzMwMDMzMzI2NzgyMzg1OTY0NjcxMzg1ODQ2MzcxNjQ5MzQxMTg2MDM5NjE4MTQwOTIwMDUxMzg1MDAwNTYxMDcyMTc5NTEyMzc5Nzk0OTU4NjE1ODIyODI2OTExNzIwNTQyNTE0MTQ1NDc5MTAxOTUyMzM4MDMwMDY1MDk5NjcxOTU2OTMxMzE2NjE5MjM0NTQ0NTE5NTQ1ODQ1MzA4MzgxMjQyNTM0NDcyOTc3NjY0MjAwMjc2MTMyOTgxODE1ODAzNTIxOTExMzk4ODkxMjE0NjE1NzA1MDM2ODM2ODU1NDU1NzY4ODg4MTUxNDgzODAyNDcyODQyMzczNzE0MTI0NTYwMzIyNTI3NDE4MTEwNzYyMjgyNzY4NzMyNTIzMDQyMDA3MDY2OTk2ODIxMTQwMzE1NDg0NzI4NTM2NzIwNDI3MDg5MTI2NDk1NTAzMjc0ODQ4MDM3MjUzOTM3NjI3MDU2ODUzMTQ4NjE5NDA4NDYxOTI5NzEzMjM4MjEwNDc4MjcyMTIxNTUwNjQzODc4ODM1NDYwMzY1OTIwMjE3NTk5NDYyNDUzMDMyNDQ4MjYyMTM3NjE5ODY0OTU4MzA1MDE3MjA4OTYwNDc1MTQxODgwMTMiLCJ4cl9jYXAiOltbIm5hbWUiLCI1MDcyNzU2NDE2NDA2ODIxNzU1OTc0MzUxMTg0NjE1NjA4NDY2NTk3Mzk0NzA2MTY1NDg2ODAzMjc3MjMyMzQyOTk4MDA0MzY0OTU0MTczMzc0NDIwOTc5NTkwMDcyODgxNDgxNDA0MTg2OTExODg5NzQ4MTgzMzQ1OTk5NzQ0NzgxMTQ1MTMwNzEyNDIzODY0Nzc1MzQzNjAzNTk2NDM3Mzg4OTgzNTExNDAzODA0NjEyNjU1MDE5NzQ4MTI5NDk3ODY2NTcwMDQyMjcwNDQxNDQ5MjYwODY0NzgyMzI5MjAxNDEzMTc5ODU3NzA0MjM5OTMyMTg4NTc4NzE3MDczNzM3NjUyNzY5MzY5NDg4OTgxNzg2NDQwNTExODAzMjMzNDMxNzA4NDk4MTU2NTA0OTUzNzkzNjU2NjQ2NzMyNTU4MzQwNDI2MDI1MjA3NTk0OTIwMDY4OTc2OTQ4Nzg2OTUxNzM3MDIwNDQ0NTA5NzYyMDQ2MzIzNzA0MDQ3MjU1ODU3NDE5ODE3MDc5NTI3NDgzNTE1NDY2NTAyMDkzOTY1NDMzMzk3MjQ1MzA4MjQ5MDgyMTQ4Mjc4NDA1MzI5Njg1Mjc0MDYwNjk0MzI0MTI2ODgxMjkyMDIyMjY1ODczMjk5MDU0NDU1OTA5NzkyNjUwNjAyMTk0NjUzMjYxMDk0ODYwOTc2NzA4ODE1ODgwMjExMTY0MTkwMDM0NjY0MzI2MDc3NjcwNzkyMDE4NTE2MzMzNDI3NjkwODYwMjIxODEwMzk5MDgxMjc5NjAwNTYzMjk3MjI0NjM0MDM0NjcxNTIwODE5MzU3NzQ0Njk2NzU1Njg1NDI2NjIzMzAwMjQ3MDUwODE4NTQ2MDM2NjA0NjMxNjcyNzE5MjI0NDA4NTE2NDM4NTgxMDM5Njk4NzI0MSJdLFsibWFzdGVyX3NlY3JldCIsIjU2MzYzNTgyMDQ5Mjg4OTY1OTg1MDA4NzgyMzU0NjgyNjMwNDkxMzQ3MTM1NDIxNTAyMDEyMTIwMzI4MDI4ODIyMjUyMzg4NjgwNTMwNTgxMTcwMTgwNDU1MTcyNTc3ODkyMTEyMTY1OTM0Mjk5NjUyNzAxNDExMzUyNDkzMzkyODU0ODI4NzMyMDQzMDI0MDI0MzM0MzMzNzc0NjEyOTEzOTUyMjAzNjM1NDk2MDQ0ODMzMDI5NDE2NjUwOTU5NjE0ODgzNTUwOTMxNzgzNTA5MzE1Nzg4MDEyODQ0MzAwMDQwMDE5MTY5MTc3NTI1OTgxMTU3OTkwNjQzMDcyMjQyNzcxMjU0MTYyNzMxOTU4NzI2Nzc1NjYwMjkxODIzMDcyNDk1Mzg0NzM5MTcwODc4ODMxNzkxMjQzMjEzMjU5MzA5ODQxNjU3MjUwOTg1NzMxMjEyNzE2MDM2MDY3MDUxNjM2NzA0MjA1NDEzMDk2MDU3MTA2NTM2MTI2ODUyNDU0NzcwMzQzMTMwMTczMjAwNjEzMDIxOTE4MzgzMDQxOTU4MTkwOTE2NzQ0NjU4NTI0ODA1NjM4Mzk2OTY3OTA3MzIwNjY1MDU1MzcwMjY0NjAxMDczMjc5NDMyNjM5MjM3Njc1NTA0OTg1NzQyNTI4NjYwMTAyMDEzNzIxMzA2MTE4MTg0NDk1MTEyNDQ2NDYyNDc2NTkwMjYxODkxMjA0OTQxOTA4MjMyNzMzNDA3MTg4MDA3NzE2NTA2OTUzMDY0Nzc5NDk5ODExNzI0ODI5NjcyNjY2NzIyNjIzOTAxMTc1OTk0NTIyNjkwMjk1ODI0MDgyNzY5NjQ0NDYxOTAxMDk2NzI3MTE5NzAzMjUzNzI4NjY3MTU1MzA5MDYzNDUyNDY2MDY3NzU5NzIwOTgyNDA3MiJdLFsiYWdlIiwiMTM2NTQxMjE0MjM5MTcyNDQxNzQ1MjU3MjcyMDI3MTA4NDYwMzU0MjgxMTA2OTA2MzYwNDIwMDE0NjUyMDIxMDgyNDEzODM2ODEyMjk3NjY3ODk2MTYzNDkzMjM4NDIxNDI4NjMyNTMxODE0ODk4NzkwMDg4OTg2NjgyMTE2OTAyMzc4NDgwNTE4OTUxNDExNzg1OTk3NTk5MDMyNDYxNjExNjIyMDUyNjMzMDQ5ODYxMzc5MTQzNzI4MTM5MTUyMDkyMzI0ODc3MjMxMTYwNTgzNzA5NjE0MzA1NzQ1MjA5MjQwNjU2MDU4NjY3OTMwODEzNzYyNDY5MDc2ODc5MTk1Nzg0Nzg4NTE2NjI3MjgxMDY0NjE3MzgzMDc4Njc5MTkwODIwMzQwNTgwNDY2MjU3ODU3NjA1MTc2MTg4NTI3OTMxMDI4MTMzNTY5Njc0Mzg2ODAwMTA2MDE2MDg1Nzc0OTcyMzI1NTAyNDA2MTY0OTY0MjU2OTUxNDI3ODAxMTQzNTQxMzUzMzI0Nzg0MzA5OTY4MjIyOTU1NDk4Njk3NTAwMDUwMzc0MDg0NjIwNzQ4MTk0NzIyMTI2NjE2OTY3OTY3Mzc1NTM3Nzc5NTc4NTMwMDIxODExNTA2MTIxNjcxMDUwNDgzNTM2MjA3Njc3MTg5NDQwNjEwNzk0NTcyNzI5MTgzMzAyMjM1MDkxMDg4NTU2ODc5NTg3OTE3MDMzMzQyODcyMzg2NDQ5MTQ0NzgwMDYyNjc4MzA3NzE4MzU1MjQ5MTUxNjc5MDA1MzkxNDA5NDE4OTQxMjEzNDkxMjQyMjg2NTAwODcyMzQxNDI3Nzk1MjQ1ODYzODE2MDY2NDY3NDkxOTg4OTU3MDEwNDIxNDA3NDkyMDUxOTc0NTMwNjIxOTk1ODU0ODczNTM5Mjk3MyJdXX0sIm5vbmNlIjoiNzk3NjAzMjE3NzA5MzM1MzAwMTcwODI4In0=", + }, + "mime-type": "application/json", + }, + ], + "~service": { + "recipientKeys": [ + "GVbwqUMqzxKaEVWjn1aPBfjJpYQHVejinpx8GCeEuQjW", + ], + "routingKeys": [], + "serviceEndpoint": "http://localhost:3000", + }, + }, + "metadata": {}, + "role": "receiver", + "updatedAt": "2023-03-18T18:54:00.025Z", + }, + }, + "93a327ad-2bf3-4ec4-b01c-bdd58ba2f6e3": { + "id": "93a327ad-2bf3-4ec4-b01c-bdd58ba2f6e3", + "tags": { + "connectionId": undefined, + "credentialIds": [], + "state": "offer-received", + "threadId": "f9f79a46-a4d8-4ee7-9745-1b9cdf03676b", + }, + "type": "CredentialRecord", + "value": { + "createdAt": "2023-03-18T18:54:01.133Z", + "credentials": [], + "id": "93a327ad-2bf3-4ec4-b01c-bdd58ba2f6e3", + "metadata": {}, + "protocolVersion": "v2", + "state": "offer-received", + "threadId": "f9f79a46-a4d8-4ee7-9745-1b9cdf03676b", + "updatedAt": "2023-03-19T22:50:20.522Z", + }, + }, + "STORAGE_VERSION_RECORD_ID": { + "id": "STORAGE_VERSION_RECORD_ID", + "tags": {}, + "type": "StorageVersionRecord", + "value": { + "createdAt": "2023-03-18T18:53:44.041Z", + "id": "STORAGE_VERSION_RECORD_ID", + "metadata": {}, + "storageVersion": "0.4", + "updatedAt": "2023-03-19T22:50:20.522Z", + }, + }, + "b71d455b-9437-4ef8-b4f3-b6a0dd6bbfb3": { + "id": "b71d455b-9437-4ef8-b4f3-b6a0dd6bbfb3", + "tags": { + "associatedRecordId": "2c250bf3-da8b-46ac-999d-509e4e6daafa", + "messageId": "578fc144-1e01-418c-b564-1523eb1e95b8", + "messageName": "issue-credential", + "messageType": "https://didcomm.org/issue-credential/1.0/issue-credential", + "protocolMajorVersion": "1", + "protocolMinorVersion": "0", + "protocolName": "issue-credential", + "role": "receiver", + "threadId": "c5fc78be-b355-4411-86f3-3d97482b9841", + }, + "type": "DidCommMessageRecord", + "value": { + "_tags": {}, + "associatedRecordId": "2c250bf3-da8b-46ac-999d-509e4e6daafa", + "createdAt": "2023-03-18T18:54:01.369Z", + "id": "b71d455b-9437-4ef8-b4f3-b6a0dd6bbfb3", + "message": { + "@id": "578fc144-1e01-418c-b564-1523eb1e95b8", + "@type": "https://didcomm.org/issue-credential/1.0/issue-credential", + "credentials~attach": [ + { + "@id": "libindy-cred-0", + "data": { + "base64": "eyJzY2hlbWFfaWQiOiJBNENZUEFTSllSWlJ0OThZV3JhYzNIOjI6VGVzdCBTY2hlbWE6NS4wIiwiY3JlZF9kZWZfaWQiOiJBNENZUEFTSllSWlJ0OThZV3JhYzNIOjM6Q0w6NzI4MjY1OlRBRyIsInJldl9yZWdfaWQiOm51bGwsInZhbHVlcyI6eyJhZ2UiOnsicmF3IjoiOTkiLCJlbmNvZGVkIjoiOTkifSwibmFtZSI6eyJyYXciOiJKb2huIiwiZW5jb2RlZCI6Ijc2MzU1NzEzOTAzNTYxODY1ODY2NzQxMjkyOTg4NzQ2MTkxOTcyNTIzMDE1MDk4Nzg5NDU4MjQwMDc3NDc4ODI2NTEzMTE0NzQzMjU4In19LCJzaWduYXR1cmUiOnsicF9jcmVkZW50aWFsIjp7Im1fMiI6IjMyMTIwMDQ1ODc4MzIxMjcyMzA1ODI5MTc3NzMzMTIwNzE1OTY5NDEyNjkwNjUyNDQ4OTc0MTA4NzEzNjU0ODc3NTg2MzIzMTI3ODk2IiwiYSI6IjIyMjY0MTYwNjIwODcwNDUyNTExMjcyMzE1MzMzMDA0MjQzMzY3NTM2NzM3NDMwNjM1NjExMjcwMDkwOTE4NDMwNzc0ODEzMjAzNjQwNjMxMjIyNDczMzk0MjQ4MTgzMDIzMjIyNzExNDUwMzQxMDcxOTQyNDQwMDgwMjY2Nzk1Mzg5Mzg5Njc1NjYwOTUzNTQyMDE4OTA3NjQ3NzI4OTQ4NjY1MzA2Njg0NjExNDU1NTI5NzM5OTY1NDcyMjQ2NDQxMzE1NzAxMzM1ODc1MDY3MjExMDk3NzcyOTgwMjU1NDIxMDMzMTI1MjAyMTQzNDk3NjMyOTAyMjM1NDAyMzU5OTA1MzY5MzE4MjI1NTc4MjUxNjY4NTYzNzc1NTY0MDM2MjUxNzE0Mzk3MTEzNjQ3OTg0MjcxMTE5MTU2NDQ3NjI1OTk1NjE5MjAwMDk4MTgzNzY1NjkzMTg1ODEzNjA1NDU3OTQwMzE0MDU2MDkzMTI2MzQ3OTU5MzYwODIyMzg0OTEzODg3Mjg3ODI2NjkyNDIyNDMyNDUwMDA5OTYxNjQ2MjMzNTE3MjY3NDU1OTkyMjA3MTE3Mzk5NzU1NjY3MTA3MzM1NTQ0MzEwNDQwNDE1NDE5NTk5NTA1OTgxMzkwMjk5NDUxNzQyODg4NDg0MTc0NTU5MDA5NDgwNjU5MDk2Nzg2ODI2MDgxNzc3MzcwNTk1MTU3OTg5NjQ1MDYxNDI2OTA2ODM2MDk5NTU5MDQ0MDI4ODM2MzYwOTM2MDkwOTkxNjA1OTU0NTM2OTQxMjgxODQwNzk2MjkxODc0ODk2NDEzNTM5IiwiZSI6IjI1OTM0NDcyMzA1NTA2MjA1OTkwNzAyNTQ5MTQ4MDY5NzU3MTkzODI3Nzg4OTUxNTE1MjMwNjI0OTcyODU4MzEwNTY2NTgwMDcxMzMwNjc1OTE0OTk4MTY5MDU1OTE5Mzk4NzE0MzAxMjM2NzkxMzIwNjI5OTMyMzg5OTY5Njk0MjIxMzIzNTk1Njc0MjkzMDExMzE1MTE0OTUxODg0MDQ5ODkyNjAwNTY3NTgzMTc4ODkwMyIsInYiOiI2NTE5ODc4MzYyODQ5NzExNDY4NDkyNDM1OTM3MDU4NzMzOTYxMTkxNjA3NjI4MzUzNjkxMDg1MzM5MDMwNDU0OTkyODc0ODYyODkyNDg4ODYzNTA3MDQ1MTM1MzA4ODI1NDA2NzYwMTQwNDQzNzM0NDYzODE5NTM2MzE0NzcxMTQ3MDk4MjU2ODMzNTc2MjIwNDI5ODQyNjc3NzMwMzQwODYwNjE2NTcxNzc5NjU4OTIxNDY4Mjc0NTUwOTc5NjYyMDkxNzEwNDU5MDk2MDgzMzYzNTc1Mjc0MjQzNzIyMzIzOTIxMjY5MDYyMjE0NjQyNzQyMTI0MzQ4MTY0MDUxNzE3MTk5MTkzODY3NTM3NTEzNjYzOTY1ODQzMDI5MjAxODA0OTE2MTEzNzMxODYzOTUzNjQ5MDkwNDgzNzMyMTkxNTQ2MTEwMjAxNTg0NzMxODg4NTE5NjA2MjE1OTkyNTgxNzk2MDg2NzUzOTE5NzUxMjkwMDI3MDI4NzczMTAwOTc5ODI5MzQ5NzA0MTUyMDEzNjg2MzU1MzM1MjIyNjU5MDY2NzE0NDQ2NDc4NzY3MTE5NDE4MjY3OTg5NTAyNzc4MjMzNzM3MjM4MjU1MTQxNzQyMjk4NTU3MDY2NzA2MTM0NzYwMjQwMzY3OTMzMzc5NzYzMTc5MTI1NTI4MDQwMzkxNjQwNTIyNTM5NjE5NTU0NTE0NTk4OTUxNTg0NjA3MjYwNzk1NzE1MDMyMjM4NTQ3ODMyMzA0NTY2MzQ4NjYzMTc0NzQwMDE2MzQ2NTU2MTM1ODc4MzgxNTYzODQ2NzU0MzQzMjk0NTIzNjc0NDI3NjQxNjAxNjAwNjE2NzI3NjEyMzc0MzI2NzY4ODA5NjAyNTE5MTAzOTk3NDY4OTg1NTg3Nzg4MjI3Njc5MzQ4NTgwNzk1OTkyOTkxNzMzMDg5MTUyMTg2MDg4OTU2MTg2MTQ0OTkyMDI5OTI2OTUxOTU0OTQyNjYwMDUxOTM0MDc5NzkxODI1NzA2MTExNzg0MDU2NDM2OTA2MDgxMDQ2MDQ5ODI0ODE1NDE0MTc5NTMzMDA2ODE4NzQ3NzgwNTQ5In0sInJfY3JlZGVudGlhbCI6bnVsbH0sInNpZ25hdHVyZV9jb3JyZWN0bmVzc19wcm9vZiI6eyJzZSI6IjEwMTUyMDI0OTk1MTUxMzcyOTgwNzI5NDM1MTQ5MzgyMzQxMDQ3MDIzNjI2NjA4MDc3ODg1NzQ2Mjg4MTE3MjI2OTAyNzM4OTY5OTEwMTU0NjgzNzM2MzI4MzQ2MDg0MjcwMDA3MTY2NTg3ODI5MjE2MzEzNDc4MDk3Njc0MTI0ODU2ODIyMjA4NzI0Nzk1NTE0ODgzOTYwMzE5NDc5OTg4NzAzNDUyNjI4NjYxMDc3MTg3OTIyMzA1NDc5MDE2NzQzOTk0NzYwMzE5NzI1OTExODk0MjM2NDMxMDkxMTIyNTUxNTU0NzgwODg0NjQ2MjE0MTUzMDUzMTM2NDMwMTk4OTA5MTM0OTk4OTM2NjY3MzI4ODI2MDcwNzEzMzk0NDg0NDI0ODUxNjkxMzUxNDc0NjAxMjIwODk2NTIyMDYzNDA5NzA4NDA1Njk2MzY5MjA0MzU0NzE1MDkxMzk2Mzc4Mzc3MzA0ODk3MzMwOTM0Mjc2NTQyNjE2NjAxNTk1ODI5NzgxOTg3NTMyNzkxMzIyNTgzOTE1Njk1OTY2MjM3MTc4Njg1NTMzNTE3MTQxNTAyNDE3MzQxMDIzMTA1MTczMjMwMTcwNzUzODYwMjgxNDAxODk4MDE5OTQwNjA2MzczOTYwMzYxNjA3NTE2NjgyMDg4MTc1NzU4ODA0Mzg4MTM5MTQ0MDkwMjg5MzI5NzMzNTQ1NDg4MjUyNjczNDIyODkzMzc1MzE5ODQ2OTMwOTIyNjIwNzAzMTEwMDgwODU5OTE4ODQ0MzgyOTQ3ODczMjAwNzA4MTY2MzA0NDk4ODk0MDA4NTMyIiwiYyI6IjIyNDQyNTM5MzYwMzYzNjQyODI1ODkxNTc5ODgzMDE5Mjc3Mjk0NTQ2MjUwMDEzNTM3MzI2OTY2NzM3MzE0NTUxMjEwMjU3MjU2NDU5In0sInJldl9yZWciOm51bGwsIndpdG5lc3MiOm51bGx9", + }, + "mime-type": "application/json", + }, + ], + "~please_ack": { + "on": [ + "RECEIPT", + ], + }, + "~service": { + "recipientKeys": [ + "GVbwqUMqzxKaEVWjn1aPBfjJpYQHVejinpx8GCeEuQjW", + ], + "routingKeys": [], + "serviceEndpoint": "http://localhost:3000", + }, + "~thread": { + "thid": "c5fc78be-b355-4411-86f3-3d97482b9841", + }, + }, + "metadata": {}, + "role": "receiver", + "updatedAt": "2023-03-18T18:54:01.369Z", + }, + }, + "e1e7b5cb-9489-4cb5-9edd-77aa9b3edb64": { + "id": "e1e7b5cb-9489-4cb5-9edd-77aa9b3edb64", + "tags": { + "associatedRecordId": "2c250bf3-da8b-46ac-999d-509e4e6daafa", + "messageId": "2a6a3dad-8838-489b-aeea-deef649b0dc1", + "messageName": "request-credential", + "messageType": "https://didcomm.org/issue-credential/1.0/request-credential", + "protocolMajorVersion": "1", + "protocolMinorVersion": "0", + "protocolName": "issue-credential", + "role": "sender", + "threadId": "c5fc78be-b355-4411-86f3-3d97482b9841", + }, + "type": "DidCommMessageRecord", + "value": { + "associatedRecordId": "2c250bf3-da8b-46ac-999d-509e4e6daafa", + "createdAt": "2023-03-18T18:54:01.098Z", + "id": "e1e7b5cb-9489-4cb5-9edd-77aa9b3edb64", + "message": { + "@id": "2a6a3dad-8838-489b-aeea-deef649b0dc1", + "@type": "https://didcomm.org/issue-credential/1.0/request-credential", + "requests~attach": [ + { + "@id": "libindy-cred-request-0", + "data": { + "base64": "eyJwcm92ZXJfZGlkIjoiY3F4ZW8ybzVIWmVjYWc3VHo5aTJXcTRqejQxTml4THJjZW5HV0s2QmJKVyIsImNyZWRfZGVmX2lkIjoiQTRDWVBBU0pZUlpSdDk4WVdyYWMzSDozOkNMOjcyODI2NTpUQUciLCJibGluZGVkX21zIjp7InUiOiI3OTE4NzUwNzgzMzQxMjU3ODU3OTUzMjc2OTU5MjcxOTcyMDQwMjQxMTU1MzcyODEwOTQ2NTEwMjk5MDA1ODEyMTcwNjkwMjIzMTQ2ODU0NDk1MTI1NjQ1MTg3ODQxNzk0NjA3OTUwOTQ1OTM3MDYxMjk1ODgwMjIxMzE2NTg1MTIyNDY1Mzk1MjAwNDQ3MDIwNzAxNDA0NjEwMDc4MzkyMjA4NzkxMjk5NzYwMzM4OTIxNDMzMDc1Njk2ODU0NTY3MTIzNjYxNTYwNDMwNDE3NzQwMzc5MzA4NDQzODcyOTU1NzAwNTk1MTg2NzcxODY3MzM5NzQ5NDgzODYxNTQ2NTE2MTU4NTM5MjkyNDQzNTQ3OTg3MDUwMzE4OTAyOTI5OTgyNzMzMDk1ODk4MDIyMjg2OTk1OTQwMjkzMTg3NTg5NDgwNTgwNTAwNjM0NzAyNjQxNDM0MTgxNjIwMTU4OTU3MzUyNTE1OTA4NDE2MjI4MDQ0NDA2OTU4MTk1MDg4Mjc0ODI4Njc3OTQxMDgxOTczOTg3NjU1MDEzNDUxMzA4ODQyMjYyMzY4MTQzOTExNjIxMTE0NzYyNTk3Nzg1MDczMTM4MDg3NTQ2MDIyMzc1NjQxODQ5ODI2OTg2MjYwMDE5NTAzNzE3OTk0NjM3MzIyNDExNTgzNTY0MTQ1NjcwMTM5OTQ1MjQxOTU2Njk2MDQ3MTQzNjA4NjQ0MDM5OTg2NTYwODUyMzA1MTczMjUxMTUyMDIzODI5NjI3MzQyODM2NDI3MjkwNDQ5NTA3OTY0Nzk4MTQ2NjkzOTUxMDkwNzUwOTAyNiIsInVyIjpudWxsLCJoaWRkZW5fYXR0cmlidXRlcyI6WyJtYXN0ZXJfc2VjcmV0Il0sImNvbW1pdHRlZF9hdHRyaWJ1dGVzIjp7fX0sImJsaW5kZWRfbXNfY29ycmVjdG5lc3NfcHJvb2YiOnsiYyI6Ijk4MjIzNzMzMDcwMDk1NjM0MzU5MTE2MDEyOTgzMDcyMjc5MjcxMzk1MTg0NjA0NDcxNDQ5NzA1Nzg0MTEyNDAyODMwNzIyMTUxMzIxIiwidl9kYXNoX2NhcCI6IjU5ODA0MTY4ODAxODk1NDAzMjkwMzczMTA3NzA4MzUwODc1NjY4MDcyNDY3ODAyOTcyOTIyNjUzNDE5ODU2MjMyNTIzNDI4OTUxODQ4NjEyNDE1MjM5Nzk3Mjk5ODY2MzIxNjU5NDQ1MTM1NzQ4NzU2MDY1NjgyOTY5MjY4ODI5MTYyMDA0NjQ4NzYwMzg4NTg4NDkyNjg1NDI1MTg1MDU2OTAxOTkxMjcwMzYwMDk3MDc5NjEyNTYxMzY4NzU1OTcwMjY5MjI4MDYzMjMyODU0NzI0NDkyOTA5Njg5MDMyOTg4NjYyMjk5Mzg3MDU2NDEwNjU5MDU3MDUwNjE0MDQ2NzE1NjA0NTgyMzM2NTg4MjMxMjI3MTEzMDEzMDQxMTA0NTU2NzM1MDE3ODUwNzUzODcwNjc2ODYxNDA4MjA0NzkzMDIzNTYwMDEwNTEzODAwNzA4MjAyNjAyNjQ0Mjg2NzI4NjAyOTk5MzU5MDcwODQxMTQ5MTAzMjA5MzY0ODkyMzMzMDYwMDgzMTA5NDIzOTQ5NzE4NTk5NjEzMzk2NjIyMjc4MTExMzk5ODU0MTcyMjMyNTQzOTk1Njc5NDk3Mjk1Nzc1NzA0MjA0MTQxOTU2NDI1MDc4NjYzMzgwMDA1Nzc2ODY2MTcxNTY4OTU1NjE4NjAwMTA2NzkxMjIyNDkyODA2NzI1ODU1NDY2Nzk4OTEzMTc2NDcxMDY3MTk5ODQ2ODEwNDI5MDIzMDc3ODI3OTc1OTIzMDIzNjU3MTg0NjkwNzE0MjkxNDk0MDc5MTM1NzYyOTUxNTc0MjMzNjMwMjExNDQ1Njc3NzE1Mzg3Mzc1NjkyMjAzODE3NDczNDk5NDExNzE5MzIwMjczNzExOTIzMzM3MzYzMTAyOTkwMDcyMjE2MjYzMzUxMzMxNTk4ODk1OTU3MzU1MDc1NTEzODE0NTUwNzkyMCIsIm1fY2FwcyI6eyJtYXN0ZXJfc2VjcmV0IjoiNTY0MTY1NjA2NjM0MTUwODgzMjg2MjM1NDM1MjUyODQ3MjAxMTk4NTQyNDAxMjYzNTY2MDQ2MTA3OTU3NzI4NTQ0MTMwMjgzNjUyNjQzOTI0NDc2NTU2NTIzNzg0ODI1OTgyMzMwMzc4NDI4OTM0MDkxNDcwNDM0OTAwMTM3NzkwNDkxMjM4NTA4ODk2ODQxMzkwNjQ4MTA1NzY5ODYzMzI1OTAzODI1Mjk1MjU2OTQ0NSJ9LCJyX2NhcHMiOnt9fSwibm9uY2UiOiIxMzE1MDIwOTY0MDY4NjgyMDQ0Mzc4MjEifQ==", + }, + "mime-type": "application/json", + }, + ], + "~service": { + "recipientKeys": [ + "cqxeo2o5HZecag7Tz9i2Wq4jz41NixLrcenGWK6BbJW", + ], + "routingKeys": [], + "serviceEndpoint": "http://localhost:3001", + }, + "~thread": { + "thid": "c5fc78be-b355-4411-86f3-3d97482b9841", + }, + }, + "metadata": {}, + "role": "sender", + "updatedAt": "2023-03-18T18:54:01.099Z", + }, + }, +} +`; + +exports[`UpdateAssistant | AnonCreds | v0.3.1 - v0.4 should correctly update the schema and credential definition, and create link secret records for issuers 1`] = ` +{ + "1-4e4f-41d9-94c4-f49351b811f1": { + "id": "1-4e4f-41d9-94c4-f49351b811f1", + "tags": { + "isDefault": true, + "linkSecretId": "Wallet: 0.3 Update AnonCreds - Issuer", + }, + "type": "AnonCredsLinkSecretRecord", + "value": { + "_tags": { + "isDefault": true, + }, + "id": "1-4e4f-41d9-94c4-f49351b811f1", + "linkSecretId": "Wallet: 0.3 Update AnonCreds - Issuer", + "metadata": {}, + "updatedAt": "2023-03-19T22:50:20.522Z", + "value": undefined, + }, + }, + "1545e17d-fc88-4020-a1f7-e6dbcf1e5266": { + "id": "1545e17d-fc88-4020-a1f7-e6dbcf1e5266", + "tags": { + "credentialDefinitionId": "did:indy:bcovrin:test:A4CYPASJYRZRt98YWrac3H/anoncreds/v0/CLAIM_DEF/728266/TAG2222", + "issuerId": "did:indy:bcovrin:test:A4CYPASJYRZRt98YWrac3H", + "methodName": "indy", + "schemaId": "did:indy:bcovrin:test:A4CYPASJYRZRt98YWrac3H/anoncreds/v0/SCHEMA/AnotherSchema/5.12", + "tag": "TAG2222", + "unqualifiedCredentialDefinitionId": "A4CYPASJYRZRt98YWrac3H:3:CL:728266:TAG2222", + }, + "type": "AnonCredsCredentialDefinitionRecord", + "value": { + "credentialDefinition": { + "issuerId": "did:indy:bcovrin:test:A4CYPASJYRZRt98YWrac3H", + "schemaId": "did:indy:bcovrin:test:A4CYPASJYRZRt98YWrac3H/anoncreds/v0/SCHEMA/AnotherSchema/5.12", + "tag": "TAG2222", + "type": "CL", + "value": { + "primary": { + "n": "92672464557302826159958381706610232890780336783477671819498833000372263812875113518039840314305532823865676182383425212337361529127538393953888294696727490398569250059424479369124987018050461872589017845243006613503064725987487445193151580781503573638936354603906684667833347097853363102011613363551325414493877438329911648643160153986822516630519571283817404410939266429143144325310144873915523634615108054232698216677813083664813055224968248142239446186423096615162232894052206134565411335121805318762068246410255953720296084525738290155785653879950387998340378428740625858243516259978797729172915362664095388670853", + "r": { + "age": "66774168049579501626527407565561158517617240253618394664527561632035323705337586053746273530704030779131642005263474574499533256973752287111528352278167213322154697290967283640418150957238004730763043665983334023181560033670971095508406493073727137576662898702804435263291473328275724172150330235410304531103984478435316648590218258879268883696376276091511367418038567366131461327869666106899795056026111553656932251156588986604454718398629113510266779047268855074155849278155719183039926867214509122089958991364786653941718444527779068428328047815224843863247382688134945397530917090461254004883032104714157971400208", + "height": "36770374391380149834988196363447736840005566975684817148359676140020826239618728242171844190597784913998189387814084045750250841733745991085876913508447852492274928778550079342017977247125002133117906534740912461625630470754160325262589990928728689070499835994964192507742581994860212500470412940278375419595406129858839275229421691764136274418279944569154327695608011398611897919792595046386574831604431186160019573221025054141054966299987505071844770166968281403659227192031982497703452822527121064221030191938050276126255137769594174387744686048921264418842943478063585931864099188919773279516048122408000535396365", + "master_secret": "26619502892062275386286102324954654427871501074061444846499515284182097331967223335934051936866595058991987589854477281430063143491959604612779394547177027208671151839864660333634457188140162529133121090987235146837242477233778516233683361556079466930407338673047472758762971774183683006400366713364299999136369605402942210978218705656266115751492424192940375368169431001551131077280268253962541139755004287154221749191778445668471756569604156885298127934116907544590473960073154419342138695278066485640775060389330807300193554886282756714343171543381166744147102049996134009291163457413551838522312496539196521595692", + "name": "86741028136853574348723360731891313985090403925160846711944073250686426070668157504590860843944722066104971819518996745252253900749842002049747953678564857190954502037349272982356665401492886602390599170831356482930058593126740772109115907363756874709445041702269262783286817223011097284796236690595266721670997137095592005971209969288260603902458413116126663192645410011918509026240763669966445865557485752253073758758805818980495379553872266089697405986128733558878942127067722757597848458411141451957344742184798866278323991155218917859626726262257431337439505881892995617030558234045945209395337282759265659447047", + }, + "rctxt": "71013751275772779114070724661642241189015436101735233481124050655632421295506098157799226697991094582116557937036881377025107827713675564553986787961039221830812177248435167562891351835998258222703796710987072076518659197627933717399137564619646356496210281862112127733957003638837075816198062819168957810762822613691407808469027306413697001991060047213339777833838291591976754857934071589843434238025803790508552421154902537027548698271140571140256835534208651964449214890690159171682094521879102663244464066621388809286987873635426369915309596945084951678722672915158041830248278889303704844284468270547467324686757", + "s": "14126994029068124564262196574803727042317991235159231485233854758856355239996741822278406673337232628669751727662479515044513565209261235580848666630891738643990084502393352476512637677170660741636200618878417433799077613673205726221908822955109963272016538705991333626487531499501561952303907487494079241110050020874027756313402672435051524680914533743665605349121374703526870439925807395782970618162620991315112088226807823652545755186406850860290372739405126488851340032404507898084409367889215777693868794728141508635105180827151292046483128114528214465463152927678575672993454367871685772245405671312263615738674", + "z": "90415953543044389740703639345387867170174070770040351538453902580989033567810029650534915348296084212079064544906463014824475317557221991571331212308335167828473551776349999211544897426382305096215624787217055491736755052175278235595298571339706430785816901931128536495808042995635624112114867111658850659510246291844949806165980806847525704751270260070165853067310918184720602183083989806069386048683955313982129380729637761521928446431397104973906871109766946008926113644488012281655650467201044142029022180536946134328567554182760495139058952910079169456941591963348364521142012653606596379566245637724637892435425", + }, + "revocation": { + "g": "1 1864FF219549D1BC1E492955305FC5EED27C114580F206532D2F5D983A1DD3BD 1 0414758D7B6B254A9CA81E1084721A97CA312497C21BB9B16096636C59F9D105 2 095E45DDF417D05FB10933FFC63D474548B7FFFF7888802F07FFFFFF7D07A8A8", + "g_dash": "1 2327DA248E721E3935D81C5579DD3707882FFB962B518D37FB1112D96CC63611 1 164989452135CF5D840A20EE354DBF26BEEC74DE7FD53672E55224BEE0228128 1 0634D5E85C210319BFD2535AFD8F7F79590B2F5CC61AF794218CC50B43FBB8C6 1 0A63F1C0FC2C4540156C7A2E2A2DF1DDF99879C25B4F622933707DD6074A0F1B 2 095E45DDF417D05FB10933FFC63D474548B7FFFF7888802F07FFFFFF7D07A8A8 1 0000000000000000000000000000000000000000000000000000000000000000", + "h": "1 0A031B1932CDFEE76C448CA0B13A7DDC81615036DA17B81DB2E5DFC7D1F6CD6F 1 06F46C9CC7D32A11C7D2A308D4C71BEE42B3BD9DD54141284D92D64D3AC2CE04 2 095E45DDF417D05FB10933FFC63D474548B7FFFF7888802F07FFFFFF7D07A8A8", + "h0": "1 1C88CA353EF878B74E7F515C88E2CBF11FDC3047E3C5057B34ECC2635B4F8FA5 1 1D645261FBC6164EC493BB700B5D8D5C8BF876FD9BA034B107753C79A53B0321 2 095E45DDF417D05FB10933FFC63D474548B7FFFF7888802F07FFFFFF7D07A8A8", + "h1": "1 16AC82FE7769689173EABA532E7A489DF87F81AE891C1FDA90FE9813F6761D71 1 147E45451C76CD3A9B0649B12E27EA0BF4E85E632D1B2BEC3EC9FFFA51780ACE 2 095E45DDF417D05FB10933FFC63D474548B7FFFF7888802F07FFFFFF7D07A8A8", + "h2": "1 2522C4FAA35392EE9B35DAC9CD8E270364598A5ED019CB34695E9C01D43C16DC 1 21D353FB299C9E39C976055BF4555198C63F912DBE3471E930185EF5A20470E5 2 095E45DDF417D05FB10933FFC63D474548B7FFFF7888802F07FFFFFF7D07A8A8", + "h_cap": "1 1E3272ABDFD9BF05DB5A7667335A48B9026C9EA2C8DB9FA6E59323BBEB955FE2 1 031BD12497C5BBD68BEA2D0D41713CDFFDCBE462D603C54E9CA5F50DE792E1AB 1 05A917EBAA7D4B321E34F37ADC0C3212CE297E67C7D7FEC4E28AD4CE863B7516 1 16780B2C5BF22F7868BF7F442987AF1382F6465A581F6824245EFB90D4BB8B62 2 095E45DDF417D05FB10933FFC63D474548B7FFFF7888802F07FFFFFF7D07A8A8 1 0000000000000000000000000000000000000000000000000000000000000000", + "htilde": "1 24D87DBC6283534AE2AA38C45E52D83CC1E70BD589C813F412CC68563F52A2CA 1 05189BC1AAEE8E2A6CB92F65A8C0A18E4125EE61E5CEF1809EF68B388844D1B1 2 095E45DDF417D05FB10933FFC63D474548B7FFFF7888802F07FFFFFF7D07A8A8", + "pk": "1 0A165BF9A5546F44298356622C58CA29D2C8D194402CAFCAF5944BE65239474E 1 24BA0620893059732B89897F601F37EF92F9F29B4526E094DA9DC612EB5A90CD 2 095E45DDF417D05FB10933FFC63D474548B7FFFF7888802F07FFFFFF7D07A8A8", + "u": "1 1F654067166C73E14C4600C2349F0756763653A0B66F8872D99F9642F3BD2013 1 24B074FFB3EE1E5E7A17A06F4BCB4082478224BD4711619286266B59E3110777 1 001B07BEE5A1E36C0BBC31E56E039B39BB0A1BA2F491C2F674EC5CB89150FC2F 1 0F4F1E71A11EB1215DE5A081B7651E1E22C30FCCC5566E13F0A8062DB67B9E32 2 095E45DDF417D05FB10933FFC63D474548B7FFFF7888802F07FFFFFF7D07A8A8 1 0000000000000000000000000000000000000000000000000000000000000000", + "y": "1 020240A177435C7D5B1DBDB78A5F0A34A353447991E670BA09E69CCD03FA6800 1 1501D3C784703A097EDDE368B27B85229030C2942C4874CB913C7AAB8C3EF61A 1 109DB12EF355D8A477E353970300E8C0AC2E48793D3DC13416BFF75145BAD753 1 079C6F242737A5D97AC34CDE4FDE4BEC057A399E73E4EF87E7024048163A005F 2 095E45DDF417D05FB10933FFC63D474548B7FFFF7888802F07FFFFFF7D07A8A8 1 0000000000000000000000000000000000000000000000000000000000000000", + }, + }, + }, + "credentialDefinitionId": "did:indy:bcovrin:test:A4CYPASJYRZRt98YWrac3H/anoncreds/v0/CLAIM_DEF/728266/TAG2222", + "id": "1545e17d-fc88-4020-a1f7-e6dbcf1e5266", + "metadata": {}, + "methodName": "indy", + "updatedAt": "2023-03-19T22:50:20.522Z", + }, + }, + "598dbcc3-5272-4503-9c67-b0cb69a9d3d6": { + "id": "598dbcc3-5272-4503-9c67-b0cb69a9d3d6", + "tags": { + "associatedRecordId": "be76cfbf-111b-4332-b1fe-7a1fea272188", + "messageId": "2a6a3dad-8838-489b-aeea-deef649b0dc1", + "messageName": "request-credential", + "messageType": "https://didcomm.org/issue-credential/1.0/request-credential", + "protocolMajorVersion": "1", + "protocolMinorVersion": "0", + "protocolName": "issue-credential", + "role": "receiver", + "threadId": "c5fc78be-b355-4411-86f3-3d97482b9841", + }, + "type": "DidCommMessageRecord", + "value": { + "_tags": {}, + "associatedRecordId": "be76cfbf-111b-4332-b1fe-7a1fea272188", + "createdAt": "2023-03-18T18:54:01.126Z", + "id": "598dbcc3-5272-4503-9c67-b0cb69a9d3d6", + "message": { + "@id": "2a6a3dad-8838-489b-aeea-deef649b0dc1", + "@type": "https://didcomm.org/issue-credential/1.0/request-credential", + "requests~attach": [ + { + "@id": "libindy-cred-request-0", + "data": { + "base64": "eyJwcm92ZXJfZGlkIjoiY3F4ZW8ybzVIWmVjYWc3VHo5aTJXcTRqejQxTml4THJjZW5HV0s2QmJKVyIsImNyZWRfZGVmX2lkIjoiQTRDWVBBU0pZUlpSdDk4WVdyYWMzSDozOkNMOjcyODI2NTpUQUciLCJibGluZGVkX21zIjp7InUiOiI3OTE4NzUwNzgzMzQxMjU3ODU3OTUzMjc2OTU5MjcxOTcyMDQwMjQxMTU1MzcyODEwOTQ2NTEwMjk5MDA1ODEyMTcwNjkwMjIzMTQ2ODU0NDk1MTI1NjQ1MTg3ODQxNzk0NjA3OTUwOTQ1OTM3MDYxMjk1ODgwMjIxMzE2NTg1MTIyNDY1Mzk1MjAwNDQ3MDIwNzAxNDA0NjEwMDc4MzkyMjA4NzkxMjk5NzYwMzM4OTIxNDMzMDc1Njk2ODU0NTY3MTIzNjYxNTYwNDMwNDE3NzQwMzc5MzA4NDQzODcyOTU1NzAwNTk1MTg2NzcxODY3MzM5NzQ5NDgzODYxNTQ2NTE2MTU4NTM5MjkyNDQzNTQ3OTg3MDUwMzE4OTAyOTI5OTgyNzMzMDk1ODk4MDIyMjg2OTk1OTQwMjkzMTg3NTg5NDgwNTgwNTAwNjM0NzAyNjQxNDM0MTgxNjIwMTU4OTU3MzUyNTE1OTA4NDE2MjI4MDQ0NDA2OTU4MTk1MDg4Mjc0ODI4Njc3OTQxMDgxOTczOTg3NjU1MDEzNDUxMzA4ODQyMjYyMzY4MTQzOTExNjIxMTE0NzYyNTk3Nzg1MDczMTM4MDg3NTQ2MDIyMzc1NjQxODQ5ODI2OTg2MjYwMDE5NTAzNzE3OTk0NjM3MzIyNDExNTgzNTY0MTQ1NjcwMTM5OTQ1MjQxOTU2Njk2MDQ3MTQzNjA4NjQ0MDM5OTg2NTYwODUyMzA1MTczMjUxMTUyMDIzODI5NjI3MzQyODM2NDI3MjkwNDQ5NTA3OTY0Nzk4MTQ2NjkzOTUxMDkwNzUwOTAyNiIsInVyIjpudWxsLCJoaWRkZW5fYXR0cmlidXRlcyI6WyJtYXN0ZXJfc2VjcmV0Il0sImNvbW1pdHRlZF9hdHRyaWJ1dGVzIjp7fX0sImJsaW5kZWRfbXNfY29ycmVjdG5lc3NfcHJvb2YiOnsiYyI6Ijk4MjIzNzMzMDcwMDk1NjM0MzU5MTE2MDEyOTgzMDcyMjc5MjcxMzk1MTg0NjA0NDcxNDQ5NzA1Nzg0MTEyNDAyODMwNzIyMTUxMzIxIiwidl9kYXNoX2NhcCI6IjU5ODA0MTY4ODAxODk1NDAzMjkwMzczMTA3NzA4MzUwODc1NjY4MDcyNDY3ODAyOTcyOTIyNjUzNDE5ODU2MjMyNTIzNDI4OTUxODQ4NjEyNDE1MjM5Nzk3Mjk5ODY2MzIxNjU5NDQ1MTM1NzQ4NzU2MDY1NjgyOTY5MjY4ODI5MTYyMDA0NjQ4NzYwMzg4NTg4NDkyNjg1NDI1MTg1MDU2OTAxOTkxMjcwMzYwMDk3MDc5NjEyNTYxMzY4NzU1OTcwMjY5MjI4MDYzMjMyODU0NzI0NDkyOTA5Njg5MDMyOTg4NjYyMjk5Mzg3MDU2NDEwNjU5MDU3MDUwNjE0MDQ2NzE1NjA0NTgyMzM2NTg4MjMxMjI3MTEzMDEzMDQxMTA0NTU2NzM1MDE3ODUwNzUzODcwNjc2ODYxNDA4MjA0NzkzMDIzNTYwMDEwNTEzODAwNzA4MjAyNjAyNjQ0Mjg2NzI4NjAyOTk5MzU5MDcwODQxMTQ5MTAzMjA5MzY0ODkyMzMzMDYwMDgzMTA5NDIzOTQ5NzE4NTk5NjEzMzk2NjIyMjc4MTExMzk5ODU0MTcyMjMyNTQzOTk1Njc5NDk3Mjk1Nzc1NzA0MjA0MTQxOTU2NDI1MDc4NjYzMzgwMDA1Nzc2ODY2MTcxNTY4OTU1NjE4NjAwMTA2NzkxMjIyNDkyODA2NzI1ODU1NDY2Nzk4OTEzMTc2NDcxMDY3MTk5ODQ2ODEwNDI5MDIzMDc3ODI3OTc1OTIzMDIzNjU3MTg0NjkwNzE0MjkxNDk0MDc5MTM1NzYyOTUxNTc0MjMzNjMwMjExNDQ1Njc3NzE1Mzg3Mzc1NjkyMjAzODE3NDczNDk5NDExNzE5MzIwMjczNzExOTIzMzM3MzYzMTAyOTkwMDcyMjE2MjYzMzUxMzMxNTk4ODk1OTU3MzU1MDc1NTEzODE0NTUwNzkyMCIsIm1fY2FwcyI6eyJtYXN0ZXJfc2VjcmV0IjoiNTY0MTY1NjA2NjM0MTUwODgzMjg2MjM1NDM1MjUyODQ3MjAxMTk4NTQyNDAxMjYzNTY2MDQ2MTA3OTU3NzI4NTQ0MTMwMjgzNjUyNjQzOTI0NDc2NTU2NTIzNzg0ODI1OTgyMzMwMzc4NDI4OTM0MDkxNDcwNDM0OTAwMTM3NzkwNDkxMjM4NTA4ODk2ODQxMzkwNjQ4MTA1NzY5ODYzMzI1OTAzODI1Mjk1MjU2OTQ0NSJ9LCJyX2NhcHMiOnt9fSwibm9uY2UiOiIxMzE1MDIwOTY0MDY4NjgyMDQ0Mzc4MjEifQ==", + }, + "mime-type": "application/json", + }, + ], + "~service": { + "recipientKeys": [ + "cqxeo2o5HZecag7Tz9i2Wq4jz41NixLrcenGWK6BbJW", + ], + "routingKeys": [], + "serviceEndpoint": "http://localhost:3001", + }, + "~thread": { + "thid": "c5fc78be-b355-4411-86f3-3d97482b9841", + }, + }, + "metadata": {}, + "role": "receiver", + "updatedAt": "2023-03-18T18:54:01.126Z", + }, + }, + "6ef35f59-a732-42f0-9c5e-4540cd3a672f": { + "id": "6ef35f59-a732-42f0-9c5e-4540cd3a672f", + "tags": { + "credentialDefinitionId": "did:indy:bcovrin:test:A4CYPASJYRZRt98YWrac3H/anoncreds/v0/CLAIM_DEF/728265/TAG", + "issuerId": "did:indy:bcovrin:test:A4CYPASJYRZRt98YWrac3H", + "methodName": "indy", + "schemaId": "did:indy:bcovrin:test:A4CYPASJYRZRt98YWrac3H/anoncreds/v0/SCHEMA/Test Schema/5.0", + "tag": "TAG", + "unqualifiedCredentialDefinitionId": "A4CYPASJYRZRt98YWrac3H:3:CL:728265:TAG", + }, + "type": "AnonCredsCredentialDefinitionRecord", + "value": { + "credentialDefinition": { + "issuerId": "did:indy:bcovrin:test:A4CYPASJYRZRt98YWrac3H", + "schemaId": "did:indy:bcovrin:test:A4CYPASJYRZRt98YWrac3H/anoncreds/v0/SCHEMA/Test Schema/5.0", + "tag": "TAG", + "type": "CL", + "value": { + "primary": { + "n": "92212366077388130017820454980772482128748816766820141476572599854614095851660955000471493059368591899172871902601780138917819366396362308478329294184309858890996528496805316851980442998603067852135492500241351106196875782591605768921500179261268030423733287913264566336690041275292095018304899931956463465418485815424864260174164039300668997079647515281912887296402163314193409758676035183692610399804909476026418386307889108672419432084350222061008099663029495600327790438170442656903258282723208685959709427842790363181237326817713760262728130215152068903053780106153722598661062532884431955981726066921637468626277", + "r": { + "age": "12830581846716232289919923091802380953776468678758115385731032778424701987000173171859986490394782070339145726689704906636521504338663443469452098276346339448054923530423862972901740020260863939784049655599141309168321131841197392728580317478651190091260391159517458959241170623799027865010022955890184958710784660242539198197998462816406524943537217991903198815091955260278449922637325465043293444707204707128649276474679898162587929569212222042385297095967670138838722149998051089657830225229881876437390119475653879155105350339634203813849831587911926503279160004910687478611349149984784835918594248713746244647783", + "master_secret": "61760181601132349837705650289020474131050187135887129471275844481815813236212130783118399756778708344638568886652376797607377320325668612002653752234977886335615451602379984880071434500085608574636210148262041392898193694256008614118948399335181637372037261847305940365423773073896368876304671332779131812342778821167205383614143093932646167069176375555949468490333033638790088487176980785886865670928635382374747549737473235069853277820515331625504955674335885563904945632728269515723913822149934246500994026445014344664596837782532383727917670585587931554459150014400148586199456993200824425072825041491149065115358", + "name": "26931653629593338073547610164492146524581067674323312766422801723649824593245481234130445257275008372300577748467390938672361842062002005882497002927312107798057743381013725196864084323240188855871993429346248168719358184490582297236588103100736704037766893167139178159330117766371806271005063205199099350905918805615139883380562348264630567225617537443345104841331985857206740142310735949731954114795552226430346325242557801443933408634628778255674180716568613268278944764455783252702248656985033565125477742417595184280107251126994232013125430027211388949790163391384834400043466265407965987657397646084753620067162", + }, + "rctxt": "49138795132156579347604024288478735151511429635862925688354411685205551763173458098934068417340097826251030547752551543780926866551808708614689637810970695962341030571486307177314332719168625736959985286432056963760600243473038903885347227651607234887915878119362501367507071709125019506105125043394599512754034429977523734855754182754166158276654375145600716372728023694171066421047665189687655246390105632221713801254689564447819382923248801463300558408016868673087319876644152902663657524012266707505607127264589517707325298805787788577090696580253467312664036297509153665682462337661380935241888630672980409135218", + "s": "51390585781167888666038495435187170763184923351566453067945476469346756595806461020566734704158200027078692575370502193819960413516290740555746465017482403889478846290536023708403164732218491843776868132606601025003681747438312581577370961516850128243993069117644352618102176047630881347535103984514944899145266563740618494984195198066875837169587608421653434298405108448043919659694417868161307274719186874014050768478275366248108923366328095899343801270111152240906954275776825865228792303252410200003812030838965966766135547588341334766187306815530098180130152857685278588510653805870629396608258594629734808653690", + "z": "60039858321231958911193979301402644724013798961769784342413248136534681852773598059805490735235936787666273383388316713664379360735859198156203333524277752965063504355175962212112042368638829236003950022345790744597825843498279654720032726822247321101635671237626308268641767351508666548662103083107416168951088459343716911392807952489009684909391952363633692353090657169830487309162716174148340837088238136793727262599036868196525437496909391247737814314203700293659965465494637540937762691328712617352605531361117679740841379808332881579693119257467828678864789270752346248637901288389165259844857126172669320275054", + }, + }, + }, + "credentialDefinitionId": "did:indy:bcovrin:test:A4CYPASJYRZRt98YWrac3H/anoncreds/v0/CLAIM_DEF/728265/TAG", + "id": "6ef35f59-a732-42f0-9c5e-4540cd3a672f", + "metadata": {}, + "methodName": "indy", + "updatedAt": "2023-03-19T22:50:20.522Z", + }, + }, + "STORAGE_VERSION_RECORD_ID": { + "id": "STORAGE_VERSION_RECORD_ID", + "tags": {}, + "type": "StorageVersionRecord", + "value": { + "createdAt": "2023-03-18T18:53:43.140Z", + "id": "STORAGE_VERSION_RECORD_ID", + "metadata": {}, + "storageVersion": "0.4", + "updatedAt": "2023-03-19T22:50:20.522Z", + }, + }, + "a56d83c5-2427-4f06-9a90-585623cf854a": { + "id": "a56d83c5-2427-4f06-9a90-585623cf854a", + "tags": { + "connectionId": undefined, + "credentialIds": [], + "state": "offer-sent", + "threadId": "f9f79a46-a4d8-4ee7-9745-1b9cdf03676b", + }, + "type": "CredentialRecord", + "value": { + "createdAt": "2023-03-18T18:53:59.859Z", + "credentialAttributes": [ + { + "mime-type": "text/plain", + "name": "name", + "value": "John", + }, + { + "mime-type": "text/plain", + "name": "age", + "value": "99", + }, + { + "mime-type": "text/plain", + "name": "height", + "value": "180", + }, + ], + "credentials": [], + "id": "a56d83c5-2427-4f06-9a90-585623cf854a", + "metadata": { + "_anoncreds/credential": { + "credentialDefinitionId": "A4CYPASJYRZRt98YWrac3H:3:CL:728266:TAG2222", + "schemaId": "A4CYPASJYRZRt98YWrac3H:2:AnotherSchema:5.12", + }, + }, + "protocolVersion": "v2", + "state": "offer-sent", + "threadId": "f9f79a46-a4d8-4ee7-9745-1b9cdf03676b", + "updatedAt": "2023-03-19T22:50:20.522Z", + }, + }, + "b88030f7-dc33-4e7e-bf4d-cdfaa6e51ebf": { + "id": "b88030f7-dc33-4e7e-bf4d-cdfaa6e51ebf", + "tags": { + "associatedRecordId": "be76cfbf-111b-4332-b1fe-7a1fea272188", + "messageId": "578fc144-1e01-418c-b564-1523eb1e95b8", + "messageName": "issue-credential", + "messageType": "https://didcomm.org/issue-credential/1.0/issue-credential", + "protocolMajorVersion": "1", + "protocolMinorVersion": "0", + "protocolName": "issue-credential", + "role": "sender", + "threadId": "c5fc78be-b355-4411-86f3-3d97482b9841", + }, + "type": "DidCommMessageRecord", + "value": { + "associatedRecordId": "be76cfbf-111b-4332-b1fe-7a1fea272188", + "createdAt": "2023-03-18T18:54:01.192Z", + "id": "b88030f7-dc33-4e7e-bf4d-cdfaa6e51ebf", + "message": { + "@id": "578fc144-1e01-418c-b564-1523eb1e95b8", + "@type": "https://didcomm.org/issue-credential/1.0/issue-credential", + "credentials~attach": [ + { + "@id": "libindy-cred-0", + "data": { + "base64": "eyJzY2hlbWFfaWQiOiJBNENZUEFTSllSWlJ0OThZV3JhYzNIOjI6VGVzdCBTY2hlbWE6NS4wIiwiY3JlZF9kZWZfaWQiOiJBNENZUEFTSllSWlJ0OThZV3JhYzNIOjM6Q0w6NzI4MjY1OlRBRyIsInJldl9yZWdfaWQiOm51bGwsInZhbHVlcyI6eyJhZ2UiOnsicmF3IjoiOTkiLCJlbmNvZGVkIjoiOTkifSwibmFtZSI6eyJyYXciOiJKb2huIiwiZW5jb2RlZCI6Ijc2MzU1NzEzOTAzNTYxODY1ODY2NzQxMjkyOTg4NzQ2MTkxOTcyNTIzMDE1MDk4Nzg5NDU4MjQwMDc3NDc4ODI2NTEzMTE0NzQzMjU4In19LCJzaWduYXR1cmUiOnsicF9jcmVkZW50aWFsIjp7Im1fMiI6IjMyMTIwMDQ1ODc4MzIxMjcyMzA1ODI5MTc3NzMzMTIwNzE1OTY5NDEyNjkwNjUyNDQ4OTc0MTA4NzEzNjU0ODc3NTg2MzIzMTI3ODk2IiwiYSI6IjIyMjY0MTYwNjIwODcwNDUyNTExMjcyMzE1MzMzMDA0MjQzMzY3NTM2NzM3NDMwNjM1NjExMjcwMDkwOTE4NDMwNzc0ODEzMjAzNjQwNjMxMjIyNDczMzk0MjQ4MTgzMDIzMjIyNzExNDUwMzQxMDcxOTQyNDQwMDgwMjY2Nzk1Mzg5Mzg5Njc1NjYwOTUzNTQyMDE4OTA3NjQ3NzI4OTQ4NjY1MzA2Njg0NjExNDU1NTI5NzM5OTY1NDcyMjQ2NDQxMzE1NzAxMzM1ODc1MDY3MjExMDk3NzcyOTgwMjU1NDIxMDMzMTI1MjAyMTQzNDk3NjMyOTAyMjM1NDAyMzU5OTA1MzY5MzE4MjI1NTc4MjUxNjY4NTYzNzc1NTY0MDM2MjUxNzE0Mzk3MTEzNjQ3OTg0MjcxMTE5MTU2NDQ3NjI1OTk1NjE5MjAwMDk4MTgzNzY1NjkzMTg1ODEzNjA1NDU3OTQwMzE0MDU2MDkzMTI2MzQ3OTU5MzYwODIyMzg0OTEzODg3Mjg3ODI2NjkyNDIyNDMyNDUwMDA5OTYxNjQ2MjMzNTE3MjY3NDU1OTkyMjA3MTE3Mzk5NzU1NjY3MTA3MzM1NTQ0MzEwNDQwNDE1NDE5NTk5NTA1OTgxMzkwMjk5NDUxNzQyODg4NDg0MTc0NTU5MDA5NDgwNjU5MDk2Nzg2ODI2MDgxNzc3MzcwNTk1MTU3OTg5NjQ1MDYxNDI2OTA2ODM2MDk5NTU5MDQ0MDI4ODM2MzYwOTM2MDkwOTkxNjA1OTU0NTM2OTQxMjgxODQwNzk2MjkxODc0ODk2NDEzNTM5IiwiZSI6IjI1OTM0NDcyMzA1NTA2MjA1OTkwNzAyNTQ5MTQ4MDY5NzU3MTkzODI3Nzg4OTUxNTE1MjMwNjI0OTcyODU4MzEwNTY2NTgwMDcxMzMwNjc1OTE0OTk4MTY5MDU1OTE5Mzk4NzE0MzAxMjM2NzkxMzIwNjI5OTMyMzg5OTY5Njk0MjIxMzIzNTk1Njc0MjkzMDExMzE1MTE0OTUxODg0MDQ5ODkyNjAwNTY3NTgzMTc4ODkwMyIsInYiOiI2NTE5ODc4MzYyODQ5NzExNDY4NDkyNDM1OTM3MDU4NzMzOTYxMTkxNjA3NjI4MzUzNjkxMDg1MzM5MDMwNDU0OTkyODc0ODYyODkyNDg4ODYzNTA3MDQ1MTM1MzA4ODI1NDA2NzYwMTQwNDQzNzM0NDYzODE5NTM2MzE0NzcxMTQ3MDk4MjU2ODMzNTc2MjIwNDI5ODQyNjc3NzMwMzQwODYwNjE2NTcxNzc5NjU4OTIxNDY4Mjc0NTUwOTc5NjYyMDkxNzEwNDU5MDk2MDgzMzYzNTc1Mjc0MjQzNzIyMzIzOTIxMjY5MDYyMjE0NjQyNzQyMTI0MzQ4MTY0MDUxNzE3MTk5MTkzODY3NTM3NTEzNjYzOTY1ODQzMDI5MjAxODA0OTE2MTEzNzMxODYzOTUzNjQ5MDkwNDgzNzMyMTkxNTQ2MTEwMjAxNTg0NzMxODg4NTE5NjA2MjE1OTkyNTgxNzk2MDg2NzUzOTE5NzUxMjkwMDI3MDI4NzczMTAwOTc5ODI5MzQ5NzA0MTUyMDEzNjg2MzU1MzM1MjIyNjU5MDY2NzE0NDQ2NDc4NzY3MTE5NDE4MjY3OTg5NTAyNzc4MjMzNzM3MjM4MjU1MTQxNzQyMjk4NTU3MDY2NzA2MTM0NzYwMjQwMzY3OTMzMzc5NzYzMTc5MTI1NTI4MDQwMzkxNjQwNTIyNTM5NjE5NTU0NTE0NTk4OTUxNTg0NjA3MjYwNzk1NzE1MDMyMjM4NTQ3ODMyMzA0NTY2MzQ4NjYzMTc0NzQwMDE2MzQ2NTU2MTM1ODc4MzgxNTYzODQ2NzU0MzQzMjk0NTIzNjc0NDI3NjQxNjAxNjAwNjE2NzI3NjEyMzc0MzI2NzY4ODA5NjAyNTE5MTAzOTk3NDY4OTg1NTg3Nzg4MjI3Njc5MzQ4NTgwNzk1OTkyOTkxNzMzMDg5MTUyMTg2MDg4OTU2MTg2MTQ0OTkyMDI5OTI2OTUxOTU0OTQyNjYwMDUxOTM0MDc5NzkxODI1NzA2MTExNzg0MDU2NDM2OTA2MDgxMDQ2MDQ5ODI0ODE1NDE0MTc5NTMzMDA2ODE4NzQ3NzgwNTQ5In0sInJfY3JlZGVudGlhbCI6bnVsbH0sInNpZ25hdHVyZV9jb3JyZWN0bmVzc19wcm9vZiI6eyJzZSI6IjEwMTUyMDI0OTk1MTUxMzcyOTgwNzI5NDM1MTQ5MzgyMzQxMDQ3MDIzNjI2NjA4MDc3ODg1NzQ2Mjg4MTE3MjI2OTAyNzM4OTY5OTEwMTU0NjgzNzM2MzI4MzQ2MDg0MjcwMDA3MTY2NTg3ODI5MjE2MzEzNDc4MDk3Njc0MTI0ODU2ODIyMjA4NzI0Nzk1NTE0ODgzOTYwMzE5NDc5OTg4NzAzNDUyNjI4NjYxMDc3MTg3OTIyMzA1NDc5MDE2NzQzOTk0NzYwMzE5NzI1OTExODk0MjM2NDMxMDkxMTIyNTUxNTU0NzgwODg0NjQ2MjE0MTUzMDUzMTM2NDMwMTk4OTA5MTM0OTk4OTM2NjY3MzI4ODI2MDcwNzEzMzk0NDg0NDI0ODUxNjkxMzUxNDc0NjAxMjIwODk2NTIyMDYzNDA5NzA4NDA1Njk2MzY5MjA0MzU0NzE1MDkxMzk2Mzc4Mzc3MzA0ODk3MzMwOTM0Mjc2NTQyNjE2NjAxNTk1ODI5NzgxOTg3NTMyNzkxMzIyNTgzOTE1Njk1OTY2MjM3MTc4Njg1NTMzNTE3MTQxNTAyNDE3MzQxMDIzMTA1MTczMjMwMTcwNzUzODYwMjgxNDAxODk4MDE5OTQwNjA2MzczOTYwMzYxNjA3NTE2NjgyMDg4MTc1NzU4ODA0Mzg4MTM5MTQ0MDkwMjg5MzI5NzMzNTQ1NDg4MjUyNjczNDIyODkzMzc1MzE5ODQ2OTMwOTIyNjIwNzAzMTEwMDgwODU5OTE4ODQ0MzgyOTQ3ODczMjAwNzA4MTY2MzA0NDk4ODk0MDA4NTMyIiwiYyI6IjIyNDQyNTM5MzYwMzYzNjQyODI1ODkxNTc5ODgzMDE5Mjc3Mjk0NTQ2MjUwMDEzNTM3MzI2OTY2NzM3MzE0NTUxMjEwMjU3MjU2NDU5In0sInJldl9yZWciOm51bGwsIndpdG5lc3MiOm51bGx9", + }, + "mime-type": "application/json", + }, + ], + "~please_ack": { + "on": [ + "RECEIPT", + ], + }, + "~service": { + "recipientKeys": [ + "GVbwqUMqzxKaEVWjn1aPBfjJpYQHVejinpx8GCeEuQjW", + ], + "routingKeys": [], + "serviceEndpoint": "http://localhost:3000", + }, + "~thread": { + "thid": "c5fc78be-b355-4411-86f3-3d97482b9841", + }, + }, + "metadata": {}, + "role": "sender", + "updatedAt": "2023-03-18T18:54:01.192Z", + }, + }, + "be76cfbf-111b-4332-b1fe-7a1fea272188": { + "id": "be76cfbf-111b-4332-b1fe-7a1fea272188", + "tags": { + "connectionId": undefined, + "credentialIds": [], + "state": "done", + "threadId": "c5fc78be-b355-4411-86f3-3d97482b9841", + }, + "type": "CredentialRecord", + "value": { + "createdAt": "2023-03-18T18:53:59.068Z", + "credentialAttributes": [ + { + "mime-type": "text/plain", + "name": "name", + "value": "John", + }, + { + "mime-type": "text/plain", + "name": "age", + "value": "99", + }, + ], + "credentials": [], + "id": "be76cfbf-111b-4332-b1fe-7a1fea272188", + "metadata": { + "_anoncreds/credential": { + "credentialDefinitionId": "A4CYPASJYRZRt98YWrac3H:3:CL:728265:TAG", + "schemaId": "A4CYPASJYRZRt98YWrac3H:2:Test Schema:5.0", + }, + }, + "protocolVersion": "v1", + "state": "done", + "threadId": "c5fc78be-b355-4411-86f3-3d97482b9841", + "updatedAt": "2023-03-19T22:50:20.522Z", + }, + }, + "d7353d4a-24fc-405f-9bf5-f99fae726349": { + "id": "d7353d4a-24fc-405f-9bf5-f99fae726349", + "tags": { + "associatedRecordId": "be76cfbf-111b-4332-b1fe-7a1fea272188", + "messageId": "c5fc78be-b355-4411-86f3-3d97482b9841", + "messageName": "offer-credential", + "messageType": "https://didcomm.org/issue-credential/1.0/offer-credential", + "protocolMajorVersion": "1", + "protocolMinorVersion": "0", + "protocolName": "issue-credential", + "role": "sender", + "threadId": "c5fc78be-b355-4411-86f3-3d97482b9841", + }, + "type": "DidCommMessageRecord", + "value": { + "associatedRecordId": "be76cfbf-111b-4332-b1fe-7a1fea272188", + "createdAt": "2023-03-18T18:53:59.857Z", + "id": "d7353d4a-24fc-405f-9bf5-f99fae726349", + "message": { + "@id": "c5fc78be-b355-4411-86f3-3d97482b9841", + "@type": "https://didcomm.org/issue-credential/1.0/offer-credential", + "credential_preview": { + "@type": "https://didcomm.org/issue-credential/1.0/credential-preview", + "attributes": [ + { + "name": "name", + "value": "John", + }, + { + "name": "age", + "value": "99", + }, + ], + }, + "offers~attach": [ + { + "@id": "libindy-cred-offer-0", + "data": { + "base64": "eyJzY2hlbWFfaWQiOiJBNENZUEFTSllSWlJ0OThZV3JhYzNIOjI6VGVzdCBTY2hlbWE6NS4wIiwiY3JlZF9kZWZfaWQiOiJBNENZUEFTSllSWlJ0OThZV3JhYzNIOjM6Q0w6NzI4MjY1OlRBRyIsImtleV9jb3JyZWN0bmVzc19wcm9vZiI6eyJjIjoiODUxODAxMDMyNzEzNDg5NzYxOTg5MzAzNjMzMDkzOTEyOTExMDUxNjI0OTQ0OTYzMTgzNzM2MDY3NDkwOTc2MDYxODEwMDgxODkxMzQiLCJ4el9jYXAiOiI4NDk0NDg4MjQzNTk2NTkwOTc2MjQzMjc0NDg4ODk2Mjc1NTcyODAyMTQ1ODE5NDQzNTE0NzQxMzk1NDI1NjM5MzQwMTczMDIzMTQ5NzI3MDY5NzMzMzQwODgzMTU4MzQ1NTYzOTA5OTcxNDMzNTg1MjMwODAxNTYyMTM0NjczNjM1ODg5NTA3Njg5ODQwOTgyODU5Mzg1NjA1MTc1NTkxNDYxOTkyMDExNzU2Mzg1MTI3MTQ3ODgxNDMwODEzNjYxNzY0MDU5MzE0ODk4MTc2NzQzMTQ5MjYzMDMwMDQ1NzMwMDMzMzI2NzgyMzg1OTY0NjcxMzg1ODQ2MzcxNjQ5MzQxMTg2MDM5NjE4MTQwOTIwMDUxMzg1MDAwNTYxMDcyMTc5NTEyMzc5Nzk0OTU4NjE1ODIyODI2OTExNzIwNTQyNTE0MTQ1NDc5MTAxOTUyMzM4MDMwMDY1MDk5NjcxOTU2OTMxMzE2NjE5MjM0NTQ0NTE5NTQ1ODQ1MzA4MzgxMjQyNTM0NDcyOTc3NjY0MjAwMjc2MTMyOTgxODE1ODAzNTIxOTExMzk4ODkxMjE0NjE1NzA1MDM2ODM2ODU1NDU1NzY4ODg4MTUxNDgzODAyNDcyODQyMzczNzE0MTI0NTYwMzIyNTI3NDE4MTEwNzYyMjgyNzY4NzMyNTIzMDQyMDA3MDY2OTk2ODIxMTQwMzE1NDg0NzI4NTM2NzIwNDI3MDg5MTI2NDk1NTAzMjc0ODQ4MDM3MjUzOTM3NjI3MDU2ODUzMTQ4NjE5NDA4NDYxOTI5NzEzMjM4MjEwNDc4MjcyMTIxNTUwNjQzODc4ODM1NDYwMzY1OTIwMjE3NTk5NDYyNDUzMDMyNDQ4MjYyMTM3NjE5ODY0OTU4MzA1MDE3MjA4OTYwNDc1MTQxODgwMTMiLCJ4cl9jYXAiOltbIm5hbWUiLCI1MDcyNzU2NDE2NDA2ODIxNzU1OTc0MzUxMTg0NjE1NjA4NDY2NTk3Mzk0NzA2MTY1NDg2ODAzMjc3MjMyMzQyOTk4MDA0MzY0OTU0MTczMzc0NDIwOTc5NTkwMDcyODgxNDgxNDA0MTg2OTExODg5NzQ4MTgzMzQ1OTk5NzQ0NzgxMTQ1MTMwNzEyNDIzODY0Nzc1MzQzNjAzNTk2NDM3Mzg4OTgzNTExNDAzODA0NjEyNjU1MDE5NzQ4MTI5NDk3ODY2NTcwMDQyMjcwNDQxNDQ5MjYwODY0NzgyMzI5MjAxNDEzMTc5ODU3NzA0MjM5OTMyMTg4NTc4NzE3MDczNzM3NjUyNzY5MzY5NDg4OTgxNzg2NDQwNTExODAzMjMzNDMxNzA4NDk4MTU2NTA0OTUzNzkzNjU2NjQ2NzMyNTU4MzQwNDI2MDI1MjA3NTk0OTIwMDY4OTc2OTQ4Nzg2OTUxNzM3MDIwNDQ0NTA5NzYyMDQ2MzIzNzA0MDQ3MjU1ODU3NDE5ODE3MDc5NTI3NDgzNTE1NDY2NTAyMDkzOTY1NDMzMzk3MjQ1MzA4MjQ5MDgyMTQ4Mjc4NDA1MzI5Njg1Mjc0MDYwNjk0MzI0MTI2ODgxMjkyMDIyMjY1ODczMjk5MDU0NDU1OTA5NzkyNjUwNjAyMTk0NjUzMjYxMDk0ODYwOTc2NzA4ODE1ODgwMjExMTY0MTkwMDM0NjY0MzI2MDc3NjcwNzkyMDE4NTE2MzMzNDI3NjkwODYwMjIxODEwMzk5MDgxMjc5NjAwNTYzMjk3MjI0NjM0MDM0NjcxNTIwODE5MzU3NzQ0Njk2NzU1Njg1NDI2NjIzMzAwMjQ3MDUwODE4NTQ2MDM2NjA0NjMxNjcyNzE5MjI0NDA4NTE2NDM4NTgxMDM5Njk4NzI0MSJdLFsibWFzdGVyX3NlY3JldCIsIjU2MzYzNTgyMDQ5Mjg4OTY1OTg1MDA4NzgyMzU0NjgyNjMwNDkxMzQ3MTM1NDIxNTAyMDEyMTIwMzI4MDI4ODIyMjUyMzg4NjgwNTMwNTgxMTcwMTgwNDU1MTcyNTc3ODkyMTEyMTY1OTM0Mjk5NjUyNzAxNDExMzUyNDkzMzkyODU0ODI4NzMyMDQzMDI0MDI0MzM0MzMzNzc0NjEyOTEzOTUyMjAzNjM1NDk2MDQ0ODMzMDI5NDE2NjUwOTU5NjE0ODgzNTUwOTMxNzgzNTA5MzE1Nzg4MDEyODQ0MzAwMDQwMDE5MTY5MTc3NTI1OTgxMTU3OTkwNjQzMDcyMjQyNzcxMjU0MTYyNzMxOTU4NzI2Nzc1NjYwMjkxODIzMDcyNDk1Mzg0NzM5MTcwODc4ODMxNzkxMjQzMjEzMjU5MzA5ODQxNjU3MjUwOTg1NzMxMjEyNzE2MDM2MDY3MDUxNjM2NzA0MjA1NDEzMDk2MDU3MTA2NTM2MTI2ODUyNDU0NzcwMzQzMTMwMTczMjAwNjEzMDIxOTE4MzgzMDQxOTU4MTkwOTE2NzQ0NjU4NTI0ODA1NjM4Mzk2OTY3OTA3MzIwNjY1MDU1MzcwMjY0NjAxMDczMjc5NDMyNjM5MjM3Njc1NTA0OTg1NzQyNTI4NjYwMTAyMDEzNzIxMzA2MTE4MTg0NDk1MTEyNDQ2NDYyNDc2NTkwMjYxODkxMjA0OTQxOTA4MjMyNzMzNDA3MTg4MDA3NzE2NTA2OTUzMDY0Nzc5NDk5ODExNzI0ODI5NjcyNjY2NzIyNjIzOTAxMTc1OTk0NTIyNjkwMjk1ODI0MDgyNzY5NjQ0NDYxOTAxMDk2NzI3MTE5NzAzMjUzNzI4NjY3MTU1MzA5MDYzNDUyNDY2MDY3NzU5NzIwOTgyNDA3MiJdLFsiYWdlIiwiMTM2NTQxMjE0MjM5MTcyNDQxNzQ1MjU3MjcyMDI3MTA4NDYwMzU0MjgxMTA2OTA2MzYwNDIwMDE0NjUyMDIxMDgyNDEzODM2ODEyMjk3NjY3ODk2MTYzNDkzMjM4NDIxNDI4NjMyNTMxODE0ODk4NzkwMDg4OTg2NjgyMTE2OTAyMzc4NDgwNTE4OTUxNDExNzg1OTk3NTk5MDMyNDYxNjExNjIyMDUyNjMzMDQ5ODYxMzc5MTQzNzI4MTM5MTUyMDkyMzI0ODc3MjMxMTYwNTgzNzA5NjE0MzA1NzQ1MjA5MjQwNjU2MDU4NjY3OTMwODEzNzYyNDY5MDc2ODc5MTk1Nzg0Nzg4NTE2NjI3MjgxMDY0NjE3MzgzMDc4Njc5MTkwODIwMzQwNTgwNDY2MjU3ODU3NjA1MTc2MTg4NTI3OTMxMDI4MTMzNTY5Njc0Mzg2ODAwMTA2MDE2MDg1Nzc0OTcyMzI1NTAyNDA2MTY0OTY0MjU2OTUxNDI3ODAxMTQzNTQxMzUzMzI0Nzg0MzA5OTY4MjIyOTU1NDk4Njk3NTAwMDUwMzc0MDg0NjIwNzQ4MTk0NzIyMTI2NjE2OTY3OTY3Mzc1NTM3Nzc5NTc4NTMwMDIxODExNTA2MTIxNjcxMDUwNDgzNTM2MjA3Njc3MTg5NDQwNjEwNzk0NTcyNzI5MTgzMzAyMjM1MDkxMDg4NTU2ODc5NTg3OTE3MDMzMzQyODcyMzg2NDQ5MTQ0NzgwMDYyNjc4MzA3NzE4MzU1MjQ5MTUxNjc5MDA1MzkxNDA5NDE4OTQxMjEzNDkxMjQyMjg2NTAwODcyMzQxNDI3Nzk1MjQ1ODYzODE2MDY2NDY3NDkxOTg4OTU3MDEwNDIxNDA3NDkyMDUxOTc0NTMwNjIxOTk1ODU0ODczNTM5Mjk3MyJdXX0sIm5vbmNlIjoiNzk3NjAzMjE3NzA5MzM1MzAwMTcwODI4In0=", + }, + "mime-type": "application/json", + }, + ], + "~service": { + "recipientKeys": [ + "GVbwqUMqzxKaEVWjn1aPBfjJpYQHVejinpx8GCeEuQjW", + ], + "routingKeys": [], + "serviceEndpoint": "http://localhost:3000", + }, + }, + "metadata": {}, + "role": "sender", + "updatedAt": "2023-03-18T18:54:00.011Z", + }, + }, + "de4c170b-b277-4220-b9dc-7e645ff4f041": { + "id": "de4c170b-b277-4220-b9dc-7e645ff4f041", + "tags": { + "issuerId": "did:indy:bcovrin:test:A4CYPASJYRZRt98YWrac3H", + "methodName": "indy", + "schemaId": "did:indy:bcovrin:test:A4CYPASJYRZRt98YWrac3H/anoncreds/v0/SCHEMA/AnotherSchema/5.12", + "schemaIssuerDid": undefined, + "schemaName": "AnotherSchema", + "schemaVersion": "5.12", + "unqualifiedSchemaId": "A4CYPASJYRZRt98YWrac3H:2:AnotherSchema:5.12", + }, + "type": "AnonCredsSchemaRecord", + "value": { + "id": "de4c170b-b277-4220-b9dc-7e645ff4f041", + "metadata": {}, + "methodName": "indy", + "schema": { + "attrNames": [ + "name", + "height", + "age", + ], + "issuerId": "did:indy:bcovrin:test:A4CYPASJYRZRt98YWrac3H", + "name": "AnotherSchema", + "version": "5.12", + }, + "schemaId": "did:indy:bcovrin:test:A4CYPASJYRZRt98YWrac3H/anoncreds/v0/SCHEMA/AnotherSchema/5.12", + "updatedAt": "2023-03-19T22:50:20.522Z", + }, + }, + "e531476a-8147-44db-9e3f-2c8f97fa8f94": { + "id": "e531476a-8147-44db-9e3f-2c8f97fa8f94", + "tags": { + "associatedRecordId": "a56d83c5-2427-4f06-9a90-585623cf854a", + "messageId": "4d2c80b7-4a25-42ac-b8cf-a68b1374b9b7", + "messageName": "offer-credential", + "messageType": "https://didcomm.org/issue-credential/2.0/offer-credential", + "protocolMajorVersion": "2", + "protocolMinorVersion": "0", + "protocolName": "issue-credential", + "role": "sender", + "threadId": "f9f79a46-a4d8-4ee7-9745-1b9cdf03676b", + }, + "type": "DidCommMessageRecord", + "value": { + "associatedRecordId": "a56d83c5-2427-4f06-9a90-585623cf854a", + "createdAt": "2023-03-18T18:54:00.005Z", + "id": "e531476a-8147-44db-9e3f-2c8f97fa8f94", + "message": { + "@id": "4d2c80b7-4a25-42ac-b8cf-a68b1374b9b7", + "@type": "https://didcomm.org/issue-credential/2.0/offer-credential", + "credential_preview": { + "@type": "https://didcomm.org/issue-credential/2.0/credential-preview", + "attributes": [ + { + "name": "name", + "value": "John", + }, + { + "name": "age", + "value": "99", + }, + { + "name": "height", + "value": "180", + }, + ], + }, + "formats": [ + { + "attach_id": "8430ddb8-b0c3-4074-8ded-f4dcfe80303d", + "format": "hlindy/cred-abstract@v2.0", + }, + ], + "offers~attach": [ + { + "@id": "8430ddb8-b0c3-4074-8ded-f4dcfe80303d", + "data": { + "base64": "eyJzY2hlbWFfaWQiOiJBNENZUEFTSllSWlJ0OThZV3JhYzNIOjI6QW5vdGhlclNjaGVtYTo1LjEyIiwiY3JlZF9kZWZfaWQiOiJBNENZUEFTSllSWlJ0OThZV3JhYzNIOjM6Q0w6NzI4MjY2OlRBRzIyMjIiLCJrZXlfY29ycmVjdG5lc3NfcHJvb2YiOnsiYyI6IjQwMTQ0MTA0NDg3MjM0NDU2MTc1NzYwMDc1NzMxNjUyNjg1MTk0MjE5MTk5NDk3NDczNTM4NjU4ODM3OTIyODMzNTEzMDg0Nzk2MDQ5IiwieHpfY2FwIjoiMzgxOTQyMjM1Mzc3MzYwODEyNjY0MDA4MjYzNDQxMDg2MDMwOTMyMjAxNzgzMjM3ODQxODQ5NDg3ODk2ODg1MTYwODY2MTY1MDM3NzI2MTIxNjU0MjcwOTg5NDY3NjAzNDExOTAzODk4MzUwMDAzNDIwODg3MzI4NTUwMTY2MTI1ODMyMjAxOTQzMTkwNzAxMDU4NTAwMDE5ODM1NjA1ODczNDYzOTkwODg3NzQ0NjY3MzU0MjM2Njc3MzcyODg0ODQyNjE5NTEwMTUwOTA2MjI1OTMzMjc1ODEyNjg2NDg3NTg5NjY3ODI3MjAwODcwOTQ0OTIyMjk5MzI3OTI4MDQ1MTk1OTIwMDI3NTc0MDQwNDA4ODU5MzAwMzY1MDYwODc3Nzg2ODkwOTE1MDU5NTA2ODc1OTI0NzE2OTI1MDM2MTc4Njg2NDE5NTYyMzcwODI4MTMzODY2Nzg3NzkyMDcwNjAyNDQzNTkzMTk2NzEzNzcyNDM2NTYzODI0MzkwMDIyNzg4MjU2MzA4NjU4OTc0OTEzMTk1ODYxODUwMTQ3ODE1Mjg5NzQwOTA4NDk1MjQ3NTAyNjYyNDc3NzQ2NTI5ODA3Mzg0OTgxODI5MDc3NTQ4OTI2NzExMDkzNzQ5MjM1ODU4NjUwNDc5NzE5NDI4MzUwMzAwNzUyNjQ0OTg1MTQ5MTMxNjA1NjUzMDIxMDYxNzkwMjY3MzQyNTY4NTkyNTY2MTQ0MDM5NzY4OTg0NTMyNDMzNzk0MzUzNjQ2Nzg1MjA3NDgzOTk2ODQ0OTcxNTgzNzY3NDQ5ODYyODgxMjMxMjI1MzM4MzAzMTQ4NzA0ODczMDEzNDM3MDgyNzY1MTk4OTY2MzE5NTM0OTkyNjk4MzMzMDQ0MDI3MjIyNTYyNTIzNzk3ODk5Mjk2MTQ1NDU5IiwieHJfY2FwIjpbWyJtYXN0ZXJfc2VjcmV0IiwiOTE5MTc5NzQ4MTE5NTg5MTY3Njc5MjQxODk5NzY0ODIwNTk0MDc5OTQxNzIzOTgzOTYyNzQ1MTczODM0NDQxMDQ3MjU4MDcyOTE4OTUzNjIzOTQ4MDMyNzI2NDgyNzI2MTEwOTk2Mjk3MDU3NTYwNjcwNzAxOTU1MTkxNDc0NjM0MzQ0ODMxMzg3NTk4NzI2MzMxMjc0NjI4NDU3Njk5NzczMDA1NDMwMDIxNzMwMzg4MzcwMTEyMjc3MzI2MzU4OTgwMTA3ODIzNzUzODc3MTU0NjIwMDkzMjE5MjYyNjAxNDM2NzMyNTgzNDI4Nzc4NDA4OTc0NTQyNzkzMDk0NTQ5MTczOTA3MzQ3OTUxNTc1NjM5NzU2NDg5MTA0Mzk0MTY3NzExMzY1MjM3OTI1MjAwNjk4OTg5NTI5MTQ3OTIzNTYzNDMyODgyMzgwMTg0NzU0NzkzODMwMTE3MTQ1MDAwMTI0NDYxNjkzOTcxMDQ5MjgzNDk1NTE4MDQxMDc5ODUyMzAwMjk0NDM1MjYzOTIwNDU0NTU3MzUxNDQ1MDM3NDI4MDg3OTk2Mzg2NjY3NjU3Nzk5OTYyNzQzNzIyNzA3NzczOTEzMzc0NzIxODUyNTQ3MjkwMTY5MjI5NTAzMTQxOTMwODYzNTk4NTExNjc4NDEyMDE0MzE2MDM2MzYxMzczNDcwOTQwMDEyODcwMDgwMDA2MzE0NzYxNzYzNzUyNzYwODk5MTQ3NzA1MTA0NzQyNjAxNjkxMzMxNjkzMDIwMjg2MjA2NzQ2NzE0MzI3NjU2MjA2NTMzMjk3NDg4MjU2NTM2NTQ3MzY4MjM2OTQ2MDM5NzAzMzc0OTMzNTE0NTc2NDg2NjQyNTY4MjgyNTY2MjMyNDU1NTU5MDY4MzE3NzU5NDM0ODU4NTI3MDg2NjQ0Il0sWyJoZWlnaHQiLCI5MjMwMzkyNDc1NjI4ODc1MjA4OTM0NjM0NzE4MjYzNzA4MDIzOTI1MDU0NjY2NDgzMzgxMzIyMzc3MDg1MjMxMjU4MTM4MzgwOTU1NTk3NDQxNTEyOTYwNDA2MjI3MjUwODgyNjA3NjExMDkwODk3MTM1NDcxNzAwMDIzNDcwOTM2ODg4MDE3NDY5Nzk0ODYzNDk4NzUyNTI3Njc3MjMwMTEwNzg0ODQzNzI0NDUyNTUzODYyOTA2MzM5MDc0OTIzNDU4NTQ3NDYzODcwNzU3OTg5MzMxNzk4OTI2MjM4MjUxMTM2NTYzNjM2MjIyOTQwNDkwMzY3MjQ2OTg0OTU2NTE5MTAzODcwNDE0MDM5NzM2MDE2MDY5MzA2NjQ0NjQzODI4OTgxMTE3OTM3NzYyNDAzODY1Mjc1MDU5MjEyOTY2NzIxOTU3MzM0MTM2ODEyMDI0OTE0MzA4MzAxMzk5MzM4NzMyOTIzNTA0MjA5MDM5ODMxMTc5NjU1NTkyNjg0MjMyMTIzMTI2Mjc4ODQzNDMyOTUwMTk1Mjg3MzE4ODI3NTM2MTMwNDQ3NzM3MTgwMjk3MDE0ODEzNDg3NDQyOTg2NjQ1NzQyNjEyMzE5NzQxNDY2MDMyNTg5OTU0NzYwNjE4MDU0MDUxMjAzMTE1NTAxNDcxNDExMzg3NzU0NDk5MzAwNTU4MTc5NjM5NDAxOTM0NTAzMTMyMDEzMjAzOTg2NzkyMTEzMDAzNTkwODg1NTc3NjgyMzU2NDY3MjA5NTUwNjQxODQxMDYyNTkzNDYyODIwODg3NzgxNDYyODM3ODkzODcxNDM4MzM3Mjc5MTcwMTExMTQ5MTU4NDMzNDE0ODI1NTkyNjcyODU2MzM5OTM4NTgyODg2NzM3OTIwMjc1MzI0MjEwMTUzMjE5MjI2OTYiXSxbImFnZSIsIjkxNTg1ODk3NDkwNzE0ODA3OTY2MDYzOTg5MjE1NTMxNDkyOTQwMDI5NDcyMTM4MjgwNjcxNjcyMjQ0NjY5MDc5NzIyNTQyMDU0NTU3NjY0MTcxMDI1NzM1NjQ4NTIwMTM4ODQ4ODAxNzIyMTc4MTcxMTA5NTc0MTMyNTExMzM1MDEwNTc5NzExMzcyODM5MjI3MDExOTg4MTUyMTEwMzI4MTE5MjkyMjI4NjM3MDU4MDQ3NzYwODYwOTQ0NTY3MzQxMjY4MTY4Mjk3NjE5MDM2ODEwMjYwODM2NDI1NDkwMzU3NjE4NzM4NTYxNTY2MTUxODQ3MzIxNzM1MjQ5ODk1MDU5NTY2OTQxODI5MjE0Nzc0MTA0NzYyNTQwMjcyMjk2NjE1NTE3NjUwMDcyNDQyMTI0NjY5MDEzMTc1ODAyMDk5MDQxMzk3MzE5ODQ0OTA2MDgwOTYxNTcyMTcwNjg2NzgzNDM1Mjg2MDUyMzE5ODY3ODExMDE5MjAxMDYwODM2OTM3Mzc0MzM0NDM5MTQxMDAzMTI3NTcyNjgzNTgwODI0OTkwOTg3MjE5MzU4NzkzOTM2NTU4Nzk3MjI0MDQzNTM1ODA5NzMyNzgxMjE1NzEwNjI1MjQzODYwNTk4MTk0MjU2MjAwODkwOTA3ODAzMDcyMTAzNzc3MzkwODk4MDczOTgyNjY3Njc1ODg0MjI3MjU0Mzc2OTI5Mjg3ODQyNDE0MTE0MjcwNDQwMTEzNDUxNjk4NzE5Nzc5NjQyNTI4MDA4NDM3Mzk5NjI0NTE3OTM4Nzg5MDc3ODE5ODA0MDY5MzcxOTM0NzExMTIyNTQyODU0OTg4MDA0Mjc4NDkwMjAxNTk2NjE0MjUwODc3NDYxMDczNjc3NTUzNzYxMTMyMTA5Nzg3NTQ2ODE1ODk5Njc2NCJdLFsibmFtZSIsIjYyNzgwNTIwMTM3MzI3NTUzMDc3MDg4NTE4NDg1NDYyMTA0NjEzMjEyNzY3ODUwMzYwNTc3NDQ4MDUxNTk5MTMxMTM1NTI2NzQ3Nzc2NzMzMDg1MDMwODcyMDE1OTM2MTI2NzE0MTIxMDgxMzg3ODU2MTkwMTkzMzI3ODY3OTE0NTEzODM2NTQ1OTY4Mjg1NTc5ODEyODMxMDI4ODc2Nzg1NzI3OTQ2MTEwNzg5Mzc0MjcyODgzMzkyOTgwNDkwODk3NDkwMTc5MDQ0ODM0NTgwMzQ2ODY4NDI2ODc0ODU4NTY1OTg4NTUyMDcwNjI1NDczNjM4MDM3Njc5NTU1NTk2MzE5MTc3Nzc5OTcxMTIxMjQzMjgyMTIyOTQ2NjY0ODMxOTgxMTg3MzQ3MzcyMjkxMjYwOTM3MzkzNDA1ODk5OTI0NjM4MzE3ODI5MDczODMxMjI4ODc1Njg5MTcyMTg4NjIyMDI5NzcxNzM5MTQ5NDY2Mzg3NTM5NjkyNDQ5NDU5MjczNDI5NzM5MjMzNjkyNTEzNDA5OTkyNDgxNTQ4ODk0NjAzNjM3MTYzNjA4MTM0MTAzMTk3Nzc3NTM4OTYwMDcyMjcyMzYyNzM4NDM1MTM3MDcyNzIxMjExMDYxNTg4MDE3ODczODg3MTEwNDA2OTk1NDQ4ODIwMDEzMDA5MjgyMzk0OTczMDMwMDI5MTY3NjQ5NzY1OTI1MTUxMzY4NTg5OTkyNzMyMDE1ODAwNjAzNzYxOTI3MTg3MDM4MDkxNDY3MDE1MjA3MzIwNDczMDM0NDA3MDIyNDA0NjQ4MTI0NTk2NjQwNjU1NjY1MTIzMzY5Njc0ODI2NDE3MjE2ODUxNTM4Njc1NTM3NzAwOTg4MTQzNzE1NTE3NzMwMTM4NjA4NzkxMjcyMzM0MDUyMzY4OCJdXX0sIm5vbmNlIjoiNDE0MzQ4Njg0NDk2OTAxNjkyMjI2OTY0In0=", + }, + "mime-type": "application/json", + }, + ], + "~service": { + "recipientKeys": [ + "DXubCT3ahg6N7aASVFVei1GNUTecne8m3iRWjVNiAw31", + ], + "routingKeys": [], + "serviceEndpoint": "http://localhost:3000", + }, + "~thread": { + "thid": "f9f79a46-a4d8-4ee7-9745-1b9cdf03676b", + }, + }, + "metadata": {}, + "role": "sender", + "updatedAt": "2023-03-18T18:54:00.014Z", + }, + }, + "fcdba9cd-3132-4e46-9677-f78c5a146cf0": { + "id": "fcdba9cd-3132-4e46-9677-f78c5a146cf0", + "tags": { + "issuerId": "did:indy:bcovrin:test:A4CYPASJYRZRt98YWrac3H", + "methodName": "indy", + "schemaId": "did:indy:bcovrin:test:A4CYPASJYRZRt98YWrac3H/anoncreds/v0/SCHEMA/Test Schema/5.0", + "schemaIssuerDid": undefined, + "schemaName": "Test Schema", + "schemaVersion": "5.0", + "unqualifiedSchemaId": "A4CYPASJYRZRt98YWrac3H:2:Test Schema:5.0", + }, + "type": "AnonCredsSchemaRecord", + "value": { + "id": "fcdba9cd-3132-4e46-9677-f78c5a146cf0", + "metadata": {}, + "methodName": "indy", + "schema": { + "attrNames": [ + "name", + "age", + ], + "issuerId": "did:indy:bcovrin:test:A4CYPASJYRZRt98YWrac3H", + "name": "Test Schema", + "version": "5.0", + }, + "schemaId": "did:indy:bcovrin:test:A4CYPASJYRZRt98YWrac3H/anoncreds/v0/SCHEMA/Test Schema/5.0", + "updatedAt": "2023-03-19T22:50:20.522Z", + }, + }, +} +`; diff --git a/packages/anoncreds/src/utils/__tests__/areRequestsEqual.test.ts b/packages/anoncreds/src/utils/__tests__/areRequestsEqual.test.ts new file mode 100644 index 0000000000..51f9c3317e --- /dev/null +++ b/packages/anoncreds/src/utils/__tests__/areRequestsEqual.test.ts @@ -0,0 +1,419 @@ +import type { AnonCredsProofRequest } from '../../models' + +import { areAnonCredsProofRequestsEqual } from '../areRequestsEqual' + +const proofRequest = { + name: 'Proof Request', + version: '1.0.0', + nonce: 'nonce', + ver: '1.0', + non_revoked: {}, + requested_attributes: { + a: { + names: ['name1', 'name2'], + restrictions: [ + { + cred_def_id: 'cred_def_id1', + }, + { + schema_id: 'schema_id', + }, + ], + }, + }, + requested_predicates: { + p: { + name: 'Hello', + p_type: '<', + p_value: 10, + restrictions: [ + { + cred_def_id: 'string2', + }, + { + cred_def_id: 'string', + }, + ], + }, + }, +} satisfies AnonCredsProofRequest + +describe('util | areAnonCredsProofRequestsEqual', () => { + test('does not compare name, ver, version and nonce', () => { + expect( + areAnonCredsProofRequestsEqual(proofRequest, { + ...proofRequest, + name: 'Proof Request 2', + version: '2.0.0', + nonce: 'nonce2', + ver: '2.0', + }) + ).toBe(true) + }) + + test('check top level non_revocation interval', () => { + // empty object is semantically equal to undefined + expect( + areAnonCredsProofRequestsEqual(proofRequest, { + ...proofRequest, + non_revoked: {}, + }) + ).toBe(true) + + // properties inside object are different + expect( + areAnonCredsProofRequestsEqual( + { + ...proofRequest, + non_revoked: { + to: 5, + }, + }, + { + ...proofRequest, + non_revoked: { + from: 5, + }, + } + ) + ).toBe(false) + + // One has non_revoked, other doesn't + expect( + areAnonCredsProofRequestsEqual(proofRequest, { + ...proofRequest, + non_revoked: { + from: 5, + }, + }) + ).toBe(false) + }) + + test('ignores attribute group name differences', () => { + expect( + areAnonCredsProofRequestsEqual(proofRequest, { + ...proofRequest, + requested_attributes: { + b: proofRequest.requested_attributes.a, + }, + }) + ).toBe(true) + }) + + test('ignores attribute restriction order', () => { + expect( + areAnonCredsProofRequestsEqual(proofRequest, { + ...proofRequest, + requested_attributes: { + a: { + ...proofRequest.requested_attributes.a, + restrictions: [...proofRequest.requested_attributes.a.restrictions].reverse(), + }, + }, + }) + ).toBe(true) + }) + + test('ignores attribute restriction undefined vs empty array', () => { + expect( + areAnonCredsProofRequestsEqual( + { + ...proofRequest, + requested_attributes: { + a: { + ...proofRequest.requested_attributes.a, + restrictions: undefined, + }, + }, + }, + { + ...proofRequest, + requested_attributes: { + a: { + ...proofRequest.requested_attributes.a, + restrictions: [], + }, + }, + } + ) + ).toBe(true) + }) + + test('ignores attribute names order', () => { + expect( + areAnonCredsProofRequestsEqual(proofRequest, { + ...proofRequest, + requested_attributes: { + a: { + ...proofRequest.requested_attributes.a, + names: ['name2', 'name1'], + }, + }, + }) + ).toBe(true) + }) + + test('checks attribute non_revocation interval', () => { + // empty object is semantically equal to undefined + expect( + areAnonCredsProofRequestsEqual(proofRequest, { + ...proofRequest, + requested_attributes: { + a: { + ...proofRequest.requested_attributes.a, + non_revoked: {}, + }, + }, + }) + ).toBe(true) + + // properties inside object are different + expect( + areAnonCredsProofRequestsEqual( + { + ...proofRequest, + requested_attributes: { + a: { + ...proofRequest.requested_attributes.a, + non_revoked: { + to: 5, + }, + }, + }, + }, + { + ...proofRequest, + requested_attributes: { + a: { + ...proofRequest.requested_attributes.a, + non_revoked: { + from: 5, + }, + }, + }, + } + ) + ).toBe(false) + + // One has non_revoked, other doesn't + expect( + areAnonCredsProofRequestsEqual(proofRequest, { + ...proofRequest, + requested_attributes: { + a: { + ...proofRequest.requested_attributes.a, + non_revoked: { + from: 5, + }, + }, + }, + }) + ).toBe(false) + }) + + test('checks attribute restriction differences', () => { + expect( + areAnonCredsProofRequestsEqual(proofRequest, { + ...proofRequest, + requested_attributes: { + a: { + ...proofRequest.requested_attributes.a, + restrictions: [ + { + cred_def_id: 'cred_def_id1', + }, + { + cred_def_id: 'cred_def_id2', + }, + ], + }, + }, + }) + ).toBe(false) + }) + + test('checks attribute name differences', () => { + expect( + areAnonCredsProofRequestsEqual(proofRequest, { + ...proofRequest, + requested_attributes: { + a: { + ...proofRequest.requested_attributes.a, + names: ['name3'], + }, + }, + }) + ).toBe(false) + + expect( + areAnonCredsProofRequestsEqual(proofRequest, { + ...proofRequest, + requested_attributes: { + a: { + ...proofRequest.requested_attributes.a, + name: 'name3', + names: undefined, + }, + }, + }) + ).toBe(false) + }) + + test('ignores predicate group name differences', () => { + expect( + areAnonCredsProofRequestsEqual(proofRequest, { + ...proofRequest, + requested_predicates: { + a: proofRequest.requested_predicates.p, + }, + }) + ).toBe(true) + }) + + test('ignores predicate restriction order', () => { + expect( + areAnonCredsProofRequestsEqual(proofRequest, { + ...proofRequest, + requested_predicates: { + p: { + ...proofRequest.requested_predicates.p, + restrictions: [...proofRequest.requested_predicates.p.restrictions].reverse(), + }, + }, + }) + ).toBe(true) + }) + + test('ignores predicate restriction undefined vs empty array', () => { + expect( + areAnonCredsProofRequestsEqual( + { + ...proofRequest, + requested_predicates: { + p: { + ...proofRequest.requested_predicates.p, + restrictions: undefined, + }, + }, + }, + { + ...proofRequest, + requested_predicates: { + p: { + ...proofRequest.requested_predicates.p, + restrictions: [], + }, + }, + } + ) + ).toBe(true) + }) + + test('checks predicate restriction differences', () => { + expect( + areAnonCredsProofRequestsEqual(proofRequest, { + ...proofRequest, + requested_attributes: { + p: { + ...proofRequest.requested_predicates.p, + restrictions: [ + { + cred_def_id: 'cred_def_id1', + }, + { + cred_def_id: 'cred_def_id2', + }, + ], + }, + }, + }) + ).toBe(false) + }) + + test('checks predicate name differences', () => { + expect( + areAnonCredsProofRequestsEqual(proofRequest, { + ...proofRequest, + requested_predicates: { + p: { + ...proofRequest.requested_predicates.p, + name: 'name3', + }, + }, + }) + ).toBe(false) + }) + + test('checks predicate non_revocation interval', () => { + // empty object is semantically equal to undefined + expect( + areAnonCredsProofRequestsEqual(proofRequest, { + ...proofRequest, + requested_predicates: { + p: { + ...proofRequest.requested_predicates.p, + non_revoked: {}, + }, + }, + }) + ).toBe(true) + + // properties inside object are different + expect( + areAnonCredsProofRequestsEqual( + { + ...proofRequest, + requested_predicates: { + p: { + ...proofRequest.requested_predicates.p, + non_revoked: { + to: 5, + }, + }, + }, + }, + { + ...proofRequest, + requested_predicates: { + p: { + ...proofRequest.requested_predicates.p, + non_revoked: { + from: 5, + }, + }, + }, + } + ) + ).toBe(false) + + // One has non_revoked, other doesn't + expect( + areAnonCredsProofRequestsEqual(proofRequest, { + ...proofRequest, + requested_predicates: { + p: { + ...proofRequest.requested_predicates.p, + non_revoked: { + from: 5, + }, + }, + }, + }) + ).toBe(false) + }) + + test('checks predicate p_type and p_value', () => { + expect( + areAnonCredsProofRequestsEqual(proofRequest, { + ...proofRequest, + requested_predicates: { + p: { + ...proofRequest.requested_predicates.p, + p_type: '<', + p_value: 134134, + }, + }, + }) + ).toBe(false) + }) +}) diff --git a/packages/core/src/modules/credentials/formats/indy/__tests__/IndyCredentialUtils.test.ts b/packages/anoncreds/src/utils/__tests__/credential.test.ts similarity index 86% rename from packages/core/src/modules/credentials/formats/indy/__tests__/IndyCredentialUtils.test.ts rename to packages/anoncreds/src/utils/__tests__/credential.test.ts index e89a849001..f75598bb3c 100644 --- a/packages/core/src/modules/credentials/formats/indy/__tests__/IndyCredentialUtils.test.ts +++ b/packages/anoncreds/src/utils/__tests__/credential.test.ts @@ -1,5 +1,10 @@ -import { CredentialPreviewAttribute } from '../../../models/CredentialPreviewAttribute' -import { IndyCredentialUtils } from '../IndyCredentialUtils' +import { CredentialPreviewAttribute } from '@aries-framework/core' + +import { + assertCredentialValuesMatch, + checkValidCredentialValueEncoding, + convertAttributesToCredentialValues, +} from '../credential' /** * Sample test cases for encoding/decoding of verifiable credential claims - Aries RFCs 0036 and 0037 @@ -100,8 +105,8 @@ const testEncodings: { [key: string]: { raw: string | number | boolean | null; e }, } -describe('IndyCredentialUtils', () => { - describe('convertAttributesToValues', () => { +describe('Utils | Credentials', () => { + describe('convertAttributesToCredentialValues', () => { test('returns object with raw and encoded attributes', () => { const attributes = [ new CredentialPreviewAttribute({ @@ -116,7 +121,7 @@ describe('IndyCredentialUtils', () => { }), ] - expect(IndyCredentialUtils.convertAttributesToValues(attributes)).toEqual({ + expect(convertAttributesToCredentialValues(attributes)).toEqual({ name: { raw: '101 Wilson Lane', encoded: '68086943237164982734333428280784300550565381723532936263016368251445461241953', @@ -126,7 +131,7 @@ describe('IndyCredentialUtils', () => { }) }) - describe('assertValuesMatch', () => { + describe('assertCredentialValuesMatch', () => { test('does not throw if attributes match', () => { const firstValues = { name: { @@ -143,7 +148,7 @@ describe('IndyCredentialUtils', () => { age: { raw: '1234', encoded: '1234' }, } - expect(() => IndyCredentialUtils.assertValuesMatch(firstValues, secondValues)).not.toThrow() + expect(() => assertCredentialValuesMatch(firstValues, secondValues)).not.toThrow() }) test('throws if number of values in the entries do not match', () => { @@ -158,7 +163,7 @@ describe('IndyCredentialUtils', () => { age: { raw: '1234', encoded: '1234' }, } - expect(() => IndyCredentialUtils.assertValuesMatch(firstValues, secondValues)).toThrow( + expect(() => assertCredentialValuesMatch(firstValues, secondValues)).toThrow( 'Number of values in first entry (1) does not match number of values in second entry (2)' ) }) @@ -179,7 +184,7 @@ describe('IndyCredentialUtils', () => { age: { raw: '1234', encoded: '1234' }, } - expect(() => IndyCredentialUtils.assertValuesMatch(firstValues, secondValues)).toThrow( + expect(() => assertCredentialValuesMatch(firstValues, secondValues)).toThrow( "Second cred values object has no value for key 'name'" ) }) @@ -192,7 +197,7 @@ describe('IndyCredentialUtils', () => { age: { raw: '1234', encoded: '12345' }, } - expect(() => IndyCredentialUtils.assertValuesMatch(firstValues, secondValues)).toThrow( + expect(() => assertCredentialValuesMatch(firstValues, secondValues)).toThrow( "Encoded credential values for key 'age' do not match" ) }) @@ -205,7 +210,7 @@ describe('IndyCredentialUtils', () => { age: { raw: '12345', encoded: '1234' }, } - expect(() => IndyCredentialUtils.assertValuesMatch(firstValues, secondValues)).toThrow( + expect(() => assertCredentialValuesMatch(firstValues, secondValues)).toThrow( "Raw credential values for key 'age' do not match" ) }) @@ -218,7 +223,7 @@ describe('IndyCredentialUtils', () => { ) test.each(testEntries)('returns true for valid encoding %s', (_, raw, encoded) => { - expect(IndyCredentialUtils.checkValidEncoding(raw, encoded)).toEqual(true) + expect(checkValidCredentialValueEncoding(raw, encoded)).toEqual(true) }) }) }) diff --git a/packages/anoncreds/src/utils/__tests__/credentialPreviewAttributes.test.ts b/packages/anoncreds/src/utils/__tests__/credentialPreviewAttributes.test.ts new file mode 100644 index 0000000000..419f9af0b7 --- /dev/null +++ b/packages/anoncreds/src/utils/__tests__/credentialPreviewAttributes.test.ts @@ -0,0 +1,143 @@ +import { areCredentialPreviewAttributesEqual } from '../credentialPreviewAttributes' + +describe('areCredentialPreviewAttributesEqual', () => { + test('returns true if the attributes are equal', () => { + const firstAttributes = [ + { + name: 'firstName', + value: 'firstValue', + mimeType: 'text/grass', + }, + { + name: 'secondName', + value: 'secondValue', + mimeType: 'text/grass', + }, + ] + + const secondAttribute = [ + { + name: 'firstName', + value: 'firstValue', + mimeType: 'text/grass', + }, + { + name: 'secondName', + value: 'secondValue', + mimeType: 'text/grass', + }, + ] + + expect(areCredentialPreviewAttributesEqual(firstAttributes, secondAttribute)).toBe(true) + }) + + test('returns false if the attribute name and value are equal but the mime type is different', () => { + const firstAttributes = [ + { + name: 'secondName', + value: 'secondValue', + mimeType: 'text/grass', + }, + ] + + const secondAttribute = [ + { + name: 'secondName', + value: 'secondValue', + mimeType: 'text/notGrass', + }, + ] + + expect(areCredentialPreviewAttributesEqual(firstAttributes, secondAttribute)).toBe(false) + }) + + test('returns false if the attribute name and mime type are equal but the value is different', () => { + const firstAttributes = [ + { + name: 'secondName', + value: 'secondValue', + mimeType: 'text/grass', + }, + ] + + const secondAttribute = [ + { + name: 'secondName', + value: 'thirdValue', + mimeType: 'text/grass', + }, + ] + + expect(areCredentialPreviewAttributesEqual(firstAttributes, secondAttribute)).toBe(false) + }) + + test('returns false if the value and mime type are equal but the name is different', () => { + const firstAttributes = [ + { + name: 'secondName', + value: 'secondValue', + mimeType: 'text/grass', + }, + ] + + const secondAttribute = [ + { + name: 'thirdName', + value: 'secondValue', + mimeType: 'text/grass', + }, + ] + + expect(areCredentialPreviewAttributesEqual(firstAttributes, secondAttribute)).toBe(false) + }) + + test('returns false if the length of the attributes does not match', () => { + const firstAttributes = [ + { + name: 'secondName', + value: 'secondValue', + mimeType: 'text/grass', + }, + ] + + const secondAttribute = [ + { + name: 'thirdName', + value: 'secondValue', + mimeType: 'text/grass', + }, + { + name: 'fourthName', + value: 'secondValue', + mimeType: 'text/grass', + }, + ] + + expect(areCredentialPreviewAttributesEqual(firstAttributes, secondAttribute)).toBe(false) + }) + + test('returns false if duplicate key names exist', () => { + const firstAttributes = [ + { + name: 'secondName', + value: 'secondValue', + mimeType: 'text/grass', + }, + { + name: 'secondName', + value: 'secondValue', + mimeType: 'text/grass', + }, + ] + + const secondAttribute = [ + { + name: 'secondName', + value: 'secondValue', + mimeType: 'text/grass', + }, + ] + + expect(areCredentialPreviewAttributesEqual(firstAttributes, secondAttribute)).toBe(false) + }) +}) diff --git a/packages/anoncreds/src/utils/__tests__/hasDuplicateGroupNames.test.ts b/packages/anoncreds/src/utils/__tests__/hasDuplicateGroupNames.test.ts new file mode 100644 index 0000000000..c4deb02be7 --- /dev/null +++ b/packages/anoncreds/src/utils/__tests__/hasDuplicateGroupNames.test.ts @@ -0,0 +1,72 @@ +import type { AnonCredsProofRequest } from '../../models' + +import { assertNoDuplicateGroupsNamesInProofRequest } from '../hasDuplicateGroupNames' + +const credentialDefinitionId = '9vPXgSpQJPkJEALbLXueBp:3:CL:57753:tag1' + +describe('util | assertNoDuplicateGroupsNamesInProofRequest', () => { + describe('assertNoDuplicateGroupsNamesInProofRequest', () => { + test('attribute names match', () => { + const proofRequest = { + name: 'proof-request', + version: '1.0', + nonce: 'testtesttest12345', + requested_attributes: { + age1: { + name: 'age', + restrictions: [ + { + cred_def_id: credentialDefinitionId, + }, + ], + }, + age2: { + name: 'age', + restrictions: [ + { + cred_def_id: credentialDefinitionId, + }, + ], + }, + }, + requested_predicates: {}, + } satisfies AnonCredsProofRequest + + expect(() => assertNoDuplicateGroupsNamesInProofRequest(proofRequest)).not.toThrow() + }) + + test('attribute names match with predicates name', () => { + const proofRequest = { + name: 'proof-request', + version: '1.0', + nonce: 'testtesttest12345', + requested_attributes: { + attrib: { + name: 'age', + restrictions: [ + { + cred_def_id: credentialDefinitionId, + }, + ], + }, + }, + requested_predicates: { + predicate: { + name: 'age', + p_type: '>=', + p_value: 50, + restrictions: [ + { + cred_def_id: credentialDefinitionId, + }, + ], + }, + }, + } satisfies AnonCredsProofRequest + + expect(() => assertNoDuplicateGroupsNamesInProofRequest(proofRequest)).toThrowError( + 'The proof request contains duplicate predicates and attributes: age' + ) + }) + }) +}) diff --git a/packages/anoncreds/src/utils/__tests__/indyIdentifiers.test.ts b/packages/anoncreds/src/utils/__tests__/indyIdentifiers.test.ts new file mode 100644 index 0000000000..7fba68562d --- /dev/null +++ b/packages/anoncreds/src/utils/__tests__/indyIdentifiers.test.ts @@ -0,0 +1,147 @@ +import { + getUnqualifiedCredentialDefinitionId, + getUnqualifiedRevocationRegistryId, + getUnqualifiedSchemaId, + parseIndyCredentialDefinitionId, + parseIndyRevocationRegistryId, + parseIndySchemaId, + unqualifiedCredentialDefinitionIdRegex, + unqualifiedIndyDidRegex, + unqualifiedRevocationRegistryIdRegex, + unqualifiedSchemaIdRegex, + unqualifiedSchemaVersionRegex, +} from '../indyIdentifiers' + +describe('Legacy Indy Identifier Regex', () => { + const invalidTest = 'test' + + test('test for legacyIndyCredentialDefinitionIdRegex', async () => { + const test = 'q7ATwTYbQDgiigVijUAej:3:CL:160971:1.0.0' + expect(test).toMatch(unqualifiedCredentialDefinitionIdRegex) + expect(unqualifiedCredentialDefinitionIdRegex.test(invalidTest)).toBeFalsy() + }) + + test('test for legacyIndyDidRegex', async () => { + const test = 'did:sov:q7ATwTYbQDgiigVijUAej' + expect(test).toMatch(unqualifiedIndyDidRegex) + expect(unqualifiedIndyDidRegex.test(invalidTest)).toBeFalsy() + }) + + test('test for legacyIndySchemaIdRegex', async () => { + const test = 'q7ATwTYbQDgiigVijUAej:2:test:1.0' + expect(test).toMatch(unqualifiedSchemaIdRegex) + expect(unqualifiedSchemaIdRegex.test(invalidTest)).toBeFalsy() + }) + + test('test for legacyIndySchemaIdRegex', async () => { + const test = 'N7baRMcyvPwWc8v85CtZ6e:4:N7baRMcyvPwWc8v85CtZ6e:3:CL:100669:SCH Employee ID:CL_ACCUM:1-1024' + expect(test).toMatch(unqualifiedRevocationRegistryIdRegex) + expect(unqualifiedRevocationRegistryIdRegex.test(invalidTest)).toBeFalsy() + }) + + test('test for legacyIndySchemaVersionRegex', async () => { + const test = '1.0.0' + expect(test).toMatch(unqualifiedSchemaVersionRegex) + expect(unqualifiedSchemaVersionRegex.test(invalidTest)).toBeFalsy() + }) + + test('getUnqualifiedSchemaId returns a valid schema id given a did, name, and version', () => { + const did = '12345' + const name = 'backbench' + const version = '420' + + expect(getUnqualifiedSchemaId(did, name, version)).toEqual('12345:2:backbench:420') + }) + + test('getUnqualifiedCredentialDefinitionId returns a valid credential definition id given a did, seqNo, and tag', () => { + const did = '12345' + const seqNo = 420 + const tag = 'someTag' + + expect(getUnqualifiedCredentialDefinitionId(did, seqNo, tag)).toEqual('12345:3:CL:420:someTag') + }) + + test('getUnqualifiedRevocationRegistryId returns a valid credential definition id given a did, seqNo, and tag', () => { + const did = '12345' + const seqNo = 420 + const credentialDefinitionTag = 'someTag' + const tag = 'anotherTag' + + expect(getUnqualifiedRevocationRegistryId(did, seqNo, credentialDefinitionTag, tag)).toEqual( + '12345:4:12345:3:CL:420:someTag:CL_ACCUM:anotherTag' + ) + }) + + describe('parseIndySchemaId', () => { + test('parses legacy schema id', () => { + expect(parseIndySchemaId('SDqTzbVuCowusqGBNbNDjH:2:schema-name:1.0')).toEqual({ + did: 'SDqTzbVuCowusqGBNbNDjH', + namespaceIdentifier: 'SDqTzbVuCowusqGBNbNDjH', + schemaName: 'schema-name', + schemaVersion: '1.0', + }) + }) + + test('parses did:indy schema id', () => { + expect( + parseIndySchemaId('did:indy:bcovrin:test:SDqTzbVuCowusqGBNbNDjH/anoncreds/v0/SCHEMA/schema-name/1.0') + ).toEqual({ + namespaceIdentifier: 'SDqTzbVuCowusqGBNbNDjH', + did: 'did:indy:bcovrin:test:SDqTzbVuCowusqGBNbNDjH', + schemaName: 'schema-name', + schemaVersion: '1.0', + namespace: 'bcovrin:test', + }) + }) + }) + + describe('parseIndyCredentialDefinitionId', () => { + test('parses legacy credential definition id', () => { + expect(parseIndyCredentialDefinitionId('TL1EaPFCZ8Si5aUrqScBDt:3:CL:10:TAG')).toEqual({ + did: 'TL1EaPFCZ8Si5aUrqScBDt', + namespaceIdentifier: 'TL1EaPFCZ8Si5aUrqScBDt', + schemaSeqNo: '10', + tag: 'TAG', + }) + }) + + test('parses did:indy credential definition id', () => { + expect( + parseIndyCredentialDefinitionId('did:indy:pool:localtest:TL1EaPFCZ8Si5aUrqScBDt/anoncreds/v0/CLAIM_DEF/10/TAG') + ).toEqual({ + namespaceIdentifier: 'TL1EaPFCZ8Si5aUrqScBDt', + did: 'did:indy:pool:localtest:TL1EaPFCZ8Si5aUrqScBDt', + namespace: 'pool:localtest', + schemaSeqNo: '10', + tag: 'TAG', + }) + }) + }) + + describe('parseIndyRevocationRegistryId', () => { + test('parses legacy revocation registry id', () => { + expect( + parseIndyRevocationRegistryId('5nDyJVP1NrcPAttP3xwMB9:4:5nDyJVP1NrcPAttP3xwMB9:3:CL:56495:npdb:CL_ACCUM:TAG1') + ).toEqual({ + did: '5nDyJVP1NrcPAttP3xwMB9', + namespaceIdentifier: '5nDyJVP1NrcPAttP3xwMB9', + schemaSeqNo: '56495', + credentialDefinitionTag: 'npdb', + revocationRegistryTag: 'TAG1', + }) + }) + + test('parses did:indy revocation registry id', () => { + expect( + parseIndyRevocationRegistryId('did:indy:sovrin:5nDyJVP1NrcPAttP3xwMB9/anoncreds/v0/REV_REG_DEF/56495/npdb/TAG1') + ).toEqual({ + namespace: 'sovrin', + namespaceIdentifier: '5nDyJVP1NrcPAttP3xwMB9', + did: 'did:indy:sovrin:5nDyJVP1NrcPAttP3xwMB9', + schemaSeqNo: '56495', + credentialDefinitionTag: 'npdb', + revocationRegistryTag: 'TAG1', + }) + }) + }) +}) diff --git a/packages/anoncreds/src/utils/__tests__/revocationInterval.test.ts b/packages/anoncreds/src/utils/__tests__/revocationInterval.test.ts new file mode 100644 index 0000000000..0aa0b15d9e --- /dev/null +++ b/packages/anoncreds/src/utils/__tests__/revocationInterval.test.ts @@ -0,0 +1,37 @@ +import { assertBestPracticeRevocationInterval } from '../../utils' + +describe('assertBestPracticeRevocationInterval', () => { + test("throws if no 'to' value is specified", () => { + expect(() => + assertBestPracticeRevocationInterval({ + from: 10, + }) + ).toThrow() + }) + + test("throws if a 'from' value is specified and it is different from 'to'", () => { + expect(() => + assertBestPracticeRevocationInterval({ + to: 5, + from: 10, + }) + ).toThrow() + }) + + test('does not throw if only to is provided', () => { + expect(() => + assertBestPracticeRevocationInterval({ + to: 5, + }) + ).not.toThrow() + }) + + test('does not throw if from and to are equal', () => { + expect(() => + assertBestPracticeRevocationInterval({ + to: 10, + from: 10, + }) + ).not.toThrow() + }) +}) diff --git a/packages/anoncreds/src/utils/__tests__/sortRequestedCredentialsMatches.test.ts b/packages/anoncreds/src/utils/__tests__/sortRequestedCredentialsMatches.test.ts new file mode 100644 index 0000000000..0bd658a646 --- /dev/null +++ b/packages/anoncreds/src/utils/__tests__/sortRequestedCredentialsMatches.test.ts @@ -0,0 +1,57 @@ +import type { AnonCredsCredentialInfo, AnonCredsRequestedAttributeMatch } from '../../models' + +import { sortRequestedCredentialsMatches } from '../sortRequestedCredentialsMatches' + +const credentialInfo = {} as unknown as AnonCredsCredentialInfo + +const credentials: AnonCredsRequestedAttributeMatch[] = [ + { + credentialId: '1', + revealed: true, + revoked: true, + credentialInfo, + }, + { + credentialId: '2', + revealed: true, + revoked: undefined, + credentialInfo, + }, + { + credentialId: '3', + revealed: true, + revoked: false, + credentialInfo, + }, + { + credentialId: '4', + revealed: true, + revoked: false, + credentialInfo, + }, + { + credentialId: '5', + revealed: true, + revoked: true, + credentialInfo, + }, + { + credentialId: '6', + revealed: true, + revoked: undefined, + credentialInfo, + }, +] + +describe('sortRequestedCredentialsMatches', () => { + test('sorts the credentials', () => { + expect(sortRequestedCredentialsMatches(credentials)).toEqual([ + credentials[1], + credentials[5], + credentials[2], + credentials[3], + credentials[0], + credentials[4], + ]) + }) +}) diff --git a/packages/anoncreds/src/utils/areRequestsEqual.ts b/packages/anoncreds/src/utils/areRequestsEqual.ts new file mode 100644 index 0000000000..759312cf87 --- /dev/null +++ b/packages/anoncreds/src/utils/areRequestsEqual.ts @@ -0,0 +1,156 @@ +import type { AnonCredsNonRevokedInterval, AnonCredsProofRequest, AnonCredsProofRequestRestriction } from '../models' + +// Copied from the core package so we don't have to export these silly utils. We should probably move these to a separate package. +// eslint-disable-next-line @typescript-eslint/no-explicit-any +function areObjectsEqual(a: any, b: any): boolean { + if (typeof a == 'object' && a != null && typeof b == 'object' && b != null) { + if (Object.keys(a).length !== Object.keys(b).length) return false + for (const key in a) { + if (!(key in b) || !areObjectsEqual(a[key], b[key])) { + return false + } + } + for (const key in b) { + if (!(key in a) || !areObjectsEqual(b[key], a[key])) { + return false + } + } + return true + } else { + return a === b + } +} + +/** + * Checks whether two `names` arrays are equal. The order of the names doesn't matter. + */ +function areNamesEqual(namesA: string[] | undefined, namesB: string[] | undefined) { + if (namesA === undefined) return namesB === undefined || namesB.length === 0 + if (namesB === undefined) return namesA.length === 0 + + // Check if there are any duplicates + if (new Set(namesA).size !== namesA.length || new Set(namesB).size !== namesB.length) return false + + // Check if the number of names is equal between A & B + if (namesA.length !== namesB.length) return false + + return namesA.every((a) => namesB.includes(a)) +} + +/** + * Checks whether two proof requests are semantically equal. The `name`, `version` and `nonce`, `ver` fields are ignored. + * In addition the group names don't have to be the same between the different requests. + */ +export function areAnonCredsProofRequestsEqual( + requestA: AnonCredsProofRequest, + requestB: AnonCredsProofRequest +): boolean { + // Check if the top-level non-revocation interval is equal + if (!isNonRevokedEqual(requestA.non_revoked, requestB.non_revoked)) return false + + const attributeAList = Object.values(requestA.requested_attributes) + const attributeBList = Object.values(requestB.requested_attributes) + + // Check if the number of attribute groups is equal in both requests + if (attributeAList.length !== attributeBList.length) return false + + // Check if all attribute groups in A are also in B + const attributesMatch = attributeAList.every((a) => { + // find an attribute in B that matches this attribute + const bIndex = attributeBList.findIndex((b) => { + return ( + b.name === a.name && + areNamesEqual(a.names, b.names) && + isNonRevokedEqual(a.non_revoked, b.non_revoked) && + areRestrictionsEqual(a.restrictions, b.restrictions) + ) + }) + + // Match found + if (bIndex !== -1) { + attributeBList.splice(bIndex, 1) + return true + } + + // Match not found + return false + }) + + if (!attributesMatch) return false + + const predicatesA = Object.values(requestA.requested_predicates) + const predicatesB = Object.values(requestB.requested_predicates) + + if (predicatesA.length !== predicatesB.length) return false + const predicatesMatch = predicatesA.every((a) => { + // find a predicate in B that matches this predicate + const bIndex = predicatesB.findIndex((b) => { + return ( + a.name === b.name && + a.p_type === b.p_type && + a.p_value === b.p_value && + isNonRevokedEqual(a.non_revoked, b.non_revoked) && + areRestrictionsEqual(a.restrictions, b.restrictions) + ) + }) + + if (bIndex !== -1) { + predicatesB.splice(bIndex, 1) + return true + } + + return false + }) + + if (!predicatesMatch) return false + + return true +} + +/** + * Checks whether two non-revocation intervals are semantically equal. They are considered equal if: + * - Both are undefined + * - Both are empty objects + * - One if undefined and the other is an empty object + * - Both have the same from and to values + */ +function isNonRevokedEqual( + nonRevokedA: AnonCredsNonRevokedInterval | undefined, + nonRevokedB: AnonCredsNonRevokedInterval | undefined +) { + // Having an empty non-revoked object is the same as not having one + if (nonRevokedA === undefined) + return nonRevokedB === undefined || (nonRevokedB.from === undefined && nonRevokedB.to === undefined) + if (nonRevokedB === undefined) return nonRevokedA.from === undefined && nonRevokedA.to === undefined + + return nonRevokedA.from === nonRevokedB.from && nonRevokedA.to === nonRevokedB.to +} + +/** + * Check if two restriction lists are equal. The order of the restrictions does not matter. + */ +function areRestrictionsEqual( + restrictionsA: AnonCredsProofRequestRestriction[] | undefined, + restrictionsB: AnonCredsProofRequestRestriction[] | undefined +) { + // Having an undefined restrictions property or an empty array is the same + if (restrictionsA === undefined) return restrictionsB === undefined || restrictionsB.length === 0 + if (restrictionsB === undefined) return restrictionsA.length === 0 + + // Clone array to not modify input object + const bList = [...restrictionsB] + + // Check if all restrictions in A are also in B + return restrictionsA.every((a) => { + const bIndex = restrictionsB.findIndex((b) => areObjectsEqual(a, b)) + + // Match found + if (bIndex !== -1) { + bList.splice(bIndex, 1) + return true + } + + // Match not found + return false + }) +} diff --git a/packages/anoncreds/src/utils/composeAutoAccept.ts b/packages/anoncreds/src/utils/composeAutoAccept.ts new file mode 100644 index 0000000000..0d874154d2 --- /dev/null +++ b/packages/anoncreds/src/utils/composeAutoAccept.ts @@ -0,0 +1,21 @@ +import { AutoAcceptCredential, AutoAcceptProof } from '@aries-framework/core' + +/** + * Returns the credential auto accept config based on priority: + * - The record config takes first priority + * - Otherwise the agent config + * - Otherwise {@link AutoAcceptCredential.Never} is returned + */ +export function composeCredentialAutoAccept(recordConfig?: AutoAcceptCredential, agentConfig?: AutoAcceptCredential) { + return recordConfig ?? agentConfig ?? AutoAcceptCredential.Never +} + +/** + * Returns the proof auto accept config based on priority: + * - The record config takes first priority + * - Otherwise the agent config + * - Otherwise {@link AutoAcceptProof.Never} is returned + */ +export function composeProofAutoAccept(recordConfig?: AutoAcceptProof, agentConfig?: AutoAcceptProof) { + return recordConfig ?? agentConfig ?? AutoAcceptProof.Never +} diff --git a/packages/anoncreds/src/utils/createRequestFromPreview.ts b/packages/anoncreds/src/utils/createRequestFromPreview.ts new file mode 100644 index 0000000000..d1738d000e --- /dev/null +++ b/packages/anoncreds/src/utils/createRequestFromPreview.ts @@ -0,0 +1,89 @@ +import type { + AnonCredsPresentationPreviewAttribute, + AnonCredsPresentationPreviewPredicate, +} from '../formats/AnonCredsProofFormat' +import type { AnonCredsProofRequest } from '../models' + +import { utils } from '@aries-framework/core' + +export function createRequestFromPreview({ + name, + version, + nonce, + attributes, + predicates, +}: { + name: string + version: string + nonce: string + attributes: AnonCredsPresentationPreviewAttribute[] + predicates: AnonCredsPresentationPreviewPredicate[] +}): AnonCredsProofRequest { + const proofRequest: AnonCredsProofRequest = { + name, + version, + nonce, + requested_attributes: {}, + requested_predicates: {}, + } + + /** + * Create mapping of attributes by referent. This required the + * attributes to come from the same credential. + * @see https://github.com/hyperledger/aries-rfcs/blob/master/features/0037-present-proof/README.md#referent + * + * { + * "referent1": [Attribute1, Attribute2], + * "referent2": [Attribute3] + * } + */ + const attributesByReferent: Record = {} + for (const proposedAttributes of attributes ?? []) { + const referent = proposedAttributes.referent ?? utils.uuid() + + const referentAttributes = attributesByReferent[referent] + + // Referent key already exist, add to list + if (referentAttributes) { + referentAttributes.push(proposedAttributes) + } + + // Referent key does not exist yet, create new entry + else { + attributesByReferent[referent] = [proposedAttributes] + } + } + + // Transform attributes by referent to requested attributes + for (const [referent, proposedAttributes] of Object.entries(attributesByReferent)) { + // Either attributeName or attributeNames will be undefined + const attributeName = proposedAttributes.length == 1 ? proposedAttributes[0].name : undefined + const attributeNames = proposedAttributes.length > 1 ? proposedAttributes.map((a) => a.name) : undefined + + proofRequest.requested_attributes[referent] = { + name: attributeName, + names: attributeNames, + restrictions: [ + { + cred_def_id: proposedAttributes[0].credentialDefinitionId, + }, + ], + } + } + + // Transform proposed predicates to requested predicates + for (const proposedPredicate of predicates ?? []) { + proofRequest.requested_predicates[utils.uuid()] = { + name: proposedPredicate.name, + p_type: proposedPredicate.predicate, + p_value: proposedPredicate.threshold, + restrictions: [ + { + cred_def_id: proposedPredicate.credentialDefinitionId, + }, + ], + } + } + + return proofRequest +} diff --git a/packages/anoncreds/src/utils/credential.ts b/packages/anoncreds/src/utils/credential.ts new file mode 100644 index 0000000000..33a7a05c41 --- /dev/null +++ b/packages/anoncreds/src/utils/credential.ts @@ -0,0 +1,199 @@ +import type { AnonCredsSchema, AnonCredsCredentialValues } from '../models' +import type { CredentialPreviewAttributeOptions, LinkedAttachment } from '@aries-framework/core' + +import { AriesFrameworkError, Hasher, encodeAttachment, Buffer } from '@aries-framework/core' +import BigNumber from 'bn.js' + +const isString = (value: unknown): value is string => typeof value === 'string' +const isNumber = (value: unknown): value is number => typeof value === 'number' +const isBoolean = (value: unknown): value is boolean => typeof value === 'boolean' +const isNumeric = (value: string) => /^-?\d+$/.test(value) + +const isInt32 = (number: number) => { + const minI32 = -2147483648 + const maxI32 = 2147483647 + + // Check if number is integer and in range of int32 + return Number.isInteger(number) && number >= minI32 && number <= maxI32 +} + +/** + * Converts int value to string + * Converts string value: + * - hash with sha256, + * - convert to byte array and reverse it + * - convert it to BigInteger and return as a string + * @param attributes + * + * @returns CredValues + */ +export function convertAttributesToCredentialValues( + attributes: CredentialPreviewAttributeOptions[] +): AnonCredsCredentialValues { + return attributes.reduce((credentialValues, attribute) => { + return { + [attribute.name]: { + raw: attribute.value, + encoded: encodeCredentialValue(attribute.value), + }, + ...credentialValues, + } + }, {}) +} + +/** + * Check whether the values of two credentials match (using {@link assertCredentialValuesMatch}) + * + * @returns a boolean whether the values are equal + * + */ +export function checkCredentialValuesMatch( + firstValues: AnonCredsCredentialValues, + secondValues: AnonCredsCredentialValues +): boolean { + try { + assertCredentialValuesMatch(firstValues, secondValues) + return true + } catch { + return false + } +} + +/** + * Assert two credential values objects match. + * + * @param firstValues The first values object + * @param secondValues The second values object + * + * @throws If not all values match + */ +export function assertCredentialValuesMatch( + firstValues: AnonCredsCredentialValues, + secondValues: AnonCredsCredentialValues +) { + const firstValuesKeys = Object.keys(firstValues) + const secondValuesKeys = Object.keys(secondValues) + + if (firstValuesKeys.length !== secondValuesKeys.length) { + throw new Error( + `Number of values in first entry (${firstValuesKeys.length}) does not match number of values in second entry (${secondValuesKeys.length})` + ) + } + + for (const key of firstValuesKeys) { + const firstValue = firstValues[key] + const secondValue = secondValues[key] + + if (!secondValue) { + throw new Error(`Second cred values object has no value for key '${key}'`) + } + + if (firstValue.encoded !== secondValue.encoded) { + throw new Error(`Encoded credential values for key '${key}' do not match`) + } + + if (firstValue.raw !== secondValue.raw) { + throw new Error(`Raw credential values for key '${key}' do not match`) + } + } +} + +/** + * Check whether the raw value matches the encoded version according to the encoding format described in Aries RFC 0037 + * Use this method to ensure the received proof (over the encoded) value is the same as the raw value of the data. + * + * @param raw + * @param encoded + * @returns Whether raw and encoded value match + * + * @see https://github.com/hyperledger/aries-framework-dotnet/blob/a18bef91e5b9e4a1892818df7408e2383c642dfa/src/Hyperledger.Aries/Utils/CredentialUtils.cs#L78-L89 + * @see https://github.com/hyperledger/aries-rfcs/blob/be4ad0a6fb2823bb1fc109364c96f077d5d8dffa/features/0037-present-proof/README.md#verifying-claims-of-indy-based-verifiable-credentials + */ +export function checkValidCredentialValueEncoding(raw: unknown, encoded: string) { + return encoded === encodeCredentialValue(raw) +} + +/** + * Encode value according to the encoding format described in Aries RFC 0036/0037 + * + * @param value + * @returns Encoded version of value + * + * @see https://github.com/hyperledger/aries-cloudagent-python/blob/0000f924a50b6ac5e6342bff90e64864672ee935/aries_cloudagent/messaging/util.py#L106-L136 + * @see https://github.com/hyperledger/aries-rfcs/blob/be4ad0a6fb2823bb1fc109364c96f077d5d8dffa/features/0037-present-proof/README.md#verifying-claims-of-indy-based-verifiable-credentials + * @see https://github.com/hyperledger/aries-rfcs/blob/be4ad0a6fb2823bb1fc109364c96f077d5d8dffa/features/0036-issue-credential/README.md#encoding-claims-for-indy-based-verifiable-credentials + */ +export function encodeCredentialValue(value: unknown) { + const isEmpty = (value: unknown) => isString(value) && value === '' + + // If bool return bool as number string + if (isBoolean(value)) { + return Number(value).toString() + } + + // If value is int32 return as number string + if (isNumber(value) && isInt32(value)) { + return value.toString() + } + + // If value is an int32 number string return as number string + if (isString(value) && !isEmpty(value) && !isNaN(Number(value)) && isNumeric(value) && isInt32(Number(value))) { + return Number(value).toString() + } + + if (isNumber(value)) { + value = value.toString() + } + + // If value is null we must use the string value 'None' + if (value === null || value === undefined) { + value = 'None' + } + + return new BigNumber(Hasher.hash(Buffer.from(value as string), 'sha2-256')).toString() +} + +export function assertAttributesMatch(schema: AnonCredsSchema, attributes: CredentialPreviewAttributeOptions[]) { + const schemaAttributes = schema.attrNames + const credAttributes = attributes.map((a) => a.name) + + const difference = credAttributes + .filter((x) => !schemaAttributes.includes(x)) + .concat(schemaAttributes.filter((x) => !credAttributes.includes(x))) + + if (difference.length > 0) { + throw new AriesFrameworkError( + `The credential preview attributes do not match the schema attributes (difference is: ${difference}, needs: ${schemaAttributes})` + ) + } +} + +/** + * Adds attribute(s) to the credential preview that is linked to the given attachment(s) + * + * @param attachments a list of the attachments that need to be linked to a credential + * @param preview the credential previews where the new linked credential has to be appended to + * + * @returns a modified version of the credential preview with the linked credentials + * */ +export function createAndLinkAttachmentsToPreview( + attachments: LinkedAttachment[], + previewAttributes: CredentialPreviewAttributeOptions[] +) { + const credentialPreviewAttributeNames = previewAttributes.map((attribute) => attribute.name) + const newPreviewAttributes = [...previewAttributes] + + attachments.forEach((linkedAttachment) => { + if (credentialPreviewAttributeNames.includes(linkedAttachment.attributeName)) { + throw new AriesFrameworkError(`linkedAttachment ${linkedAttachment.attributeName} already exists in the preview`) + } else { + newPreviewAttributes.push({ + name: linkedAttachment.attributeName, + mimeType: linkedAttachment.attachment.mimeType, + value: encodeAttachment(linkedAttachment.attachment), + }) + } + }) + + return newPreviewAttributes +} diff --git a/packages/anoncreds/src/utils/credentialPreviewAttributes.ts b/packages/anoncreds/src/utils/credentialPreviewAttributes.ts new file mode 100644 index 0000000000..686e07ac80 --- /dev/null +++ b/packages/anoncreds/src/utils/credentialPreviewAttributes.ts @@ -0,0 +1,27 @@ +import type { CredentialPreviewAttributeOptions } from '@aries-framework/core' + +export function areCredentialPreviewAttributesEqual( + firstAttributes: CredentialPreviewAttributeOptions[], + secondAttributes: CredentialPreviewAttributeOptions[] +) { + if (firstAttributes.length !== secondAttributes.length) return false + + const secondAttributeMap = secondAttributes.reduce>( + (attributeMap, attribute) => ({ ...attributeMap, [attribute.name]: attribute }), + {} + ) + + // check if no duplicate keys exist + if (new Set(firstAttributes.map((attribute) => attribute.name)).size !== firstAttributes.length) return false + if (new Set(secondAttributes.map((attribute) => attribute.name)).size !== secondAttributes.length) return false + + for (const firstAttribute of firstAttributes) { + const secondAttribute = secondAttributeMap[firstAttribute.name] + + if (!secondAttribute) return false + if (firstAttribute.value !== secondAttribute.value) return false + if (firstAttribute.mimeType !== secondAttribute.mimeType) return false + } + + return true +} diff --git a/packages/anoncreds/src/utils/getRevocationRegistries.ts b/packages/anoncreds/src/utils/getRevocationRegistries.ts new file mode 100644 index 0000000000..ffc402d2a4 --- /dev/null +++ b/packages/anoncreds/src/utils/getRevocationRegistries.ts @@ -0,0 +1,201 @@ +import type { AnonCredsProof, AnonCredsProofRequest, AnonCredsSelectedCredentials } from '../models' +import type { CreateProofOptions, VerifyProofOptions } from '../services' +import type { AgentContext } from '@aries-framework/core' + +import { AriesFrameworkError } from '@aries-framework/core' + +import { AnonCredsRegistryService } from '../services' + +import { assertBestPracticeRevocationInterval } from './revocationInterval' +import { downloadTailsFile } from './tails' + +export async function getRevocationRegistriesForRequest( + agentContext: AgentContext, + proofRequest: AnonCredsProofRequest, + selectedCredentials: AnonCredsSelectedCredentials +) { + const revocationRegistries: CreateProofOptions['revocationRegistries'] = {} + + // NOTE: we don't want to mutate this object, when modifying we need to always deeply clone objects firsts. + let updatedSelectedCredentials = selectedCredentials + + try { + agentContext.config.logger.debug(`Retrieving revocation registries for proof request`, { + proofRequest, + selectedCredentials, + }) + + const referentCredentials = [] + + // Retrieve information for referents and push to single array + for (const [referent, selectedCredential] of Object.entries(selectedCredentials.attributes)) { + referentCredentials.push({ + type: 'attributes' as const, + referent, + selectedCredential, + nonRevoked: proofRequest.requested_attributes[referent].non_revoked ?? proofRequest.non_revoked, + }) + } + for (const [referent, selectedCredential] of Object.entries(selectedCredentials.predicates)) { + referentCredentials.push({ + type: 'predicates' as const, + referent, + selectedCredential, + nonRevoked: proofRequest.requested_predicates[referent].non_revoked ?? proofRequest.non_revoked, + }) + } + + for (const { referent, selectedCredential, nonRevoked, type } of referentCredentials) { + if (!selectedCredential.credentialInfo) { + throw new AriesFrameworkError( + `Credential for referent '${referent} does not have credential info for revocation state creation` + ) + } + + // Prefer referent-specific revocation interval over global revocation interval + const credentialRevocationId = selectedCredential.credentialInfo.credentialRevocationId + const revocationRegistryId = selectedCredential.credentialInfo.revocationRegistryId + const timestamp = selectedCredential.timestamp + + // If revocation interval is present and the credential is revocable then create revocation state + if (nonRevoked && credentialRevocationId && revocationRegistryId) { + agentContext.config.logger.trace( + `Presentation is requesting proof of non revocation for referent '${referent}', creating revocation state for credential`, + { + nonRevoked, + credentialRevocationId, + revocationRegistryId, + timestamp, + } + ) + + // Make sure the revocation interval follows best practices from Aries RFC 0441 + assertBestPracticeRevocationInterval(nonRevoked) + + const registry = agentContext.dependencyManager + .resolve(AnonCredsRegistryService) + .getRegistryForIdentifier(agentContext, revocationRegistryId) + + // Fetch revocation registry definition if not in revocation registries list yet + if (!revocationRegistries[revocationRegistryId]) { + const { revocationRegistryDefinition, resolutionMetadata } = await registry.getRevocationRegistryDefinition( + agentContext, + revocationRegistryId + ) + if (!revocationRegistryDefinition) { + throw new AriesFrameworkError( + `Could not retrieve revocation registry definition for revocation registry ${revocationRegistryId}: ${resolutionMetadata.message}` + ) + } + + const { tailsLocation, tailsHash } = revocationRegistryDefinition.value + const { tailsFilePath } = await downloadTailsFile(agentContext, tailsLocation, tailsHash) + + // const tails = await this.indyUtilitiesService.downloadTails(tailsHash, tailsLocation) + revocationRegistries[revocationRegistryId] = { + definition: revocationRegistryDefinition, + tailsFilePath, + revocationStatusLists: {}, + } + } + + // In most cases we will have a timestamp, but if it's not defined, we use the nonRevoked.to value + const timestampToFetch = timestamp ?? nonRevoked.to + + // Fetch revocation status list if we don't already have a revocation status list for the given timestamp + if (!revocationRegistries[revocationRegistryId].revocationStatusLists[timestampToFetch]) { + const { revocationStatusList, resolutionMetadata: statusListResolutionMetadata } = + await registry.getRevocationStatusList(agentContext, revocationRegistryId, timestampToFetch) + + if (!revocationStatusList) { + throw new AriesFrameworkError( + `Could not retrieve revocation status list for revocation registry ${revocationRegistryId}: ${statusListResolutionMetadata.message}` + ) + } + + revocationRegistries[revocationRegistryId].revocationStatusLists[revocationStatusList.timestamp] = + revocationStatusList + + // If we don't have a timestamp on the selected credential, we set it to the timestamp of the revocation status list + // this way we know which revocation status list to use when creating the proof. + if (!timestamp) { + updatedSelectedCredentials = { + ...updatedSelectedCredentials, + [type]: { + ...updatedSelectedCredentials[type], + [referent]: { + ...updatedSelectedCredentials[type][referent], + timestamp: revocationStatusList.timestamp, + }, + }, + } + } + } + } + } + + agentContext.config.logger.debug(`Retrieved revocation registries for proof request`, { + revocationRegistries, + }) + + return { revocationRegistries, updatedSelectedCredentials } + } catch (error) { + agentContext.config.logger.error(`Error retrieving revocation registry for proof request`, { + error, + proofRequest, + selectedCredentials, + }) + + throw error + } +} + +export async function getRevocationRegistriesForProof(agentContext: AgentContext, proof: AnonCredsProof) { + const revocationRegistries: VerifyProofOptions['revocationRegistries'] = {} + + for (const identifier of proof.identifiers) { + const revocationRegistryId = identifier.rev_reg_id + const timestamp = identifier.timestamp + + // Skip if no revocation registry id is present + if (!revocationRegistryId || !timestamp) continue + + const registry = agentContext.dependencyManager + .resolve(AnonCredsRegistryService) + .getRegistryForIdentifier(agentContext, revocationRegistryId) + + // Fetch revocation registry definition if not already fetched + if (!revocationRegistries[revocationRegistryId]) { + const { revocationRegistryDefinition, resolutionMetadata } = await registry.getRevocationRegistryDefinition( + agentContext, + revocationRegistryId + ) + if (!revocationRegistryDefinition) { + throw new AriesFrameworkError( + `Could not retrieve revocation registry definition for revocation registry ${revocationRegistryId}: ${resolutionMetadata.message}` + ) + } + + revocationRegistries[revocationRegistryId] = { + definition: revocationRegistryDefinition, + revocationStatusLists: {}, + } + } + + // Fetch revocation status list by timestamp if not already fetched + if (!revocationRegistries[revocationRegistryId].revocationStatusLists[timestamp]) { + const { revocationStatusList, resolutionMetadata: statusListResolutionMetadata } = + await registry.getRevocationStatusList(agentContext, revocationRegistryId, timestamp) + + if (!revocationStatusList) { + throw new AriesFrameworkError( + `Could not retrieve revocation status list for revocation registry ${revocationRegistryId}: ${statusListResolutionMetadata.message}` + ) + } + + revocationRegistries[revocationRegistryId].revocationStatusLists[timestamp] = revocationStatusList + } + } + + return revocationRegistries +} diff --git a/packages/anoncreds/src/utils/hasDuplicateGroupNames.ts b/packages/anoncreds/src/utils/hasDuplicateGroupNames.ts new file mode 100644 index 0000000000..7a16743eb9 --- /dev/null +++ b/packages/anoncreds/src/utils/hasDuplicateGroupNames.ts @@ -0,0 +1,29 @@ +import type { AnonCredsProofRequest } from '../models' + +import { AriesFrameworkError } from '@aries-framework/core' + +function attributeNamesToArray(proofRequest: AnonCredsProofRequest) { + // Attributes can contain either a `name` string value or an `names` string array. We reduce it to a single array + // containing all attribute names from the requested attributes. + return Object.values(proofRequest.requested_attributes).reduce( + (names, a) => [...names, ...(a.name ? [a.name] : a.names ? a.names : [])], + [] + ) +} + +function predicateNamesToArray(proofRequest: AnonCredsProofRequest) { + return Array.from(new Set(Object.values(proofRequest.requested_predicates).map((a) => a.name))) +} + +// TODO: This is still not ideal. The requested groups can specify different credentials using restrictions. +export function assertNoDuplicateGroupsNamesInProofRequest(proofRequest: AnonCredsProofRequest) { + const attributes = attributeNamesToArray(proofRequest) + const predicates = predicateNamesToArray(proofRequest) + + const duplicates = predicates.filter((item) => attributes.indexOf(item) !== -1) + if (duplicates.length > 0) { + throw new AriesFrameworkError( + `The proof request contains duplicate predicates and attributes: ${duplicates.toString()}` + ) + } +} diff --git a/packages/anoncreds/src/utils/index.ts b/packages/anoncreds/src/utils/index.ts new file mode 100644 index 0000000000..7f2d7763fe --- /dev/null +++ b/packages/anoncreds/src/utils/index.ts @@ -0,0 +1,17 @@ +export { createRequestFromPreview } from './createRequestFromPreview' +export { sortRequestedCredentialsMatches } from './sortRequestedCredentialsMatches' +export { assertNoDuplicateGroupsNamesInProofRequest } from './hasDuplicateGroupNames' +export { areAnonCredsProofRequestsEqual } from './areRequestsEqual' +export { downloadTailsFile } from './tails' +export { assertBestPracticeRevocationInterval } from './revocationInterval' +export { getRevocationRegistriesForRequest, getRevocationRegistriesForProof } from './getRevocationRegistries' +export { encodeCredentialValue, checkValidCredentialValueEncoding } from './credential' +export { IsMap } from './isMap' +export { composeCredentialAutoAccept, composeProofAutoAccept } from './composeAutoAccept' +export { areCredentialPreviewAttributesEqual } from './credentialPreviewAttributes' +export { + unqualifiedCredentialDefinitionIdRegex, + unqualifiedIndyDidRegex, + unqualifiedSchemaIdRegex, + unqualifiedSchemaVersionRegex, +} from './indyIdentifiers' diff --git a/packages/anoncreds/src/utils/indyIdentifiers.ts b/packages/anoncreds/src/utils/indyIdentifiers.ts new file mode 100644 index 0000000000..1e20f75c55 --- /dev/null +++ b/packages/anoncreds/src/utils/indyIdentifiers.ts @@ -0,0 +1,196 @@ +import { AriesFrameworkError } from '@aries-framework/core' + +const didIndyAnonCredsBase = + /(did:indy:((?:[a-z][_a-z0-9-]*)(?::[a-z][_a-z0-9-]*)?):([1-9A-HJ-NP-Za-km-z]{21,22}))\/anoncreds\/v0/ + +// :2:: +export const unqualifiedSchemaIdRegex = /^([a-zA-Z0-9]{21,22}):2:(.+):([0-9.]+)$/ +// did:indy::/anoncreds/v0/SCHEMA// +export const didIndySchemaIdRegex = new RegExp(`^${didIndyAnonCredsBase.source}/SCHEMA/(.+)/([0-9.]+)$`) + +export const unqualifiedSchemaVersionRegex = /^(\d+\.)?(\d+\.)?(\*|\d+)$/ +export const unqualifiedIndyDidRegex = /^(did:sov:)?[a-zA-Z0-9]{21,22}$/ + +// :3:CL:: +export const unqualifiedCredentialDefinitionIdRegex = /^([a-zA-Z0-9]{21,22}):3:CL:([1-9][0-9]*):(.+)$/ +// did:indy::/anoncreds/v0/CLAIM_DEF// +export const didIndyCredentialDefinitionIdRegex = new RegExp( + `^${didIndyAnonCredsBase.source}/CLAIM_DEF/([1-9][0-9]*)/(.+)$` +) + +// :4::3:CL::CL_ACCUM: +export const unqualifiedRevocationRegistryIdRegex = + /^([a-zA-Z0-9]{21,22}):4:[a-zA-Z0-9]{21,22}:3:CL:([1-9][0-9]*):(.+):CL_ACCUM:(.+)$/ +// did:indy::/anoncreds/v0/REV_REG_DEF/// +export const didIndyRevocationRegistryIdRegex = new RegExp( + `^${didIndyAnonCredsBase.source}/REV_REG_DEF/([1-9][0-9]*)/(.+)/(.+)$` +) + +export const didIndyRegex = /^did:indy:((?:[a-z][_a-z0-9-]*)(?::[a-z][_a-z0-9-]*)?):([1-9A-HJ-NP-Za-km-z]{21,22})$/ + +export function getUnqualifiedSchemaId(unqualifiedDid: string, name: string, version: string) { + return `${unqualifiedDid}:2:${name}:${version}` +} + +export function getUnqualifiedCredentialDefinitionId(unqualifiedDid: string, seqNo: string | number, tag: string) { + return `${unqualifiedDid}:3:CL:${seqNo}:${tag}` +} + +// TZQuLp43UcYTdtc3HewcDz:4:TZQuLp43UcYTdtc3HewcDz:3:CL:98158:BaustellenzertifikateNU1:CL_ACCUM:1-100 +export function getUnqualifiedRevocationRegistryId( + unqualifiedDid: string, + seqNo: string | number, + credentialDefinitionTag: string, + revocationRegistryTag: string +) { + return `${unqualifiedDid}:4:${unqualifiedDid}:3:CL:${seqNo}:${credentialDefinitionTag}:CL_ACCUM:${revocationRegistryTag}` +} + +export function isUnqualifiedCredentialDefinitionId(credentialDefinitionId: string) { + return unqualifiedCredentialDefinitionIdRegex.test(credentialDefinitionId) +} + +export function isUnqualifiedRevocationRegistryId(revocationRegistryId: string) { + return unqualifiedRevocationRegistryIdRegex.test(revocationRegistryId) +} + +export function isUnqualifiedSchemaId(schemaId: string) { + return unqualifiedSchemaIdRegex.test(schemaId) +} + +export function isDidIndySchemaId(schemaId: string) { + return didIndySchemaIdRegex.test(schemaId) +} + +export function isDidIndyCredentialDefinitionId(credentialDefinitionId: string) { + return didIndyCredentialDefinitionIdRegex.test(credentialDefinitionId) +} + +export function isDidIndyRevocationRegistryId(revocationRegistryId: string) { + return didIndyRevocationRegistryIdRegex.test(revocationRegistryId) +} + +export function parseIndyDid(did: string) { + const match = did.match(didIndyRegex) + if (match) { + const [, namespace, namespaceIdentifier] = match + return { namespace, namespaceIdentifier } + } else { + throw new AriesFrameworkError(`${did} is not a valid did:indy did`) + } +} + +interface ParsedIndySchemaId { + did: string + namespaceIdentifier: string + schemaName: string + schemaVersion: string + namespace?: string +} + +export function parseIndySchemaId(schemaId: string): ParsedIndySchemaId { + const didIndyMatch = schemaId.match(didIndySchemaIdRegex) + if (didIndyMatch) { + const [, did, namespace, namespaceIdentifier, schemaName, schemaVersion] = didIndyMatch + + return { + did, + namespaceIdentifier, + schemaName, + schemaVersion, + namespace, + } + } + + const legacyMatch = schemaId.match(unqualifiedSchemaIdRegex) + if (legacyMatch) { + const [, did, schemaName, schemaVersion] = legacyMatch + + return { + did, + namespaceIdentifier: did, + schemaName, + schemaVersion, + } + } + + throw new Error(`Invalid schema id: ${schemaId}`) +} + +interface ParsedIndyCredentialDefinitionId { + did: string + namespaceIdentifier: string + schemaSeqNo: string + tag: string + namespace?: string +} + +export function parseIndyCredentialDefinitionId(credentialDefinitionId: string): ParsedIndyCredentialDefinitionId { + const didIndyMatch = credentialDefinitionId.match(didIndyCredentialDefinitionIdRegex) + if (didIndyMatch) { + const [, did, namespace, namespaceIdentifier, schemaSeqNo, tag] = didIndyMatch + + return { + did, + namespaceIdentifier, + schemaSeqNo, + tag, + namespace, + } + } + + const legacyMatch = credentialDefinitionId.match(unqualifiedCredentialDefinitionIdRegex) + if (legacyMatch) { + const [, did, schemaSeqNo, tag] = legacyMatch + + return { + did, + namespaceIdentifier: did, + schemaSeqNo, + tag, + } + } + + throw new Error(`Invalid credential definition id: ${credentialDefinitionId}`) +} + +interface ParsedIndyRevocationRegistryId { + did: string + namespaceIdentifier: string + schemaSeqNo: string + credentialDefinitionTag: string + revocationRegistryTag: string + namespace?: string +} + +export function parseIndyRevocationRegistryId(revocationRegistryId: string): ParsedIndyRevocationRegistryId { + const didIndyMatch = revocationRegistryId.match(didIndyRevocationRegistryIdRegex) + if (didIndyMatch) { + const [, did, namespace, namespaceIdentifier, schemaSeqNo, credentialDefinitionTag, revocationRegistryTag] = + didIndyMatch + + return { + did, + namespaceIdentifier, + schemaSeqNo, + credentialDefinitionTag, + revocationRegistryTag, + namespace, + } + } + + const legacyMatch = revocationRegistryId.match(unqualifiedRevocationRegistryIdRegex) + if (legacyMatch) { + const [, did, schemaSeqNo, credentialDefinitionTag, revocationRegistryTag] = legacyMatch + + return { + did, + namespaceIdentifier: did, + schemaSeqNo, + credentialDefinitionTag, + revocationRegistryTag, + } + } + + throw new Error(`Invalid revocation registry id: ${revocationRegistryId}`) +} diff --git a/packages/anoncreds/src/utils/isMap.ts b/packages/anoncreds/src/utils/isMap.ts new file mode 100644 index 0000000000..1ee81fe4a4 --- /dev/null +++ b/packages/anoncreds/src/utils/isMap.ts @@ -0,0 +1,19 @@ +import type { ValidationOptions } from 'class-validator' + +import { ValidateBy, buildMessage } from 'class-validator' + +/** + * Checks if a given value is a Map + */ +export function IsMap(validationOptions?: ValidationOptions): PropertyDecorator { + return ValidateBy( + { + name: 'isMap', + validator: { + validate: (value: unknown): boolean => value instanceof Map, + defaultMessage: buildMessage((eachPrefix) => eachPrefix + '$property must be a Map', validationOptions), + }, + }, + validationOptions + ) +} diff --git a/packages/anoncreds/src/utils/metadata.ts b/packages/anoncreds/src/utils/metadata.ts new file mode 100644 index 0000000000..c8c1c245fc --- /dev/null +++ b/packages/anoncreds/src/utils/metadata.ts @@ -0,0 +1,29 @@ +// TODO: we may want to already support multiple credentials in the metadata of a credential +// record, as that's what the RFCs support. We already need to write a migration script for modules + +/** + * Metadata key for strong metadata on an AnonCreds credential. + * + * MUST be used with {@link AnonCredsCredentialMetadata} + */ +export const AnonCredsCredentialMetadataKey = '_anoncreds/credential' + +/** + * Metadata key for strong metadata on an AnonCreds credential request. + * + * MUST be used with {@link AnonCredsCredentialRequestMetadata} + */ +export const AnonCredsCredentialRequestMetadataKey = '_anoncreds/credentialRequest' + +/** + * Metadata for an AnonCreds credential that will be stored + * in the credential record. + * + * MUST be used with {@link AnonCredsCredentialMetadataKey} + */ +export interface AnonCredsCredentialMetadata { + schemaId?: string + credentialDefinitionId?: string + revocationRegistryId?: string + credentialRevocationId?: string +} diff --git a/packages/anoncreds/src/utils/proverDid.ts b/packages/anoncreds/src/utils/proverDid.ts new file mode 100644 index 0000000000..2d12648c70 --- /dev/null +++ b/packages/anoncreds/src/utils/proverDid.ts @@ -0,0 +1,12 @@ +import { TypedArrayEncoder, utils } from '@aries-framework/core' + +/** + * generates a string that adheres to the format of a legacy indy did. + * + * This can be used for the `prover_did` property that is required in the legacy anoncreds credential + * request. This doesn't actually have to be a did, but some frameworks (like ACA-Py) require it to be + * an unqualified indy did. + */ +export function generateLegacyProverDidLikeString() { + return TypedArrayEncoder.toBase58(TypedArrayEncoder.fromString(utils.uuid()).slice(0, 16)) +} diff --git a/packages/anoncreds/src/utils/revocationInterval.ts b/packages/anoncreds/src/utils/revocationInterval.ts new file mode 100644 index 0000000000..59f490f569 --- /dev/null +++ b/packages/anoncreds/src/utils/revocationInterval.ts @@ -0,0 +1,25 @@ +import type { AnonCredsNonRevokedInterval } from '../models' + +import { AriesFrameworkError } from '@aries-framework/core' + +// This sets the `to` value to be required. We do this check in the `assertBestPracticeRevocationInterval` method, +// and it makes it easier to work with the object in TS +interface BestPracticeNonRevokedInterval { + from?: number + to: number +} + +// Check revocation interval in accordance with https://github.com/hyperledger/aries-rfcs/blob/main/concepts/0441-present-proof-best-practices/README.md#semantics-of-non-revocation-interval-endpoints +export function assertBestPracticeRevocationInterval( + revocationInterval: AnonCredsNonRevokedInterval +): asserts revocationInterval is BestPracticeNonRevokedInterval { + if (!revocationInterval.to) { + throw new AriesFrameworkError(`Presentation requests proof of non-revocation with no 'to' value specified`) + } + + if ((revocationInterval.from || revocationInterval.from === 0) && revocationInterval.to !== revocationInterval.from) { + throw new AriesFrameworkError( + `Presentation requests proof of non-revocation with an interval from: '${revocationInterval.from}' that does not match the interval to: '${revocationInterval.to}', as specified in Aries RFC 0441` + ) + } +} diff --git a/packages/anoncreds/src/utils/sortRequestedCredentialsMatches.ts b/packages/anoncreds/src/utils/sortRequestedCredentialsMatches.ts new file mode 100644 index 0000000000..1d190c7e31 --- /dev/null +++ b/packages/anoncreds/src/utils/sortRequestedCredentialsMatches.ts @@ -0,0 +1,33 @@ +import type { AnonCredsRequestedAttributeMatch, AnonCredsRequestedPredicateMatch } from '../models' + +/** + * Sort requested attributes and predicates by `revoked` status. The order is: + * - first credentials with `revoked` set to undefined, this means no revocation status is needed for the credentials + * - then credentials with `revoked` set to false, this means the credentials are not revoked + * - then credentials with `revoked` set to true, this means the credentials are revoked + */ +export function sortRequestedCredentialsMatches< + Requested extends Array | Array +>(credentials: Requested) { + const staySame = 0 + const credentialGoUp = -1 + const credentialGoDown = 1 + + // Clone as sort is in place + const credentialsClone = [...credentials] + + return credentialsClone.sort((credential, compareTo) => { + // Nothing needs to happen if values are the same + if (credential.revoked === compareTo.revoked) return staySame + + // Undefined always is at the top + if (credential.revoked === undefined) return credentialGoUp + if (compareTo.revoked === undefined) return credentialGoDown + + // Then revoked + if (credential.revoked === false) return credentialGoUp + + // It means that compareTo is false and credential is true + return credentialGoDown + }) +} diff --git a/packages/anoncreds/src/utils/tails.ts b/packages/anoncreds/src/utils/tails.ts new file mode 100644 index 0000000000..e706f00914 --- /dev/null +++ b/packages/anoncreds/src/utils/tails.ts @@ -0,0 +1,57 @@ +import type { AgentContext, FileSystem } from '@aries-framework/core' + +import { TypedArrayEncoder, InjectionSymbols } from '@aries-framework/core' + +const getTailsFilePath = (cachePath: string, tailsHash: string) => `${cachePath}/anoncreds/tails/${tailsHash}` + +export function tailsFileExists(agentContext: AgentContext, tailsHash: string): Promise { + const fileSystem = agentContext.dependencyManager.resolve(InjectionSymbols.FileSystem) + const tailsFilePath = getTailsFilePath(fileSystem.cachePath, tailsHash) + + return fileSystem.exists(tailsFilePath) +} + +export async function downloadTailsFile( + agentContext: AgentContext, + tailsLocation: string, + tailsHashBase58: string +): Promise<{ + tailsFilePath: string +}> { + const fileSystem = agentContext.dependencyManager.resolve(InjectionSymbols.FileSystem) + + try { + agentContext.config.logger.debug( + `Checking to see if tails file for URL ${tailsLocation} has been stored in the FileSystem` + ) + + // hash is used as file identifier + const tailsExists = await tailsFileExists(agentContext, tailsHashBase58) + const tailsFilePath = getTailsFilePath(fileSystem.cachePath, tailsHashBase58) + agentContext.config.logger.debug( + `Tails file for ${tailsLocation} ${tailsExists ? 'is stored' : 'is not stored'} at ${tailsFilePath}` + ) + + if (!tailsExists) { + agentContext.config.logger.debug(`Retrieving tails file from URL ${tailsLocation}`) + + // download file and verify hash + await fileSystem.downloadToFile(tailsLocation, tailsFilePath, { + verifyHash: { + algorithm: 'sha256', + hash: TypedArrayEncoder.fromBase58(tailsHashBase58), + }, + }) + agentContext.config.logger.debug(`Saved tails file to FileSystem at path ${tailsFilePath}`) + } + + return { + tailsFilePath, + } + } catch (error) { + agentContext.config.logger.error(`Error while retrieving tails file from URL ${tailsLocation}`, { + error, + }) + throw error + } +} diff --git a/packages/anoncreds/tests/InMemoryAnonCredsRegistry.ts b/packages/anoncreds/tests/InMemoryAnonCredsRegistry.ts new file mode 100644 index 0000000000..9cb3a9adf3 --- /dev/null +++ b/packages/anoncreds/tests/InMemoryAnonCredsRegistry.ts @@ -0,0 +1,258 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +import type { + AnonCredsRegistry, + GetSchemaReturn, + RegisterSchemaOptions, + RegisterSchemaReturn, + GetCredentialDefinitionReturn, + RegisterCredentialDefinitionOptions, + RegisterCredentialDefinitionReturn, + GetRevocationRegistryDefinitionReturn, + GetRevocationStatusListReturn, + AnonCredsRevocationStatusList, + AnonCredsRevocationRegistryDefinition, + AnonCredsSchema, + AnonCredsCredentialDefinition, +} from '../src' +import type { AgentContext } from '@aries-framework/core' + +import { Hasher, TypedArrayEncoder } from '@aries-framework/core' +import BigNumber from 'bn.js' + +import { getDidIndyCredentialDefinitionId, getDidIndySchemaId } from '../../indy-sdk/src/anoncreds/utils/identifiers' +import { getUnqualifiedCredentialDefinitionId, getUnqualifiedSchemaId, parseIndyDid, parseIndySchemaId } from '../src' + +/** + * In memory implementation of the {@link AnonCredsRegistry} interface. Useful for testing. + */ +export class InMemoryAnonCredsRegistry implements AnonCredsRegistry { + public readonly methodName = 'inMemory' + + // Roughly match that the identifier starts with an unqualified indy did. Once the + // anoncreds tests are not based on the indy-sdk anymore, we can use any identifier + // we want, but the indy-sdk is picky about the identifier format. + public readonly supportedIdentifier = /.+/ + + private schemas: Record + private credentialDefinitions: Record + private revocationRegistryDefinitions: Record + private revocationStatusLists: Record> + + public constructor({ + existingSchemas = {}, + existingCredentialDefinitions = {}, + existingRevocationRegistryDefinitions = {}, + existingRevocationStatusLists = {}, + }: { + existingSchemas?: Record + existingCredentialDefinitions?: Record + existingRevocationRegistryDefinitions?: Record + existingRevocationStatusLists?: Record> + } = {}) { + this.schemas = existingSchemas + this.credentialDefinitions = existingCredentialDefinitions + this.revocationRegistryDefinitions = existingRevocationRegistryDefinitions + this.revocationStatusLists = existingRevocationStatusLists + } + + public async getSchema(agentContext: AgentContext, schemaId: string): Promise { + const schema = this.schemas[schemaId] + + const parsed = parseIndySchemaId(schemaId) + + const legacySchemaId = getUnqualifiedSchemaId(parsed.namespaceIdentifier, parsed.schemaName, parsed.schemaVersion) + const indyLedgerSeqNo = getSeqNoFromSchemaId(legacySchemaId) + + if (!schema) { + return { + resolutionMetadata: { + error: 'notFound', + message: `Schema with id ${schemaId} not found in memory registry`, + }, + schemaId, + schemaMetadata: {}, + } + } + + return { + resolutionMetadata: {}, + schema, + schemaId, + schemaMetadata: { + // NOTE: the seqNo is required by the indy-sdk even though not present in AnonCreds v1. + // For this reason we return it in the metadata. + indyLedgerSeqNo, + }, + } + } + + public async registerSchema( + agentContext: AgentContext, + options: RegisterSchemaOptions + ): Promise { + const { namespace, namespaceIdentifier } = parseIndyDid(options.schema.issuerId) + const legacyIssuerId = namespaceIdentifier + const didIndySchemaId = getDidIndySchemaId( + namespace, + namespaceIdentifier, + options.schema.name, + options.schema.version + ) + this.schemas[didIndySchemaId] = options.schema + + const legacySchemaId = getUnqualifiedSchemaId(legacyIssuerId, options.schema.name, options.schema.version) + const indyLedgerSeqNo = getSeqNoFromSchemaId(legacySchemaId) + + this.schemas[legacySchemaId] = { + ...options.schema, + issuerId: legacyIssuerId, + } + + return { + registrationMetadata: {}, + schemaMetadata: { + // NOTE: the seqNo is required by the indy-sdk even though not present in AnonCreds v1. + // For this reason we return it in the metadata. + indyLedgerSeqNo, + }, + schemaState: { + state: 'finished', + schema: options.schema, + schemaId: didIndySchemaId, + }, + } + } + + public async getCredentialDefinition( + agentContext: AgentContext, + credentialDefinitionId: string + ): Promise { + const credentialDefinition = this.credentialDefinitions[credentialDefinitionId] + + if (!credentialDefinition) { + return { + resolutionMetadata: { + error: 'notFound', + message: `Credential definition with id ${credentialDefinitionId} not found in memory registry`, + }, + credentialDefinitionId, + credentialDefinitionMetadata: {}, + } + } + + return { + resolutionMetadata: {}, + credentialDefinition, + credentialDefinitionId, + credentialDefinitionMetadata: {}, + } + } + + public async registerCredentialDefinition( + agentContext: AgentContext, + options: RegisterCredentialDefinitionOptions + ): Promise { + const parsedSchema = parseIndySchemaId(options.credentialDefinition.schemaId) + const legacySchemaId = getUnqualifiedSchemaId( + parsedSchema.namespaceIdentifier, + parsedSchema.schemaName, + parsedSchema.schemaVersion + ) + const indyLedgerSeqNo = getSeqNoFromSchemaId(legacySchemaId) + + const { namespace, namespaceIdentifier } = parseIndyDid(options.credentialDefinition.issuerId) + const legacyIssuerId = namespaceIdentifier + const didIndyCredentialDefinitionId = getDidIndyCredentialDefinitionId( + namespace, + namespaceIdentifier, + indyLedgerSeqNo, + options.credentialDefinition.tag + ) + + this.credentialDefinitions[didIndyCredentialDefinitionId] = options.credentialDefinition + + const legacyCredentialDefinitionId = getUnqualifiedCredentialDefinitionId( + legacyIssuerId, + indyLedgerSeqNo, + options.credentialDefinition.tag + ) + + this.credentialDefinitions[legacyCredentialDefinitionId] = { + ...options.credentialDefinition, + issuerId: legacyIssuerId, + schemaId: legacySchemaId, + } + + return { + registrationMetadata: {}, + credentialDefinitionMetadata: {}, + credentialDefinitionState: { + state: 'finished', + credentialDefinition: options.credentialDefinition, + credentialDefinitionId: didIndyCredentialDefinitionId, + }, + } + } + + public async getRevocationRegistryDefinition( + agentContext: AgentContext, + revocationRegistryDefinitionId: string + ): Promise { + const revocationRegistryDefinition = this.revocationRegistryDefinitions[revocationRegistryDefinitionId] + + if (!revocationRegistryDefinition) { + return { + resolutionMetadata: { + error: 'notFound', + message: `Revocation registry definition with id ${revocationRegistryDefinition} not found in memory registry`, + }, + revocationRegistryDefinitionId, + revocationRegistryDefinitionMetadata: {}, + } + } + + return { + resolutionMetadata: {}, + revocationRegistryDefinition, + revocationRegistryDefinitionId, + revocationRegistryDefinitionMetadata: {}, + } + } + + public async getRevocationStatusList( + agentContext: AgentContext, + revocationRegistryId: string, + timestamp: number + ): Promise { + const revocationStatusLists = this.revocationStatusLists[revocationRegistryId] + + if (!revocationStatusLists || !revocationStatusLists[timestamp]) { + return { + resolutionMetadata: { + error: 'notFound', + message: `Revocation status list for revocation registry with id ${revocationRegistryId} not found in memory registry`, + }, + revocationStatusListMetadata: {}, + } + } + + return { + resolutionMetadata: {}, + revocationStatusList: revocationStatusLists[timestamp], + revocationStatusListMetadata: {}, + } + } +} + +/** + * Calculates a consistent sequence number for a given schema id. + * + * Does this by hashing the schema id, transforming the hash to a number and taking the first 6 digits. + */ +function getSeqNoFromSchemaId(schemaId: string) { + const seqNo = Number( + new BigNumber(Hasher.hash(TypedArrayEncoder.fromString(schemaId), 'sha2-256')).toString().slice(0, 5) + ) + + return seqNo +} diff --git a/packages/anoncreds/tests/anoncreds.test.ts b/packages/anoncreds/tests/anoncreds.test.ts new file mode 100644 index 0000000000..d5590ca4ba --- /dev/null +++ b/packages/anoncreds/tests/anoncreds.test.ts @@ -0,0 +1,315 @@ +import { Agent, KeyDerivationMethod, KeyType, TypedArrayEncoder } from '@aries-framework/core' +import { agentDependencies } from '@aries-framework/node' +import * as indySdk from 'indy-sdk' + +import { IndySdkModule } from '../../indy-sdk/src/IndySdkModule' +import { AnonCredsModule } from '../src' + +import { InMemoryAnonCredsRegistry } from './InMemoryAnonCredsRegistry' + +const existingSchemas = { + '7Cd2Yj9yEZNcmNoH54tq9i:2:Test Schema:1.0.0': { + attrNames: ['one', 'two'], + issuerId: '7Cd2Yj9yEZNcmNoH54tq9i', + name: 'Test Schema', + version: '1.0.0', + }, +} + +const existingCredentialDefinitions = { + 'VsKV7grR1BUE29mG2Fm2kX:3:CL:75206:TAG': { + issuerId: 'VsKV7grR1BUE29mG2Fm2kX', + tag: 'TAG', + schemaId: '7Cd2Yj9yEZNcmNoH54tq9i:2:Test Schema:1.0.0', + type: 'CL', + value: { + primary: { + n: '92511867718854414868106363741369833735017762038454769060600859608405811709675033445666654908195955460485998711087020152978597220168927505650092431295783175164390266561239892662085428655566792056852960599485298025843840058914610127716620252006466964070280255168745873592143068949458568751438337748294055976926080232538440619420568859737673474560851456027625679328271511966332808025880807996449998057729417608399774744254122385012832309402226532031122728445959276178939234308090390331654445053482963947804769291501664200141562885660084823885847247231002821472258218384342423605116504024514572826071246440130942849549441', + s: '80388543865249952799447792504739237616187770512259677275061283897050980768551818104137338144380636412773836688624071360386172349725818126495487584981520630638409717065318132420766896092370913800616033623618952639023946750307405126873476182540669638841562357523429245685476919178722373320218824590869735129801004394337640642997250464303104754942997839179333543643110326022824394934965538190976474473353762308333205671176627192797138375084260446324344637548455228161138089974447059481109651156379803576163576511072261388342837813901850712083922506433336723723235701670225584863772222447543742649328218950436824219992164', + r: { + one: '676933340341980399002624386891134393471002096508227567343731826159610079436978196421307099268754545293545727546242372579987825752872485684085629459107300175443328323289748793060894500514926703654606851666031895448970879827423190730510730624784665299646624113512701254199984520803796529034094958026048762178753193812250643294518237843809104055653333871102658177900702978008644780459400512716361564897282969982554031820285585105004870317861287847206222714589633178648982299799311192432563797220854755882933052881306804544233529886513105815543097685128456041780804442879272476590077760678785460726492895806240870944398', + master_secret: + '57770757113548032970308439965749734133430520933173186296299026579579930337912607419798836831937319372744879560676750427054135869214212225572618340088847222727882935159356459822445182287686057012197046378986248048722180093079919306125315662058290895629438767985427829790980355162853804522854494960613869765167538645624719923127052541372069255024631093663068055100579264049925388231368871107383977060590248865498902704546409806115171120555709438784189721957301548212242748685629860268468247494986146122636455769804467583612610341632602695197189514316033637331733820369170763954604394734655429769801516997967996980978751', + two: '60366631925664005237432731340682977203246802182440530784833565276111958129922833461368205267143124766208499918438803966972947830682551774196763124331578934778868938718942789067536194229546670608604626738087066151521062180022991840618459591148096543440942293686250499935227881144460486543061212259250663566176469333982946568767707989969471450673037590849807300874360022327312564559087769485266016496010132793446151658150957771177955095876947792797176338483943233433284791481746843006255371654617950568875773118157773566188096075078351362095061968279597354733768049622048871890495958175847017320945873812850638157518451', + }, + rctxt: + '19574881057684356733946284215946569464410211018678168661028327420122678446653210056362495902735819742274128834330867933095119512313591151219353395069123546495720010325822330866859140765940839241212947354612836044244554152389691282543839111284006009168728161183863936810142428875817934316327118674532328892591410224676539770085459540786747902789677759379901079898127879301595929571621032704093287675668250862222728331030586585586110859977896767318814398026750215625180255041545607499673023585546720788973882263863911222208020438685873501025545464213035270207099419236974668665979962146355749687924650853489277747454993', + z: '18569464356833363098514177097771727133940629758890641648661259687745137028161881113251218061243607037717553708179509640909238773964066423807945164288256211132195919975343578956381001087353353060599758005375631247614777454313440511375923345538396573548499287265163879524050255226779884271432737062283353279122281220812931572456820130441114446870167673796490210349453498315913599982158253821945225264065364670730546176140788405935081171854642125236557475395879246419105888077042924382595999612137336915304205628167917473420377397118829734604949103124514367857266518654728464539418834291071874052392799652266418817991437', + }, + }, + }, +} as const + +const existingRevocationRegistryDefinitions = { + 'VsKV7grR1BUE29mG2Fm2kX:4:VsKV7grR1BUE29mG2Fm2kX:3:CL:75206:TAG:CL_ACCUM:TAG': { + credDefId: 'VsKV7grR1BUE29mG2Fm2kX:3:CL:75206:TAG', + issuerId: 'VsKV7grR1BUE29mG2Fm2kX', + revocDefType: 'CL_ACCUM', + value: { + publicKeys: { + accumKey: { + z: 'ab81257c-be63-4051-9e21-c7d384412f64', + }, + }, + maxCredNum: 100, + tailsHash: 'ab81257c-be63-4051-9e21-c7d384412f64', + tailsLocation: 'http://localhost:7200/tails', + }, + tag: 'TAG', + }, +} as const + +const existingRevocationStatusLists = { + 'VsKV7grR1BUE29mG2Fm2kX:4:VsKV7grR1BUE29mG2Fm2kX:3:CL:75206:TAG:CL_ACCUM:TAG': { + 10123: { + currentAccumulator: 'ab81257c-be63-4051-9e21-c7d384412f64', + issuerId: 'VsKV7grR1BUE29mG2Fm2kX', + revocationList: [1, 0, 1], + revRegDefId: 'VsKV7grR1BUE29mG2Fm2kX:4:VsKV7grR1BUE29mG2Fm2kX:3:CL:75206:TAG:CL_ACCUM:TAG', + timestamp: 10123, + }, + }, +} + +const agent = new Agent({ + config: { + label: '@aries-framework/anoncreds', + walletConfig: { + id: '@aries-framework/anoncreds', + key: 'CwNJroKHTSSj3XvE7ZAnuKiTn2C4QkFvxEqfm5rzhNrb', + keyDerivationMethod: KeyDerivationMethod.Raw, + }, + }, + modules: { + indySdk: new IndySdkModule({ + indySdk, + }), + anoncreds: new AnonCredsModule({ + registries: [ + new InMemoryAnonCredsRegistry({ + existingSchemas, + existingCredentialDefinitions, + existingRevocationRegistryDefinitions, + existingRevocationStatusLists, + }), + ], + }), + }, + dependencies: agentDependencies, +}) + +describe('AnonCreds API', () => { + beforeEach(async () => { + await agent.initialize() + }) + + afterEach(async () => { + await agent.wallet.delete() + await agent.shutdown() + }) + + test('create and get link secret', async () => { + await agent.modules.anoncreds.createLinkSecret({ + linkSecretId: 'anoncreds-link-secret', + }) + + const linkSecretIds = await agent.modules.anoncreds.getLinkSecretIds() + + expect(linkSecretIds).toEqual(['anoncreds-link-secret']) + }) + + test('register a schema', async () => { + const schemaResult = await agent.modules.anoncreds.registerSchema({ + options: {}, + schema: { + attrNames: ['name', 'age'], + issuerId: 'did:indy:pool:localtest:6xDN7v3AiGgusRp4bqZACZ', + name: 'Employee Credential', + version: '1.0.0', + }, + }) + + expect(schemaResult).toEqual({ + registrationMetadata: {}, + schemaMetadata: { indyLedgerSeqNo: 16908 }, + schemaState: { + state: 'finished', + schema: { + attrNames: ['name', 'age'], + issuerId: 'did:indy:pool:localtest:6xDN7v3AiGgusRp4bqZACZ', + name: 'Employee Credential', + version: '1.0.0', + }, + schemaId: 'did:indy:pool:localtest:6xDN7v3AiGgusRp4bqZACZ/anoncreds/v0/SCHEMA/Employee Credential/1.0.0', + }, + }) + + // Check if record was created + const [schemaRecord] = await agent.modules.anoncreds.getCreatedSchemas({ + schemaId: 'did:indy:pool:localtest:6xDN7v3AiGgusRp4bqZACZ/anoncreds/v0/SCHEMA/Employee Credential/1.0.0', + }) + expect(schemaRecord).toMatchObject({ + schemaId: 'did:indy:pool:localtest:6xDN7v3AiGgusRp4bqZACZ/anoncreds/v0/SCHEMA/Employee Credential/1.0.0', + methodName: 'inMemory', + schema: { + attrNames: ['name', 'age'], + issuerId: 'did:indy:pool:localtest:6xDN7v3AiGgusRp4bqZACZ', + name: 'Employee Credential', + version: '1.0.0', + }, + }) + + expect(schemaRecord.getTags()).toEqual({ + schemaId: 'did:indy:pool:localtest:6xDN7v3AiGgusRp4bqZACZ/anoncreds/v0/SCHEMA/Employee Credential/1.0.0', + issuerId: 'did:indy:pool:localtest:6xDN7v3AiGgusRp4bqZACZ', + schemaName: 'Employee Credential', + schemaVersion: '1.0.0', + methodName: 'inMemory', + unqualifiedSchemaId: '6xDN7v3AiGgusRp4bqZACZ:2:Employee Credential:1.0.0', + }) + }) + + test('resolve a schema', async () => { + const schemaResult = await agent.modules.anoncreds.getSchema('7Cd2Yj9yEZNcmNoH54tq9i:2:Test Schema:1.0.0') + + expect(schemaResult).toEqual({ + resolutionMetadata: {}, + schemaMetadata: { indyLedgerSeqNo: 75206 }, + schema: { + attrNames: ['one', 'two'], + issuerId: '7Cd2Yj9yEZNcmNoH54tq9i', + name: 'Test Schema', + version: '1.0.0', + }, + schemaId: '7Cd2Yj9yEZNcmNoH54tq9i:2:Test Schema:1.0.0', + }) + }) + + test('register a credential definition', async () => { + // Create key + await agent.wallet.createKey({ + privateKey: TypedArrayEncoder.fromString('00000000000000000000000000000My1'), + keyType: KeyType.Ed25519, + }) + + const issuerId = 'did:indy:pool:localhost:VsKV7grR1BUE29mG2Fm2kX' + + const credentialDefinitionResult = await agent.modules.anoncreds.registerCredentialDefinition({ + credentialDefinition: { + issuerId, + schemaId: '7Cd2Yj9yEZNcmNoH54tq9i:2:Test Schema:1.0.0', + tag: 'TAG', + }, + options: {}, + }) + + expect(credentialDefinitionResult).toEqual({ + registrationMetadata: {}, + credentialDefinitionMetadata: {}, + credentialDefinitionState: { + state: 'finished', + credentialDefinition: { + issuerId: 'did:indy:pool:localhost:VsKV7grR1BUE29mG2Fm2kX', + tag: 'TAG', + schemaId: '7Cd2Yj9yEZNcmNoH54tq9i:2:Test Schema:1.0.0', + type: 'CL', + value: { + primary: { + n: expect.any(String), + s: expect.any(String), + r: { + one: expect.any(String), + master_secret: expect.any(String), + two: expect.any(String), + }, + rctxt: expect.any(String), + z: expect.any(String), + }, + }, + }, + credentialDefinitionId: 'did:indy:pool:localhost:VsKV7grR1BUE29mG2Fm2kX/anoncreds/v0/CLAIM_DEF/75206/TAG', + }, + }) + + const [credentialDefinitionRecord] = await agent.modules.anoncreds.getCreatedCredentialDefinitions({ + credentialDefinitionId: 'did:indy:pool:localhost:VsKV7grR1BUE29mG2Fm2kX/anoncreds/v0/CLAIM_DEF/75206/TAG', + }) + expect(credentialDefinitionRecord).toMatchObject({ + credentialDefinitionId: 'did:indy:pool:localhost:VsKV7grR1BUE29mG2Fm2kX/anoncreds/v0/CLAIM_DEF/75206/TAG', + methodName: 'inMemory', + credentialDefinition: { + issuerId: 'did:indy:pool:localhost:VsKV7grR1BUE29mG2Fm2kX', + tag: 'TAG', + schemaId: '7Cd2Yj9yEZNcmNoH54tq9i:2:Test Schema:1.0.0', + type: 'CL', + value: { + primary: { + n: expect.any(String), + s: expect.any(String), + r: { + one: expect.any(String), + master_secret: expect.any(String), + two: expect.any(String), + }, + rctxt: expect.any(String), + z: expect.any(String), + }, + }, + }, + }) + + expect(credentialDefinitionRecord.getTags()).toEqual({ + methodName: 'inMemory', + credentialDefinitionId: 'did:indy:pool:localhost:VsKV7grR1BUE29mG2Fm2kX/anoncreds/v0/CLAIM_DEF/75206/TAG', + schemaId: '7Cd2Yj9yEZNcmNoH54tq9i:2:Test Schema:1.0.0', + issuerId: 'did:indy:pool:localhost:VsKV7grR1BUE29mG2Fm2kX', + tag: 'TAG', + unqualifiedCredentialDefinitionId: 'VsKV7grR1BUE29mG2Fm2kX:3:CL:75206:TAG', + }) + }) + + test('resolve a credential definition', async () => { + const credentialDefinitionResult = await agent.modules.anoncreds.getCredentialDefinition( + 'VsKV7grR1BUE29mG2Fm2kX:3:CL:75206:TAG' + ) + + expect(credentialDefinitionResult).toEqual({ + resolutionMetadata: {}, + credentialDefinitionMetadata: {}, + credentialDefinition: existingCredentialDefinitions['VsKV7grR1BUE29mG2Fm2kX:3:CL:75206:TAG'], + credentialDefinitionId: 'VsKV7grR1BUE29mG2Fm2kX:3:CL:75206:TAG', + }) + }) + + test('resolve a revocation regsitry definition', async () => { + const revocationRegistryDefinition = await agent.modules.anoncreds.getRevocationRegistryDefinition( + 'VsKV7grR1BUE29mG2Fm2kX:4:VsKV7grR1BUE29mG2Fm2kX:3:CL:75206:TAG:CL_ACCUM:TAG' + ) + + expect(revocationRegistryDefinition).toEqual({ + revocationRegistryDefinitionId: 'VsKV7grR1BUE29mG2Fm2kX:4:VsKV7grR1BUE29mG2Fm2kX:3:CL:75206:TAG:CL_ACCUM:TAG', + revocationRegistryDefinition: + existingRevocationRegistryDefinitions[ + 'VsKV7grR1BUE29mG2Fm2kX:4:VsKV7grR1BUE29mG2Fm2kX:3:CL:75206:TAG:CL_ACCUM:TAG' + ], + resolutionMetadata: {}, + revocationRegistryDefinitionMetadata: {}, + }) + }) + + test('resolve a revocation status list', async () => { + const revocationStatusList = await agent.modules.anoncreds.getRevocationStatusList( + 'VsKV7grR1BUE29mG2Fm2kX:4:VsKV7grR1BUE29mG2Fm2kX:3:CL:75206:TAG:CL_ACCUM:TAG', + 10123 + ) + + expect(revocationStatusList).toEqual({ + revocationStatusList: + existingRevocationStatusLists[ + 'VsKV7grR1BUE29mG2Fm2kX:4:VsKV7grR1BUE29mG2Fm2kX:3:CL:75206:TAG:CL_ACCUM:TAG' + ][10123], + resolutionMetadata: {}, + revocationStatusListMetadata: {}, + }) + }) +}) diff --git a/packages/anoncreds/tests/legacyAnonCredsSetup.ts b/packages/anoncreds/tests/legacyAnonCredsSetup.ts new file mode 100644 index 0000000000..7565352e10 --- /dev/null +++ b/packages/anoncreds/tests/legacyAnonCredsSetup.ts @@ -0,0 +1,536 @@ +import type { EventReplaySubject } from '../../core/tests' +import type { + AnonCredsRegisterCredentialDefinitionOptions, + AnonCredsRequestedAttribute, + AnonCredsRequestedPredicate, + AnonCredsOfferCredentialFormat, + AnonCredsSchema, + RegisterCredentialDefinitionReturnStateFinished, + RegisterSchemaReturnStateFinished, +} from '../src' +import type { AutoAcceptProof, ConnectionRecord } from '@aries-framework/core' + +import { + TypedArrayEncoder, + CacheModule, + InMemoryLruCache, + Agent, + AriesFrameworkError, + AutoAcceptCredential, + CredentialEventTypes, + CredentialsModule, + CredentialState, + ProofEventTypes, + ProofsModule, + ProofState, + V2CredentialProtocol, + V2ProofProtocol, + DidsModule, +} from '@aries-framework/core' +import { anoncreds } from '@hyperledger/anoncreds-nodejs' +import { randomUUID } from 'crypto' + +import { AnonCredsRsModule } from '../../anoncreds-rs/src' +import { AskarModule } from '../../askar/src' +import { askarModuleConfig } from '../../askar/tests/helpers' +import { sleep } from '../../core/src/utils/sleep' +import { setupSubjectTransports, setupEventReplaySubjects } from '../../core/tests' +import { + getAgentOptions, + importExistingIndyDidFromPrivateKey, + makeConnection, + publicDidSeed, + waitForCredentialRecordSubject, + waitForProofExchangeRecordSubject, +} from '../../core/tests/helpers' +import testLogger from '../../core/tests/logger' +import { + IndySdkAnonCredsRegistry, + IndySdkIndyDidRegistrar, + IndySdkIndyDidResolver, + IndySdkModule, + IndySdkSovDidResolver, +} from '../../indy-sdk/src' +import { getIndySdkModuleConfig } from '../../indy-sdk/tests/setupIndySdkModule' +import { + IndyVdrAnonCredsRegistry, + IndyVdrSovDidResolver, + IndyVdrModule, + IndyVdrIndyDidResolver, + IndyVdrIndyDidRegistrar, +} from '../../indy-vdr/src' +import { indyVdrModuleConfig } from '../../indy-vdr/tests/helpers' +import { + getUnqualifiedCredentialDefinitionId, + getUnqualifiedSchemaId, + parseIndyCredentialDefinitionId, + parseIndySchemaId, + V1CredentialProtocol, + V1ProofProtocol, + AnonCredsModule, + LegacyIndyCredentialFormatService, + LegacyIndyProofFormatService, +} from '../src' + +// Helper type to get the type of the agents (with the custom modules) for the credential tests +export type AnonCredsTestsAgent = + | Agent & { mediationRecipient?: any; mediator?: any }> + | Agent & { mediationRecipient?: any; mediator?: any }> + +export const getLegacyAnonCredsModules = ({ + autoAcceptCredentials, + autoAcceptProofs, +}: { autoAcceptCredentials?: AutoAcceptCredential; autoAcceptProofs?: AutoAcceptProof } = {}) => { + const indyCredentialFormat = new LegacyIndyCredentialFormatService() + const indyProofFormat = new LegacyIndyProofFormatService() + + // Register the credential and proof protocols + const modules = { + credentials: new CredentialsModule({ + autoAcceptCredentials, + credentialProtocols: [ + new V1CredentialProtocol({ indyCredentialFormat }), + new V2CredentialProtocol({ + credentialFormats: [indyCredentialFormat], + }), + ], + }), + proofs: new ProofsModule({ + autoAcceptProofs, + proofProtocols: [ + new V1ProofProtocol({ indyProofFormat }), + new V2ProofProtocol({ + proofFormats: [indyProofFormat], + }), + ], + }), + anoncreds: new AnonCredsModule({ + registries: [new IndySdkAnonCredsRegistry()], + }), + dids: new DidsModule({ + resolvers: [new IndySdkSovDidResolver(), new IndySdkIndyDidResolver()], + registrars: [new IndySdkIndyDidRegistrar()], + }), + indySdk: new IndySdkModule(getIndySdkModuleConfig()), + cache: new CacheModule({ + cache: new InMemoryLruCache({ limit: 100 }), + }), + } as const + + return modules +} + +export const getAskarAnonCredsIndyModules = ({ + autoAcceptCredentials, + autoAcceptProofs, +}: { autoAcceptCredentials?: AutoAcceptCredential; autoAcceptProofs?: AutoAcceptProof } = {}) => { + const legacyIndyCredentialFormatService = new LegacyIndyCredentialFormatService() + const legacyIndyProofFormatService = new LegacyIndyProofFormatService() + + const modules = { + credentials: new CredentialsModule({ + autoAcceptCredentials, + credentialProtocols: [ + new V1CredentialProtocol({ + indyCredentialFormat: legacyIndyCredentialFormatService, + }), + new V2CredentialProtocol({ + credentialFormats: [legacyIndyCredentialFormatService], + }), + ], + }), + proofs: new ProofsModule({ + autoAcceptProofs, + proofProtocols: [ + new V1ProofProtocol({ + indyProofFormat: legacyIndyProofFormatService, + }), + new V2ProofProtocol({ + proofFormats: [legacyIndyProofFormatService], + }), + ], + }), + anoncreds: new AnonCredsModule({ + registries: [new IndyVdrAnonCredsRegistry()], + }), + anoncredsRs: new AnonCredsRsModule({ + anoncreds, + }), + indyVdr: new IndyVdrModule(indyVdrModuleConfig), + dids: new DidsModule({ + resolvers: [new IndyVdrSovDidResolver(), new IndyVdrIndyDidResolver()], + registrars: [new IndyVdrIndyDidRegistrar()], + }), + askar: new AskarModule(askarModuleConfig), + cache: new CacheModule({ + cache: new InMemoryLruCache({ limit: 100 }), + }), + } as const + + return modules +} + +export async function presentLegacyAnonCredsProof({ + verifierAgent, + verifierReplay, + + holderAgent, + holderReplay, + + verifierHolderConnectionId, + + request: { attributes, predicates }, +}: { + holderAgent: AnonCredsTestsAgent + holderReplay: EventReplaySubject + + verifierAgent: AnonCredsTestsAgent + verifierReplay: EventReplaySubject + + verifierHolderConnectionId: string + request: { + attributes?: Record + predicates?: Record + } +}) { + let holderProofExchangeRecordPromise = waitForProofExchangeRecordSubject(holderReplay, { + state: ProofState.RequestReceived, + }) + + let verifierProofExchangeRecord = await verifierAgent.proofs.requestProof({ + connectionId: verifierHolderConnectionId, + proofFormats: { + indy: { + name: 'Test Proof Request', + requested_attributes: attributes, + requested_predicates: predicates, + version: '1.0', + }, + }, + protocolVersion: 'v2', + }) + + let holderProofExchangeRecord = await holderProofExchangeRecordPromise + + const selectedCredentials = await holderAgent.proofs.selectCredentialsForRequest({ + proofRecordId: holderProofExchangeRecord.id, + }) + + const verifierProofExchangeRecordPromise = waitForProofExchangeRecordSubject(verifierReplay, { + threadId: holderProofExchangeRecord.threadId, + state: ProofState.PresentationReceived, + }) + + await holderAgent.proofs.acceptRequest({ + proofRecordId: holderProofExchangeRecord.id, + proofFormats: { indy: selectedCredentials.proofFormats.indy }, + }) + + verifierProofExchangeRecord = await verifierProofExchangeRecordPromise + + // assert presentation is valid + expect(verifierProofExchangeRecord.isVerified).toBe(true) + + holderProofExchangeRecordPromise = waitForProofExchangeRecordSubject(holderReplay, { + threadId: holderProofExchangeRecord.threadId, + state: ProofState.Done, + }) + + verifierProofExchangeRecord = await verifierAgent.proofs.acceptPresentation({ + proofRecordId: verifierProofExchangeRecord.id, + }) + holderProofExchangeRecord = await holderProofExchangeRecordPromise + + return { + verifierProofExchangeRecord, + holderProofExchangeRecord, + } +} + +export async function issueLegacyAnonCredsCredential({ + issuerAgent, + issuerReplay, + + holderAgent, + holderReplay, + + issuerHolderConnectionId, + offer, +}: { + issuerAgent: AnonCredsTestsAgent + issuerReplay: EventReplaySubject + + holderAgent: AnonCredsTestsAgent + holderReplay: EventReplaySubject + + issuerHolderConnectionId: string + offer: AnonCredsOfferCredentialFormat +}) { + let issuerCredentialExchangeRecord = await issuerAgent.credentials.offerCredential({ + comment: 'some comment about credential', + connectionId: issuerHolderConnectionId, + protocolVersion: 'v1', + credentialFormats: { + indy: offer, + }, + autoAcceptCredential: AutoAcceptCredential.ContentApproved, + }) + + let holderCredentialExchangeRecord = await waitForCredentialRecordSubject(holderReplay, { + threadId: issuerCredentialExchangeRecord.threadId, + state: CredentialState.OfferReceived, + }) + + await holderAgent.credentials.acceptOffer({ + credentialRecordId: holderCredentialExchangeRecord.id, + autoAcceptCredential: AutoAcceptCredential.ContentApproved, + }) + + // Because we use auto-accept it can take a while to have the whole credential flow finished + // Both parties need to interact with the ledger and sign/verify the credential + holderCredentialExchangeRecord = await waitForCredentialRecordSubject(holderReplay, { + threadId: issuerCredentialExchangeRecord.threadId, + state: CredentialState.Done, + }) + issuerCredentialExchangeRecord = await waitForCredentialRecordSubject(issuerReplay, { + threadId: issuerCredentialExchangeRecord.threadId, + state: CredentialState.Done, + }) + + return { + issuerCredentialExchangeRecord, + holderCredentialExchangeRecord, + } +} + +interface SetupAnonCredsTestsReturn { + issuerAgent: AnonCredsTestsAgent + issuerReplay: EventReplaySubject + + holderAgent: AnonCredsTestsAgent + holderReplay: EventReplaySubject + + issuerHolderConnectionId: CreateConnections extends true ? string : undefined + holderIssuerConnectionId: CreateConnections extends true ? string : undefined + + verifierHolderConnectionId: CreateConnections extends true + ? VerifierName extends string + ? string + : undefined + : undefined + holderVerifierConnectionId: CreateConnections extends true + ? VerifierName extends string + ? string + : undefined + : undefined + + verifierAgent: VerifierName extends string ? AnonCredsTestsAgent : undefined + verifierReplay: VerifierName extends string ? EventReplaySubject : undefined + + schemaId: string + credentialDefinitionId: string +} + +export async function setupAnonCredsTests< + VerifierName extends string | undefined = undefined, + CreateConnections extends boolean = true +>({ + issuerName, + holderName, + verifierName, + autoAcceptCredentials, + autoAcceptProofs, + attributeNames, + createConnections, +}: { + issuerName: string + holderName: string + verifierName?: VerifierName + autoAcceptCredentials?: AutoAcceptCredential + autoAcceptProofs?: AutoAcceptProof + attributeNames: string[] + createConnections?: CreateConnections +}): Promise> { + const issuerAgent = new Agent( + getAgentOptions( + issuerName, + { + endpoints: ['rxjs:issuer'], + }, + getLegacyAnonCredsModules({ + autoAcceptCredentials, + autoAcceptProofs, + }) + ) + ) + + const holderAgent = new Agent( + getAgentOptions( + holderName, + { + endpoints: ['rxjs:holder'], + }, + getLegacyAnonCredsModules({ + autoAcceptCredentials, + autoAcceptProofs, + }) + ) + ) + + const verifierAgent = verifierName + ? new Agent( + getAgentOptions( + verifierName, + { + endpoints: ['rxjs:verifier'], + }, + getLegacyAnonCredsModules({ + autoAcceptCredentials, + autoAcceptProofs, + }) + ) + ) + : undefined + + setupSubjectTransports(verifierAgent ? [issuerAgent, holderAgent, verifierAgent] : [issuerAgent, holderAgent]) + const [issuerReplay, holderReplay, verifierReplay] = setupEventReplaySubjects( + verifierAgent ? [issuerAgent, holderAgent, verifierAgent] : [issuerAgent, holderAgent], + [CredentialEventTypes.CredentialStateChanged, ProofEventTypes.ProofStateChanged] + ) + + await issuerAgent.initialize() + await holderAgent.initialize() + if (verifierAgent) await verifierAgent.initialize() + + // Create default link secret for holder + await holderAgent.modules.anoncreds.createLinkSecret({ + linkSecretId: 'default', + setAsDefault: true, + }) + + const { credentialDefinition, schema } = await prepareForAnonCredsIssuance(issuerAgent, { + attributeNames, + }) + + let issuerHolderConnection: ConnectionRecord | undefined + let holderIssuerConnection: ConnectionRecord | undefined + let verifierHolderConnection: ConnectionRecord | undefined + let holderVerifierConnection: ConnectionRecord | undefined + + if (createConnections ?? true) { + ;[issuerHolderConnection, holderIssuerConnection] = await makeConnection(issuerAgent, holderAgent) + + if (verifierAgent) { + ;[holderVerifierConnection, verifierHolderConnection] = await makeConnection(holderAgent, verifierAgent) + } + } + + return { + issuerAgent, + issuerReplay, + + holderAgent, + holderReplay, + + verifierAgent: verifierName ? verifierAgent : undefined, + verifierReplay: verifierName ? verifierReplay : undefined, + + credentialDefinitionId: credentialDefinition.credentialDefinitionId, + schemaId: schema.schemaId, + + issuerHolderConnectionId: issuerHolderConnection?.id, + holderIssuerConnectionId: holderIssuerConnection?.id, + holderVerifierConnectionId: holderVerifierConnection?.id, + verifierHolderConnectionId: verifierHolderConnection?.id, + } as unknown as SetupAnonCredsTestsReturn +} + +export async function prepareForAnonCredsIssuance(agent: Agent, { attributeNames }: { attributeNames: string[] }) { + // Add existing endorser did to the wallet + const unqualifiedDid = await importExistingIndyDidFromPrivateKey(agent, TypedArrayEncoder.fromString(publicDidSeed)) + const didIndyDid = `did:indy:pool:localtest:${unqualifiedDid}` + + const schema = await registerSchema(agent, { + // TODO: update attrNames to attributeNames + attrNames: attributeNames, + name: `Schema ${randomUUID()}`, + version: '1.0', + issuerId: didIndyDid, + }) + + // Wait some time pass to let ledger settle the object + await sleep(1000) + + const credentialDefinition = await registerCredentialDefinition(agent, { + schemaId: schema.schemaId, + issuerId: didIndyDid, + tag: 'default', + }) + + const s = parseIndySchemaId(schema.schemaId) + const cd = parseIndyCredentialDefinitionId(credentialDefinition.credentialDefinitionId) + + const legacySchemaId = getUnqualifiedSchemaId(s.namespaceIdentifier, s.schemaName, s.schemaVersion) + const legacyCredentialDefinitionId = getUnqualifiedCredentialDefinitionId( + cd.namespaceIdentifier, + cd.schemaSeqNo, + cd.tag + ) + + // Wait some time pass to let ledger settle the object + await sleep(1000) + + // NOTE: we return the legacy schema and credential definition ids here because that's what currently expected + // in all tests. If we also support did:indy in tests we probably want to return the qualified identifiers here + // and transform them to the legacy variant in the specific tests that need it. + return { + schema: { + ...schema, + schemaId: legacySchemaId, + }, + credentialDefinition: { + ...credentialDefinition, + credentialDefinitionId: legacyCredentialDefinitionId, + }, + } +} + +async function registerSchema( + agent: AnonCredsTestsAgent, + schema: AnonCredsSchema +): Promise { + const { schemaState } = await agent.modules.anoncreds.registerSchema({ + schema, + options: {}, + }) + + testLogger.test(`created schema with id ${schemaState.schemaId}`, schema) + + if (schemaState.state !== 'finished') { + throw new AriesFrameworkError( + `Schema not created: ${schemaState.state === 'failed' ? schemaState.reason : 'Not finished'}` + ) + } + + return schemaState +} + +async function registerCredentialDefinition( + agent: AnonCredsTestsAgent, + credentialDefinition: AnonCredsRegisterCredentialDefinitionOptions +): Promise { + const { credentialDefinitionState } = await agent.modules.anoncreds.registerCredentialDefinition({ + credentialDefinition, + options: {}, + }) + + if (credentialDefinitionState.state !== 'finished') { + throw new AriesFrameworkError( + `Credential definition not created: ${ + credentialDefinitionState.state === 'failed' ? credentialDefinitionState.reason : 'Not finished' + }` + ) + } + + return credentialDefinitionState +} diff --git a/packages/anoncreds/tests/setup.ts b/packages/anoncreds/tests/setup.ts new file mode 100644 index 0000000000..34e38c9705 --- /dev/null +++ b/packages/anoncreds/tests/setup.ts @@ -0,0 +1 @@ +jest.setTimeout(120000) diff --git a/packages/anoncreds/tsconfig.build.json b/packages/anoncreds/tsconfig.build.json new file mode 100644 index 0000000000..2b75d0adab --- /dev/null +++ b/packages/anoncreds/tsconfig.build.json @@ -0,0 +1,7 @@ +{ + "extends": "../../tsconfig.build.json", + "compilerOptions": { + "outDir": "./build" + }, + "include": ["src/**/*"] +} diff --git a/packages/anoncreds/tsconfig.json b/packages/anoncreds/tsconfig.json new file mode 100644 index 0000000000..46efe6f721 --- /dev/null +++ b/packages/anoncreds/tsconfig.json @@ -0,0 +1,6 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "types": ["jest"] + } +} diff --git a/packages/askar/README.md b/packages/askar/README.md new file mode 100644 index 0000000000..5f68099a30 --- /dev/null +++ b/packages/askar/README.md @@ -0,0 +1,31 @@ +

+
+ Hyperledger Aries logo +

+

Aries Framework JavaScript Askar Module

+

+ License + typescript + @aries-framework/askar version + +

+
+ +Askar module for [Aries Framework JavaScript](https://github.com/hyperledger/aries-framework-javascript.git). diff --git a/packages/askar/jest.config.ts b/packages/askar/jest.config.ts new file mode 100644 index 0000000000..93c0197296 --- /dev/null +++ b/packages/askar/jest.config.ts @@ -0,0 +1,13 @@ +import type { Config } from '@jest/types' + +import base from '../../jest.config.base' + +import packageJson from './package.json' + +const config: Config.InitialOptions = { + ...base, + displayName: packageJson.name, + setupFilesAfterEnv: ['./tests/setup.ts'], +} + +export default config diff --git a/packages/askar/package.json b/packages/askar/package.json new file mode 100644 index 0000000000..29d8f86786 --- /dev/null +++ b/packages/askar/package.json @@ -0,0 +1,42 @@ +{ + "name": "@aries-framework/askar", + "main": "build/index", + "types": "build/index", + "version": "0.3.3", + "files": [ + "build" + ], + "license": "Apache-2.0", + "publishConfig": { + "access": "public" + }, + "homepage": "https://github.com/hyperledger/aries-framework-javascript/tree/main/packages/askar", + "repository": { + "type": "git", + "url": "https://github.com/hyperledger/aries-framework-javascript", + "directory": "packages/askar" + }, + "scripts": { + "build": "yarn run clean && yarn run compile", + "clean": "rimraf ./build", + "compile": "tsc -p tsconfig.build.json", + "prepublishOnly": "yarn run build", + "test": "jest" + }, + "dependencies": { + "@aries-framework/core": "0.3.3", + "@hyperledger/aries-askar-shared": "^0.1.0-dev.8", + "bn.js": "^5.2.1", + "class-transformer": "0.5.1", + "class-validator": "0.14.0", + "rxjs": "^7.2.0", + "tsyringe": "^4.7.0" + }, + "devDependencies": { + "@types/bn.js": "^5.1.0", + "@hyperledger/aries-askar-nodejs": "^0.1.0-dev.8", + "reflect-metadata": "^0.1.13", + "rimraf": "^4.4.0", + "typescript": "~4.9.5" + } +} diff --git a/packages/askar/src/AskarModule.ts b/packages/askar/src/AskarModule.ts new file mode 100644 index 0000000000..8a22d82323 --- /dev/null +++ b/packages/askar/src/AskarModule.ts @@ -0,0 +1,32 @@ +import type { AskarModuleConfigOptions } from './AskarModuleConfig' +import type { DependencyManager, Module } from '@aries-framework/core' + +import { AriesFrameworkError, InjectionSymbols } from '@aries-framework/core' + +import { AskarModuleConfig } from './AskarModuleConfig' +import { AskarStorageService } from './storage' +import { AskarWallet } from './wallet' + +export class AskarModule implements Module { + public readonly config: AskarModuleConfig + + public constructor(config: AskarModuleConfigOptions) { + this.config = new AskarModuleConfig(config) + } + + public register(dependencyManager: DependencyManager) { + dependencyManager.registerInstance(AskarModuleConfig, this.config) + + if (dependencyManager.isRegistered(InjectionSymbols.Wallet)) { + throw new AriesFrameworkError('There is an instance of Wallet already registered') + } else { + dependencyManager.registerContextScoped(InjectionSymbols.Wallet, AskarWallet) + } + + if (dependencyManager.isRegistered(InjectionSymbols.StorageService)) { + throw new AriesFrameworkError('There is an instance of StorageService already registered') + } else { + dependencyManager.registerSingleton(InjectionSymbols.StorageService, AskarStorageService) + } + } +} diff --git a/packages/askar/src/AskarModuleConfig.ts b/packages/askar/src/AskarModuleConfig.ts new file mode 100644 index 0000000000..38eebdde86 --- /dev/null +++ b/packages/askar/src/AskarModuleConfig.ts @@ -0,0 +1,54 @@ +import type { AriesAskar } from '@hyperledger/aries-askar-shared' + +export interface AskarModuleConfigOptions { + /** + * + * ## Node.JS + * + * ```ts + * import { ariesAskar } from '@hyperledger/aries-askar-nodejs' + * + * const agent = new Agent({ + * config: {}, + * dependencies: agentDependencies, + * modules: { + * ariesAskar: new AskarModule({ + * ariesAskar, + * }) + * } + * }) + * ``` + * + * ## React Native + * + * ```ts + * import { ariesAskar } from '@hyperledger/aries-askar-react-native' + * + * const agent = new Agent({ + * config: {}, + * dependencies: agentDependencies, + * modules: { + * ariesAskar: new AskarModule({ + * ariesAskar, + * }) + * } + * }) + * ``` + */ + ariesAskar: AriesAskar +} + +/** + * @public + */ +export class AskarModuleConfig { + private options: AskarModuleConfigOptions + + public constructor(options: AskarModuleConfigOptions) { + this.options = options + } + + public get ariesAskar() { + return this.options.ariesAskar + } +} diff --git a/packages/askar/src/index.ts b/packages/askar/src/index.ts new file mode 100644 index 0000000000..438ae1d7f9 --- /dev/null +++ b/packages/askar/src/index.ts @@ -0,0 +1,13 @@ +// Wallet +export { + AskarWallet, + AskarWalletPostgresStorageConfig, + AskarWalletPostgresConfig, + AskarWalletPostgresCredentials, +} from './wallet' + +// Storage +export { AskarStorageService } from './storage' + +// Module +export { AskarModule } from './AskarModule' diff --git a/packages/askar/src/storage/AskarStorageService.ts b/packages/askar/src/storage/AskarStorageService.ts new file mode 100644 index 0000000000..2174291c81 --- /dev/null +++ b/packages/askar/src/storage/AskarStorageService.ts @@ -0,0 +1,165 @@ +import type { BaseRecordConstructor, AgentContext, BaseRecord, Query, StorageService } from '@aries-framework/core' + +import { + RecordDuplicateError, + WalletError, + RecordNotFoundError, + injectable, + JsonTransformer, +} from '@aries-framework/core' +import { Scan } from '@hyperledger/aries-askar-shared' + +import { AskarErrorCode, isAskarError } from '../utils/askarError' +import { assertAskarWallet } from '../utils/assertAskarWallet' + +import { askarQueryFromSearchQuery, recordToInstance, transformFromRecordTagValues } from './utils' + +@injectable() +export class AskarStorageService implements StorageService { + /** @inheritDoc */ + public async save(agentContext: AgentContext, record: T) { + assertAskarWallet(agentContext.wallet) + const session = agentContext.wallet.session + + record.updatedAt = new Date() + + const value = JsonTransformer.serialize(record) + const tags = transformFromRecordTagValues(record.getTags()) as Record + + try { + await session.insert({ category: record.type, name: record.id, value, tags }) + } catch (error) { + if (isAskarError(error, AskarErrorCode.Duplicate)) { + throw new RecordDuplicateError(`Record with id ${record.id} already exists`, { recordType: record.type }) + } + + throw new WalletError('Error saving record', { cause: error }) + } + } + + /** @inheritDoc */ + public async update(agentContext: AgentContext, record: T): Promise { + assertAskarWallet(agentContext.wallet) + const session = agentContext.wallet.session + + record.updatedAt = new Date() + + const value = JsonTransformer.serialize(record) + const tags = transformFromRecordTagValues(record.getTags()) as Record + + try { + await session.replace({ category: record.type, name: record.id, value, tags }) + } catch (error) { + if (isAskarError(error, AskarErrorCode.NotFound)) { + throw new RecordNotFoundError(`record with id ${record.id} not found.`, { + recordType: record.type, + cause: error, + }) + } + + throw new WalletError('Error updating record', { cause: error }) + } + } + + /** @inheritDoc */ + public async delete(agentContext: AgentContext, record: T) { + assertAskarWallet(agentContext.wallet) + const session = agentContext.wallet.session + + try { + await session.remove({ category: record.type, name: record.id }) + } catch (error) { + if (isAskarError(error, AskarErrorCode.NotFound)) { + throw new RecordNotFoundError(`record with id ${record.id} not found.`, { + recordType: record.type, + cause: error, + }) + } + throw new WalletError('Error deleting record', { cause: error }) + } + } + + /** @inheritDoc */ + public async deleteById( + agentContext: AgentContext, + recordClass: BaseRecordConstructor, + id: string + ): Promise { + assertAskarWallet(agentContext.wallet) + const session = agentContext.wallet.session + + try { + await session.remove({ category: recordClass.type, name: id }) + } catch (error) { + if (isAskarError(error, AskarErrorCode.NotFound)) { + throw new RecordNotFoundError(`record with id ${id} not found.`, { + recordType: recordClass.type, + cause: error, + }) + } + throw new WalletError('Error deleting record', { cause: error }) + } + } + + /** @inheritDoc */ + public async getById(agentContext: AgentContext, recordClass: BaseRecordConstructor, id: string): Promise { + assertAskarWallet(agentContext.wallet) + const session = agentContext.wallet.session + + try { + const record = await session.fetch({ category: recordClass.type, name: id }) + if (!record) { + throw new RecordNotFoundError(`record with id ${id} not found.`, { + recordType: recordClass.type, + }) + } + return recordToInstance(record, recordClass) + } catch (error) { + if (error instanceof RecordNotFoundError) throw error + throw new WalletError(`Error getting record`, { cause: error }) + } + } + + /** @inheritDoc */ + public async getAll(agentContext: AgentContext, recordClass: BaseRecordConstructor): Promise { + assertAskarWallet(agentContext.wallet) + const session = agentContext.wallet.session + + const records = await session.fetchAll({ category: recordClass.type }) + + const instances = [] + for (const record of records) { + instances.push(recordToInstance(record, recordClass)) + } + return instances + } + + /** @inheritDoc */ + public async findByQuery( + agentContext: AgentContext, + recordClass: BaseRecordConstructor, + query: Query + ): Promise { + assertAskarWallet(agentContext.wallet) + const store = agentContext.wallet.store + + const askarQuery = askarQueryFromSearchQuery(query) + + const scan = new Scan({ + category: recordClass.type, + store, + tagFilter: askarQuery, + }) + + const instances = [] + try { + const records = await scan.fetchAll() + for (const record of records) { + instances.push(recordToInstance(record, recordClass)) + } + return instances + } catch (error) { + throw new WalletError(`Error executing query. ${error.message}`, { cause: error }) + } + } +} diff --git a/packages/askar/src/storage/__tests__/AskarStorageService.test.ts b/packages/askar/src/storage/__tests__/AskarStorageService.test.ts new file mode 100644 index 0000000000..d39aadf83c --- /dev/null +++ b/packages/askar/src/storage/__tests__/AskarStorageService.test.ts @@ -0,0 +1,319 @@ +import type { AgentContext, TagsBase } from '@aries-framework/core' + +import { + TypedArrayEncoder, + KeyProviderRegistry, + RecordDuplicateError, + RecordNotFoundError, +} from '@aries-framework/core' +import { ariesAskar } from '@hyperledger/aries-askar-nodejs' + +import { describeRunInNodeVersion } from '../../../../../tests/runInVersion' +import { TestRecord } from '../../../../core/src/storage/__tests__/TestRecord' +import { agentDependencies, getAgentConfig, getAgentContext } from '../../../../core/tests/helpers' +import { AskarWallet } from '../../wallet/AskarWallet' +import { AskarStorageService } from '../AskarStorageService' +import { askarQueryFromSearchQuery } from '../utils' + +const startDate = Date.now() + +describeRunInNodeVersion([18], 'AskarStorageService', () => { + let wallet: AskarWallet + let storageService: AskarStorageService + let agentContext: AgentContext + + beforeEach(async () => { + const agentConfig = getAgentConfig('AskarStorageServiceTest') + + wallet = new AskarWallet(agentConfig.logger, new agentDependencies.FileSystem(), new KeyProviderRegistry([])) + agentContext = getAgentContext({ + wallet, + agentConfig, + }) + await wallet.createAndOpen(agentConfig.walletConfig) + storageService = new AskarStorageService() + }) + + afterEach(async () => { + await wallet.delete() + }) + + const insertRecord = async ({ id, tags }: { id?: string; tags?: TagsBase }) => { + const props = { + id, + foo: 'bar', + tags: tags ?? { myTag: 'foobar' }, + } + const record = new TestRecord(props) + await storageService.save(agentContext, record) + return record + } + + describe('tag transformation', () => { + it('should correctly transform tag values to string before storing', async () => { + const record = await insertRecord({ + id: 'test-id', + tags: { + someBoolean: true, + someOtherBoolean: false, + someStringValue: 'string', + anArrayValue: ['foo', 'bar'], + // booleans are stored as '1' and '0' so we store the string values '1' and '0' as 'n__1' and 'n__0' + someStringNumberValue: '1', + anotherStringNumberValue: '0', + }, + }) + + const retrieveRecord = await ariesAskar.sessionFetch({ + category: record.type, + name: record.id, + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + sessionHandle: wallet.session.handle!, + forUpdate: false, + }) + + expect(JSON.parse(retrieveRecord?.getTags(0) ?? '{}')).toEqual({ + someBoolean: '1', + someOtherBoolean: '0', + someStringValue: 'string', + 'anArrayValue:foo': '1', + 'anArrayValue:bar': '1', + someStringNumberValue: 'n__1', + anotherStringNumberValue: 'n__0', + }) + }) + + it('should correctly transform tag values from string after retrieving', async () => { + await ariesAskar.sessionUpdate({ + category: TestRecord.type, + name: 'some-id', + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + sessionHandle: wallet.session.handle!, + value: TypedArrayEncoder.fromString('{}'), + tags: { + someBoolean: '1', + someOtherBoolean: '0', + someStringValue: 'string', + 'anArrayValue:foo': '1', + 'anArrayValue:bar': '1', + // booleans are stored as '1' and '0' so we store the string values '1' and '0' as 'n__1' and 'n__0' + someStringNumberValue: 'n__1', + anotherStringNumberValue: 'n__0', + }, + operation: 0, // EntryOperation.Insert + }) + + const record = await storageService.getById(agentContext, TestRecord, 'some-id') + + expect(record.getTags()).toEqual({ + someBoolean: true, + someOtherBoolean: false, + someStringValue: 'string', + anArrayValue: expect.arrayContaining(['bar', 'foo']), + someStringNumberValue: '1', + anotherStringNumberValue: '0', + }) + }) + }) + + describe('save()', () => { + it('should throw RecordDuplicateError if a record with the id already exists', async () => { + const record = await insertRecord({ id: 'test-id' }) + + return expect(() => storageService.save(agentContext, record)).rejects.toThrowError(RecordDuplicateError) + }) + + it('should save the record', async () => { + const record = await insertRecord({ id: 'test-id' }) + const found = await storageService.getById(agentContext, TestRecord, 'test-id') + + expect(record).toEqual(found) + }) + + it('updatedAt should have a new value after a save', async () => { + const record = await insertRecord({ id: 'test-id' }) + expect(record.updatedAt?.getTime()).toBeGreaterThan(startDate) + }) + }) + + describe('getById()', () => { + it('should throw RecordNotFoundError if the record does not exist', async () => { + return expect(() => storageService.getById(agentContext, TestRecord, 'does-not-exist')).rejects.toThrowError( + RecordNotFoundError + ) + }) + + it('should return the record by id', async () => { + const record = await insertRecord({ id: 'test-id' }) + const found = await storageService.getById(agentContext, TestRecord, 'test-id') + + expect(found).toEqual(record) + }) + }) + + describe('update()', () => { + it('should throw RecordNotFoundError if the record does not exist', async () => { + const record = new TestRecord({ + id: 'test-id', + foo: 'test', + tags: { some: 'tag' }, + }) + + return expect(() => storageService.update(agentContext, record)).rejects.toThrowError(RecordNotFoundError) + }) + + it('should update the record', async () => { + const record = await insertRecord({ id: 'test-id' }) + + record.replaceTags({ ...record.getTags(), foo: 'bar' }) + record.foo = 'foobaz' + await storageService.update(agentContext, record) + + const retrievedRecord = await storageService.getById(agentContext, TestRecord, record.id) + expect(retrievedRecord).toEqual(record) + }) + + it('updatedAt should have a new value after an update', async () => { + const record = await insertRecord({ id: 'test-id' }) + expect(record.updatedAt?.getTime()).toBeGreaterThan(startDate) + }) + }) + + describe('delete()', () => { + it('should throw RecordNotFoundError if the record does not exist', async () => { + const record = new TestRecord({ + id: 'test-id', + foo: 'test', + tags: { some: 'tag' }, + }) + + return expect(() => storageService.delete(agentContext, record)).rejects.toThrowError(RecordNotFoundError) + }) + + it('should delete the record', async () => { + const record = await insertRecord({ id: 'test-id' }) + await storageService.delete(agentContext, record) + + return expect(() => storageService.getById(agentContext, TestRecord, record.id)).rejects.toThrowError( + RecordNotFoundError + ) + }) + }) + + describe('getAll()', () => { + it('should retrieve all records', async () => { + const createdRecords = await Promise.all( + Array(5) + .fill(undefined) + .map((_, index) => insertRecord({ id: `record-${index}` })) + ) + + const records = await storageService.getAll(agentContext, TestRecord) + + expect(records).toEqual(expect.arrayContaining(createdRecords)) + }) + }) + + describe('findByQuery()', () => { + it('should retrieve all records that match the query', async () => { + const expectedRecord = await insertRecord({ tags: { myTag: 'foobar' } }) + const expectedRecord2 = await insertRecord({ tags: { myTag: 'foobar' } }) + await insertRecord({ tags: { myTag: 'notfoobar' } }) + + const records = await storageService.findByQuery(agentContext, TestRecord, { myTag: 'foobar' }) + + expect(records.length).toBe(2) + expect(records).toEqual(expect.arrayContaining([expectedRecord, expectedRecord2])) + }) + + it('finds records using $and statements', async () => { + const expectedRecord = await insertRecord({ tags: { myTag: 'foo', anotherTag: 'bar' } }) + await insertRecord({ tags: { myTag: 'notfoobar' } }) + + const records = await storageService.findByQuery(agentContext, TestRecord, { + $and: [{ myTag: 'foo' }, { anotherTag: 'bar' }], + }) + + expect(records.length).toBe(1) + expect(records[0]).toEqual(expectedRecord) + }) + + it('finds records using $or statements', async () => { + const expectedRecord = await insertRecord({ tags: { myTag: 'foo' } }) + const expectedRecord2 = await insertRecord({ tags: { anotherTag: 'bar' } }) + await insertRecord({ tags: { myTag: 'notfoobar' } }) + + const records = await storageService.findByQuery(agentContext, TestRecord, { + $or: [{ myTag: 'foo' }, { anotherTag: 'bar' }], + }) + + expect(records.length).toBe(2) + expect(records).toEqual(expect.arrayContaining([expectedRecord, expectedRecord2])) + }) + + it('finds records using $not statements', async () => { + const expectedRecord = await insertRecord({ tags: { myTag: 'foo' } }) + const expectedRecord2 = await insertRecord({ tags: { anotherTag: 'bar' } }) + await insertRecord({ tags: { myTag: 'notfoobar' } }) + + const records = await storageService.findByQuery(agentContext, TestRecord, { + $not: { myTag: 'notfoobar' }, + }) + + expect(records.length).toBe(2) + expect(records).toEqual(expect.arrayContaining([expectedRecord, expectedRecord2])) + }) + + it('correctly transforms an advanced query into a valid WQL query', async () => { + const expectedQuery = { + $and: [ + { + $and: undefined, + $not: undefined, + $or: [ + { myTag: '1', $and: undefined, $or: undefined, $not: undefined }, + { myTag: '0', $and: undefined, $or: undefined, $not: undefined }, + ], + }, + { + $or: undefined, + $not: undefined, + $and: [ + { theNumber: 'n__0', $and: undefined, $or: undefined, $not: undefined }, + { theNumber: 'n__1', $and: undefined, $or: undefined, $not: undefined }, + ], + }, + ], + $or: [ + { + 'aValue:foo': '1', + 'aValue:bar': '1', + $and: undefined, + $or: undefined, + $not: undefined, + }, + ], + $not: { myTag: 'notfoobar', $and: undefined, $or: undefined, $not: undefined }, + } + + expect( + askarQueryFromSearchQuery({ + $and: [ + { + $or: [{ myTag: true }, { myTag: false }], + }, + { + $and: [{ theNumber: '0' }, { theNumber: '1' }], + }, + ], + $or: [ + { + aValue: ['foo', 'bar'], + }, + ], + $not: { myTag: 'notfoobar' }, + }) + ).toEqual(expectedQuery) + }) + }) +}) diff --git a/packages/askar/src/storage/index.ts b/packages/askar/src/storage/index.ts new file mode 100644 index 0000000000..ac0265f1ea --- /dev/null +++ b/packages/askar/src/storage/index.ts @@ -0,0 +1 @@ +export * from './AskarStorageService' diff --git a/packages/askar/src/storage/utils.ts b/packages/askar/src/storage/utils.ts new file mode 100644 index 0000000000..381bd98dd7 --- /dev/null +++ b/packages/askar/src/storage/utils.ts @@ -0,0 +1,110 @@ +import type { BaseRecord, BaseRecordConstructor, Query, TagsBase } from '@aries-framework/core' +import type { EntryObject } from '@hyperledger/aries-askar-shared' + +import { JsonTransformer } from '@aries-framework/core' + +export function recordToInstance(record: EntryObject, recordClass: BaseRecordConstructor): T { + const instance = JsonTransformer.deserialize(record.value as string, recordClass) + instance.id = record.name + + const tags = record.tags ? transformToRecordTagValues(record.tags) : {} + instance.replaceTags(tags) + + return instance +} + +export function transformToRecordTagValues(tags: Record): TagsBase { + const transformedTags: TagsBase = {} + + for (const [key, value] of Object.entries(tags)) { + // If the value is a boolean string ('1' or '0') + // use the boolean val + if (value === '1' && key?.includes(':')) { + const [tagName, tagValue] = key.split(':') + + const transformedValue = transformedTags[tagName] + + if (Array.isArray(transformedValue)) { + transformedTags[tagName] = [...transformedValue, tagValue] + } else { + transformedTags[tagName] = [tagValue] + } + } + // Transform '1' and '0' to boolean + else if (value === '1' || value === '0') { + transformedTags[key] = value === '1' + } + // If 1 or 0 is prefixed with 'n__' we need to remove it. This is to prevent + // casting the value to a boolean + else if (value === 'n__1' || value === 'n__0') { + transformedTags[key] = value === 'n__1' ? '1' : '0' + } + // Otherwise just use the value + else { + transformedTags[key] = value as string + } + } + + return transformedTags +} + +export function transformFromRecordTagValues(tags: TagsBase): { [key: string]: string | undefined } { + const transformedTags: { [key: string]: string | undefined } = {} + + for (const [key, value] of Object.entries(tags)) { + // If the value is of type null we use the value undefined + // Askar doesn't support null as a value + if (value === null) { + transformedTags[key] = undefined + } + // If the value is a boolean use the Askar + // '1' or '0' syntax + else if (typeof value === 'boolean') { + transformedTags[key] = value ? '1' : '0' + } + // If the value is 1 or 0, we need to add something to the value, otherwise + // the next time we deserialize the tag values it will be converted to boolean + else if (value === '1' || value === '0') { + transformedTags[key] = `n__${value}` + } + // If the value is an array we create a tag for each array + // item ("tagName:arrayItem" = "1") + else if (Array.isArray(value)) { + value.forEach((item) => { + const tagName = `${key}:${item}` + transformedTags[tagName] = '1' + }) + } + // Otherwise just use the value + else { + transformedTags[key] = value + } + } + + return transformedTags +} + +/** + * Transforms the search query into a wallet query compatible with Askar WQL. + * + * The format used by AFJ is almost the same as the WQL query, with the exception of + * the encoding of values, however this is handled by the {@link AskarStorageServiceUtil.transformToRecordTagValues} + * method. + */ +export function askarQueryFromSearchQuery(query: Query): Record { + // eslint-disable-next-line prefer-const + let { $and, $or, $not, ...tags } = query + + $and = ($and as Query[] | undefined)?.map((q) => askarQueryFromSearchQuery(q)) + $or = ($or as Query[] | undefined)?.map((q) => askarQueryFromSearchQuery(q)) + $not = $not ? askarQueryFromSearchQuery($not as Query) : undefined + + const askarQuery = { + ...transformFromRecordTagValues(tags as unknown as TagsBase), + $and, + $or, + $not, + } + + return askarQuery +} diff --git a/packages/askar/src/utils/askarError.ts b/packages/askar/src/utils/askarError.ts new file mode 100644 index 0000000000..632733413a --- /dev/null +++ b/packages/askar/src/utils/askarError.ts @@ -0,0 +1,17 @@ +import { AriesAskarError } from '@hyperledger/aries-askar-shared' + +export enum AskarErrorCode { + Success = 0, + Backend = 1, + Busy = 2, + Duplicate = 3, + Encryption = 4, + Input = 5, + NotFound = 6, + Unexpected = 7, + Unsupported = 8, + Custom = 100, +} + +export const isAskarError = (error: Error, askarErrorCode?: AskarErrorCode): error is AriesAskarError => + error instanceof AriesAskarError && (askarErrorCode === undefined || error.code === askarErrorCode) diff --git a/packages/askar/src/utils/askarKeyTypes.ts b/packages/askar/src/utils/askarKeyTypes.ts new file mode 100644 index 0000000000..5d59a26493 --- /dev/null +++ b/packages/askar/src/utils/askarKeyTypes.ts @@ -0,0 +1,13 @@ +import { KeyType } from '@aries-framework/core' +import { KeyAlgs } from '@hyperledger/aries-askar-shared' + +const keyTypeToAskarAlg = { + [KeyType.Ed25519]: KeyAlgs.Ed25519, + [KeyType.X25519]: KeyAlgs.X25519, + [KeyType.Bls12381g1]: KeyAlgs.Bls12381G1, + [KeyType.Bls12381g2]: KeyAlgs.Bls12381G2, + [KeyType.Bls12381g1g2]: KeyAlgs.Bls12381G1G2, + [KeyType.P256]: KeyAlgs.EcSecp256r1, +} as const + +export const keyTypeSupportedByAskar = (keyType: KeyType) => keyType in keyTypeToAskarAlg diff --git a/packages/askar/src/utils/askarWalletConfig.ts b/packages/askar/src/utils/askarWalletConfig.ts new file mode 100644 index 0000000000..50424bcd84 --- /dev/null +++ b/packages/askar/src/utils/askarWalletConfig.ts @@ -0,0 +1,81 @@ +import type { AskarWalletPostgresStorageConfig } from '../wallet/AskarWalletPostgresStorageConfig' +import type { WalletConfig } from '@aries-framework/core' + +import { KeyDerivationMethod, WalletError } from '@aries-framework/core' +import { KdfMethod, StoreKeyMethod } from '@hyperledger/aries-askar-shared' + +export const keyDerivationMethodToStoreKeyMethod = (keyDerivationMethod: KeyDerivationMethod) => { + const correspondenceTable = { + [KeyDerivationMethod.Raw]: KdfMethod.Raw, + [KeyDerivationMethod.Argon2IInt]: KdfMethod.Argon2IInt, + [KeyDerivationMethod.Argon2IMod]: KdfMethod.Argon2IMod, + } + + return new StoreKeyMethod(correspondenceTable[keyDerivationMethod]) +} + +/** + * Creates a proper askar wallet URI value based on walletConfig + * @param walletConfig WalletConfig object + * @param afjDataPath framework data path (used in case walletConfig.storage.path is undefined) + * @returns string containing the askar wallet URI + */ +export const uriFromWalletConfig = ( + walletConfig: WalletConfig, + afjDataPath: string +): { uri: string; path?: string } => { + let uri = '' + let path + + // By default use sqlite as database backend + if (!walletConfig.storage) { + walletConfig.storage = { type: 'sqlite' } + } + + if (walletConfig.storage.type === 'sqlite') { + if (walletConfig.storage.inMemory) { + uri = 'sqlite://:memory:' + } else { + path = (walletConfig.storage.path as string) ?? `${afjDataPath}/wallet/${walletConfig.id}/sqlite.db` + uri = `sqlite://${path}` + } + } else if (walletConfig.storage.type === 'postgres') { + const storageConfig = walletConfig.storage as unknown as AskarWalletPostgresStorageConfig + + if (!storageConfig.config || !storageConfig.credentials) { + throw new WalletError('Invalid storage configuration for postgres wallet') + } + + const urlParams = [] + if (storageConfig.config.connectTimeout !== undefined) { + urlParams.push(`connect_timeout=${encodeURIComponent(storageConfig.config.connectTimeout)}`) + } + if (storageConfig.config.idleTimeout !== undefined) { + urlParams.push(`idle_timeout=${encodeURIComponent(storageConfig.config.idleTimeout)}`) + } + if (storageConfig.config.maxConnections !== undefined) { + urlParams.push(`max_connections=${encodeURIComponent(storageConfig.config.maxConnections)}`) + } + if (storageConfig.config.minConnections !== undefined) { + urlParams.push(`min_connections=${encodeURIComponent(storageConfig.config.minConnections)}`) + } + if (storageConfig.credentials.adminAccount !== undefined) { + urlParams.push(`admin_account=${encodeURIComponent(storageConfig.credentials.adminAccount)}`) + } + if (storageConfig.credentials.adminPassword !== undefined) { + urlParams.push(`admin_password=${encodeURIComponent(storageConfig.credentials.adminPassword)}`) + } + + uri = `postgres://${encodeURIComponent(storageConfig.credentials.account)}:${encodeURIComponent( + storageConfig.credentials.password + )}@${storageConfig.config.host}/${encodeURIComponent(walletConfig.id)}` + + if (urlParams.length > 0) { + uri = `${uri}?${urlParams.join('&')}` + } + } else { + throw new WalletError(`Storage type not supported: ${walletConfig.storage.type}`) + } + + return { uri, path } +} diff --git a/packages/askar/src/utils/assertAskarWallet.ts b/packages/askar/src/utils/assertAskarWallet.ts new file mode 100644 index 0000000000..37213e3d28 --- /dev/null +++ b/packages/askar/src/utils/assertAskarWallet.ts @@ -0,0 +1,13 @@ +import type { Wallet } from '@aries-framework/core' + +import { AriesFrameworkError } from '@aries-framework/core' + +import { AskarWallet } from '../wallet/AskarWallet' + +export function assertAskarWallet(wallet: Wallet): asserts wallet is AskarWallet { + if (!(wallet instanceof AskarWallet)) { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const walletClassName = (wallet as any).constructor?.name ?? 'unknown' + throw new AriesFrameworkError(`Expected wallet to be instance of AskarWallet, found ${walletClassName}`) + } +} diff --git a/packages/askar/src/utils/index.ts b/packages/askar/src/utils/index.ts new file mode 100644 index 0000000000..b9f658de82 --- /dev/null +++ b/packages/askar/src/utils/index.ts @@ -0,0 +1,3 @@ +export * from './askarError' +export * from './askarKeyTypes' +export * from './askarWalletConfig' diff --git a/packages/askar/src/wallet/AskarWallet.ts b/packages/askar/src/wallet/AskarWallet.ts new file mode 100644 index 0000000000..e2ddee9154 --- /dev/null +++ b/packages/askar/src/wallet/AskarWallet.ts @@ -0,0 +1,1279 @@ +import type { + EncryptedMessage, + KeyPair, + UnpackedMessageContext, + Wallet, + WalletConfig, + WalletConfigRekey, + WalletCreateKeyOptions, + WalletExportImportConfig, + WalletPackOptions, + WalletSignOptions, + WalletVerifyOptions, +} from '@aries-framework/core' +import type { KeyEntryObject, Session } from '@hyperledger/aries-askar-shared' + +import { + AriesFrameworkError, + Buffer, + DidCommMessageVersion, + DidCommV2EncryptionAlgs, + DidCommV2KeyProtectionAlgs, + DidCommV2Types, + DidKey, + FileSystem, + InjectionSymbols, + isDidCommV1EncryptedEnvelope, + isValidPrivateKey, + isValidSeed, + JsonEncoder, + JsonTransformer, + JweEnvelope, + JweEnvelopeBuilder, + JweRecipient, + Key, + KeyDerivationMethod, + KeyProviderRegistry, + KeyType, + Logger, + TypedArrayEncoder, + WalletDuplicateError, + WalletError, + WalletExportPathExistsError, + WalletInvalidKeyError, + WalletKeyExistsError, + WalletNotFoundError, +} from '@aries-framework/core' +import { + CryptoBox, + Ecdh1PU, + EcdhEs, + Key as AskarKey, + keyAlgFromString, + KeyAlgs, + Store, +} from '@hyperledger/aries-askar-shared' +import { Jwk } from '@hyperledger/aries-askar-shared/build/crypto/Jwk' +import BigNumber from 'bn.js' +import { inject, injectable } from 'tsyringe' + +import { + AskarErrorCode, + isAskarError, + keyDerivationMethodToStoreKeyMethod, + keyTypeSupportedByAskar, + uriFromWalletConfig, +} from '../utils' + +const isError = (error: unknown): error is Error => error instanceof Error + +@injectable() +export class AskarWallet implements Wallet { + private walletConfig?: WalletConfig + private _session?: Session + + private _store?: Store + + private logger: Logger + private fileSystem: FileSystem + + private keyProviderRegistry: KeyProviderRegistry + + public constructor( + @inject(InjectionSymbols.Logger) logger: Logger, + @inject(InjectionSymbols.FileSystem) fileSystem: FileSystem, + keyProviderRegistry: KeyProviderRegistry + ) { + this.logger = logger + this.fileSystem = fileSystem + this.keyProviderRegistry = keyProviderRegistry + } + + public get isProvisioned() { + return this.walletConfig !== undefined + } + + public get isInitialized() { + return this._store !== undefined + } + + public get store() { + if (!this._store) { + throw new AriesFrameworkError( + 'Wallet has not been initialized yet. Make sure to await agent.initialize() before using the agent.' + ) + } + + return this._store + } + + public get session() { + if (!this._session) { + throw new AriesFrameworkError('No Wallet Session is opened') + } + + return this._session + } + + /** + * Dispose method is called when an agent context is disposed. + */ + public async dispose() { + if (this.isInitialized) { + await this.close() + } + } + + /** + * @throws {WalletDuplicateError} if the wallet already exists + * @throws {WalletError} if another error occurs + */ + public async create(walletConfig: WalletConfig): Promise { + await this.createAndOpen(walletConfig) + await this.close() + } + + /** + * @throws {WalletDuplicateError} if the wallet already exists + * @throws {WalletError} if another error occurs + */ + public async createAndOpen(walletConfig: WalletConfig): Promise { + this.logger.debug(`Creating wallet '${walletConfig.id}`) + + const askarWalletConfig = await this.getAskarWalletConfig(walletConfig) + + // Check if database exists + const { path: filePath } = uriFromWalletConfig(walletConfig, this.fileSystem.dataPath) + if (filePath && (await this.fileSystem.exists(filePath))) { + throw new WalletDuplicateError(`Wallet '${walletConfig.id}' already exists.`, { + walletType: 'AskarWallet', + }) + } + try { + this._store = await Store.provision({ + recreate: false, + uri: askarWalletConfig.uri, + profile: askarWalletConfig.profile, + keyMethod: askarWalletConfig.keyMethod, + passKey: askarWalletConfig.passKey, + }) + this.walletConfig = walletConfig + this._session = await this._store.openSession() + } catch (error) { + // FIXME: Askar should throw a Duplicate error code, but is currently returning Encryption + // And if we provide the very same wallet key, it will open it without any error + if ( + isAskarError(error) && + (error.code === AskarErrorCode.Encryption || error.code === AskarErrorCode.Duplicate) + ) { + const errorMessage = `Wallet '${walletConfig.id}' already exists` + this.logger.debug(errorMessage) + + throw new WalletDuplicateError(errorMessage, { + walletType: 'AskarWallet', + cause: error, + }) + } + + const errorMessage = `Error creating wallet '${walletConfig.id}'` + this.logger.error(errorMessage, { + error, + errorMessage: error.message, + }) + + throw new WalletError(errorMessage, { cause: error }) + } + + this.logger.debug(`Successfully created wallet '${walletConfig.id}'`) + } + + /** + * @throws {WalletNotFoundError} if the wallet does not exist + * @throws {WalletError} if another error occurs + */ + public async open(walletConfig: WalletConfig): Promise { + await this._open(walletConfig) + } + + /** + * @throws {WalletNotFoundError} if the wallet does not exist + * @throws {WalletError} if another error occurs + */ + public async rotateKey(walletConfig: WalletConfigRekey): Promise { + if (!walletConfig.rekey) { + throw new WalletError('Wallet rekey undefined!. Please specify the new wallet key') + } + await this._open( + { + id: walletConfig.id, + key: walletConfig.key, + keyDerivationMethod: walletConfig.keyDerivationMethod, + }, + walletConfig.rekey, + walletConfig.rekeyDerivationMethod + ) + } + + /** + * @throws {WalletNotFoundError} if the wallet does not exist + * @throws {WalletError} if another error occurs + */ + private async _open( + walletConfig: WalletConfig, + rekey?: string, + rekeyDerivation?: KeyDerivationMethod + ): Promise { + if (this._store) { + throw new WalletError( + 'Wallet instance already opened. Close the currently opened wallet before re-opening the wallet' + ) + } + + const askarWalletConfig = await this.getAskarWalletConfig(walletConfig) + + try { + this._store = await Store.open({ + uri: askarWalletConfig.uri, + keyMethod: askarWalletConfig.keyMethod, + passKey: askarWalletConfig.passKey, + }) + + if (rekey) { + await this._store.rekey({ + passKey: rekey, + keyMethod: keyDerivationMethodToStoreKeyMethod(rekeyDerivation ?? KeyDerivationMethod.Argon2IMod), + }) + } + this._session = await this._store.openSession() + + this.walletConfig = walletConfig + } catch (error) { + if (isAskarError(error) && error.code === AskarErrorCode.NotFound) { + const errorMessage = `Wallet '${walletConfig.id}' not found` + this.logger.debug(errorMessage) + + throw new WalletNotFoundError(errorMessage, { + walletType: 'AskarWallet', + cause: error, + }) + } else if (isAskarError(error) && error.code === AskarErrorCode.Encryption) { + const errorMessage = `Incorrect key for wallet '${walletConfig.id}'` + this.logger.debug(errorMessage) + throw new WalletInvalidKeyError(errorMessage, { + walletType: 'AskarWallet', + cause: error, + }) + } + throw new WalletError(`Error opening wallet ${walletConfig.id}: ${error.message}`, { cause: error }) + } + + this.logger.debug(`Wallet '${walletConfig.id}' opened with handle '${this._store.handle.handle}'`) + } + + /** + * @throws {WalletNotFoundError} if the wallet does not exist + * @throws {WalletError} if another error occurs + */ + public async delete(): Promise { + if (!this.walletConfig) { + throw new WalletError( + 'Can not delete wallet that does not have wallet config set. Make sure to call create wallet before deleting the wallet' + ) + } + + this.logger.info(`Deleting wallet '${this.walletConfig.id}'`) + if (this._store) { + await this.close() + } + + try { + const { uri } = uriFromWalletConfig(this.walletConfig, this.fileSystem.dataPath) + await Store.remove(uri) + } catch (error) { + const errorMessage = `Error deleting wallet '${this.walletConfig.id}': ${error.message}` + this.logger.error(errorMessage, { + error, + errorMessage: error.message, + }) + + throw new WalletError(errorMessage, { cause: error }) + } + } + + public async export(exportConfig: WalletExportImportConfig) { + if (!this.walletConfig) { + throw new WalletError( + 'Can not export wallet that does not have wallet config set. Make sure to open it before exporting' + ) + } + + const { path: destinationPath, key: exportKey } = exportConfig + + const { path: sourcePath } = uriFromWalletConfig(this.walletConfig, this.fileSystem.dataPath) + if (!sourcePath) { + throw new WalletError('Export is only supported for SQLite backend') + } + + try { + // This method ensures that destination directory is created + const exportedWalletConfig = await this.getAskarWalletConfig({ + ...this.walletConfig, + storage: { type: 'sqlite', path: destinationPath }, + }) + + // Close this wallet before copying + await this.close() + + // Export path already exists + if (await this.fileSystem.exists(destinationPath)) { + throw new WalletExportPathExistsError( + `Unable to create export, wallet export at path '${exportConfig.path}' already exists` + ) + } + + // Copy wallet to the destination path + await this.fileSystem.copyFile(sourcePath, destinationPath) + + // Open exported wallet and rotate its key to the one requested + const exportedWalletStore = await Store.open({ + uri: exportedWalletConfig.uri, + keyMethod: exportedWalletConfig.keyMethod, + passKey: exportedWalletConfig.passKey, + }) + await exportedWalletStore.rekey({ keyMethod: exportedWalletConfig.keyMethod, passKey: exportKey }) + + await exportedWalletStore.close() + + await this._open(this.walletConfig) + } catch (error) { + if (error instanceof WalletExportPathExistsError) throw error + + const errorMessage = `Error exporting wallet '${this.walletConfig.id}': ${error.message}` + this.logger.error(errorMessage, { + error, + errorMessage: error.message, + }) + + throw new WalletError(errorMessage, { cause: error }) + } + } + + public async import(walletConfig: WalletConfig, importConfig: WalletExportImportConfig) { + const { path: sourcePath, key: importKey } = importConfig + const { path: destinationPath } = uriFromWalletConfig(walletConfig, this.fileSystem.dataPath) + + if (!destinationPath) { + throw new WalletError('Import is only supported for SQLite backend') + } + + try { + // This method ensures that destination directory is created + const importWalletConfig = await this.getAskarWalletConfig(walletConfig) + + // Copy wallet to the destination path + await this.fileSystem.copyFile(sourcePath, destinationPath) + + // Open imported wallet and rotate its key to the one requested + const importedWalletStore = await Store.open({ + uri: importWalletConfig.uri, + keyMethod: importWalletConfig.keyMethod, + passKey: importKey, + }) + + await importedWalletStore.rekey({ + keyMethod: importWalletConfig.keyMethod, + passKey: importWalletConfig.passKey, + }) + + await importedWalletStore.close() + } catch (error) { + const errorMessage = `Error importing wallet '${walletConfig.id}': ${error.message}` + this.logger.error(errorMessage, { + error, + errorMessage: error.message, + }) + + throw new WalletError(errorMessage, { cause: error }) + } + } + + /** + * @throws {WalletError} if the wallet is already closed or another error occurs + */ + public async close(): Promise { + this.logger.debug(`Closing wallet ${this.walletConfig?.id}`) + if (!this._store) { + throw new WalletError('Wallet is in invalid state, you are trying to close wallet that has no handle.') + } + + try { + await this.session.close() + await this.store.close() + this._session = undefined + this._store = undefined + } catch (error) { + const errorMessage = `Error closing wallet': ${error.message}` + this.logger.error(errorMessage, { + error, + errorMessage: error.message, + }) + + throw new WalletError(errorMessage, { cause: error }) + } + } + + /** + * Create a key with an optional seed and keyType. + * The keypair is also automatically stored in the wallet afterwards + */ + public async createKey({ seed, privateKey, keyType }: WalletCreateKeyOptions): Promise { + try { + if (seed && privateKey) { + throw new WalletError('Only one of seed and privateKey can be set') + } + + if (seed && !isValidSeed(seed, keyType)) { + throw new WalletError('Invalid seed provided') + } + + if (privateKey && !isValidPrivateKey(privateKey, keyType)) { + throw new WalletError('Invalid private key provided') + } + + if (keyTypeSupportedByAskar(keyType)) { + const algorithm = keyAlgFromString(keyType) + + // Create key + let key: AskarKey | undefined + try { + const key = privateKey + ? AskarKey.fromSecretBytes({ secretKey: privateKey, algorithm }) + : seed + ? AskarKey.fromSeed({ seed, algorithm }) + : AskarKey.generate(algorithm) + + const keyPublicBytes = key.publicBytes + // Store key + await this.session.insertKey({ key, name: TypedArrayEncoder.toBase58(keyPublicBytes) }) + key.handle.free() + return Key.fromPublicKey(keyPublicBytes, keyType) + } catch (error) { + key?.handle.free() + // Handle case where key already exists + if (isAskarError(error, AskarErrorCode.Duplicate)) { + throw new WalletKeyExistsError('Key already exists') + } + + // Otherwise re-throw error + throw error + } + } else { + // Check if there is a signing key provider for the specified key type. + if (this.keyProviderRegistry.hasProviderForKeyType(keyType)) { + const signingKeyProvider = this.keyProviderRegistry.getProviderForKeyType(keyType) + + const keyPair = await signingKeyProvider.createKeyPair({ seed, privateKey }) + await this.storeKeyPair(keyPair) + return Key.fromPublicKeyBase58(keyPair.publicKeyBase58, keyType) + } + throw new WalletError(`Unsupported key type: '${keyType}'`) + } + } catch (error) { + // If already instance of `WalletError`, re-throw + if (error instanceof WalletError) throw error + + if (!isError(error)) { + throw new AriesFrameworkError('Attempted to throw error, but it was not of type Error', { cause: error }) + } + throw new WalletError(`Error creating key with key type '${keyType}': ${error.message}`, { cause: error }) + } + } + + /** + * sign a Buffer with an instance of a Key class + * + * @param data Buffer The data that needs to be signed + * @param key Key The key that is used to sign the data + * + * @returns A signature for the data + */ + public async sign({ data, key }: WalletSignOptions): Promise { + let keyEntry: KeyEntryObject | null | undefined + try { + if (keyTypeSupportedByAskar(key.keyType)) { + if (!TypedArrayEncoder.isTypedArray(data)) { + throw new WalletError(`Currently not supporting signing of multiple messages`) + } + keyEntry = await this.session.fetchKey({ name: key.publicKeyBase58 }) + + if (!keyEntry) { + throw new WalletError('Key entry not found') + } + + const signed = keyEntry.key.signMessage({ message: data as Buffer }) + + keyEntry.key.handle.free() + + return Buffer.from(signed) + } else { + // Check if there is a signing key provider for the specified key type. + if (this.keyProviderRegistry.hasProviderForKeyType(key.keyType)) { + const signingKeyProvider = this.keyProviderRegistry.getProviderForKeyType(key.keyType) + + const keyPair = await this.retrieveKeyPair(key.publicKeyBase58) + const signed = await signingKeyProvider.sign({ + data, + privateKeyBase58: keyPair.privateKeyBase58, + publicKeyBase58: key.publicKeyBase58, + }) + + return signed + } + throw new WalletError(`Unsupported keyType: ${key.keyType}`) + } + } catch (error) { + keyEntry?.key.handle.free() + if (!isError(error)) { + throw new AriesFrameworkError('Attempted to throw error, but it was not of type Error', { cause: error }) + } + throw new WalletError(`Error signing data with verkey ${key.publicKeyBase58}. ${error.message}`, { cause: error }) + } + } + + /** + * Verify the signature with the data and the used key + * + * @param data Buffer The data that has to be confirmed to be signed + * @param key Key The key that was used in the signing process + * @param signature Buffer The signature that was created by the signing process + * + * @returns A boolean whether the signature was created with the supplied data and key + * + * @throws {WalletError} When it could not do the verification + * @throws {WalletError} When an unsupported keytype is used + */ + public async verify({ data, key, signature }: WalletVerifyOptions): Promise { + let askarKey: AskarKey | undefined + try { + if (keyTypeSupportedByAskar(key.keyType)) { + if (!TypedArrayEncoder.isTypedArray(data)) { + throw new WalletError(`Currently not supporting verification of multiple messages`) + } + + const askarKey = AskarKey.fromPublicBytes({ + algorithm: keyAlgFromString(key.keyType), + publicKey: key.publicKey, + }) + const verified = askarKey.verifySignature({ message: data as Buffer, signature }) + askarKey.handle.free() + return verified + } else { + // Check if there is a signing key provider for the specified key type. + if (this.keyProviderRegistry.hasProviderForKeyType(key.keyType)) { + const signingKeyProvider = this.keyProviderRegistry.getProviderForKeyType(key.keyType) + + const signed = await signingKeyProvider.verify({ + data, + signature, + publicKeyBase58: key.publicKeyBase58, + }) + + return signed + } + throw new WalletError(`Unsupported keyType: ${key.keyType}`) + } + } catch (error) { + askarKey?.handle.free() + if (!isError(error)) { + throw new AriesFrameworkError('Attempted to throw error, but it was not of type Error', { cause: error }) + } + throw new WalletError(`Error verifying signature of data signed with verkey ${key.publicKeyBase58}`, { + cause: error, + }) + } + } + + /** + * Pack a message using DIDComm V1 or DIDComm V2 encryption algorithms + * + * @param payload message to send + * @param params packing options specific for envelop version + * @returns JWE Envelope to send + */ + public async pack(payload: Record, params: WalletPackOptions): Promise { + if (params.didCommVersion === DidCommMessageVersion.V1) { + return this.packDidCommV1(payload, params) + } + if (params.didCommVersion === DidCommMessageVersion.V2) { + return this.packDidCommV2(payload, params) + } + throw new AriesFrameworkError(`Unsupported DidComm version: ${params.didCommVersion}`) + } + + /** + * Pack a message using DIDComm V1 encryption algorithms + * + * @param payload message to send + * @param params packing options specific for envelop version + * @returns JWE Envelope to send + */ + private async packDidCommV1(payload: Record, params: WalletPackOptions): Promise { + const { senderKey: senderVerkey, recipientKeys } = params + + let cek: AskarKey | undefined + let senderKey: KeyEntryObject | null | undefined + let senderExchangeKey: AskarKey | undefined + + try { + cek = AskarKey.generate(KeyAlgs.Chacha20C20P) + + const senderKid = senderVerkey?.publicKeyBase58 + senderKey = senderKid ? await this.session.fetchKey({ name: senderKid }) : undefined + if (senderVerkey && !senderKey) { + throw new WalletError(`Unable to pack message. Sender key ${senderVerkey} not found in wallet.`) + } + + senderExchangeKey = senderKey ? senderKey.key.convertkey({ algorithm: KeyAlgs.X25519 }) : undefined + + const recipients: JweRecipient[] = [] + + for (const recipientKey of recipientKeys) { + const recipientKid = recipientKey.publicKeyBase58 + let targetExchangeKey: AskarKey | undefined + try { + targetExchangeKey = AskarKey.fromPublicBytes({ + publicKey: Key.fromPublicKeyBase58(recipientKid, KeyType.Ed25519).publicKey, + algorithm: KeyAlgs.Ed25519, + }).convertkey({ algorithm: KeyAlgs.X25519 }) + + if (senderVerkey && senderExchangeKey && senderKid) { + const encryptedSender = CryptoBox.seal({ + recipientKey: targetExchangeKey, + message: Buffer.from(senderKid), + }) + const nonce = CryptoBox.randomNonce() + const encryptedCek = CryptoBox.cryptoBox({ + recipientKey: targetExchangeKey, + senderKey: senderExchangeKey, + message: cek.secretBytes, + nonce, + }) + + recipients.push( + new JweRecipient({ + encryptedKey: encryptedCek, + header: { + kid: recipientKid, + sender: TypedArrayEncoder.toBase64URL(encryptedSender), + iv: TypedArrayEncoder.toBase64URL(nonce), + }, + }) + ) + } else { + const encryptedCek = CryptoBox.seal({ + recipientKey: targetExchangeKey, + message: cek.secretBytes, + }) + recipients.push( + new JweRecipient({ + encryptedKey: encryptedCek, + header: { + kid: recipientKid, + }, + }) + ) + } + } finally { + targetExchangeKey?.handle.free() + } + } + + const protectedJson = { + enc: 'xchacha20poly1305_ietf', + typ: 'JWM/1.0', + alg: senderVerkey ? 'Authcrypt' : 'Anoncrypt', + recipients: recipients.map((item) => JsonTransformer.toJSON(item)), + } + + const { ciphertext, tag, nonce } = cek.aeadEncrypt({ + message: Buffer.from(JSON.stringify(payload)), + aad: Buffer.from(JsonEncoder.toBase64URL(protectedJson)), + }).parts + + const envelope = new JweEnvelope({ + ciphertext: TypedArrayEncoder.toBase64URL(ciphertext), + iv: TypedArrayEncoder.toBase64URL(nonce), + protected: JsonEncoder.toBase64URL(protectedJson), + tag: TypedArrayEncoder.toBase64URL(tag), + }).toJson() + + return envelope as EncryptedMessage + } finally { + cek?.handle.free() + senderKey?.key.handle.free() + senderExchangeKey?.handle.free() + } + } + + /** + * Pack a message using DIDComm V2 encryption algorithms + * + * @param payload message to send + * @param params packing options specific for envelop version + * @returns JWE Envelope to send + */ + private async packDidCommV2(payload: Record, params: WalletPackOptions): Promise { + if (params.senderKey) { + return this.encryptEcdh1Pu(payload, params.senderKey, params.recipientKeys) + } else { + return this.encryptEcdhEs(payload, params.recipientKeys) + } + } + + private async encryptEcdhEs(payload: Record, recipientKeys: Key[]): Promise { + const wrapId = DidCommV2KeyProtectionAlgs.EcdhEsA256Kw + const wrapAlg = KeyAlgs.AesA256Kw + const encId = DidCommV2EncryptionAlgs.XC20P + const encAlg = KeyAlgs.Chacha20XC20P + const keyAlg = KeyAlgs.X25519 + + let recipientX25519Key: AskarKey | undefined + let cek: AskarKey | undefined + let epk: AskarKey | undefined + + try { + // Generated once for all recipients + // https://identity.foundation/didcomm-messaging/spec/#ecdh-es-key-wrapping-and-common-protected-headers + epk = AskarKey.generate(keyAlg, true) + + const jweBuilder = new JweEnvelopeBuilder({ + typ: DidCommV2Types.EncryptedJson, + enc: encId, + alg: wrapId, + }) + .setEpk(JsonEncoder.toString(epk.jwkPublic)) + .setApv([...recipientKeys].map((recipientKey) => recipientKey.fingerprint)) + + // As per spec we firstly need to encrypt the payload and then use tag as part of the key derivation process + // https://identity.foundation/didcomm-messaging/spec/#ecdh-es-key-wrapping-and-common-protected-headers + cek = AskarKey.generate(encAlg) + + const { ciphertext, tag, nonce } = cek.aeadEncrypt({ + message: Buffer.from(JSON.stringify(payload)), + aad: jweBuilder.aad(), + }).parts + + for (const recipientKey of recipientKeys) { + try { + recipientX25519Key = AskarKey.fromPublicBytes({ + publicKey: recipientKey.publicKey, + algorithm: keyAlg, + }) + + // According to the spec `kid` MUST be a DID URI + // https://identity.foundation/didcomm-messaging/spec/#construction + const recipientDidKey = new DidKey(recipientKey).did + const recipientKid = `${recipientDidKey}#${recipientKey.fingerprint}` + + // Wrap the recipient key using ECDH-ES + // FIXME: according to the spec `tag` must be used for the wrapping but there is not such parameter + // https://identity.foundation/didcomm-messaging/spec/#ecdh-es-key-wrapping-and-common-protected-headers + const encryptedKey = new EcdhEs({ + algId: jweBuilder.alg(), + apu: jweBuilder.apu(), + apv: jweBuilder.apv(), + }).senderWrapKey({ + wrapAlg, + ephemeralKey: epk, + recipientKey: recipientX25519Key, + cek, + }).ciphertext + + jweBuilder.setRecipient( + new JweRecipient({ + encryptedKey, + header: { + kid: recipientKid, + }, + }) + ) + } finally { + recipientX25519Key?.handle.free() + } + } + + const jwe = jweBuilder.setCiphertext(ciphertext).setIv(nonce).setTag(tag).finalize() + return jwe.toJson() as EncryptedMessage + } finally { + epk?.handle.free() + cek?.handle.free() + } + } + + private async encryptEcdh1Pu( + payload: Record, + senderKey: Key, + recipientKeys: Key[] + ): Promise { + const wrapAlg = KeyAlgs.AesA256Kw + const encAlg = KeyAlgs.AesA256CbcHs512 + const keyAlg = keyAlgFromString(senderKey.keyType) + + let senderAskarKey: KeyEntryObject | undefined | null + let recipientAskarKey: AskarKey | undefined + let cek: AskarKey | undefined + let epk: AskarKey | undefined + + try { + // currently, keys are stored in the wallet by their base58 representation + senderAskarKey = await this.session.fetchKey({ name: senderKey.publicKeyBase58 }) + if (!senderAskarKey) { + throw new WalletError(`Unable to pack message. Sender key ${senderKey} not found in wallet.`) + } + + // According to the spec `skid` MUST be a DID URI + const senderDidKey = new DidKey(senderKey).did + const senderKid = `${senderDidKey}#${senderKey.fingerprint}` + + // Generated once for all recipients + // https://identity.foundation/didcomm-messaging/spec/#ecdh-1pu-key-wrapping-and-common-protected-headers + epk = AskarKey.generate(keyAlg, true) + + const jweBuilder = new JweEnvelopeBuilder({ + typ: DidCommV2Types.EncryptedJson, + enc: DidCommV2EncryptionAlgs.A256CbcHs512, + alg: DidCommV2KeyProtectionAlgs.Ecdh1PuA256Kw, + }) + .setSkid(senderKid) + .setEpk(JsonEncoder.toString(epk.jwkPublic)) + .setApu(senderKid) + .setApv([...recipientKeys].map((recipientKey) => recipientKey.fingerprint)) + + // As per spec we firstly need to encrypt the payload and then use tag as part of the key derivation process + // https://identity.foundation/didcomm-messaging/spec/#ecdh-1pu-key-wrapping-and-common-protected-headers + cek = AskarKey.generate(encAlg) + + const message = Buffer.from(JSON.stringify(payload)) + const { ciphertext, tag, nonce } = cek.aeadEncrypt({ + message, + aad: jweBuilder.aad(), + }).parts + + for (const recipientKey of recipientKeys) { + try { + recipientAskarKey = AskarKey.fromPublicBytes({ + publicKey: recipientKey.publicKey, + algorithm: keyAlg, + }) + + // According to the spec `kid` MUST be a DID URI + // https://identity.foundation/didcomm-messaging/spec/#construction + const recipientDidKey = new DidKey(recipientKey).did + const recipientKid = `${recipientDidKey}#${recipientKey.fingerprint}` + + // Wrap the recipient key using ECDH-1PU + const encryptedCek = new Ecdh1PU({ + algId: jweBuilder.alg(), + apv: jweBuilder.apv(), + apu: jweBuilder.apu(), + }).senderWrapKey({ + wrapAlg, + cek, + ephemeralKey: epk, + ccTag: tag, + senderKey: senderAskarKey.key, + recipientKey: recipientAskarKey, + }).ciphertext + + jweBuilder.setRecipient( + new JweRecipient({ + encryptedKey: encryptedCek, + header: { + kid: recipientKid, + }, + }) + ) + } finally { + recipientAskarKey?.handle.free() + } + } + + const jwe = jweBuilder.setCiphertext(ciphertext).setIv(nonce).setTag(tag).finalize() + return jwe.toJson() as EncryptedMessage + } finally { + epk?.handle.free() + cek?.handle.free() + senderAskarKey?.key.handle.free() + } + } + + /** + * Unpacks a JWE Envelope coded using DIDComm V1 of DIDComm V2 encryption algorithms + * + * @param messagePackage JWE Envelope + * + * @returns UnpackedMessageContext with plain text message, sender key, recipient key, and didcomm message version + */ + public async unpack(messagePackage: EncryptedMessage): Promise { + if (isDidCommV1EncryptedEnvelope(messagePackage)) { + return this.unpackDidCommV1(messagePackage) + } else { + return this.unpackDidCommV2(messagePackage) + } + } + + /** + * Unpacks a JWE Envelope coded using DIDComm V1 encryption algorithms + * + * @param messagePackage JWE Envelope + * + * @returns UnpackedMessageContext with plain text message, sender key, recipient key, and didcomm message version + */ + private async unpackDidCommV1(messagePackage: EncryptedMessage): Promise { + // Decode a message using DIDComm v1 encryption. + const protected_ = JsonEncoder.fromBase64(messagePackage.protected) + + const recipients = [] + + for (const recip of protected_.recipients) { + const kid = recip.header.kid + if (!kid) { + throw new WalletError('Blank recipient key') + } + const sender = recip.header.sender ? TypedArrayEncoder.fromBase64(recip.header.sender) : undefined + const iv = recip.header.iv ? TypedArrayEncoder.fromBase64(recip.header.iv) : undefined + if (sender && !iv) { + throw new WalletError('Missing IV') + } else if (!sender && iv) { + throw new WalletError('Unexpected IV') + } + recipients.push({ + kid, + sender, + iv, + encrypted_key: TypedArrayEncoder.fromBase64(recip.encrypted_key), + }) + } + + let payloadKey, senderKey, recipientKey + + for (const recipient of recipients) { + let recipientKeyEntry: KeyEntryObject | null | undefined + let sender_x: AskarKey | undefined + let recip_x: AskarKey | undefined + + try { + recipientKeyEntry = await this.session.fetchKey({ name: recipient.kid }) + if (recipientKeyEntry) { + const recip_x = recipientKeyEntry.key.convertkey({ algorithm: KeyAlgs.X25519 }) + recipientKey = recipient.kid + + if (recipient.sender && recipient.iv) { + senderKey = TypedArrayEncoder.toUtf8String( + CryptoBox.sealOpen({ + recipientKey: recip_x, + ciphertext: recipient.sender, + }) + ) + const sender_x = AskarKey.fromPublicBytes({ + algorithm: KeyAlgs.Ed25519, + publicKey: TypedArrayEncoder.fromBase58(senderKey), + }).convertkey({ algorithm: KeyAlgs.X25519 }) + + payloadKey = CryptoBox.open({ + recipientKey: recip_x, + senderKey: sender_x, + message: recipient.encrypted_key, + nonce: recipient.iv, + }) + } else { + payloadKey = CryptoBox.sealOpen({ ciphertext: recipient.encrypted_key, recipientKey: recip_x }) + } + break + } + } finally { + recipientKeyEntry?.key.handle.free() + sender_x?.handle.free() + recip_x?.handle.free() + } + } + if (!payloadKey) { + throw new WalletError('No corresponding recipient key found') + } + + if (!senderKey && protected_.alg === 'Authcrypt') { + throw new WalletError('Sender public key not provided for Authcrypt') + } + + let cek: AskarKey | undefined + try { + cek = AskarKey.fromSecretBytes({ algorithm: KeyAlgs.Chacha20C20P, secretKey: payloadKey }) + const message = cek.aeadDecrypt({ + ciphertext: TypedArrayEncoder.fromBase64(messagePackage.ciphertext as any), + nonce: TypedArrayEncoder.fromBase64(messagePackage.iv as any), + tag: TypedArrayEncoder.fromBase64(messagePackage.tag as any), + aad: TypedArrayEncoder.fromString(messagePackage.protected), + }) + return { + didCommVersion: DidCommMessageVersion.V1, + plaintextMessage: JsonEncoder.fromBuffer(message), + recipientKey, + senderKey: senderKey ? Key.fromPublicKeyBase58(senderKey, KeyType.Ed25519) : undefined, + } + } finally { + cek?.handle.free() + } + } + + /** + * Unpacks a JWE Envelope coded using DIDComm V2 encryption algorithms + * + * @param messagePackage JWE Envelope + * + * @returns UnpackedMessageContext with plain text message, sender key, recipient key, and didcomm message version + */ + private async unpackDidCommV2(messagePackage: EncryptedMessage): Promise { + const protected_ = JsonEncoder.fromBase64(messagePackage.protected) + if ( + protected_.alg === DidCommV2KeyProtectionAlgs.EcdhEsA128Kw || + protected_.alg === DidCommV2KeyProtectionAlgs.EcdhEsA256Kw + ) { + return this.decryptEcdhEs(messagePackage, protected_) + } + if ( + protected_.alg === DidCommV2KeyProtectionAlgs.Ecdh1PuA128Kw || + protected_.alg === DidCommV2KeyProtectionAlgs.Ecdh1PuA256Kw + ) { + return this.decryptEcdh1Pu(messagePackage, protected_) + } + throw new AriesFrameworkError(`Unsupported JWE algorithm: ${protected_.alg}`) + } + + private async decryptEcdhEs(jwe: EncryptedMessage, protected_: any): Promise { + const { alg, apu, apv, enc } = protected_ + const wrapAlg = alg.slice(8) + + if (![DidCommV2KeyProtectionAlgs.EcdhEsA128Kw, DidCommV2KeyProtectionAlgs.EcdhEsA256Kw].includes(alg)) { + throw new AriesFrameworkError(`Unsupported ECDH-ES algorithm: ${alg}`) + } + if (!['A128GCM', 'A256GCM', 'A128CBC-HS256', 'A256CBC-HS512', 'XC20P'].includes(enc)) { + throw new AriesFrameworkError(`Unsupported ECDH-ES content encryption: ${alg}`) + } + + let recipientAskarKey: KeyEntryObject | null | undefined + let cek: AskarKey | undefined + let epk: AskarKey | undefined + + try { + // Generated once for all recipients + // https://identity.foundation/didcomm-messaging/spec/#ecdh-es-key-wrapping-and-common-protected-headers + epk = AskarKey.fromJwk({ jwk: Jwk.fromString(protected_.epk) }) + + for (const recipient of jwe.recipients) { + try { + // currently, keys are stored in the wallet by their base58 representation + const recipientKey = Key.fromPublicKeyId(recipient.header.kid) + recipientAskarKey = await this.session.fetchKey({ name: recipientKey.publicKeyBase58 }) + if (!recipientAskarKey) continue + + // unwrap the key using ECDH-ES + cek = new EcdhEs({ + algId: Uint8Array.from(Buffer.from(alg)), + apv: Uint8Array.from(Buffer.from(apv ?? [])), + apu: Uint8Array.from(Buffer.from(apu ?? [])), + }).receiverUnwrapKey({ + wrapAlg, + encAlg: enc, + ephemeralKey: epk, + recipientKey: recipientAskarKey.key, + ciphertext: TypedArrayEncoder.fromBase64(recipient.encrypted_key), + // tag: TypedArrayEncoder.fromBase64(jwe.tag), + }) + + // decrypt the message using the key + const plaintext = cek.aeadDecrypt({ + ciphertext: TypedArrayEncoder.fromBase64(jwe.ciphertext), + nonce: TypedArrayEncoder.fromBase64(jwe.iv), + tag: TypedArrayEncoder.fromBase64(jwe.tag), + aad: TypedArrayEncoder.fromString(jwe.protected), + }) + + return { + didCommVersion: DidCommMessageVersion.V2, + plaintextMessage: JsonEncoder.fromBuffer(plaintext), + recipientKey, + } + } finally { + recipientAskarKey?.key.handle.free() + cek?.handle.free() + } + } + } finally { + epk?.handle.free() + } + + throw new AriesFrameworkError('Unable to decrypt message') + } + + private async decryptEcdh1Pu(jwe: EncryptedMessage, protected_: any): Promise { + const { alg, enc, apu, apv, skid } = protected_ + const wrapAlg = alg.slice(9) + + if (![DidCommV2KeyProtectionAlgs.Ecdh1PuA128Kw, DidCommV2KeyProtectionAlgs.Ecdh1PuA256Kw].includes(alg)) { + throw new AriesFrameworkError(`Unsupported ECDH-1PU algorithm: ${alg}`) + } + if (!['A128CBC-HS256', 'A256CBC-HS512'].includes(enc)) { + throw new AriesFrameworkError(`Unsupported ECDH-1PU content encryption: ${alg}`) + } + + let recipientAskarKey: KeyEntryObject | null | undefined + let senderAskarKey: AskarKey | undefined + let cek: AskarKey | undefined + let epk: AskarKey | undefined + + try { + // Validate the `apu` filed is similar to `skid` + // https://identity.foundation/didcomm-messaging/spec/#ecdh-1pu-key-wrapping-and-common-protected-headers + const senderKidApu = TypedArrayEncoder.fromBase64(apu).toString('utf-8') + if (senderKidApu && skid && senderKidApu !== skid) { + throw new AriesFrameworkError('Mismatch between skid and apu') + } + const senderKid = skid ?? senderKidApu + if (!senderKid) { + throw new AriesFrameworkError('Sender key ID not provided') + } + + // FIXME: Properly, we need to properly resolve sender key doing the following steps: + // 1. Extract a DID from DID URL + // 2. Resolve DID Doc for sender + // 3. Get matching the ID + // So it looks like we need to use DidResolver inside of the wallet + const senderKey = Key.fromPublicKeyId(senderKid) + senderAskarKey = AskarKey.fromPublicBytes({ + publicKey: senderKey.publicKey, + algorithm: keyAlgFromString(senderKey.keyType), + }) + + // Generated once for all recipients + epk = AskarKey.fromJwk({ jwk: Jwk.fromString(protected_.epk) }) + + for (const recipient of jwe.recipients) { + try { + // currently, keys are stored in the wallet by their base58 representation + const recipientKey = Key.fromPublicKeyId(recipient.header.kid) + recipientAskarKey = await this.session.fetchKey({ name: recipientKey.publicKeyBase58 }) + if (!recipientAskarKey) continue + + // unwrap the key using ECDH-1PU + cek = new Ecdh1PU({ + apv: Uint8Array.from(Buffer.from(apv)), + apu: Uint8Array.from(Buffer.from(apu)), + algId: Uint8Array.from(Buffer.from(alg)), + }).receiverUnwrapKey({ + wrapAlg: wrapAlg, + encAlg: enc, + ephemeralKey: epk, + senderKey: senderAskarKey, + recipientKey: recipientAskarKey.key, + ccTag: TypedArrayEncoder.fromBase64(jwe.tag), + ciphertext: TypedArrayEncoder.fromBase64(recipient.encrypted_key), + }) + + // decrypt the message using the key + const plaintext = cek.aeadDecrypt({ + ciphertext: TypedArrayEncoder.fromBase64(jwe.ciphertext), + nonce: TypedArrayEncoder.fromBase64(jwe.iv), + tag: TypedArrayEncoder.fromBase64(jwe.tag), + aad: TypedArrayEncoder.fromString(jwe.protected), + }) + + return { + didCommVersion: DidCommMessageVersion.V2, + plaintextMessage: JsonEncoder.fromBuffer(plaintext), + senderKey, + recipientKey, + } + } finally { + cek?.handle.free() + recipientAskarKey?.key?.handle.free() + } + } + } finally { + senderAskarKey?.handle.free() + epk?.handle.free() + } + throw new AriesFrameworkError('Unable to decrypt didcomm v2 envelop') + } + + public async generateNonce(): Promise { + try { + // generate an 80-bit nonce suitable for AnonCreds proofs + const nonce = CryptoBox.randomNonce().slice(0, 10) + return new BigNumber(nonce).toString() + } catch (error) { + if (!isError(error)) { + throw new AriesFrameworkError('Attempted to throw error, but it was not of type Error', { cause: error }) + } + throw new WalletError('Error generating nonce', { cause: error }) + } + } + + public async generateWalletKey() { + try { + return Store.generateRawKey() + } catch (error) { + throw new WalletError('Error generating wallet key', { cause: error }) + } + } + + private async getAskarWalletConfig(walletConfig: WalletConfig) { + const { uri, path } = uriFromWalletConfig(walletConfig, this.fileSystem.dataPath) + + // Make sure path exists before creating the wallet + if (path) { + await this.fileSystem.createDirectory(path) + } + + return { + uri, + profile: walletConfig.id, + // FIXME: Default derivation method should be set somewhere in either agent config or some constants + keyMethod: keyDerivationMethodToStoreKeyMethod( + walletConfig.keyDerivationMethod ?? KeyDerivationMethod.Argon2IMod + ), + passKey: walletConfig.key, + } + } + + private async retrieveKeyPair(publicKeyBase58: string): Promise { + try { + const entryObject = await this.session.fetch({ category: 'KeyPairRecord', name: `key-${publicKeyBase58}` }) + + if (entryObject?.value) { + return JsonEncoder.fromString(entryObject?.value as string) as KeyPair + } else { + throw new WalletError(`No content found for record with public key: ${publicKeyBase58}`) + } + } catch (error) { + throw new WalletError('Error retrieving KeyPair record', { cause: error }) + } + } + + private async storeKeyPair(keyPair: KeyPair): Promise { + try { + await this.session.insert({ + category: 'KeyPairRecord', + name: `key-${keyPair.publicKeyBase58}`, + value: JSON.stringify(keyPair), + tags: { + keyType: keyPair.keyType, + }, + }) + } catch (error) { + if (isAskarError(error, AskarErrorCode.Duplicate)) { + throw new WalletKeyExistsError('Key already exists') + } + throw new WalletError('Error saving KeyPair record', { cause: error }) + } + } +} diff --git a/packages/askar/src/wallet/AskarWalletPostgresStorageConfig.ts b/packages/askar/src/wallet/AskarWalletPostgresStorageConfig.ts new file mode 100644 index 0000000000..2ca48f0c56 --- /dev/null +++ b/packages/askar/src/wallet/AskarWalletPostgresStorageConfig.ts @@ -0,0 +1,22 @@ +import type { WalletStorageConfig } from '@aries-framework/core' + +export interface AskarWalletPostgresConfig { + host: string + connectTimeout?: number + idleTimeout?: number + maxConnections?: number + minConnections?: number +} + +export interface AskarWalletPostgresCredentials { + account: string + password: string + adminAccount?: string + adminPassword?: string +} + +export interface AskarWalletPostgresStorageConfig extends WalletStorageConfig { + type: 'postgres' + config: AskarWalletPostgresConfig + credentials: AskarWalletPostgresCredentials +} diff --git a/packages/askar/src/wallet/__tests__/AskarWallet.test.ts b/packages/askar/src/wallet/__tests__/AskarWallet.test.ts new file mode 100644 index 0000000000..446ec79cae --- /dev/null +++ b/packages/askar/src/wallet/__tests__/AskarWallet.test.ts @@ -0,0 +1,294 @@ +import type { + KeyProvider, + WalletConfig, + CreateKeyPairOptions, + KeyPair, + SignOptions, + VerifyOptions, +} from '@aries-framework/core' + +import { + WalletKeyExistsError, + Key, + WalletError, + WalletDuplicateError, + WalletNotFoundError, + WalletInvalidKeyError, + KeyType, + KeyProviderRegistry, + TypedArrayEncoder, + KeyDerivationMethod, + Buffer, +} from '@aries-framework/core' +import { Store } from '@hyperledger/aries-askar-shared' + +import { describeRunInNodeVersion } from '../../../../../tests/runInVersion' +import { encodeToBase58 } from '../../../../core/src/utils/base58' +import { agentDependencies } from '../../../../core/tests/helpers' +import testLogger from '../../../../core/tests/logger' +import { AskarWallet } from '../AskarWallet' + +// use raw key derivation method to speed up wallet creating / opening / closing between tests +const walletConfig: WalletConfig = { + id: 'Wallet: AskarWalletTest', + // generated using indy.generateWalletKey + key: 'CwNJroKHTSSj3XvE7ZAnuKiTn2C4QkFvxEqfm5rzhNrb', + keyDerivationMethod: KeyDerivationMethod.Raw, +} + +describeRunInNodeVersion([18], 'AskarWallet basic operations', () => { + let askarWallet: AskarWallet + + const seed = TypedArrayEncoder.fromString('sample-seed-min-of-32-bytes-long') + const privateKey = TypedArrayEncoder.fromString('2103de41b4ae37e8e28586d84a342b67') + const message = TypedArrayEncoder.fromString('sample-message') + + beforeEach(async () => { + askarWallet = new AskarWallet(testLogger, new agentDependencies.FileSystem(), new KeyProviderRegistry([])) + await askarWallet.createAndOpen(walletConfig) + }) + + afterEach(async () => { + await askarWallet.delete() + }) + + test('Get the wallet store', () => { + expect(askarWallet.store).toEqual(expect.any(Store)) + }) + + test('Generate Nonce', async () => { + const nonce = await askarWallet.generateNonce() + + expect(nonce).toMatch(/[0-9]+/) + }) + + test('Create ed25519 keypair from seed', async () => { + const key = await askarWallet.createKey({ + seed, + keyType: KeyType.Ed25519, + }) + + expect(key).toMatchObject({ + keyType: KeyType.Ed25519, + }) + }) + + test('Create ed25519 keypair from private key', async () => { + const key = await askarWallet.createKey({ + privateKey, + keyType: KeyType.Ed25519, + }) + + expect(key).toMatchObject({ + keyType: KeyType.Ed25519, + }) + }) + + test('Attempt to create ed25519 keypair from both seed and private key', async () => { + await expect( + askarWallet.createKey({ + privateKey, + seed, + keyType: KeyType.Ed25519, + }) + ).rejects.toThrowError() + }) + + test('Create x25519 keypair', async () => { + await expect(askarWallet.createKey({ seed, keyType: KeyType.X25519 })).resolves.toMatchObject({ + keyType: KeyType.X25519, + }) + }) + + test('Create P-256 keypair', async () => { + await expect( + askarWallet.createKey({ seed: Buffer.concat([seed, seed]), keyType: KeyType.P256 }) + ).resolves.toMatchObject({ + keyType: KeyType.P256, + }) + }) + + test('throws WalletKeyExistsError when a key already exists', async () => { + const privateKey = TypedArrayEncoder.fromString('2103de41b4ae37e8e28586d84a342b68') + await expect(askarWallet.createKey({ privateKey, keyType: KeyType.Ed25519 })).resolves.toEqual(expect.any(Key)) + await expect(askarWallet.createKey({ privateKey, keyType: KeyType.Ed25519 })).rejects.toThrowError( + WalletKeyExistsError + ) + }) + + test('Fail to create a P384 keypair', async () => { + await expect(askarWallet.createKey({ seed, keyType: KeyType.P384 })).rejects.toThrowError(WalletError) + }) + + test('Create a signature with a ed25519 keypair', async () => { + const ed25519Key = await askarWallet.createKey({ keyType: KeyType.Ed25519 }) + const signature = await askarWallet.sign({ + data: message, + key: ed25519Key, + }) + expect(signature.length).toStrictEqual(64) + }) + + test('Verify a signed message with a ed25519 publicKey', async () => { + const ed25519Key = await askarWallet.createKey({ keyType: KeyType.Ed25519 }) + const signature = await askarWallet.sign({ + data: message, + key: ed25519Key, + }) + await expect(askarWallet.verify({ key: ed25519Key, data: message, signature })).resolves.toStrictEqual(true) + }) +}) + +describe.skip('Currently, all KeyTypes are supported by Askar natively', () => { + describe('AskarWallet with custom signing provider', () => { + let askarWallet: AskarWallet + + const seed = TypedArrayEncoder.fromString('sample-seed') + const message = TypedArrayEncoder.fromString('sample-message') + + class DummySigningProvider implements KeyProvider { + public keyType: KeyType = KeyType.Bls12381g1g2 + + public async createKeyPair(options: CreateKeyPairOptions): Promise { + return { + publicKeyBase58: encodeToBase58(Buffer.from(options.seed || TypedArrayEncoder.fromString('publicKeyBase58'))), + privateKeyBase58: 'privateKeyBase58', + keyType: KeyType.Bls12381g1g2, + } + } + + // eslint-disable-next-line @typescript-eslint/no-unused-vars + public async sign(options: SignOptions): Promise { + return new Buffer('signed') + } + + // eslint-disable-next-line @typescript-eslint/no-unused-vars + public async verify(options: VerifyOptions): Promise { + return true + } + } + + beforeEach(async () => { + askarWallet = new AskarWallet( + testLogger, + new agentDependencies.FileSystem(), + new KeyProviderRegistry([new DummySigningProvider()]) + ) + await askarWallet.createAndOpen(walletConfig) + }) + + afterEach(async () => { + await askarWallet.delete() + }) + + test('Create custom keypair and use it for signing', async () => { + const key = await askarWallet.createKey({ seed, keyType: KeyType.Bls12381g1g2 }) + expect(key.keyType).toBe(KeyType.Bls12381g1g2) + expect(key.publicKeyBase58).toBe(encodeToBase58(Buffer.from(seed))) + + const signature = await askarWallet.sign({ + data: message, + key, + }) + + expect(signature).toBeInstanceOf(Buffer) + }) + + test('Create custom keypair and use it for verifying', async () => { + const key = await askarWallet.createKey({ seed, keyType: KeyType.Bls12381g1g2 }) + expect(key.keyType).toBe(KeyType.Bls12381g1g2) + expect(key.publicKeyBase58).toBe(encodeToBase58(Buffer.from(seed))) + + const signature = await askarWallet.verify({ + data: message, + signature: new Buffer('signature'), + key, + }) + + expect(signature).toBeTruthy() + }) + + test('Attempt to create the same custom keypair twice', async () => { + await askarWallet.createKey({ seed: TypedArrayEncoder.fromString('keybase58'), keyType: KeyType.Bls12381g1g2 }) + + await expect( + askarWallet.createKey({ seed: TypedArrayEncoder.fromString('keybase58'), keyType: KeyType.Bls12381g1g2 }) + ).rejects.toThrow(WalletError) + }) + }) +}) + +describeRunInNodeVersion([18], 'AskarWallet management', () => { + let askarWallet: AskarWallet + + afterEach(async () => { + if (askarWallet) { + await askarWallet.delete() + } + }) + + test('Create', async () => { + askarWallet = new AskarWallet(testLogger, new agentDependencies.FileSystem(), new KeyProviderRegistry([])) + + const initialKey = Store.generateRawKey() + const anotherKey = Store.generateRawKey() + + // Create and open wallet + await askarWallet.createAndOpen({ ...walletConfig, id: 'AskarWallet Create', key: initialKey }) + + // Close and try to re-create it + await askarWallet.close() + await expect( + askarWallet.createAndOpen({ ...walletConfig, id: 'AskarWallet Create', key: anotherKey }) + ).rejects.toThrowError(WalletDuplicateError) + }) + + test('Open', async () => { + askarWallet = new AskarWallet(testLogger, new agentDependencies.FileSystem(), new KeyProviderRegistry([])) + + const initialKey = Store.generateRawKey() + const wrongKey = Store.generateRawKey() + + // Create and open wallet + await askarWallet.createAndOpen({ ...walletConfig, id: 'AskarWallet Open', key: initialKey }) + + // Close and try to re-opening it with a wrong key + await askarWallet.close() + await expect(askarWallet.open({ ...walletConfig, id: 'AskarWallet Open', key: wrongKey })).rejects.toThrowError( + WalletInvalidKeyError + ) + + // Try to open a non existent wallet + await expect( + askarWallet.open({ ...walletConfig, id: 'AskarWallet Open - Non existent', key: initialKey }) + ).rejects.toThrowError(WalletNotFoundError) + }) + + test('Rotate key', async () => { + askarWallet = new AskarWallet(testLogger, new agentDependencies.FileSystem(), new KeyProviderRegistry([])) + + const initialKey = Store.generateRawKey() + await askarWallet.createAndOpen({ ...walletConfig, id: 'AskarWallet Key Rotation', key: initialKey }) + + await askarWallet.close() + + const newKey = Store.generateRawKey() + await askarWallet.rotateKey({ + ...walletConfig, + id: 'AskarWallet Key Rotation', + key: initialKey, + rekey: newKey, + rekeyDerivationMethod: KeyDerivationMethod.Raw, + }) + + await askarWallet.close() + + await expect( + askarWallet.open({ ...walletConfig, id: 'AskarWallet Key Rotation', key: initialKey }) + ).rejects.toThrowError(WalletInvalidKeyError) + + await askarWallet.open({ ...walletConfig, id: 'AskarWallet Key Rotation', key: newKey }) + + await askarWallet.close() + }) +}) diff --git a/packages/askar/src/wallet/__tests__/packing.test.ts b/packages/askar/src/wallet/__tests__/packing.test.ts new file mode 100644 index 0000000000..7c6bc0606c --- /dev/null +++ b/packages/askar/src/wallet/__tests__/packing.test.ts @@ -0,0 +1,104 @@ +import type { WalletConfig, WalletPackOptions } from '@aries-framework/core' + +import { + BasicMessage, + DidCommMessageVersion, + JsonTransformer, + KeyDerivationMethod, + KeyProviderRegistry, + KeyType, +} from '@aries-framework/core' + +import { describeRunInNodeVersion } from '../../../../../tests/runInVersion' +import { agentDependencies } from '../../../../core/tests/helpers' +import testLogger from '../../../../core/tests/logger' +import { AskarWallet } from '../AskarWallet' + +// use raw key derivation method to speed up wallet creating / opening / closing between tests +const walletConfig: WalletConfig = { + id: 'Askar Wallet Packing', + // generated using indy.generateWalletKey + key: 'CwNJroKHTSSj3XvE7ZAnuKiTn2C4QkFvxEqfm5rzhNrb', + keyDerivationMethod: KeyDerivationMethod.Raw, +} + +const message = new BasicMessage({ content: 'hello' }) + +describeRunInNodeVersion([18], 'askarWallet packing', () => { + let askarWallet: AskarWallet + + beforeEach(async () => { + askarWallet = new AskarWallet(testLogger, new agentDependencies.FileSystem(), new KeyProviderRegistry([])) + await askarWallet.createAndOpen(walletConfig) + }) + + afterEach(async () => { + await askarWallet.delete() + }) + + describe('DIDComm V1 packing and unpacking', () => { + test('Authcrypt', async () => { + // Create both sender and recipient keys + const senderKey = await askarWallet.createKey({ keyType: KeyType.Ed25519 }) + const recipientKey = await askarWallet.createKey({ keyType: KeyType.Ed25519 }) + + const params: WalletPackOptions = { + didCommVersion: DidCommMessageVersion.V1, + recipientKeys: [recipientKey], + senderKey: senderKey, + } + + const encryptedMessage = await askarWallet.pack(message.toJSON(), params) + const plainTextMessage = await askarWallet.unpack(encryptedMessage) + expect(JsonTransformer.fromJSON(plainTextMessage.plaintextMessage, BasicMessage)).toEqual(message) + }) + + test('Anoncrypt', async () => { + // Create recipient keys only + const recipientKey = await askarWallet.createKey({ keyType: KeyType.Ed25519 }) + + const params: WalletPackOptions = { + didCommVersion: DidCommMessageVersion.V1, + recipientKeys: [recipientKey], + senderKey: null, + } + + const encryptedMessage = await askarWallet.pack(message.toJSON(), params) + const plainTextMessage = await askarWallet.unpack(encryptedMessage) + expect(JsonTransformer.fromJSON(plainTextMessage.plaintextMessage, BasicMessage)).toEqual(message) + }) + }) + + describe('DIDComm V2 packing and unpacking', () => { + test('Authcrypt', async () => { + // Create both sender and recipient keys + const senderKey = await askarWallet.createKey({ keyType: KeyType.X25519 }) + const recipientKey = await askarWallet.createKey({ keyType: KeyType.X25519 }) + + const params: WalletPackOptions = { + didCommVersion: DidCommMessageVersion.V2, + recipientKeys: [recipientKey], + senderKey: senderKey, + } + + const encryptedMessage = await askarWallet.pack(message.toJSON(), params) + const plainTextMessage = await askarWallet.unpack(encryptedMessage) + expect(JsonTransformer.fromJSON(plainTextMessage.plaintextMessage, BasicMessage)).toEqual(message) + }) + + test('Anoncrypt', async () => { + // Create recipient keys only + const recipientKey = await askarWallet.createKey({ keyType: KeyType.X25519 }) + + const params: WalletPackOptions = { + didCommVersion: DidCommMessageVersion.V2, + recipientKeys: [recipientKey], + senderKey: null, + } + + const encryptedMessage = await askarWallet.pack(message.toJSON(), params) + const plainTextMessage = await askarWallet.unpack(encryptedMessage) + expect(JsonTransformer.fromJSON(plainTextMessage.plaintextMessage, BasicMessage)).toEqual(message) + }) + }) +}) diff --git a/packages/askar/src/wallet/index.ts b/packages/askar/src/wallet/index.ts new file mode 100644 index 0000000000..8d569fdf4c --- /dev/null +++ b/packages/askar/src/wallet/index.ts @@ -0,0 +1,2 @@ +export { AskarWallet } from './AskarWallet' +export * from './AskarWalletPostgresStorageConfig' diff --git a/packages/askar/tests/askar-postgres.e2e.test.ts b/packages/askar/tests/askar-postgres.e2e.test.ts new file mode 100644 index 0000000000..e7577a85cd --- /dev/null +++ b/packages/askar/tests/askar-postgres.e2e.test.ts @@ -0,0 +1,103 @@ +/* eslint-disable @typescript-eslint/no-non-null-assertion */ +import type { SubjectMessage } from '../../../tests/transport/SubjectInboundTransport' +import type { AskarWalletPostgresStorageConfig } from '../src/wallet' +import type { ConnectionRecord } from '@aries-framework/core' + +import { Agent, HandshakeProtocol } from '@aries-framework/core' +import { Subject } from 'rxjs' + +import { describeRunInNodeVersion } from '../../../tests/runInVersion' +import { SubjectInboundTransport } from '../../../tests/transport/SubjectInboundTransport' +import { SubjectOutboundTransport } from '../../../tests/transport/SubjectOutboundTransport' +import { waitForBasicMessage } from '../../core/tests/helpers' + +import { getPostgresAgentOptions } from './helpers' + +const storageConfig: AskarWalletPostgresStorageConfig = { + type: 'postgres', + config: { + host: 'localhost:5432', + }, + credentials: { + account: 'postgres', + password: 'postgres', + }, +} + +const alicePostgresAgentOptions = getPostgresAgentOptions('AgentsAlice', storageConfig, { + endpoints: ['rxjs:alice'], +}) +const bobPostgresAgentOptions = getPostgresAgentOptions('AgentsBob', storageConfig, { + endpoints: ['rxjs:bob'], +}) + +// FIXME: Re-include in tests when Askar NodeJS wrapper performance is improved +describeRunInNodeVersion([18], 'Askar Postgres agents', () => { + let aliceAgent: Agent + let bobAgent: Agent + let aliceConnection: ConnectionRecord + let bobConnection: ConnectionRecord + + afterAll(async () => { + if (bobAgent) { + await bobAgent.shutdown() + await bobAgent.wallet.delete() + } + + if (aliceAgent) { + await aliceAgent.shutdown() + await aliceAgent.wallet.delete() + } + }) + + test('make a connection between postgres agents', async () => { + const aliceMessages = new Subject() + const bobMessages = new Subject() + + const subjectMap = { + 'rxjs:alice': aliceMessages, + 'rxjs:bob': bobMessages, + } + + aliceAgent = new Agent(alicePostgresAgentOptions) + aliceAgent.registerInboundTransport(new SubjectInboundTransport(aliceMessages)) + aliceAgent.registerOutboundTransport(new SubjectOutboundTransport(subjectMap)) + await aliceAgent.initialize() + + bobAgent = new Agent(bobPostgresAgentOptions) + bobAgent.registerInboundTransport(new SubjectInboundTransport(bobMessages)) + bobAgent.registerOutboundTransport(new SubjectOutboundTransport(subjectMap)) + await bobAgent.initialize() + + const aliceBobOutOfBandRecord = await aliceAgent.oob.createInvitation({ + handshakeProtocols: [HandshakeProtocol.Connections], + }) + + const { connectionRecord: bobConnectionAtBobAlice } = await bobAgent.oob.receiveInvitation( + aliceBobOutOfBandRecord.getOutOfBandInvitation() + ) + bobConnection = await bobAgent.connections.returnWhenIsConnected(bobConnectionAtBobAlice!.id) + + const [aliceConnectionAtAliceBob] = await aliceAgent.connections.findAllByOutOfBandId(aliceBobOutOfBandRecord.id) + aliceConnection = await aliceAgent.connections.returnWhenIsConnected(aliceConnectionAtAliceBob!.id) + }) + + test('send a message to connection', async () => { + const message = 'hello, world' + await aliceAgent.basicMessages.sendMessage(aliceConnection.id, message) + + const basicMessage = await waitForBasicMessage(bobAgent, { + content: message, + }) + + expect(basicMessage.content).toBe(message) + }) + + test('can shutdown and re-initialize the same postgres agent', async () => { + expect(aliceAgent.isInitialized).toBe(true) + await aliceAgent.shutdown() + expect(aliceAgent.isInitialized).toBe(false) + await aliceAgent.initialize() + expect(aliceAgent.isInitialized).toBe(true) + }) +}) diff --git a/packages/askar/tests/askar-sqlite.e2e.test.ts b/packages/askar/tests/askar-sqlite.e2e.test.ts new file mode 100644 index 0000000000..41280f0684 --- /dev/null +++ b/packages/askar/tests/askar-sqlite.e2e.test.ts @@ -0,0 +1,192 @@ +/* eslint-disable @typescript-eslint/no-non-null-assertion */ +import { + Agent, + BasicMessageRecord, + BasicMessageRepository, + BasicMessageRole, + KeyDerivationMethod, + TypedArrayEncoder, + utils, + WalletDuplicateError, + WalletInvalidKeyError, + WalletNotFoundError, +} from '@aries-framework/core' +import { Store } from '@hyperledger/aries-askar-shared' +import { tmpdir } from 'os' +import path from 'path' + +import { describeRunInNodeVersion } from '../../../tests/runInVersion' + +import { getSqliteAgentOptions } from './helpers' + +const aliceAgentOptions = getSqliteAgentOptions('AgentsAlice') +const bobAgentOptions = getSqliteAgentOptions('AgentsBob') + +// FIXME: Re-include in tests when Askar NodeJS wrapper performance is improved +describeRunInNodeVersion([18], 'Askar SQLite agents', () => { + let aliceAgent: Agent + let bobAgent: Agent + + beforeEach(async () => { + aliceAgent = new Agent(aliceAgentOptions) + bobAgent = new Agent(bobAgentOptions) + }) + + afterEach(async () => { + await aliceAgent.shutdown() + await bobAgent.shutdown() + + if (aliceAgent.wallet.isProvisioned) { + await aliceAgent.wallet.delete() + } + if (bobAgent.wallet.isProvisioned) { + await bobAgent.wallet.delete() + } + }) + + test('open, create and open wallet with different wallet key that it is in agent config', async () => { + const walletConfig = { + id: 'mywallet', + key: 'mysecretwalletkey-0', + } + + try { + await aliceAgent.wallet.open(walletConfig) + } catch (error) { + if (error instanceof WalletNotFoundError) { + await aliceAgent.wallet.create(walletConfig) + await aliceAgent.wallet.open(walletConfig) + } + } + + await aliceAgent.initialize() + + expect(aliceAgent.isInitialized).toBe(true) + }) + + test('when opening non-existing wallet throw WalletNotFoundError', async () => { + const walletConfig = { + id: 'mywallet', + key: 'mysecretwalletkey-1', + } + + await expect(aliceAgent.wallet.open(walletConfig)).rejects.toThrowError(WalletNotFoundError) + }) + + test('when create wallet and shutdown, wallet is closed', async () => { + const walletConfig = { + id: 'mywallet', + key: 'mysecretwalletkey-2', + } + + await aliceAgent.wallet.create(walletConfig) + + await aliceAgent.shutdown() + + await expect(aliceAgent.wallet.open(walletConfig)).resolves.toBeUndefined() + }) + + test('create wallet with custom key derivation method', async () => { + const walletConfig = { + id: 'mywallet', + key: Store.generateRawKey(TypedArrayEncoder.fromString('mysecretwalletkey')), + keyDerivationMethod: KeyDerivationMethod.Raw, + } + + await aliceAgent.wallet.createAndOpen(walletConfig) + + expect(aliceAgent.wallet.isInitialized).toBe(true) + }) + + test('when exporting and importing a wallet, content is copied', async () => { + await bobAgent.initialize() + const bobBasicMessageRepository = bobAgent.dependencyManager.resolve(BasicMessageRepository) + + const basicMessageRecord = new BasicMessageRecord({ + id: 'some-id', + connectionId: 'connId', + content: 'hello', + role: BasicMessageRole.Receiver, + sentTime: 'sentIt', + }) + + // Save in wallet + await bobBasicMessageRepository.save(bobAgent.context, basicMessageRecord) + + if (!bobAgent.config.walletConfig) { + throw new Error('No wallet config on bobAgent') + } + + const backupKey = 'someBackupKey' + const backupWalletName = `backup-${utils.uuid()}` + const backupPath = path.join(tmpdir(), backupWalletName) + + // Create backup and delete wallet + await bobAgent.wallet.export({ path: backupPath, key: backupKey }) + await bobAgent.wallet.delete() + + // Initialize the wallet again and assert record does not exist + // This should create a new wallet + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + await bobAgent.wallet.initialize(bobAgentOptions.config.walletConfig!) + expect(await bobBasicMessageRepository.findById(bobAgent.context, basicMessageRecord.id)).toBeNull() + await bobAgent.wallet.delete() + + // Import backup with different wallet id and initialize + await bobAgent.wallet.import({ id: backupWalletName, key: backupWalletName }, { path: backupPath, key: backupKey }) + await bobAgent.wallet.initialize({ id: backupWalletName, key: backupWalletName }) + + // Expect same basic message record to exist in new wallet + expect(await bobBasicMessageRepository.getById(bobAgent.context, basicMessageRecord.id)).toMatchObject({ + id: basicMessageRecord.id, + connectionId: basicMessageRecord.connectionId, + content: basicMessageRecord.content, + createdAt: basicMessageRecord.createdAt, + updatedAt: basicMessageRecord.updatedAt, + type: basicMessageRecord.type, + }) + }) + + test('changing wallet key', async () => { + const walletConfig = { + id: 'mywallet', + key: 'mysecretwalletkey', + } + + await aliceAgent.wallet.createAndOpen(walletConfig) + await aliceAgent.initialize() + + //Close agent + const walletConfigRekey = { + id: 'mywallet', + key: 'mysecretwalletkey', + rekey: '123', + } + + await aliceAgent.shutdown() + await aliceAgent.wallet.rotateKey(walletConfigRekey) + await aliceAgent.initialize() + + expect(aliceAgent.isInitialized).toBe(true) + }) + + test('when creating already existing wallet throw WalletDuplicateError', async () => { + const walletConfig = { + id: 'mywallet', + key: 'mysecretwalletkey-2', + } + + await aliceAgent.wallet.create(walletConfig) + await expect(aliceAgent.wallet.create(walletConfig)).rejects.toThrowError(WalletDuplicateError) + }) + + test('when opening wallet with invalid key throw WalletInvalidKeyError', async () => { + const walletConfig = { + id: 'mywallet', + key: 'mysecretwalletkey-3', + } + + await aliceAgent.wallet.create(walletConfig) + await expect(aliceAgent.wallet.open({ ...walletConfig, key: 'abcd' })).rejects.toThrowError(WalletInvalidKeyError) + }) +}) diff --git a/packages/askar/tests/helpers.ts b/packages/askar/tests/helpers.ts new file mode 100644 index 0000000000..b182c66abc --- /dev/null +++ b/packages/askar/tests/helpers.ts @@ -0,0 +1,73 @@ +import type { AskarWalletPostgresStorageConfig } from '../src/wallet' +import type { InitConfig } from '@aries-framework/core' + +import { ConnectionsModule, LogLevel, utils } from '@aries-framework/core' +import { ariesAskar } from '@hyperledger/aries-askar-nodejs' +import path from 'path' + +import { TestLogger } from '../../core/tests/logger' +import { agentDependencies } from '../../node/src' +import { AskarModule } from '../src/AskarModule' +import { AskarModuleConfig } from '../src/AskarModuleConfig' + +export const askarModuleConfig = new AskarModuleConfig({ ariesAskar }) + +export const genesisPath = process.env.GENESIS_TXN_PATH + ? path.resolve(process.env.GENESIS_TXN_PATH) + : path.join(__dirname, '../../../../network/genesis/local-genesis.txn') + +export const publicDidSeed = process.env.TEST_AGENT_PUBLIC_DID_SEED ?? '000000000000000000000000Trustee9' + +export function getPostgresAgentOptions( + name: string, + storageConfig: AskarWalletPostgresStorageConfig, + extraConfig: Partial = {} +) { + const random = utils.uuid().slice(0, 4) + const config: InitConfig = { + label: `PostgresAgent: ${name} - ${random}`, + walletConfig: { + id: `PostgresWallet${name}${random}`, + key: `Key${name}`, + storage: storageConfig, + }, + autoUpdateStorageOnStartup: false, + logger: new TestLogger(LogLevel.off, name), + ...extraConfig, + } + return { + config, + dependencies: agentDependencies, + modules: { + askar: new AskarModule(askarModuleConfig), + connections: new ConnectionsModule({ + autoAcceptConnections: true, + }), + }, + } as const +} + +export function getSqliteAgentOptions(name: string, extraConfig: Partial = {}) { + const random = utils.uuid().slice(0, 4) + const config: InitConfig = { + label: `SQLiteAgent: ${name} - ${random}`, + walletConfig: { + id: `SQLiteWallet${name} - ${random}`, + key: `Key${name}`, + storage: { type: 'sqlite' }, + }, + autoUpdateStorageOnStartup: false, + logger: new TestLogger(LogLevel.off, name), + ...extraConfig, + } + return { + config, + dependencies: agentDependencies, + modules: { + askar: new AskarModule(askarModuleConfig), + connections: new ConnectionsModule({ + autoAcceptConnections: true, + }), + }, + } as const +} diff --git a/packages/askar/tests/setup.ts b/packages/askar/tests/setup.ts new file mode 100644 index 0000000000..97eeec3b92 --- /dev/null +++ b/packages/askar/tests/setup.ts @@ -0,0 +1,4 @@ +import 'reflect-metadata' +import '@hyperledger/aries-askar-nodejs' + +jest.setTimeout(180000) diff --git a/packages/askar/tsconfig.build.json b/packages/askar/tsconfig.build.json new file mode 100644 index 0000000000..2b75d0adab --- /dev/null +++ b/packages/askar/tsconfig.build.json @@ -0,0 +1,7 @@ +{ + "extends": "../../tsconfig.build.json", + "compilerOptions": { + "outDir": "./build" + }, + "include": ["src/**/*"] +} diff --git a/packages/askar/tsconfig.json b/packages/askar/tsconfig.json new file mode 100644 index 0000000000..46efe6f721 --- /dev/null +++ b/packages/askar/tsconfig.json @@ -0,0 +1,6 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "types": ["jest"] + } +} diff --git a/packages/bbs-signatures/jest.config.ts b/packages/bbs-signatures/jest.config.ts index 55c67d70a6..8641cf4d67 100644 --- a/packages/bbs-signatures/jest.config.ts +++ b/packages/bbs-signatures/jest.config.ts @@ -6,7 +6,7 @@ import packageJson from './package.json' const config: Config.InitialOptions = { ...base, - name: packageJson.name, + displayName: packageJson.name, setupFilesAfterEnv: ['./tests/setup.ts'], } diff --git a/packages/bbs-signatures/package.json b/packages/bbs-signatures/package.json index bdf141b5c1..0e490775ce 100644 --- a/packages/bbs-signatures/package.json +++ b/packages/bbs-signatures/package.json @@ -2,8 +2,7 @@ "name": "@aries-framework/bbs-signatures", "main": "build/index", "types": "build/index", - "version": "0.3.0", - "private": true, + "version": "0.3.3", "files": [ "build" ], @@ -19,7 +18,7 @@ }, "scripts": { "build": "yarn run clean && yarn run compile", - "clean": "rimraf -rf ./build", + "clean": "rimraf ./build", "compile": "tsc -p tsconfig.build.json", "prepublishOnly": "yarn run build", "test": "jest" @@ -37,8 +36,8 @@ "@aries-framework/core": "*", "@aries-framework/node": "*", "reflect-metadata": "^0.1.13", - "rimraf": "~3.0.2", - "typescript": "~4.3.0" + "rimraf": "^4.4.0", + "typescript": "~4.9.5" }, "peerDependenciesMeta": { "@animo-id/react-native-bbs-signatures": { diff --git a/packages/bbs-signatures/src/Bls12381g2KeyProvider.ts b/packages/bbs-signatures/src/Bls12381g2KeyProvider.ts index 67574ac64e..3de02ed426 100644 --- a/packages/bbs-signatures/src/Bls12381g2KeyProvider.ts +++ b/packages/bbs-signatures/src/Bls12381g2KeyProvider.ts @@ -15,11 +15,12 @@ export class Bls12381g2KeyProvider implements KeyProvider { * * @throws {KeyProviderError} When a key could not be created */ - public async createKeyPair({ seed }: CreateKeyPairOptions): Promise { - // Generate bytes from the seed as required by the bbs-signatures libraries - const seedBytes = seed ? TypedArrayEncoder.fromString(seed) : undefined + public async createKeyPair({ seed, privateKey }: CreateKeyPairOptions): Promise { + if (privateKey) { + throw new KeyProviderError('Cannot create keypair from private key') + } - const blsKeyPair = await generateBls12381G2KeyPair(seedBytes) + const blsKeyPair = await generateBls12381G2KeyPair(seed) return { keyType: KeyType.Bls12381g2, diff --git a/packages/bbs-signatures/tests/bbs-key-provider.e2e.test.ts b/packages/bbs-signatures/tests/bbs-key-provider.e2e.test.ts index 0e02886f15..734e2e4ca5 100644 --- a/packages/bbs-signatures/tests/bbs-key-provider.e2e.test.ts +++ b/packages/bbs-signatures/tests/bbs-key-provider.e2e.test.ts @@ -1,4 +1,4 @@ -import type { WalletConfig } from '@aries-framework/core' +import type { Wallet, WalletConfig } from '@aries-framework/core' import { KeyDerivationMethod, @@ -6,12 +6,12 @@ import { WalletError, TypedArrayEncoder, KeyProviderRegistry, - IndyWallet, } from '@aries-framework/core' -import { agentDependencies } from '@aries-framework/node' import { BBS_SIGNATURE_LENGTH } from '@mattrglobal/bbs-signatures' import testLogger from '../../core/tests/logger' +import { IndySdkWallet } from '../../indy-sdk/src' +import { indySdk } from '../../indy-sdk/tests/setupIndySdkModule' import { Bls12381g2KeyProvider } from '../src' import { describeSkipNode17And18 } from './util' @@ -24,35 +24,35 @@ const walletConfig: WalletConfig = { keyDerivationMethod: KeyDerivationMethod.Raw, } -describeSkipNode17And18('BBS Key Provider', () => { - let indyWallet: IndyWallet - const seed = 'sample-seed' +describeSkipNode17And18('BBS Signing Provider', () => { + let wallet: Wallet + const seed = TypedArrayEncoder.fromString('sample-seed-min-of-32-bytes-long') const message = TypedArrayEncoder.fromString('sample-message') beforeEach(async () => { - indyWallet = new IndyWallet(agentDependencies, testLogger, new KeyProviderRegistry([new Bls12381g2KeyProvider()])) - await indyWallet.createAndOpen(walletConfig) + wallet = new IndySdkWallet(indySdk, testLogger, new KeyProviderRegistry([new Bls12381g2KeyProvider()])) + await wallet.createAndOpen(walletConfig) }) afterEach(async () => { - await indyWallet.delete() + await wallet.delete() }) test('Create bls12381g2 keypair', async () => { - await expect(indyWallet.createKey({ seed, keyType: KeyType.Bls12381g2 })).resolves.toMatchObject({ + await expect(wallet.createKey({ seed, keyType: KeyType.Bls12381g2 })).resolves.toMatchObject({ publicKeyBase58: - 't54oLBmhhRcDLUyWTvfYRWw8VRXRy1p43pVm62hrpShrYPuHe9WNAgS33DPfeTK6xK7iPrtJDwCHZjYgbFYDVTJHxXex9xt2XEGF8D356jBT1HtqNeucv3YsPLfTWcLcpFA', + '25TvGExLTWRTgn9h2wZuohrQmmLafXiacY4dhv66wcbY8pLbuNTBRMTgWVcPKh2wsEyrRPmnhLdc4C7LEcJ2seoxzBkoydJEdQD8aqg5dw8wesBTS9Twg8EjuFG1WPRAiERd', keyType: KeyType.Bls12381g2, }) }) test('Fail to create bls12381g1g2 keypair', async () => { - await expect(indyWallet.createKey({ seed, keyType: KeyType.Bls12381g1g2 })).rejects.toThrowError(WalletError) + await expect(wallet.createKey({ seed, keyType: KeyType.Bls12381g1g2 })).rejects.toThrowError(WalletError) }) test('Create a signature with a bls12381g2 keypair', async () => { - const bls12381g2Key = await indyWallet.createKey({ seed, keyType: KeyType.Bls12381g2 }) - const signature = await indyWallet.sign({ + const bls12381g2Key = await wallet.createKey({ seed, keyType: KeyType.Bls12381g2 }) + const signature = await wallet.sign({ data: message, key: bls12381g2Key, }) @@ -60,11 +60,11 @@ describeSkipNode17And18('BBS Key Provider', () => { }) test('Verify a signed message with a bls12381g2 publicKey', async () => { - const bls12381g2Key = await indyWallet.createKey({ seed, keyType: KeyType.Bls12381g2 }) - const signature = await indyWallet.sign({ + const bls12381g2Key = await wallet.createKey({ seed, keyType: KeyType.Bls12381g2 }) + const signature = await wallet.sign({ data: message, key: bls12381g2Key, }) - await expect(indyWallet.verify({ key: bls12381g2Key, data: message, signature })).resolves.toStrictEqual(true) + await expect(wallet.verify({ key: bls12381g2Key, data: message, signature })).resolves.toStrictEqual(true) }) }) diff --git a/packages/bbs-signatures/tests/bbs-signatures.e2e.test.ts b/packages/bbs-signatures/tests/bbs-signatures.e2e.test.ts index 5c2b0a59bf..9673547add 100644 --- a/packages/bbs-signatures/tests/bbs-signatures.e2e.test.ts +++ b/packages/bbs-signatures/tests/bbs-signatures.e2e.test.ts @@ -1,5 +1,5 @@ import type { W3cCredentialRepository } from '../../core/src/modules/vc/repository' -import type { AgentContext } from '@aries-framework/core' +import type { AgentContext, Wallet } from '@aries-framework/core' import { VERIFICATION_METHOD_TYPE_ED25519_VERIFICATION_KEY_2018, @@ -17,14 +17,16 @@ import { LinkedDataProof, W3cPresentation, W3cVerifiablePresentation, - IndyWallet, Ed25519Signature2018, + TypedArrayEncoder, } from '@aries-framework/core' import { SignatureSuiteRegistry } from '../../core/src/modules/vc/SignatureSuiteRegistry' -import { W3cVcModuleConfig } from '../../core/src/modules/vc/W3cVcModuleConfig' +import { W3cCredentialsModuleConfig } from '../../core/src/modules/vc/W3cCredentialsModuleConfig' import { customDocumentLoader } from '../../core/src/modules/vc/__tests__/documentLoader' import { getAgentConfig, getAgentContext } from '../../core/tests/helpers' +import { IndySdkWallet } from '../../indy-sdk/src' +import { indySdk } from '../../indy-sdk/tests/setupIndySdkModule' import { BbsBlsSignature2020, BbsBlsSignatureProof2020, Bls12381g2KeyProvider } from '../src' import { BbsBlsSignature2020Fixtures } from './fixtures' @@ -59,15 +61,15 @@ const keyProviderRegistry = new KeyProviderRegistry([new Bls12381g2KeyProvider() const agentConfig = getAgentConfig('BbsSignaturesE2eTest') describeSkipNode17And18('BBS W3cCredentialService', () => { - let wallet: IndyWallet + let wallet: Wallet let agentContext: AgentContext let w3cCredentialService: W3cCredentialService - const seed = 'testseed000000000000000000000001' + const seed = TypedArrayEncoder.fromString('testseed000000000000000000000001') + const privateKey = TypedArrayEncoder.fromString('testseed000000000000000000000001') beforeAll(async () => { - wallet = new IndyWallet(agentConfig.agentDependencies, agentConfig.logger, keyProviderRegistry) - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - await wallet.createAndOpen(agentConfig.walletConfig!) + wallet = new IndySdkWallet(indySdk, agentConfig.logger, keyProviderRegistry) + await wallet.createAndOpen(agentConfig.walletConfig) agentContext = getAgentContext({ agentConfig, wallet, @@ -75,7 +77,7 @@ describeSkipNode17And18('BBS W3cCredentialService', () => { w3cCredentialService = new W3cCredentialService( {} as unknown as W3cCredentialRepository, signatureSuiteRegistry, - new W3cVcModuleConfig({ + new W3cCredentialsModuleConfig({ documentLoader: customDocumentLoader, }) ) @@ -219,7 +221,10 @@ describeSkipNode17And18('BBS W3cCredentialService', () => { describe('signPresentation', () => { it('should sign the presentation successfully', async () => { - const signingKey = await wallet.createKey({ seed, keyType: KeyType.Ed25519 }) + const signingKey = await wallet.createKey({ + privateKey, + keyType: KeyType.Ed25519, + }) const signingDidKey = new DidKey(signingKey) const verificationMethod = `${signingDidKey.did}#${signingDidKey.key.fingerprint}` const presentation = JsonTransformer.fromJSON(BbsBlsSignature2020Fixtures.TEST_VP_DOCUMENT, W3cPresentation) @@ -252,10 +257,7 @@ describeSkipNode17And18('BBS W3cCredentialService', () => { const result = await w3cCredentialService.verifyPresentation(agentContext, { presentation: vp, - proofType: 'Ed25519Signature2018', challenge: 'e950bfe5-d7ec-4303-ad61-6983fb976ac9', - verificationMethod: - 'did:key:z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL#z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL', }) expect(result.verified).toBe(true) diff --git a/packages/bbs-signatures/tests/setup.ts b/packages/bbs-signatures/tests/setup.ts index 00b77cc0fe..78143033f2 100644 --- a/packages/bbs-signatures/tests/setup.ts +++ b/packages/bbs-signatures/tests/setup.ts @@ -1,3 +1,3 @@ import 'reflect-metadata' -jest.setTimeout(30000) +jest.setTimeout(120000) diff --git a/packages/bbs-signatures/tests/util.ts b/packages/bbs-signatures/tests/util.ts index 5d73cbe64b..208a6ce8ac 100644 --- a/packages/bbs-signatures/tests/util.ts +++ b/packages/bbs-signatures/tests/util.ts @@ -1,13 +1,3 @@ -export function testSkipNode17And18(...parameters: Parameters) { - const version = process.version - - if (version.startsWith('v17.') || version.startsWith('v18.')) { - test.skip(...parameters) - } else { - test(...parameters) - } -} - export function describeSkipNode17And18(...parameters: Parameters) { const version = process.version diff --git a/packages/bbs-signatures/tests/v2.ldproof.credentials.propose-offerBbs.test.ts b/packages/bbs-signatures/tests/v2.ldproof.credentials.propose-offerBbs.test.ts index 4569b44631..78d06d862a 100644 --- a/packages/bbs-signatures/tests/v2.ldproof.credentials.propose-offerBbs.test.ts +++ b/packages/bbs-signatures/tests/v2.ldproof.credentials.propose-offerBbs.test.ts @@ -1,46 +1,80 @@ -import type { ConnectionRecord } from '../../core/src/modules/connections' -import type { JsonCredential, JsonLdCredentialDetailFormat } from '../../core/src/modules/credentials/formats/jsonld' -import type { Wallet } from '../../core/src/wallet' -import type { CredentialTestsAgent } from '../../core/tests/helpers' +import type { V2IssueCredentialMessage } from '../../core/src/modules/credentials/protocol/v2/messages/V2IssueCredentialMessage' +import type { EventReplaySubject, JsonLdTestsAgent } from '../../core/tests' -import { InjectionSymbols } from '../../core/src/constants' +import { TypedArrayEncoder } from '../../core/src' import { KeyType } from '../../core/src/crypto' import { CredentialState } from '../../core/src/modules/credentials/models' -import { V2IssueCredentialMessage } from '../../core/src/modules/credentials/protocol/v2/messages/V2IssueCredentialMessage' -import { V2OfferCredentialMessage } from '../../core/src/modules/credentials/protocol/v2/messages/V2OfferCredentialMessage' import { CredentialExchangeRecord } from '../../core/src/modules/credentials/repository/CredentialExchangeRecord' -import { DidKey } from '../../core/src/modules/dids' import { CREDENTIALS_CONTEXT_V1_URL, SECURITY_CONTEXT_BBS_URL } from '../../core/src/modules/vc' -import { DidCommMessageRepository } from '../../core/src/storage' import { JsonTransformer } from '../../core/src/utils/JsonTransformer' -import { setupCredentialTests, waitForCredentialRecord } from '../../core/tests/helpers' -import testLogger from '../../core/tests/logger' +import { waitForCredentialRecordSubject, setupJsonLdTests, testLogger } from '../../core/tests' import { describeSkipNode17And18 } from './util' -let faberAgent: CredentialTestsAgent -let aliceAgent: CredentialTestsAgent -let aliceConnection: ConnectionRecord +let faberAgent: JsonLdTestsAgent +let faberReplay: EventReplaySubject +let aliceAgent: JsonLdTestsAgent +let aliceReplay: EventReplaySubject +let aliceConnectionId: string let aliceCredentialRecord: CredentialExchangeRecord let faberCredentialRecord: CredentialExchangeRecord +const signCredentialOptions = { + credential: { + '@context': [CREDENTIALS_CONTEXT_V1_URL, 'https://w3id.org/citizenship/v1', SECURITY_CONTEXT_BBS_URL], + id: 'https://issuer.oidp.uscis.gov/credentials/83627465', + type: ['VerifiableCredential', 'PermanentResidentCard'], + issuer: + 'did:key:zUC72Q7XD4PE4CrMiDVXuvZng3sBvMmaGgNeTUJuzavH2BS7ThbHL9FhsZM9QYY5fqAQ4MB8M9oudz3tfuaX36Ajr97QRW7LBt6WWmrtESe6Bs5NYzFtLWEmeVtvRYVAgjFcJSa', + issuanceDate: '2019-12-03T12:19:52Z', + expirationDate: '2029-12-03T12:19:52Z', + identifier: '83627465', + name: 'Permanent Resident Card', + credentialSubject: { + id: 'did:example:b34ca6cd37bbf23', + type: ['PermanentResident', 'Person'], + givenName: 'JOHN', + familyName: 'SMITH', + gender: 'Male', + image: 'data:image/png;base64,iVBORw0KGgokJggg==', + residentSince: '2015-01-01', + description: 'Government of Example Permanent Resident Card.', + lprCategory: 'C09', + lprNumber: '999-999-999', + commuterClassification: 'C1', + birthCountry: 'Bahamas', + birthDate: '1958-07-17', + }, + }, + options: { + proofType: 'BbsBlsSignature2020', + proofPurpose: 'assertionMethod', + }, +} + describeSkipNode17And18('credentials, BBS+ signature', () => { - let wallet - let issuerDidKey: DidKey - let didCommMessageRepository: DidCommMessageRepository - let signCredentialOptions: JsonLdCredentialDetailFormat - const seed = 'testseed000000000000000000000001' beforeAll(async () => { - ;({ faberAgent, aliceAgent, aliceConnection } = await setupCredentialTests( - 'Faber Agent Credentials LD BBS+', - 'Alice Agent Credentials LD BBS+' - )) - wallet = faberAgent.injectionContainer.resolve(InjectionSymbols.Wallet) - await wallet.createKey({ keyType: KeyType.Ed25519, seed }) - const key = await wallet.createKey({ keyType: KeyType.Bls12381g2, seed }) + ;({ + issuerAgent: faberAgent, + issuerReplay: faberReplay, + holderAgent: aliceAgent, + holderReplay: aliceReplay, + holderIssuerConnectionId: aliceConnectionId, + } = await setupJsonLdTests({ + issuerName: 'Faber Agent Credentials LD BBS+', + holderName: 'Alice Agent Credentials LD BBS+', + })) - issuerDidKey = new DidKey(key) + await faberAgent.context.wallet.createKey({ + keyType: KeyType.Ed25519, + privateKey: TypedArrayEncoder.fromString('testseed000000000000000000000001'), + }) + await faberAgent.context.wallet.createKey({ + keyType: KeyType.Bls12381g2, + seed: TypedArrayEncoder.fromString('testseed000000000000000000000001'), + }) }) + afterAll(async () => { await faberAgent.shutdown() await faberAgent.wallet.delete() @@ -50,45 +84,8 @@ describeSkipNode17And18('credentials, BBS+ signature', () => { test('Alice starts with V2 (ld format, BbsBlsSignature2020 signature) credential proposal to Faber', async () => { testLogger.test('Alice sends (v2 jsonld) credential proposal to Faber') - // set the propose options - - const TEST_LD_DOCUMENT: JsonCredential = { - '@context': [CREDENTIALS_CONTEXT_V1_URL, 'https://w3id.org/citizenship/v1', SECURITY_CONTEXT_BBS_URL], - id: 'https://issuer.oidp.uscis.gov/credentials/83627465', - type: ['VerifiableCredential', 'PermanentResidentCard'], - issuer: issuerDidKey.did, - issuanceDate: '2019-12-03T12:19:52Z', - expirationDate: '2029-12-03T12:19:52Z', - identifier: '83627465', - name: 'Permanent Resident Card', - credentialSubject: { - id: 'did:example:b34ca6cd37bbf23', - type: ['PermanentResident', 'Person'], - givenName: 'JOHN', - familyName: 'SMITH', - gender: 'Male', - image: 'data:image/png;base64,iVBORw0KGgokJggg==', - residentSince: '2015-01-01', - description: 'Government of Example Permanent Resident Card.', - lprCategory: 'C09', - lprNumber: '999-999-999', - commuterClassification: 'C1', - birthCountry: 'Bahamas', - birthDate: '1958-07-17', - }, - } - signCredentialOptions = { - credential: TEST_LD_DOCUMENT, - options: { - proofType: 'BbsBlsSignature2020', - proofPurpose: 'assertionMethod', - }, - } - - testLogger.test('Alice sends (v2, Indy) credential proposal to Faber') - - const credentialExchangeRecord: CredentialExchangeRecord = await aliceAgent.credentials.proposeCredential({ - connectionId: aliceConnection.id, + const credentialExchangeRecord = await aliceAgent.credentials.proposeCredential({ + connectionId: aliceConnectionId, protocolVersion: 'v2', credentialFormats: { jsonld: signCredentialOptions, @@ -96,16 +93,17 @@ describeSkipNode17And18('credentials, BBS+ signature', () => { comment: 'v2 propose credential test for W3C Credentials', }) - expect(credentialExchangeRecord.connectionId).toEqual(aliceConnection.id) + expect(credentialExchangeRecord.connectionId).toEqual(aliceConnectionId) expect(credentialExchangeRecord.protocolVersion).toEqual('v2') expect(credentialExchangeRecord.state).toEqual(CredentialState.ProposalSent) expect(credentialExchangeRecord.threadId).not.toBeNull() testLogger.test('Faber waits for credential proposal from Alice') - faberCredentialRecord = await waitForCredentialRecord(faberAgent, { + faberCredentialRecord = await waitForCredentialRecordSubject(faberReplay, { threadId: credentialExchangeRecord.threadId, state: CredentialState.ProposalReceived, }) + testLogger.test('Faber sends credential offer to Alice') await faberAgent.credentials.acceptProposal({ credentialRecordId: faberCredentialRecord.id, @@ -113,18 +111,12 @@ describeSkipNode17And18('credentials, BBS+ signature', () => { }) testLogger.test('Alice waits for credential offer from Faber') - aliceCredentialRecord = await waitForCredentialRecord(aliceAgent, { + aliceCredentialRecord = await waitForCredentialRecordSubject(aliceReplay, { threadId: faberCredentialRecord.threadId, state: CredentialState.OfferReceived, }) - didCommMessageRepository = faberAgent.dependencyManager.resolve(DidCommMessageRepository) - - const offerMessage = await didCommMessageRepository.findAgentMessage(aliceAgent.context, { - associatedRecordId: aliceCredentialRecord.id, - messageClass: V2OfferCredentialMessage, - }) - + const offerMessage = await faberAgent.credentials.findOfferMessage(faberCredentialRecord.id) expect(JsonTransformer.toJSON(offerMessage)).toMatchObject({ '@type': 'https://didcomm.org/issue-credential/2.0/offer-credential', '@id': expect.any(String), @@ -162,37 +154,32 @@ describeSkipNode17And18('credentials, BBS+ signature', () => { expect(aliceCredentialRecord.id).not.toBeNull() expect(aliceCredentialRecord.type).toBe(CredentialExchangeRecord.type) - if (!aliceCredentialRecord.connectionId) { - throw new Error('Missing Connection Id') - } - - const offerCredentialExchangeRecord: CredentialExchangeRecord = await aliceAgent.credentials.acceptOffer({ + const offerCredentialExchangeRecord = await aliceAgent.credentials.acceptOffer({ credentialRecordId: aliceCredentialRecord.id, credentialFormats: { jsonld: undefined, }, }) - expect(offerCredentialExchangeRecord.connectionId).toEqual(aliceConnection.id) + expect(offerCredentialExchangeRecord.connectionId).toEqual(aliceConnectionId) expect(offerCredentialExchangeRecord.protocolVersion).toEqual('v2') expect(offerCredentialExchangeRecord.state).toEqual(CredentialState.RequestSent) expect(offerCredentialExchangeRecord.threadId).not.toBeNull() testLogger.test('Faber waits for credential request from Alice') - await waitForCredentialRecord(faberAgent, { + await waitForCredentialRecordSubject(faberReplay, { threadId: aliceCredentialRecord.threadId, state: CredentialState.RequestReceived, }) testLogger.test('Faber sends credential to Alice') - await faberAgent.credentials.acceptRequest({ credentialRecordId: faberCredentialRecord.id, comment: 'V2 W3C Offer', }) testLogger.test('Alice waits for credential from Faber') - aliceCredentialRecord = await waitForCredentialRecord(aliceAgent, { + aliceCredentialRecord = await waitForCredentialRecordSubject(aliceReplay, { threadId: faberCredentialRecord.threadId, state: CredentialState.CredentialReceived, }) @@ -201,7 +188,7 @@ describeSkipNode17And18('credentials, BBS+ signature', () => { await aliceAgent.credentials.acceptCredential({ credentialRecordId: aliceCredentialRecord.id }) testLogger.test('Faber waits for credential ack from Alice') - faberCredentialRecord = await waitForCredentialRecord(faberAgent, { + faberCredentialRecord = await waitForCredentialRecordSubject(faberReplay, { threadId: faberCredentialRecord.threadId, state: CredentialState.Done, }) @@ -214,15 +201,11 @@ describeSkipNode17And18('credentials, BBS+ signature', () => { state: CredentialState.CredentialReceived, }) - const credentialMessage = await didCommMessageRepository.getAgentMessage(faberAgent.context, { - associatedRecordId: faberCredentialRecord.id, - messageClass: V2IssueCredentialMessage, - }) - - const w3cCredential = credentialMessage.credentialAttachments[0].getDataAsJson() + const credentialMessage = await faberAgent.credentials.findCredentialMessage(faberCredentialRecord.id) + const w3cCredential = (credentialMessage as V2IssueCredentialMessage).credentialAttachments[0].getDataAsJson() expect(w3cCredential).toMatchObject({ - context: [ + '@context': [ 'https://www.w3.org/2018/credentials/v1', 'https://w3id.org/citizenship/v1', 'https://w3id.org/security/bbs/v1', @@ -267,7 +250,7 @@ describeSkipNode17And18('credentials, BBS+ signature', () => { formats: [ { attach_id: expect.any(String), - format: 'aries/ld-proof-vc@1.0', + format: 'aries/ld-proof-vc@v1.0', }, ], 'credentials~attach': [ diff --git a/packages/cheqd/README.md b/packages/cheqd/README.md new file mode 100644 index 0000000000..732f6e3fdd --- /dev/null +++ b/packages/cheqd/README.md @@ -0,0 +1,35 @@ +

+
+ Hyperledger Aries logo +

+

Aries Framework JavaScript - Cheqd

+

+ License + typescript + @aries-framework/cheqd version + +

+
+ +### Installation + +### Quick start + +### Example of usage diff --git a/packages/cheqd/jest.config.ts b/packages/cheqd/jest.config.ts new file mode 100644 index 0000000000..93c0197296 --- /dev/null +++ b/packages/cheqd/jest.config.ts @@ -0,0 +1,13 @@ +import type { Config } from '@jest/types' + +import base from '../../jest.config.base' + +import packageJson from './package.json' + +const config: Config.InitialOptions = { + ...base, + displayName: packageJson.name, + setupFilesAfterEnv: ['./tests/setup.ts'], +} + +export default config diff --git a/packages/cheqd/package.json b/packages/cheqd/package.json new file mode 100644 index 0000000000..65a5feee4c --- /dev/null +++ b/packages/cheqd/package.json @@ -0,0 +1,45 @@ +{ + "name": "@aries-framework/cheqd", + "main": "build/index", + "types": "build/index", + "version": "0.3.3", + "files": [ + "build" + ], + "license": "Apache-2.0", + "publishConfig": { + "access": "public" + }, + "homepage": "https://github.com/hyperledger/aries-framework-javascript/tree/main/packages/cheqd", + "repository": { + "type": "git", + "url": "https://github.com/hyperledger/aries-framework-javascript", + "directory": "packages/cheqd" + }, + "scripts": { + "build": "yarn run clean && yarn run compile", + "clean": "rimraf ./build", + "compile": "tsc -p tsconfig.build.json", + "prepublishOnly": "yarn run build", + "test": "jest" + }, + "dependencies": { + "@aries-framework/anoncreds": "0.3.3", + "@aries-framework/core": "0.3.3", + "@cheqd/sdk": "cjs", + "@cheqd/ts-proto": "cjs", + "class-transformer": "^0.5.1", + "class-validator": "^0.14.0", + "rxjs": "^7.2.0", + "tsyringe": "^4.7.0", + "@cosmjs/proto-signing": "^0.29.5", + "@cosmjs/crypto": "^0.29.5", + "@stablelib/ed25519": "^1.0.3" + }, + "devDependencies": { + "rimraf": "^4.0.7", + "typescript": "~4.9.4", + "@aries-framework/indy-sdk": "*", + "@types/indy-sdk": "*" + } +} diff --git a/packages/cheqd/src/CheqdModule.ts b/packages/cheqd/src/CheqdModule.ts new file mode 100644 index 0000000000..a52968f83c --- /dev/null +++ b/packages/cheqd/src/CheqdModule.ts @@ -0,0 +1,26 @@ +import type { CheqdModuleConfigOptions } from './CheqdModuleConfig' +import type { AgentContext, DependencyManager, Module } from '@aries-framework/core' + +import { CheqdModuleConfig } from './CheqdModuleConfig' +import { CheqdLedgerService } from './ledger' + +export class CheqdModule implements Module { + public readonly config: CheqdModuleConfig + + public constructor(config: CheqdModuleConfigOptions) { + this.config = new CheqdModuleConfig(config) + } + + public register(dependencyManager: DependencyManager) { + // Register config + dependencyManager.registerInstance(CheqdModuleConfig, this.config) + + dependencyManager.registerSingleton(CheqdLedgerService) + } + + public async initialize(agentContext: AgentContext): Promise { + // not required + const cheqdLedgerService = agentContext.dependencyManager.resolve(CheqdLedgerService) + await cheqdLedgerService.connect() + } +} diff --git a/packages/cheqd/src/CheqdModuleConfig.ts b/packages/cheqd/src/CheqdModuleConfig.ts new file mode 100644 index 0000000000..3d0077883c --- /dev/null +++ b/packages/cheqd/src/CheqdModuleConfig.ts @@ -0,0 +1,25 @@ +/** + * CheqdModuleConfigOptions defines the interface for the options of the CheqdModuleConfig class. + */ +export interface CheqdModuleConfigOptions { + networks: NetworkConfig[] +} + +export interface NetworkConfig { + rpcUrl?: string + cosmosPayerSeed: string + network: string +} + +export class CheqdModuleConfig { + private options: CheqdModuleConfigOptions + + public constructor(options: CheqdModuleConfigOptions) { + this.options = options + } + + /** See {@link CheqdModuleConfigOptions.networks} */ + public get networks() { + return this.options.networks + } +} diff --git a/packages/cheqd/src/anoncreds/index.ts b/packages/cheqd/src/anoncreds/index.ts new file mode 100644 index 0000000000..6a8bd47548 --- /dev/null +++ b/packages/cheqd/src/anoncreds/index.ts @@ -0,0 +1 @@ +export { CheqdAnonCredsRegistry } from './services/CheqdAnonCredsRegistry' diff --git a/packages/cheqd/src/anoncreds/services/CheqdAnonCredsRegistry.ts b/packages/cheqd/src/anoncreds/services/CheqdAnonCredsRegistry.ts new file mode 100644 index 0000000000..4b3d5db18c --- /dev/null +++ b/packages/cheqd/src/anoncreds/services/CheqdAnonCredsRegistry.ts @@ -0,0 +1,340 @@ +import type { CheqdCreateResourceOptions } from '../../dids' +import type { + AnonCredsRegistry, + GetCredentialDefinitionReturn, + GetRevocationStatusListReturn, + GetRevocationRegistryDefinitionReturn, + GetSchemaReturn, + RegisterCredentialDefinitionOptions, + RegisterCredentialDefinitionReturn, + RegisterSchemaReturn, + RegisterSchemaOptions, +} from '@aries-framework/anoncreds' +import type { AgentContext } from '@aries-framework/core' + +import { AriesFrameworkError, JsonTransformer, utils } from '@aries-framework/core' + +import { CheqdDidResolver, CheqdDidRegistrar } from '../../dids' +import { cheqdSdkAnonCredsRegistryIdentifierRegex, parseCheqdDid } from '../utils/identifiers' +import { + CheqdCredentialDefinition, + CheqdRevocationRegistryDefinition, + CheqdRevocationStatusList, + CheqdSchema, +} from '../utils/transform' + +export class CheqdAnonCredsRegistry implements AnonCredsRegistry { + public methodName = 'cheqd' + + /** + * This class supports resolving and registering objects with cheqd identifiers. + * It needs to include support for the schema, credential definition, revocation registry as well + * as the issuer id (which is needed when registering objects). + */ + public readonly supportedIdentifier = cheqdSdkAnonCredsRegistryIdentifierRegex + + public async getSchema(agentContext: AgentContext, schemaId: string): Promise { + try { + const cheqdDidResolver = agentContext.dependencyManager.resolve(CheqdDidResolver) + const parsedDid = parseCheqdDid(schemaId) + if (!parsedDid) { + throw new Error(`Invalid schemaId: ${schemaId}`) + } + + agentContext.config.logger.trace(`Submitting get schema request for schema '${schemaId}' to ledger`) + + const response = await cheqdDidResolver.resolveResource(agentContext, schemaId) + const schema = JsonTransformer.fromJSON(response.resource, CheqdSchema) + + return { + schema: { + attrNames: schema.attrNames, + name: schema.name, + version: schema.version, + issuerId: parsedDid.did, + }, + schemaId, + resolutionMetadata: {}, + schemaMetadata: {}, + } + } catch (error) { + agentContext.config.logger.error(`Error retrieving schema '${schemaId}'`, { + error, + schemaId, + }) + + return { + schemaId, + resolutionMetadata: { + error: 'notFound', + message: `unable to resolve schema: ${error.message}`, + }, + schemaMetadata: {}, + } + } + } + + public async registerSchema( + agentContext: AgentContext, + options: RegisterSchemaOptions + ): Promise { + try { + const cheqdDidRegistrar = agentContext.dependencyManager.resolve(CheqdDidRegistrar) + + const schema = options.schema + const schemaResource = { + id: utils.uuid(), + name: `${schema.name}-Schema`, + resourceType: 'anonCredsSchema', + data: { + name: schema.name, + version: schema.version, + attrNames: schema.attrNames, + }, + version: schema.version, + } satisfies CheqdCreateResourceOptions + + const response = await cheqdDidRegistrar.createResource(agentContext, schema.issuerId, schemaResource) + if (response.resourceState.state !== 'finished') { + throw new Error(response.resourceState.reason) + } + + return { + schemaState: { + state: 'finished', + schema, + schemaId: `${schema.issuerId}/resources/${schemaResource.id}`, + }, + registrationMetadata: {}, + schemaMetadata: {}, + } + } catch (error) { + agentContext.config.logger.debug(`Error registering schema for did '${options.schema.issuerId}'`, { + error, + did: options.schema.issuerId, + schema: options, + }) + + return { + schemaMetadata: {}, + registrationMetadata: {}, + schemaState: { + state: 'failed', + schema: options.schema, + reason: `unknownError: ${error.message}`, + }, + } + } + } + + public async registerCredentialDefinition( + agentContext: AgentContext, + options: RegisterCredentialDefinitionOptions + ): Promise { + try { + const cheqdDidRegistrar = agentContext.dependencyManager.resolve(CheqdDidRegistrar) + const { credentialDefinition } = options + const schema = await this.getSchema(agentContext, credentialDefinition.schemaId) + const credDefResource = { + id: utils.uuid(), + name: `${schema.schema?.name}-${credentialDefinition.tag}-CredDef`, + resourceType: 'anonCredsCredDef', + data: { + type: credentialDefinition.type, + tag: credentialDefinition.tag, + value: credentialDefinition.value, + schemaId: credentialDefinition.schemaId, + }, + version: utils.uuid(), + } satisfies CheqdCreateResourceOptions + + const response = await cheqdDidRegistrar.createResource( + agentContext, + credentialDefinition.issuerId, + credDefResource + ) + if (response.resourceState.state !== 'finished') { + throw new Error(response.resourceState.reason) + } + + return { + credentialDefinitionState: { + state: 'finished', + credentialDefinition, + credentialDefinitionId: `${credentialDefinition.issuerId}/resources/${credDefResource.id}`, + }, + registrationMetadata: {}, + credentialDefinitionMetadata: {}, + } + } catch (error) { + agentContext.config.logger.error( + `Error registering credential definition for did '${options.credentialDefinition.issuerId}'`, + { + error, + did: options.credentialDefinition.issuerId, + schema: options, + } + ) + + return { + credentialDefinitionMetadata: {}, + registrationMetadata: {}, + credentialDefinitionState: { + state: 'failed', + credentialDefinition: options.credentialDefinition, + reason: `unknownError: ${error.message}`, + }, + } + } + } + + public async getCredentialDefinition( + agentContext: AgentContext, + credentialDefinitionId: string + ): Promise { + try { + const cheqdDidResolver = agentContext.dependencyManager.resolve(CheqdDidResolver) + const parsedDid = parseCheqdDid(credentialDefinitionId) + if (!parsedDid) { + throw new Error(`Invalid credentialDefinitionId: ${credentialDefinitionId}`) + } + + agentContext.config.logger.trace( + `Submitting get credential definition request for '${credentialDefinitionId}' to ledger` + ) + + const response = await cheqdDidResolver.resolveResource(agentContext, credentialDefinitionId) + const credentialDefinition = JsonTransformer.fromJSON(response.resource, CheqdCredentialDefinition) + return { + credentialDefinition: { + ...credentialDefinition, + issuerId: parsedDid.did, + }, + credentialDefinitionId, + resolutionMetadata: {}, + credentialDefinitionMetadata: (response.resourceMetadata ?? {}) as Record, + } + } catch (error) { + agentContext.config.logger.error(`Error retrieving credential definition '${credentialDefinitionId}'`, { + error, + credentialDefinitionId, + }) + + return { + credentialDefinitionId, + resolutionMetadata: { + error: 'notFound', + message: `unable to resolve credential definition: ${error.message}`, + }, + credentialDefinitionMetadata: {}, + } + } + } + + public async getRevocationRegistryDefinition( + agentContext: AgentContext, + revocationRegistryDefinitionId: string + ): Promise { + try { + const cheqdDidResolver = agentContext.dependencyManager.resolve(CheqdDidResolver) + const parsedDid = parseCheqdDid(revocationRegistryDefinitionId) + if (!parsedDid) { + throw new Error(`Invalid revocationRegistryDefinitionId: ${revocationRegistryDefinitionId}`) + } + + agentContext.config.logger.trace( + `Submitting get revocation registry definition request for '${revocationRegistryDefinitionId}' to ledger` + ) + + const response = await cheqdDidResolver.resolveResource( + agentContext, + `${revocationRegistryDefinitionId}&resourceType=anonCredsRevocRegDef` + ) + const revocationRegistryDefinition = JsonTransformer.fromJSON( + response.resource, + CheqdRevocationRegistryDefinition + ) + return { + revocationRegistryDefinition: { + ...revocationRegistryDefinition, + issuerId: parsedDid.did, + }, + revocationRegistryDefinitionId, + resolutionMetadata: {}, + revocationRegistryDefinitionMetadata: (response.resourceMetadata ?? {}) as Record, + } + } catch (error) { + agentContext.config.logger.error( + `Error retrieving revocation registry definition '${revocationRegistryDefinitionId}'`, + { + error, + revocationRegistryDefinitionId, + } + ) + + return { + revocationRegistryDefinitionId, + resolutionMetadata: { + error: 'notFound', + message: `unable to resolve revocation registry definition: ${error.message}`, + }, + revocationRegistryDefinitionMetadata: {}, + } + } + } + + // FIXME: this method doesn't retrieve the revocation status list at a specified time, it just resolves the revocation registry definition + public async getRevocationStatusList( + agentContext: AgentContext, + revocationRegistryId: string, + timestamp: number + ): Promise { + try { + const cheqdDidResolver = agentContext.dependencyManager.resolve(CheqdDidResolver) + const parsedDid = parseCheqdDid(revocationRegistryId) + if (!parsedDid) { + throw new Error(`Invalid revocationRegistryId: ${revocationRegistryId}`) + } + + agentContext.config.logger.trace( + `Submitting get revocation status request for '${revocationRegistryId}' to ledger` + ) + + const response = await cheqdDidResolver.resolveResource( + agentContext, + `${revocationRegistryId}&resourceType=anonCredsStatusList&resourceVersionTime=${timestamp}` + ) + const revocationStatusList = JsonTransformer.fromJSON(response.resource, CheqdRevocationStatusList) + + const statusListTimestamp = response.resourceMetadata?.created?.getUTCSeconds() + if (!statusListTimestamp) { + throw new AriesFrameworkError( + `Unable to extract revocation status list timestamp from resource ${revocationRegistryId}` + ) + } + + return { + revocationStatusList: { + ...revocationStatusList, + issuerId: parsedDid.did, + timestamp: statusListTimestamp, + }, + resolutionMetadata: {}, + revocationStatusListMetadata: (response.resourceMetadata ?? {}) as Record, + } + } catch (error) { + agentContext.config.logger.error(`Error retrieving revocation registry status list '${revocationRegistryId}'`, { + error, + revocationRegistryId, + }) + + return { + resolutionMetadata: { + error: 'notFound', + message: `unable to resolve revocation registry status list: ${error.message}`, + }, + revocationStatusListMetadata: {}, + } + } + } +} diff --git a/packages/cheqd/src/anoncreds/utils/identifiers.ts b/packages/cheqd/src/anoncreds/utils/identifiers.ts new file mode 100644 index 0000000000..ff21b32065 --- /dev/null +++ b/packages/cheqd/src/anoncreds/utils/identifiers.ts @@ -0,0 +1,59 @@ +import type { ParsedDid } from '@aries-framework/core' + +import { TypedArrayEncoder, utils } from '@aries-framework/core' +import { isBase58 } from 'class-validator' + +const ID_CHAR = '([a-z,A-Z,0-9,-])' +const NETWORK = '(testnet|mainnet)' +const IDENTIFIER = `((?:${ID_CHAR}*:)*(${ID_CHAR}+))` +const PATH = `(/[^#?]*)?` +const QUERY = `([?][^#]*)?` +const VERSION_ID = `(.*?)` + +export const cheqdSdkAnonCredsRegistryIdentifierRegex = new RegExp( + `^did:cheqd:${NETWORK}:${IDENTIFIER}${PATH}${QUERY}$` +) + +export const cheqdDidRegex = new RegExp(`^did:cheqd:${NETWORK}:${IDENTIFIER}${QUERY}$`) +export const cheqdDidVersionRegex = new RegExp(`^did:cheqd:${NETWORK}:${IDENTIFIER}/version/${VERSION_ID}${QUERY}$`) +export const cheqdDidVersionsRegex = new RegExp(`^did:cheqd:${NETWORK}:${IDENTIFIER}/versions${QUERY}$`) +export const cheqdDidMetadataRegex = new RegExp(`^did:cheqd:${NETWORK}:${IDENTIFIER}/metadata${QUERY}$`) +export const cheqdResourceRegex = new RegExp(`^did:cheqd:${NETWORK}:${IDENTIFIER}/resources/${IDENTIFIER}${QUERY}$`) +export const cheqdResourceMetadataRegex = new RegExp( + `^did:cheqd:${NETWORK}:${IDENTIFIER}/resources/${IDENTIFIER}/metadata${QUERY}` +) + +export type ParsedCheqdDid = ParsedDid & { network: string } +export function parseCheqdDid(didUrl: string): ParsedCheqdDid | null { + if (didUrl === '' || !didUrl) return null + const sections = didUrl.match(cheqdSdkAnonCredsRegistryIdentifierRegex) + if (sections) { + if ( + !( + utils.isValidUuid(sections[2]) || + (isBase58(sections[2]) && TypedArrayEncoder.fromBase58(sections[2]).length == 16) + ) + ) { + return null + } + const parts: ParsedCheqdDid = { + did: `did:cheqd:${sections[1]}:${sections[2]}`, + method: 'cheqd', + network: sections[1], + id: sections[2], + didUrl, + } + if (sections[7]) { + const params = sections[7].slice(1).split('&') + parts.params = {} + for (const p of params) { + const kv = p.split('=') + parts.params[kv[0]] = kv[1] + } + } + if (sections[6]) parts.path = sections[6] + if (sections[8]) parts.fragment = sections[8].slice(1) + return parts + } + return null +} diff --git a/packages/cheqd/src/anoncreds/utils/transform.ts b/packages/cheqd/src/anoncreds/utils/transform.ts new file mode 100644 index 0000000000..47c7b076a7 --- /dev/null +++ b/packages/cheqd/src/anoncreds/utils/transform.ts @@ -0,0 +1,149 @@ +// These functions will throw an error if the JSON doesn't +// match the expected interface, even if the JSON is valid. + +import type { + AnonCredsCredentialDefinition, + AnonCredsRevocationRegistryDefinition, + AnonCredsRevocationStatusList, + AnonCredsSchema, +} from '@aries-framework/anoncreds' + +import { Type } from 'class-transformer' +import { + ArrayMinSize, + Contains, + IsArray, + IsInstance, + IsNumber, + IsObject, + IsOptional, + IsString, + ValidateNested, +} from 'class-validator' + +export class CheqdSchema { + public constructor(options: Omit) { + if (options) { + this.name = options.name + this.attrNames = options.attrNames + this.version = options.version + } + } + + @IsString() + public name!: string + + @IsArray() + @IsString({ each: true }) + @ArrayMinSize(1) + public attrNames!: string[] + + @IsString() + public version!: string +} + +export class CheqdCredentialDefinitionValue { + @IsObject() + public primary!: Record + + @IsObject() + @IsOptional() + public revocation?: unknown +} + +export class CheqdCredentialDefinition { + public constructor(options: Omit) { + if (options) { + this.schemaId = options.schemaId + this.type = options.type + this.tag = options.tag + this.value = options.value + } + } + + @IsString() + public schemaId!: string + + @Contains('CL') + public type!: 'CL' + + @IsString() + public tag!: string + + @ValidateNested() + @IsInstance(CheqdCredentialDefinitionValue) + @Type(() => CheqdCredentialDefinitionValue) + public value!: CheqdCredentialDefinitionValue +} + +export class AccumKey { + @IsString() + public z!: string +} + +export class PublicKeys { + @ValidateNested() + @IsInstance(AccumKey) + @Type(() => AccumKey) + public accumKey!: AccumKey +} + +export class CheqdRevocationRegistryDefinitionValue { + @ValidateNested() + @IsInstance(PublicKeys) + @Type(() => PublicKeys) + public publicKeys!: PublicKeys + + @IsNumber() + public maxCredNum!: number + + @IsString() + public tailsLocation!: string + + @IsString() + public tailsHash!: string +} + +export class CheqdRevocationRegistryDefinition { + public constructor(options: Omit) { + if (options) { + this.revocDefType = options.revocDefType + this.credDefId = options.credDefId + this.tag = options.tag + this.value = options.value + } + } + + @Contains('CL_ACCUM') + public revocDefType!: 'CL_ACCUM' + + @IsString() + public credDefId!: string + + @IsString() + public tag!: string + + @ValidateNested() + @IsInstance(CheqdRevocationRegistryDefinitionValue) + @Type(() => CheqdRevocationRegistryDefinitionValue) + public value!: CheqdRevocationRegistryDefinitionValue +} + +export class CheqdRevocationStatusList { + public constructor(options: Omit) { + if (options) { + this.revRegDefId = options.revRegDefId + this.revocationList = options.revocationList + this.currentAccumulator = options.currentAccumulator + } + } + + @IsString() + public revRegDefId!: string + + @IsNumber({}, { each: true }) + public revocationList!: number[] + + @IsString() + public currentAccumulator!: string +} diff --git a/packages/cheqd/src/dids/CheqdDidRegistrar.ts b/packages/cheqd/src/dids/CheqdDidRegistrar.ts new file mode 100644 index 0000000000..0f4c243098 --- /dev/null +++ b/packages/cheqd/src/dids/CheqdDidRegistrar.ts @@ -0,0 +1,429 @@ +import type { + AgentContext, + DidRegistrar, + DidCreateOptions, + DidCreateResult, + DidDeactivateResult, + DidUpdateResult, +} from '@aries-framework/core' +import type { CheqdNetwork, DIDDocument, DidStdFee, TVerificationKey, VerificationMethods } from '@cheqd/sdk' +import type { SignInfo } from '@cheqd/ts-proto/cheqd/did/v2' + +import { + DidDocument, + DidDocumentRole, + DidRecord, + DidRepository, + KeyType, + Buffer, + isValidPrivateKey, + utils, + TypedArrayEncoder, + getKeyFromVerificationMethod, + JsonTransformer, + VerificationMethod, +} from '@aries-framework/core' +import { MethodSpecificIdAlgo, createDidVerificationMethod } from '@cheqd/sdk' +import { MsgCreateResourcePayload } from '@cheqd/ts-proto/cheqd/resource/v2' + +import { CheqdLedgerService } from '../ledger' + +import { + createMsgCreateDidDocPayloadToSign, + generateDidDoc, + validateSpecCompliantPayload, + createMsgDeactivateDidDocPayloadToSign, +} from './didCheqdUtil' + +export class CheqdDidRegistrar implements DidRegistrar { + public readonly supportedMethods = ['cheqd'] + + public async create(agentContext: AgentContext, options: CheqdDidCreateOptions): Promise { + const didRepository = agentContext.dependencyManager.resolve(DidRepository) + const cheqdLedgerService = agentContext.dependencyManager.resolve(CheqdLedgerService) + + const { methodSpecificIdAlgo, network, versionId = utils.uuid() } = options.options + const verificationMethod = options.secret?.verificationMethod + let didDocument: DidDocument + + try { + if (options.didDocument && validateSpecCompliantPayload(options.didDocument)) { + didDocument = options.didDocument + } else if (verificationMethod) { + const privateKey = verificationMethod.privateKey + if (privateKey && !isValidPrivateKey(privateKey, KeyType.Ed25519)) { + return { + didDocumentMetadata: {}, + didRegistrationMetadata: {}, + didState: { + state: 'failed', + reason: 'Invalid private key provided', + }, + } + } + + const key = await agentContext.wallet.createKey({ + keyType: KeyType.Ed25519, + privateKey: privateKey, + }) + + didDocument = generateDidDoc({ + verificationMethod: verificationMethod.type as VerificationMethods, + verificationMethodId: verificationMethod.id || 'key-1', + methodSpecificIdAlgo: (methodSpecificIdAlgo as MethodSpecificIdAlgo) || MethodSpecificIdAlgo.Uuid, + network: network as CheqdNetwork, + publicKey: TypedArrayEncoder.toHex(key.publicKey), + }) + + const contextMapping = { + Ed25519VerificationKey2018: 'https://w3id.org/security/suites/ed25519-2018/v1', + Ed25519VerificationKey2020: 'https://w3id.org/security/suites/ed25519-2020/v1', + JsonWebKey2020: 'https://w3id.org/security/suites/jws-2020/v1', + } + const contextUrl = contextMapping[verificationMethod.type] + + // Add the context to the did document + // NOTE: cheqd sdk uses https://www.w3.org/ns/did/v1 while AFJ did doc uses https://w3id.org/did/v1 + // We should align these at some point. For now we just return a consistent value. + didDocument.context = ['https://www.w3.org/ns/did/v1', contextUrl] + } else { + return { + didDocumentMetadata: {}, + didRegistrationMetadata: {}, + didState: { + state: 'failed', + reason: 'Provide a didDocument or at least one verificationMethod with seed in secret', + }, + } + } + + const didDocumentJson = didDocument.toJSON() as DIDDocument + + const payloadToSign = await createMsgCreateDidDocPayloadToSign(didDocumentJson, versionId) + const signInputs = await this.signPayload(agentContext, payloadToSign, didDocument.verificationMethod) + + const response = await cheqdLedgerService.create(didDocumentJson, signInputs, versionId) + if (response.code !== 0) { + throw new Error(`${response.rawLog}`) + } + + // Save the did so we know we created it and can issue with it + const didRecord = new DidRecord({ + did: didDocument.id, + role: DidDocumentRole.Created, + didDocument, + }) + await didRepository.save(agentContext, didRecord) + + return { + didDocumentMetadata: {}, + didRegistrationMetadata: {}, + didState: { + state: 'finished', + did: didDocument.id, + didDocument, + secret: options.secret, + }, + } + } catch (error) { + agentContext.config.logger.error(`Error registering DID`, error) + return { + didDocumentMetadata: {}, + didRegistrationMetadata: {}, + didState: { + state: 'failed', + reason: `unknownError: ${error.message}`, + }, + } + } + } + + public async update(agentContext: AgentContext, options: CheqdDidUpdateOptions): Promise { + const didRepository = agentContext.dependencyManager.resolve(DidRepository) + const cheqdLedgerService = agentContext.dependencyManager.resolve(CheqdLedgerService) + + const versionId = options.options?.versionId || utils.uuid() + const verificationMethod = options.secret?.verificationMethod + let didDocument: DidDocument + let didRecord: DidRecord | null + + try { + if (options.didDocument && validateSpecCompliantPayload(options.didDocument)) { + didDocument = options.didDocument + const resolvedDocument = await cheqdLedgerService.resolve(didDocument.id) + didRecord = await didRepository.findCreatedDid(agentContext, didDocument.id) + if (!resolvedDocument.didDocument || resolvedDocument.didDocumentMetadata.deactivated || !didRecord) { + return { + didDocumentMetadata: {}, + didRegistrationMetadata: {}, + didState: { + state: 'failed', + reason: 'Did not found', + }, + } + } + + if (verificationMethod) { + const privateKey = verificationMethod.privateKey + if (privateKey && !isValidPrivateKey(privateKey, KeyType.Ed25519)) { + return { + didDocumentMetadata: {}, + didRegistrationMetadata: {}, + didState: { + state: 'failed', + reason: 'Invalid private key provided', + }, + } + } + + const key = await agentContext.wallet.createKey({ + keyType: KeyType.Ed25519, + privateKey: privateKey, + }) + + didDocument.verificationMethod?.concat( + JsonTransformer.fromJSON( + createDidVerificationMethod( + [verificationMethod.type as VerificationMethods], + [ + { + methodSpecificId: didDocument.id.split(':')[3], + didUrl: didDocument.id, + keyId: `${didDocument.id}#${verificationMethod.id}`, + publicKey: TypedArrayEncoder.toHex(key.publicKey), + }, + ] + ), + VerificationMethod + ) + ) + } + } else { + return { + didDocumentMetadata: {}, + didRegistrationMetadata: {}, + didState: { + state: 'failed', + reason: 'Provide a valid didDocument', + }, + } + } + + const payloadToSign = await createMsgCreateDidDocPayloadToSign(didDocument as DIDDocument, versionId) + const signInputs = await this.signPayload(agentContext, payloadToSign, didDocument.verificationMethod) + + const response = await cheqdLedgerService.update(didDocument as DIDDocument, signInputs, versionId) + if (response.code !== 0) { + throw new Error(`${response.rawLog}`) + } + + // Save the did so we know we created it and can issue with it + didRecord.didDocument = didDocument + await didRepository.update(agentContext, didRecord) + + return { + didDocumentMetadata: {}, + didRegistrationMetadata: {}, + didState: { + state: 'finished', + did: didDocument.id, + didDocument, + secret: options.secret, + }, + } + } catch (error) { + agentContext.config.logger.error(`Error updating DID`, error) + return { + didDocumentMetadata: {}, + didRegistrationMetadata: {}, + didState: { + state: 'failed', + reason: `unknownError: ${error.message}`, + }, + } + } + } + + public async deactivate( + agentContext: AgentContext, + options: CheqdDidDeactivateOptions + ): Promise { + const didRepository = agentContext.dependencyManager.resolve(DidRepository) + const cheqdLedgerService = agentContext.dependencyManager.resolve(CheqdLedgerService) + + const did = options.did + const versionId = options.options?.versionId || utils.uuid() + + try { + const { didDocument, didDocumentMetadata } = await cheqdLedgerService.resolve(did) + + const didRecord = await didRepository.findCreatedDid(agentContext, did) + if (!didDocument || didDocumentMetadata.deactivated || !didRecord) { + return { + didDocumentMetadata: {}, + didRegistrationMetadata: {}, + didState: { + state: 'failed', + reason: 'Did not found', + }, + } + } + const payloadToSign = createMsgDeactivateDidDocPayloadToSign(didDocument, versionId) + const didDocumentInstance = JsonTransformer.fromJSON(didDocument, DidDocument) + const signInputs = await this.signPayload(agentContext, payloadToSign, didDocumentInstance.verificationMethod) + const response = await cheqdLedgerService.deactivate(didDocument, signInputs, versionId) + if (response.code !== 0) { + throw new Error(`${response.rawLog}`) + } + + await didRepository.update(agentContext, didRecord) + + return { + didDocumentMetadata: {}, + didRegistrationMetadata: {}, + didState: { + state: 'finished', + did: didDocument.id, + didDocument: JsonTransformer.fromJSON(didDocument, DidDocument), + secret: options.secret, + }, + } + } catch (error) { + agentContext.config.logger.error(`Error deactivating DID`, error) + return { + didDocumentMetadata: {}, + didRegistrationMetadata: {}, + didState: { + state: 'failed', + reason: `unknownError: ${error.message}`, + }, + } + } + } + + public async createResource(agentContext: AgentContext, did: string, resource: CheqdCreateResourceOptions) { + const didRepository = agentContext.dependencyManager.resolve(DidRepository) + const cheqdLedgerService = agentContext.dependencyManager.resolve(CheqdLedgerService) + const { didDocument, didDocumentMetadata } = await cheqdLedgerService.resolve(did) + const didRecord = await didRepository.findCreatedDid(agentContext, did) + if (!didDocument || didDocumentMetadata.deactivated || !didRecord) { + return { + resourceMetadata: {}, + resourceRegistrationMetadata: {}, + resourceState: { + state: 'failed', + reason: `DID: ${did} not found`, + }, + } + } + + try { + let data: Uint8Array + if (typeof resource.data === 'string') { + data = TypedArrayEncoder.fromBase64(resource.data) + } else if (typeof resource.data == 'object') { + data = TypedArrayEncoder.fromString(JSON.stringify(resource.data)) + } else { + data = resource.data + } + + const resourcePayload = MsgCreateResourcePayload.fromPartial({ + collectionId: did.split(':')[3], + id: resource.id, + resourceType: resource.resourceType, + name: resource.name, + version: resource.version, + alsoKnownAs: resource.alsoKnownAs, + data, + }) + const payloadToSign = MsgCreateResourcePayload.encode(resourcePayload).finish() + + const didDocumentInstance = JsonTransformer.fromJSON(didDocument, DidDocument) + const signInputs = await this.signPayload(agentContext, payloadToSign, didDocumentInstance.verificationMethod) + const response = await cheqdLedgerService.createResource(did, resourcePayload, signInputs) + if (response.code !== 0) { + throw new Error(`${response.rawLog}`) + } + + return { + resourceMetadata: {}, + resourceRegistrationMetadata: {}, + resourceState: { + state: 'finished', + resourceId: resourcePayload.id, + resource: resourcePayload, + }, + } + } catch (error) { + return { + resourceMetadata: {}, + resourceRegistrationMetadata: {}, + resourceState: { + state: 'failed', + reason: `unknownError: ${error.message}`, + }, + } + } + } + + private async signPayload( + agentContext: AgentContext, + payload: Uint8Array, + verificationMethod: VerificationMethod[] = [] + ) { + return await Promise.all( + verificationMethod.map(async (method) => { + const key = getKeyFromVerificationMethod(method) + return { + verificationMethodId: method.id, + signature: await agentContext.wallet.sign({ data: Buffer.from(payload), key }), + } satisfies SignInfo + }) + ) + } +} + +export interface CheqdDidCreateOptions extends DidCreateOptions { + method: 'cheqd' + options: { + network: `${CheqdNetwork}` + fee?: DidStdFee + versionId?: string + methodSpecificIdAlgo?: `${MethodSpecificIdAlgo}` + } + secret: { + verificationMethod?: IVerificationMethod + } +} + +export interface CheqdDidUpdateOptions extends DidCreateOptions { + method: 'cheqd' + did: string + didDocument: DidDocument + options: { + fee?: DidStdFee + versionId?: string + } + secret?: { + verificationMethod: IVerificationMethod + } +} + +export interface CheqdDidDeactivateOptions extends DidCreateOptions { + method: 'cheqd' + did: string + options: { + fee?: DidStdFee + versionId?: string + } +} + +export interface CheqdCreateResourceOptions extends Omit, 'data'> { + data: string | Uint8Array | object +} + +interface IVerificationMethod { + type: `${VerificationMethods}` + id: TVerificationKey + privateKey?: Buffer +} diff --git a/packages/cheqd/src/dids/CheqdDidResolver.ts b/packages/cheqd/src/dids/CheqdDidResolver.ts new file mode 100644 index 0000000000..edd94c2aa1 --- /dev/null +++ b/packages/cheqd/src/dids/CheqdDidResolver.ts @@ -0,0 +1,192 @@ +import type { ParsedCheqdDid } from '../anoncreds/utils/identifiers' +import type { AgentContext, DidResolutionResult, DidResolver, ParsedDid } from '@aries-framework/core' +import type { Metadata } from '@cheqd/ts-proto/cheqd/resource/v2' + +import { DidDocument, AriesFrameworkError, utils, JsonTransformer } from '@aries-framework/core' + +import { + cheqdDidMetadataRegex, + cheqdDidRegex, + cheqdDidVersionRegex, + cheqdDidVersionsRegex, + cheqdResourceMetadataRegex, + cheqdResourceRegex, + parseCheqdDid, +} from '../anoncreds/utils/identifiers' +import { CheqdLedgerService } from '../ledger' + +import { filterResourcesByNameAndType, getClosestResourceVersion, renderResourceData } from './didCheqdUtil' + +export class CheqdDidResolver implements DidResolver { + public readonly supportedMethods = ['cheqd'] + + public async resolve(agentContext: AgentContext, did: string, parsed: ParsedDid): Promise { + const didDocumentMetadata = {} + + try { + const parsedDid = parseCheqdDid(parsed.didUrl) + if (!parsedDid) { + throw new Error('Invalid DID') + } + + switch (did) { + case did.match(cheqdDidRegex)?.input: + return await this.resolveDidDoc(agentContext, parsedDid.did) + case did.match(cheqdDidVersionRegex)?.input: { + const version = did.split('/')[2] + return await this.resolveDidDoc(agentContext, parsedDid.did, version) + } + case did.match(cheqdDidVersionsRegex)?.input: + return await this.resolveAllDidDocVersions(agentContext, parsedDid) + case did.match(cheqdDidMetadataRegex)?.input: + return await this.dereferenceCollectionResources(agentContext, parsedDid) + case did.match(cheqdResourceMetadataRegex)?.input: + return await this.dereferenceResourceMetadata(agentContext, parsedDid) + default: + return { + didDocument: null, + didDocumentMetadata, + didResolutionMetadata: { + error: 'Invalid request', + message: `Unsupported did Url: '${did}'`, + }, + } + } + } catch (error) { + return { + didDocument: null, + didDocumentMetadata, + didResolutionMetadata: { + error: 'notFound', + message: `resolver_error: Unable to resolve did '${did}': ${error}`, + }, + } + } + } + + public async resolveResource(agentContext: AgentContext, did: string) { + const cheqdLedgerService = agentContext.dependencyManager.resolve(CheqdLedgerService) + try { + const parsedDid = parseCheqdDid(did) + if (!parsedDid) { + throw new Error('Invalid DID') + } + + const { params, id } = parsedDid + let resourceId: string + if (did.match(cheqdResourceRegex)?.input) { + resourceId = did.split('/')[2] + } else if (params && params.resourceName && params.resourceType) { + let resources = (await cheqdLedgerService.resolveCollectionResources(parsedDid.did, id)).resources + resources = filterResourcesByNameAndType(resources, params.resourceName, params.resourceType) + if (!resources.length) { + throw new Error(`No resources found`) + } + + let resource: Metadata | undefined + if (params.version) { + resource = resources.find((resource) => resource.version == params.version) + } else { + const date = params.resourceVersionTime ? new Date(Number(params.resourceVersionTime) * 1000) : new Date() + // find the resourceId closest to the created time + resource = getClosestResourceVersion(resources, date) + } + + if (!resource) { + throw new Error(`No resources found`) + } + + resourceId = resource.id + } else { + return { + error: 'notFound', + message: `resolver_error: Invalid did url '${did}'`, + } + } + if (!utils.isValidUuid(resourceId)) { + throw new Error('Invalid resource Id') + } + + const { resource, metadata } = await cheqdLedgerService.resolveResource(parsedDid.did, id, resourceId) + if (!resource || !metadata) { + throw new Error('resolver_error: Unable to resolve resource, Please try again') + } + + const result = await renderResourceData(resource.data, metadata.mediaType) + return { + resource: result, + resourceMetadata: metadata, + resourceResolutionMetadata: {}, + } + } catch (error) { + return { + error: 'notFound', + message: `resolver_error: Unable to resolve resource '${did}': ${error}`, + } + } + } + + private async resolveAllDidDocVersions(agentContext: AgentContext, parsedDid: ParsedCheqdDid) { + const cheqdLedgerService = agentContext.dependencyManager.resolve(CheqdLedgerService) + const { did } = parsedDid + + const { didDocumentVersionsMetadata } = await cheqdLedgerService.resolveMetadata(did) + return { + didDocument: new DidDocument({ id: did }), + didDocumentMetadata: didDocumentVersionsMetadata, + didResolutionMetadata: {}, + } + } + + private async dereferenceCollectionResources(agentContext: AgentContext, parsedDid: ParsedCheqdDid) { + const cheqdLedgerService = agentContext.dependencyManager.resolve(CheqdLedgerService) + const { did, id } = parsedDid + + const metadata = await cheqdLedgerService.resolveCollectionResources(did, id) + return { + didDocument: new DidDocument({ id: did }), + didDocumentMetadata: { + linkedResourceMetadata: metadata, + }, + didResolutionMetadata: {}, + } + } + + private async dereferenceResourceMetadata(agentContext: AgentContext, parsedDid: ParsedCheqdDid) { + const cheqdLedgerService = agentContext.dependencyManager.resolve(CheqdLedgerService) + const { did, id } = parsedDid + + if (!parsedDid.path) { + throw new AriesFrameworkError(`Missing path in did ${parsedDid.did}`) + } + + const [, , resourceId] = parsedDid.path.split('/') + + if (!resourceId) { + throw new AriesFrameworkError(`Missing resource id in didUrl ${parsedDid.didUrl}`) + } + + const metadata = await cheqdLedgerService.resolveResourceMetadata(did, id, resourceId) + return { + didDocument: new DidDocument({ id: did }), + didDocumentMetadata: { + linkedResourceMetadata: metadata, + }, + didResolutionMetadata: {}, + } + } + + private async resolveDidDoc(agentContext: AgentContext, did: string, version?: string): Promise { + const cheqdLedgerService = agentContext.dependencyManager.resolve(CheqdLedgerService) + + const { didDocument, didDocumentMetadata } = await cheqdLedgerService.resolve(did, version) + const { resources } = await cheqdLedgerService.resolveCollectionResources(did, did.split(':')[3]) + didDocumentMetadata.linkedResourceMetadata = resources + + return { + didDocument: JsonTransformer.fromJSON(didDocument, DidDocument), + didDocumentMetadata, + didResolutionMetadata: {}, + } + } +} diff --git a/packages/cheqd/src/dids/didCheqdUtil.ts b/packages/cheqd/src/dids/didCheqdUtil.ts new file mode 100644 index 0000000000..97c193292e --- /dev/null +++ b/packages/cheqd/src/dids/didCheqdUtil.ts @@ -0,0 +1,157 @@ +import type { CheqdNetwork, DIDDocument, MethodSpecificIdAlgo, TVerificationKey } from '@cheqd/sdk' +import type { Metadata } from '@cheqd/ts-proto/cheqd/resource/v2' + +import { + DidDocument, + AriesFrameworkError, + JsonEncoder, + TypedArrayEncoder, + JsonTransformer, +} from '@aries-framework/core' +import { + createDidPayload, + createDidVerificationMethod, + createVerificationKeys, + DIDModule, + VerificationMethods, +} from '@cheqd/sdk' +import { MsgCreateDidDocPayload, MsgDeactivateDidDocPayload } from '@cheqd/ts-proto/cheqd/did/v2' +import { EnglishMnemonic as _ } from '@cosmjs/crypto' +import { DirectSecp256k1HdWallet, DirectSecp256k1Wallet } from '@cosmjs/proto-signing' + +export function validateSpecCompliantPayload(didDocument: DidDocument): SpecValidationResult { + // id is required, validated on both compile and runtime + if (!didDocument.id && !didDocument.id.startsWith('did:cheqd:')) return { valid: false, error: 'id is required' } + + // verificationMethod is required + if (!didDocument.verificationMethod) return { valid: false, error: 'verificationMethod is required' } + + // verificationMethod must be an array + if (!Array.isArray(didDocument.verificationMethod)) + return { valid: false, error: 'verificationMethod must be an array' } + + // verificationMethod must be not be empty + if (!didDocument.verificationMethod.length) return { valid: false, error: 'verificationMethod must be not be empty' } + + // verificationMethod types must be supported + const isValidVerificationMethod = didDocument.verificationMethod.every((vm) => { + switch (vm.type) { + case VerificationMethods.Ed255192020: + return vm.publicKeyMultibase != null + case VerificationMethods.JWK: + return vm.publicKeyJwk != null + case VerificationMethods.Ed255192018: + return vm.publicKeyBase58 != null + default: + return false + } + }) + + if (!isValidVerificationMethod) return { valid: false, error: 'verificationMethod publicKey is Invalid' } + + const isValidService = didDocument.service + ? didDocument?.service?.every((s) => { + return s?.serviceEndpoint && s?.id && s?.type + }) + : true + + if (!isValidService) return { valid: false, error: 'Service is Invalid' } + return { valid: true } as SpecValidationResult +} + +// Create helpers in sdk like MsgCreateDidDocPayload.fromDIDDocument to replace the below +export async function createMsgCreateDidDocPayloadToSign(didPayload: DIDDocument, versionId: string) { + didPayload.service = didPayload.service?.map((e) => { + return { + ...e, + serviceEndpoint: Array.isArray(e.serviceEndpoint) ? e.serviceEndpoint : [e.serviceEndpoint], + } + }) + const { protobufVerificationMethod, protobufService } = await DIDModule.validateSpecCompliantPayload(didPayload) + return MsgCreateDidDocPayload.encode( + MsgCreateDidDocPayload.fromPartial({ + context: didPayload?.['@context'], + id: didPayload.id, + controller: didPayload.controller, + verificationMethod: protobufVerificationMethod, + authentication: didPayload.authentication, + assertionMethod: didPayload.assertionMethod, + capabilityInvocation: didPayload.capabilityInvocation, + capabilityDelegation: didPayload.capabilityDelegation, + keyAgreement: didPayload.keyAgreement, + service: protobufService, + alsoKnownAs: didPayload.alsoKnownAs, + versionId, + }) + ).finish() +} + +export function createMsgDeactivateDidDocPayloadToSign(didPayload: DIDDocument, versionId?: string) { + return MsgDeactivateDidDocPayload.encode( + MsgDeactivateDidDocPayload.fromPartial({ + id: didPayload.id, + versionId, + }) + ).finish() +} + +export type SpecValidationResult = { + valid: boolean + error?: string +} + +export function generateDidDoc(options: IDidDocOptions) { + const { verificationMethod, methodSpecificIdAlgo, verificationMethodId, network, publicKey } = options + const verificationKeys = createVerificationKeys(publicKey, methodSpecificIdAlgo, verificationMethodId, network) + if (!verificationKeys) { + throw new Error('Invalid DID options') + } + const verificationMethods = createDidVerificationMethod([verificationMethod], [verificationKeys]) + const didPayload = createDidPayload(verificationMethods, [verificationKeys]) + return JsonTransformer.fromJSON(didPayload, DidDocument) +} + +export interface IDidDocOptions { + verificationMethod: VerificationMethods + verificationMethodId: TVerificationKey + methodSpecificIdAlgo: MethodSpecificIdAlgo + network: CheqdNetwork + publicKey: string +} + +export function getClosestResourceVersion(resources: Metadata[], date: Date) { + const result = resources.sort(function (a, b) { + if (!a.created || !b.created) throw new AriesFrameworkError("Missing required property 'created' on resource") + const distancea = Math.abs(date.getTime() - a.created.getTime()) + const distanceb = Math.abs(date.getTime() - b.created.getTime()) + return distancea - distanceb + }) + return result[0] +} + +export function filterResourcesByNameAndType(resources: Metadata[], name: string, type: string) { + return resources.filter((resource) => resource.name == name && resource.resourceType == type) +} + +export async function renderResourceData(data: Uint8Array, mimeType: string) { + if (mimeType == 'application/json') { + return await JsonEncoder.fromBuffer(data) + } else if (mimeType == 'text/plain') { + return TypedArrayEncoder.toUtf8String(data) + } else { + return TypedArrayEncoder.toBase64URL(data) + } +} + +export class EnglishMnemonic extends _ { + public static readonly _mnemonicMatcher = /^[a-z]+( [a-z]+)*$/ +} + +export function getCosmosPayerWallet(cosmosPayerSeed?: string) { + if (!cosmosPayerSeed || cosmosPayerSeed === '') { + return DirectSecp256k1HdWallet.generate() + } + return EnglishMnemonic._mnemonicMatcher.test(cosmosPayerSeed) + ? DirectSecp256k1HdWallet.fromMnemonic(cosmosPayerSeed, { prefix: 'cheqd' }) + : DirectSecp256k1Wallet.fromKey(TypedArrayEncoder.fromString(cosmosPayerSeed.replace(/^0x/, '')), 'cheqd') +} diff --git a/packages/cheqd/src/dids/index.ts b/packages/cheqd/src/dids/index.ts new file mode 100644 index 0000000000..315b1c0982 --- /dev/null +++ b/packages/cheqd/src/dids/index.ts @@ -0,0 +1,8 @@ +export { + CheqdDidRegistrar, + CheqdDidCreateOptions, + CheqdDidDeactivateOptions, + CheqdDidUpdateOptions, + CheqdCreateResourceOptions, +} from './CheqdDidRegistrar' +export { CheqdDidResolver } from './CheqdDidResolver' diff --git a/packages/cheqd/src/index.ts b/packages/cheqd/src/index.ts new file mode 100644 index 0000000000..4270e5c072 --- /dev/null +++ b/packages/cheqd/src/index.ts @@ -0,0 +1,17 @@ +// Dids +export { + CheqdDidRegistrar, + CheqdDidCreateOptions, + CheqdDidDeactivateOptions, + CheqdDidUpdateOptions, + CheqdDidResolver, +} from './dids' + +// AnonCreds +export { CheqdAnonCredsRegistry } from './anoncreds' + +export { CheqdLedgerService } from './ledger' + +export { CheqdModule } from './CheqdModule' + +export { CheqdModuleConfig, CheqdModuleConfigOptions } from './CheqdModuleConfig' diff --git a/packages/cheqd/src/ledger/CheqdLedgerService.ts b/packages/cheqd/src/ledger/CheqdLedgerService.ts new file mode 100644 index 0000000000..36adf6eb2a --- /dev/null +++ b/packages/cheqd/src/ledger/CheqdLedgerService.ts @@ -0,0 +1,123 @@ +import type { AbstractCheqdSDKModule, CheqdSDK, DidStdFee, DIDDocument } from '@cheqd/sdk' +import type { SignInfo } from '@cheqd/ts-proto/cheqd/did/v2' +import type { MsgCreateResourcePayload } from '@cheqd/ts-proto/cheqd/resource/v2' +import type { DirectSecp256k1HdWallet, DirectSecp256k1Wallet } from '@cosmjs/proto-signing' + +import { injectable } from '@aries-framework/core' +import { createCheqdSDK, DIDModule, ResourceModule, CheqdNetwork } from '@cheqd/sdk' + +import { CheqdModuleConfig } from '../CheqdModuleConfig' +import { parseCheqdDid } from '../anoncreds/utils/identifiers' +import { getCosmosPayerWallet } from '../dids/didCheqdUtil' + +export interface ICheqdLedgerConfig { + network: string + rpcUrl: string + readonly cosmosPayerWallet: Promise + sdk?: CheqdSDK +} + +export enum DefaultRPCUrl { + Mainnet = 'https://rpc.cheqd.net', + Testnet = 'https://rpc.cheqd.network', +} + +@injectable() +export class CheqdLedgerService { + private networks: ICheqdLedgerConfig[] + + public constructor(cheqdSdkModuleConfig: CheqdModuleConfig) { + this.networks = cheqdSdkModuleConfig.networks.map((config) => { + const { network, rpcUrl, cosmosPayerSeed } = config + return { + network, + rpcUrl: rpcUrl ? rpcUrl : network === CheqdNetwork.Mainnet ? DefaultRPCUrl.Mainnet : DefaultRPCUrl.Testnet, + cosmosPayerWallet: getCosmosPayerWallet(cosmosPayerSeed), + } + }) + } + + public async connect() { + for (const network of this.networks) { + network.sdk = await createCheqdSDK({ + modules: [DIDModule as unknown as AbstractCheqdSDKModule, ResourceModule as unknown as AbstractCheqdSDKModule], + rpcUrl: network.rpcUrl, + wallet: await network.cosmosPayerWallet.catch(() => { + throw new Error(`[did-provider-cheqd]: valid cosmosPayerSeed is required`) + }), + }) + } + } + + private getSdk(did: string) { + const parsedDid = parseCheqdDid(did) + if (!parsedDid) { + throw new Error('Invalid DID') + } + if (this.networks.length === 0) { + throw new Error('No cheqd networks configured') + } + + const network = this.networks.find((network) => network.network === parsedDid.network) + if (!network || !network.sdk) { + throw new Error('Network not configured') + } + return network.sdk + } + + public async create( + didPayload: DIDDocument, + signInputs: SignInfo[], + versionId?: string | undefined, + fee?: DidStdFee + ) { + return await this.getSdk(didPayload.id).createDidDocTx(signInputs, didPayload, '', fee, undefined, versionId) + } + + public async update( + didPayload: DIDDocument, + signInputs: SignInfo[], + versionId?: string | undefined, + fee?: DidStdFee + ) { + return await this.getSdk(didPayload.id).updateDidDocTx(signInputs, didPayload, '', fee, undefined, versionId) + } + + public async deactivate( + didPayload: DIDDocument, + signInputs: SignInfo[], + versionId?: string | undefined, + fee?: DidStdFee + ) { + return await this.getSdk(didPayload.id).deactivateDidDocTx(signInputs, didPayload, '', fee, undefined, versionId) + } + + public async resolve(did: string, version?: string) { + return version ? await this.getSdk(did).queryDidDocVersion(did, version) : await this.getSdk(did).queryDidDoc(did) + } + + public async resolveMetadata(did: string) { + return await this.getSdk(did).queryAllDidDocVersionsMetadata(did) + } + + public async createResource( + did: string, + resourcePayload: Partial, + signInputs: SignInfo[], + fee?: DidStdFee + ) { + return await this.getSdk(did).createLinkedResourceTx(signInputs, resourcePayload, '', fee, undefined) + } + + public async resolveResource(did: string, collectionId: string, resourceId: string) { + return await this.getSdk(did).queryLinkedResource(collectionId, resourceId) + } + + public async resolveCollectionResources(did: string, collectionId: string) { + return await this.getSdk(did).queryLinkedResources(collectionId) + } + + public async resolveResourceMetadata(did: string, collectionId: string, resourceId: string) { + return await this.getSdk(did).queryLinkedResourceMetadata(collectionId, resourceId) + } +} diff --git a/packages/cheqd/src/ledger/index.ts b/packages/cheqd/src/ledger/index.ts new file mode 100644 index 0000000000..db2eec776a --- /dev/null +++ b/packages/cheqd/src/ledger/index.ts @@ -0,0 +1 @@ +export { CheqdLedgerService } from './CheqdLedgerService' diff --git a/packages/cheqd/tests/cheqd-did-registrar.e2e.test.ts b/packages/cheqd/tests/cheqd-did-registrar.e2e.test.ts new file mode 100644 index 0000000000..d47463ba7e --- /dev/null +++ b/packages/cheqd/tests/cheqd-did-registrar.e2e.test.ts @@ -0,0 +1,129 @@ +import type { CheqdDidCreateOptions } from '../src' +import type { DidDocument } from '@aries-framework/core' + +import { Agent, TypedArrayEncoder } from '@aries-framework/core' +import { generateKeyPairFromSeed } from '@stablelib/ed25519' + +import { getAgentOptions } from '../../core/tests/helpers' + +import { validService } from './setup' +import { getCheqdModules } from './setupCheqdModule' + +const agentOptions = getAgentOptions('Faber Dids Registrar', {}, getCheqdModules()) + +describe('Cheqd DID registrar', () => { + let agent: Agent> + + beforeAll(async () => { + agent = new Agent(agentOptions) + await agent.initialize() + }) + + afterAll(async () => { + await agent.shutdown() + await agent.wallet.delete() + }) + + it('should create a did:cheqd did', async () => { + // Generate a seed and the cheqd did. This allows us to create a new did every time + // but still check if the created output document is as expected. + const privateKey = TypedArrayEncoder.fromString( + Array(32 + 1) + .join((Math.random().toString(36) + '00000000000000000').slice(2, 18)) + .slice(0, 32) + ) + const publicKeyEd25519 = generateKeyPairFromSeed(privateKey).publicKey + const ed25519PublicKeyBase58 = TypedArrayEncoder.toBase58(publicKeyEd25519) + const did = await agent.dids.create({ + method: 'cheqd', + secret: { + verificationMethod: { + id: 'key-1', + type: 'Ed25519VerificationKey2018', + privateKey, + }, + }, + options: { + network: 'testnet', + methodSpecificIdAlgo: 'base58btc', + }, + }) + expect(did).toMatchObject({ + didState: { + state: 'finished', + didDocument: { + verificationMethod: [ + { + type: 'Ed25519VerificationKey2018', + publicKeyBase58: ed25519PublicKeyBase58, + }, + ], + }, + }, + }) + }) + + it('should create a did:cheqd using Ed25519VerificationKey2020', async () => { + const did = await agent.dids.create({ + method: 'cheqd', + secret: { + verificationMethod: { + id: 'key-1', + type: 'Ed25519VerificationKey2020', + }, + }, + options: { + network: 'testnet', + methodSpecificIdAlgo: 'uuid', + }, + }) + expect(did.didState).toMatchObject({ state: 'finished' }) + }) + + it('should create a did:cheqd using JsonWebKey2020', async () => { + const createResult = await agent.dids.create({ + method: 'cheqd', + secret: { + verificationMethod: { + id: 'key-11', + type: 'JsonWebKey2020', + }, + }, + options: { + network: 'testnet', + methodSpecificIdAlgo: 'base58btc', + }, + }) + + expect(createResult).toMatchObject({ + didState: { + state: 'finished', + didDocument: { + verificationMethod: [{ type: 'JsonWebKey2020' }], + }, + }, + }) + expect(createResult.didState.did).toBeDefined() + const did = createResult.didState.did as string + const didDocument = createResult.didState.didDocument as DidDocument + didDocument.service = [validService(did)] + + const updateResult = await agent.dids.update({ + did, + didDocument, + }) + expect(updateResult).toMatchObject({ + didState: { + state: 'finished', + didDocument, + }, + }) + + const deactivateResult = await agent.dids.deactivate({ did }) + expect(deactivateResult.didState.didDocument?.toJSON()).toMatchObject(didDocument.toJSON()) + expect(deactivateResult.didState.state).toEqual('finished') + + const resolvedDocument = await agent.dids.resolve(did) + expect(resolvedDocument.didDocumentMetadata.deactivated).toBe(true) + }) +}) diff --git a/packages/cheqd/tests/cheqd-did-resolver.e2e.test.ts b/packages/cheqd/tests/cheqd-did-resolver.e2e.test.ts new file mode 100644 index 0000000000..4dd7302da0 --- /dev/null +++ b/packages/cheqd/tests/cheqd-did-resolver.e2e.test.ts @@ -0,0 +1,72 @@ +import { Agent, JsonTransformer } from '@aries-framework/core' + +import { getAgentOptions } from '../../core/tests/helpers' +import { getClosestResourceVersion } from '../src/dids/didCheqdUtil' +import { DefaultRPCUrl } from '../src/ledger/CheqdLedgerService' + +import { getCheqdModules } from './setupCheqdModule' + +export const resolverAgent = new Agent( + getAgentOptions('Cheqd resolver', {}, getCheqdModules(undefined, DefaultRPCUrl.Testnet)) +) + +describe('Cheqd DID resolver', () => { + beforeAll(async () => { + await resolverAgent.initialize() + }) + + afterAll(async () => { + await resolverAgent.shutdown() + await resolverAgent.wallet.delete() + }) + + it('should resolve a did:cheqd:testnet did', async () => { + const did = await resolverAgent.dids.resolve('did:cheqd:testnet:3053e034-8faa-458d-9f01-2e3e1e8b2ab8') + expect(JsonTransformer.toJSON(did)).toMatchObject({ + didDocument: { + '@context': ['https://www.w3.org/ns/did/v1', 'https://w3id.org/security/suites/ed25519-2020/v1'], + id: 'did:cheqd:testnet:3053e034-8faa-458d-9f01-2e3e1e8b2ab8', + controller: ['did:cheqd:testnet:3053e034-8faa-458d-9f01-2e3e1e8b2ab8'], + verificationMethod: [ + { + controller: 'did:cheqd:testnet:3053e034-8faa-458d-9f01-2e3e1e8b2ab8', + id: 'did:cheqd:testnet:3053e034-8faa-458d-9f01-2e3e1e8b2ab8#key-1', + publicKeyMultibase: 'z6MksPpyxgw5aFymMboa81CQ7h1kJJ9yehNzPgo714y1HrAA', + type: 'Ed25519VerificationKey2020', + }, + ], + authentication: ['did:cheqd:testnet:3053e034-8faa-458d-9f01-2e3e1e8b2ab8#key-1'], + }, + didDocumentMetadata: { + created: '2022-10-17T13:42:37.000Z', + updated: '0001-01-01T00:00:00.000Z', + deactivated: false, + versionId: '7314e3e5-f9cc-50e9-b249-348963937c96', + nextVersionId: '', + }, + didResolutionMetadata: {}, + }) + }) + + it('should getClosestResourceVersion', async () => { + const did = await resolverAgent.dids.resolve('did:cheqd:testnet:SiVQgrFZ7jFZFrTGstT4ZD') + let resource = getClosestResourceVersion(did.didDocumentMetadata.linkedResourceMetadata, new Date()) + expect(resource).toMatchObject({ + id: '0b02ebf4-07c4-4df7-9015-e93c21108240', + }) + resource = getClosestResourceVersion( + did.didDocumentMetadata.linkedResourceMetadata, + new Date('2022-11-16T10:56:34Z') + ) + expect(resource).toMatchObject({ + id: '8140ec3a-d8bb-4f59-9784-a1cbf91a4a35', + }) + resource = getClosestResourceVersion( + did.didDocumentMetadata.linkedResourceMetadata, + new Date('2022-11-16T11:41:48Z') + ) + expect(resource).toMatchObject({ + id: 'a20aa56a-a76f-4828-8a98-4c85d9494545', + }) + }) +}) diff --git a/packages/cheqd/tests/cheqd-did-utils.e2e.test.ts b/packages/cheqd/tests/cheqd-did-utils.e2e.test.ts new file mode 100644 index 0000000000..cec86ac799 --- /dev/null +++ b/packages/cheqd/tests/cheqd-did-utils.e2e.test.ts @@ -0,0 +1,48 @@ +import type { DIDDocument } from '@cheqd/sdk' + +import { DidDocument } from '@aries-framework/core' + +import { + createMsgCreateDidDocPayloadToSign, + createMsgDeactivateDidDocPayloadToSign, + validateSpecCompliantPayload, +} from '../src/dids/didCheqdUtil' + +import { validDid, validDidDoc } from './setup' + +describe('Test Cheqd Did Utils', () => { + it('should validate did spec compliant payload', () => { + const didDoc = validDidDoc() + const result = validateSpecCompliantPayload(didDoc) + expect(result.valid).toBe(true) + expect(result.error).toBeUndefined() + }) + + it('should detect invalid verification method', () => { + const result = validateSpecCompliantPayload( + new DidDocument({ + id: validDid, + verificationMethod: [ + { + id: validDid + '#key-1', + publicKeyBase58: 'asca12e3as', + type: 'JsonWebKey2020', + controller: validDid, + }, + ], + }) + ) + expect(result.valid).toBe(false) + expect(result.error).toBeDefined() + }) + + it('should create MsgCreateDidDocPayloadToSign', async () => { + const result = await createMsgCreateDidDocPayloadToSign(validDidDoc().toJSON() as DIDDocument, '1.0') + expect(result).toBeDefined() + }) + + it('should create MsgDeactivateDidDocPayloadToSign', async () => { + const result = createMsgDeactivateDidDocPayloadToSign({ id: validDid }, '2.0') + expect(result).toBeDefined() + }) +}) diff --git a/packages/cheqd/tests/cheqd-sdk-anoncreds-registry.e2e.test.ts b/packages/cheqd/tests/cheqd-sdk-anoncreds-registry.e2e.test.ts new file mode 100644 index 0000000000..32d99d9918 --- /dev/null +++ b/packages/cheqd/tests/cheqd-sdk-anoncreds-registry.e2e.test.ts @@ -0,0 +1,246 @@ +import type { CheqdDidCreateOptions } from '../src' + +import { Agent, JsonTransformer, TypedArrayEncoder } from '@aries-framework/core' + +import { agentDependencies, getAgentConfig } from '../../core/tests/helpers' +import { CheqdAnonCredsRegistry } from '../src/anoncreds' + +import { resolverAgent } from './cheqd-did-resolver.e2e.test' +import { getCheqdModules } from './setupCheqdModule' + +const agentConfig = getAgentConfig('cheqdAnonCredsRegistry') + +const agent = new Agent({ + config: agentConfig, + dependencies: agentDependencies, + modules: getCheqdModules( + 'ugly dirt sorry girl prepare argue door man that manual glow scout bomb pigeon matter library transfer flower clown cat miss pluck drama dizzy' + ), +}) + +const cheqdAnonCredsRegistry = new CheqdAnonCredsRegistry() + +let issuerId: string + +describe('cheqdAnonCredsRegistry', () => { + beforeAll(async () => { + await agent.initialize() + }) + + afterAll(async () => { + await agent.shutdown() + await agent.wallet.delete() + }) + + // One test as the credential definition depends on the schema + test('register and resolve a schema and credential definition', async () => { + const privateKey = TypedArrayEncoder.fromString('000000000000000000000000000cheqd') + + const did = await agent.dids.create({ + method: 'cheqd', + secret: { + verificationMethod: { + id: 'key-10', + type: 'Ed25519VerificationKey2020', + privateKey, + }, + }, + options: { + network: 'testnet', + methodSpecificIdAlgo: 'uuid', + }, + }) + expect(did.didState).toMatchObject({ state: 'finished' }) + issuerId = did.didState.did as string + + const dynamicVersion = `1.${Math.random() * 100}` + + const schemaResult = await cheqdAnonCredsRegistry.registerSchema(agent.context, { + schema: { + attrNames: ['name'], + issuerId, + name: 'test11', + version: dynamicVersion, + }, + options: {}, + }) + + expect(JsonTransformer.toJSON(schemaResult)).toMatchObject({ + schemaState: { + state: 'finished', + schema: { + attrNames: ['name'], + issuerId, + name: 'test11', + version: dynamicVersion, + }, + schemaId: `${schemaResult.schemaState.schemaId}`, + }, + registrationMetadata: {}, + schemaMetadata: {}, + }) + + const schemaResponse = await cheqdAnonCredsRegistry.getSchema(agent.context, `${schemaResult.schemaState.schemaId}`) + expect(schemaResponse).toMatchObject({ + schema: { + attrNames: ['name'], + name: 'test11', + version: dynamicVersion, + issuerId, + }, + schemaId: `${schemaResult.schemaState.schemaId}`, + resolutionMetadata: {}, + schemaMetadata: {}, + }) + + const credentialDefinitionResult = await cheqdAnonCredsRegistry.registerCredentialDefinition(agent.context, { + credentialDefinition: { + issuerId, + tag: 'TAG', + schemaId: `${schemaResult.schemaState.schemaId}`, + type: 'CL', + value: { + primary: { + n: '92511867718854414868106363741369833735017762038454769060600859608405811709675033445666654908195955460485998711087020152978597220168927505650092431295783175164390266561239892662085428655566792056852960599485298025843840058914610127716620252006466964070280255168745873592143068949458568751438337748294055976926080232538440619420568859737673474560851456027625679328271511966332808025880807996449998057729417608399774744254122385012832309402226532031122728445959276178939234308090390331654445053482963947804769291501664200141562885660084823885847247231002821472258218384342423605116504024514572826071246440130942849549441', + s: '80388543865249952799447792504739237616187770512259677275061283897050980768551818104137338144380636412773836688624071360386172349725818126495487584981520630638409717065318132420766896092370913800616033623618952639023946750307405126873476182540669638841562357523429245685476919178722373320218824590869735129801004394337640642997250464303104754942997839179333543643110326022824394934965538190976474473353762308333205671176627192797138375084260446324344637548455228161138089974447059481109651156379803576163576511072261388342837813901850712083922506433336723723235701670225584863772222447543742649328218950436824219992164', + r: { + age: '676933340341980399002624386891134393471002096508227567343731826159610079436978196421307099268754545293545727546242372579987825752872485684085629459107300175443328323289748793060894500514926703654606851666031895448970879827423190730510730624784665299646624113512701254199984520803796529034094958026048762178753193812250643294518237843809104055653333871102658177900702978008644780459400512716361564897282969982554031820285585105004870317861287847206222714589633178648982299799311192432563797220854755882933052881306804544233529886513105815543097685128456041780804442879272476590077760678785460726492895806240870944398', + master_secret: + '57770757113548032970308439965749734133430520933173186296299026579579930337912607419798836831937319372744879560676750427054135869214212225572618340088847222727882935159356459822445182287686057012197046378986248048722180093079919306125315662058290895629438767985427829790980355162853804522854494960613869765167538645624719923127052541372069255024631093663068055100579264049925388231368871107383977060590248865498902704546409806115171120555709438784189721957301548212242748685629860268468247494986146122636455769804467583612610341632602695197189514316033637331733820369170763954604394734655429769801516997967996980978751', + }, + rctxt: + '19574881057684356733946284215946569464410211018678168661028327420122678446653210056362495902735819742274128834330867933095119512313591151219353395069123546495720010325822330866859140765940839241212947354612836044244554152389691282543839111284006009168728161183863936810142428875817934316327118674532328892591410224676539770085459540786747902789677759379901079898127879301595929571621032704093287675668250862222728331030586585586110859977896767318814398026750215625180255041545607499673023585546720788973882263863911222208020438685873501025545464213035270207099419236974668665979962146355749687924650853489277747454993', + z: '18569464356833363098514177097771727133940629758890641648661259687745137028161881113251218061243607037717553708179509640909238773964066423807945164288256211132195919975343578956381001087353353060599758005375631247614777454313440511375923345538396573548499287265163879524050255226779884271432737062283353279122281220812931572456820130441114446870167673796490210349453498315913599982158253821945225264065364670730546176140788405935081171854642125236557475395879246419105888077042924382595999612137336915304205628167917473420377397118829734604949103124514367857266518654728464539418834291071874052392799652266418817991437', + }, + }, + }, + options: {}, + }) + + expect(credentialDefinitionResult).toMatchObject({ + credentialDefinitionState: { + credentialDefinition: { + issuerId, + tag: 'TAG', + schemaId: `${schemaResult.schemaState.schemaId}`, + type: 'CL', + value: { + primary: { + n: '92511867718854414868106363741369833735017762038454769060600859608405811709675033445666654908195955460485998711087020152978597220168927505650092431295783175164390266561239892662085428655566792056852960599485298025843840058914610127716620252006466964070280255168745873592143068949458568751438337748294055976926080232538440619420568859737673474560851456027625679328271511966332808025880807996449998057729417608399774744254122385012832309402226532031122728445959276178939234308090390331654445053482963947804769291501664200141562885660084823885847247231002821472258218384342423605116504024514572826071246440130942849549441', + s: '80388543865249952799447792504739237616187770512259677275061283897050980768551818104137338144380636412773836688624071360386172349725818126495487584981520630638409717065318132420766896092370913800616033623618952639023946750307405126873476182540669638841562357523429245685476919178722373320218824590869735129801004394337640642997250464303104754942997839179333543643110326022824394934965538190976474473353762308333205671176627192797138375084260446324344637548455228161138089974447059481109651156379803576163576511072261388342837813901850712083922506433336723723235701670225584863772222447543742649328218950436824219992164', + r: { + age: '676933340341980399002624386891134393471002096508227567343731826159610079436978196421307099268754545293545727546242372579987825752872485684085629459107300175443328323289748793060894500514926703654606851666031895448970879827423190730510730624784665299646624113512701254199984520803796529034094958026048762178753193812250643294518237843809104055653333871102658177900702978008644780459400512716361564897282969982554031820285585105004870317861287847206222714589633178648982299799311192432563797220854755882933052881306804544233529886513105815543097685128456041780804442879272476590077760678785460726492895806240870944398', + master_secret: + '57770757113548032970308439965749734133430520933173186296299026579579930337912607419798836831937319372744879560676750427054135869214212225572618340088847222727882935159356459822445182287686057012197046378986248048722180093079919306125315662058290895629438767985427829790980355162853804522854494960613869765167538645624719923127052541372069255024631093663068055100579264049925388231368871107383977060590248865498902704546409806115171120555709438784189721957301548212242748685629860268468247494986146122636455769804467583612610341632602695197189514316033637331733820369170763954604394734655429769801516997967996980978751', + }, + rctxt: + '19574881057684356733946284215946569464410211018678168661028327420122678446653210056362495902735819742274128834330867933095119512313591151219353395069123546495720010325822330866859140765940839241212947354612836044244554152389691282543839111284006009168728161183863936810142428875817934316327118674532328892591410224676539770085459540786747902789677759379901079898127879301595929571621032704093287675668250862222728331030586585586110859977896767318814398026750215625180255041545607499673023585546720788973882263863911222208020438685873501025545464213035270207099419236974668665979962146355749687924650853489277747454993', + z: '18569464356833363098514177097771727133940629758890641648661259687745137028161881113251218061243607037717553708179509640909238773964066423807945164288256211132195919975343578956381001087353353060599758005375631247614777454313440511375923345538396573548499287265163879524050255226779884271432737062283353279122281220812931572456820130441114446870167673796490210349453498315913599982158253821945225264065364670730546176140788405935081171854642125236557475395879246419105888077042924382595999612137336915304205628167917473420377397118829734604949103124514367857266518654728464539418834291071874052392799652266418817991437', + }, + }, + }, + credentialDefinitionId: `${credentialDefinitionResult.credentialDefinitionState.credentialDefinitionId}`, + state: 'finished', + }, + }) + + const credentialDefinitionResponse = await cheqdAnonCredsRegistry.getCredentialDefinition( + agent.context, + credentialDefinitionResult.credentialDefinitionState.credentialDefinitionId as string + ) + + expect(credentialDefinitionResponse).toMatchObject({ + credentialDefinitionId: `${credentialDefinitionResult.credentialDefinitionState.credentialDefinitionId}`, + credentialDefinition: { + issuerId, + schemaId: `${schemaResult.schemaState.schemaId}`, + tag: 'TAG', + type: 'CL', + value: { + primary: { + n: '92511867718854414868106363741369833735017762038454769060600859608405811709675033445666654908195955460485998711087020152978597220168927505650092431295783175164390266561239892662085428655566792056852960599485298025843840058914610127716620252006466964070280255168745873592143068949458568751438337748294055976926080232538440619420568859737673474560851456027625679328271511966332808025880807996449998057729417608399774744254122385012832309402226532031122728445959276178939234308090390331654445053482963947804769291501664200141562885660084823885847247231002821472258218384342423605116504024514572826071246440130942849549441', + r: { + age: '676933340341980399002624386891134393471002096508227567343731826159610079436978196421307099268754545293545727546242372579987825752872485684085629459107300175443328323289748793060894500514926703654606851666031895448970879827423190730510730624784665299646624113512701254199984520803796529034094958026048762178753193812250643294518237843809104055653333871102658177900702978008644780459400512716361564897282969982554031820285585105004870317861287847206222714589633178648982299799311192432563797220854755882933052881306804544233529886513105815543097685128456041780804442879272476590077760678785460726492895806240870944398', + master_secret: + '57770757113548032970308439965749734133430520933173186296299026579579930337912607419798836831937319372744879560676750427054135869214212225572618340088847222727882935159356459822445182287686057012197046378986248048722180093079919306125315662058290895629438767985427829790980355162853804522854494960613869765167538645624719923127052541372069255024631093663068055100579264049925388231368871107383977060590248865498902704546409806115171120555709438784189721957301548212242748685629860268468247494986146122636455769804467583612610341632602695197189514316033637331733820369170763954604394734655429769801516997967996980978751', + }, + rctxt: + '19574881057684356733946284215946569464410211018678168661028327420122678446653210056362495902735819742274128834330867933095119512313591151219353395069123546495720010325822330866859140765940839241212947354612836044244554152389691282543839111284006009168728161183863936810142428875817934316327118674532328892591410224676539770085459540786747902789677759379901079898127879301595929571621032704093287675668250862222728331030586585586110859977896767318814398026750215625180255041545607499673023585546720788973882263863911222208020438685873501025545464213035270207099419236974668665979962146355749687924650853489277747454993', + s: '80388543865249952799447792504739237616187770512259677275061283897050980768551818104137338144380636412773836688624071360386172349725818126495487584981520630638409717065318132420766896092370913800616033623618952639023946750307405126873476182540669638841562357523429245685476919178722373320218824590869735129801004394337640642997250464303104754942997839179333543643110326022824394934965538190976474473353762308333205671176627192797138375084260446324344637548455228161138089974447059481109651156379803576163576511072261388342837813901850712083922506433336723723235701670225584863772222447543742649328218950436824219992164', + z: '18569464356833363098514177097771727133940629758890641648661259687745137028161881113251218061243607037717553708179509640909238773964066423807945164288256211132195919975343578956381001087353353060599758005375631247614777454313440511375923345538396573548499287265163879524050255226779884271432737062283353279122281220812931572456820130441114446870167673796490210349453498315913599982158253821945225264065364670730546176140788405935081171854642125236557475395879246419105888077042924382595999612137336915304205628167917473420377397118829734604949103124514367857266518654728464539418834291071874052392799652266418817991437', + }, + }, + }, + }) + }) + + // Should not resolve invalid schema + test('false test cases', async () => { + const invalidSchemaResourceId = + 'did:cheqd:testnet:d8ac0372-0d4b-413e-8ef5-8e8f07822b2c/resources/ffd001c2-1f80-4cd8-84b2-945fba309457' + const schemaResponse = await cheqdAnonCredsRegistry.getSchema(agent.context, `${invalidSchemaResourceId}`) + + expect(schemaResponse).toMatchObject({ + resolutionMetadata: { + error: 'notFound', + }, + schemaMetadata: {}, + }) + }) + + // Should resolve query based url + test('resolve query based url', async () => { + const schemaResourceId = + 'did:cheqd:testnet:d8ac0372-0d4b-413e-8ef5-8e8f07822b2c?resourceName=test - 11&resourceType=anonCredsSchema' + const schemaResponse = await cheqdAnonCredsRegistry.getSchema(resolverAgent.context, `${schemaResourceId}`) + + expect(schemaResponse).toMatchObject({ + schema: { + attrNames: ['name'], + name: 'test - 11', + }, + }) + }) + + // Should resolve revocationRegistryDefinition and statusList + test('resolve revocation registry definition and statusList', async () => { + const revocationRegistryId = 'did:cheqd:testnet:e42ccb8b-78e8-4e54-9d11-f375153d63f8?resourceName=universityDegree' + const revocationDefinitionResponse = await cheqdAnonCredsRegistry.getRevocationRegistryDefinition( + resolverAgent.context, + revocationRegistryId + ) + + expect(revocationDefinitionResponse.revocationRegistryDefinition).toMatchObject({ + revocDefType: 'CL_ACCUM', + credDefId: 'did:cheqd:mainnet:zF7rhDBfUt9d1gJPjx7s1J/resources/77465164-5646-42d9-9a0a-f7b2dcb855c0', + tag: '2.0', + value: { + publicKeys: { + accumKey: { + z: '1 08C6E71D1CE1D1690AED25BC769646538BEC69600829CE1FB7AA788479E0B878 1 026909513F9901655B3F9153071DB43A846418F00F305BA25FE851730ED41102 1 10E9D5438AE95AE2BED78A33716BFF923A0F4CA980A9A599C25A24A2295658DA 1 0A04C318A0DFD29ABB1F1D8DD697999F9B89D6682272C591B586D53F8A9D3DC4 1 0501E5FFCE863E08D209C2FA7B390A5AA91F462BB71957CF8DB41EACDC9EB222 1 14BED208817ACB398D8476212C987E7FF77265A72F145EF2853DDB631758AED4 1 180774B2F67179FB62BD452A15F6C034599DA7BF45CC15AA2138212B53A0C110 1 00A0B87DDFFC047BE07235DD11D31226A9F5FA1E03D49C03843AA36A8AF68194 1 10218703955E0B53DB93A8D2D593EB8120A9C9739F127325CB0865ECA4B2B42F 1 08685A263CD0A045FD845AAC6DAA0FDDAAD0EC222C1A0286799B69F37CD75919 1 1FA3D27E70C185C1A16D9A83D3EE7D8CACE727A99C882EE649F87BD52E9EEE47 1 054704706B95A154F5AFC3FBB536D38DC9DCB9702EA0BFDCCB2E36A3AA23F3EC', + }, + }, + maxCredNum: 666, + tailsLocation: 'https://my.revocations.tails/tailsfile.txt', + tailsHash: '91zvq2cFmBZmHCcLqFyzv7bfehHH5rMhdAG5wTjqy2PE', + }, + }) + + const revocationStatusListResponse = await cheqdAnonCredsRegistry.getRevocationStatusList( + resolverAgent.context, + revocationRegistryId, + 1680789403 + ) + + expect(revocationStatusListResponse.revocationStatusList).toMatchObject({ + revRegDefId: `${revocationRegistryId}&resourceType=anonCredsRevocRegDef`, + revocationList: [ + 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + ], + currentAccumulator: + '21 124C594B6B20E41B681E92B2C43FD165EA9E68BC3C9D63A82C8893124983CAE94 21 124C5341937827427B0A3A32113BD5E64FB7AB39BD3E5ABDD7970874501CA4897 6 5438CB6F442E2F807812FD9DC0C39AFF4A86B1E6766DBB5359E86A4D70401B0F 4 39D1CA5C4716FFC4FE0853C4FF7F081DFD8DF8D2C2CA79705211680AC77BF3A1 6 70504A5493F89C97C225B68310811A41AD9CD889301F238E93C95AD085E84191 4 39582252194D756D5D86D0EED02BF1B95CE12AED2FA5CD3C53260747D891993C', + }) + }) +}) diff --git a/packages/cheqd/tests/setup.ts b/packages/cheqd/tests/setup.ts new file mode 100644 index 0000000000..9af7086f02 --- /dev/null +++ b/packages/cheqd/tests/setup.ts @@ -0,0 +1,33 @@ +jest.setTimeout(60000) + +import { DidDocument, DidDocumentService, VerificationMethod } from '@aries-framework/core' + +export const validDid = 'did:cheqd:testnet:SiVQgrFZ7jFZFrTGstT4ZD' + +export function validVerificationMethod(did: string) { + return new VerificationMethod({ + id: did + '#key-1', + type: 'Ed25519VerificationKey2020', + controller: did, + publicKeyMultibase: 'z6MkkBaWtQKyx7Mr54XaXyMAEpNKqphK4x7ztuBpSfR6Wqwr', + }) +} + +export function validService(did: string) { + return new DidDocumentService({ + id: did + '#service-1', + type: 'DIDCommMessaging', + serviceEndpoint: 'https://rand.io', + }) +} + +export function validDidDoc() { + const service = [validService(validDid)] + const verificationMethod = [validVerificationMethod(validDid)] + + return new DidDocument({ + id: validDid, + verificationMethod, + service, + }) +} diff --git a/packages/cheqd/tests/setupCheqdModule.ts b/packages/cheqd/tests/setupCheqdModule.ts new file mode 100644 index 0000000000..17881fdf26 --- /dev/null +++ b/packages/cheqd/tests/setupCheqdModule.ts @@ -0,0 +1,34 @@ +import type { CheqdModuleConfigOptions } from '../src' + +import { DidsModule, KeyDidRegistrar, KeyDidResolver } from '@aries-framework/core' +import { IndySdkModule, IndySdkModuleConfig } from '@aries-framework/indy-sdk' +import indySdk from 'indy-sdk' + +import { CheqdModule, CheqdDidRegistrar, CheqdDidResolver } from '../src' + +export const getIndySdkModuleConfig = () => + new IndySdkModuleConfig({ + indySdk, + }) + +export const getCheqdModuleConfig = (seed?: string, rpcUrl?: string) => + ({ + networks: [ + { + rpcUrl: rpcUrl || 'http://localhost:26657', + network: 'testnet', + cosmosPayerSeed: + seed || + 'sketch mountain erode window enact net enrich smoke claim kangaroo another visual write meat latin bacon pulp similar forum guilt father state erase bright', + }, + ], + } satisfies CheqdModuleConfigOptions) + +export const getCheqdModules = (seed?: string, rpcUrl?: string) => ({ + cheqdSdk: new CheqdModule(getCheqdModuleConfig(seed, rpcUrl)), + dids: new DidsModule({ + registrars: [new CheqdDidRegistrar(), new KeyDidRegistrar()], + resolvers: [new CheqdDidResolver(), new KeyDidResolver()], + }), + indySdk: new IndySdkModule(getIndySdkModuleConfig()), +}) diff --git a/packages/cheqd/tsconfig.build.json b/packages/cheqd/tsconfig.build.json new file mode 100644 index 0000000000..2b75d0adab --- /dev/null +++ b/packages/cheqd/tsconfig.build.json @@ -0,0 +1,7 @@ +{ + "extends": "../../tsconfig.build.json", + "compilerOptions": { + "outDir": "./build" + }, + "include": ["src/**/*"] +} diff --git a/packages/cheqd/tsconfig.json b/packages/cheqd/tsconfig.json new file mode 100644 index 0000000000..7958700c2b --- /dev/null +++ b/packages/cheqd/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "types": ["jest"], + "moduleResolution": "node", + "resolveJsonModule": true + } +} diff --git a/packages/core/CHANGELOG.md b/packages/core/CHANGELOG.md index 341ff76014..8f07830b5f 100644 --- a/packages/core/CHANGELOG.md +++ b/packages/core/CHANGELOG.md @@ -3,6 +3,17 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [0.3.3](https://github.com/hyperledger/aries-framework-javascript/compare/v0.3.2...v0.3.3) (2023-01-18) + +### Bug Fixes + +- fix typing issues with typescript 4.9 ([#1214](https://github.com/hyperledger/aries-framework-javascript/issues/1214)) ([087980f](https://github.com/hyperledger/aries-framework-javascript/commit/087980f1adf3ee0bc434ca9782243a62c6124444)) + +### Features + +- adding trust ping events and trust ping command ([#1182](https://github.com/hyperledger/aries-framework-javascript/issues/1182)) ([fd006f2](https://github.com/hyperledger/aries-framework-javascript/commit/fd006f262a91f901e7f8a9c6e6882ea178230005)) +- **indy-sdk:** add indy-sdk package ([#1200](https://github.com/hyperledger/aries-framework-javascript/issues/1200)) ([9933b35](https://github.com/hyperledger/aries-framework-javascript/commit/9933b35a6aa4524caef8a885e71b742cd0d7186b)) + ## [0.3.2](https://github.com/hyperledger/aries-framework-javascript/compare/v0.3.1...v0.3.2) (2023-01-04) ### Bug Fixes diff --git a/packages/core/jest.config.ts b/packages/core/jest.config.ts index 55c67d70a6..7b6ec7f1c5 100644 --- a/packages/core/jest.config.ts +++ b/packages/core/jest.config.ts @@ -4,9 +4,10 @@ import base from '../../jest.config.base' import packageJson from './package.json' +process.env.TZ = 'GMT' + const config: Config.InitialOptions = { ...base, - name: packageJson.name, displayName: packageJson.name, setupFilesAfterEnv: ['./tests/setup.ts'], } diff --git a/packages/core/package.json b/packages/core/package.json index 3325039d25..9eb5d56a3f 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -2,7 +2,7 @@ "name": "@aries-framework/core", "main": "build/index", "types": "build/index", - "version": "0.3.2", + "version": "0.3.3", "files": [ "build" ], @@ -18,7 +18,7 @@ }, "scripts": { "build": "yarn run clean && yarn run compile", - "clean": "rimraf -rf ./build", + "clean": "rimraf ./build", "compile": "tsc -p tsconfig.build.json", "prepublishOnly": "yarn run build" }, @@ -30,38 +30,37 @@ "@stablelib/ed25519": "^1.0.2", "@stablelib/random": "^1.0.1", "@stablelib/sha256": "^1.0.1", - "@types/indy-sdk": "^1.16.21", - "@types/node-fetch": "^2.5.10", - "@types/ws": "^7.4.6", + "node-fetch": "^2.6.1", + "@types/ws": "^8.5.4", "abort-controller": "^3.0.0", - "bn.js": "^5.2.0", + "big-integer": "^1.6.51", "borc": "^3.0.0", "buffer": "^6.0.3", "class-transformer": "0.5.1", - "class-validator": "0.13.1", - "did-resolver": "^3.1.3", + "class-validator": "0.14.0", + "did-resolver": "^4.1.0", "lru_map": "^0.4.1", - "luxon": "^1.27.0", + "luxon": "^3.3.0", "make-error": "^1.3.6", "object-inspect": "^1.10.3", "query-string": "^7.0.1", "reflect-metadata": "^0.1.13", "rxjs": "^7.2.0", "tsyringe": "^4.7.0", - "uuid": "^8.3.2", + "uuid": "^9.0.0", "varint": "^6.0.0", - "web-did-resolver": "^2.0.8" + "web-did-resolver": "^2.0.21" }, "devDependencies": { - "@types/bn.js": "^5.1.0", "@types/events": "^3.0.0", - "@types/luxon": "^1.27.0", + "@types/luxon": "^3.2.0", "@types/object-inspect": "^1.8.0", - "@types/uuid": "^8.3.0", + "@types/uuid": "^9.0.1", "@types/varint": "^6.0.0", - "node-fetch": "^2.0", - "rimraf": "~3.0.2", - "tslog": "^3.2.0", - "typescript": "~4.3.0" + "nock": "^13.3.0", + "@types/node-fetch": "2.6.2", + "rimraf": "^4.4.0", + "tslog": "^4.8.2", + "typescript": "~4.9.5" } } diff --git a/packages/core/src/agent/Agent.ts b/packages/core/src/agent/Agent.ts index 2f3715bf92..9ff83cbb55 100644 --- a/packages/core/src/agent/Agent.ts +++ b/packages/core/src/agent/Agent.ts @@ -1,28 +1,22 @@ -import type { InboundTransport } from '../transport/InboundTransport' -import type { OutboundTransport } from '../transport/OutboundTransport' -import type { InitConfig } from '../types' import type { AgentDependencies } from './AgentDependencies' import type { AgentModulesInput } from './AgentModules' import type { AgentMessageReceivedEvent } from './Events' +import type { Module } from '../plugins' +import type { InboundTransport } from '../transport/InboundTransport' +import type { OutboundTransport } from '../transport/OutboundTransport' +import type { InitConfig } from '../types' import type { Subscription } from 'rxjs' import { Subject } from 'rxjs' import { concatMap, takeUntil } from 'rxjs/operators' -import { CacheRepository } from '../cache' import { InjectionSymbols } from '../constants' import { KeyProviderToken } from '../crypto' import { JwsService } from '../crypto/JwsService' -import { X25519KeyProvider } from '../crypto/key-provider/X25519KeyProvider' -import { DidCommV1EnvelopeServiceToken } from '../didcomm/versions/v1' -import { DefaultDidCommV1EnvelopeService } from '../didcomm/versions/v1/indy/DefaultDidCommV1EnvelopeService' -import { DefaultDidCommV2EnvelopeService, DidCommV2EnvelopeServiceToken } from '../didcomm/versions/v2' import { AriesFrameworkError } from '../error' import { DependencyManager } from '../plugins' import { DidCommMessageRepository, StorageUpdateService, StorageVersionRepository } from '../storage' import { InMemoryMessageRepository } from '../storage/InMemoryMessageRepository' -import { IndyStorageService } from '../storage/IndyStorageService' -import { IndyWallet } from '../wallet/IndyWallet' import { AgentConfig } from './AgentConfig' import { extendModulesWithDefaultModules } from './AgentModules' @@ -51,7 +45,7 @@ export class Agent extends BaseAge public constructor(options: AgentOptions, dependencyManager = new DependencyManager()) { const agentConfig = new AgentConfig(options.config, options.dependencies) - const modulesWithDefaultModules = extendModulesWithDefaultModules(agentConfig, options.modules) + const modulesWithDefaultModules = extendModulesWithDefaultModules(options.modules) // Register internal dependencies dependencyManager.registerSingleton(MessageHandlerRegistry) @@ -63,39 +57,44 @@ export class Agent extends BaseAge dependencyManager.registerSingleton(EnvelopeService) dependencyManager.registerSingleton(FeatureRegistry) dependencyManager.registerSingleton(JwsService) - dependencyManager.registerSingleton(CacheRepository) dependencyManager.registerSingleton(DidCommMessageRepository) dependencyManager.registerSingleton(StorageVersionRepository) dependencyManager.registerSingleton(StorageUpdateService) - dependencyManager.registerSingleton(DidCommV1EnvelopeServiceToken, DefaultDidCommV1EnvelopeService) - dependencyManager.registerInstance(DidCommV2EnvelopeServiceToken, DefaultDidCommV2EnvelopeService) - - dependencyManager.registerInstance(KeyProviderToken, new X25519KeyProvider()) + // This is a really ugly hack to make tsyringe work without any SigningProviders registered + // It is currently impossible to use @injectAll if there are no instances registered for the + // token. We register a value of `default` by default and will filter that out in the registry. + // Once we have a signing provider that should always be registered we can remove this. We can make an ed25519 + // signer using the @stablelib/ed25519 library. + dependencyManager.registerInstance(KeyProviderToken, 'default') dependencyManager.registerInstance(AgentConfig, agentConfig) dependencyManager.registerInstance(InjectionSymbols.AgentDependencies, agentConfig.agentDependencies) dependencyManager.registerInstance(InjectionSymbols.Stop$, new Subject()) dependencyManager.registerInstance(InjectionSymbols.FileSystem, new agentConfig.agentDependencies.FileSystem()) + // Register all modules. This will also include the default modules + dependencyManager.registerModules(modulesWithDefaultModules) + // Register possibly already defined services if (!dependencyManager.isRegistered(InjectionSymbols.Wallet)) { - dependencyManager.registerContextScoped(InjectionSymbols.Wallet, IndyWallet) + throw new AriesFrameworkError( + "Missing required dependency: 'Wallet'. You can register it using one of the provided modules such as the AskarModule or the IndySdkModule, or implement your own." + ) } if (!dependencyManager.isRegistered(InjectionSymbols.Logger)) { dependencyManager.registerInstance(InjectionSymbols.Logger, agentConfig.logger) } if (!dependencyManager.isRegistered(InjectionSymbols.StorageService)) { - dependencyManager.registerSingleton(InjectionSymbols.StorageService, IndyStorageService) + throw new AriesFrameworkError( + "Missing required dependency: 'StorageService'. You can register it using one of the provided modules such as the AskarModule or the IndySdkModule, or implement your own." + ) } if (!dependencyManager.isRegistered(InjectionSymbols.MessageRepository)) { dependencyManager.registerSingleton(InjectionSymbols.MessageRepository, InMemoryMessageRepository) } - // Register all modules. This will also include the default modules - dependencyManager.registerModules(modulesWithDefaultModules) - // TODO: contextCorrelationId for base wallet // Bind the default agent context to the container for use in modules etc. dependencyManager.registerInstance( @@ -138,6 +137,10 @@ export class Agent extends BaseAge this.messageReceiver.registerInboundTransport(inboundTransport) } + public async unregisterInboundTransport(inboundTransport: InboundTransport) { + await this.messageReceiver.unregisterInboundTransport(inboundTransport) + } + public get inboundTransports() { return this.messageReceiver.inboundTransports } @@ -146,6 +149,10 @@ export class Agent extends BaseAge this.messageSender.registerOutboundTransport(outboundTransport) } + public async unregisterOutboundTransport(outboundTransport: OutboundTransport) { + await this.messageSender.unregisterOutboundTransport(outboundTransport) + } + public get outboundTransports() { return this.messageSender.outboundTransports } @@ -164,13 +171,10 @@ export class Agent extends BaseAge public async initialize() { await super.initialize() - // set the pools on the ledger. - this.ledger.setPools(this.ledger.config.indyLedgers) - // As long as value isn't false we will async connect to all genesis pools on startup - if (this.ledger.config.connectToIndyLedgersOnStartup) { - this.ledger.connectToPools().catch((error) => { - this.logger.warn('Error connecting to ledger, will try to reconnect when needed.', { error }) - }) + for (const [, module] of Object.entries(this.dependencyManager.registeredModules) as [string, Module][]) { + if (module.initialize) { + await module.initialize(this.agentContext) + } } for (const transport of this.inboundTransports) { diff --git a/packages/core/src/agent/AgentConfig.ts b/packages/core/src/agent/AgentConfig.ts index cceef0e271..7e43029088 100644 --- a/packages/core/src/agent/AgentConfig.ts +++ b/packages/core/src/agent/AgentConfig.ts @@ -1,58 +1,24 @@ +import type { AgentDependencies } from './AgentDependencies' import type { Logger } from '../logger' import type { InitConfig } from '../types' -import type { AgentDependencies } from './AgentDependencies' import { DID_COMM_TRANSPORT_QUEUE } from '../constants' -import { AriesFrameworkError } from '../error' import { ConsoleLogger, LogLevel } from '../logger' -import { AutoAcceptCredential } from '../modules/credentials/models/CredentialAutoAcceptType' -import { AutoAcceptProof } from '../modules/proofs/models/ProofAutoAcceptType' import { DidCommMimeType } from '../types' export class AgentConfig { private initConfig: InitConfig + private _endpoints: string[] | undefined public label: string public logger: Logger public readonly agentDependencies: AgentDependencies public constructor(initConfig: InitConfig, agentDependencies: AgentDependencies) { this.initConfig = initConfig + this._endpoints = initConfig.endpoints this.label = initConfig.label this.logger = initConfig.logger ?? new ConsoleLogger(LogLevel.off) this.agentDependencies = agentDependencies - - const { mediatorConnectionsInvite, clearDefaultMediator, defaultMediatorId } = this.initConfig - - const allowOne = [mediatorConnectionsInvite, clearDefaultMediator, defaultMediatorId].filter((e) => e !== undefined) - if (allowOne.length > 1) { - throw new AriesFrameworkError( - `Only one of 'mediatorConnectionsInvite', 'clearDefaultMediator' and 'defaultMediatorId' can be set as they negate each other` - ) - } - } - - /** - * @deprecated use connectToIndyLedgersOnStartup from the `LedgerModuleConfig` class - */ - public get connectToIndyLedgersOnStartup() { - return this.initConfig.connectToIndyLedgersOnStartup ?? true - } - - /** - * @deprecated The public did functionality of the wallet has been deprecated in favour of the DidsModule, which can be - * used to create and resolve dids. Currently the global agent public did functionality is still used by the `LedgerModule`, but - * will be removed once the `LedgerModule` has been deprecated. Do not use this property for new functionality, but rather - * use the `DidsModule`. - */ - public get publicDidSeed() { - return this.initConfig.publicDidSeed - } - - /** - * @deprecated use indyLedgers from the `LedgerModuleConfig` class - */ - public get indyLedgers() { - return this.initConfig.indyLedgers ?? [] } /** @@ -62,63 +28,8 @@ export class AgentConfig { return this.initConfig.walletConfig } - /** - * @deprecated use autoAcceptConnections from the `ConnectionsModuleConfig` class - */ - public get autoAcceptConnections() { - return this.initConfig.autoAcceptConnections ?? false - } - - /** - * @deprecated use autoAcceptProofs from the `ProofsModuleConfig` class - */ - public get autoAcceptProofs() { - return this.initConfig.autoAcceptProofs ?? AutoAcceptProof.Never - } - - /** - * @deprecated use autoAcceptCredentials from the `CredentialsModuleConfig` class - */ - public get autoAcceptCredentials() { - return this.initConfig.autoAcceptCredentials ?? AutoAcceptCredential.Never - } - public get didCommMimeType() { - return this.initConfig.didCommMimeType ?? DidCommMimeType.V0 - } - - /** - * @deprecated use mediatorPollingInterval from the `RecipientModuleConfig` class - */ - public get mediatorPollingInterval() { - return this.initConfig.mediatorPollingInterval ?? 5000 - } - - /** - * @deprecated use mediatorPickupStrategy from the `RecipientModuleConfig` class - */ - public get mediatorPickupStrategy() { - return this.initConfig.mediatorPickupStrategy - } - - /** - * @deprecated use maximumMessagePickup from the `RecipientModuleConfig` class - */ - public get maximumMessagePickup() { - return this.initConfig.maximumMessagePickup ?? 10 - } - /** - * @deprecated use baseMediatorReconnectionIntervalMs from the `RecipientModuleConfig` class - */ - public get baseMediatorReconnectionIntervalMs() { - return this.initConfig.baseMediatorReconnectionIntervalMs ?? 100 - } - - /** - * @deprecated use maximumMediatorReconnectionIntervalMs from the `RecipientModuleConfig` class - */ - public get maximumMediatorReconnectionIntervalMs() { - return this.initConfig.maximumMediatorReconnectionIntervalMs ?? Number.POSITIVE_INFINITY + return this.initConfig.didCommMimeType ?? DidCommMimeType.V1 } /** @@ -134,43 +45,19 @@ export class AgentConfig { public get endpoints(): [string, ...string[]] { // if endpoints is not set, return queue endpoint // https://github.com/hyperledger/aries-rfcs/issues/405#issuecomment-582612875 - if (!this.initConfig.endpoints || this.initConfig.endpoints.length === 0) { + if (!this._endpoints || this._endpoints.length === 0) { return [DID_COMM_TRANSPORT_QUEUE] } - return this.initConfig.endpoints as [string, ...string[]] - } - - /** - * @deprecated use mediatorInvitationUrl from the `RecipientModuleConfig` class - */ - public get mediatorConnectionsInvite() { - return this.initConfig.mediatorConnectionsInvite + return this._endpoints as [string, ...string[]] } - /** - * @deprecated use autoAcceptMediationRequests from the `MediatorModuleConfig` class - */ - public get autoAcceptMediationRequests() { - return this.initConfig.autoAcceptMediationRequests ?? false - } - - /** - * @deprecated you can use `RecipientApi.setDefaultMediator` to set the default mediator. - */ - public get defaultMediatorId() { - return this.initConfig.defaultMediatorId - } - - /** - * @deprecated you can set the `default` tag to `false` (or remove it completely) to clear the default mediator. - */ - public get clearDefaultMediator() { - return this.initConfig.clearDefaultMediator ?? false + public set endpoints(endpoints: string[]) { + this._endpoints = endpoints } - public get useLegacyDidSovPrefix() { - return this.initConfig.useLegacyDidSovPrefix ?? false + public get useDidSovPrefixWhereAllowed() { + return this.initConfig.useDidSovPrefixWhereAllowed ?? false } /** diff --git a/packages/core/src/agent/AgentDependencies.ts b/packages/core/src/agent/AgentDependencies.ts index 1aa681645d..be1146e818 100644 --- a/packages/core/src/agent/AgentDependencies.ts +++ b/packages/core/src/agent/AgentDependencies.ts @@ -1,6 +1,5 @@ import type { FileSystem } from '../storage/FileSystem' import type { EventEmitter } from 'events' -import type * as Indy from 'indy-sdk' import type fetch from 'node-fetch' import type WebSocket from 'ws' @@ -8,7 +7,6 @@ export interface AgentDependencies { FileSystem: { new (): FileSystem } - indy: typeof Indy EventEmitterClass: typeof EventEmitter fetch: typeof fetch WebSocketClass: typeof WebSocket diff --git a/packages/core/src/agent/AgentMessage.ts b/packages/core/src/agent/AgentMessage.ts index c7293c7ea0..e60a556141 100644 --- a/packages/core/src/agent/AgentMessage.ts +++ b/packages/core/src/agent/AgentMessage.ts @@ -1,3 +1,4 @@ +import type { ServiceDecorator } from '../decorators/service/ServiceDecorator' import type { ReturnRouteTypes } from '../decorators/transport/TransportDecorator' import type { DidCommMessageVersion } from '../didcomm/types' @@ -8,9 +9,12 @@ export interface AgentMessage { get id(): string get threadId(): string | undefined + // setServiceDecorator(): ServiceDecorator | undefined + serviceDecorator(): ServiceDecorator | undefined + hasAnyReturnRoute(): boolean hasReturnRouting(threadId?: string): boolean setReturnRouting(type: ReturnRouteTypes, thread?: string): void - toJSON(params?: { useLegacyDidSovPrefix?: boolean }): Record + toJSON(params?: { useDidSovPrefixWhereAllowed?: boolean }): Record } diff --git a/packages/core/src/agent/AgentModules.ts b/packages/core/src/agent/AgentModules.ts index ce59b48daa..805a147918 100644 --- a/packages/core/src/agent/AgentModules.ts +++ b/packages/core/src/agent/AgentModules.ts @@ -1,20 +1,19 @@ import type { Module, DependencyManager, ApiModule } from '../plugins' import type { IsAny } from '../types' import type { Constructor } from '../utils/mixins' -import type { AgentConfig } from './AgentConfig' import { BasicMessagesModule } from '../modules/basic-messages' +import { CacheModule } from '../modules/cache' import { ConnectionsModule } from '../modules/connections' import { CredentialsModule } from '../modules/credentials' import { DidsModule } from '../modules/dids' import { DiscoverFeaturesModule } from '../modules/discover-features' import { GenericRecordsModule } from '../modules/generic-records' -import { IndyModule } from '../modules/indy' -import { LedgerModule } from '../modules/ledger' +import { MessagePickupModule } from '../modules/message-pìckup' import { OutOfBandModule } from '../modules/oob' import { ProofsModule } from '../modules/proofs' -import { MediatorModule, RecipientModule } from '../modules/routing' -import { W3cVcModule } from '../modules/vc' +import { MediationRecipientModule, MediatorModule } from '../modules/routing' +import { W3cCredentialsModule } from '../modules/vc' import { WalletModule } from '../wallet' /** @@ -35,9 +34,11 @@ export type AgentModulesInput = Partial & ModulesMap * Defines the input type for the default agent modules. This is overwritten as we * want the input type to allow for generics to be passed in for the credentials module. */ -export type DefaultAgentModulesInput = Omit & { +export type DefaultAgentModulesInput = Omit & { // eslint-disable-next-line @typescript-eslint/no-explicit-any credentials: CredentialsModule + // eslint-disable-next-line @typescript-eslint/no-explicit-any + proofs: ProofsModule } /** @@ -111,52 +112,25 @@ export type CustomOrDefaultApi< : InstanceType /** - * Method to get the default agent modules to be registered on any agent instance. - * - * @note This implementation is quite ugly and is meant to be temporary. It extracts the module specific config from the agent config - * and will only construct the module if the method is called. This prevents the modules from being initialized if they are already configured by the end - * user using the `module` property in the agent constructor. + * Method to get the default agent modules to be registered on any agent instance. It doens't configure the modules in any way, + * and if that's needed the user needs to provide the module in the agent constructor */ -function getDefaultAgentModules(agentConfig: AgentConfig) { +function getDefaultAgentModules() { return { - connections: () => - new ConnectionsModule({ - autoAcceptConnections: agentConfig.autoAcceptConnections, - }), - credentials: () => - new CredentialsModule({ - autoAcceptCredentials: agentConfig.autoAcceptCredentials, - }), - proofs: () => - new ProofsModule({ - autoAcceptProofs: agentConfig.autoAcceptProofs, - }), - mediator: () => - new MediatorModule({ - autoAcceptMediationRequests: agentConfig.autoAcceptMediationRequests, - }), - mediationRecipient: () => - new RecipientModule({ - maximumMessagePickup: agentConfig.maximumMessagePickup, - mediatorInvitationUrl: agentConfig.mediatorConnectionsInvite, - mediatorPickupStrategy: agentConfig.mediatorPickupStrategy, - baseMediatorReconnectionIntervalMs: agentConfig.baseMediatorReconnectionIntervalMs, - maximumMediatorReconnectionIntervalMs: agentConfig.maximumMediatorReconnectionIntervalMs, - mediatorPollingInterval: agentConfig.mediatorPollingInterval, - }), + connections: () => new ConnectionsModule(), + credentials: () => new CredentialsModule(), + proofs: () => new ProofsModule(), + mediator: () => new MediatorModule(), + mediationRecipient: () => new MediationRecipientModule(), + messagePickup: () => new MessagePickupModule(), basicMessages: () => new BasicMessagesModule(), genericRecords: () => new GenericRecordsModule(), - ledger: () => - new LedgerModule({ - connectToIndyLedgersOnStartup: agentConfig.connectToIndyLedgersOnStartup, - indyLedgers: agentConfig.indyLedgers, - }), discovery: () => new DiscoverFeaturesModule(), dids: () => new DidsModule(), wallet: () => new WalletModule(), oob: () => new OutOfBandModule(), - indy: () => new IndyModule(), - w3cVc: () => new W3cVcModule(), + w3cCredentials: () => new W3cCredentialsModule(), + cache: () => new CacheModule(), } as const } @@ -167,11 +141,10 @@ function getDefaultAgentModules(agentConfig: AgentConfig) { * on the default agent. */ export function extendModulesWithDefaultModules( - agentConfig: AgentConfig, modules?: AgentModules ): AgentModules & DefaultAgentModules { const extendedModules: Record = { ...modules } - const defaultAgentModules = getDefaultAgentModules(agentConfig) + const defaultAgentModules = getDefaultAgentModules() // Register all default modules, if not registered yet for (const [moduleKey, getConfiguredModule] of Object.entries(defaultAgentModules)) { diff --git a/packages/core/src/agent/BaseAgent.ts b/packages/core/src/agent/BaseAgent.ts index b0abda3209..ae9e6656ba 100644 --- a/packages/core/src/agent/BaseAgent.ts +++ b/packages/core/src/agent/BaseAgent.ts @@ -1,9 +1,11 @@ +import type { AgentConfig } from './AgentConfig' +import type { AgentApi, CustomOrDefaultApi, EmptyModuleMap, ModulesMap, WithoutDefaultModules } from './AgentModules' +import type { TransportSession } from './TransportService' import type { Logger } from '../logger' import type { CredentialsModule } from '../modules/credentials' +import type { MessagePickupModule } from '../modules/message-pìckup' +import type { ProofsModule } from '../modules/proofs' import type { DependencyManager } from '../plugins' -import type { AgentConfig } from './AgentConfig' -import type { AgentApi, EmptyModuleMap, ModulesMap, WithoutDefaultModules, CustomOrDefaultApi } from './AgentModules' -import type { TransportSession } from './TransportService' import { AriesFrameworkError } from '../error' import { BasicMessagesApi } from '../modules/basic-messages' @@ -12,10 +14,11 @@ import { CredentialsApi } from '../modules/credentials' import { DidsApi } from '../modules/dids' import { DiscoverFeaturesApi } from '../modules/discover-features' import { GenericRecordsApi } from '../modules/generic-records' -import { LedgerApi } from '../modules/ledger' +import { MessagePickupApi } from '../modules/message-pìckup/MessagePickupApi' import { OutOfBandApi } from '../modules/oob' -import { ProofsApi } from '../modules/proofs/ProofsApi' -import { MediatorApi, RecipientApi } from '../modules/routing' +import { ProofsApi } from '../modules/proofs' +import { MediatorApi, MediationRecipientApi } from '../modules/routing' +import { W3cCredentialsApi } from '../modules/vc/W3cCredentialsApi' import { StorageUpdateService } from '../storage' import { UpdateAssistant } from '../storage/migration/UpdateAssistant' import { DEFAULT_UPDATE_CONFIG } from '../storage/migration/updates' @@ -44,16 +47,17 @@ export abstract class BaseAgent - public readonly proofs: ProofsApi + public readonly proofs: CustomOrDefaultApi public readonly mediator: MediatorApi - public readonly mediationRecipient: RecipientApi + public readonly mediationRecipient: MediationRecipientApi + public readonly messagePickup: CustomOrDefaultApi public readonly basicMessages: BasicMessagesApi public readonly genericRecords: GenericRecordsApi - public readonly ledger: LedgerApi public readonly discovery: DiscoverFeaturesApi public readonly dids: DidsApi public readonly wallet: WalletApi public readonly oob: OutOfBandApi + public readonly w3cCredentials: W3cCredentialsApi public readonly modules: AgentApi> @@ -88,16 +92,20 @@ export abstract class BaseAgent - this.proofs = this.dependencyManager.resolve(ProofsApi) + this.proofs = this.dependencyManager.resolve(ProofsApi) as CustomOrDefaultApi this.mediator = this.dependencyManager.resolve(MediatorApi) - this.mediationRecipient = this.dependencyManager.resolve(RecipientApi) + this.mediationRecipient = this.dependencyManager.resolve(MediationRecipientApi) + this.messagePickup = this.dependencyManager.resolve(MessagePickupApi) as CustomOrDefaultApi< + AgentModules['messagePickup'], + MessagePickupModule + > this.basicMessages = this.dependencyManager.resolve(BasicMessagesApi) this.genericRecords = this.dependencyManager.resolve(GenericRecordsApi) - this.ledger = this.dependencyManager.resolve(LedgerApi) this.discovery = this.dependencyManager.resolve(DiscoverFeaturesApi) this.dids = this.dependencyManager.resolve(DidsApi) this.wallet = this.dependencyManager.resolve(WalletApi) this.oob = this.dependencyManager.resolve(OutOfBandApi) + this.w3cCredentials = this.dependencyManager.resolve(W3cCredentialsApi) const defaultApis = [ this.connections, @@ -105,13 +113,14 @@ export abstract class BaseAgent { const { agentContext, connection, senderKey, recipientKey, message } = messageContext const messageHandler = this.messageHandlerRegistry.getHandlerForMessageType(message.type) @@ -57,12 +50,25 @@ class Dispatcher { const problemReportMessage = error.problemReport if (problemReportMessage instanceof ProblemReportMessage && messageContext.connection) { - problemReportMessage.setThread({ - threadId: message.threadId, - }) + const { protocolUri: problemReportProtocolUri } = parseMessageType(problemReportMessage.type) + const { protocolUri: inboundProtocolUri } = parseMessageType(messageContext.message.type) + + // If the inbound protocol uri is the same as the problem report protocol uri, we can see the interaction as the same thread + // However if it is no the same we should see it as a new thread, where the inbound message `@id` is the parentThreadId + if (inboundProtocolUri === problemReportProtocolUri) { + problemReportMessage.setThread({ + threadId: message.threadId, + }) + } else { + problemReportMessage.setThread({ + parentThreadId: message.id, + }) + } + outboundMessage = new OutboundMessageContext(problemReportMessage, { agentContext, connection: messageContext.connection, + inboundMessageContext: messageContext, }) } else { this.logger.error(`Error handling message with type ${message.type}`, { @@ -78,10 +84,14 @@ class Dispatcher { } if (outboundMessage) { + // set the inbound message context, if not already defined + if (!outboundMessage.inboundMessageContext) { + outboundMessage.inboundMessageContext = messageContext + } + if (outboundMessage.isOutboundServiceMessage()) { await this.messageSender.sendMessageToService(outboundMessage) } else { - outboundMessage.sessionId = messageContext.sessionId await this.messageSender.sendMessage(outboundMessage) } } diff --git a/packages/core/src/agent/EnvelopeService.ts b/packages/core/src/agent/EnvelopeService.ts index 6f77ca9065..8e56215851 100644 --- a/packages/core/src/agent/EnvelopeService.ts +++ b/packages/core/src/agent/EnvelopeService.ts @@ -1,42 +1,35 @@ -import type { DecryptedMessageContext, EncryptedMessage, SignedMessage } from '../didcomm/types' -import type { DidCommV1Message, PackMessageParams as DidCommV1PackMessageParams } from '../didcomm/versions/v1' -import type { - DidCommV2EnvelopeService, - DidCommV2Message, - V2PackMessageParams as DidCommV2PackMessageParams, -} from '../didcomm/versions/v2' import type { AgentMessage } from './AgentMessage' import type { AgentContext } from './context' +import type { Key } from '../crypto' +import type { + DidCommV1PackMessageParams, + DidCommV2PackMessageParams, + DecryptedMessageContext, + EncryptedMessage, +} from '../didcomm' +import type { DidDocument } from '../modules/dids' +import type { WalletPackOptions } from '../wallet/Wallet' -import { isEncryptedMessage, isSignedMessage } from '../didcomm' +import { InjectionSymbols } from '../constants' +import { V2Attachment } from '../decorators/attachment' +import { V2AttachmentData } from '../decorators/attachment/V2Attachment' import { DidCommMessageVersion } from '../didcomm/types' -import { DidCommV1EnvelopeService, DidCommV1EnvelopeServiceToken } from '../didcomm/versions/v1' -import { isDidCommV1EncryptedEnvelope } from '../didcomm/versions/v1/helpers' -import { DefaultDidCommV2EnvelopeService, DidCommV2EnvelopeServiceToken } from '../didcomm/versions/v2' import { AriesFrameworkError } from '../error' +import { Logger } from '../logger' +import { DidResolverService, getAgreementKeys, keyReferenceToKey } from '../modules/dids' +import { ForwardMessage, V2ForwardMessage } from '../modules/routing/messages' import { inject, injectable } from '../plugins' export type PackMessageParams = DidCommV1PackMessageParams | DidCommV2PackMessageParams @injectable() export class EnvelopeService { - private didCommV1EnvelopeService: DidCommV1EnvelopeService - private didCommV2EnvelopeService: DidCommV2EnvelopeService | typeof DefaultDidCommV2EnvelopeService - - public constructor( - @inject(DidCommV1EnvelopeServiceToken) didCommV1EnvelopeService: DidCommV1EnvelopeService, - @inject(DidCommV2EnvelopeServiceToken) - didCommV2EnvelopeService: DidCommV2EnvelopeService | typeof DefaultDidCommV2EnvelopeService - ) { - this.didCommV1EnvelopeService = didCommV1EnvelopeService - this.didCommV2EnvelopeService = didCommV2EnvelopeService - } + private logger: Logger + private didResolverService: DidResolverService - private getDidCommV2EnvelopeService(): DidCommV2EnvelopeService { - if (this.didCommV2EnvelopeService === DefaultDidCommV2EnvelopeService) { - throw new AriesFrameworkError('Unable to resolve DidCommV2EnvelopeService') - } - return this.didCommV2EnvelopeService + public constructor(@inject(InjectionSymbols.Logger) logger: Logger, didResolverService: DidResolverService) { + this.logger = logger + this.didResolverService = didResolverService } public async packMessage( @@ -45,44 +38,180 @@ export class EnvelopeService { params: PackMessageParams ): Promise { if (message.didCommVersion === DidCommMessageVersion.V1) { - return this.didCommV1EnvelopeService.packMessage( - agentContext, - message as DidCommV1Message, - params as DidCommV1PackMessageParams - ) + return this.packDIDCommV1Message(agentContext, message, params as DidCommV1PackMessageParams) } if (message.didCommVersion === DidCommMessageVersion.V2) { - return this.getDidCommV2EnvelopeService().packMessage( - agentContext, - message as DidCommV2Message, - params as DidCommV2PackMessageParams - ) + return this.packDIDCommV2Message(agentContext, message, params as DidCommV2PackMessageParams) } throw new AriesFrameworkError(`Unexpected pack DIDComm message params: ${params}`) } public async unpackMessage( agentContext: AgentContext, - message: EncryptedMessage | SignedMessage + encryptedMessage: EncryptedMessage ): Promise { - if (isEncryptedMessage(message)) { - return this.unpackJwe(agentContext, message) + return await agentContext.wallet.unpack(encryptedMessage) + } + + private async packDIDCommV1Message( + agentContext: AgentContext, + message: AgentMessage, + params: DidCommV1PackMessageParams + ): Promise { + const { recipientKeys, senderKey } = params + + // pass whether we want to use legacy did sov prefix + const unboundMessage = message.toJSON({ + useDidSovPrefixWhereAllowed: agentContext.config.useDidSovPrefixWhereAllowed, + }) + + this.logger.debug(`Pack outbound message ${unboundMessage['@type']}`) + + // Forward messages are anon packed + const packParams: WalletPackOptions = { + didCommVersion: DidCommMessageVersion.V1, + senderKey, + recipientKeys, } - if (isSignedMessage(message)) { - return this.unpackJws(agentContext, message) + const encryptedMessage = await agentContext.wallet.pack(unboundMessage, packParams) + return await this.wrapDIDCommV1MessageInForward(agentContext, encryptedMessage, params) + } + + private async wrapDIDCommV1MessageInForward( + agentContext: AgentContext, + encryptedMessage: EncryptedMessage, + params: DidCommV1PackMessageParams + ): Promise { + const { recipientKeys, routingKeys } = params + + if (!recipientKeys.length || !routingKeys.length) { + return encryptedMessage } - throw new AriesFrameworkError(`Unexpected message!`) + + // If the message has routing keys (mediator) pack for each mediator + let to = recipientKeys[0].publicKeyBase58 + for (const routingKey of routingKeys) { + const forwardMessage = new ForwardMessage({ + to, + message: encryptedMessage, + }) + to = routingKey.publicKeyBase58 + this.logger.debug('Forward message created', forwardMessage) + + const forwardJson = forwardMessage.toJSON({ + useDidSovPrefixWhereAllowed: agentContext.config.useDidSovPrefixWhereAllowed, + }) + + // Forward messages are anon packed + const forwardParams: WalletPackOptions = { + didCommVersion: DidCommMessageVersion.V1, + recipientKeys: [routingKey], + } + encryptedMessage = await agentContext.wallet.pack(forwardJson, forwardParams) + } + + return encryptedMessage } - private async unpackJwe(agentContext: AgentContext, message: EncryptedMessage): Promise { - if (isDidCommV1EncryptedEnvelope(message)) { - return this.didCommV1EnvelopeService.unpackMessage(agentContext, message) - } else { - return this.getDidCommV2EnvelopeService().unpackMessage(agentContext, message) + private async packDIDCommV2Message( + agentContext: AgentContext, + message: AgentMessage, + params: DidCommV2PackMessageParams + ): Promise { + const { recipientDidDoc, senderDidDoc } = params + const { senderKey, recipientKey } = EnvelopeService.findCommonSupportedEncryptionKeys(recipientDidDoc, senderDidDoc) + if (!recipientKey) { + throw new AriesFrameworkError( + `Unable to pack message ${message.id} because there is no any commonly supported key types to encrypt message` + ) } + const unboundMessage = message.toJSON() + const packParams: WalletPackOptions = { + didCommVersion: DidCommMessageVersion.V2, + recipientKeys: [recipientKey], + senderKey, + } + const encryptedMessage = await agentContext.wallet.pack(unboundMessage, packParams) + return await this.wrapDIDCommV2MessageInForward(agentContext, encryptedMessage, params) } - private async unpackJws(agentContext: AgentContext, message: SignedMessage): Promise { - return this.getDidCommV2EnvelopeService().unpackMessage(agentContext, message) + private async wrapDIDCommV2MessageInForward( + agentContext: AgentContext, + encryptedMessage: EncryptedMessage, + params: DidCommV2PackMessageParams + ): Promise { + const { recipientDidDoc, service } = params + if (!recipientDidDoc) { + return encryptedMessage + } + + const routings: { did: string; key: Key }[] = [] + for (const routingKey of service.routingKeys ?? []) { + const routingDidDocument = await this.didResolverService.resolveDidDocument(agentContext, routingKey) + routings.push({ + did: routingDidDocument.id, + key: keyReferenceToKey(routingDidDocument, routingKey), + }) + } + + if (!routings.length) { + return encryptedMessage + } + + // If the message has routing keys (mediator) pack for each mediator + let next = recipientDidDoc.id + for (const routing of routings) { + const forwardMessage = new V2ForwardMessage({ + to: [routing.did], + body: { next }, + attachments: [ + new V2Attachment({ + data: new V2AttachmentData({ json: encryptedMessage }), + }), + ], + }) + next = routing.did + this.logger.debug('Forward message created', forwardMessage) + + const forwardJson = forwardMessage.toJSON() + + // Forward messages are anon packed + const forwardParams: WalletPackOptions = { + didCommVersion: DidCommMessageVersion.V2, + recipientKeys: [routing.key], + } + encryptedMessage = await agentContext.wallet.pack(forwardJson, forwardParams) + } + + return encryptedMessage + } + + private static findCommonSupportedEncryptionKeys(recipientDidDocument: DidDocument, senderDidDocument?: DidDocument) { + const recipientAgreementKeys = getAgreementKeys(recipientDidDocument) + + if (!senderDidDocument) { + return { senderKey: undefined, recipientKey: recipientAgreementKeys[0] } + } + + const senderAgreementKeys = getAgreementKeys(senderDidDocument) + + let senderKey: Key | undefined + let recipientKey: Key | undefined + + for (const senderAgreementKey of senderAgreementKeys) { + for (const recipientAgreementKey of recipientAgreementKeys) { + if (senderAgreementKey.keyType === recipientAgreementKey.keyType) { + senderKey = senderAgreementKey + recipientKey = recipientAgreementKey + break + } + } + if (senderKey) break + } + + return { + senderKey, + recipientKey, + } } } diff --git a/packages/core/src/agent/Events.ts b/packages/core/src/agent/Events.ts index 4e7bb4b076..0eecc21fe4 100644 --- a/packages/core/src/agent/Events.ts +++ b/packages/core/src/agent/Events.ts @@ -1,6 +1,6 @@ -import type { ConnectionRecord } from '../modules/connections' import type { AgentMessage } from './AgentMessage' import type { OutboundMessageContext, OutboundMessageSendStatus } from './models' +import type { ConnectionRecord } from '../modules/connections' import type { Observable } from 'rxjs' import { filter } from 'rxjs' diff --git a/packages/core/src/agent/MessageHandler.ts b/packages/core/src/agent/MessageHandler.ts index 191a775ac1..30b5347ae0 100644 --- a/packages/core/src/agent/MessageHandler.ts +++ b/packages/core/src/agent/MessageHandler.ts @@ -1,5 +1,5 @@ -import type { ConstructableDidCommMessage } from '../didcomm' import type { InboundMessageContext, OutboundMessageContext } from './models' +import type { ConstructableDidCommMessage } from '../didcomm' export interface MessageHandler { readonly supportedMessages: readonly ConstructableDidCommMessage[] diff --git a/packages/core/src/agent/MessageHandlerRegistry.ts b/packages/core/src/agent/MessageHandlerRegistry.ts index b8348e0782..f050d4ea9e 100644 --- a/packages/core/src/agent/MessageHandlerRegistry.ts +++ b/packages/core/src/agent/MessageHandlerRegistry.ts @@ -1,5 +1,5 @@ -import type { ConstructableDidCommMessage } from '../didcomm' import type { MessageHandler } from './MessageHandler' +import type { ConstructableDidCommMessage } from '../didcomm' import { injectable } from 'tsyringe' diff --git a/packages/core/src/agent/MessageReceiver.ts b/packages/core/src/agent/MessageReceiver.ts index 6fa71ef5e8..919db39944 100644 --- a/packages/core/src/agent/MessageReceiver.ts +++ b/packages/core/src/agent/MessageReceiver.ts @@ -1,14 +1,14 @@ -import type { EncryptedMessage, PlaintextMessage, SignedMessage } from '../didcomm' -import type { DecryptedMessageContext } from '../didcomm/types' -import type { ConnectionRecord } from '../modules/connections' -import type { InboundTransport } from '../transport' import type { AgentMessage } from './AgentMessage' import type { TransportSession } from './TransportService' import type { AgentContext } from './context' +import type { EncryptedMessage, PlaintextMessage } from '../didcomm' +import type { DecryptedMessageContext } from '../didcomm/types' +import type { ConnectionRecord } from '../modules/connections' +import type { InboundTransport } from '../transport' import { InjectionSymbols } from '../constants' import { isPlaintextMessageV1, isPlaintextMessageV2 } from '../didcomm' -import { getPlaintextMessageType, isEncryptedMessage, isPlaintextMessage, isSignedMessage } from '../didcomm/helpers' +import { getPlaintextMessageType, isEncryptedMessage, isPlaintextMessage } from '../didcomm/helpers' import { AriesFrameworkError } from '../error' import { Logger } from '../logger' import { ConnectionService } from '../modules/connections' @@ -42,7 +42,7 @@ export class MessageReceiver { private connectionService: ConnectionService private messageHandlerRegistry: MessageHandlerRegistry private agentContextProvider: AgentContextProvider - public readonly inboundTransports: InboundTransport[] = [] + private _inboundTransports: InboundTransport[] = [] public constructor( envelopeService: EnvelopeService, @@ -62,10 +62,20 @@ export class MessageReceiver { this.messageHandlerRegistry = messageHandlerRegistry this.agentContextProvider = agentContextProvider this.logger = logger + this._inboundTransports = [] + } + + public get inboundTransports() { + return this._inboundTransports } public registerInboundTransport(inboundTransport: InboundTransport) { - this.inboundTransports.push(inboundTransport) + this._inboundTransports.push(inboundTransport) + } + + public async unregisterInboundTransport(inboundTransport: InboundTransport) { + this._inboundTransports = this._inboundTransports.filter((transport) => transport !== inboundTransport) + await inboundTransport.stop() } /** @@ -92,8 +102,6 @@ export class MessageReceiver { try { if (isEncryptedMessage(inboundMessage)) { return await this.receiveEncryptedMessage(agentContext, inboundMessage, session) - } else if (isSignedMessage(inboundMessage)) { - return await this.receiveSignedMessage(agentContext, inboundMessage, session) } else if (isPlaintextMessage(inboundMessage)) { await this.receivePlaintextMessage(agentContext, inboundMessage, connection) } else { @@ -124,15 +132,6 @@ export class MessageReceiver { return this.processUnpackedMessage(agentContext, unpackedMessage, session) } - private async receiveSignedMessage( - agentContext: AgentContext, - packedMessage: SignedMessage, - session?: TransportSession - ) { - const unpackedMessage = await this.envelopeService.unpackMessage(agentContext, packedMessage) - return this.processUnpackedMessage(agentContext, unpackedMessage, session) - } - private async processUnpackedMessage( agentContext: AgentContext, unpackedMessage: DecryptedMessageContext, @@ -174,7 +173,7 @@ export class MessageReceiver { // We allow unready connections to be attached to the session as we want to be able to // use return routing to make connections. This is especially useful for creating connections // with mediators when you don't have a public endpoint yet. - session.connection = connection ?? undefined + session.connectionId = connection?.id messageContext.sessionId = session.id this.transportService.saveSession(session) } else if (session) { @@ -232,10 +231,15 @@ export class MessageReceiver { }) } if (isPlaintextMessageV2(decryptedMessageContext.plaintextMessage)) { - // Try to find the did records that holds the sender and recipient keys - const { from } = decryptedMessageContext.plaintextMessage + // Try to find the did records that hold the sender and recipient did's + const { from, to } = decryptedMessageContext.plaintextMessage + if (!from) return null - return this.connectionService.findByTheirDid(agentContext, from) + const connection = this.connectionService.findByTheirDid(agentContext, from) + if (connection) return connection + + if (!to?.length) return null + return this.connectionService.findByOurDid(agentContext, to[0]) } return null diff --git a/packages/core/src/agent/MessageSender.ts b/packages/core/src/agent/MessageSender.ts index a0e97b64cf..d15b79feaf 100644 --- a/packages/core/src/agent/MessageSender.ts +++ b/packages/core/src/agent/MessageSender.ts @@ -1,26 +1,29 @@ -import type { EncryptedMessage, OutboundPackage, OutboundPackagePayload } from '../didcomm/types' -import type { DidCommV1Message } from '../didcomm/versions/v1' -import type { DidCommV2Message } from '../didcomm/versions/v2/DidCommV2Message' -import type { ConnectionRecord } from '../modules/connections' -import type { ResolvedDidCommService } from '../modules/didcomm' -import type { DidCommV2Service, DidDocument, DidDocumentService } from '../modules/dids' -import type { OutOfBandRecord } from '../modules/oob/repository' -import type { OutboundTransport } from '../transport/OutboundTransport' import type { AgentMessage } from './AgentMessage' import type { PackMessageParams } from './EnvelopeService' import type { AgentMessageSentEvent } from './Events' import type { TransportSession } from './TransportService' import type { AgentContext } from './context' +import type { + DidCommV1Message, + DidCommV2Message, + DidCommV2PackMessageParams, + EncryptedMessage, + EnvelopeType, + OutboundPackage, +} from '../didcomm' +import type { ConnectionRecord } from '../modules/connections' +import type { ResolvedDidCommService } from '../modules/didcomm' +import type { DidDocument } from '../modules/dids' +import type { OutOfBandRecord } from '../modules/oob/repository' +import type { OutboundTransport } from '../transport/OutboundTransport' import { DID_COMM_TRANSPORT_QUEUE, InjectionSymbols } from '../constants' import { ReturnRouteTypes } from '../decorators/transport/TransportDecorator' -import { EnvelopeType } from '../didcomm/types' -import { isDidCommV1Message } from '../didcomm/versions/v1' -import { isDidCommV2Message } from '../didcomm/versions/v2' +import { isDidCommV1Message, isDidCommV2Message } from '../didcomm/' import { AriesFrameworkError, MessageSendingError } from '../error' import { Logger } from '../logger' import { DidCommDocumentService } from '../modules/didcomm/services/DidCommDocumentService' -import { getKeyDidMappingByVerificationMethod } from '../modules/dids/domain/key-type' +import { DidCommV2Service, getAuthenticationKeys } from '../modules/dids' import { didKeyToInstanceOfKey } from '../modules/dids/helpers' import { DidResolverService } from '../modules/dids/services/DidResolverService' import { inject, injectable } from '../plugins' @@ -48,7 +51,7 @@ export class MessageSender { private didResolverService: DidResolverService private didCommDocumentService: DidCommDocumentService private eventEmitter: EventEmitter - public readonly outboundTransports: OutboundTransport[] = [] + private _outboundTransports: OutboundTransport[] = [] public constructor( envelopeService: EnvelopeService, @@ -66,26 +69,50 @@ export class MessageSender { this.didResolverService = didResolverService this.didCommDocumentService = didCommDocumentService this.eventEmitter = eventEmitter - this.outboundTransports = [] + this._outboundTransports = [] + } + + public get outboundTransports() { + return this._outboundTransports } public registerOutboundTransport(outboundTransport: OutboundTransport) { - this.outboundTransports.push(outboundTransport) + this._outboundTransports.push(outboundTransport) + } + + public async unregisterOutboundTransport(outboundTransport: OutboundTransport) { + this._outboundTransports = this.outboundTransports.filter((transport) => transport !== outboundTransport) + await outboundTransport.stop() + } + + public async sendMessage( + outboundMessage: OutboundMessageContext, + options?: { + transportPriority?: TransportPriorityOptions + } + ) { + if (isDidCommV1Message(outboundMessage.message)) { + return this.sendDIDCommV1Message(outboundMessage, options) + } + if (isDidCommV2Message(outboundMessage.message)) { + return this.sendDIDCommV2Message(outboundMessage, options) + } + throw new AriesFrameworkError(`Unexpected case`) } public async packMessage( agentContext: AgentContext, { - keys, + params, message, endpoint, }: { - keys: PackMessageParams + params: PackMessageParams message: AgentMessage endpoint: string } ): Promise { - const encryptedMessage = await this.envelopeService.packMessage(agentContext, message, keys) + const encryptedMessage = await this.envelopeService.packMessage(agentContext, message, params) return { payload: encryptedMessage, @@ -184,30 +211,16 @@ export class MessageSender { throw new AriesFrameworkError(`Message is undeliverable to connection ${connection.id} (${connection.theirLabel})`) } - public async sendMessage( - outboundMessage: OutboundMessageContext, - options?: { - transportPriority?: TransportPriorityOptions - } - ) { - if (isDidCommV1Message(outboundMessage.message)) { - return this.sendDIDCommV1Message(outboundMessage.message, outboundMessage, options) - } - if (isDidCommV2Message(outboundMessage.message)) { - return this.sendDIDCommV2Message(outboundMessage.message, outboundMessage, options) - } - throw new AriesFrameworkError(`Unexpected case`) - } - private async sendDIDCommV1Message( - message: DidCommV1Message, outboundMessageContext: OutboundMessageContext, options?: { envelopeType?: EnvelopeType transportPriority?: TransportPriorityOptions } ) { - const { agentContext, connection, outOfBand, sessionId } = outboundMessageContext + const { agentContext, connection, outOfBand } = outboundMessageContext + const message = outboundMessageContext.message as DidCommV1Message + const errors: Error[] = [] if (!connection) { @@ -223,17 +236,9 @@ export class MessageSender { connectionId: connection?.id, }) - let session: TransportSession | undefined + const session = this.findSessionForOutboundContext(outboundMessageContext) - if (sessionId) { - session = this.transportService.findSessionById(sessionId) - } - if (!session) { - // Try to send to already open session - session = this.transportService.findSessionByConnectionId(connection.id) - } - - if (session?.inboundMessage?.hasReturnRouting(message.threadId)) { + if (session) { this.logger.debug(`Found session with return routing for message '${message.id}' (connection '${connection.id}'`) try { await this.sendMessageToSession(agentContext, session, message) @@ -257,7 +262,7 @@ export class MessageSender { outOfBand )) } catch (error) { - this.logger.error(`Unable to retrieve services for connection '${connection.id}`) + this.logger.error(`Unable to retrieve services for connection '${connection.id}. ${error.message}`) this.emitMessageSentEvent(outboundMessageContext, OutboundMessageSendStatus.Undeliverable) throw new MessageSendingError(`Unable to retrieve services for connection '${connection.id}`, { outboundMessageContext, @@ -336,13 +341,13 @@ export class MessageSender { if (queueService) { this.logger.debug(`Queue message for connection ${connection.id} (${connection.theirLabel})`) - const keys = { + const params = { recipientKeys: queueService.recipientKeys, routingKeys: queueService.routingKeys, senderKey: firstOurAuthenticationKey, } - const encryptedMessage = await this.envelopeService.packMessage(agentContext, message, keys) + const encryptedMessage = await this.envelopeService.packMessage(agentContext, message, params) await this.messageRepository.add(connection.id, encryptedMessage) this.emitMessageSentEvent(outboundMessageContext, OutboundMessageSendStatus.QueuedForPickup) @@ -365,6 +370,20 @@ export class MessageSender { } public async sendMessageToService(outboundMessageContext: OutboundMessageContext) { + const session = this.findSessionForOutboundContext(outboundMessageContext) + + if (session) { + this.logger.debug(`Found session with return routing for message '${outboundMessageContext.message.id}'`) + try { + await this.sendMessageToSession(outboundMessageContext.agentContext, session, outboundMessageContext.message) + this.emitMessageSentEvent(outboundMessageContext, OutboundMessageSendStatus.SentToSession) + return + } catch (error) { + this.logger.debug(`Sending an outbound message via session failed with error: ${error.message}.`, error) + } + } + + // If there is no session try sending to service instead try { await this.sendToService(outboundMessageContext) this.emitMessageSentEvent(outboundMessageContext, OutboundMessageSendStatus.SentToTransport) @@ -402,7 +421,7 @@ export class MessageSender { service: { ...service, recipientKeys: 'omitted...', routingKeys: 'omitted...' }, }) - const keys = { + const params = { recipientKeys: service.recipientKeys, routingKeys: service.routingKeys, senderKey, @@ -427,13 +446,13 @@ export class MessageSender { throw error } - const outboundPackage = await this.packMessage(agentContext, { message, keys, endpoint: service.serviceEndpoint }) + const outboundPackage = await this.packMessage(agentContext, { message, params, endpoint: service.serviceEndpoint }) outboundPackage.endpoint = service.serviceEndpoint outboundPackage.connectionId = connection?.id for (const transport of this.outboundTransports) { const protocolScheme = getProtocolScheme(service.serviceEndpoint) if (!protocolScheme) { - this.logger.warn('Service does not have valid protocolScheme.') + this.logger.warn('Service does not have a protocol scheme.') } else if (transport.supportedSchemes.includes(protocolScheme)) { await transport.sendMessage(outboundPackage) return @@ -444,6 +463,25 @@ export class MessageSender { }) } + private findSessionForOutboundContext(outboundContext: OutboundMessageContext) { + let session: TransportSession | undefined = undefined + + // Use session id from outbound context if present, or use the session from the inbound message context + const sessionId = outboundContext.sessionId ?? outboundContext.inboundMessageContext?.sessionId + + // Try to find session by id + if (sessionId) { + session = this.transportService.findSessionById(sessionId) + } + + // Try to find session by connection id + if (!session && outboundContext.connection?.id) { + session = this.transportService.findSessionByConnectionId(outboundContext.connection.id) + } + + return session && session.inboundMessage?.hasAnyReturnRoute() ? session : null + } + private async retrieveServicesByConnection( agentContext: AgentContext, connection: ConnectionRecord, @@ -463,7 +501,8 @@ export class MessageSender { } else if (outOfBand) { this.logger.debug(`Resolving services from out-of-band record ${outOfBand.id}.`) if (connection.isRequester) { - for (const service of outOfBand.getOutOfBandInvitation().getServices()) { + const services = outOfBand.outOfBandInvitation?.getServices() || [] + for (const service of services) { // Resolve dids to DIDDocs to retrieve services if (typeof service === 'string') { this.logger.debug(`Resolving services for did ${service}.`) @@ -521,212 +560,132 @@ export class MessageSender { } private async sendDIDCommV2Message( - message: DidCommV2Message, outboundMessageContext: OutboundMessageContext, options?: { - envelopeType?: EnvelopeType transportPriority?: TransportPriorityOptions } ) { const { agentContext } = outboundMessageContext - const envelopeType = options?.envelopeType || EnvelopeType.Encrypted + const message = outboundMessageContext.message as DidCommV2Message - // recipient is not specified -> send to defaultTransport const recipient = message.firstRecipient if (!recipient) { + this.emitMessageSentEvent(outboundMessageContext, OutboundMessageSendStatus.Undeliverable) + this.logger.error(`Unable to send message. Message doesn't sender DID.`) throw new AriesFrameworkError(`Unable to send message. Message doesn't contain recipient DID.`) } - - // find service transport supported for both sender and receiver - const senderToRecipientService = await this.findCommonSupportedServices( - agentContext, - recipient, - message.from, - options?.transportPriority - ) - if (!senderToRecipientService) { - this.logger.error( - `Unable to send message ${message.id} because there is no any commonly supported service between sender and recipient` - ) - return - } - - if (envelopeType === EnvelopeType.Plain) { - // send message plaintext - return await this.sendDIDCommV2PlaintextMessage(agentContext, message, senderToRecipientService) - } - - if (envelopeType === EnvelopeType.Signed) { - // send message signed - return await this.sendDIDCommV2SignedMessage(agentContext, message, senderToRecipientService) + if (!message.from) { + this.emitMessageSentEvent(outboundMessageContext, OutboundMessageSendStatus.Undeliverable) + this.logger.error(`Unable to send message. Message doesn't sender DID.`) + throw new AriesFrameworkError(`Unable to send message. Message doesn't sender DID.`) } - if (envelopeType === EnvelopeType.Encrypted) { - // send message encrypted - return await this.sendDIDCommV2EncryptedMessage(agentContext, message, senderToRecipientService) + const { didDocument: senderDidDoc } = await this.didResolverService.resolve(agentContext, message.from) + if (!senderDidDoc) { + this.emitMessageSentEvent(outboundMessageContext, OutboundMessageSendStatus.Undeliverable) + this.logger.error(`Unable to resolve did document for did '${message.from}'`) + throw new AriesFrameworkError(`Unable to resolve did document for did '${message.from}'`) } - } - - private async findCommonSupportedServices( - agentContext: AgentContext, - recipient: string, - sender?: string, - transportPriority?: TransportPriorityOptions - ): Promise { - if (!sender) return undefined - - const { didDocument: senderDidDocument } = await this.didResolverService.resolve(agentContext, sender) - const { didDocument: recipientDidDocument } = await this.didResolverService.resolve(agentContext, recipient) - if (!recipientDidDocument) { + const { didDocument: recipientDidDoc } = await this.didResolverService.resolve(agentContext, recipient) + if (!recipientDidDoc) { + this.emitMessageSentEvent(outboundMessageContext, OutboundMessageSendStatus.Undeliverable) + this.logger.error(`Unable to resolve did document for did '${recipient}'`) throw new AriesFrameworkError(`Unable to resolve did document for did '${recipient}'`) } - const senderServices = senderDidDocument?.service || [] - const recipientServices = recipientDidDocument?.service || [] - - const senderTransports = senderServices.map((service) => service.protocolScheme) - const supportedTransports = transportPriority - ? [...transportPriority.schemes, ...senderTransports] - : senderTransports - - // Sort services according to supported transports - const priority = supportedTransports.map((transport) => transport.toString()) - - const services = recipientServices.sort(function (a, b) { - return priority.indexOf(a.protocolScheme) - priority.indexOf(b.protocolScheme) - }) - - const commonServices = services.filter((service) => { - if (priority.includes(service.protocolScheme)) return service - }) - - return commonServices - } - - private async sendDIDCommV2PlaintextMessage( - agentContext: AgentContext, - message: DidCommV2Message, - services: DidDocumentService[] - ) { - this.logger.debug(`Sending plaintext message ${message.id}`) - const recipientDid = message.firstRecipient - return this.sendOutboundDIDCommV2Message(agentContext, message, services, recipientDid) - } - - private async sendDIDCommV2SignedMessage( - agentContext: AgentContext, - message: DidCommV2Message, - services: DidDocumentService[] - ) { - this.logger.debug(`Sending JWS message ${message.id}`) - - const recipientDid = message.firstRecipient - - const pack = async (message: DidCommV2Message, service: DidDocumentService) => { - if (!message.from) { - throw new AriesFrameworkError(`Unable to send message signed. Message doesn't contain sender DID.`) - } - const params = { signByDID: message.from, serviceId: service?.id, type: EnvelopeType.Signed } - return this.envelopeService.packMessage(agentContext, message, params) - } - - return this.sendOutboundDIDCommV2Message(agentContext, message, services, recipientDid, pack) - } - - private async sendDIDCommV2EncryptedMessage( - agentContext: AgentContext, - message: DidCommV2Message, - services: DidDocumentService[] - ) { - const recipientDid = message.firstRecipient - if (!recipientDid) { - throw new AriesFrameworkError(`Unable to send message encrypted. Message doesn't contain recipient DID.`) - } - this.logger.debug(`Sending JWE message ${message.id}`) - - const pack = async (message: DidCommV2Message, service: DidDocumentService) => { - return await this.encryptDIDCommV2Message(agentContext, message, service) - } - - return this.sendOutboundDIDCommV2Message(agentContext, message, services, recipientDid, pack) - } - - private async encryptDIDCommV2Message( - agentContext: AgentContext, - message: DidCommV2Message, - service: DidDocumentService, - forward?: boolean - ) { - const recipientDid = message.firstRecipient - if (!recipientDid) { - throw new AriesFrameworkError(`Unable to send message encrypted. Message doesn't contain recipient DID.`) - } - - const params: PackMessageParams = { - toDid: recipientDid, - fromDid: message.from, - signByDid: undefined, - serviceId: service?.id, - wrapIntoForward: forward, - envelopeType: EnvelopeType.Encrypted, + // find service transport supported for both sender and receiver + const services = this.findCommonSupportedDidCommV2Services( + senderDidDoc, + recipientDidDoc, + options?.transportPriority + ) + if (!services) { + this.emitMessageSentEvent(outboundMessageContext, OutboundMessageSendStatus.Undeliverable) + this.logger.error( + `Unable to send message ${message.id} because there is no any commonly supported service between sender and recipient` + ) + throw new AriesFrameworkError( + `Unable to send message ${message.id} because there is no any commonly supported service between sender and recipient` + ) } - return this.envelopeService.packMessage(agentContext, message, params) - } - private async sendOutboundDIDCommV2Message( - agentContext: AgentContext, - message: DidCommV2Message, - services: DidDocumentService[], - recipientDid?: string, - packMessage?: (message: DidCommV2Message, service: DidDocumentService) => Promise - ) { + // pack and send message until first success for (const service of services) { try { - this.logger.info(`Sending message to ${service.serviceEndpoint}. Transport ${service.protocolScheme}`) - - const payload = packMessage ? await packMessage(message, service) : { ...message } - const outboundPackage = { payload, recipientDid, endpoint: service.serviceEndpoint } + this.logger.info( + `Sending message with id: ${message.id} to ${service.serviceEndpoint}. Transport ${service.protocolScheme}` + ) + const params: DidCommV2PackMessageParams = { + recipientDidDoc, + senderDidDoc, + service, + } + const payload = await this.envelopeService.packMessage(agentContext, message, params) + const outboundPackage = { + payload, + endpoint: service.serviceEndpoint, + } this.logger.trace(`Sending outbound message to transport:`, { transport: service.protocolScheme, outboundPackage, }) - for (const outboundTransport of this.outboundTransports) { if (outboundTransport.supportedSchemes.includes(service.protocolScheme)) { await outboundTransport.sendMessage(outboundPackage) break } } - + this.emitMessageSentEvent(outboundMessageContext, OutboundMessageSendStatus.SentToTransport) this.logger.info( - `Message sent ${message.id} to ${service.serviceEndpoint}. Transport ${service.protocolScheme}` + `Message with id: ${message.id} sent to ${service.serviceEndpoint}. Transport ${service.protocolScheme}` ) return } catch (error) { - this.logger.warn(`Unable to send message to ${service.serviceEndpoint}. Transport failure `, { - errors: error, - }) + this.logger.warn( + `Unable to send message with id: ${message.id} to ${service.serviceEndpoint}. Transport failure `, + { + errors: error, + } + ) // ignore and try another transport } } + this.emitMessageSentEvent(outboundMessageContext, OutboundMessageSendStatus.Undeliverable) this.logger.error(`Unable to send message ${message.id} through any commonly supported transport.`) + throw new AriesFrameworkError(`Unable to send message ${message.id} through any commonly supported transport.`) + } + + private findCommonSupportedDidCommV2Services( + senderDidDocument: DidDocument, + recipientDidDocument: DidDocument, + transportPriority?: TransportPriorityOptions + ): DidCommV2Service[] { + const senderServices = senderDidDocument?.service || [] + const recipientServices = recipientDidDocument?.service || [] + let services = recipientServices.filter((recipientService) => { + return senderServices.find((senderService) => { + const service = senderService as DidCommV2Service + return ( + senderService.protocolScheme === recipientService.protocolScheme && + senderService.type === DidCommV2Service.type && + (!service.accept?.length || service.accept.includes('didcomm/v2')) + ) + }) + }) + // If transport priority is set we will sort services by our priority + if (transportPriority?.schemes) { + services = services.sort(function (a, b) { + const aScheme = getProtocolScheme(a.serviceEndpoint) + const bScheme = getProtocolScheme(b.serviceEndpoint) + return transportPriority?.schemes.indexOf(aScheme) - transportPriority?.schemes.indexOf(bScheme) + }) + } + return services } } export function isDidCommTransportQueue(serviceEndpoint: string): serviceEndpoint is typeof DID_COMM_TRANSPORT_QUEUE { return serviceEndpoint === DID_COMM_TRANSPORT_QUEUE } - -function getAuthenticationKeys(didDocument: DidDocument) { - return ( - didDocument.authentication?.map((authentication) => { - const verificationMethod = - typeof authentication === 'string' ? didDocument.dereferenceVerificationMethod(authentication) : authentication - const { getKeyFromVerificationMethod } = getKeyDidMappingByVerificationMethod(verificationMethod) - const key = getKeyFromVerificationMethod(verificationMethod) - return key - }) ?? [] - ) -} diff --git a/packages/core/src/agent/TransportService.ts b/packages/core/src/agent/TransportService.ts index a734136879..6ea20d4aaa 100644 --- a/packages/core/src/agent/TransportService.ts +++ b/packages/core/src/agent/TransportService.ts @@ -1,10 +1,10 @@ -import type { EncryptedMessage } from '../didcomm' -import type { ConnectionRecord } from '../modules/connections/repository' -import type { DidDocument } from '../modules/dids' import type { AgentMessage } from './AgentMessage' import type { PackMessageParams } from './EnvelopeService' +import type { EncryptedMessage } from '../didcomm' +import type { DidDocument } from '../modules/dids' import { DID_COMM_TRANSPORT_QUEUE } from '../constants' +import { AriesFrameworkError } from '../error' import { injectable } from '../plugins' @injectable() @@ -16,7 +16,16 @@ export class TransportService { } public findSessionByConnectionId(connectionId: string) { - return Object.values(this.transportSessionTable).find((session) => session?.connection?.id === connectionId) + return Object.values(this.transportSessionTable).find((session) => session?.connectionId === connectionId) + } + + public setConnectionIdForSession(sessionId: string, connectionId: string) { + const session = this.findSessionById(sessionId) + if (!session) { + throw new AriesFrameworkError(`Session not found with id ${sessionId}`) + } + session.connectionId = connectionId + this.saveSession(session) } public hasInboundEndpoint(didDocument: DidDocument): boolean { @@ -36,12 +45,34 @@ interface TransportSessionTable { [sessionId: string]: TransportSession | undefined } +// In the framework Transport sessions are used for communication. A session is +// associated with a connection and it can be reused when we want to respond to +// a message. If the message, for example, does not contain any way to reply to +// this message, the session should be closed. When a new sequence of messages +// starts it can be used again. A session will be deleted when a WebSocket +// closes, for the WsTransportSession that is. export interface TransportSession { + // unique identifier for a transport session. This can a uuid, or anything else, as long + // as it uniquely identifies a transport. id: string + + // The type is something that explicitly defines the transport type. For WebSocket it would + // be "WebSocket" and for HTTP it would be "HTTP". type: string + + // The enveloping keys that can be used during the transport. This is used so the framework + // does not have to look up the associated keys for sending a message. keys?: PackMessageParams + + // A received message that will be used to check whether it has any return routing. inboundMessage?: AgentMessage - connection?: ConnectionRecord + + // A stored connection id used to find this session via the `TransportService` for a specific connection + connectionId?: string + + // Send an encrypted message send(encryptedMessage: EncryptedMessage): Promise + + // Close the session to prevent dangling sessions. close(): Promise } diff --git a/packages/core/src/agent/__tests__/Agent.test.ts b/packages/core/src/agent/__tests__/Agent.test.ts index ad3f8d8cbe..ed3154f8fd 100644 --- a/packages/core/src/agent/__tests__/Agent.test.ts +++ b/packages/core/src/agent/__tests__/Agent.test.ts @@ -2,6 +2,7 @@ import type { DependencyManager, Module } from '../../plugins' import { injectable } from 'tsyringe' +import { getIndySdkModules } from '../../../../indy-sdk/tests/setupIndySdkModule' import { getAgentOptions } from '../../../tests/helpers' import { InjectionSymbols } from '../../constants' import { BasicMessageRepository, BasicMessageService } from '../../modules/basic-messages' @@ -12,22 +13,18 @@ import { ConnectionRepository } from '../../modules/connections/repository/Conne import { ConnectionService } from '../../modules/connections/services/ConnectionService' import { CredentialRepository } from '../../modules/credentials' import { CredentialsApi } from '../../modules/credentials/CredentialsApi' -import { IndyLedgerService } from '../../modules/ledger' -import { LedgerApi } from '../../modules/ledger/LedgerApi' +import { MessagePickupApi } from '../../modules/message-pìckup' import { ProofRepository } from '../../modules/proofs' import { ProofsApi } from '../../modules/proofs/ProofsApi' -import { V1ProofService } from '../../modules/proofs/protocol/v1' -import { V2ProofService } from '../../modules/proofs/protocol/v2' import { MediationRecipientService, MediationRepository, MediatorApi, MediatorService, - RecipientApi, - RecipientModule, + MediationRecipientApi, + MediationRecipientModule, } from '../../modules/routing' import { InMemoryMessageRepository } from '../../storage/InMemoryMessageRepository' -import { IndyStorageService } from '../../storage/IndyStorageService' import { WalletError } from '../../wallet/error' import { Agent } from '../Agent' import { Dispatcher } from '../Dispatcher' @@ -36,7 +33,7 @@ import { FeatureRegistry } from '../FeatureRegistry' import { MessageReceiver } from '../MessageReceiver' import { MessageSender } from '../MessageSender' -const agentOptions = getAgentOptions('Agent Class Test') +const agentOptions = getAgentOptions('Agent Class Test', {}, getIndySdkModules()) const myModuleMethod = jest.fn() @injectable() @@ -64,6 +61,7 @@ describe('Agent', () => { ...agentOptions, modules: { myModule: new MyModule(), + ...getIndySdkModules(), }, }) @@ -78,9 +76,10 @@ describe('Agent', () => { ...agentOptions, modules: { myModule: new MyModule(), - mediationRecipient: new RecipientModule({ + mediationRecipient: new MediationRecipientModule({ maximumMessagePickup: 42, }), + ...getIndySdkModules(), }, }) @@ -161,8 +160,6 @@ describe('Agent', () => { expect(container.resolve(ConnectionRepository)).toBeInstanceOf(ConnectionRepository) expect(container.resolve(V1TrustPingService)).toBeInstanceOf(V1TrustPingService) - expect(container.resolve(V1ProofService)).toBeInstanceOf(V1ProofService) - expect(container.resolve(V2ProofService)).toBeInstanceOf(V2ProofService) expect(container.resolve(ProofsApi)).toBeInstanceOf(ProofsApi) expect(container.resolve(ProofRepository)).toBeInstanceOf(ProofRepository) @@ -174,18 +171,15 @@ describe('Agent', () => { expect(container.resolve(BasicMessageRepository)).toBeInstanceOf(BasicMessageRepository) expect(container.resolve(MediatorApi)).toBeInstanceOf(MediatorApi) - expect(container.resolve(RecipientApi)).toBeInstanceOf(RecipientApi) + expect(container.resolve(MediationRecipientApi)).toBeInstanceOf(MediationRecipientApi) + expect(container.resolve(MessagePickupApi)).toBeInstanceOf(MessagePickupApi) expect(container.resolve(MediationRepository)).toBeInstanceOf(MediationRepository) expect(container.resolve(MediatorService)).toBeInstanceOf(MediatorService) expect(container.resolve(MediationRecipientService)).toBeInstanceOf(MediationRecipientService) - expect(container.resolve(LedgerApi)).toBeInstanceOf(LedgerApi) - expect(container.resolve(IndyLedgerService)).toBeInstanceOf(IndyLedgerService) - // Symbols, interface based expect(container.resolve(InjectionSymbols.Logger)).toBe(agentOptions.config.logger) expect(container.resolve(InjectionSymbols.MessageRepository)).toBeInstanceOf(InMemoryMessageRepository) - expect(container.resolve(InjectionSymbols.StorageService)).toBeInstanceOf(IndyStorageService) // Agent expect(container.resolve(MessageSender)).toBeInstanceOf(MessageSender) @@ -204,8 +198,6 @@ describe('Agent', () => { expect(container.resolve(ConnectionRepository)).toBe(container.resolve(ConnectionRepository)) expect(container.resolve(V1TrustPingService)).toBe(container.resolve(V1TrustPingService)) - expect(container.resolve(V1ProofService)).toBe(container.resolve(V1ProofService)) - expect(container.resolve(V2ProofService)).toBe(container.resolve(V2ProofService)) expect(container.resolve(ProofsApi)).toBe(container.resolve(ProofsApi)) expect(container.resolve(ProofRepository)).toBe(container.resolve(ProofRepository)) @@ -217,14 +209,12 @@ describe('Agent', () => { expect(container.resolve(BasicMessageRepository)).toBe(container.resolve(BasicMessageRepository)) expect(container.resolve(MediatorApi)).toBe(container.resolve(MediatorApi)) - expect(container.resolve(RecipientApi)).toBe(container.resolve(RecipientApi)) + expect(container.resolve(MediationRecipientApi)).toBe(container.resolve(MediationRecipientApi)) + expect(container.resolve(MessagePickupApi)).toBe(container.resolve(MessagePickupApi)) expect(container.resolve(MediationRepository)).toBe(container.resolve(MediationRepository)) expect(container.resolve(MediatorService)).toBe(container.resolve(MediatorService)) expect(container.resolve(MediationRecipientService)).toBe(container.resolve(MediationRecipientService)) - expect(container.resolve(LedgerApi)).toBe(container.resolve(LedgerApi)) - expect(container.resolve(IndyLedgerService)).toBe(container.resolve(IndyLedgerService)) - // Symbols, interface based expect(container.resolve(InjectionSymbols.Logger)).toBe(container.resolve(InjectionSymbols.Logger)) expect(container.resolve(InjectionSymbols.MessageRepository)).toBe( @@ -254,19 +244,18 @@ describe('Agent', () => { 'https://didcomm.org/basicmessage/1.0', 'https://didcomm.org/connections/1.0', 'https://didcomm.org/coordinate-mediation/1.0', + 'https://didcomm.org/issue-credential/2.0', + 'https://didcomm.org/present-proof/2.0', 'https://didcomm.org/didexchange/1.0', 'https://didcomm.org/discover-features/1.0', 'https://didcomm.org/discover-features/2.0', - 'https://didcomm.org/issue-credential/1.0', - 'https://didcomm.org/issue-credential/2.0', 'https://didcomm.org/messagepickup/1.0', 'https://didcomm.org/messagepickup/2.0', 'https://didcomm.org/out-of-band/1.1', - 'https://didcomm.org/present-proof/1.0', 'https://didcomm.org/revocation_notification/1.0', 'https://didcomm.org/revocation_notification/2.0', ]) ) - expect(protocols.length).toEqual(14) + expect(protocols.length).toEqual(13) }) }) diff --git a/packages/core/src/agent/__tests__/AgentConfig.test.ts b/packages/core/src/agent/__tests__/AgentConfig.test.ts index 559a9880a3..59a32da7f3 100644 --- a/packages/core/src/agent/__tests__/AgentConfig.test.ts +++ b/packages/core/src/agent/__tests__/AgentConfig.test.ts @@ -18,6 +18,18 @@ describe('AgentConfig', () => { expect(agentConfig.endpoints).toStrictEqual(['didcomm:transport/queue']) }) + + it('should return the new config endpoint after setter is called', () => { + const endpoint = 'https://local-url.com' + const newEndpoint = 'https://new-local-url.com' + + const agentConfig = getAgentConfig('AgentConfig Test', { + endpoints: [endpoint], + }) + + agentConfig.endpoints = [newEndpoint] + expect(agentConfig.endpoints).toEqual([newEndpoint]) + }) }) describe('label', () => { @@ -40,7 +52,6 @@ describe('AgentConfig', () => { const agentConfig = new AgentConfig( { label: 'hello', - publicDidSeed: 'hello', }, agentDependencies ) @@ -49,7 +60,6 @@ describe('AgentConfig', () => { expect(newAgentConfig).toMatchObject({ label: 'hello', - publicDidSeed: 'hello', }) }) @@ -57,20 +67,16 @@ describe('AgentConfig', () => { const agentConfig = new AgentConfig( { label: 'hello', - publicDidSeed: 'hello', }, agentDependencies ) const newAgentConfig = agentConfig.extend({ label: 'anotherLabel', - autoAcceptConnections: true, }) expect(newAgentConfig).toMatchObject({ label: 'anotherLabel', - autoAcceptConnections: true, - publicDidSeed: 'hello', }) }) }) diff --git a/packages/core/src/agent/__tests__/AgentModules.test.ts b/packages/core/src/agent/__tests__/AgentModules.test.ts index cfb88ab7b0..aa3d9af950 100644 --- a/packages/core/src/agent/__tests__/AgentModules.test.ts +++ b/packages/core/src/agent/__tests__/AgentModules.test.ts @@ -1,24 +1,21 @@ import type { Module } from '../../plugins' -import { getAgentConfig } from '../../../tests/helpers' import { BasicMessagesModule } from '../../modules/basic-messages' +import { CacheModule } from '../../modules/cache' import { ConnectionsModule } from '../../modules/connections' import { CredentialsModule } from '../../modules/credentials' import { DidsModule } from '../../modules/dids' import { DiscoverFeaturesModule } from '../../modules/discover-features' import { GenericRecordsModule } from '../../modules/generic-records' -import { IndyModule } from '../../modules/indy' -import { LedgerModule } from '../../modules/ledger' +import { MessagePickupModule } from '../../modules/message-pìckup' import { OutOfBandModule } from '../../modules/oob' import { ProofsModule } from '../../modules/proofs' -import { MediatorModule, RecipientModule } from '../../modules/routing' -import { W3cVcModule } from '../../modules/vc' +import { MediationRecipientModule, MediatorModule } from '../../modules/routing' +import { W3cCredentialsModule } from '../../modules/vc' import { DependencyManager, injectable } from '../../plugins' import { WalletModule } from '../../wallet' import { extendModulesWithDefaultModules, getAgentApi } from '../AgentModules' -const agentConfig = getAgentConfig('AgentModules Test') - @injectable() class MyApi {} @@ -55,29 +52,29 @@ describe('AgentModules', () => { describe('extendModulesWithDefaultModules', () => { test('returns default modules if no modules were provided', () => { - const extendedModules = extendModulesWithDefaultModules(agentConfig) + const extendedModules = extendModulesWithDefaultModules() expect(extendedModules).toEqual({ connections: expect.any(ConnectionsModule), credentials: expect.any(CredentialsModule), proofs: expect.any(ProofsModule), mediator: expect.any(MediatorModule), - mediationRecipient: expect.any(RecipientModule), + mediationRecipient: expect.any(MediationRecipientModule), + messagePickup: expect.any(MessagePickupModule), basicMessages: expect.any(BasicMessagesModule), genericRecords: expect.any(GenericRecordsModule), - ledger: expect.any(LedgerModule), discovery: expect.any(DiscoverFeaturesModule), dids: expect.any(DidsModule), wallet: expect.any(WalletModule), oob: expect.any(OutOfBandModule), - indy: expect.any(IndyModule), - w3cVc: expect.any(W3cVcModule), + w3cCredentials: expect.any(W3cCredentialsModule), + cache: expect.any(CacheModule), }) }) test('returns custom and default modules if custom modules are provided', () => { const myModule = new MyModuleWithApi() - const extendedModules = extendModulesWithDefaultModules(agentConfig, { + const extendedModules = extendModulesWithDefaultModules({ myModule, }) @@ -86,16 +83,16 @@ describe('AgentModules', () => { credentials: expect.any(CredentialsModule), proofs: expect.any(ProofsModule), mediator: expect.any(MediatorModule), - mediationRecipient: expect.any(RecipientModule), + mediationRecipient: expect.any(MediationRecipientModule), + messagePickup: expect.any(MessagePickupModule), basicMessages: expect.any(BasicMessagesModule), genericRecords: expect.any(GenericRecordsModule), - ledger: expect.any(LedgerModule), discovery: expect.any(DiscoverFeaturesModule), dids: expect.any(DidsModule), wallet: expect.any(WalletModule), oob: expect.any(OutOfBandModule), - indy: expect.any(IndyModule), - w3cVc: expect.any(W3cVcModule), + w3cCredentials: expect.any(W3cCredentialsModule), + cache: expect.any(CacheModule), myModule, }) }) @@ -103,7 +100,7 @@ describe('AgentModules', () => { test('does not override default module if provided as custom module', () => { const myModule = new MyModuleWithApi() const connections = new ConnectionsModule() - const extendedModules = extendModulesWithDefaultModules(agentConfig, { + const extendedModules = extendModulesWithDefaultModules({ myModule, connections, }) @@ -113,16 +110,16 @@ describe('AgentModules', () => { credentials: expect.any(CredentialsModule), proofs: expect.any(ProofsModule), mediator: expect.any(MediatorModule), - mediationRecipient: expect.any(RecipientModule), + mediationRecipient: expect.any(MediationRecipientModule), + messagePickup: expect.any(MessagePickupModule), basicMessages: expect.any(BasicMessagesModule), genericRecords: expect.any(GenericRecordsModule), - ledger: expect.any(LedgerModule), discovery: expect.any(DiscoverFeaturesModule), dids: expect.any(DidsModule), wallet: expect.any(WalletModule), oob: expect.any(OutOfBandModule), - indy: expect.any(IndyModule), - w3cVc: expect.any(W3cVcModule), + w3cCredentials: expect.any(W3cCredentialsModule), + cache: expect.any(CacheModule), myModule, }) }) diff --git a/packages/core/src/agent/__tests__/DidCommV1Message.test.ts b/packages/core/src/agent/__tests__/DidCommV1Message.test.ts index 0c64b1aeee..1eeaee2bd7 100644 --- a/packages/core/src/agent/__tests__/DidCommV1Message.test.ts +++ b/packages/core/src/agent/__tests__/DidCommV1Message.test.ts @@ -10,16 +10,35 @@ class CustomProtocolMessage extends DidCommV1Message { public static readonly type = parseMessageType('https://didcomm.org/fake-protocol/1.5/message') } -describe('AgentMessage', () => { +class LegacyDidSovPrefixMessage extends DidCommV1Message { + public readonly allowDidSovPrefix = true + + @IsValidMessageType(LegacyDidSovPrefixMessage.type) + public readonly type = LegacyDidSovPrefixMessage.type.messageTypeUri + public static readonly type = parseMessageType('https://didcomm.org/fake-protocol/1.5/another-message') +} + +describe('DIDCommV1Message', () => { describe('toJSON', () => { - it('should only use did:sov message prefix if useLegacyDidSovPrefix is true', () => { + it('should only use did:sov message prefix if useDidSovPrefixWhereAllowed and allowDidSovPrefix are both true', () => { const message = new TestMessage() + const legacyPrefixMessage = new LegacyDidSovPrefixMessage() + + // useDidSovPrefixWhereAllowed & allowDidSovPrefix are both false + let testMessageJson = message.toJSON() + expect(testMessageJson['@type']).toBe('https://didcomm.org/connections/1.0/invitation') + + // useDidSovPrefixWhereAllowed is true, but allowDidSovPrefix is false + testMessageJson = message.toJSON({ useDidSovPrefixWhereAllowed: true }) + expect(testMessageJson['@type']).toBe('https://didcomm.org/connections/1.0/invitation') - const jsonDidComm = message.toJSON() - expect(jsonDidComm['@type']).toBe('https://didcomm.org/connections/1.0/invitation') + // useDidSovPrefixWhereAllowed is false, but allowDidSovPrefix is true + testMessageJson = legacyPrefixMessage.toJSON() + expect(testMessageJson['@type']).toBe('https://didcomm.org/fake-protocol/1.5/another-message') - const jsonSov = message.toJSON({ useLegacyDidSovPrefix: true }) - expect(jsonSov['@type']).toBe('did:sov:BzCbsNYhMrjHiqZDTUASHg;spec/connections/1.0/invitation') + // useDidSovPrefixWhereAllowed & allowDidSovPrefix are both true + testMessageJson = legacyPrefixMessage.toJSON({ useDidSovPrefixWhereAllowed: true }) + expect(testMessageJson['@type']).toBe('did:sov:BzCbsNYhMrjHiqZDTUASHg;spec/fake-protocol/1.5/another-message') }) }) diff --git a/packages/core/src/agent/__tests__/Dispatcher.test.ts b/packages/core/src/agent/__tests__/Dispatcher.test.ts index 55855f78b3..3bd968440c 100644 --- a/packages/core/src/agent/__tests__/Dispatcher.test.ts +++ b/packages/core/src/agent/__tests__/Dispatcher.test.ts @@ -1,7 +1,7 @@ import { Subject } from 'rxjs' import { getAgentConfig, getAgentContext } from '../../../tests/helpers' -import { DidCommV1Message, DidCommV2Message } from '../../didcomm' +import { DidCommV1Message } from '../../didcomm' import { parseMessageType } from '../../utils/messageType' import { Dispatcher } from '../Dispatcher' import { EventEmitter } from '../EventEmitter' @@ -9,16 +9,9 @@ import { MessageHandlerRegistry } from '../MessageHandlerRegistry' import { MessageSender } from '../MessageSender' import { InboundMessageContext } from '../models/InboundMessageContext' -const testMessageType = `https://didcomm.org/fake-protocol/1.5/message` - -class V1CustomProtocolMessage extends DidCommV1Message { - public readonly type = V1CustomProtocolMessage.type.messageTypeUri - public static readonly type = parseMessageType(testMessageType) -} - -class V2CustomProtocolMessage extends DidCommV2Message { - public readonly type = V2CustomProtocolMessage.type.messageTypeUri - public static readonly type = parseMessageType(testMessageType) +class CustomProtocolMessage extends DidCommV1Message { + public readonly type = CustomProtocolMessage.type.messageTypeUri + public static readonly type = parseMessageType('https://didcomm.org/fake-protocol/1.5/message') } describe('Dispatcher', () => { @@ -27,57 +20,20 @@ describe('Dispatcher', () => { const MessageSenderMock = MessageSender as jest.Mock const eventEmitter = new EventEmitter(agentConfig.agentDependencies, new Subject()) - describe('dispatch() for DidCommV1Message', () => { - it('calls the handle method of the handler', async () => { - const dispatcher = new Dispatcher( - new MessageSenderMock(), - eventEmitter, - new MessageHandlerRegistry(), - agentConfig.logger - ) - const customProtocolMessage = new V1CustomProtocolMessage() - const inboundMessageContext = new InboundMessageContext(customProtocolMessage, { agentContext }) - - const mockHandle = jest.fn() - dispatcher.registerMessageHandler({ supportedMessages: [V1CustomProtocolMessage], handle: mockHandle }) - - await dispatcher.dispatch(inboundMessageContext) - - expect(mockHandle).toHaveBeenNthCalledWith(1, inboundMessageContext) - }) - - it('throws an error if no handler for the message could be found', async () => { - const dispatcher = new Dispatcher( - new MessageSenderMock(), - eventEmitter, - new MessageHandlerRegistry(), - agentConfig.logger - ) - const customProtocolMessage = new V1CustomProtocolMessage() - const inboundMessageContext = new InboundMessageContext(customProtocolMessage, { agentContext }) - - const mockHandle = jest.fn() - dispatcher.registerMessageHandler({ supportedMessages: [], handle: mockHandle }) - - await expect(dispatcher.dispatch(inboundMessageContext)).rejects.toThrow( - `No handler for message type "${testMessageType}" found` - ) - }) - }) - - describe('dispatch() for DidCommV2Message', () => { + describe('dispatch()', () => { it('calls the handle method of the handler', async () => { + const messageHandlerRegistry = new MessageHandlerRegistry() const dispatcher = new Dispatcher( new MessageSenderMock(), eventEmitter, - new MessageHandlerRegistry(), + messageHandlerRegistry, agentConfig.logger ) - const customProtocolMessage = new V2CustomProtocolMessage() + const customProtocolMessage = new CustomProtocolMessage() const inboundMessageContext = new InboundMessageContext(customProtocolMessage, { agentContext }) const mockHandle = jest.fn() - dispatcher.registerMessageHandler({ supportedMessages: [V2CustomProtocolMessage], handle: mockHandle }) + messageHandlerRegistry.registerMessageHandler({ supportedMessages: [CustomProtocolMessage], handle: mockHandle }) await dispatcher.dispatch(inboundMessageContext) @@ -85,20 +41,21 @@ describe('Dispatcher', () => { }) it('throws an error if no handler for the message could be found', async () => { + const messageHandlerRegistry = new MessageHandlerRegistry() const dispatcher = new Dispatcher( new MessageSenderMock(), eventEmitter, new MessageHandlerRegistry(), agentConfig.logger ) - const customProtocolMessage = new V2CustomProtocolMessage() + const customProtocolMessage = new CustomProtocolMessage() const inboundMessageContext = new InboundMessageContext(customProtocolMessage, { agentContext }) const mockHandle = jest.fn() - dispatcher.registerMessageHandler({ supportedMessages: [], handle: mockHandle }) + messageHandlerRegistry.registerMessageHandler({ supportedMessages: [], handle: mockHandle }) await expect(dispatcher.dispatch(inboundMessageContext)).rejects.toThrow( - `No handler for message type "${testMessageType}" found` + 'No handler for message type "https://didcomm.org/fake-protocol/1.5/message" found' ) }) }) diff --git a/packages/core/src/agent/__tests__/MessageSender.test.ts b/packages/core/src/agent/__tests__/MessageSender.test.ts index 963153a7e8..54376087e3 100644 --- a/packages/core/src/agent/__tests__/MessageSender.test.ts +++ b/packages/core/src/agent/__tests__/MessageSender.test.ts @@ -655,16 +655,16 @@ describe('MessageSender', () => { jest.resetAllMocks() }) - test('return outbound message context with connection, payload and endpoint', async () => { + test('return outbound message context for DidCommV1 message with connection, payload and endpoint', async () => { const message = new TestMessage() const endpoint = 'https://example.com' - const keys = { + const params = { recipientKeys: [recipientKey], routingKeys: [], senderKey: senderKey, } - const result = await messageSender.packMessage(agentContext, { message, keys, endpoint }) + const result = await messageSender.packMessage(agentContext, { message, params, endpoint }) expect(result).toEqual({ payload: encryptedMessage, diff --git a/packages/core/src/agent/__tests__/TransportService.test.ts b/packages/core/src/agent/__tests__/TransportService.test.ts index c16d00478b..f46707fa3d 100644 --- a/packages/core/src/agent/__tests__/TransportService.test.ts +++ b/packages/core/src/agent/__tests__/TransportService.test.ts @@ -15,7 +15,7 @@ describe('TransportService', () => { test(`remove session saved for a given connection`, () => { const connection = getMockConnection({ id: 'test-123', role: DidExchangeRole.Responder }) const session = new DummyTransportSession('dummy-session-123') - session.connection = connection + session.connectionId = connection.id transportService.saveSession(session) expect(transportService.findSessionByConnectionId(connection.id)).toEqual(session) diff --git a/packages/core/src/agent/__tests__/stubs.ts b/packages/core/src/agent/__tests__/stubs.ts index 54ee3833fc..a55c6e1b3e 100644 --- a/packages/core/src/agent/__tests__/stubs.ts +++ b/packages/core/src/agent/__tests__/stubs.ts @@ -1,4 +1,3 @@ -import type { ConnectionRecord } from '../../modules/connections' import type { AgentMessage } from '../AgentMessage' import type { PackMessageParams } from '../EnvelopeService' import type { TransportSession } from '../TransportService' @@ -8,7 +7,7 @@ export class DummyTransportSession implements TransportSession { public readonly type = 'http' public keys?: PackMessageParams public inboundMessage?: AgentMessage - public connection?: ConnectionRecord + public connectionId?: string public constructor(id: string) { this.id = id diff --git a/packages/core/src/agent/context/AgentContext.ts b/packages/core/src/agent/context/AgentContext.ts index 714a5933a5..73a857d518 100644 --- a/packages/core/src/agent/context/AgentContext.ts +++ b/packages/core/src/agent/context/AgentContext.ts @@ -1,6 +1,6 @@ +import type { AgentContextProvider } from './AgentContextProvider' import type { DependencyManager } from '../../plugins' import type { Wallet } from '../../wallet' -import type { AgentContextProvider } from './AgentContextProvider' import { InjectionSymbols } from '../../constants' import { AgentConfig } from '../AgentConfig' diff --git a/packages/core/src/agent/models/OutboundMessageContext.ts b/packages/core/src/agent/models/OutboundMessageContext.ts index 465b339eb2..de0eca1705 100644 --- a/packages/core/src/agent/models/OutboundMessageContext.ts +++ b/packages/core/src/agent/models/OutboundMessageContext.ts @@ -1,4 +1,5 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ +import type { InboundMessageContext } from './InboundMessageContext' import type { Key } from '../../crypto' import type { ConnectionRecord } from '../../modules/connections' import type { ResolvedDidCommService } from '../../modules/didcomm' @@ -17,6 +18,7 @@ export interface ServiceMessageParams { export interface OutboundMessageContextParams { agentContext: AgentContext + inboundMessageContext?: InboundMessageContext associatedRecord?: BaseRecord connection?: ConnectionRecord serviceParams?: ServiceMessageParams @@ -31,6 +33,7 @@ export class OutboundMessageContext { public outOfBand?: OutOfBandRecord public associatedRecord?: BaseRecord public sessionId?: string + public inboundMessageContext?: InboundMessageContext public readonly agentContext: AgentContext public constructor(message: T, context: OutboundMessageContextParams) { @@ -40,6 +43,7 @@ export class OutboundMessageContext { this.outOfBand = context.outOfBand this.serviceParams = context.serviceParams this.associatedRecord = context.associatedRecord + this.inboundMessageContext = context.inboundMessageContext this.agentContext = context.agentContext } diff --git a/packages/core/src/cache/CacheRecord.ts b/packages/core/src/cache/CacheRecord.ts deleted file mode 100644 index 26388d1706..0000000000 --- a/packages/core/src/cache/CacheRecord.ts +++ /dev/null @@ -1,41 +0,0 @@ -import type { RecordTags, TagsBase } from '../storage/BaseRecord' - -import { BaseRecord } from '../storage/BaseRecord' -import { uuid } from '../utils/uuid' - -export type CustomCacheTags = TagsBase -export type DefaultCacheTags = TagsBase - -export type CacheTags = RecordTags - -export interface CacheStorageProps { - id?: string - createdAt?: Date - tags?: CustomCacheTags - - entries: Array<{ key: string; value: unknown }> -} - -export class CacheRecord extends BaseRecord { - public entries!: Array<{ key: string; value: unknown }> - - public static readonly type = 'CacheRecord' - public readonly type = CacheRecord.type - - public constructor(props: CacheStorageProps) { - super() - - if (props) { - this.id = props.id ?? uuid() - this.createdAt = props.createdAt ?? new Date() - this.entries = props.entries - this._tags = props.tags ?? {} - } - } - - public getTags() { - return { - ...this._tags, - } - } -} diff --git a/packages/core/src/cache/CacheRepository.ts b/packages/core/src/cache/CacheRepository.ts deleted file mode 100644 index 3adb2e4fd2..0000000000 --- a/packages/core/src/cache/CacheRepository.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { EventEmitter } from '../agent/EventEmitter' -import { InjectionSymbols } from '../constants' -import { inject, injectable } from '../plugins' -import { Repository } from '../storage/Repository' -import { StorageService } from '../storage/StorageService' - -import { CacheRecord } from './CacheRecord' - -@injectable() -export class CacheRepository extends Repository { - public constructor( - @inject(InjectionSymbols.StorageService) storageService: StorageService, - eventEmitter: EventEmitter - ) { - super(CacheRecord, storageService, eventEmitter) - } -} diff --git a/packages/core/src/cache/PersistedLruCache.ts b/packages/core/src/cache/PersistedLruCache.ts deleted file mode 100644 index ab00e0d14e..0000000000 --- a/packages/core/src/cache/PersistedLruCache.ts +++ /dev/null @@ -1,75 +0,0 @@ -import type { AgentContext } from '../agent' -import type { CacheRepository } from './CacheRepository' - -import { LRUMap } from 'lru_map' - -import { CacheRecord } from './CacheRecord' - -export class PersistedLruCache { - private cacheId: string - private limit: number - private _cache?: LRUMap - private cacheRepository: CacheRepository - - public constructor(cacheId: string, limit: number, cacheRepository: CacheRepository) { - this.cacheId = cacheId - this.limit = limit - this.cacheRepository = cacheRepository - } - - public async get(agentContext: AgentContext, key: string) { - const cache = await this.getCache(agentContext) - - return cache.get(key) - } - - public async set(agentContext: AgentContext, key: string, value: CacheValue) { - const cache = await this.getCache(agentContext) - - cache.set(key, value) - await this.persistCache(agentContext) - } - - private async getCache(agentContext: AgentContext) { - if (!this._cache) { - const cacheRecord = await this.fetchCacheRecord(agentContext) - this._cache = this.lruFromRecord(cacheRecord) - } - - return this._cache - } - - private lruFromRecord(cacheRecord: CacheRecord) { - return new LRUMap( - this.limit, - cacheRecord.entries.map((e) => [e.key, e.value as CacheValue]) - ) - } - - private async fetchCacheRecord(agentContext: AgentContext) { - let cacheRecord = await this.cacheRepository.findById(agentContext, this.cacheId) - - if (!cacheRecord) { - cacheRecord = new CacheRecord({ - id: this.cacheId, - entries: [], - }) - - await this.cacheRepository.save(agentContext, cacheRecord) - } - - return cacheRecord - } - - private async persistCache(agentContext: AgentContext) { - const cache = await this.getCache(agentContext) - - await this.cacheRepository.update( - agentContext, - new CacheRecord({ - entries: cache.toJSON(), - id: this.cacheId, - }) - ) - } -} diff --git a/packages/core/src/cache/__tests__/PersistedLruCache.test.ts b/packages/core/src/cache/__tests__/PersistedLruCache.test.ts deleted file mode 100644 index c7b893108d..0000000000 --- a/packages/core/src/cache/__tests__/PersistedLruCache.test.ts +++ /dev/null @@ -1,73 +0,0 @@ -import { getAgentContext, mockFunction } from '../../../tests/helpers' -import { CacheRecord } from '../CacheRecord' -import { CacheRepository } from '../CacheRepository' -import { PersistedLruCache } from '../PersistedLruCache' - -jest.mock('../CacheRepository') -const CacheRepositoryMock = CacheRepository as jest.Mock - -const agentContext = getAgentContext() - -describe('PersistedLruCache', () => { - let cacheRepository: CacheRepository - let cache: PersistedLruCache - - beforeEach(() => { - cacheRepository = new CacheRepositoryMock() - mockFunction(cacheRepository.findById).mockResolvedValue(null) - - cache = new PersistedLruCache('cacheId', 2, cacheRepository) - }) - - it('should return the value from the persisted record', async () => { - const findMock = mockFunction(cacheRepository.findById).mockResolvedValue( - new CacheRecord({ - id: 'cacheId', - entries: [ - { - key: 'test', - value: 'somevalue', - }, - ], - }) - ) - - expect(await cache.get(agentContext, 'doesnotexist')).toBeUndefined() - expect(await cache.get(agentContext, 'test')).toBe('somevalue') - expect(findMock).toHaveBeenCalledWith(agentContext, 'cacheId') - }) - - it('should set the value in the persisted record', async () => { - const updateMock = mockFunction(cacheRepository.update).mockResolvedValue() - - await cache.set(agentContext, 'test', 'somevalue') - const [[, cacheRecord]] = updateMock.mock.calls - - expect(cacheRecord.entries.length).toBe(1) - expect(cacheRecord.entries[0].key).toBe('test') - expect(cacheRecord.entries[0].value).toBe('somevalue') - - expect(await cache.get(agentContext, 'test')).toBe('somevalue') - }) - - it('should remove least recently used entries if entries are added that exceed the limit', async () => { - // Set first value in cache, resolves fine - await cache.set(agentContext, 'one', 'valueone') - expect(await cache.get(agentContext, 'one')).toBe('valueone') - - // Set two more entries in the cache. Third item - // exceeds limit, so first item gets removed - await cache.set(agentContext, 'two', 'valuetwo') - await cache.set(agentContext, 'three', 'valuethree') - expect(await cache.get(agentContext, 'one')).toBeUndefined() - expect(await cache.get(agentContext, 'two')).toBe('valuetwo') - expect(await cache.get(agentContext, 'three')).toBe('valuethree') - - // Get two from the cache, meaning three will be removed first now - // because it is not recently used - await cache.get(agentContext, 'two') - await cache.set(agentContext, 'four', 'valuefour') - expect(await cache.get(agentContext, 'three')).toBeUndefined() - expect(await cache.get(agentContext, 'two')).toBe('valuetwo') - }) -}) diff --git a/packages/core/src/cache/index.ts b/packages/core/src/cache/index.ts deleted file mode 100644 index dab23e81d6..0000000000 --- a/packages/core/src/cache/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export * from './PersistedLruCache' -export * from './CacheRecord' -export * from './CacheRepository' diff --git a/packages/core/src/crypto/EcCompression.ts b/packages/core/src/crypto/EcCompression.ts new file mode 100644 index 0000000000..42cbd30398 --- /dev/null +++ b/packages/core/src/crypto/EcCompression.ts @@ -0,0 +1,101 @@ +/** + * Based on https://github.com/transmute-industries/verifiable-data/blob/main/packages/web-crypto-key-pair/src/compression/ec-compression.ts + */ + +// native BigInteger is only supported in React Native 0.70+, so we use big-integer for now. +import bigInt from 'big-integer' + +import { Buffer } from '../utils/buffer' + +const curveToPointLength = { + 'P-256': 64, + 'P-384': 96, + 'P-521': 132, +} + +function getConstantsForCurve(curve: 'P-256' | 'P-384' | 'P-521') { + let two, prime, b, pIdent + + if (curve === 'P-256') { + two = bigInt(2) + prime = two.pow(256).subtract(two.pow(224)).add(two.pow(192)).add(two.pow(96)).subtract(1) + + pIdent = prime.add(1).divide(4) + + b = bigInt('5ac635d8aa3a93e7b3ebbd55769886bc651d06b0cc53b0f63bce3c3e27d2604b', 16) + } + + if (curve === 'P-384') { + two = bigInt(2) + prime = two.pow(384).subtract(two.pow(128)).subtract(two.pow(96)).add(two.pow(32)).subtract(1) + + pIdent = prime.add(1).divide(4) + b = bigInt('b3312fa7e23ee7e4988e056be3f82d19181d9c6efe8141120314088f5013875ac656398d8a2ed19d2a85c8edd3ec2aef', 16) + } + + if (curve === 'P-521') { + two = bigInt(2) + prime = two.pow(521).subtract(1) + b = bigInt( + '00000051953eb9618e1c9a1f929a21a0b68540eea2da725b99b315f3b8b489918ef109e156193951ec7e937b1652c0bd3bb1bf073573df883d2c34f1ef451fd46b503f00', + 16 + ) + pIdent = prime.add(1).divide(4) + } + + if (!prime || !b || !pIdent) { + throw new Error(`Unsupported curve ${curve}`) + } + + return { prime, b, pIdent } +} + +// see https://stackoverflow.com/questions/17171542/algorithm-for-elliptic-curve-point-compression +// https://github.com/w3c-ccg/did-method-key/pull/36 +/** + * Point compress elliptic curve key + * @return Compressed representation + */ +function compressECPoint(x: Uint8Array, y: Uint8Array): Uint8Array { + const out = new Uint8Array(x.length + 1) + out[0] = 2 + (y[y.length - 1] & 1) + out.set(x, 1) + return out +} + +function padWithZeroes(number: number | string, length: number) { + let value = '' + number + while (value.length < length) { + value = '0' + value + } + return value +} + +export function compress(publicKey: Uint8Array): Uint8Array { + const publicKeyHex = Buffer.from(publicKey).toString('hex') + const xHex = publicKeyHex.slice(0, publicKeyHex.length / 2) + const yHex = publicKeyHex.slice(publicKeyHex.length / 2, publicKeyHex.length) + const xOctet = Uint8Array.from(Buffer.from(xHex, 'hex')) + const yOctet = Uint8Array.from(Buffer.from(yHex, 'hex')) + return compressECPoint(xOctet, yOctet) +} + +export function expand(publicKey: Uint8Array, curve: 'P-256' | 'P-384' | 'P-521'): Uint8Array { + const publicKeyComponent = Buffer.from(publicKey).toString('hex') + const { prime, b, pIdent } = getConstantsForCurve(curve) + const signY = new Number(publicKeyComponent[1]).valueOf() - 2 + const x = bigInt(publicKeyComponent.substring(2), 16) + // y^2 = x^3 - 3x + b + let y = x.pow(3).subtract(x.multiply(3)).add(b).modPow(pIdent, prime) + + // If the parity doesn't match it's the *other* root + if (y.mod(2).toJSNumber() !== signY) { + // y = prime - y + y = prime.subtract(y) + } + + return Buffer.from( + padWithZeroes(x.toString(16), curveToPointLength[curve]) + padWithZeroes(y.toString(16), curveToPointLength[curve]), + 'hex' + ) +} diff --git a/packages/core/src/crypto/Jwk.ts b/packages/core/src/crypto/Jwk.ts new file mode 100644 index 0000000000..2c8d49d243 --- /dev/null +++ b/packages/core/src/crypto/Jwk.ts @@ -0,0 +1,73 @@ +import type { + Ed25519JwkPublicKey, + Jwk, + P256JwkPublicKey, + P384JwkPublicKey, + P521JwkPublicKey, + X25519JwkPublicKey, +} from './JwkTypes' +import type { Key } from './Key' + +import { TypedArrayEncoder, Buffer } from '../utils' + +import { compress, expand } from './EcCompression' +import { + jwkCurveToKeyTypeMapping, + keyTypeToJwkCurveMapping, + isEd25519JwkPublicKey, + isX25519JwkPublicKey, + isP256JwkPublicKey, + isP384JwkPublicKey, + isP521JwkPublicKey, +} from './JwkTypes' +import { KeyType } from './KeyType' + +export function getKeyDataFromJwk(jwk: Jwk): { keyType: KeyType; publicKey: Uint8Array } { + // ed25519, x25519 + if (isEd25519JwkPublicKey(jwk) || isX25519JwkPublicKey(jwk)) { + return { + publicKey: TypedArrayEncoder.fromBase64(jwk.x), + keyType: jwkCurveToKeyTypeMapping[jwk.crv], + } + } + + // p-256, p-384, p-521 + if (isP256JwkPublicKey(jwk) || isP384JwkPublicKey(jwk) || isP521JwkPublicKey(jwk)) { + // TODO: do we want to use the compressed key in the Key instance? + const publicKeyBuffer = Buffer.concat([TypedArrayEncoder.fromBase64(jwk.x), TypedArrayEncoder.fromBase64(jwk.y)]) + const compressedPublicKey = compress(publicKeyBuffer) + + return { + publicKey: compressedPublicKey, + keyType: jwkCurveToKeyTypeMapping[jwk.crv], + } + } + + throw new Error(`Unsupported JWK kty '${jwk.kty}' and crv '${jwk.crv}'`) +} + +export function getJwkFromKey(key: Key): Jwk { + if (key.keyType === KeyType.Ed25519 || key.keyType === KeyType.X25519) { + return { + kty: 'OKP', + crv: keyTypeToJwkCurveMapping[key.keyType], + x: TypedArrayEncoder.toBase64URL(key.publicKey), + } satisfies Ed25519JwkPublicKey | X25519JwkPublicKey + } + + if (key.keyType === KeyType.P256 || key.keyType === KeyType.P384 || key.keyType === KeyType.P521) { + const crv = keyTypeToJwkCurveMapping[key.keyType] + const expanded = expand(key.publicKey, crv) + const x = expanded.slice(0, expanded.length / 2) + const y = expanded.slice(expanded.length / 2) + + return { + kty: 'EC', + crv, + x: TypedArrayEncoder.toBase64URL(x), + y: TypedArrayEncoder.toBase64URL(y), + } satisfies P256JwkPublicKey | P384JwkPublicKey | P521JwkPublicKey + } + + throw new Error(`Cannot encode Key as JWK. Unsupported key type '${key.keyType}'`) +} diff --git a/packages/core/src/crypto/JwkTypes.ts b/packages/core/src/crypto/JwkTypes.ts new file mode 100644 index 0000000000..560b2c5e1d --- /dev/null +++ b/packages/core/src/crypto/JwkTypes.ts @@ -0,0 +1,139 @@ +import { KeyType } from './KeyType' + +export type JwkCurve = 'Ed25519' | 'X25519' | 'P-256' | 'P-384' | 'P-521' | 'Bls12381G1' | 'Bls12381G2' + +export interface Jwk { + kty: 'EC' | 'OKP' + crv: JwkCurve + x: string + y?: string + use?: 'sig' | 'enc' +} + +export interface Ed25519JwkPublicKey extends Jwk { + kty: 'OKP' + crv: 'Ed25519' + x: string + y?: never + use?: 'sig' +} + +export interface X25519JwkPublicKey extends Jwk { + kty: 'OKP' + crv: 'X25519' + x: string + y?: never + use?: 'enc' +} + +export interface P256JwkPublicKey extends Jwk { + kty: 'EC' + crv: 'P-256' + x: string + y: string + use?: 'sig' | 'enc' +} + +export interface P384JwkPublicKey extends Jwk { + kty: 'EC' + crv: 'P-384' + x: string + y: string + use?: 'sig' | 'enc' +} + +export interface P521JwkPublicKey extends Jwk { + kty: 'EC' + crv: 'P-521' + x: string + y: string + use?: 'sig' | 'enc' +} + +export function isEd25519JwkPublicKey(jwk: Jwk): jwk is Ed25519JwkPublicKey { + return jwk.kty === 'OKP' && jwk.crv === 'Ed25519' && jwk.x !== undefined && (!jwk.use || jwk.use === 'sig') +} + +export function isX25519JwkPublicKey(jwk: Jwk): jwk is X25519JwkPublicKey { + return jwk.kty === 'OKP' && jwk.crv === 'X25519' && jwk.x !== undefined && (!jwk.use || jwk.use === 'enc') +} + +export function isP256JwkPublicKey(jwk: Jwk): jwk is P256JwkPublicKey { + return ( + jwk.kty === 'EC' && + jwk.crv === 'P-256' && + jwk.x !== undefined && + jwk.y !== undefined && + (!jwk.use || ['sig', 'enc'].includes(jwk.use)) + ) +} + +export function isP384JwkPublicKey(jwk: Jwk): jwk is P384JwkPublicKey { + return ( + jwk.kty === 'EC' && + jwk.crv === 'P-384' && + jwk.x !== undefined && + jwk.y !== undefined && + (!jwk.use || ['sig', 'enc'].includes(jwk.use)) + ) +} + +export function isP521JwkPublicKey(jwk: Jwk): jwk is P521JwkPublicKey { + return ( + jwk.kty === 'EC' && + jwk.crv === 'P-521' && + jwk.x !== undefined && + jwk.y !== undefined && + (!jwk.use || ['sig', 'enc'].includes(jwk.use)) + ) +} + +export const jwkCurveToKeyTypeMapping = { + Ed25519: KeyType.Ed25519, + X25519: KeyType.X25519, + 'P-256': KeyType.P256, + 'P-384': KeyType.P384, + 'P-521': KeyType.P521, + Bls12381G1: KeyType.Bls12381g1, + Bls12381G2: KeyType.Bls12381g2, +} as const + +export const keyTypeToJwkCurveMapping = { + [KeyType.Ed25519]: 'Ed25519', + [KeyType.X25519]: 'X25519', + [KeyType.P256]: 'P-256', + [KeyType.P384]: 'P-384', + [KeyType.P521]: 'P-521', + [KeyType.Bls12381g1]: 'Bls12381G1', + [KeyType.Bls12381g2]: 'Bls12381G2', +} as const + +const keyTypeSigningSupportedMapping = { + [KeyType.Ed25519]: true, + [KeyType.X25519]: false, + [KeyType.P256]: true, + [KeyType.P384]: true, + [KeyType.P521]: true, + [KeyType.Bls12381g1]: true, + [KeyType.Bls12381g2]: true, + [KeyType.Bls12381g1g2]: true, +} as const + +const keyTypeEncryptionSupportedMapping = { + [KeyType.Ed25519]: false, + [KeyType.X25519]: true, + [KeyType.P256]: true, + [KeyType.P384]: true, + [KeyType.P521]: true, + [KeyType.Bls12381g1]: false, + [KeyType.Bls12381g2]: false, + [KeyType.Bls12381g1g2]: false, +} as const + +export function isSigningSupportedForKeyType(keyType: KeyType): boolean { + return keyTypeSigningSupportedMapping[keyType] +} + +export function isEncryptionSupportedForKeyType(keyType: KeyType): boolean { + return keyTypeEncryptionSupportedMapping[keyType] +} diff --git a/packages/core/src/crypto/JwsService.ts b/packages/core/src/crypto/JwsService.ts index 29a7f390e0..e81be473b8 100644 --- a/packages/core/src/crypto/JwsService.ts +++ b/packages/core/src/crypto/JwsService.ts @@ -1,6 +1,7 @@ +import type { Jwk } from './JwkTypes' +import type { Jws, JwsGeneralFormat } from './JwsTypes' import type { AgentContext } from '../agent' import type { Buffer } from '../utils' -import type { Jws, JwsGeneralFormat } from './JwsTypes' import { AriesFrameworkError } from '../error' import { injectable } from '../plugins' @@ -17,25 +18,63 @@ const JWS_ALG = 'EdDSA' @injectable() export class JwsService { - public async createJws( - agentContext: AgentContext, - { payload, verkey, header }: CreateJwsOptions - ): Promise { - const base64Payload = TypedArrayEncoder.toBase64URL(payload) - const base64Protected = JsonEncoder.toBase64URL(this.buildProtected(verkey)) - const key = Key.fromPublicKeyBase58(verkey, KeyType.Ed25519) + public static supportedKeyTypes = [KeyType.Ed25519] + + private async createJwsBase(agentContext: AgentContext, options: CreateJwsBaseOptions) { + if (!JwsService.supportedKeyTypes.includes(options.key.keyType)) { + throw new AriesFrameworkError( + `Only ${JwsService.supportedKeyTypes.join(',')} key type(s) supported for creating JWS` + ) + } + const base64Payload = TypedArrayEncoder.toBase64URL(options.payload) + const base64UrlProtectedHeader = JsonEncoder.toBase64URL(this.buildProtected(options.protectedHeaderOptions)) const signature = TypedArrayEncoder.toBase64URL( - await agentContext.wallet.sign({ data: TypedArrayEncoder.fromString(`${base64Protected}.${base64Payload}`), key }) + await agentContext.wallet.sign({ + data: TypedArrayEncoder.fromString(`${base64UrlProtectedHeader}.${base64Payload}`), + key: options.key, + }) ) return { - protected: base64Protected, + base64Payload, + base64UrlProtectedHeader, + signature, + } + } + + public async createJws( + agentContext: AgentContext, + { payload, key, header, protectedHeaderOptions }: CreateJwsOptions + ): Promise { + const { base64UrlProtectedHeader, signature } = await this.createJwsBase(agentContext, { + payload, + key, + protectedHeaderOptions, + }) + + return { + protected: base64UrlProtectedHeader, signature, header, } } + /** + * @see {@link https://www.rfc-editor.org/rfc/rfc7515#section-3.1} + * */ + public async createJwsCompact( + agentContext: AgentContext, + { payload, key, protectedHeaderOptions }: CreateCompactJwsOptions + ): Promise { + const { base64Payload, base64UrlProtectedHeader, signature } = await this.createJwsBase(agentContext, { + payload, + key, + protectedHeaderOptions, + }) + return `${base64UrlProtectedHeader}.${base64Payload}.${signature}` + } + /** * Verify a JWS */ @@ -47,7 +86,7 @@ export class JwsService { throw new AriesFrameworkError('Unable to verify JWS: No entries in JWS signatures array.') } - const signerVerkeys = [] + const signerKeys: Key[] = [] for (const jws of signatures) { const protectedJson = JsonEncoder.fromBase64(jws.protected) @@ -62,9 +101,9 @@ export class JwsService { const data = TypedArrayEncoder.fromString(`${jws.protected}.${base64Payload}`) const signature = TypedArrayEncoder.fromBase64(jws.signature) - const verkey = TypedArrayEncoder.toBase58(TypedArrayEncoder.fromBase64(protectedJson?.jwk?.x)) - const key = Key.fromPublicKeyBase58(verkey, KeyType.Ed25519) - signerVerkeys.push(verkey) + const publicKey = TypedArrayEncoder.fromBase64(protectedJson?.jwk?.x) + const key = Key.fromPublicKey(publicKey, KeyType.Ed25519) + signerKeys.push(key) try { const isValid = await agentContext.wallet.verify({ key, data, signature }) @@ -72,7 +111,7 @@ export class JwsService { if (!isValid) { return { isValid: false, - signerVerkeys: [], + signerKeys: [], } } } catch (error) { @@ -81,7 +120,7 @@ export class JwsService { if (error instanceof WalletError) { return { isValid: false, - signerVerkeys: [], + signerKeys: [], } } @@ -89,31 +128,36 @@ export class JwsService { } } - return { isValid: true, signerVerkeys } + return { isValid: true, signerKeys: signerKeys } } - /** - * @todo This currently only work with a single alg, key type and curve - * This needs to be extended with other formats in the future - */ - private buildProtected(verkey: string) { + private buildProtected(options: ProtectedHeaderOptions) { + if (!options.jwk && !options.kid) { + throw new AriesFrameworkError('Both JWK and kid are undefined. Please provide one or the other.') + } + if (options.jwk && options.kid) { + throw new AriesFrameworkError('Both JWK and kid are provided. Please only provide one of the two.') + } + return { - alg: 'EdDSA', - jwk: { - kty: 'OKP', - crv: 'Ed25519', - x: TypedArrayEncoder.toBase64URL(TypedArrayEncoder.fromBase58(verkey)), - }, + alg: options.alg, + jwk: options.jwk, + kid: options.kid, } } } export interface CreateJwsOptions { - verkey: string + key: Key payload: Buffer header: Record + protectedHeaderOptions: ProtectedHeaderOptions } +type CreateJwsBaseOptions = Omit + +type CreateCompactJwsOptions = Omit + export interface VerifyJwsOptions { jws: Jws payload: Buffer @@ -121,5 +165,14 @@ export interface VerifyJwsOptions { export interface VerifyJwsResult { isValid: boolean - signerVerkeys: string[] + signerKeys: Key[] +} + +export type kid = string + +export interface ProtectedHeaderOptions { + alg: string + jwk?: Jwk + kid?: kid + [key: string]: any } diff --git a/packages/core/src/crypto/Key.ts b/packages/core/src/crypto/Key.ts index 7ce19ec0ff..61efb67ef3 100644 --- a/packages/core/src/crypto/Key.ts +++ b/packages/core/src/crypto/Key.ts @@ -1,8 +1,11 @@ +import type { Jwk } from './JwkTypes' import type { KeyType } from './KeyType' import { Buffer, MultiBaseEncoder, TypedArrayEncoder, VarintEncoder } from '../utils' -import { getKeyTypeByMultiCodecPrefix, getMultiCodecPrefixByKeytype } from './multiCodecKey' +import { getJwkFromKey, getKeyDataFromJwk } from './Jwk' +import { isEncryptionSupportedForKeyType, isSigningSupportedForKeyType } from './JwkTypes' +import { getKeyTypeByMultiCodecPrefix, getMultiCodecPrefixByKeyType } from './multiCodecKey' export class Key { public readonly publicKey: Buffer @@ -39,7 +42,7 @@ export class Key { } public get prefixedPublicKey() { - const multiCodecPrefix = getMultiCodecPrefixByKeytype(this.keyType) + const multiCodecPrefix = getMultiCodecPrefixByKeyType(this.keyType) // Create Buffer with length of the prefix bytes, then use varint to fill the prefix bytes const prefixBytes = VarintEncoder.encode(multiCodecPrefix) @@ -55,4 +58,22 @@ export class Key { public get publicKeyBase58() { return TypedArrayEncoder.toBase58(this.publicKey) } + + public get supportsEncrypting() { + return isEncryptionSupportedForKeyType(this.keyType) + } + + public get supportsSigning() { + return isSigningSupportedForKeyType(this.keyType) + } + + public toJwk(): Jwk { + return getJwkFromKey(this) + } + + public static fromJwk(jwk: Jwk) { + const { keyType, publicKey } = getKeyDataFromJwk(jwk) + + return Key.fromPublicKey(publicKey, keyType) + } } diff --git a/packages/core/src/crypto/KeyType.ts b/packages/core/src/crypto/KeyType.ts index 858762f670..d378e4bffb 100644 --- a/packages/core/src/crypto/KeyType.ts +++ b/packages/core/src/crypto/KeyType.ts @@ -4,4 +4,7 @@ export enum KeyType { Bls12381g1 = 'bls12381g1', Bls12381g2 = 'bls12381g2', X25519 = 'x25519', + P256 = 'p256', + P384 = 'p384', + P521 = 'p521', } diff --git a/packages/core/src/crypto/WalletKeyPair.ts b/packages/core/src/crypto/WalletKeyPair.ts index f5008112db..97c8db0a56 100644 --- a/packages/core/src/crypto/WalletKeyPair.ts +++ b/packages/core/src/crypto/WalletKeyPair.ts @@ -1,9 +1,9 @@ +import type { Key } from './Key' import type { LdKeyPairOptions } from '../modules/vc/models/LdKeyPair' import type { Wallet } from '../wallet' -import type { Key } from './Key' import { VerificationMethod } from '../modules/dids' -import { getKeyDidMappingByVerificationMethod } from '../modules/dids/domain/key-type/keyDidMapping' +import { getKeyFromVerificationMethod } from '../modules/dids/domain/key-type/keyDidMapping' import { LdKeyPair } from '../modules/vc/models/LdKeyPair' import { JsonTransformer } from '../utils' import { MessageValidator } from '../utils/MessageValidator' @@ -43,7 +43,6 @@ export function createWalletKeyPairClass(wallet: Wallet) { public static async from(verificationMethod: VerificationMethod): Promise { const vMethod = JsonTransformer.fromJSON(verificationMethod, VerificationMethod) MessageValidator.validateSync(vMethod) - const { getKeyFromVerificationMethod } = getKeyDidMappingByVerificationMethod(vMethod) const key = getKeyFromVerificationMethod(vMethod) return new WalletKeyPair({ diff --git a/packages/core/src/crypto/__tests__/Jwk.test.ts b/packages/core/src/crypto/__tests__/Jwk.test.ts new file mode 100644 index 0000000000..0cbe81255e --- /dev/null +++ b/packages/core/src/crypto/__tests__/Jwk.test.ts @@ -0,0 +1,97 @@ +import type { + Ed25519JwkPublicKey, + P256JwkPublicKey, + P384JwkPublicKey, + P521JwkPublicKey, + X25519JwkPublicKey, +} from '../JwkTypes' + +import { getJwkFromKey, getKeyDataFromJwk } from '../Jwk' +import { Key } from '../Key' +import { KeyType } from '../KeyType' + +describe('jwk', () => { + it('Ed25519', () => { + const fingerprint = 'z6MkiTBz1ymuepAQ4HEHYSF1H8quG5GLVVQR3djdX3mDooWp' + const jwk = { + kty: 'OKP', + crv: 'Ed25519', + x: 'O2onvM62pC1io6jQKm8Nc2UyFXcd4kOmOsBIoYtZ2ik', + } satisfies Ed25519JwkPublicKey + + const { keyType, publicKey } = getKeyDataFromJwk(jwk) + expect(keyType).toEqual(KeyType.Ed25519) + expect(Key.fromPublicKey(publicKey, KeyType.Ed25519).fingerprint).toEqual(fingerprint) + + const actualJwk = getJwkFromKey(Key.fromFingerprint(fingerprint)) + expect(actualJwk).toEqual(jwk) + }) + + it('X25519', () => { + const fingerprint = 'z6LShs9GGnqk85isEBzzshkuVWrVKsRp24GnDuHk8QWkARMW' + const jwk = { + kty: 'OKP', + crv: 'X25519', + x: 'W_Vcc7guviK-gPNDBmevVw-uJVamQV5rMNQGUwCqlH0', + } satisfies X25519JwkPublicKey + + const { keyType, publicKey } = getKeyDataFromJwk(jwk) + expect(keyType).toEqual(KeyType.X25519) + expect(Key.fromPublicKey(publicKey, KeyType.X25519).fingerprint).toEqual(fingerprint) + + const actualJwk = getJwkFromKey(Key.fromFingerprint(fingerprint)) + expect(actualJwk).toEqual(jwk) + }) + + it('P-256', () => { + const fingerprint = 'zDnaerx9CtbPJ1q36T5Ln5wYt3MQYeGRG5ehnPAmxcf5mDZpv' + const jwk = { + kty: 'EC', + crv: 'P-256', + x: 'igrFmi0whuihKnj9R3Om1SoMph72wUGeFaBbzG2vzns', + y: 'efsX5b10x8yjyrj4ny3pGfLcY7Xby1KzgqOdqnsrJIM', + } satisfies P256JwkPublicKey + + const { keyType, publicKey } = getKeyDataFromJwk(jwk) + expect(keyType).toEqual(KeyType.P256) + expect(Key.fromPublicKey(publicKey, KeyType.P256).fingerprint).toEqual(fingerprint) + + const actualJwk = getJwkFromKey(Key.fromFingerprint(fingerprint)) + expect(actualJwk).toEqual(jwk) + }) + + it('P-384', () => { + const fingerprint = 'z82Lm1MpAkeJcix9K8TMiLd5NMAhnwkjjCBeWHXyu3U4oT2MVJJKXkcVBgjGhnLBn2Kaau9' + const jwk = { + kty: 'EC', + crv: 'P-384', + x: 'lInTxl8fjLKp_UCrxI0WDklahi-7-_6JbtiHjiRvMvhedhKVdHBfi2HCY8t_QJyc', + y: 'y6N1IC-2mXxHreETBW7K3mBcw0qGr3CWHCs-yl09yCQRLcyfGv7XhqAngHOu51Zv', + } satisfies P384JwkPublicKey + + const { keyType, publicKey } = getKeyDataFromJwk(jwk) + expect(keyType).toEqual(KeyType.P384) + expect(Key.fromPublicKey(publicKey, KeyType.P384).fingerprint).toEqual(fingerprint) + + const actualJwk = getJwkFromKey(Key.fromFingerprint(fingerprint)) + expect(actualJwk).toEqual(jwk) + }) + + it('P-521', () => { + const fingerprint = + 'z2J9gaYxrKVpdoG9A4gRnmpnRCcxU6agDtFVVBVdn1JedouoZN7SzcyREXXzWgt3gGiwpoHq7K68X4m32D8HgzG8wv3sY5j7' + const jwk = { + kty: 'EC', + crv: 'P-521', + x: 'ASUHPMyichQ0QbHZ9ofNx_l4y7luncn5feKLo3OpJ2nSbZoC7mffolj5uy7s6KSKXFmnNWxGJ42IOrjZ47qqwqyS', + y: 'AW9ziIC4ZQQVSNmLlp59yYKrjRY0_VqO-GOIYQ9tYpPraBKUloEId6cI_vynCzlZWZtWpgOM3HPhYEgawQ703RjC', + } satisfies P521JwkPublicKey + + const { keyType, publicKey } = getKeyDataFromJwk(jwk) + expect(keyType).toEqual(KeyType.P521) + expect(Key.fromPublicKey(publicKey, KeyType.P521).fingerprint).toEqual(fingerprint) + + const actualJwk = getJwkFromKey(Key.fromFingerprint(fingerprint)) + expect(actualJwk).toEqual(jwk) + }) +}) diff --git a/packages/core/src/crypto/__tests__/JwsService.test.ts b/packages/core/src/crypto/__tests__/JwsService.test.ts index 97db2ee81b..ed600880d4 100644 --- a/packages/core/src/crypto/__tests__/JwsService.test.ts +++ b/packages/core/src/crypto/__tests__/JwsService.test.ts @@ -1,10 +1,11 @@ import type { AgentContext } from '../../agent' -import type { Wallet } from '../../wallet' +import type { Key, Wallet } from '@aries-framework/core' +import { IndySdkWallet } from '../../../../indy-sdk/src' +import { indySdk } from '../../../../indy-sdk/tests/setupIndySdkModule' import { getAgentConfig, getAgentContext } from '../../../tests/helpers' import { DidKey } from '../../modules/dids' -import { Buffer, JsonEncoder } from '../../utils' -import { IndyWallet } from '../../wallet/IndyWallet' +import { Buffer, JsonEncoder, TypedArrayEncoder } from '../../utils' import { JwsService } from '../JwsService' import { KeyType } from '../KeyType' import { KeyProviderRegistry } from '../key-provider' @@ -16,17 +17,26 @@ describe('JwsService', () => { let wallet: Wallet let agentContext: AgentContext let jwsService: JwsService - + let didJwsz6MkfKey: Key + let didJwsz6MkvKey: Key beforeAll(async () => { const config = getAgentConfig('JwsService') - wallet = new IndyWallet(config.agentDependencies, config.logger, new KeyProviderRegistry([])) + // TODO: update to InMemoryWallet + wallet = new IndySdkWallet(indySdk, config.logger, new KeyProviderRegistry([])) agentContext = getAgentContext({ wallet, }) - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - await wallet.createAndOpen(config.walletConfig!) + await wallet.createAndOpen(config.walletConfig) jwsService = new JwsService() + didJwsz6MkfKey = await wallet.createKey({ + privateKey: TypedArrayEncoder.fromString(didJwsz6Mkf.SEED), + keyType: KeyType.Ed25519, + }) + didJwsz6MkvKey = await wallet.createKey({ + privateKey: TypedArrayEncoder.fromString(didJwsz6Mkv.SEED), + keyType: KeyType.Ed25519, + }) }) afterAll(async () => { @@ -35,16 +45,17 @@ describe('JwsService', () => { describe('createJws', () => { it('creates a jws for the payload with the key associated with the verkey', async () => { - const key = await wallet.createKey({ seed: didJwsz6Mkf.SEED, keyType: KeyType.Ed25519 }) - const payload = JsonEncoder.toBuffer(didJwsz6Mkf.DATA_JSON) - const kid = new DidKey(key).did + const kid = new DidKey(didJwsz6MkfKey).did const jws = await jwsService.createJws(agentContext, { payload, - // FIXME: update to use key instance instead of verkey - verkey: key.publicKeyBase58, + key: didJwsz6MkfKey, header: { kid }, + protectedHeaderOptions: { + alg: 'EdDSA', + jwk: didJwsz6MkfKey.toJwk(), + }, }) expect(jws).toEqual(didJwsz6Mkf.JWS_JSON) @@ -55,37 +66,37 @@ describe('JwsService', () => { it('returns true if the jws signature matches the payload', async () => { const payload = JsonEncoder.toBuffer(didJwsz6Mkf.DATA_JSON) - const { isValid, signerVerkeys } = await jwsService.verifyJws(agentContext, { + const { isValid, signerKeys } = await jwsService.verifyJws(agentContext, { payload, jws: didJwsz6Mkf.JWS_JSON, }) expect(isValid).toBe(true) - expect(signerVerkeys).toEqual([didJwsz6Mkf.VERKEY]) + expect(signerKeys).toEqual([didJwsz6MkfKey]) }) it('returns all verkeys that signed the jws', async () => { const payload = JsonEncoder.toBuffer(didJwsz6Mkf.DATA_JSON) - const { isValid, signerVerkeys } = await jwsService.verifyJws(agentContext, { + const { isValid, signerKeys } = await jwsService.verifyJws(agentContext, { payload, jws: { signatures: [didJwsz6Mkf.JWS_JSON, didJwsz6Mkv.JWS_JSON] }, }) expect(isValid).toBe(true) - expect(signerVerkeys).toEqual([didJwsz6Mkf.VERKEY, didJwsz6Mkv.VERKEY]) + expect(signerKeys).toEqual([didJwsz6MkfKey, didJwsz6MkvKey]) }) it('returns false if the jws signature does not match the payload', async () => { const payload = JsonEncoder.toBuffer({ ...didJwsz6Mkf.DATA_JSON, did: 'another_did' }) - const { isValid, signerVerkeys } = await jwsService.verifyJws(agentContext, { + const { isValid, signerKeys } = await jwsService.verifyJws(agentContext, { payload, jws: didJwsz6Mkf.JWS_JSON, }) expect(isValid).toBe(false) - expect(signerVerkeys).toMatchObject([]) + expect(signerKeys).toMatchObject([]) }) it('throws an error if the jws signatures array does not contain a JWS', async () => { diff --git a/packages/core/src/crypto/index.ts b/packages/core/src/crypto/index.ts index 2475153573..e8ed494387 100644 --- a/packages/core/src/crypto/index.ts +++ b/packages/core/src/crypto/index.ts @@ -1,3 +1,9 @@ +export { Jwk } from './JwkTypes' +export { JwsService } from './JwsService' + +export * from './jwtUtils' +export * from './keyUtils' + export { KeyType } from './KeyType' export { Key } from './Key' export * from './key-provider' diff --git a/packages/core/src/crypto/jwtUtils.ts b/packages/core/src/crypto/jwtUtils.ts new file mode 100644 index 0000000000..f60958fdf4 --- /dev/null +++ b/packages/core/src/crypto/jwtUtils.ts @@ -0,0 +1,13 @@ +export const jwtKeyAlgMapping = { + HMAC: ['HS256', 'HS384', 'HS512'], + RSA: ['RS256', 'RS384', 'RS512'], + ECDSA: ['ES256', 'ES384', 'ES512'], + 'RSA-PSS': ['PS256', 'PS384', 'PS512'], + EdDSA: ['Ed25519'], +} + +export type JwtAlgorithm = keyof typeof jwtKeyAlgMapping + +export function isJwtAlgorithm(value: string): value is JwtAlgorithm { + return Object.keys(jwtKeyAlgMapping).includes(value) +} diff --git a/packages/core/src/crypto/key-provider/Ed25519KeyProvider.ts b/packages/core/src/crypto/key-provider/Ed25519KeyProvider.ts deleted file mode 100644 index 155648d7c3..0000000000 --- a/packages/core/src/crypto/key-provider/Ed25519KeyProvider.ts +++ /dev/null @@ -1,42 +0,0 @@ -import type { CreateKeyPairOptions, KeyPair, KeyProvider, SignOptions, VerifyOptions } from './KeyProvider' - -import * as ed25519 from '@stablelib/ed25519' - -import { injectable } from '../../plugins' -import { TypedArrayEncoder } from '../../utils' -import { Buffer } from '../../utils/buffer' -import { KeyType } from '../KeyType' - -/** - * This will be extracted to the ed25519 package. - */ -@injectable() -export class Ed25519KeyProvider implements KeyProvider { - public readonly keyType = KeyType.Ed25519 - - /** - * Create a KeyPair with type ED25519 - * - * @throws {KeyProviderError} When a key could not be created - */ - public async createKeyPair({ seed }: CreateKeyPairOptions): Promise { - const keyPair = seed ? ed25519.generateKeyPairFromSeed(new Buffer(seed)) : ed25519.generateKeyPair() - - return { - keyType: KeyType.Ed25519, - publicKeyBase58: TypedArrayEncoder.toBase58(keyPair.publicKey), - privateKeyBase58: TypedArrayEncoder.toBase58(keyPair.secretKey), - } - } - - public async sign({ data, privateKeyBase58 }: SignOptions): Promise { - const secretKeyBytes = TypedArrayEncoder.fromBase58(privateKeyBase58) - return Buffer.from(ed25519.sign(secretKeyBytes, data as Buffer)) - } - - public async verify({ data, publicKeyBase58, signature }: VerifyOptions): Promise { - const publicKeyBytes = TypedArrayEncoder.fromBase58(publicKeyBase58) - const message = Uint8Array.from(data as Buffer) - return ed25519.verify(publicKeyBytes, message, signature) - } -} diff --git a/packages/core/src/crypto/key-provider/KeyProvider.ts b/packages/core/src/crypto/key-provider/KeyProvider.ts index 20fa658403..c36bcfe476 100644 --- a/packages/core/src/crypto/key-provider/KeyProvider.ts +++ b/packages/core/src/crypto/key-provider/KeyProvider.ts @@ -20,7 +20,8 @@ export interface VerifyOptions { } export interface CreateKeyPairOptions { - seed?: string + seed?: Buffer + privateKey?: Buffer } export interface KeyProvider { diff --git a/packages/core/src/crypto/key-provider/KeyProviderRegistry.ts b/packages/core/src/crypto/key-provider/KeyProviderRegistry.ts index c817ddfc57..ad4ea35aa9 100644 --- a/packages/core/src/crypto/key-provider/KeyProviderRegistry.ts +++ b/packages/core/src/crypto/key-provider/KeyProviderRegistry.ts @@ -1,5 +1,5 @@ -import type { KeyType } from '..' import type { KeyProvider } from './KeyProvider' +import type { KeyType } from '..' import { AriesFrameworkError } from '../../error' import { injectable, injectAll } from '../../plugins' @@ -10,23 +10,28 @@ export const KeyProviderToken = Symbol('KeyProviderToken') export class KeyProviderRegistry { public keyProviders: KeyProvider[] - public constructor(@injectAll(KeyProviderToken) keyProviders: KeyProvider[]) { - this.keyProviders = keyProviders + public constructor(@injectAll(KeyProviderToken) keyProviders: Array<'default' | KeyProvider>) { + // This is a really ugly hack to make tsyringe work without any SigningProviders registered + // It is currently impossible to use @injectAll if there are no instances registered for the + // token. We register a value of `default` by default and will filter that out in the registry. + // Once we have a signing provider that should always be registered we can remove this. We can make an ed25519 + // signer using the @stablelib/ed25519 library. + this.keyProviders = keyProviders.filter((provider) => provider !== 'default') as KeyProvider[] } public hasProviderForKeyType(keyType: KeyType): boolean { - const keyProvider = this.keyProviders.find((x) => x.keyType === keyType) + const signingKeyProvider = this.keyProviders.find((x) => x.keyType === keyType) - return keyProvider !== undefined + return signingKeyProvider !== undefined } public getProviderForKeyType(keyType: KeyType): KeyProvider { - const keyProvider = this.keyProviders.find((x) => x.keyType === keyType) + const signingKeyProvider = this.keyProviders.find((x) => x.keyType === keyType) - if (!keyProvider) { + if (!signingKeyProvider) { throw new AriesFrameworkError(`No key provider for key type: ${keyType}`) } - return keyProvider + return signingKeyProvider } } diff --git a/packages/core/src/crypto/key-provider/X25519KeyProvider.ts b/packages/core/src/crypto/key-provider/X25519KeyProvider.ts deleted file mode 100644 index bb10c9926b..0000000000 --- a/packages/core/src/crypto/key-provider/X25519KeyProvider.ts +++ /dev/null @@ -1,41 +0,0 @@ -import type { CreateKeyPairOptions, KeyPair, KeyProvider } from './KeyProvider' - -import * as ed25519 from '@stablelib/ed25519' - -import { injectable } from '../../plugins' -import { TypedArrayEncoder } from '../../utils' -import { Buffer } from '../../utils/buffer' -import { KeyType } from '../KeyType' - -/** - * This will be extracted to the x25519 package. - */ -@injectable() -export class X25519KeyProvider implements KeyProvider { - public readonly keyType = KeyType.X25519 - - /** - * Create a KeyPair with type X25519 - * - * @throws {KeyProviderError} When a key could not be created - */ - public async createKeyPair({ seed }: CreateKeyPairOptions): Promise { - const keyPair = seed ? ed25519.generateKeyPairFromSeed(new Buffer(seed)) : ed25519.generateKeyPair() - const privateKey = ed25519.convertSecretKeyToX25519(keyPair.secretKey) - const publicKey = ed25519.convertPublicKeyToX25519(keyPair.publicKey) - - return { - keyType: KeyType.X25519, - publicKeyBase58: TypedArrayEncoder.toBase58(publicKey), - privateKeyBase58: TypedArrayEncoder.toBase58(privateKey), - } - } - - public async sign(): Promise { - throw new Error("Method 'sign' not supported for X25519KeyProvider.") - } - - public async verify(): Promise { - throw new Error("Method 'verify' not supported for X25519KeyProvider.") - } -} diff --git a/packages/core/src/crypto/keyUtils.ts b/packages/core/src/crypto/keyUtils.ts new file mode 100644 index 0000000000..d772c63e92 --- /dev/null +++ b/packages/core/src/crypto/keyUtils.ts @@ -0,0 +1,33 @@ +import { Buffer } from '../utils' + +import { KeyType } from './KeyType' + +export function isValidSeed(seed: Buffer, keyType: KeyType): boolean { + const minimumSeedLength: Record = { + [KeyType.Ed25519]: 32, + [KeyType.X25519]: 32, + [KeyType.Bls12381g1]: 32, + [KeyType.Bls12381g2]: 32, + [KeyType.Bls12381g1g2]: 32, + [KeyType.P256]: 64, + [KeyType.P384]: 64, + [KeyType.P521]: 64, + } + + return Buffer.isBuffer(seed) && seed.length >= minimumSeedLength[keyType] +} + +export function isValidPrivateKey(privateKey: Buffer, keyType: KeyType): boolean { + const privateKeyLength: Record = { + [KeyType.Ed25519]: 32, + [KeyType.X25519]: 32, + [KeyType.Bls12381g1]: 32, + [KeyType.Bls12381g2]: 32, + [KeyType.Bls12381g1g2]: 32, + [KeyType.P256]: 32, + [KeyType.P384]: 48, + [KeyType.P521]: 66, + } + + return Buffer.isBuffer(privateKey) && privateKey.length === privateKeyLength[keyType] +} diff --git a/packages/core/src/crypto/multiCodecKey.ts b/packages/core/src/crypto/multiCodecKey.ts index 20d3f4b070..1ebcbd5ca9 100644 --- a/packages/core/src/crypto/multiCodecKey.ts +++ b/packages/core/src/crypto/multiCodecKey.ts @@ -7,6 +7,9 @@ const multiCodecPrefixMap: Record = { 236: KeyType.X25519, 237: KeyType.Ed25519, 238: KeyType.Bls12381g1g2, + 4608: KeyType.P256, + 4609: KeyType.P384, + 4610: KeyType.P521, } export function getKeyTypeByMultiCodecPrefix(multiCodecPrefix: number): KeyType { @@ -19,7 +22,7 @@ export function getKeyTypeByMultiCodecPrefix(multiCodecPrefix: number): KeyType return keyType } -export function getMultiCodecPrefixByKeytype(keyType: KeyType): number { +export function getMultiCodecPrefixByKeyType(keyType: KeyType): number { const codes = Object.keys(multiCodecPrefixMap) const code = codes.find((key) => multiCodecPrefixMap[key] === keyType) diff --git a/packages/core/src/decorators/service/ServiceDecoratorExtension.ts b/packages/core/src/decorators/service/ServiceDecoratorExtension.ts index 51bed49fd6..b05ba52a8c 100644 --- a/packages/core/src/decorators/service/ServiceDecoratorExtension.ts +++ b/packages/core/src/decorators/service/ServiceDecoratorExtension.ts @@ -1,5 +1,5 @@ -import type { DidComV1BaseMessageConstructor } from '../../didcomm/' import type { ServiceDecoratorOptions } from './ServiceDecorator' +import type { DidComV1BaseMessageConstructor } from '../../didcomm/' import { Expose, Type } from 'class-transformer' import { IsOptional, ValidateNested } from 'class-validator' diff --git a/packages/core/src/decorators/signature/SignatureDecoratorUtils.test.ts b/packages/core/src/decorators/signature/SignatureDecoratorUtils.test.ts index 7ebd323c0e..322c0014a0 100644 --- a/packages/core/src/decorators/signature/SignatureDecoratorUtils.test.ts +++ b/packages/core/src/decorators/signature/SignatureDecoratorUtils.test.ts @@ -1,7 +1,11 @@ +import type { Wallet } from '../../wallet' + +import { IndySdkWallet } from '../../../../indy-sdk/src' +import { indySdk } from '../../../../indy-sdk/tests/setupIndySdkModule' import { getAgentConfig } from '../../../tests/helpers' import { KeyType } from '../../crypto' import { KeyProviderRegistry } from '../../crypto/key-provider' -import { IndyWallet } from '../../wallet/IndyWallet' +import { TypedArrayEncoder } from '../../utils' import { SignatureDecorator } from './SignatureDecorator' import { signData, unpackAndVerifySignatureDecorator } from './SignatureDecoratorUtils' @@ -39,11 +43,11 @@ describe('Decorators | Signature | SignatureDecoratorUtils', () => { signer: 'GjZWsBLgZCR18aL468JAT7w9CZRiBnpxUPPgyQxh4voa', }) - let wallet: IndyWallet + let wallet: Wallet beforeAll(async () => { const config = getAgentConfig('SignatureDecoratorUtilsTest') - wallet = new IndyWallet(config.agentDependencies, config.logger, new KeyProviderRegistry([])) + wallet = new IndySdkWallet(indySdk, config.logger, new KeyProviderRegistry([])) // eslint-disable-next-line @typescript-eslint/no-non-null-assertion await wallet.createAndOpen(config.walletConfig!) }) @@ -53,8 +57,8 @@ describe('Decorators | Signature | SignatureDecoratorUtils', () => { }) test('signData signs json object and returns SignatureDecorator', async () => { - const seed1 = '00000000000000000000000000000My1' - const key = await wallet.createKey({ seed: seed1, keyType: KeyType.Ed25519 }) + const privateKey = TypedArrayEncoder.fromString('00000000000000000000000000000My1') + const key = await wallet.createKey({ privateKey, keyType: KeyType.Ed25519 }) const result = await signData(data, wallet, key.publicKeyBase58) diff --git a/packages/core/src/didcomm/JweEnvelope.ts b/packages/core/src/didcomm/JweEnvelope.ts new file mode 100644 index 0000000000..7201b53d54 --- /dev/null +++ b/packages/core/src/didcomm/JweEnvelope.ts @@ -0,0 +1,200 @@ +import type { DidCommV2EncryptionAlgs, DidCommV2KeyProtectionAlgs, DidCommV2Types } from './' + +import { Expose, Type } from 'class-transformer' + +import { Buffer, JsonEncoder, JsonTransformer, TypedArrayEncoder } from '../utils' + +export class JweRecipient { + @Expose({ name: 'encrypted_key' }) + public encryptedKey!: string + public header?: Record + + public constructor(options: { encryptedKey: Uint8Array; header?: Record }) { + if (options) { + this.encryptedKey = TypedArrayEncoder.toBase64URL(options.encryptedKey) + this.header = options.header + } + } +} + +export interface ProtectedOptions { + typ?: string + enc: string + alg: string + skid?: string + epk?: string + apu?: string + apv?: string +} + +export class Protected { + public typ?: string + public enc!: string + public alg!: string + public skid?: string + public epk?: string + public apu?: string + public apv?: string + + public constructor(options: ProtectedOptions) { + if (options) { + this.typ = options.typ + this.enc = options.enc + this.alg = options.alg + this.skid = options.skid + this.epk = options.epk + this.apu = options.apu + this.apv = options.apv + } + } + + public toJson() { + return JsonTransformer.toJSON(this) + } +} + +export interface JweEnvelopeOptions { + protected: string + unprotected?: string + recipients?: JweRecipient[] + ciphertext: string + iv: string + tag: string + aad?: string + header?: string[] + encryptedKey?: string +} + +export class JweEnvelope { + public protected!: string + public unprotected?: string + + @Type(() => JweRecipient) + public recipients?: JweRecipient[] + public ciphertext!: string + public iv!: string + public tag!: string + public aad?: string + public header?: string[] + + @Expose({ name: 'encrypted_key' }) + public encryptedKey?: string + + public constructor(options: JweEnvelopeOptions) { + if (options) { + this.protected = options.protected + this.unprotected = options.unprotected + this.recipients = options.recipients + this.ciphertext = options.ciphertext + this.iv = options.iv + this.tag = options.tag + this.aad = options.aad + this.header = options.header + this.encryptedKey = options.encryptedKey + } + } + + public toJson() { + return JsonTransformer.toJSON(this) + } +} + +export class JweEnvelopeBuilder { + public protected!: Protected + public unprotected?: string + + public ciphertext!: string + public iv!: string + public tag!: string + public header?: string[] + public encryptedKey?: string + public recipients!: JweRecipient[] + + public constructor({ + typ, + alg, + enc, + }: { + typ: DidCommV2Types + enc: DidCommV2EncryptionAlgs + alg: DidCommV2KeyProtectionAlgs + }) { + this.recipients = [] + this.protected = new Protected({ typ, alg, enc }) + } + + public setRecipient(recipient: JweRecipient): JweEnvelopeBuilder { + this.recipients.push(recipient) + return this + } + + public setCiphertext(ciphertext: Uint8Array | Buffer): JweEnvelopeBuilder { + this.ciphertext = TypedArrayEncoder.toBase64URL(ciphertext) + return this + } + + public setIv(iv: Uint8Array | Buffer): JweEnvelopeBuilder { + this.iv = TypedArrayEncoder.toBase64URL(iv) + return this + } + + public setTag(tag: Uint8Array | Buffer): JweEnvelopeBuilder { + this.tag = TypedArrayEncoder.toBase64URL(tag) + return this + } + + public setProtected(protected_: Protected): JweEnvelopeBuilder { + this.protected = protected_ + return this + } + + public setSkid(skid: string): JweEnvelopeBuilder { + this.protected.skid = skid + return this + } + + public setEpk(epk: string): JweEnvelopeBuilder { + this.protected.epk = epk + return this + } + + public setApu(apu: string): JweEnvelopeBuilder { + this.protected.apu = TypedArrayEncoder.toBase64URL(Buffer.from(apu)) + return this + } + + public setApv(apv: string[]): JweEnvelopeBuilder { + this.protected.apv = TypedArrayEncoder.toBase64URL(Buffer.from(apv.sort().join('.'))) + return this + } + + public apv(): Uint8Array { + return this.protected.apv ? Uint8Array.from(Buffer.from(this.protected.apv)) : Uint8Array.from([]) + } + + public apu(): Uint8Array { + return this.protected.apu ? Uint8Array.from(Buffer.from(this.protected.apu)) : Uint8Array.from([]) + } + + public alg(): Uint8Array { + return Uint8Array.from(Buffer.from(this.protected.alg)) + } + + public aad(): Buffer { + return Buffer.from(this.protected_()) + } + + private protected_(): string { + return JsonEncoder.toBase64URL(this.protected.toJson()) + } + + public finalize() { + return new JweEnvelope({ + ciphertext: this.ciphertext, + tag: this.tag, + iv: this.iv, + protected: this.protected_(), + recipients: this.recipients, + }) + } +} diff --git a/packages/core/src/didcomm/index.ts b/packages/core/src/didcomm/index.ts index d6a9daab29..e74379142a 100644 --- a/packages/core/src/didcomm/index.ts +++ b/packages/core/src/didcomm/index.ts @@ -1,11 +1,12 @@ -import type { ParsedMessageType } from '../utils/messageType' -import type { Constructor } from '../utils/mixins' import type { DidCommV1Message } from './versions/v1' import type { DidCommV2Message } from './versions/v2' +import type { ParsedMessageType } from '../utils/messageType' +import type { Constructor } from '../utils/mixins' export * from './versions/v1' export * from './versions/v2' export * from './types' export * from './helpers' +export * from './JweEnvelope' export type ConstructableDidCommMessage = Constructor & { type: ParsedMessageType } diff --git a/packages/core/src/didcomm/types.ts b/packages/core/src/didcomm/types.ts index 1d16214791..1b1a6c9100 100644 --- a/packages/core/src/didcomm/types.ts +++ b/packages/core/src/didcomm/types.ts @@ -1,6 +1,6 @@ -import type { Key } from '../crypto' import type { PlaintextDidCommV1Message } from './versions/v1' import type { PlaintextDidCommV2Message } from './versions/v2' +import type { Key } from '../crypto' export enum EnvelopeType { Plain = 'plain', @@ -12,6 +12,7 @@ export type PlaintextMessage = PlaintextDidCommV1Message | PlaintextDidCommV2Mes export type EncryptedMessageRecipientHeader = { kid: string + epk?: string } export type EncryptedMessageRecipient = { diff --git a/packages/core/src/didcomm/versions/v1/DidCommV1Message.ts b/packages/core/src/didcomm/versions/v1/DidCommV1Message.ts index aa8f2a7e51..b49470517c 100644 --- a/packages/core/src/didcomm/versions/v1/DidCommV1Message.ts +++ b/packages/core/src/didcomm/versions/v1/DidCommV1Message.ts @@ -1,6 +1,8 @@ import type { AgentMessage } from '../../../agent/AgentMessage' import type { ServiceDecorator } from '../../../decorators/service/ServiceDecorator' +import { Exclude } from 'class-transformer' + import { AckDecorated } from '../../../decorators/ack/AckDecoratorExtension' import { V1AttachmentDecorated } from '../../../decorators/attachment/V1AttachmentExtension' import { L10nDecorated } from '../../../decorators/l10n/L10nDecoratorExtension' @@ -21,6 +23,15 @@ const Decorated = ThreadDecorated( ) export class DidCommV1Message extends Decorated implements AgentMessage { + /** + * Whether the protocol RFC was initially written using the legacy did:prefix instead of the + * new https://didcomm.org message type prefix. + * + * @see https://github.com/hyperledger/aries-rfcs/blob/main/features/0348-transition-msg-type-to-https/README.md + */ + @Exclude() + public readonly allowDidSovPrefix: boolean = false + public get didCommVersion(): DidCommMessageVersion { return DidCommMessageVersion.V1 } @@ -29,10 +40,16 @@ export class DidCommV1Message extends Decorated implements AgentMessage { return this.service } - public toJSON({ useLegacyDidSovPrefix = false }: { useLegacyDidSovPrefix?: boolean } = {}): Record { + public toJSON({ useDidSovPrefixWhereAllowed = false }: { useDidSovPrefixWhereAllowed?: boolean } = {}): Record< + string, + unknown + > { const json = JsonTransformer.toJSON(this) - if (useLegacyDidSovPrefix) { + // If we have `useDidSovPrefixWhereAllowed` enabled, we want to replace the new https://didcomm.org prefix with the legacy did:sov prefix. + // However, we only do this if the protocol RFC was initially written with the did:sov message type prefix + // See https://github.com/hyperledger/aries-rfcs/blob/main/features/0348-transition-msg-type-to-https/README.md + if (this.allowDidSovPrefix && useDidSovPrefixWhereAllowed) { replaceNewDidCommPrefixWithLegacyDidSovOnMessage(json) } diff --git a/packages/core/src/didcomm/versions/v1/helpers.ts b/packages/core/src/didcomm/versions/v1/helpers.ts index 3335d4b74d..8750820154 100644 --- a/packages/core/src/didcomm/versions/v1/helpers.ts +++ b/packages/core/src/didcomm/versions/v1/helpers.ts @@ -1,7 +1,7 @@ -import type { AgentMessage } from '../../../agent/AgentMessage' -import type { EncryptedMessage, ProtectedMessage } from '../../types' import type { DidCommV1Message } from './DidCommV1Message' import type { PlaintextDidCommV1Message } from './types' +import type { AgentMessage } from '../../../agent/AgentMessage' +import type { EncryptedMessage, ProtectedMessage } from '../../types' import { AriesFrameworkError } from '../../../error' import { JsonEncoder } from '../../../utils' diff --git a/packages/core/src/didcomm/versions/v1/index.ts b/packages/core/src/didcomm/versions/v1/index.ts index e1283f963d..a26c45be9d 100644 --- a/packages/core/src/didcomm/versions/v1/index.ts +++ b/packages/core/src/didcomm/versions/v1/index.ts @@ -1,27 +1,13 @@ -import type { AgentContext } from '../../../agent/context' import type { Key } from '../../../crypto' -import type { DecryptedMessageContext, EncryptedMessage, SignedMessage, EnvelopeType } from '../../types' -import type { DidCommV1Message } from './DidCommV1Message' export { DidCommV1Message } from './DidCommV1Message' export { DidCommV1BaseMessage, DidComV1BaseMessageConstructor } from './DidCommV1BaseMessage' -export interface PackMessageParams { +export interface DidCommV1PackMessageParams { recipientKeys: Key[] routingKeys: Key[] senderKey: Key | null - envelopeType?: EnvelopeType } -export const DidCommV1EnvelopeServiceToken = Symbol('DidCommV1EnvelopeService') - -export interface DidCommV1EnvelopeService { - packMessage(agentContext: AgentContext, payload: DidCommV1Message, keys: PackMessageParams): Promise - - unpackMessage(agentContext: AgentContext, message: EncryptedMessage | SignedMessage): Promise -} -export { isPlaintextMessageV1 } from './helpers' -export { isDidCommV1Message } from './helpers' -export { DidCommV1Algorithms } from './types' -export { DidCommV1Types } from './types' -export { PlaintextDidCommV1Message } from './types' +export { isPlaintextMessageV1, isDidCommV1Message, isDidCommV1EncryptedEnvelope } from './helpers' +export { DidCommV1Algorithms, DidCommV1Types, PlaintextDidCommV1Message } from './types' diff --git a/packages/core/src/didcomm/versions/v1/indy/DefaultDidCommV1EnvelopeService.ts b/packages/core/src/didcomm/versions/v1/indy/DefaultDidCommV1EnvelopeService.ts deleted file mode 100644 index 9c4b4dd8b9..0000000000 --- a/packages/core/src/didcomm/versions/v1/indy/DefaultDidCommV1EnvelopeService.ts +++ /dev/null @@ -1,78 +0,0 @@ -import type { AgentContext } from '../../../../agent/context' -import type { DecryptedMessageContext, EncryptedMessage, SignedMessage } from '../../../types' -import type { DidCommV1Message } from '../DidCommV1Message' -import type { PackMessageParams } from '../index' - -import { InjectionSymbols } from '../../../../constants' -import { Key, KeyType } from '../../../../crypto' -import { AriesFrameworkError } from '../../../../error/AriesFrameworkError' -import { Logger } from '../../../../logger' -import { ForwardMessage } from '../../../../modules/routing/messages' -import { inject, injectable } from '../../../../plugins' -import { DidCommMessageVersion, EnvelopeType } from '../../../types' -import { DidCommV1EnvelopeService } from '../index' - -@injectable() -export class DefaultDidCommV1EnvelopeService implements DidCommV1EnvelopeService { - private logger: Logger - - public constructor(@inject(InjectionSymbols.Logger) logger: Logger) { - this.logger = logger - } - - public async packMessage( - agentContext: AgentContext, - payload: DidCommV1Message, - params: PackMessageParams - ): Promise { - if (params.envelopeType === EnvelopeType.Signed) { - throw new AriesFrameworkError('JWS messages are not supported by DIDComm V1 service') - } - - const { recipientKeys, routingKeys, senderKey } = params - let recipientKeysBase58 = recipientKeys.map((key) => key.publicKeyBase58) - const routingKeysBase58 = routingKeys.map((key) => key.publicKeyBase58) - const senderKeyBase58 = senderKey && senderKey.publicKeyBase58 - - // pass whether we want to use legacy did sov prefix - const message = payload.toJSON({ useLegacyDidSovPrefix: agentContext.config.useLegacyDidSovPrefix }) - - this.logger.debug(`Pack outbound message ${message['@type']}`) - - let encryptedMessage = await agentContext.wallet.pack(message, recipientKeysBase58, senderKeyBase58 ?? undefined) - - // If the message has routing keys (mediator) pack for each mediator - for (const routingKeyBase58 of routingKeysBase58) { - const forwardMessage = new ForwardMessage({ - // Forward to first recipient key - to: recipientKeysBase58[0], - message: encryptedMessage, - }) - recipientKeysBase58 = [routingKeyBase58] - this.logger.debug('Forward message created', forwardMessage) - - const forwardJson = forwardMessage.toJSON({ useLegacyDidSovPrefix: agentContext.config.useLegacyDidSovPrefix }) - - // Forward messages are anon packed - encryptedMessage = await agentContext.wallet.pack(forwardJson, [routingKeyBase58], undefined) - } - - return encryptedMessage - } - - public async unpackMessage( - agentContext: AgentContext, - encryptedMessage: EncryptedMessage | SignedMessage - ): Promise { - const decryptedMessage = await agentContext.wallet.unpack(encryptedMessage as EncryptedMessage) - const { recipientKey, senderKey, plaintextMessage } = decryptedMessage - return { - recipientKey: recipientKey ? Key.fromPublicKeyBase58(recipientKey, KeyType.Ed25519) : undefined, - senderKey: senderKey ? Key.fromPublicKeyBase58(senderKey, KeyType.Ed25519) : undefined, - plaintextMessage, - didCommVersion: DidCommMessageVersion.V1, - } - } -} - -export { DidCommV1EnvelopeService } diff --git a/packages/core/src/didcomm/versions/v2/DidCommV2Message.ts b/packages/core/src/didcomm/versions/v2/DidCommV2Message.ts index aed80cd0d7..e0a8d81ef9 100644 --- a/packages/core/src/didcomm/versions/v2/DidCommV2Message.ts +++ b/packages/core/src/didcomm/versions/v2/DidCommV2Message.ts @@ -1,4 +1,5 @@ import type { AgentMessage } from '../../../agent/AgentMessage' +import type { ServiceDecorator } from '../../../decorators/service/ServiceDecorator' import { AriesFrameworkError } from '../../../error' import { JsonTransformer } from '../../../utils/JsonTransformer' @@ -7,12 +8,16 @@ import { DidCommMessageVersion } from '../../types' import { DidCommV2BaseMessage } from './DidCommV2BaseMessage' export class DidCommV2Message extends DidCommV2BaseMessage implements AgentMessage { + public get didCommVersion(): DidCommMessageVersion { + return DidCommMessageVersion.V2 + } + public toJSON(): Record { return JsonTransformer.toJSON(this) } - public get didCommVersion(): DidCommMessageVersion { - return DidCommMessageVersion.V2 + public serviceDecorator(): ServiceDecorator | undefined { + return undefined } public get threadId(): string | undefined { @@ -35,10 +40,6 @@ export class DidCommV2Message extends DidCommV2BaseMessage implements AgentMessa return this.type === Class.type.messageTypeUri } - public setRecipient(to?: string) { - this.to = to ? [to] : undefined - } - public get firstRecipient(): string | undefined { return this.to?.length ? this.to[0] : undefined } diff --git a/packages/core/src/didcomm/versions/v2/helpers.ts b/packages/core/src/didcomm/versions/v2/helpers.ts index 79294a87dd..1ae3c3282e 100644 --- a/packages/core/src/didcomm/versions/v2/helpers.ts +++ b/packages/core/src/didcomm/versions/v2/helpers.ts @@ -1,6 +1,6 @@ -import type { AgentMessage } from '../../../agent/AgentMessage' import type { DidCommV2Message } from './DidCommV2Message' import type { PlaintextDidCommV2Message } from './types' +import type { AgentMessage } from '../../../agent/AgentMessage' import { DidCommMessageVersion } from '../../types' diff --git a/packages/core/src/didcomm/versions/v2/index.ts b/packages/core/src/didcomm/versions/v2/index.ts index eb4314eebc..51582b019c 100644 --- a/packages/core/src/didcomm/versions/v2/index.ts +++ b/packages/core/src/didcomm/versions/v2/index.ts @@ -1,31 +1,13 @@ -import type { AgentContext } from '../../../agent/context/AgentContext' -import type { DecryptedMessageContext, EncryptedMessage, SignedMessage, EnvelopeType } from '../../types' -import type { DidCommV2Message } from './DidCommV2Message' +import type { DidDocument, DidCommV2Service } from '../../../modules/dids' export { DidCommV2Message } from './DidCommV2Message' export { DidCommV2BaseMessage, DidComV2BaseMessageConstructor, DidCommV2MessageParams } from './DidCommV2BaseMessage' -export interface V2PackMessageParams { - toDid?: string - fromDid?: string - signByDid?: string - serviceId?: string - wrapIntoForward?: boolean - envelopeType?: EnvelopeType +export interface DidCommV2PackMessageParams { + recipientDidDoc: DidDocument + senderDidDoc?: DidDocument + service: DidCommV2Service } -export const DidCommV2EnvelopeServiceToken = Symbol('DidCommV2EnvelopeService') -export const DefaultDidCommV2EnvelopeService = 'default' - -export interface DidCommV2EnvelopeService { - packMessage( - agentContext: AgentContext, - payload: DidCommV2Message, - params: V2PackMessageParams - ): Promise - - unpackMessage(agentContext: AgentContext, message: EncryptedMessage | SignedMessage): Promise -} -export { isPlaintextMessageV2 } from './helpers' -export { isDidCommV2Message } from './helpers' -export { PlaintextDidCommV2Message } from './types' +export { isPlaintextMessageV2, isDidCommV2Message } from './helpers' +export { PlaintextDidCommV2Message, DidCommV2Types, DidCommV2EncryptionAlgs, DidCommV2KeyProtectionAlgs } from './types' diff --git a/packages/core/src/didcomm/versions/v2/types.ts b/packages/core/src/didcomm/versions/v2/types.ts index 897457b36e..abe8c9ffee 100644 --- a/packages/core/src/didcomm/versions/v2/types.ts +++ b/packages/core/src/didcomm/versions/v2/types.ts @@ -6,3 +6,19 @@ export interface PlaintextDidCommV2Message { [key: string]: unknown } + +export enum DidCommV2Types { + EncryptedJson = 'application/didcomm-encrypted+json', +} + +export enum DidCommV2EncryptionAlgs { + XC20P = 'XC20P', + A256CbcHs512 = 'A256CBC-HS512', +} + +export enum DidCommV2KeyProtectionAlgs { + EcdhEsA128Kw = 'ECDH-ES+A128KW', + EcdhEsA256Kw = 'ECDH-ES+A256KW', + Ecdh1PuA128Kw = 'ECDH-1PU+A128KW', + Ecdh1PuA256Kw = 'ECDH-1PU+A256KW', +} diff --git a/packages/core/src/error/index.ts b/packages/core/src/error/index.ts index 7122734300..45ebd04bd7 100644 --- a/packages/core/src/error/index.ts +++ b/packages/core/src/error/index.ts @@ -1,6 +1,5 @@ export * from './AriesFrameworkError' export * from './RecordNotFoundError' export * from './RecordDuplicateError' -export * from './IndySdkError' export * from './ClassValidationError' export * from './MessageSendingError' diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 9a2c148b3d..1562100f31 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -16,20 +16,31 @@ export { AgentMessage } from './agent/AgentMessage' export { Dispatcher } from './agent/Dispatcher' export { MessageSender } from './agent/MessageSender' export type { AgentDependencies } from './agent/AgentDependencies' -export type { InitConfig, WalletConfig, JsonArray, JsonObject, JsonValue } from './types' +export type { + InitConfig, + WalletConfig, + JsonArray, + JsonObject, + JsonValue, + WalletConfigRekey, + WalletExportImportConfig, + WalletStorageConfig, +} from './types' export { DidCommMimeType, KeyDerivationMethod } from './types' -export type { FileSystem } from './storage/FileSystem' +export type { FileSystem, DownloadToFileOptions } from './storage/FileSystem' export * from './storage/BaseRecord' +export { DidCommMessageRecord, DidCommMessageRole, DidCommMessageRepository } from './storage/didcomm' export { InMemoryMessageRepository } from './storage/InMemoryMessageRepository' export { Repository } from './storage/Repository' export * from './storage/RepositoryEvents' -export { StorageService, Query } from './storage/StorageService' +export { StorageService, Query, SimpleQuery, BaseRecordConstructor } from './storage/StorageService' export * from './storage/migration' export { getDirFromFilePath } from './utils/path' export { InjectionSymbols } from './constants' export * from './wallet' export type { TransportSession } from './agent/TransportService' export { TransportService } from './agent/TransportService' +export { ServiceDecorator, ServiceDecoratorOptions } from './decorators/service/ServiceDecorator' export { V1Attachment, V1AttachmentData } from './decorators/attachment/V1Attachment' export { ReturnRouteTypes } from './decorators/transport/TransportDecorator' @@ -42,26 +53,32 @@ export * from './modules/discover-features' export * from './modules/problem-reports' export * from './modules/proofs' export * from './modules/connections' -export * from './modules/ledger' export * from './modules/routing' export * from './modules/oob' export * from './modules/dids' export * from './modules/vc' +export * from './modules/cache' export { JsonEncoder, JsonTransformer, isJsonObject, isValidJweStructure, TypedArrayEncoder, Buffer } from './utils' export * from './logger' export * from './error' export * from './wallet/error' -export { parseMessageType, IsValidMessageType } from './utils/messageType' +export { parseMessageType, IsValidMessageType, replaceLegacyDidSovPrefix } from './utils/messageType' export type { Constructor } from './utils/mixins' export * from './agent/Events' export * from './crypto/' export * from './didcomm/' +// TODO: clean up util exports +export { encodeAttachment, isLinkedAttachment } from './utils/attachment' +export { Hasher } from './utils/Hasher' +export { MessageValidator } from './utils/MessageValidator' +export { LinkedAttachment, LinkedAttachmentOptions } from './utils/LinkedAttachment' import { parseInvitationUrl } from './utils/parseInvitation' -import { uuid } from './utils/uuid' +import { uuid, isValidUuid } from './utils/uuid' const utils = { uuid, + isValidUuid, parseInvitationUrl, } diff --git a/packages/core/src/modules/basic-messages/BasicMessageEvents.ts b/packages/core/src/modules/basic-messages/BasicMessageEvents.ts index 7c25f5b9c4..f05873f5de 100644 --- a/packages/core/src/modules/basic-messages/BasicMessageEvents.ts +++ b/packages/core/src/modules/basic-messages/BasicMessageEvents.ts @@ -1,6 +1,6 @@ -import type { BaseEvent } from '../../agent/Events' import type { BasicMessage } from './messages' import type { BasicMessageRecord } from './repository' +import type { BaseEvent } from '../../agent/Events' export enum BasicMessageEventTypes { BasicMessageStateChanged = 'BasicMessageStateChanged', diff --git a/packages/core/src/modules/basic-messages/BasicMessagesApi.ts b/packages/core/src/modules/basic-messages/BasicMessagesApi.ts index 816340429d..82e94cf9e4 100644 --- a/packages/core/src/modules/basic-messages/BasicMessagesApi.ts +++ b/packages/core/src/modules/basic-messages/BasicMessagesApi.ts @@ -1,8 +1,8 @@ -import type { Query } from '../../storage/StorageService' import type { BasicMessageRecord } from './repository/BasicMessageRecord' +import type { Query } from '../../storage/StorageService' import { AgentContext } from '../../agent' -import { Dispatcher } from '../../agent/Dispatcher' +import { MessageHandlerRegistry } from '../../agent/MessageHandlerRegistry' import { MessageSender } from '../../agent/MessageSender' import { OutboundMessageContext } from '../../agent/models' import { injectable } from '../../plugins' @@ -19,7 +19,7 @@ export class BasicMessagesApi { private agentContext: AgentContext public constructor( - dispatcher: Dispatcher, + messageHandlerRegistry: MessageHandlerRegistry, basicMessageService: BasicMessageService, messageSender: MessageSender, connectionService: ConnectionService, @@ -29,7 +29,7 @@ export class BasicMessagesApi { this.messageSender = messageSender this.connectionService = connectionService this.agentContext = agentContext - this.registerMessageHandlers(dispatcher) + this.registerMessageHandlers(messageHandlerRegistry) } /** @@ -41,13 +41,14 @@ export class BasicMessagesApi { * @throws {MessageSendingError} If message is undeliverable * @returns the created record */ - public async sendMessage(connectionId: string, message: string) { + public async sendMessage(connectionId: string, message: string, parentThreadId?: string) { const connection = await this.connectionService.getById(this.agentContext, connectionId) const { message: basicMessage, record: basicMessageRecord } = await this.basicMessageService.createMessage( this.agentContext, message, - connection + connection, + parentThreadId ) const outboundMessageContext = new OutboundMessageContext(basicMessage, { agentContext: this.agentContext, @@ -81,6 +82,18 @@ export class BasicMessagesApi { return this.basicMessageService.getById(this.agentContext, basicMessageRecordId) } + /** + * Retrieve a basic message record by thread id + * + * @param threadId The thread id + * @throws {RecordNotFoundError} If no record is found + * @throws {RecordDuplicateError} If multiple records are found + * @returns The connection record + */ + public async getByThreadId(basicMessageRecordId: string) { + return this.basicMessageService.getByThreadId(this.agentContext, basicMessageRecordId) + } + /** * Delete a basic message record by id * @@ -91,7 +104,7 @@ export class BasicMessagesApi { await this.basicMessageService.deleteById(this.agentContext, basicMessageRecordId) } - private registerMessageHandlers(dispatcher: Dispatcher) { - dispatcher.registerMessageHandler(new BasicMessageHandler(this.basicMessageService)) + private registerMessageHandlers(messageHandlerRegistry: MessageHandlerRegistry) { + messageHandlerRegistry.registerMessageHandler(new BasicMessageHandler(this.basicMessageService)) } } diff --git a/packages/core/src/modules/basic-messages/__tests__/basic-messages.e2e.test.ts b/packages/core/src/modules/basic-messages/__tests__/basic-messages.e2e.test.ts index 11271e1d15..a18aa98754 100644 --- a/packages/core/src/modules/basic-messages/__tests__/basic-messages.e2e.test.ts +++ b/packages/core/src/modules/basic-messages/__tests__/basic-messages.e2e.test.ts @@ -6,6 +6,7 @@ import { Subject } from 'rxjs' import { SubjectInboundTransport } from '../../../../../../tests/transport/SubjectInboundTransport' import { SubjectOutboundTransport } from '../../../../../../tests/transport/SubjectOutboundTransport' +import { getIndySdkModules } from '../../../../../indy-sdk/tests/setupIndySdkModule' import { getAgentOptions, makeConnection, waitForBasicMessage } from '../../../../tests/helpers' import testLogger from '../../../../tests/logger' import { Agent } from '../../../agent/Agent' @@ -13,13 +14,21 @@ import { MessageSendingError, RecordNotFoundError } from '../../../error' import { BasicMessage } from '../messages' import { BasicMessageRecord } from '../repository' -const faberConfig = getAgentOptions('Faber Basic Messages', { - endpoints: ['rxjs:faber'], -}) - -const aliceConfig = getAgentOptions('Alice Basic Messages', { - endpoints: ['rxjs:alice'], -}) +const faberConfig = getAgentOptions( + 'Faber Basic Messages', + { + endpoints: ['rxjs:faber'], + }, + getIndySdkModules() +) + +const aliceConfig = getAgentOptions( + 'Alice Basic Messages', + { + endpoints: ['rxjs:alice'], + }, + getIndySdkModules() +) describe('Basic Messages E2E', () => { let faberAgent: Agent @@ -75,6 +84,55 @@ describe('Basic Messages E2E', () => { }) }) + test('Alice and Faber exchange messages using threadId', async () => { + testLogger.test('Alice sends message to Faber') + const helloRecord = await aliceAgent.basicMessages.sendMessage(aliceConnection.id, 'Hello') + + expect(helloRecord.content).toBe('Hello') + + testLogger.test('Faber waits for message from Alice') + const helloMessage = await waitForBasicMessage(faberAgent, { + content: 'Hello', + }) + + testLogger.test('Faber sends message to Alice') + const replyRecord = await faberAgent.basicMessages.sendMessage(faberConnection.id, 'How are you?', helloMessage.id) + expect(replyRecord.content).toBe('How are you?') + expect(replyRecord.parentThreadId).toBe(helloMessage.id) + + testLogger.test('Alice waits until she receives message from faber') + const replyMessage = await waitForBasicMessage(aliceAgent, { + content: 'How are you?', + }) + expect(replyMessage.content).toBe('How are you?') + expect(replyMessage.thread?.parentThreadId).toBe(helloMessage.id) + + // Both sender and recipient shall be able to find the threaded messages + // Hello message + const aliceHelloMessage = await aliceAgent.basicMessages.getByThreadId(helloMessage.id) + const faberHelloMessage = await faberAgent.basicMessages.getByThreadId(helloMessage.id) + expect(aliceHelloMessage).toMatchObject({ + content: helloRecord.content, + threadId: helloRecord.threadId, + }) + expect(faberHelloMessage).toMatchObject({ + content: helloRecord.content, + threadId: helloRecord.threadId, + }) + + // Reply message + const aliceReplyMessages = await aliceAgent.basicMessages.findAllByQuery({ parentThreadId: helloMessage.id }) + const faberReplyMessages = await faberAgent.basicMessages.findAllByQuery({ parentThreadId: helloMessage.id }) + expect(aliceReplyMessages.length).toBe(1) + expect(aliceReplyMessages[0]).toMatchObject({ + content: replyRecord.content, + parentThreadId: replyRecord.parentThreadId, + threadId: replyRecord.threadId, + }) + expect(faberReplyMessages.length).toBe(1) + expect(faberReplyMessages[0]).toMatchObject(replyRecord) + }) + test('Alice is unable to send a message', async () => { testLogger.test('Alice sends message to Faber that is undeliverable') diff --git a/packages/core/src/modules/basic-messages/messages/BasicMessage.ts b/packages/core/src/modules/basic-messages/messages/BasicMessage.ts index 42f27e55f5..1b720dceec 100644 --- a/packages/core/src/modules/basic-messages/messages/BasicMessage.ts +++ b/packages/core/src/modules/basic-messages/messages/BasicMessage.ts @@ -6,6 +6,8 @@ import { IsValidMessageType, parseMessageType } from '../../../utils/messageType import { DateParser } from '../../../utils/transformers' export class BasicMessage extends DidCommV1Message { + public readonly allowDidSovPrefix = true + /** * Create new BasicMessage instance. * sentTime will be assigned to new Date if not passed, id will be assigned to uuid/v4 if not passed diff --git a/packages/core/src/modules/basic-messages/repository/BasicMessageRecord.ts b/packages/core/src/modules/basic-messages/repository/BasicMessageRecord.ts index 4a0de5decd..42199106c6 100644 --- a/packages/core/src/modules/basic-messages/repository/BasicMessageRecord.ts +++ b/packages/core/src/modules/basic-messages/repository/BasicMessageRecord.ts @@ -8,6 +8,8 @@ export type CustomBasicMessageTags = TagsBase export type DefaultBasicMessageTags = { connectionId: string role: BasicMessageRole + threadId?: string + parentThreadId?: string } export type BasicMessageTags = RecordTags @@ -18,7 +20,8 @@ export interface BasicMessageStorageProps { connectionId: string role: BasicMessageRole tags?: CustomBasicMessageTags - + threadId?: string + parentThreadId?: string content: string sentTime: string } @@ -28,6 +31,8 @@ export class BasicMessageRecord extends BaseRecord(agentContext: AgentContext, key: string): Promise + set(agentContext: AgentContext, key: string, value: CacheValue, expiresInSeconds?: number): Promise + remove(agentContext: AgentContext, key: string): Promise +} diff --git a/packages/core/src/modules/cache/CacheModule.ts b/packages/core/src/modules/cache/CacheModule.ts new file mode 100644 index 0000000000..c4d2ba0e5c --- /dev/null +++ b/packages/core/src/modules/cache/CacheModule.ts @@ -0,0 +1,34 @@ +import type { CacheModuleConfigOptions } from './CacheModuleConfig' +import type { DependencyManager, Module } from '../../plugins' +import type { Optional } from '../../utils' + +import { CacheModuleConfig } from './CacheModuleConfig' +import { SingleContextLruCacheRepository } from './singleContextLruCache/SingleContextLruCacheRepository' +import { SingleContextStorageLruCache } from './singleContextLruCache/SingleContextStorageLruCache' + +// CacheModuleOptions makes the credentialProtocols property optional from the config, as it will set it when not provided. +export type CacheModuleOptions = Optional + +export class CacheModule implements Module { + public readonly config: CacheModuleConfig + + public constructor(config?: CacheModuleOptions) { + this.config = new CacheModuleConfig({ + ...config, + cache: + config?.cache ?? + new SingleContextStorageLruCache({ + limit: 500, + }), + }) + } + + public register(dependencyManager: DependencyManager) { + dependencyManager.registerInstance(CacheModuleConfig, this.config) + + // Custom handling for when we're using the SingleContextStorageLruCache + if (this.config.cache instanceof SingleContextStorageLruCache) { + dependencyManager.registerSingleton(SingleContextLruCacheRepository) + } + } +} diff --git a/packages/core/src/modules/cache/CacheModuleConfig.ts b/packages/core/src/modules/cache/CacheModuleConfig.ts new file mode 100644 index 0000000000..a04b143dc8 --- /dev/null +++ b/packages/core/src/modules/cache/CacheModuleConfig.ts @@ -0,0 +1,29 @@ +import type { Cache } from './Cache' + +/** + * CacheModuleConfigOptions defines the interface for the options of the CacheModuleConfig class. + */ +export interface CacheModuleConfigOptions { + /** + * Implementation of the {@link Cache} interface. + * + * NOTE: Starting from AFJ 0.4.0 the default cache implementation will be {@link InMemoryLruCache} + * @default SingleContextStorageLruCache - with a limit of 500 + * + * + */ + cache: Cache +} + +export class CacheModuleConfig { + private options: CacheModuleConfigOptions + + public constructor(options: CacheModuleConfigOptions) { + this.options = options + } + + /** See {@link CacheModuleConfigOptions.cache} */ + public get cache() { + return this.options.cache + } +} diff --git a/packages/core/src/modules/cache/InMemoryLruCache.ts b/packages/core/src/modules/cache/InMemoryLruCache.ts new file mode 100644 index 0000000000..4f6ba0733e --- /dev/null +++ b/packages/core/src/modules/cache/InMemoryLruCache.ts @@ -0,0 +1,75 @@ +import type { Cache } from './Cache' +import type { AgentContext } from '../../agent/context' + +import { LRUMap } from 'lru_map' + +export interface InMemoryLruCacheOptions { + /** The maximum number of entries allowed in the cache */ + limit: number +} + +/** + * In memory LRU cache. + * + * This cache can be used with multiple agent context instances, however all instances will share the same cache. + * If you need the cache to be isolated per agent context instance, make sure to use a different cache implementation. + */ +export class InMemoryLruCache implements Cache { + private readonly cache: LRUMap + + public constructor({ limit }: InMemoryLruCacheOptions) { + this.cache = new LRUMap(limit) + } + + public async get(agentContext: AgentContext, key: string) { + this.removeExpiredItems() + const item = this.cache.get(key) + + // Does not exist + if (!item) return null + + return item.value as CacheValue + } + + public async set( + agentContext: AgentContext, + key: string, + value: CacheValue, + expiresInSeconds?: number + ): Promise { + this.removeExpiredItems() + let expiresDate = undefined + + if (expiresInSeconds) { + expiresDate = new Date() + expiresDate.setSeconds(expiresDate.getSeconds() + expiresInSeconds) + } + + this.cache.set(key, { + expiresAt: expiresDate?.getTime(), + value, + }) + } + + public clear() { + this.cache.clear() + } + + public async remove(agentContext: AgentContext, key: string): Promise { + this.removeExpiredItems() + this.cache.delete(key) + } + + private removeExpiredItems() { + this.cache.forEach((value, key) => { + if (value.expiresAt && Date.now() > value.expiresAt) { + this.cache.delete(key) + } + }) + } +} + +interface CacheItem { + expiresAt?: number + value: unknown +} diff --git a/packages/core/src/modules/cache/__tests__/CacheModule.test.ts b/packages/core/src/modules/cache/__tests__/CacheModule.test.ts new file mode 100644 index 0000000000..fe38e2e139 --- /dev/null +++ b/packages/core/src/modules/cache/__tests__/CacheModule.test.ts @@ -0,0 +1,42 @@ +import { DependencyManager } from '../../../plugins/DependencyManager' +import { CacheModule } from '../CacheModule' +import { CacheModuleConfig } from '../CacheModuleConfig' +import { InMemoryLruCache } from '../InMemoryLruCache' +import { SingleContextStorageLruCache } from '../singleContextLruCache' +import { SingleContextLruCacheRepository } from '../singleContextLruCache/SingleContextLruCacheRepository' + +jest.mock('../../../plugins/DependencyManager') +const DependencyManagerMock = DependencyManager as jest.Mock + +const dependencyManager = new DependencyManagerMock() + +describe('CacheModule', () => { + afterEach(() => { + jest.clearAllMocks() + }) + + test('registers dependencies on the dependency manager', () => { + const cacheModule = new CacheModule({ + cache: new InMemoryLruCache({ limit: 1 }), + }) + cacheModule.register(dependencyManager) + + expect(dependencyManager.registerInstance).toHaveBeenCalledTimes(1) + expect(dependencyManager.registerInstance).toHaveBeenCalledWith(CacheModuleConfig, cacheModule.config) + + expect(dependencyManager.registerSingleton).toHaveBeenCalledTimes(0) + }) + + test('registers cache repository on the dependency manager if the SingleContextStorageLruCache is used', () => { + const cacheModule = new CacheModule({ + cache: new SingleContextStorageLruCache({ limit: 1 }), + }) + cacheModule.register(dependencyManager) + + expect(dependencyManager.registerInstance).toHaveBeenCalledTimes(1) + expect(dependencyManager.registerInstance).toHaveBeenCalledWith(CacheModuleConfig, cacheModule.config) + + expect(dependencyManager.registerSingleton).toHaveBeenCalledTimes(1) + expect(dependencyManager.registerSingleton).toHaveBeenCalledWith(SingleContextLruCacheRepository) + }) +}) diff --git a/packages/core/src/modules/cache/__tests__/CacheModuleConfig.test.ts b/packages/core/src/modules/cache/__tests__/CacheModuleConfig.test.ts new file mode 100644 index 0000000000..9cbc267122 --- /dev/null +++ b/packages/core/src/modules/cache/__tests__/CacheModuleConfig.test.ts @@ -0,0 +1,14 @@ +import { CacheModuleConfig } from '../CacheModuleConfig' +import { InMemoryLruCache } from '../InMemoryLruCache' + +describe('CacheModuleConfig', () => { + test('sets values', () => { + const cache = new InMemoryLruCache({ limit: 1 }) + + const config = new CacheModuleConfig({ + cache, + }) + + expect(config.cache).toEqual(cache) + }) +}) diff --git a/packages/core/src/modules/cache/__tests__/InMemoryLruCache.test.ts b/packages/core/src/modules/cache/__tests__/InMemoryLruCache.test.ts new file mode 100644 index 0000000000..aa802575c4 --- /dev/null +++ b/packages/core/src/modules/cache/__tests__/InMemoryLruCache.test.ts @@ -0,0 +1,43 @@ +import { getAgentContext } from '../../../../tests/helpers' +import { InMemoryLruCache } from '../InMemoryLruCache' + +const agentContext = getAgentContext() + +describe('InMemoryLruCache', () => { + let cache: InMemoryLruCache + + beforeEach(() => { + cache = new InMemoryLruCache({ limit: 2 }) + }) + + it('should set, get and remove a value', async () => { + expect(await cache.get(agentContext, 'item')).toBeNull() + + await cache.set(agentContext, 'item', 'somevalue') + expect(await cache.get(agentContext, 'item')).toBe('somevalue') + + await cache.remove(agentContext, 'item') + expect(await cache.get(agentContext, 'item')).toBeNull() + }) + + it('should remove least recently used entries if entries are added that exceed the limit', async () => { + // Set first value in cache, resolves fine + await cache.set(agentContext, 'one', 'valueone') + expect(await cache.get(agentContext, 'one')).toBe('valueone') + + // Set two more entries in the cache. Third item + // exceeds limit, so first item gets removed + await cache.set(agentContext, 'two', 'valuetwo') + await cache.set(agentContext, 'three', 'valuethree') + expect(await cache.get(agentContext, 'one')).toBeNull() + expect(await cache.get(agentContext, 'two')).toBe('valuetwo') + expect(await cache.get(agentContext, 'three')).toBe('valuethree') + + // Get two from the cache, meaning three will be removed first now + // because it is not recently used + await cache.get(agentContext, 'two') + await cache.set(agentContext, 'four', 'valuefour') + expect(await cache.get(agentContext, 'three')).toBeNull() + expect(await cache.get(agentContext, 'two')).toBe('valuetwo') + }) +}) diff --git a/packages/core/src/modules/cache/index.ts b/packages/core/src/modules/cache/index.ts new file mode 100644 index 0000000000..5b5d932671 --- /dev/null +++ b/packages/core/src/modules/cache/index.ts @@ -0,0 +1,10 @@ +// Module +export { CacheModule, CacheModuleOptions } from './CacheModule' +export { CacheModuleConfig } from './CacheModuleConfig' + +// Cache +export { Cache } from './Cache' + +// Cache Implementations +export { InMemoryLruCache, InMemoryLruCacheOptions } from './InMemoryLruCache' +export { SingleContextStorageLruCache, SingleContextStorageLruCacheOptions } from './singleContextLruCache' diff --git a/packages/core/src/modules/cache/singleContextLruCache/SingleContextLruCacheRecord.ts b/packages/core/src/modules/cache/singleContextLruCache/SingleContextLruCacheRecord.ts new file mode 100644 index 0000000000..257b6b6080 --- /dev/null +++ b/packages/core/src/modules/cache/singleContextLruCache/SingleContextLruCacheRecord.ts @@ -0,0 +1,44 @@ +import type { TagsBase } from '../../../storage/BaseRecord' + +import { Type } from 'class-transformer' + +import { BaseRecord } from '../../../storage/BaseRecord' +import { uuid } from '../../../utils/uuid' + +export interface SingleContextLruCacheItem { + value: unknown + expiresAt?: number +} + +export interface SingleContextLruCacheProps { + id?: string + createdAt?: Date + tags?: TagsBase + + entries: Map +} + +export class SingleContextLruCacheRecord extends BaseRecord { + @Type(() => Object) + public entries!: Map + + public static readonly type = 'SingleContextLruCacheRecord' + public readonly type = SingleContextLruCacheRecord.type + + public constructor(props: SingleContextLruCacheProps) { + super() + + if (props) { + this.id = props.id ?? uuid() + this.createdAt = props.createdAt ?? new Date() + this.entries = props.entries + this._tags = props.tags ?? {} + } + } + + public getTags() { + return { + ...this._tags, + } + } +} diff --git a/packages/core/src/modules/cache/singleContextLruCache/SingleContextLruCacheRepository.ts b/packages/core/src/modules/cache/singleContextLruCache/SingleContextLruCacheRepository.ts new file mode 100644 index 0000000000..dab71b9761 --- /dev/null +++ b/packages/core/src/modules/cache/singleContextLruCache/SingleContextLruCacheRepository.ts @@ -0,0 +1,17 @@ +import { EventEmitter } from '../../../agent/EventEmitter' +import { InjectionSymbols } from '../../../constants' +import { inject, injectable } from '../../../plugins' +import { Repository } from '../../../storage/Repository' +import { StorageService } from '../../../storage/StorageService' + +import { SingleContextLruCacheRecord } from './SingleContextLruCacheRecord' + +@injectable() +export class SingleContextLruCacheRepository extends Repository { + public constructor( + @inject(InjectionSymbols.StorageService) storageService: StorageService, + eventEmitter: EventEmitter + ) { + super(SingleContextLruCacheRecord, storageService, eventEmitter) + } +} diff --git a/packages/core/src/modules/cache/singleContextLruCache/SingleContextStorageLruCache.ts b/packages/core/src/modules/cache/singleContextLruCache/SingleContextStorageLruCache.ts new file mode 100644 index 0000000000..72498db91a --- /dev/null +++ b/packages/core/src/modules/cache/singleContextLruCache/SingleContextStorageLruCache.ts @@ -0,0 +1,158 @@ +import type { SingleContextLruCacheItem } from './SingleContextLruCacheRecord' +import type { AgentContext } from '../../../agent/context' +import type { Cache } from '../Cache' + +import { LRUMap } from 'lru_map' + +import { AriesFrameworkError } from '../../../error' + +import { SingleContextLruCacheRecord } from './SingleContextLruCacheRecord' +import { SingleContextLruCacheRepository } from './SingleContextLruCacheRepository' + +const CONTEXT_STORAGE_LRU_CACHE_ID = 'CONTEXT_STORAGE_LRU_CACHE_ID' + +export interface SingleContextStorageLruCacheOptions { + /** The maximum number of entries allowed in the cache */ + limit: number +} + +/** + * Cache that leverages the storage associated with the agent context to store cache records. + * It will keep an in-memory cache of the records to avoid hitting the storage on every read request. + * Therefor this cache is meant to be used with a single instance of the agent. + * + * Due to keeping an in-memory copy of the cache, it is also not meant to be used with multiple + * agent context instances (meaning multi-tenancy), as they will overwrite the in-memory cache. + * + * However, this means the cache is not meant for usage with multiple instances. + */ +export class SingleContextStorageLruCache implements Cache { + private limit: number + private _cache?: LRUMap + private _contextCorrelationId?: string + + public constructor({ limit }: SingleContextStorageLruCacheOptions) { + this.limit = limit + } + + public async get(agentContext: AgentContext, key: string) { + this.assertContextCorrelationId(agentContext) + + const cache = await this.getCache(agentContext) + this.removeExpiredItems(cache) + + const item = cache.get(key) + + // Does not exist + if (!item) return null + + // Expired + if (item.expiresAt && Date.now() > item.expiresAt) { + cache.delete(key) + await this.persistCache(agentContext) + return null + } + + return item.value as CacheValue + } + + public async set( + agentContext: AgentContext, + key: string, + value: CacheValue, + expiresInSeconds?: number + ): Promise { + this.assertContextCorrelationId(agentContext) + + let expiresDate = undefined + + if (expiresInSeconds) { + expiresDate = new Date() + expiresDate.setSeconds(expiresDate.getSeconds() + expiresInSeconds) + } + + const cache = await this.getCache(agentContext) + this.removeExpiredItems(cache) + + cache.set(key, { + expiresAt: expiresDate?.getTime(), + value, + }) + await this.persistCache(agentContext) + } + + public async remove(agentContext: AgentContext, key: string): Promise { + this.assertContextCorrelationId(agentContext) + + const cache = await this.getCache(agentContext) + this.removeExpiredItems(cache) + cache.delete(key) + + await this.persistCache(agentContext) + } + + private async getCache(agentContext: AgentContext) { + if (!this._cache) { + const cacheRecord = await this.fetchCacheRecord(agentContext) + this._cache = this.lruFromRecord(cacheRecord) + } + + return this._cache + } + + private lruFromRecord(cacheRecord: SingleContextLruCacheRecord) { + return new LRUMap(this.limit, cacheRecord.entries.entries()) + } + + private async fetchCacheRecord(agentContext: AgentContext) { + const cacheRepository = agentContext.dependencyManager.resolve(SingleContextLruCacheRepository) + let cacheRecord = await cacheRepository.findById(agentContext, CONTEXT_STORAGE_LRU_CACHE_ID) + + if (!cacheRecord) { + cacheRecord = new SingleContextLruCacheRecord({ + id: CONTEXT_STORAGE_LRU_CACHE_ID, + entries: new Map(), + }) + + await cacheRepository.save(agentContext, cacheRecord) + } + + return cacheRecord + } + + private removeExpiredItems(cache: LRUMap) { + cache.forEach((value, key) => { + if (value.expiresAt && Date.now() > value.expiresAt) { + cache.delete(key) + } + }) + } + + private async persistCache(agentContext: AgentContext) { + const cacheRepository = agentContext.dependencyManager.resolve(SingleContextLruCacheRepository) + const cache = await this.getCache(agentContext) + + await cacheRepository.update( + agentContext, + new SingleContextLruCacheRecord({ + entries: new Map(cache.toJSON().map(({ key, value }) => [key, value])), + id: CONTEXT_STORAGE_LRU_CACHE_ID, + }) + ) + } + + /** + * Asserts this class is not used with multiple agent context instances. + */ + private assertContextCorrelationId(agentContext: AgentContext) { + if (!this._contextCorrelationId) { + this._contextCorrelationId = agentContext.contextCorrelationId + } + + if (this._contextCorrelationId !== agentContext.contextCorrelationId) { + throw new AriesFrameworkError( + 'SingleContextStorageLruCache can not be used with multiple agent context instances. Register a custom cache implementation in the CacheModule.' + ) + } + } +} diff --git a/packages/core/src/modules/cache/singleContextLruCache/__tests__/SingleContextStorageLruCache.test.ts b/packages/core/src/modules/cache/singleContextLruCache/__tests__/SingleContextStorageLruCache.test.ts new file mode 100644 index 0000000000..2251b9b854 --- /dev/null +++ b/packages/core/src/modules/cache/singleContextLruCache/__tests__/SingleContextStorageLruCache.test.ts @@ -0,0 +1,91 @@ +import { getAgentContext, mockFunction } from '../../../../../tests/helpers' +import { SingleContextLruCacheRecord } from '../SingleContextLruCacheRecord' +import { SingleContextLruCacheRepository } from '../SingleContextLruCacheRepository' +import { SingleContextStorageLruCache } from '../SingleContextStorageLruCache' + +jest.mock('../SingleContextLruCacheRepository') +const SingleContextLruCacheRepositoryMock = + SingleContextLruCacheRepository as jest.Mock + +const cacheRepository = new SingleContextLruCacheRepositoryMock() +const agentContext = getAgentContext({ + registerInstances: [[SingleContextLruCacheRepository, cacheRepository]], +}) + +describe('SingleContextLruCache', () => { + let cache: SingleContextStorageLruCache + + beforeEach(() => { + mockFunction(cacheRepository.findById).mockResolvedValue(null) + cache = new SingleContextStorageLruCache({ limit: 2 }) + }) + + it('should return the value from the persisted record', async () => { + const findMock = mockFunction(cacheRepository.findById).mockResolvedValue( + new SingleContextLruCacheRecord({ + id: 'CONTEXT_STORAGE_LRU_CACHE_ID', + entries: new Map([ + [ + 'test', + { + value: 'somevalue', + }, + ], + ]), + }) + ) + + expect(await cache.get(agentContext, 'doesnotexist')).toBeNull() + expect(await cache.get(agentContext, 'test')).toBe('somevalue') + expect(findMock).toHaveBeenCalledWith(agentContext, 'CONTEXT_STORAGE_LRU_CACHE_ID') + }) + + it('should set the value in the persisted record', async () => { + const updateMock = mockFunction(cacheRepository.update).mockResolvedValue() + + await cache.set(agentContext, 'test', 'somevalue') + const [[, cacheRecord]] = updateMock.mock.calls + + expect(cacheRecord.entries.size).toBe(1) + + const [[key, item]] = cacheRecord.entries.entries() + expect(key).toBe('test') + expect(item.value).toBe('somevalue') + + expect(await cache.get(agentContext, 'test')).toBe('somevalue') + }) + + it('should remove least recently used entries if entries are added that exceed the limit', async () => { + // Set first value in cache, resolves fine + await cache.set(agentContext, 'one', 'valueone') + expect(await cache.get(agentContext, 'one')).toBe('valueone') + + // Set two more entries in the cache. Third item + // exceeds limit, so first item gets removed + await cache.set(agentContext, 'two', 'valuetwo') + await cache.set(agentContext, 'three', 'valuethree') + expect(await cache.get(agentContext, 'one')).toBeNull() + expect(await cache.get(agentContext, 'two')).toBe('valuetwo') + expect(await cache.get(agentContext, 'three')).toBe('valuethree') + + // Get two from the cache, meaning three will be removed first now + // because it is not recently used + await cache.get(agentContext, 'two') + await cache.set(agentContext, 'four', 'valuefour') + expect(await cache.get(agentContext, 'three')).toBeNull() + expect(await cache.get(agentContext, 'two')).toBe('valuetwo') + }) + + it('should throw an error if used with multiple context correlation ids', async () => { + // No issue, first call with an agentContext + await cache.get(agentContext, 'test') + + const secondAgentContext = getAgentContext({ + contextCorrelationId: 'another', + }) + + expect(cache.get(secondAgentContext, 'test')).rejects.toThrowError( + 'SingleContextStorageLruCache can not be used with multiple agent context instances. Register a custom cache implementation in the CacheModule.' + ) + }) +}) diff --git a/packages/core/src/modules/cache/singleContextLruCache/index.ts b/packages/core/src/modules/cache/singleContextLruCache/index.ts new file mode 100644 index 0000000000..4d01549062 --- /dev/null +++ b/packages/core/src/modules/cache/singleContextLruCache/index.ts @@ -0,0 +1 @@ +export { SingleContextStorageLruCache, SingleContextStorageLruCacheOptions } from './SingleContextStorageLruCache' diff --git a/packages/core/src/modules/connections/ConnectionEvents.ts b/packages/core/src/modules/connections/ConnectionEvents.ts index 6239efa0aa..c9f1064bab 100644 --- a/packages/core/src/modules/connections/ConnectionEvents.ts +++ b/packages/core/src/modules/connections/ConnectionEvents.ts @@ -1,6 +1,6 @@ -import type { BaseEvent } from '../../agent/Events' import type { DidExchangeState } from './models' import type { ConnectionRecord } from './repository/ConnectionRecord' +import type { BaseEvent } from '../../agent/Events' export enum ConnectionEventTypes { ConnectionStateChanged = 'ConnectionStateChanged', @@ -13,22 +13,3 @@ export interface ConnectionStateChangedEvent extends BaseEvent { previousState: DidExchangeState | null } } - -export enum TrustPingEventTypes { - PingReceived = 'PingReceived', - PingResponseReceived = 'PingResponseReceived', -} - -export interface PingReceivedEvent extends BaseEvent { - type: typeof TrustPingEventTypes.PingReceived - payload: { - from: string - } -} - -export interface PingResponseReceivedEvent extends BaseEvent { - type: typeof TrustPingEventTypes.PingResponseReceived - payload: { - from: string - } -} diff --git a/packages/core/src/modules/connections/ConnectionsApi.ts b/packages/core/src/modules/connections/ConnectionsApi.ts index 741d22e233..7b494a9f6a 100644 --- a/packages/core/src/modules/connections/ConnectionsApi.ts +++ b/packages/core/src/modules/connections/ConnectionsApi.ts @@ -1,11 +1,11 @@ -import type { Query } from '../../storage/StorageService' -import type { OutOfBandRecord } from '../oob/repository' import type { ConnectionType } from './models' import type { ConnectionRecord } from './repository/ConnectionRecord' import type { Routing } from './services' +import type { Query } from '../../storage/StorageService' +import type { OutOfBandRecord } from '../oob/repository' import { AgentContext } from '../../agent' -import { Dispatcher } from '../../agent/Dispatcher' +import { MessageHandlerRegistry } from '../../agent/MessageHandlerRegistry' import { MessageSender } from '../../agent/MessageSender' import { OutboundMessageContext } from '../../agent/models' import { ReturnRouteTypes } from '../../decorators/transport/TransportDecorator' @@ -27,10 +27,23 @@ import { DidExchangeResponseHandler, } from './handlers' import { HandshakeProtocol } from './models' +import { + TrustPingMessageHandler as V1TrustPingMessageHandler, + TrustPingResponseMessageHandler as V1TrustPingResponseMessageHandler, +} from './protocols/trust-ping/v1' import { V1TrustPingService } from './protocols/trust-ping/v1/V1TrustPingService' +import { + TrustPingMessageHandler as V2TrustPingMessageHandler, + TrustPingResponseMessageHandler as V2TrustPingResponseMessageHandler, +} from './protocols/trust-ping/v2' import { V2TrustPingService } from './protocols/trust-ping/v2/V2TrustPingService' import { ConnectionService } from './services/ConnectionService' +export interface SendPingOptions { + responseRequested?: boolean + withReturnRouting?: boolean +} + @injectable() export class ConnectionsApi { /** @@ -50,7 +63,7 @@ export class ConnectionsApi { private agentContext: AgentContext public constructor( - dispatcher: Dispatcher, + messageHandlerRegistry: MessageHandlerRegistry, didExchangeProtocol: DidExchangeProtocol, connectionService: ConnectionService, outOfBandService: OutOfBandService, @@ -75,7 +88,7 @@ export class ConnectionsApi { this.agentContext = agentContext this.config = connectionsModuleConfig - this.registerMessageHandlers(dispatcher) + this.registerMessageHandlers(messageHandlerRegistry) } public async acceptOutOfBandInvitation( @@ -281,17 +294,47 @@ export class ConnectionsApi { } /** - * Send Ping message to remote party + * Send a trust ping to an established connection + * + * @param connectionId the id of the connection for which to accept the response + * @param responseRequested do we want a response to our ping + * @param withReturnRouting do we want a response at the time of posting + * @returns TurstPingMessage */ - public async sendPing(connectionId: string) { + public async sendPing( + connectionId: string, + { responseRequested = true, withReturnRouting = undefined }: SendPingOptions + ) { const connection = await this.getById(connectionId) - const message = connection.isDidCommV1Connection - ? await this.v1trustPingService.createPing() - : await this.v2TrustPingService.createPing(connection) - await this.messageSender.sendMessage( - new OutboundMessageContext(message, { agentContext: this.agentContext, connection }) - ) + if (connection.isDidCommV1Connection) { + const { message } = await this.connectionService.createTrustPing(this.agentContext, connection, { + responseRequested: responseRequested, + }) + + if (withReturnRouting === true) { + message.setReturnRouting(ReturnRouteTypes.all) + } + + // Disable return routing as we don't want to receive a response for this message over the same channel + // This has led to long timeouts as not all clients actually close an http socket if there is no response message + if (withReturnRouting === false) { + message.setReturnRouting(ReturnRouteTypes.none) + } + + await this.messageSender.sendMessage( + new OutboundMessageContext(message, { agentContext: this.agentContext, connection }) + ) + + return message + } else { + const message = await this.v2TrustPingService.createPing(connection) + + await this.messageSender.sendMessage( + new OutboundMessageContext(message, { agentContext: this.agentContext, connection }) + ) + return message + } } /** @@ -383,8 +426,8 @@ export class ConnectionsApi { return this.connectionService.findByInvitationDid(this.agentContext, invitationDid) } - private registerMessageHandlers(dispatcher: Dispatcher) { - dispatcher.registerMessageHandler( + private registerMessageHandlers(messageHandlerRegistry: MessageHandlerRegistry) { + messageHandlerRegistry.registerMessageHandler( new ConnectionRequestHandler( this.connectionService, this.outOfBandService, @@ -393,12 +436,20 @@ export class ConnectionsApi { this.config ) ) - dispatcher.registerMessageHandler( + messageHandlerRegistry.registerMessageHandler( new ConnectionResponseHandler(this.connectionService, this.outOfBandService, this.didResolverService, this.config) ) - dispatcher.registerMessageHandler(new AckMessageHandler(this.connectionService)) + messageHandlerRegistry.registerMessageHandler(new AckMessageHandler(this.connectionService)) + messageHandlerRegistry.registerMessageHandler( + new V1TrustPingMessageHandler(this.v1trustPingService, this.connectionService) + ) + messageHandlerRegistry.registerMessageHandler(new V1TrustPingResponseMessageHandler(this.v1trustPingService)) + messageHandlerRegistry.registerMessageHandler( + new V2TrustPingMessageHandler(this.v2TrustPingService, this.connectionService) + ) + messageHandlerRegistry.registerMessageHandler(new V2TrustPingResponseMessageHandler(this.v2TrustPingService)) - dispatcher.registerMessageHandler( + messageHandlerRegistry.registerMessageHandler( new DidExchangeRequestHandler( this.didExchangeProtocol, this.outOfBandService, @@ -408,7 +459,7 @@ export class ConnectionsApi { ) ) - dispatcher.registerMessageHandler( + messageHandlerRegistry.registerMessageHandler( new DidExchangeResponseHandler( this.didExchangeProtocol, this.outOfBandService, @@ -417,6 +468,8 @@ export class ConnectionsApi { this.config ) ) - dispatcher.registerMessageHandler(new DidExchangeCompleteHandler(this.didExchangeProtocol, this.outOfBandService)) + messageHandlerRegistry.registerMessageHandler( + new DidExchangeCompleteHandler(this.didExchangeProtocol, this.outOfBandService) + ) } } diff --git a/packages/core/src/modules/connections/ConnectionsModule.ts b/packages/core/src/modules/connections/ConnectionsModule.ts index 27fa522312..98970a7f92 100644 --- a/packages/core/src/modules/connections/ConnectionsModule.ts +++ b/packages/core/src/modules/connections/ConnectionsModule.ts @@ -1,6 +1,6 @@ +import type { ConnectionsModuleConfigOptions } from './ConnectionsModuleConfig' import type { FeatureRegistry } from '../../agent/FeatureRegistry' import type { DependencyManager, Module } from '../../plugins' -import type { ConnectionsModuleConfigOptions } from './ConnectionsModuleConfig' import { Protocol } from '../../agent/models' diff --git a/packages/core/src/modules/connections/ConnectionsModuleConfig.ts b/packages/core/src/modules/connections/ConnectionsModuleConfig.ts index b4b69edacf..59aaf8cb5b 100644 --- a/packages/core/src/modules/connections/ConnectionsModuleConfig.ts +++ b/packages/core/src/modules/connections/ConnectionsModuleConfig.ts @@ -13,14 +13,21 @@ export interface ConnectionsModuleConfigOptions { } export class ConnectionsModuleConfig { + #autoAcceptConnections?: boolean private options: ConnectionsModuleConfigOptions public constructor(options?: ConnectionsModuleConfigOptions) { this.options = options ?? {} + this.#autoAcceptConnections = this.options.autoAcceptConnections } /** See {@link ConnectionsModuleConfigOptions.autoAcceptConnections} */ public get autoAcceptConnections() { - return this.options.autoAcceptConnections ?? false + return this.#autoAcceptConnections ?? false + } + + /** See {@link ConnectionsModuleConfigOptions.autoAcceptConnections} */ + public set autoAcceptConnections(autoAcceptConnections: boolean) { + this.#autoAcceptConnections = autoAcceptConnections } } diff --git a/packages/core/src/modules/connections/DidExchangeProtocol.ts b/packages/core/src/modules/connections/DidExchangeProtocol.ts index 4f8691d91e..e08f3b0878 100644 --- a/packages/core/src/modules/connections/DidExchangeProtocol.ts +++ b/packages/core/src/modules/connections/DidExchangeProtocol.ts @@ -1,11 +1,11 @@ +import type { ConnectionRecord } from './repository' +import type { Routing } from './services/ConnectionService' import type { AgentContext } from '../../agent' import type { InboundMessageContext } from '../../agent/models/InboundMessageContext' import type { ParsedMessageType } from '../../utils/messageType' import type { ResolvedDidCommService } from '../didcomm' import type { PeerDidCreateOptions } from '../dids' import type { OutOfBandRecord } from '../oob/repository' -import type { ConnectionRecord } from './repository' -import type { Routing } from './services/ConnectionService' import { InjectionSymbols } from '../../constants' import { Key, KeyType } from '../../crypto' @@ -25,7 +25,8 @@ import { getNumAlgoFromPeerDid, PeerDidNumAlgo, } from '../dids' -import { getKeyDidMappingByVerificationMethod } from '../dids/domain/key-type' +import { getKeyFromVerificationMethod } from '../dids/domain/key-type' +import { tryParseDid } from '../dids/domain/parse' import { didKeyToInstanceOfKey } from '../dids/helpers' import { DidRecord, DidRepository } from '../dids/repository' import { OutOfBandRole } from '../oob/domain/OutOfBandRole' @@ -101,6 +102,7 @@ export class DidExchangeProtocol { autoAcceptConnection: outOfBandRecord.autoAcceptConnection, outOfBandId: outOfBandRecord.id, invitationDid, + imageUrl: outOfBandInvitation.imageUrl, }) DidExchangeStateMachine.assertCreateMessageState(DidExchangeRequestMessage.type, connectionRecord) @@ -108,7 +110,7 @@ export class DidExchangeProtocol { // Create message const label = params.label ?? agentContext.config.label const didDocument = await this.createPeerDidDoc(agentContext, this.routingToServices(routing)) - const parentThreadId = outOfBandInvitation.id + const parentThreadId = outOfBandRecord.getOutOfBandInvitation().id const message = new DidExchangeRequestMessage({ label, parentThreadId, did: didDocument.id, goal, goalCode }) @@ -150,9 +152,13 @@ export class DidExchangeProtocol { const { message } = messageContext - // Check corresponding invitation ID is the request's ~thread.parentThreadId + // Check corresponding invitation ID is the request's ~thread.pthid or pthid is a public did // TODO Maybe we can do it in handler, but that actually does not make sense because we try to find oob by parent thread ID there. - if (!message.thread?.parentThreadId || message.thread?.parentThreadId !== outOfBandRecord.getTags().invitationId) { + const parentThreadId = message.thread?.parentThreadId + if ( + !parentThreadId || + (!tryParseDid(parentThreadId) && parentThreadId !== outOfBandRecord.getTags().invitationId) + ) { throw new DidExchangeProblemReportError('Missing reference to invitation.', { problemCode: DidExchangeProblemReportReason.RequestNotAccepted, }) @@ -405,8 +411,8 @@ export class DidExchangeProtocol { problemCode: DidExchangeProblemReportReason.CompleteRejected, }) } - - if (!message.thread?.parentThreadId || message.thread?.parentThreadId !== outOfBandRecord.getTags().invitationId) { + const pthid = message.thread?.parentThreadId + if (!pthid || pthid !== outOfBandRecord.getOutOfBandInvitation().id) { throw new DidExchangeProblemReportError('Invalid or missing parent thread ID referencing to the invitation.', { problemCode: DidExchangeProblemReportReason.CompleteRejected, }) @@ -468,10 +474,14 @@ export class DidExchangeProtocol { const jws = await this.jwsService.createJws(agentContext, { payload, - verkey, + key, header: { kid, }, + protectedHeaderOptions: { + alg: 'EdDSA', + jwk: key.toJwk(), + }, }) didDocAttach.addJws(jws) }) @@ -514,7 +524,7 @@ export class DidExchangeProtocol { this.logger.trace('DidDocument JSON', json) const payload = JsonEncoder.toBuffer(json) - const { isValid, signerVerkeys } = await this.jwsService.verifyJws(agentContext, { jws, payload }) + const { isValid, signerKeys } = await this.jwsService.verifyJws(agentContext, { jws, payload }) const didDocument = JsonTransformer.fromJSON(json, DidDocument) const didDocumentKeysBase58 = didDocument.authentication @@ -523,15 +533,14 @@ export class DidExchangeProtocol { typeof authentication === 'string' ? didDocument.dereferenceVerificationMethod(authentication) : authentication - const { getKeyFromVerificationMethod } = getKeyDidMappingByVerificationMethod(verificationMethod) const key = getKeyFromVerificationMethod(verificationMethod) return key.publicKeyBase58 }) .concat(invitationKeysBase58) - this.logger.trace('JWS verification result', { isValid, signerVerkeys, didDocumentKeysBase58 }) + this.logger.trace('JWS verification result', { isValid, signerKeys, didDocumentKeysBase58 }) - if (!isValid || !signerVerkeys.every((verkey) => didDocumentKeysBase58?.includes(verkey))) { + if (!isValid || !signerKeys.every((key) => didDocumentKeysBase58?.includes(key.publicKeyBase58))) { const problemCode = message instanceof DidExchangeRequestMessage ? DidExchangeProblemReportReason.RequestNotAccepted diff --git a/packages/core/src/modules/connections/DidExchangeStateMachine.ts b/packages/core/src/modules/connections/DidExchangeStateMachine.ts index 3f32f4e48d..be73793ab3 100644 --- a/packages/core/src/modules/connections/DidExchangeStateMachine.ts +++ b/packages/core/src/modules/connections/DidExchangeStateMachine.ts @@ -1,5 +1,5 @@ -import type { ParsedMessageType } from '../../utils/messageType' import type { ConnectionRecord } from './repository' +import type { ParsedMessageType } from '../../utils/messageType' import { AriesFrameworkError } from '../../error' import { canHandleMessageType } from '../../utils/messageType' diff --git a/packages/core/src/modules/connections/__tests__/ConnectionInvitationMessage.test.ts b/packages/core/src/modules/connections/__tests__/ConnectionInvitationMessage.test.ts index a2d198083c..227be3c0d0 100644 --- a/packages/core/src/modules/connections/__tests__/ConnectionInvitationMessage.test.ts +++ b/packages/core/src/modules/connections/__tests__/ConnectionInvitationMessage.test.ts @@ -65,7 +65,7 @@ describe('ConnectionInvitationMessage', () => { expect(invitationUrl).toBe(`${domain}?c_i=${JsonEncoder.toBase64URL(json)}`) }) - it('should use did:sov:BzCbsNYhMrjHiqZDTUASHg;spec/connections/1.0/invitation as type if useLegacyDidSovPrefix is set to true', async () => { + it('should use did:sov:BzCbsNYhMrjHiqZDTUASHg;spec/connections/1.0/invitation as type if useDidSovPrefixWhereAllowed is set to true', async () => { const invitation = new ConnectionInvitationMessage({ id: '04a2c382-999e-4de9-a1d2-9dec0b2fa5e4', recipientKeys: ['recipientKeyOne', 'recipientKeyTwo'], @@ -86,7 +86,7 @@ describe('ConnectionInvitationMessage', () => { const invitationUrl = invitation.toUrl({ domain: 'https://example.com', - useLegacyDidSovPrefix: true, + useDidSovPrefixWhereAllowed: true, }) const parsedUrl = parseUrl(invitationUrl).query diff --git a/packages/core/src/modules/connections/__tests__/ConnectionService.test.ts b/packages/core/src/modules/connections/__tests__/ConnectionService.test.ts index 832dbde4fa..05ee1c9c2f 100644 --- a/packages/core/src/modules/connections/__tests__/ConnectionService.test.ts +++ b/packages/core/src/modules/connections/__tests__/ConnectionService.test.ts @@ -5,6 +5,8 @@ import type { Routing } from '../services/ConnectionService' import { Subject } from 'rxjs' +import { IndySdkWallet } from '../../../../../indy-sdk/src' +import { indySdk } from '../../../../../indy-sdk/tests/setupIndySdkModule' import { getAgentConfig, getAgentContext, @@ -14,14 +16,12 @@ import { } from '../../../../tests/helpers' import { EventEmitter } from '../../../agent/EventEmitter' import { InboundMessageContext } from '../../../agent/models/InboundMessageContext' -import { Key, KeyType } from '../../../crypto' -import { KeyProviderRegistry } from '../../../crypto/key-provider' +import { Key, KeyProviderRegistry, KeyType } from '../../../crypto' import { signData, unpackAndVerifySignatureDecorator } from '../../../decorators/signature/SignatureDecoratorUtils' import { DidCommV1Message } from '../../../didcomm' import { JsonTransformer } from '../../../utils/JsonTransformer' import { indyDidFromPublicKeyBase58 } from '../../../utils/did' import { uuid } from '../../../utils/uuid' -import { IndyWallet } from '../../../wallet/IndyWallet' import { AckMessage, AckStatus } from '../../common' import { DidKey, IndyAgentService } from '../../dids' import { DidDocumentRole } from '../../dids/domain/DidDocumentRole' @@ -82,10 +82,9 @@ describe('ConnectionService', () => { let agentContext: AgentContext beforeAll(async () => { - wallet = new IndyWallet(agentConfig.agentDependencies, agentConfig.logger, new KeyProviderRegistry([])) + wallet = new IndySdkWallet(indySdk, agentConfig.logger, new KeyProviderRegistry([])) agentContext = getAgentContext({ wallet, agentConfig }) - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - await wallet.createAndOpen(agentConfig.walletConfig!) + await wallet.createAndOpen(agentConfig.walletConfig) }) afterAll(async () => { diff --git a/packages/core/src/modules/connections/__tests__/connection-manual.e2e.test.ts b/packages/core/src/modules/connections/__tests__/connection-manual.e2e.test.ts index e84bab18d1..a5e3ef2fe7 100644 --- a/packages/core/src/modules/connections/__tests__/connection-manual.e2e.test.ts +++ b/packages/core/src/modules/connections/__tests__/connection-manual.e2e.test.ts @@ -4,11 +4,12 @@ import type { ConnectionStateChangedEvent } from '../ConnectionEvents' import { firstValueFrom } from 'rxjs' import { filter, first, map, timeout } from 'rxjs/operators' -import { SubjectInboundTransport } from '../../../../../../tests/transport/SubjectInboundTransport' -import { SubjectOutboundTransport } from '../../../../../../tests/transport/SubjectOutboundTransport' +import { getIndySdkModules } from '../../../../../indy-sdk/tests/setupIndySdkModule' +import { setupSubjectTransports } from '../../../../tests' import { getAgentOptions } from '../../../../tests/helpers' import { Agent } from '../../../agent/Agent' import { ConnectionEventTypes } from '../ConnectionEvents' +import { ConnectionsModule } from '../ConnectionsModule' import { DidExchangeState } from '../models' function waitForRequest(agent: Agent, theirLabel: string) { @@ -45,42 +46,50 @@ describe('Manual Connection Flow', () => { // This test was added to reproduce a bug where all connections based on a reusable invitation would use the same keys // This was only present in the manual flow, which is almost never used. it('can connect multiple times using the same reusable invitation without manually using the connections api', async () => { - const aliceInboundTransport = new SubjectInboundTransport() - const bobInboundTransport = new SubjectInboundTransport() - const faberInboundTransport = new SubjectInboundTransport() - - const subjectMap = { - 'rxjs:faber': faberInboundTransport.ourSubject, - 'rxjs:alice': aliceInboundTransport.ourSubject, - 'rxjs:bob': bobInboundTransport.ourSubject, - } - const aliceAgentOptions = getAgentOptions('Manual Connection Flow Alice', { - label: 'alice', - autoAcceptConnections: false, - endpoints: ['rxjs:alice'], - }) - const bobAgentOptions = getAgentOptions('Manual Connection Flow Bob', { - label: 'bob', - autoAcceptConnections: false, - endpoints: ['rxjs:bob'], - }) - const faberAgentOptions = getAgentOptions('Manual Connection Flow Faber', { - autoAcceptConnections: false, - endpoints: ['rxjs:faber'], - }) + const aliceAgentOptions = getAgentOptions( + 'Manual Connection Flow Alice', + { + label: 'alice', + endpoints: ['rxjs:alice'], + }, + { + ...getIndySdkModules(), + connections: new ConnectionsModule({ + autoAcceptConnections: false, + }), + } + ) + const bobAgentOptions = getAgentOptions( + 'Manual Connection Flow Bob', + { + label: 'bob', + endpoints: ['rxjs:bob'], + }, + { + ...getIndySdkModules(), + connections: new ConnectionsModule({ + autoAcceptConnections: false, + }), + } + ) + const faberAgentOptions = getAgentOptions( + 'Manual Connection Flow Faber', + { + endpoints: ['rxjs:faber'], + }, + { + ...getIndySdkModules(), + connections: new ConnectionsModule({ + autoAcceptConnections: false, + }), + } + ) const aliceAgent = new Agent(aliceAgentOptions) - aliceAgent.registerInboundTransport(aliceInboundTransport) - aliceAgent.registerOutboundTransport(new SubjectOutboundTransport(subjectMap)) - const bobAgent = new Agent(bobAgentOptions) - bobAgent.registerInboundTransport(bobInboundTransport) - bobAgent.registerOutboundTransport(new SubjectOutboundTransport(subjectMap)) - const faberAgent = new Agent(faberAgentOptions) - faberAgent.registerInboundTransport(faberInboundTransport) - faberAgent.registerOutboundTransport(new SubjectOutboundTransport(subjectMap)) + setupSubjectTransports([aliceAgent, bobAgent, faberAgent]) await aliceAgent.initialize() await bobAgent.initialize() await faberAgent.initialize() diff --git a/packages/core/src/modules/connections/errors/ConnectionProblemReportError.ts b/packages/core/src/modules/connections/errors/ConnectionProblemReportError.ts index 764be043f9..f96f98261d 100644 --- a/packages/core/src/modules/connections/errors/ConnectionProblemReportError.ts +++ b/packages/core/src/modules/connections/errors/ConnectionProblemReportError.ts @@ -1,5 +1,5 @@ -import type { ProblemReportErrorOptions } from '../../problem-reports' import type { ConnectionProblemReportReason } from './ConnectionProblemReportReason' +import type { ProblemReportErrorOptions } from '../../problem-reports' import { ProblemReportError } from '../../problem-reports' import { ConnectionProblemReportMessage } from '../messages' diff --git a/packages/core/src/modules/connections/errors/DidExchangeProblemReportError.ts b/packages/core/src/modules/connections/errors/DidExchangeProblemReportError.ts index 17bf72ad9b..6e8d9a9925 100644 --- a/packages/core/src/modules/connections/errors/DidExchangeProblemReportError.ts +++ b/packages/core/src/modules/connections/errors/DidExchangeProblemReportError.ts @@ -1,5 +1,5 @@ -import type { ProblemReportErrorOptions } from '../../problem-reports' import type { DidExchangeProblemReportReason } from './DidExchangeProblemReportReason' +import type { ProblemReportErrorOptions } from '../../problem-reports' import { ProblemReportError } from '../../problem-reports' import { DidExchangeProblemReportMessage } from '../messages' diff --git a/packages/core/src/modules/connections/handlers/ConnectionRequestHandler.ts b/packages/core/src/modules/connections/handlers/ConnectionRequestHandler.ts index bdc2b6df79..428e4bc335 100644 --- a/packages/core/src/modules/connections/handlers/ConnectionRequestHandler.ts +++ b/packages/core/src/modules/connections/handlers/ConnectionRequestHandler.ts @@ -5,9 +5,12 @@ import type { RoutingService } from '../../routing/services/RoutingService' import type { ConnectionsModuleConfig } from '../ConnectionsModuleConfig' import type { ConnectionService } from '../services/ConnectionService' +import { TransportService } from '../../../agent/TransportService' import { OutboundMessageContext } from '../../../agent/models' import { AriesFrameworkError } from '../../../error/AriesFrameworkError' +import { tryParseDid } from '../../dids/domain/parse' import { ConnectionRequestMessage } from '../messages' +import { HandshakeProtocol } from '../models' export class ConnectionRequestHandler implements MessageHandler { private connectionService: ConnectionService @@ -32,16 +35,23 @@ export class ConnectionRequestHandler implements MessageHandler { } public async handle(messageContext: MessageHandlerInboundMessage) { - const { connection, recipientKey, senderKey } = messageContext + const { agentContext, connection, recipientKey, senderKey, message, sessionId } = messageContext if (!recipientKey || !senderKey) { throw new AriesFrameworkError('Unable to process connection request without senderVerkey or recipientKey') } - const outOfBandRecord = await this.outOfBandService.findCreatedByRecipientKey( - messageContext.agentContext, - recipientKey - ) + const parentThreadId = message.thread?.parentThreadId + + const outOfBandRecord = + parentThreadId && tryParseDid(parentThreadId) + ? await this.outOfBandService.createFromImplicitInvitation(agentContext, { + did: parentThreadId, + threadId: message.threadId, + recipientKey, + handshakeProtocols: [HandshakeProtocol.Connections], + }) + : await this.outOfBandService.findCreatedByRecipientKey(agentContext, recipientKey) if (!outOfBandRecord) { throw new AriesFrameworkError(`Out-of-band record for recipient key ${recipientKey.fingerprint} was not found.`) @@ -53,30 +63,31 @@ export class ConnectionRequestHandler implements MessageHandler { ) } - const receivedDidRecord = await this.didRepository.findReceivedDidByRecipientKey( - messageContext.agentContext, - senderKey - ) + const receivedDidRecord = await this.didRepository.findReceivedDidByRecipientKey(agentContext, senderKey) if (receivedDidRecord) { throw new AriesFrameworkError(`A received did record for sender key ${senderKey.fingerprint} already exists.`) } const connectionRecord = await this.connectionService.processRequest(messageContext, outOfBandRecord) + // Associate the new connection with the session created for the inbound message + if (sessionId) { + const transportService = agentContext.dependencyManager.resolve(TransportService) + transportService.setConnectionIdForSession(sessionId, connectionRecord.id) + } + if (connectionRecord?.autoAcceptConnection ?? this.connectionsModuleConfig.autoAcceptConnections) { // TODO: Allow rotation of keys used in the invitation for new ones not only when out-of-band is reusable - const routing = outOfBandRecord.reusable - ? await this.routingService.getRouting(messageContext.agentContext) - : undefined + const routing = outOfBandRecord.reusable ? await this.routingService.getRouting(agentContext) : undefined const { message } = await this.connectionService.createResponse( - messageContext.agentContext, + agentContext, connectionRecord, outOfBandRecord, routing ) return new OutboundMessageContext(message, { - agentContext: messageContext.agentContext, + agentContext, connection: connectionRecord, outOfBand: outOfBandRecord, }) diff --git a/packages/core/src/modules/connections/handlers/DidExchangeCompleteHandler.ts b/packages/core/src/modules/connections/handlers/DidExchangeCompleteHandler.ts index 9de47ac39d..b492998d88 100644 --- a/packages/core/src/modules/connections/handlers/DidExchangeCompleteHandler.ts +++ b/packages/core/src/modules/connections/handlers/DidExchangeCompleteHandler.ts @@ -3,6 +3,7 @@ import type { OutOfBandService } from '../../oob/protocols/v1/OutOfBandService' import type { DidExchangeProtocol } from '../DidExchangeProtocol' import { AriesFrameworkError } from '../../../error' +import { tryParseDid } from '../../dids/domain/parse' import { OutOfBandState } from '../../oob/domain/OutOfBandState' import { DidExchangeCompleteMessage } from '../messages' import { HandshakeProtocol } from '../models' @@ -32,12 +33,14 @@ export class DidExchangeCompleteHandler implements MessageHandler { } const { message } = messageContext - if (!message.thread?.parentThreadId) { + const parentThreadId = message.thread?.parentThreadId + if (!parentThreadId) { throw new AriesFrameworkError(`Message does not contain pthid attribute`) } const outOfBandRecord = await this.outOfBandService.findByCreatedInvitationId( messageContext.agentContext, - message.thread?.parentThreadId + parentThreadId, + tryParseDid(parentThreadId) ? message.threadId : undefined ) if (!outOfBandRecord) { diff --git a/packages/core/src/modules/connections/handlers/DidExchangeRequestHandler.ts b/packages/core/src/modules/connections/handlers/DidExchangeRequestHandler.ts index a6fb4c1b6e..5b9c5c1c58 100644 --- a/packages/core/src/modules/connections/handlers/DidExchangeRequestHandler.ts +++ b/packages/core/src/modules/connections/handlers/DidExchangeRequestHandler.ts @@ -5,10 +5,13 @@ import type { RoutingService } from '../../routing/services/RoutingService' import type { ConnectionsModuleConfig } from '../ConnectionsModuleConfig' import type { DidExchangeProtocol } from '../DidExchangeProtocol' +import { TransportService } from '../../../agent/TransportService' import { OutboundMessageContext } from '../../../agent/models' import { AriesFrameworkError } from '../../../error/AriesFrameworkError' +import { tryParseDid } from '../../dids/domain/parse' import { OutOfBandState } from '../../oob/domain/OutOfBandState' import { DidExchangeRequestMessage } from '../messages' +import { HandshakeProtocol } from '../models' export class DidExchangeRequestHandler implements MessageHandler { private didExchangeProtocol: DidExchangeProtocol @@ -33,22 +36,28 @@ export class DidExchangeRequestHandler implements MessageHandler { } public async handle(messageContext: MessageHandlerInboundMessage) { - const { recipientKey, senderKey, message, connection } = messageContext + const { agentContext, recipientKey, senderKey, message, connection, sessionId } = messageContext if (!recipientKey || !senderKey) { throw new AriesFrameworkError('Unable to process connection request without senderKey or recipientKey') } - if (!message.thread?.parentThreadId) { + const parentThreadId = message.thread?.parentThreadId + + if (!parentThreadId) { throw new AriesFrameworkError(`Message does not contain 'pthid' attribute`) } - const outOfBandRecord = await this.outOfBandService.findByCreatedInvitationId( - messageContext.agentContext, - message.thread.parentThreadId - ) + const outOfBandRecord = tryParseDid(parentThreadId) + ? await this.outOfBandService.createFromImplicitInvitation(agentContext, { + did: parentThreadId, + threadId: message.threadId, + recipientKey, + handshakeProtocols: [HandshakeProtocol.DidExchange], + }) + : await this.outOfBandService.findByCreatedInvitationId(agentContext, parentThreadId) if (!outOfBandRecord) { - throw new AriesFrameworkError(`OutOfBand record for message ID ${message.thread?.parentThreadId} not found!`) + throw new AriesFrameworkError(`OutOfBand record for message ID ${parentThreadId} not found!`) } if (connection && !outOfBandRecord.reusable) { @@ -57,10 +66,7 @@ export class DidExchangeRequestHandler implements MessageHandler { ) } - const receivedDidRecord = await this.didRepository.findReceivedDidByRecipientKey( - messageContext.agentContext, - senderKey - ) + const receivedDidRecord = await this.didRepository.findReceivedDidByRecipientKey(agentContext, senderKey) if (receivedDidRecord) { throw new AriesFrameworkError(`A received did record for sender key ${senderKey.fingerprint} already exists.`) } @@ -75,21 +81,25 @@ export class DidExchangeRequestHandler implements MessageHandler { const connectionRecord = await this.didExchangeProtocol.processRequest(messageContext, outOfBandRecord) + // Associate the new connection with the session created for the inbound message + if (sessionId) { + const transportService = agentContext.dependencyManager.resolve(TransportService) + transportService.setConnectionIdForSession(sessionId, connectionRecord.id) + } + if (connectionRecord.autoAcceptConnection ?? this.connectionsModuleConfig.autoAcceptConnections) { // TODO We should add an option to not pass routing and therefore do not rotate keys and use the keys from the invitation // TODO: Allow rotation of keys used in the invitation for new ones not only when out-of-band is reusable - const routing = outOfBandRecord.reusable - ? await this.routingService.getRouting(messageContext.agentContext) - : undefined + const routing = outOfBandRecord.reusable ? await this.routingService.getRouting(agentContext) : undefined const message = await this.didExchangeProtocol.createResponse( - messageContext.agentContext, + agentContext, connectionRecord, outOfBandRecord, routing ) return new OutboundMessageContext(message, { - agentContext: messageContext.agentContext, + agentContext, connection: connectionRecord, outOfBand: outOfBandRecord, }) diff --git a/packages/core/src/modules/connections/index.ts b/packages/core/src/modules/connections/index.ts index 52fe834617..afc1de2ee0 100644 --- a/packages/core/src/modules/connections/index.ts +++ b/packages/core/src/modules/connections/index.ts @@ -3,6 +3,7 @@ export * from './models' export * from './repository' export * from './services' export * from './ConnectionEvents' +export * from './protocols/trust-ping/TrustPingEvents' export * from './ConnectionsApi' export * from './DidExchangeProtocol' export * from './ConnectionsModuleConfig' diff --git a/packages/core/src/modules/connections/messages/ConnectionInvitationMessage.ts b/packages/core/src/modules/connections/messages/ConnectionInvitationMessage.ts index 64c18efce4..8cc612dea6 100644 --- a/packages/core/src/modules/connections/messages/ConnectionInvitationMessage.ts +++ b/packages/core/src/modules/connections/messages/ConnectionInvitationMessage.ts @@ -33,6 +33,8 @@ export interface DIDInvitationOptions { * @see https://github.com/hyperledger/aries-rfcs/blob/master/features/0160-connection-protocol/README.md#0-invitation-to-connect */ export class ConnectionInvitationMessage extends DidCommV1Message { + public readonly allowDidSovPrefix = true + /** * Create new ConnectionInvitationMessage instance. * @param options @@ -107,8 +109,14 @@ export class ConnectionInvitationMessage extends DidCommV1Message { * @param domain domain name to use for invitation url * @returns invitation url with base64 encoded invitation */ - public toUrl({ domain, useLegacyDidSovPrefix = false }: { domain: string; useLegacyDidSovPrefix?: boolean }) { - const invitationJson = this.toJSON({ useLegacyDidSovPrefix }) + public toUrl({ + domain, + useDidSovPrefixWhereAllowed = false, + }: { + domain: string + useDidSovPrefixWhereAllowed?: boolean + }) { + const invitationJson = this.toJSON({ useDidSovPrefixWhereAllowed }) const encodedInvitation = JsonEncoder.toBase64URL(invitationJson) const invitationUrl = `${domain}?c_i=${encodedInvitation}` diff --git a/packages/core/src/modules/connections/messages/ConnectionProblemReportMessage.ts b/packages/core/src/modules/connections/messages/ConnectionProblemReportMessage.ts index 8c84ad9981..27a6f81671 100644 --- a/packages/core/src/modules/connections/messages/ConnectionProblemReportMessage.ts +++ b/packages/core/src/modules/connections/messages/ConnectionProblemReportMessage.ts @@ -9,6 +9,8 @@ export type ConnectionProblemReportMessageOptions = ProblemReportMessageOptions * @see https://github.com/hyperledger/aries-rfcs/blob/main/features/0035-report-problem/README.md */ export class ConnectionProblemReportMessage extends ProblemReportMessage { + public readonly allowDidSovPrefix = true + /** * Create new ConnectionProblemReportMessage instance. * @param options diff --git a/packages/core/src/modules/connections/messages/ConnectionRequestMessage.ts b/packages/core/src/modules/connections/messages/ConnectionRequestMessage.ts index fd01e59844..8cc0eae339 100644 --- a/packages/core/src/modules/connections/messages/ConnectionRequestMessage.ts +++ b/packages/core/src/modules/connections/messages/ConnectionRequestMessage.ts @@ -21,6 +21,8 @@ export interface ConnectionRequestMessageOptions { * @see https://github.com/hyperledger/aries-rfcs/blob/master/features/0160-connection-protocol/README.md#1-connection-request */ export class ConnectionRequestMessage extends DidCommV1Message { + public readonly allowDidSovPrefix = true + /** * Create new ConnectionRequestMessage instance. * @param options diff --git a/packages/core/src/modules/connections/messages/ConnectionResponseMessage.ts b/packages/core/src/modules/connections/messages/ConnectionResponseMessage.ts index 386873ebf6..765fc9bd81 100644 --- a/packages/core/src/modules/connections/messages/ConnectionResponseMessage.ts +++ b/packages/core/src/modules/connections/messages/ConnectionResponseMessage.ts @@ -17,6 +17,8 @@ export interface ConnectionResponseMessageOptions { * @see https://github.com/hyperledger/aries-rfcs/blob/master/features/0160-connection-protocol/README.md#2-connection-response */ export class ConnectionResponseMessage extends DidCommV1Message { + public readonly allowDidSovPrefix = true + /** * Create new ConnectionResponseMessage instance. * @param options diff --git a/packages/core/src/modules/connections/messages/DidExchangeRequestMessage.ts b/packages/core/src/modules/connections/messages/DidExchangeRequestMessage.ts index 9d750f8c9c..dbb71783ea 100644 --- a/packages/core/src/modules/connections/messages/DidExchangeRequestMessage.ts +++ b/packages/core/src/modules/connections/messages/DidExchangeRequestMessage.ts @@ -35,7 +35,6 @@ export class DidExchangeRequestMessage extends DidCommV1Message { this.did = options.did this.setThread({ - threadId: this.id, parentThreadId: options.parentThreadId, }) } diff --git a/packages/core/src/modules/connections/models/did/DidDoc.ts b/packages/core/src/modules/connections/models/did/DidDoc.ts index 896d314221..4d86fa7e19 100644 --- a/packages/core/src/modules/connections/models/did/DidDoc.ts +++ b/packages/core/src/modules/connections/models/did/DidDoc.ts @@ -1,6 +1,6 @@ -import type { DidDocumentService } from '../../../dids/domain/service' import type { Authentication } from './authentication' import type { PublicKey } from './publicKey' +import type { DidDocumentService } from '../../../dids/domain/service' import { Expose } from 'class-transformer' import { Equals, IsArray, IsString, ValidateNested } from 'class-validator' diff --git a/packages/core/src/modules/connections/models/did/authentication/Authentication.ts b/packages/core/src/modules/connections/models/did/authentication/Authentication.ts index 76c2cef823..41ff0bc5d5 100644 --- a/packages/core/src/modules/connections/models/did/authentication/Authentication.ts +++ b/packages/core/src/modules/connections/models/did/authentication/Authentication.ts @@ -1,5 +1,5 @@ import type { PublicKey } from '../publicKey/PublicKey' export abstract class Authentication { - abstract publicKey: PublicKey + public abstract publicKey: PublicKey } diff --git a/packages/core/src/modules/connections/protocols/trust-ping/TrustPingEvents.ts b/packages/core/src/modules/connections/protocols/trust-ping/TrustPingEvents.ts new file mode 100644 index 0000000000..ae7fa7a915 --- /dev/null +++ b/packages/core/src/modules/connections/protocols/trust-ping/TrustPingEvents.ts @@ -0,0 +1,49 @@ +import type { + TrustPingMessage as V1TrustPingMessage, + TrustPingResponseMessage as V1TrustPingResponseMessage, +} from './v1' +import type { + TrustPingMessage as V2TrustPingMessage, + TrustPingResponseMessage as V2TrustPingResponseMessage, +} from './v2' +import type { BaseEvent } from '../../../../agent/Events' +import type { ConnectionRecord } from '../../repository/ConnectionRecord' + +export enum TrustPingEventTypes { + TrustPingReceivedEvent = 'TrustPingReceivedEvent', + V2TrustPingReceivedEvent = 'V2TrustPingReceivedEvent', + TrustPingResponseReceivedEvent = 'TrustPingResponseReceivedEvent', + V2TrustPingResponseReceivedEvent = 'V2TrustPingResponseReceivedEvent', +} + +export interface TrustPingReceivedEvent extends BaseEvent { + type: typeof TrustPingEventTypes.TrustPingReceivedEvent + payload: { + connectionRecord?: ConnectionRecord | null + message: V1TrustPingMessage + } +} + +export interface TrustPingResponseReceivedEvent extends BaseEvent { + type: typeof TrustPingEventTypes.TrustPingResponseReceivedEvent + payload: { + connectionRecord?: ConnectionRecord | null + message: V1TrustPingResponseMessage + } +} + +export interface V2TrustPingReceivedEvent extends BaseEvent { + type: typeof TrustPingEventTypes.V2TrustPingReceivedEvent + payload: { + connectionRecord?: ConnectionRecord | null + message: V2TrustPingMessage + } +} + +export interface V2TrustPingResponseReceivedEvent extends BaseEvent { + type: typeof TrustPingEventTypes.V2TrustPingResponseReceivedEvent + payload: { + connectionRecord?: ConnectionRecord | null + message: V2TrustPingResponseMessage + } +} diff --git a/packages/core/src/modules/connections/protocols/trust-ping/v1/V1TrustPingService.ts b/packages/core/src/modules/connections/protocols/trust-ping/v1/V1TrustPingService.ts index aef8ca6996..5668a8299a 100644 --- a/packages/core/src/modules/connections/protocols/trust-ping/v1/V1TrustPingService.ts +++ b/packages/core/src/modules/connections/protocols/trust-ping/v1/V1TrustPingService.ts @@ -1,59 +1,52 @@ +import type { TrustPingMessage } from './messages' import type { InboundMessageContext } from '../../../../../agent/models/InboundMessageContext' import type { ConnectionRecord } from '../../../repository/ConnectionRecord' +import type { TrustPingReceivedEvent, TrustPingResponseReceivedEvent } from '../TrustPingEvents' -import { Dispatcher } from '../../../../../agent/Dispatcher' -import { InjectionSymbols } from '../../../../../constants' -import { Logger } from '../../../../../logger' -import { inject, injectable } from '../../../../../plugins' -import { ConnectionService } from '../../../services/ConnectionService' +import { EventEmitter } from '../../../../../agent/EventEmitter' +import { OutboundMessageContext } from '../../../../../agent/models' +import { injectable } from '../../../../../plugins' +import { TrustPingEventTypes } from '../TrustPingEvents' -import { TrustPingMessageHandler, TrustPingResponseMessageHandler } from './handlers' -import { TrustPingMessage } from './messages' -import { TrustPingResponseMessage } from './messages/TrustPingResponseMessage' +import { TrustPingResponseMessage } from './messages' @injectable() export class V1TrustPingService { - private logger: Logger - private dispatcher: Dispatcher - private connectionService: ConnectionService - - public constructor( - @inject(InjectionSymbols.Logger) logger: Logger, - dispatcher: Dispatcher, - connectionService: ConnectionService - ) { - this.logger = logger - this.dispatcher = dispatcher - this.connectionService = connectionService - - this.registerHandlers() + private eventEmitter: EventEmitter + + public constructor(eventEmitter: EventEmitter) { + this.eventEmitter = eventEmitter } - public createPing(): TrustPingMessage { - return new TrustPingMessage({ - responseRequested: true, + public processPing({ message, agentContext }: InboundMessageContext, connection: ConnectionRecord) { + this.eventEmitter.emit(agentContext, { + type: TrustPingEventTypes.TrustPingReceivedEvent, + payload: { + connectionRecord: connection, + message: message, + }, }) - } - public processPing({ message }: InboundMessageContext, connection: ConnectionRecord) { - this.logger.info(`Send Trust Ping message to connection ${connection.id}.`) if (message.responseRequested) { const response = new TrustPingResponseMessage({ - threadId: message.id, + threadId: message.threadId, }) - return response + return new OutboundMessageContext(response, { agentContext, connection }) } } - // eslint-disable-next-line @typescript-eslint/no-unused-vars public processPingResponse(inboundMessage: InboundMessageContext) { - this.logger.info('Trust Ping Response message received.') - // TODO: handle ping response message - } + const { agentContext, message } = inboundMessage - protected registerHandlers() { - this.dispatcher.registerMessageHandler(new TrustPingMessageHandler(this, this.connectionService)) - this.dispatcher.registerMessageHandler(new TrustPingResponseMessageHandler(this)) + const connection = inboundMessage.assertReadyConnection() + + this.eventEmitter.emit(agentContext, { + type: TrustPingEventTypes.TrustPingResponseReceivedEvent, + payload: { + connectionRecord: connection, + message: message, + }, + }) } } diff --git a/packages/core/src/modules/connections/protocols/trust-ping/v1/handlers/TrustPingMessageHandler.ts b/packages/core/src/modules/connections/protocols/trust-ping/v1/handlers/TrustPingMessageHandler.ts index 4e676823ff..7e55f8d684 100644 --- a/packages/core/src/modules/connections/protocols/trust-ping/v1/handlers/TrustPingMessageHandler.ts +++ b/packages/core/src/modules/connections/protocols/trust-ping/v1/handlers/TrustPingMessageHandler.ts @@ -2,7 +2,6 @@ import type { MessageHandler, MessageHandlerInboundMessage } from '../../../../. import type { ConnectionService } from '../../../../services/ConnectionService' import type { V1TrustPingService } from '../V1TrustPingService' -import { OutboundMessageContext } from '../../../../../../agent/models' import { AriesFrameworkError } from '../../../../../../error' import { DidExchangeState } from '../../../../models' import { TrustPingMessage } from '../messages' @@ -29,12 +28,6 @@ export class TrustPingMessageHandler implements MessageHandler { await this.connectionService.updateState(messageContext.agentContext, connection, DidExchangeState.Completed) } - const message = await this.trustPingService.processPing(messageContext, connection) - if (message) { - return new OutboundMessageContext(message, { - agentContext: messageContext.agentContext, - connection, - }) - } + return this.trustPingService.processPing(messageContext, connection) } } diff --git a/packages/core/src/modules/connections/protocols/trust-ping/v1/messages/TrustPingMessage.ts b/packages/core/src/modules/connections/protocols/trust-ping/v1/messages/TrustPingMessage.ts index 7ec26d79b7..e76a4af2eb 100644 --- a/packages/core/src/modules/connections/protocols/trust-ping/v1/messages/TrustPingMessage.ts +++ b/packages/core/src/modules/connections/protocols/trust-ping/v1/messages/TrustPingMessage.ts @@ -19,6 +19,8 @@ export interface TrustPingMessageOptions { * @see https://github.com/hyperledger/aries-rfcs/blob/master/features/0048-trust-ping/README.md#messages */ export class TrustPingMessage extends DidCommV1Message { + public readonly allowDidSovPrefix = true + /** * Create new TrustPingMessage instance. * responseRequested will be true if not passed diff --git a/packages/core/src/modules/connections/protocols/trust-ping/v1/messages/TrustPingResponseMessage.ts b/packages/core/src/modules/connections/protocols/trust-ping/v1/messages/TrustPingResponseMessage.ts index 9460546e14..3e03a6c2ad 100644 --- a/packages/core/src/modules/connections/protocols/trust-ping/v1/messages/TrustPingResponseMessage.ts +++ b/packages/core/src/modules/connections/protocols/trust-ping/v1/messages/TrustPingResponseMessage.ts @@ -18,6 +18,8 @@ export interface TrustPingResponseMessageOptions { * @see https://github.com/hyperledger/aries-rfcs/blob/master/features/0048-trust-ping/README.md#messages */ export class TrustPingResponseMessage extends DidCommV1Message { + public readonly allowDidSovPrefix = true + /** * Create new TrustPingResponseMessage instance. * responseRequested will be true if not passed diff --git a/packages/core/src/modules/connections/protocols/trust-ping/v2/V2TrustPingService.ts b/packages/core/src/modules/connections/protocols/trust-ping/v2/V2TrustPingService.ts index c4495bd11f..78e6600670 100644 --- a/packages/core/src/modules/connections/protocols/trust-ping/v2/V2TrustPingService.ts +++ b/packages/core/src/modules/connections/protocols/trust-ping/v2/V2TrustPingService.ts @@ -1,15 +1,14 @@ import type { InboundMessageContext } from '../../../../../agent/models/InboundMessageContext' -import type { PingReceivedEvent, PingResponseReceivedEvent } from '../../../ConnectionEvents' import type { ConnectionRecord } from '../../../repository' +import type { V2TrustPingReceivedEvent, V2TrustPingResponseReceivedEvent } from '../TrustPingEvents' import { Dispatcher } from '../../../../../agent/Dispatcher' import { EventEmitter } from '../../../../../agent/EventEmitter' import { InjectionSymbols } from '../../../../../constants' import { Logger } from '../../../../../logger' import { inject, injectable } from '../../../../../plugins' -import { TrustPingEventTypes } from '../../../ConnectionEvents' +import { TrustPingEventTypes } from '../TrustPingEvents' -import { TrustPingMessageHandler, TrustPingResponseMessageHandler } from './handlers' import { TrustPingMessage } from './messages/TrustPingMessage' import { TrustPingResponseMessage } from './messages/TrustPingResponseMessage' @@ -27,8 +26,6 @@ export class V2TrustPingService { this.logger = logger this.dispatcher = dispatcher this.eventEmitter = eventEmitter - - this.registerHandlers() } public createPing(connection: ConnectionRecord): TrustPingMessage { @@ -45,10 +42,10 @@ export class V2TrustPingService { public processPing({ agentContext, message }: InboundMessageContext) { this.logger.info('Trust Ping message received.', message) - this.eventEmitter.emit(agentContext, { - type: TrustPingEventTypes.PingReceived, + this.eventEmitter.emit(agentContext, { + type: TrustPingEventTypes.V2TrustPingReceivedEvent, payload: { - from: message.from, + message: message, }, }) @@ -62,19 +59,15 @@ export class V2TrustPingService { } } - public processPingResponse({ agentContext, message }: InboundMessageContext) { + public processPingResponse({ agentContext, message, connection }: InboundMessageContext) { this.logger.info('Trust Ping Response message received.', message) - this.eventEmitter.emit(agentContext, { - type: TrustPingEventTypes.PingResponseReceived, + this.eventEmitter.emit(agentContext, { + type: TrustPingEventTypes.V2TrustPingResponseReceivedEvent, payload: { - from: message.from, + message: message, + connectionRecord: connection, }, }) } - - protected registerHandlers() { - this.dispatcher.registerMessageHandler(new TrustPingMessageHandler(this)) - this.dispatcher.registerMessageHandler(new TrustPingResponseMessageHandler(this)) - } } diff --git a/packages/core/src/modules/connections/protocols/trust-ping/v2/handlers/TrustPingMessageHandler.ts b/packages/core/src/modules/connections/protocols/trust-ping/v2/handlers/TrustPingMessageHandler.ts index b83d6af274..06f2cf4c88 100644 --- a/packages/core/src/modules/connections/protocols/trust-ping/v2/handlers/TrustPingMessageHandler.ts +++ b/packages/core/src/modules/connections/protocols/trust-ping/v2/handlers/TrustPingMessageHandler.ts @@ -1,4 +1,5 @@ import type { MessageHandler, MessageHandlerInboundMessage } from '../../../../../../agent/MessageHandler' +import type { ConnectionService } from '../../../../services/ConnectionService' import type { V2TrustPingService } from '../V2TrustPingService' import { OutboundMessageContext } from '../../../../../../agent/models' @@ -6,14 +7,17 @@ import { TrustPingMessage } from '../messages/TrustPingMessage' export class TrustPingMessageHandler implements MessageHandler { private v2TrustPingService: V2TrustPingService + private connectionService: ConnectionService public supportedMessages = [TrustPingMessage] - public constructor(trustPingService: V2TrustPingService) { + public constructor(trustPingService: V2TrustPingService, connectionService: ConnectionService) { this.v2TrustPingService = trustPingService + this.connectionService = connectionService } public async handle(messageContext: MessageHandlerInboundMessage) { const message = await this.v2TrustPingService.processPing(messageContext) + if (message) { return new OutboundMessageContext(message, { agentContext: messageContext.agentContext, diff --git a/packages/core/src/modules/connections/repository/ConnectionRecord.ts b/packages/core/src/modules/connections/repository/ConnectionRecord.ts index ccbb89f714..ec26d97735 100644 --- a/packages/core/src/modules/connections/repository/ConnectionRecord.ts +++ b/packages/core/src/modules/connections/repository/ConnectionRecord.ts @@ -1,6 +1,6 @@ +import type { ConnectionMetadata } from './ConnectionMetadataTypes' import type { TagsBase } from '../../../storage/BaseRecord' import type { ConnectionType } from '../models/ConnectionType' -import type { ConnectionMetadata } from './ConnectionMetadataTypes' import { AriesFrameworkError } from '../../../error' import { BaseRecord } from '../../../storage/BaseRecord' diff --git a/packages/core/src/modules/connections/services/ConnectionService.ts b/packages/core/src/modules/connections/services/ConnectionService.ts index bdc56433f3..4f3e628fd0 100644 --- a/packages/core/src/modules/connections/services/ConnectionService.ts +++ b/packages/core/src/modules/connections/services/ConnectionService.ts @@ -121,7 +121,7 @@ export class ConnectionService { }) connectionRequest.setThread({ - threadId: connectionRequest.id, + threadId: connectionRequest.threadId, parentThreadId: outOfBandInvitation.id, }) @@ -137,7 +137,7 @@ export class ConnectionService { outOfBandId: outOfBandRecord.id, invitationDid, imageUrl: outOfBandInvitation.imageUrl, - threadId: connectionRequest.id, + threadId: connectionRequest.threadId, }) await this.updateState(agentContext, connectionRecord, DidExchangeState.RequestSent) @@ -450,8 +450,8 @@ export class ConnectionService { previousSentMessage, previousReceivedMessage, }: { - previousSentMessage?: DidCommV1Message - previousReceivedMessage?: DidCommV1Message + previousSentMessage?: DidCommV1Message | null + previousReceivedMessage?: DidCommV1Message | null } = {} ) { const { connection, message } = messageContext diff --git a/packages/core/src/modules/credentials/CredentialEvents.ts b/packages/core/src/modules/credentials/CredentialEvents.ts index d9324d2bfe..2a60193d51 100644 --- a/packages/core/src/modules/credentials/CredentialEvents.ts +++ b/packages/core/src/modules/credentials/CredentialEvents.ts @@ -1,6 +1,6 @@ -import type { BaseEvent } from '../../agent/Events' import type { CredentialState } from './models/CredentialState' import type { CredentialExchangeRecord } from './repository/CredentialExchangeRecord' +import type { BaseEvent } from '../../agent/Events' export enum CredentialEventTypes { CredentialStateChanged = 'CredentialStateChanged', diff --git a/packages/core/src/modules/credentials/CredentialsApi.ts b/packages/core/src/modules/credentials/CredentialsApi.ts index 5b1b2f970f..2da13ceb69 100644 --- a/packages/core/src/modules/credentials/CredentialsApi.ts +++ b/packages/core/src/modules/credentials/CredentialsApi.ts @@ -1,26 +1,26 @@ -import type { DidCommV1Message } from '../../didcomm/versions/v1' -import type { Query } from '../../storage/StorageService' -import type { CFsFromCPs, DeleteCredentialOptions } from './CredentialProtocolOptions' import type { AcceptCredentialOptions, AcceptCredentialOfferOptions, AcceptCredentialProposalOptions, AcceptCredentialRequestOptions, - CreateOfferOptions, + CreateCredentialOfferOptions, FindCredentialMessageReturn, FindCredentialOfferMessageReturn, FindCredentialProposalMessageReturn, FindCredentialRequestMessageReturn, - GetFormatDataReturn, + GetCredentialFormatDataReturn, NegotiateCredentialOfferOptions, NegotiateCredentialProposalOptions, OfferCredentialOptions, ProposeCredentialOptions, SendCredentialProblemReportOptions, - CredentialProtocolMap, + DeleteCredentialOptions, } from './CredentialsApiOptions' import type { CredentialProtocol } from './protocol/CredentialProtocol' +import type { CredentialFormatsFromProtocols } from './protocol/CredentialProtocolOptions' import type { CredentialExchangeRecord } from './repository/CredentialExchangeRecord' +import type { DidCommV1Message } from '../../didcomm/versions/v1' +import type { Query } from '../../storage/StorageService' import { AgentContext } from '../../agent' import { MessageSender } from '../../agent/MessageSender' @@ -63,7 +63,7 @@ export interface CredentialsApi { acceptCredential(options: AcceptCredentialOptions): Promise // out of band - createOffer(options: CreateOfferOptions): Promise<{ + createOffer(options: CreateCredentialOfferOptions): Promise<{ message: DidCommV1Message credentialRecord: CredentialExchangeRecord }> @@ -77,7 +77,7 @@ export interface CredentialsApi { findById(credentialRecordId: string): Promise deleteById(credentialRecordId: string, options?: DeleteCredentialOptions): Promise update(credentialRecord: CredentialExchangeRecord): Promise - getFormatData(credentialRecordId: string): Promise>> + getFormatData(credentialRecordId: string): Promise>> // DidComm Message Records findProposalMessage(credentialExchangeId: string): Promise> @@ -89,7 +89,7 @@ export interface CredentialsApi { @injectable() export class CredentialsApi implements CredentialsApi { /** - * Configuration for the connections module + * Configuration for the credentials module */ public readonly config: CredentialsModuleConfig @@ -100,7 +100,6 @@ export class CredentialsApi implements Credent private didCommMessageRepository: DidCommMessageRepository private routingService: RoutingService private logger: Logger - private credentialProtocolMap: CredentialProtocolMap public constructor( messageSender: MessageSender, @@ -123,58 +122,49 @@ export class CredentialsApi implements Credent this.didCommMessageRepository = didCommMessageRepository this.logger = logger this.config = config - - // Dynamically build service map. This will be extracted once services are registered dynamically - this.credentialProtocolMap = config.credentialProtocols.reduce( - (protocolMap, service) => ({ - ...protocolMap, - [service.version]: service, - }), - {} - ) as CredentialProtocolMap } - private getProtocol>(protocolVersion: PVT) { - if (!this.credentialProtocolMap[protocolVersion]) { + private getProtocol(protocolVersion: PVT): CredentialProtocol { + const credentialProtocol = this.config.credentialProtocols.find((protocol) => protocol.version === protocolVersion) + + if (!credentialProtocol) { throw new AriesFrameworkError(`No credential protocol registered for protocol version ${protocolVersion}`) } - return this.credentialProtocolMap[protocolVersion] + return credentialProtocol } /** * Initiate a new credential exchange as holder by sending a credential proposal message - * to the connection with the specified credential options + * to the connection with the specified connection id. * * @param options configuration to use for the proposal * @returns Credential exchange record associated with the sent proposal message */ public async proposeCredential(options: ProposeCredentialOptions): Promise { - const service = this.getProtocol(options.protocolVersion) + const protocol = this.getProtocol(options.protocolVersion) - this.logger.debug(`Got a credentialProtocol object for version ${options.protocolVersion}`) + const connectionRecord = await this.connectionService.getById(this.agentContext, options.connectionId) - const connection = await this.connectionService.getById(this.agentContext, options.connectionId) + // Assert + connectionRecord.assertReady() // will get back a credential record -> map to Credential Exchange Record - const { credentialRecord, message } = await service.createProposal(this.agentContext, { - connection, + const { credentialRecord, message } = await protocol.createProposal(this.agentContext, { + connectionRecord, credentialFormats: options.credentialFormats, comment: options.comment, autoAcceptCredential: options.autoAcceptCredential, }) - this.logger.debug('We have a message (sending outbound): ', message) - // send the message here const outboundMessageContext = new OutboundMessageContext(message, { agentContext: this.agentContext, - connection, + connection: connectionRecord, associatedRecord: credentialRecord, }) - this.logger.debug('In proposeCredential: Send Proposal to Issuer') await this.messageSender.sendMessage(outboundMessageContext) return credentialRecord } @@ -196,11 +186,15 @@ export class CredentialsApi implements Credent ) } - // with version we can get the Service - const service = this.getProtocol(credentialRecord.protocolVersion) + // with version we can get the protocol + const protocol = this.getProtocol(credentialRecord.protocolVersion) + const connectionRecord = await this.connectionService.getById(this.agentContext, credentialRecord.connectionId) + + // Assert + connectionRecord.assertReady() // will get back a credential record -> map to Credential Exchange Record - const { message } = await service.acceptProposal(this.agentContext, { + const { message } = await protocol.acceptProposal(this.agentContext, { credentialRecord, credentialFormats: options.credentialFormats, comment: options.comment, @@ -208,10 +202,9 @@ export class CredentialsApi implements Credent }) // send the message - const connection = await this.connectionService.getById(this.agentContext, credentialRecord.connectionId) const outboundMessageContext = new OutboundMessageContext(message, { agentContext: this.agentContext, - connection, + connection: connectionRecord, associatedRecord: credentialRecord, }) await this.messageSender.sendMessage(outboundMessageContext) @@ -237,9 +230,9 @@ export class CredentialsApi implements Credent } // with version we can get the Service - const service = this.getProtocol(credentialRecord.protocolVersion) + const protocol = this.getProtocol(credentialRecord.protocolVersion) - const { message } = await service.negotiateProposal(this.agentContext, { + const { message } = await protocol.negotiateProposal(this.agentContext, { credentialRecord, credentialFormats: options.credentialFormats, comment: options.comment, @@ -265,22 +258,22 @@ export class CredentialsApi implements Credent * @returns Credential exchange record associated with the sent credential offer message */ public async offerCredential(options: OfferCredentialOptions): Promise { - const connection = await this.connectionService.getById(this.agentContext, options.connectionId) - const service = this.getProtocol(options.protocolVersion) + const connectionRecord = await this.connectionService.getById(this.agentContext, options.connectionId) + const protocol = this.getProtocol(options.protocolVersion) this.logger.debug(`Got a credentialProtocol object for version ${options.protocolVersion}`) - const { message, credentialRecord } = await service.createOffer(this.agentContext, { + const { message, credentialRecord } = await protocol.createOffer(this.agentContext, { credentialFormats: options.credentialFormats, autoAcceptCredential: options.autoAcceptCredential, comment: options.comment, - connection, + connectionRecord, }) this.logger.debug('Offer Message successfully created; message= ', message) const outboundMessageContext = new OutboundMessageContext(message, { agentContext: this.agentContext, - connection, + connection: connectionRecord, associatedRecord: credentialRecord, }) await this.messageSender.sendMessage(outboundMessageContext) @@ -298,16 +291,19 @@ export class CredentialsApi implements Credent public async acceptOffer(options: AcceptCredentialOfferOptions): Promise { const credentialRecord = await this.getById(options.credentialRecordId) - const service = this.getProtocol(credentialRecord.protocolVersion) + const protocol = this.getProtocol(credentialRecord.protocolVersion) - this.logger.debug(`Got a credentialProtocol object for this version; version = ${service.version}`) - const offerMessage = await service.findOfferMessage(this.agentContext, credentialRecord.id) + this.logger.debug(`Got a credentialProtocol object for this version; version = ${protocol.version}`) + const offerMessage = await protocol.findOfferMessage(this.agentContext, credentialRecord.id) // Use connection if present if (credentialRecord.connectionId) { - const connection = await this.connectionService.getById(this.agentContext, credentialRecord.connectionId) + const connectionRecord = await this.connectionService.getById(this.agentContext, credentialRecord.connectionId) - const { message } = await service.acceptOffer(this.agentContext, { + // Assert + connectionRecord.assertReady() + + const { message } = await protocol.acceptOffer(this.agentContext, { credentialRecord, credentialFormats: options.credentialFormats, comment: options.comment, @@ -316,7 +312,7 @@ export class CredentialsApi implements Credent const outboundMessageContext = new OutboundMessageContext(message, { agentContext: this.agentContext, - connection, + connection: connectionRecord, associatedRecord: credentialRecord, }) await this.messageSender.sendMessage(outboundMessageContext) @@ -334,7 +330,7 @@ export class CredentialsApi implements Credent }) const recipientService = offerMessage.service - const { message } = await service.acceptOffer(this.agentContext, { + const { message } = await protocol.acceptOffer(this.agentContext, { credentialRecord, credentialFormats: options.credentialFormats, comment: options.comment, @@ -375,8 +371,8 @@ export class CredentialsApi implements Credent credentialRecord.assertState(CredentialState.OfferReceived) // with version we can get the Service - const service = this.getProtocol(credentialRecord.protocolVersion) - await service.updateState(this.agentContext, credentialRecord, CredentialState.Declined) + const protocol = this.getProtocol(credentialRecord.protocolVersion) + await protocol.updateState(this.agentContext, credentialRecord, CredentialState.Declined) return credentialRecord } @@ -384,24 +380,28 @@ export class CredentialsApi implements Credent public async negotiateOffer(options: NegotiateCredentialOfferOptions): Promise { const credentialRecord = await this.getById(options.credentialRecordId) - const service = this.getProtocol(credentialRecord.protocolVersion) - const { message } = await service.negotiateOffer(this.agentContext, { - credentialFormats: options.credentialFormats, - credentialRecord, - comment: options.comment, - autoAcceptCredential: options.autoAcceptCredential, - }) - if (!credentialRecord.connectionId) { throw new AriesFrameworkError( `No connection id for credential record ${credentialRecord.id} not found. Connection-less issuance does not support negotiation` ) } - const connection = await this.connectionService.getById(this.agentContext, credentialRecord.connectionId) + const connectionRecord = await this.connectionService.getById(this.agentContext, credentialRecord.connectionId) + + // Assert + connectionRecord.assertReady() + + const protocol = this.getProtocol(credentialRecord.protocolVersion) + const { message } = await protocol.negotiateOffer(this.agentContext, { + credentialFormats: options.credentialFormats, + credentialRecord, + comment: options.comment, + autoAcceptCredential: options.autoAcceptCredential, + }) + const outboundMessageContext = new OutboundMessageContext(message, { agentContext: this.agentContext, - connection, + connection: connectionRecord, associatedRecord: credentialRecord, }) await this.messageSender.sendMessage(outboundMessageContext) @@ -415,14 +415,14 @@ export class CredentialsApi implements Credent * @param options The credential options to use for the offer * @returns The credential record and credential offer message */ - public async createOffer(options: CreateOfferOptions): Promise<{ + public async createOffer(options: CreateCredentialOfferOptions): Promise<{ message: DidCommV1Message credentialRecord: CredentialExchangeRecord }> { - const service = this.getProtocol(options.protocolVersion) + const protocol = this.getProtocol(options.protocolVersion) this.logger.debug(`Got a credentialProtocol object for version ${options.protocolVersion}`) - const { message, credentialRecord } = await service.createOffer(this.agentContext, { + const { message, credentialRecord } = await protocol.createOffer(this.agentContext, { credentialFormats: options.credentialFormats, comment: options.comment, autoAcceptCredential: options.autoAcceptCredential, @@ -444,11 +444,11 @@ export class CredentialsApi implements Credent const credentialRecord = await this.getById(options.credentialRecordId) // with version we can get the Service - const service = this.getProtocol(credentialRecord.protocolVersion) + const protocol = this.getProtocol(credentialRecord.protocolVersion) this.logger.debug(`Got a credentialProtocol object for version ${credentialRecord.protocolVersion}`) - const { message } = await service.acceptRequest(this.agentContext, { + const { message } = await protocol.acceptRequest(this.agentContext, { credentialRecord, credentialFormats: options.credentialFormats, comment: options.comment, @@ -456,8 +456,8 @@ export class CredentialsApi implements Credent }) this.logger.debug('We have a credential message (sending outbound): ', message) - const requestMessage = await service.findRequestMessage(this.agentContext, credentialRecord.id) - const offerMessage = await service.findOfferMessage(this.agentContext, credentialRecord.id) + const requestMessage = await protocol.findRequestMessage(this.agentContext, credentialRecord.id) + const offerMessage = await protocol.findOfferMessage(this.agentContext, credentialRecord.id) // Use connection if present if (credentialRecord.connectionId) { @@ -516,16 +516,16 @@ export class CredentialsApi implements Credent const credentialRecord = await this.getById(options.credentialRecordId) // with version we can get the Service - const service = this.getProtocol(credentialRecord.protocolVersion) + const protocol = this.getProtocol(credentialRecord.protocolVersion) this.logger.debug(`Got a credentialProtocol object for version ${credentialRecord.protocolVersion}`) - const { message } = await service.acceptCredential(this.agentContext, { + const { message } = await protocol.acceptCredential(this.agentContext, { credentialRecord, }) - const requestMessage = await service.findRequestMessage(this.agentContext, credentialRecord.id) - const credentialMessage = await service.findCredentialMessage(this.agentContext, credentialRecord.id) + const requestMessage = await protocol.findRequestMessage(this.agentContext, credentialRecord.id) + const credentialMessage = await protocol.findCredentialMessage(this.agentContext, credentialRecord.id) if (credentialRecord.connectionId) { const connection = await this.connectionService.getById(this.agentContext, credentialRecord.connectionId) @@ -550,7 +550,7 @@ export class CredentialsApi implements Credent serviceParams: { service: recipientService.resolvedDidCommService, senderKey: ourService.resolvedDidCommService.recipientKeys[0], - returnRoute: true, + returnRoute: false, // hard wire to be false since it's the end of the protocol so not needed here }, }) ) @@ -578,12 +578,15 @@ export class CredentialsApi implements Credent } const connection = await this.connectionService.getById(this.agentContext, credentialRecord.connectionId) - const service = this.getProtocol(credentialRecord.protocolVersion) - const problemReportMessage = service.createProblemReport(this.agentContext, { message: options.message }) - problemReportMessage.setThread({ + const protocol = this.getProtocol(credentialRecord.protocolVersion) + const { message } = await protocol.createProblemReport(this.agentContext, { + description: options.description, + credentialRecord, + }) + message.setThread({ threadId: credentialRecord.threadId, }) - const outboundMessageContext = new OutboundMessageContext(problemReportMessage, { + const outboundMessageContext = new OutboundMessageContext(message, { agentContext: this.agentContext, connection, associatedRecord: credentialRecord, @@ -593,11 +596,13 @@ export class CredentialsApi implements Credent return credentialRecord } - public async getFormatData(credentialRecordId: string): Promise>> { + public async getFormatData( + credentialRecordId: string + ): Promise>> { const credentialRecord = await this.getById(credentialRecordId) - const service = this.getProtocol(credentialRecord.protocolVersion) + const protocol = this.getProtocol(credentialRecord.protocolVersion) - return service.getFormatData(this.agentContext, credentialRecordId) + return protocol.getFormatData(this.agentContext, credentialRecordId) } /** @@ -648,8 +653,8 @@ export class CredentialsApi implements Credent */ public async deleteById(credentialId: string, options?: DeleteCredentialOptions) { const credentialRecord = await this.getById(credentialId) - const service = this.getProtocol(credentialRecord.protocolVersion) - return service.delete(this.agentContext, credentialRecord, options) + const protocol = this.getProtocol(credentialRecord.protocolVersion) + return protocol.delete(this.agentContext, credentialRecord, options) } /** @@ -662,27 +667,33 @@ export class CredentialsApi implements Credent } public async findProposalMessage(credentialExchangeId: string): Promise> { - const service = await this.getServiceForCredentialExchangeId(credentialExchangeId) + const protocol = await this.getServiceForCredentialExchangeId(credentialExchangeId) - return service.findProposalMessage(this.agentContext, credentialExchangeId) + return protocol.findProposalMessage( + this.agentContext, + credentialExchangeId + ) as FindCredentialProposalMessageReturn } public async findOfferMessage(credentialExchangeId: string): Promise> { - const service = await this.getServiceForCredentialExchangeId(credentialExchangeId) + const protocol = await this.getServiceForCredentialExchangeId(credentialExchangeId) - return service.findOfferMessage(this.agentContext, credentialExchangeId) + return protocol.findOfferMessage(this.agentContext, credentialExchangeId) as FindCredentialOfferMessageReturn } public async findRequestMessage(credentialExchangeId: string): Promise> { - const service = await this.getServiceForCredentialExchangeId(credentialExchangeId) + const protocol = await this.getServiceForCredentialExchangeId(credentialExchangeId) - return service.findRequestMessage(this.agentContext, credentialExchangeId) + return protocol.findRequestMessage( + this.agentContext, + credentialExchangeId + ) as FindCredentialRequestMessageReturn } public async findCredentialMessage(credentialExchangeId: string): Promise> { - const service = await this.getServiceForCredentialExchangeId(credentialExchangeId) + const protocol = await this.getServiceForCredentialExchangeId(credentialExchangeId) - return service.findCredentialMessage(this.agentContext, credentialExchangeId) + return protocol.findCredentialMessage(this.agentContext, credentialExchangeId) as FindCredentialMessageReturn } private async getServiceForCredentialExchangeId(credentialExchangeId: string) { diff --git a/packages/core/src/modules/credentials/CredentialsApiOptions.ts b/packages/core/src/modules/credentials/CredentialsApiOptions.ts index 7eb6c7488f..19e9f17295 100644 --- a/packages/core/src/modules/credentials/CredentialsApiOptions.ts +++ b/packages/core/src/modules/credentials/CredentialsApiOptions.ts @@ -1,10 +1,14 @@ -import type { CFsFromCPs, GetFormatDataReturn } from './CredentialProtocolOptions' import type { CredentialFormatPayload } from './formats' -import type { AutoAcceptCredential } from './models/CredentialAutoAcceptType' +import type { AutoAcceptCredential } from './models' import type { CredentialProtocol } from './protocol/CredentialProtocol' +import type { + CredentialFormatsFromProtocols, + DeleteCredentialOptions, + GetCredentialFormatDataReturn, +} from './protocol/CredentialProtocolOptions' -// re-export GetFormatDataReturn type from service, as it is also used in the module -export type { GetFormatDataReturn } +// re-export GetCredentialFormatDataReturn type from protocol, as it is also used in the api +export type { GetCredentialFormatDataReturn, DeleteCredentialOptions } export type FindCredentialProposalMessageReturn = ReturnType< CPs[number]['findProposalMessage'] @@ -25,23 +29,6 @@ export type FindCredentialMessageReturn = CPs[number]['version'] -/** - * Get the service map for usage in the credentials module. Will return a type mapping of protocol version to service. - * - * @example - * ``` - * type ProtocolMap = CredentialProtocolMap<[IndyCredentialFormatService], [V1CredentialProtocol]> - * - * // equal to - * type ProtocolMap = { - * v1: V1CredentialProtocol - * } - * ``` - */ -export type CredentialProtocolMap = { - [CP in CPs[number] as CP['version']]: CredentialProtocol -} - interface BaseOptions { autoAcceptCredential?: AutoAcceptCredential comment?: string @@ -53,7 +40,7 @@ interface BaseOptions { export interface ProposeCredentialOptions extends BaseOptions { connectionId: string protocolVersion: CredentialProtocolVersionType - credentialFormats: CredentialFormatPayload, 'createProposal'> + credentialFormats: CredentialFormatPayload, 'createProposal'> } /** @@ -64,7 +51,7 @@ export interface ProposeCredentialOptions extends BaseOptions { credentialRecordId: string - credentialFormats?: CredentialFormatPayload, 'acceptProposal'> + credentialFormats?: CredentialFormatPayload, 'acceptProposal'> } /** @@ -73,23 +60,24 @@ export interface AcceptCredentialProposalOptions extends BaseOptions { credentialRecordId: string - credentialFormats: CredentialFormatPayload, 'createOffer'> + credentialFormats: CredentialFormatPayload, 'createOffer'> } /** * Interface for CredentialsApi.createOffer. Will create an out of band offer */ -export interface CreateOfferOptions extends BaseOptions { +export interface CreateCredentialOfferOptions + extends BaseOptions { protocolVersion: CredentialProtocolVersionType - credentialFormats: CredentialFormatPayload, 'createOffer'> + credentialFormats: CredentialFormatPayload, 'createOffer'> } /** - * Interface for CredentialsApi.offerCredentials. Extends CreateOfferOptions, will send an offer + * Interface for CredentialsApi.offerCredential. Extends CreateCredentialOfferOptions, will send an offer */ export interface OfferCredentialOptions extends BaseOptions, - CreateOfferOptions { + CreateCredentialOfferOptions { connectionId: string } @@ -101,7 +89,7 @@ export interface OfferCredentialOptions extends BaseOptions { credentialRecordId: string - credentialFormats?: CredentialFormatPayload, 'acceptOffer'> + credentialFormats?: CredentialFormatPayload, 'acceptOffer'> } /** @@ -110,7 +98,7 @@ export interface AcceptCredentialOfferOptions extends BaseOptions { credentialRecordId: string - credentialFormats: CredentialFormatPayload, 'createProposal'> + credentialFormats: CredentialFormatPayload, 'createProposal'> } /** @@ -121,7 +109,7 @@ export interface NegotiateCredentialOfferOptions extends BaseOptions { credentialRecordId: string - credentialFormats?: CredentialFormatPayload, 'acceptRequest'> + credentialFormats?: CredentialFormatPayload, 'acceptRequest'> autoAcceptCredential?: AutoAcceptCredential comment?: string } @@ -138,5 +126,5 @@ export interface AcceptCredentialOptions { */ export interface SendCredentialProblemReportOptions { credentialRecordId: string - message: string + description: string } diff --git a/packages/core/src/modules/credentials/CredentialsModule.ts b/packages/core/src/modules/credentials/CredentialsModule.ts index a581f5e551..9cae75eb28 100644 --- a/packages/core/src/modules/credentials/CredentialsModule.ts +++ b/packages/core/src/modules/credentials/CredentialsModule.ts @@ -1,26 +1,24 @@ +import type { CredentialsModuleConfigOptions } from './CredentialsModuleConfig' +import type { CredentialProtocol } from './protocol/CredentialProtocol' import type { FeatureRegistry } from '../../agent/FeatureRegistry' import type { ApiModule, DependencyManager } from '../../plugins' import type { Constructor } from '../../utils/mixins' import type { Optional } from '../../utils/type' -import type { CredentialsModuleConfigOptions } from './CredentialsModuleConfig' -import type { CredentialProtocol } from './protocol/CredentialProtocol' import { Protocol } from '../../agent/models' import { CredentialsApi } from './CredentialsApi' import { CredentialsModuleConfig } from './CredentialsModuleConfig' -import { IndyCredentialFormatService } from './formats/indy' import { RevocationNotificationService } from './protocol/revocation-notification/services' -import { V1CredentialProtocol } from './protocol/v1' import { V2CredentialProtocol } from './protocol/v2' import { CredentialRepository } from './repository' /** * Default credentialProtocols that will be registered if the `credentialProtocols` property is not configured. */ -export type DefaultCredentialProtocols = [V1CredentialProtocol, V2CredentialProtocol] +export type DefaultCredentialProtocols = [] -// CredentialModuleOptions makes the credentialProtocols property optional from the config, as it will set it when not provided. +// CredentialsModuleOptions makes the credentialProtocols property optional from the config, as it will set it when not provided. export type CredentialsModuleOptions = Optional< CredentialsModuleConfigOptions, 'credentialProtocols' @@ -38,27 +36,11 @@ export class CredentialsModule } - /** - * Get the default credential protocols that will be registered if the `credentialProtocols` property is not configured. - */ - private getDefaultCredentialProtocols(): DefaultCredentialProtocols { - // Instantiate credential formats - const indyCredentialFormat = new IndyCredentialFormatService() - - // Instantiate credential protocols - const v1CredentialProtocol = new V1CredentialProtocol({ indyCredentialFormat }) - const v2CredentialProtocol = new V2CredentialProtocol({ - credentialFormats: [indyCredentialFormat], - }) - - return [v1CredentialProtocol, v2CredentialProtocol] - } - /** * Registers the dependencies of the credentials module on the dependency manager. */ diff --git a/packages/core/src/modules/credentials/CredentialsModuleConfig.ts b/packages/core/src/modules/credentials/CredentialsModuleConfig.ts index 34c20f50e7..e6d23909ed 100644 --- a/packages/core/src/modules/credentials/CredentialsModuleConfig.ts +++ b/packages/core/src/modules/credentials/CredentialsModuleConfig.ts @@ -18,11 +18,11 @@ export interface CredentialsModuleConfigOptions { ) }) - test('registers V1CredentialProtocol and V2CredentialProtocol if no credentialProtocols are configured', () => { + test('registers V2CredentialProtocol if no credentialProtocols are configured', () => { const credentialsModule = new CredentialsModule() - expect(credentialsModule.config.credentialProtocols).toEqual([ - expect.any(V1CredentialProtocol), - expect.any(V2CredentialProtocol), - ]) + expect(credentialsModule.config.credentialProtocols).toEqual([expect.any(V2CredentialProtocol)]) }) test('calls register on the provided CredentialProtocols', () => { diff --git a/packages/core/src/modules/credentials/__tests__/fixtures.ts b/packages/core/src/modules/credentials/__tests__/fixtures.ts index b8e5e7451c..057ae8d4e1 100644 --- a/packages/core/src/modules/credentials/__tests__/fixtures.ts +++ b/packages/core/src/modules/credentials/__tests__/fixtures.ts @@ -1,35 +1,3 @@ -import type { Schema } from 'indy-sdk' - -export const credDef = { - ver: '1.0', - id: 'TL1EaPFCZ8Si5aUrqScBDt:3:CL:16:TAG', - schemaId: '16', - type: 'CL', - tag: 'TAG', - value: { - primary: { - n: '92498022445845202032348897620554299694896009176315493627722439892023558526259875239808280186111059586069456394012963552956574651629517633396592827947162983189649269173220440607665417484696688946624963596710652063849006738050417440697782608643095591808084344059908523401576738321329706597491345875134180790935098782801918369980296355919072827164363500681884641551147645504164254206270541724042784184712124576190438261715948768681331862924634233043594086219221089373455065715714369325926959533971768008691000560918594972006312159600845441063618991760512232714992293187779673708252226326233136573974603552763615191259713', - s: '10526250116244590830801226936689232818708299684432892622156345407187391699799320507237066062806731083222465421809988887959680863378202697458984451550048737847231343182195679453915452156726746705017249911605739136361885518044604626564286545453132948801604882107628140153824106426249153436206037648809856342458324897885659120708767794055147846459394129610878181859361616754832462886951623882371283575513182530118220334228417923423365966593298195040550255217053655606887026300020680355874881473255854564974899509540795154002250551880061649183753819902391970912501350100175974791776321455551753882483918632271326727061054', - r: [Object], - rctxt: - '46370806529776888197599056685386177334629311939451963919411093310852010284763705864375085256873240323432329015015526097014834809926159013231804170844321552080493355339505872140068998254185756917091385820365193200970156007391350745837300010513687490459142965515562285631984769068796922482977754955668569724352923519618227464510753980134744424528043503232724934196990461197793822566137436901258663918660818511283047475389958180983391173176526879694302021471636017119966755980327241734084462963412467297412455580500138233383229217300797768907396564522366006433982511590491966618857814545264741708965590546773466047139517', - z: '84153935869396527029518633753040092509512111365149323230260584738724940130382637900926220255597132853379358675015222072417404334537543844616589463419189203852221375511010886284448841979468767444910003114007224993233448170299654815710399828255375084265247114471334540928216537567325499206413940771681156686116516158907421215752364889506967984343660576422672840921988126699885304325384925457260272972771547695861942114712679509318179363715259460727275178310181122162544785290813713205047589943947592273130618286905125194410421355167030389500160371886870735704712739886223342214864760968555566496288314800410716250791012', - }, - }, -} - -export const credOffer = { - schema_id: 'TL1EaPFCZ8Si5aUrqScBDt:2:test-schema-1599055118161:1.0', - cred_def_id: 'TL1EaPFCZ8Si5aUrqScBDt:3:CL:49:TAG', - key_correctness_proof: { - c: '50047550092211803100898435599448498249230644214602846259465380105187911562981', - xz_cap: - '903377919969858361861015636539761203188657065139923565169527138921408162179186528356880386741834936511828233627399006489728775544195659624738894378139967421189010372215352983118513580084886680005590351907106638703178655817619548698392274394080197104513101326422946899502782963819178061725651195158952405559244837834363357514238035344644245428381747318500206935512140018411279271654056625228252895211750431161165113594675112781707690650346028518711572046490157895995321932792559036799731075010805676081761818738662133557673397343395090042309895292970880031625026873886199268438633391631171327618951514526941153292890331525143330509967786605076984412387036942171388655140446222693051734534012842', - xr_cap: [[], [], []], - }, - nonce: '947121108704767252195123', -} - export const credReq = { prover_did: 'did:sov:Y8iyDrCHfUpBY2jkd7Utfx', cred_def_id: 'TL1EaPFCZ8Si5aUrqScBDt:3:CL:51:TAG', @@ -51,12 +19,3 @@ export const credReq = { }, nonce: '784158051402761459123237', } - -export const schema: Schema = { - name: 'schema', - attrNames: ['name', 'age'], - id: 'TL1EaPFCZ8Si5aUrqScBDt:2:test-schema-1599055118161:1.0', - seqNo: 989798923653, - ver: '1.0', - version: '1.0', -} diff --git a/packages/core/src/modules/credentials/errors/CredentialProblemReportError.ts b/packages/core/src/modules/credentials/errors/CredentialProblemReportError.ts deleted file mode 100644 index ffbe633004..0000000000 --- a/packages/core/src/modules/credentials/errors/CredentialProblemReportError.ts +++ /dev/null @@ -1,23 +0,0 @@ -import type { ProblemReportErrorOptions } from '../../problem-reports' -import type { CredentialProblemReportReason } from './CredentialProblemReportReason' - -import { V1CredentialProblemReportMessage } from '../protocol/v1/messages' - -import { ProblemReportError } from './../../problem-reports/errors/ProblemReportError' - -interface CredentialProblemReportErrorOptions extends ProblemReportErrorOptions { - problemCode: CredentialProblemReportReason -} -export class CredentialProblemReportError extends ProblemReportError { - public problemReport: V1CredentialProblemReportMessage - - public constructor(message: string, { problemCode }: CredentialProblemReportErrorOptions) { - super(message, { problemCode }) - this.problemReport = new V1CredentialProblemReportMessage({ - description: { - en: message, - code: problemCode, - }, - }) - } -} diff --git a/packages/core/src/modules/credentials/errors/index.ts b/packages/core/src/modules/credentials/errors/index.ts deleted file mode 100644 index 3d5c266524..0000000000 --- a/packages/core/src/modules/credentials/errors/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from './CredentialProblemReportError' -export * from './CredentialProblemReportReason' diff --git a/packages/core/src/modules/credentials/formats/CredentialFormatService.ts b/packages/core/src/modules/credentials/formats/CredentialFormatService.ts index 6fbe76d857..ac1ffde0a9 100644 --- a/packages/core/src/modules/credentials/formats/CredentialFormatService.ts +++ b/packages/core/src/modules/credentials/formats/CredentialFormatService.ts @@ -1,23 +1,22 @@ -import type { AgentContext } from '../../../agent' -import type { V1Attachment } from '../../../decorators/attachment/V1Attachment' import type { CredentialFormat } from './CredentialFormat' import type { - FormatCreateProposalOptions, - FormatCreateProposalReturn, - FormatProcessOptions, - FormatCreateOfferOptions, - FormatCreateOfferReturn, - FormatCreateRequestOptions, + CredentialFormatCreateProposalOptions, + CredentialFormatCreateProposalReturn, + CredentialFormatProcessOptions, + CredentialFormatCreateOfferOptions, + CredentialFormatCreateOfferReturn, + CredentialFormatCreateRequestOptions, CredentialFormatCreateReturn, - FormatAcceptRequestOptions, - FormatAcceptOfferOptions, - FormatAcceptProposalOptions, - FormatAutoRespondCredentialOptions, - FormatAutoRespondOfferOptions, - FormatAutoRespondProposalOptions, - FormatAutoRespondRequestOptions, - FormatProcessCredentialOptions, + CredentialFormatAcceptRequestOptions, + CredentialFormatAcceptOfferOptions, + CredentialFormatAcceptProposalOptions, + CredentialFormatAutoRespondCredentialOptions, + CredentialFormatAutoRespondOfferOptions, + CredentialFormatAutoRespondProposalOptions, + CredentialFormatAutoRespondRequestOptions, + CredentialFormatProcessCredentialOptions, } from './CredentialFormatServiceOptions' +import type { AgentContext } from '../../../agent' export interface CredentialFormatService { formatKey: CF['formatKey'] @@ -26,39 +25,58 @@ export interface CredentialFormatService - ): Promise - processProposal(agentContext: AgentContext, options: FormatProcessOptions): Promise - acceptProposal(agentContext: AgentContext, options: FormatAcceptProposalOptions): Promise + options: CredentialFormatCreateProposalOptions + ): Promise + processProposal(agentContext: AgentContext, options: CredentialFormatProcessOptions): Promise + acceptProposal( + agentContext: AgentContext, + options: CredentialFormatAcceptProposalOptions + ): Promise // offer methods - createOffer(agentContext: AgentContext, options: FormatCreateOfferOptions): Promise - processOffer(agentContext: AgentContext, options: FormatProcessOptions): Promise - acceptOffer(agentContext: AgentContext, options: FormatAcceptOfferOptions): Promise + createOffer( + agentContext: AgentContext, + options: CredentialFormatCreateOfferOptions + ): Promise + processOffer(agentContext: AgentContext, options: CredentialFormatProcessOptions): Promise + acceptOffer( + agentContext: AgentContext, + options: CredentialFormatAcceptOfferOptions + ): Promise // request methods createRequest( agentContext: AgentContext, - options: FormatCreateRequestOptions + options: CredentialFormatCreateRequestOptions ): Promise - processRequest(agentContext: AgentContext, options: FormatProcessOptions): Promise + processRequest(agentContext: AgentContext, options: CredentialFormatProcessOptions): Promise acceptRequest( agentContext: AgentContext, - options: FormatAcceptRequestOptions + options: CredentialFormatAcceptRequestOptions ): Promise // credential methods - processCredential(agentContext: AgentContext, options: FormatProcessCredentialOptions): Promise + processCredential(agentContext: AgentContext, options: CredentialFormatProcessCredentialOptions): Promise // auto accept methods - shouldAutoRespondToProposal(agentContext: AgentContext, options: FormatAutoRespondProposalOptions): boolean - shouldAutoRespondToOffer(agentContext: AgentContext, options: FormatAutoRespondOfferOptions): boolean - shouldAutoRespondToRequest(agentContext: AgentContext, options: FormatAutoRespondRequestOptions): boolean - shouldAutoRespondToCredential(agentContext: AgentContext, options: FormatAutoRespondCredentialOptions): boolean + shouldAutoRespondToProposal( + agentContext: AgentContext, + options: CredentialFormatAutoRespondProposalOptions + ): Promise + shouldAutoRespondToOffer( + agentContext: AgentContext, + options: CredentialFormatAutoRespondOfferOptions + ): Promise + shouldAutoRespondToRequest( + agentContext: AgentContext, + options: CredentialFormatAutoRespondRequestOptions + ): Promise + shouldAutoRespondToCredential( + agentContext: AgentContext, + options: CredentialFormatAutoRespondCredentialOptions + ): Promise deleteCredentialById(agentContext: AgentContext, credentialId: string): Promise - supportsFormat(format: string): boolean - - getFormatData(data: unknown, id: string): V1Attachment + supportsFormat(formatIdentifier: string): boolean } diff --git a/packages/core/src/modules/credentials/formats/CredentialFormatServiceOptions.ts b/packages/core/src/modules/credentials/formats/CredentialFormatServiceOptions.ts index 721f289679..4be6646ffc 100644 --- a/packages/core/src/modules/credentials/formats/CredentialFormatServiceOptions.ts +++ b/packages/core/src/modules/credentials/formats/CredentialFormatServiceOptions.ts @@ -1,9 +1,9 @@ +import type { CredentialFormat, CredentialFormatPayload } from './CredentialFormat' +import type { CredentialFormatService } from './CredentialFormatService' import type { V1Attachment } from '../../../decorators/attachment/V1Attachment' import type { CredentialFormatSpec } from '../models/CredentialFormatSpec' -import type { CredentialPreviewAttribute } from '../models/CredentialPreviewAttribute' +import type { CredentialPreviewAttributeOptions } from '../models/CredentialPreviewAttribute' import type { CredentialExchangeRecord } from '../repository/CredentialExchangeRecord' -import type { CredentialFormat, CredentialFormatPayload } from './CredentialFormat' -import type { CredentialFormatService } from './CredentialFormatService' /** * Infer the {@link CredentialFormat} based on a {@link CredentialFormatService}. @@ -46,93 +46,88 @@ export interface CredentialFormatCreateReturn { } /** - * Base return type for all process methods. + * Base return type for all credential process methods. */ -export interface FormatProcessOptions { +export interface CredentialFormatProcessOptions { attachment: V1Attachment credentialRecord: CredentialExchangeRecord } -export interface FormatProcessCredentialOptions extends FormatProcessOptions { +export interface CredentialFormatProcessCredentialOptions extends CredentialFormatProcessOptions { requestAttachment: V1Attachment } -export interface FormatCreateProposalOptions { +export interface CredentialFormatCreateProposalOptions { credentialRecord: CredentialExchangeRecord credentialFormats: CredentialFormatPayload<[CF], 'createProposal'> + attachmentId?: string } -export interface FormatAcceptProposalOptions { +export interface CredentialFormatAcceptProposalOptions { credentialRecord: CredentialExchangeRecord credentialFormats?: CredentialFormatPayload<[CF], 'acceptProposal'> - attachId?: string + attachmentId?: string proposalAttachment: V1Attachment } -export interface FormatCreateProposalReturn extends CredentialFormatCreateReturn { - previewAttributes?: CredentialPreviewAttribute[] +export interface CredentialFormatCreateProposalReturn extends CredentialFormatCreateReturn { + previewAttributes?: CredentialPreviewAttributeOptions[] } -export interface FormatCreateOfferOptions { +export interface CredentialFormatCreateOfferOptions { credentialRecord: CredentialExchangeRecord credentialFormats: CredentialFormatPayload<[CF], 'createOffer'> - attachId?: string + attachmentId?: string } -export interface FormatAcceptOfferOptions { +export interface CredentialFormatAcceptOfferOptions { credentialRecord: CredentialExchangeRecord credentialFormats?: CredentialFormatPayload<[CF], 'acceptOffer'> - attachId?: string + attachmentId?: string offerAttachment: V1Attachment } -export interface FormatCreateOfferReturn extends CredentialFormatCreateReturn { - previewAttributes?: CredentialPreviewAttribute[] +export interface CredentialFormatCreateOfferReturn extends CredentialFormatCreateReturn { + previewAttributes?: CredentialPreviewAttributeOptions[] } -export interface FormatCreateRequestOptions { +export interface CredentialFormatCreateRequestOptions { credentialRecord: CredentialExchangeRecord credentialFormats: CredentialFormatPayload<[CF], 'createRequest'> } -export interface FormatAcceptRequestOptions { +export interface CredentialFormatAcceptRequestOptions { credentialRecord: CredentialExchangeRecord credentialFormats?: CredentialFormatPayload<[CF], 'acceptRequest'> - attachId?: string + attachmentId?: string requestAttachment: V1Attachment offerAttachment?: V1Attachment } -export interface FormatAcceptCredentialOptions { - credentialRecord: CredentialExchangeRecord - attachId?: string - requestAttachment: V1Attachment - offerAttachment?: V1Attachment -} // Auto accept method interfaces -export interface FormatAutoRespondProposalOptions { +export interface CredentialFormatAutoRespondProposalOptions { credentialRecord: CredentialExchangeRecord proposalAttachment: V1Attachment offerAttachment: V1Attachment } -export interface FormatAutoRespondOfferOptions { +export interface CredentialFormatAutoRespondOfferOptions { credentialRecord: CredentialExchangeRecord proposalAttachment: V1Attachment offerAttachment: V1Attachment } -export interface FormatAutoRespondRequestOptions { +export interface CredentialFormatAutoRespondRequestOptions { credentialRecord: CredentialExchangeRecord proposalAttachment?: V1Attachment offerAttachment: V1Attachment requestAttachment: V1Attachment } -export interface FormatAutoRespondCredentialOptions { +export interface CredentialFormatAutoRespondCredentialOptions { credentialRecord: CredentialExchangeRecord proposalAttachment?: V1Attachment offerAttachment?: V1Attachment diff --git a/packages/core/src/modules/credentials/formats/__tests__/IndyCredentialFormatService.test.ts b/packages/core/src/modules/credentials/formats/__tests__/IndyCredentialFormatService.test.ts deleted file mode 100644 index 342abd7b03..0000000000 --- a/packages/core/src/modules/credentials/formats/__tests__/IndyCredentialFormatService.test.ts +++ /dev/null @@ -1,439 +0,0 @@ -import type { AgentContext } from '../../../../agent' -import type { AgentConfig } from '../../../../agent/AgentConfig' -import type { ParseRevocationRegistryDefinitionTemplate } from '../../../ledger/services/IndyLedgerService' -import type { CredentialFormatService } from '../../formats' -import type { IndyCredentialFormat } from '../../formats/indy/IndyCredentialFormat' -import type { CredentialPreviewAttribute } from '../../models/CredentialPreviewAttribute' -import type { V2OfferCredentialMessageOptions } from '../../protocol/v2/messages/V2OfferCredentialMessage' -import type { CustomCredentialTags } from '../../repository/CredentialExchangeRecord' -import type { RevocRegDef } from 'indy-sdk' - -import { getAgentConfig, getAgentContext, mockFunction } from '../../../../../tests/helpers' -import { V1Attachment, V1AttachmentData } from '../../../../decorators/attachment/V1Attachment' -import { JsonEncoder } from '../../../../utils/JsonEncoder' -import { ConnectionService } from '../../../connections/services/ConnectionService' -import { DidResolverService } from '../../../dids/services/DidResolverService' -import { IndyHolderService } from '../../../indy/services/IndyHolderService' -import { IndyIssuerService } from '../../../indy/services/IndyIssuerService' -import { IndyLedgerService } from '../../../ledger/services/IndyLedgerService' -import { credDef, credReq, schema } from '../../__tests__/fixtures' -import { IndyCredentialFormatService } from '../../formats' -import { IndyCredentialUtils } from '../../formats/indy/IndyCredentialUtils' -import { CredentialState } from '../../models' -import { - INDY_CREDENTIAL_ATTACHMENT_ID, - INDY_CREDENTIAL_OFFER_ATTACHMENT_ID, - INDY_CREDENTIAL_REQUEST_ATTACHMENT_ID, -} from '../../protocol/v1/messages' -import { V2CredentialPreview } from '../../protocol/v2/messages' -import { V2OfferCredentialMessage } from '../../protocol/v2/messages/V2OfferCredentialMessage' -import { CredentialMetadataKeys } from '../../repository' -import { CredentialExchangeRecord } from '../../repository/CredentialExchangeRecord' - -jest.mock('../../../../modules/ledger/services/IndyLedgerService') -jest.mock('../../../indy/services/IndyHolderService') -jest.mock('../../../indy/services/IndyIssuerService') -jest.mock('../../../dids/services/DidResolverService') -jest.mock('../../../connections/services/ConnectionService') - -const IndyLedgerServiceMock = IndyLedgerService as jest.Mock -const IndyHolderServiceMock = IndyHolderService as jest.Mock -const IndyIssuerServiceMock = IndyIssuerService as jest.Mock -const ConnectionServiceMock = ConnectionService as jest.Mock -const DidResolverServiceMock = DidResolverService as jest.Mock - -const values = { - x: { - raw: 'x', - encoded: 'y', - }, -} -const cred = { - schema_id: 'xsxs', - cred_def_id: 'xdxd', - rev_reg_id: 'x', - values: values, - signature: undefined, - signature_correctness_proof: undefined, -} - -const revDef: RevocRegDef = { - id: 'x', - revocDefType: 'CL_ACCUM', - tag: 'x', - credDefId: 'x', - value: { - issuanceType: 'ISSUANCE_BY_DEFAULT', - maxCredNum: 33, - tailsHash: 'd', - tailsLocation: 'x', - publicKeys: ['x'], - }, - ver: 't', -} - -const revocationTemplate: ParseRevocationRegistryDefinitionTemplate = { - revocationRegistryDefinition: revDef, - revocationRegistryDefinitionTxnTime: 42, -} - -const credentialPreview = V2CredentialPreview.fromRecord({ - name: 'John', - age: '99', -}) - -const offerAttachment = new V1Attachment({ - id: INDY_CREDENTIAL_OFFER_ATTACHMENT_ID, - mimeType: 'application/json', - data: new V1AttachmentData({ - base64: - 'eyJzY2hlbWFfaWQiOiJhYWEiLCJjcmVkX2RlZl9pZCI6IlRoN01wVGFSWlZSWW5QaWFiZHM4MVk6MzpDTDoxNzpUQUciLCJub25jZSI6Im5vbmNlIiwia2V5X2NvcnJlY3RuZXNzX3Byb29mIjp7fX0', - }), -}) - -const requestAttachment = new V1Attachment({ - id: INDY_CREDENTIAL_REQUEST_ATTACHMENT_ID, - mimeType: 'application/json', - data: new V1AttachmentData({ - base64: JsonEncoder.toBase64(credReq), - }), -}) - -const credentialAttachment = new V1Attachment({ - id: INDY_CREDENTIAL_ATTACHMENT_ID, - mimeType: 'application/json', - data: new V1AttachmentData({ - base64: JsonEncoder.toBase64({ - values: IndyCredentialUtils.convertAttributesToValues(credentialPreview.attributes), - }), - }), -}) - -// A record is deserialized to JSON when it's stored into the storage. We want to simulate this behaviour for `offer` -// object to test our service would behave correctly. We use type assertion for `offer` attribute to `any`. -const mockCredentialRecord = ({ - state, - metadata, - threadId, - connectionId, - tags, - id, - credentialAttributes, -}: { - state?: CredentialState - metadata?: { indyRequest: Record } - tags?: CustomCredentialTags - threadId?: string - connectionId?: string - id?: string - credentialAttributes?: CredentialPreviewAttribute[] -} = {}) => { - const offerOptions: V2OfferCredentialMessageOptions = { - id: '', - formats: [ - { - attachId: INDY_CREDENTIAL_OFFER_ATTACHMENT_ID, - format: 'hlindy/cred-abstract@v2.0', - }, - ], - comment: 'some comment', - credentialPreview: credentialPreview, - offerAttachments: [offerAttachment], - replacementId: undefined, - } - const offerMessage = new V2OfferCredentialMessage(offerOptions) - - const credentialRecord = new CredentialExchangeRecord({ - id, - credentialAttributes: credentialAttributes || credentialPreview.attributes, - state: state || CredentialState.OfferSent, - threadId: threadId ?? offerMessage.id, - connectionId: connectionId ?? '123', - tags, - protocolVersion: 'v2', - }) - - if (metadata?.indyRequest) { - credentialRecord.metadata.set(CredentialMetadataKeys.IndyRequest, { ...metadata.indyRequest }) - } - - return credentialRecord -} -let indyFormatService: CredentialFormatService -let indyLedgerService: IndyLedgerService -let indyIssuerService: IndyIssuerService -let indyHolderService: IndyHolderService -let didResolverService: DidResolverService -let connectionService: ConnectionService -let agentConfig: AgentConfig -let credentialRecord: CredentialExchangeRecord - -describe('Indy CredentialFormatService', () => { - let agentContext: AgentContext - beforeEach(async () => { - indyIssuerService = new IndyIssuerServiceMock() - indyHolderService = new IndyHolderServiceMock() - indyLedgerService = new IndyLedgerServiceMock() - didResolverService = new DidResolverServiceMock() - connectionService = new ConnectionServiceMock() - - agentConfig = getAgentConfig('IndyCredentialFormatServiceTest') - agentContext = getAgentContext({ - registerInstances: [ - [IndyIssuerService, indyIssuerService], - [IndyHolderService, indyHolderService], - [IndyLedgerService, indyLedgerService], - [DidResolverService, didResolverService], - [ConnectionService, connectionService], - ], - agentConfig, - }) - - indyFormatService = new IndyCredentialFormatService() - - mockFunction(indyLedgerService.getSchema).mockReturnValue(Promise.resolve(schema)) - }) - - describe('Create Credential Proposal / Offer', () => { - test(`Creates Credential Proposal`, async () => { - // when - const { attachment, previewAttributes, format } = await indyFormatService.createProposal(agentContext, { - credentialRecord: mockCredentialRecord(), - credentialFormats: { - indy: { - credentialDefinitionId: 'Th7MpTaRZVRYnPiabds81Y:3:CL:17:TAG', - schemaIssuerDid: 'GMm4vMw8LLrLJjp81kRRLp', - schemaName: 'ahoy', - schemaVersion: '1.0', - schemaId: 'q7ATwTYbQDgiigVijUAej:2:test:1.0', - issuerDid: 'GMm4vMw8LLrLJjp81kRRLp', - attributes: credentialPreview.attributes, - }, - }, - }) - - // then - expect(attachment).toMatchObject({ - id: expect.any(String), - description: undefined, - filename: undefined, - mimeType: 'application/json', - lastmodTime: undefined, - byteCount: undefined, - data: { - base64: - 'eyJzY2hlbWFfaXNzdWVyX2RpZCI6IkdNbTR2TXc4TExyTEpqcDgxa1JSTHAiLCJzY2hlbWFfaWQiOiJxN0FUd1RZYlFEZ2lpZ1ZpalVBZWo6Mjp0ZXN0OjEuMCIsInNjaGVtYV9uYW1lIjoiYWhveSIsInNjaGVtYV92ZXJzaW9uIjoiMS4wIiwiY3JlZF9kZWZfaWQiOiJUaDdNcFRhUlpWUlluUGlhYmRzODFZOjM6Q0w6MTc6VEFHIiwiaXNzdWVyX2RpZCI6IkdNbTR2TXc4TExyTEpqcDgxa1JSTHAifQ==', - json: undefined, - links: undefined, - jws: undefined, - sha256: undefined, - }, - }) - - expect(previewAttributes).toMatchObject([ - { - mimeType: 'text/plain', - name: 'name', - value: 'John', - }, - { - mimeType: 'text/plain', - name: 'age', - value: '99', - }, - ]) - - expect(format).toMatchObject({ - attachId: expect.any(String), - format: 'hlindy/cred-filter@v2.0', - }) - }) - - test(`Creates Credential Offer`, async () => { - // when - const { attachment, previewAttributes, format } = await indyFormatService.createOffer(agentContext, { - credentialRecord: mockCredentialRecord(), - credentialFormats: { - indy: { - attributes: credentialPreview.attributes, - credentialDefinitionId: 'Th7MpTaRZVRYnPiabds81Y:3:CL:17:TAG', - }, - }, - }) - - // then - expect(indyIssuerService.createCredentialOffer).toHaveBeenCalledTimes(1) - - expect(attachment).toMatchObject({ - id: expect.any(String), - description: undefined, - filename: undefined, - mimeType: 'application/json', - lastmodTime: undefined, - byteCount: undefined, - data: { - base64: - 'eyJzY2hlbWFfaWQiOiJhYWEiLCJjcmVkX2RlZl9pZCI6IlRoN01wVGFSWlZSWW5QaWFiZHM4MVk6MzpDTDoxNzpUQUciLCJub25jZSI6Im5vbmNlIiwia2V5X2NvcnJlY3RuZXNzX3Byb29mIjp7fX0=', - json: undefined, - links: undefined, - jws: undefined, - sha256: undefined, - }, - }) - - expect(previewAttributes).toMatchObject([ - { - mimeType: 'text/plain', - name: 'name', - value: 'John', - }, - { - mimeType: 'text/plain', - name: 'age', - value: '99', - }, - ]) - - expect(format).toMatchObject({ - attachId: expect.any(String), - format: 'hlindy/cred-abstract@v2.0', - }) - }) - }) - describe('Process Credential Offer', () => { - test(`processes credential offer - returns modified credential record (adds metadata)`, async () => { - // given - const credentialRecord = mockCredentialRecord({ - state: CredentialState.OfferReceived, - threadId: 'fd9c5ddb-ec11-4acd-bc32-540736249746', - connectionId: 'b1e2f039-aa39-40be-8643-6ce2797b5190', - }) - - // when - await indyFormatService.processOffer(agentContext, { attachment: offerAttachment, credentialRecord }) - }) - }) - - describe('Create Credential Request', () => { - test('returns credential request message base on existing credential offer message', async () => { - // given - const credentialRecord = mockCredentialRecord({ - state: CredentialState.OfferReceived, - threadId: 'fd9c5ddb-ec11-4acd-bc32-540736249746', - connectionId: 'b1e2f039-aa39-40be-8643-6ce2797b5190', - }) - - mockFunction(indyLedgerService.getCredentialDefinition).mockReturnValue(Promise.resolve(credDef)) - - // when - const { format, attachment } = await indyFormatService.acceptOffer(agentContext, { - credentialRecord, - credentialFormats: { - indy: { - holderDid: 'holderDid', - }, - }, - offerAttachment, - }) - - // then - expect(indyHolderService.createCredentialRequest).toHaveBeenCalledTimes(1) - - expect(attachment).toMatchObject({ - id: expect.any(String), - description: undefined, - filename: undefined, - mimeType: 'application/json', - lastmodTime: undefined, - byteCount: undefined, - data: { - base64: - 'eyJwcm92ZXJfZGlkIjoiaG9sZGVyRGlkIiwiY3JlZF9kZWZfaWQiOiJUTDFFYVBGQ1o4U2k1YVVycVNjQkR0OjM6Q0w6MTY6VEFHIiwiYmxpbmRlZF9tcyI6e30sImJsaW5kZWRfbXNfY29ycmVjdG5lc3NfcHJvb2YiOnt9LCJub25jZSI6Im5vbmNlIn0=', - json: undefined, - links: undefined, - jws: undefined, - sha256: undefined, - }, - }) - expect(format).toMatchObject({ - attachId: expect.any(String), - format: 'hlindy/cred-req@v2.0', - }) - - const credentialRequestMetadata = credentialRecord.metadata.get(CredentialMetadataKeys.IndyCredential) - - expect(credentialRequestMetadata?.schemaId).toBe('aaa') - expect(credentialRequestMetadata?.credentialDefinitionId).toBe('Th7MpTaRZVRYnPiabds81Y:3:CL:17:TAG') - }) - }) - - describe('Accept request', () => { - test('Creates a credentials', async () => { - // given - const credentialRecord = mockCredentialRecord({ - state: CredentialState.RequestReceived, - threadId: 'fd9c5ddb-ec11-4acd-bc32-540736249746', - connectionId: 'b1e2f039-aa39-40be-8643-6ce2797b5190', - }) - - mockFunction(indyIssuerService.createCredential).mockReturnValue(Promise.resolve([cred, 'x'])) - - // when - const { format, attachment } = await indyFormatService.acceptRequest(agentContext, { - credentialRecord, - requestAttachment, - offerAttachment, - attachId: INDY_CREDENTIAL_ATTACHMENT_ID, - }) - - expect(attachment).toMatchObject({ - id: 'libindy-cred-0', - description: undefined, - filename: undefined, - mimeType: 'application/json', - lastmodTime: undefined, - byteCount: undefined, - data: { - base64: - 'eyJzY2hlbWFfaWQiOiJ4c3hzIiwiY3JlZF9kZWZfaWQiOiJ4ZHhkIiwicmV2X3JlZ19pZCI6IngiLCJ2YWx1ZXMiOnsieCI6eyJyYXciOiJ4IiwiZW5jb2RlZCI6InkifX19', - json: undefined, - links: undefined, - jws: undefined, - sha256: undefined, - }, - }) - expect(format).toMatchObject({ - attachId: expect.any(String), - format: 'hlindy/cred@v2.0', - }) - }) - }) - - describe('Process Credential', () => { - test('finds credential record by thread ID and saves credential attachment into the wallet', async () => { - // given - credentialRecord = mockCredentialRecord({ - state: CredentialState.RequestSent, - metadata: { indyRequest: { cred_req: 'meta-data' } }, - }) - mockFunction(indyLedgerService.getCredentialDefinition).mockReturnValue(Promise.resolve(credDef)) - mockFunction(indyLedgerService.getRevocationRegistryDefinition).mockReturnValue( - Promise.resolve(revocationTemplate) - ) - mockFunction(indyHolderService.storeCredential).mockReturnValue(Promise.resolve('100')) - - // when - await indyFormatService.processCredential(agentContext, { - attachment: credentialAttachment, - requestAttachment: requestAttachment, - credentialRecord, - }) - - // then - expect(indyHolderService.storeCredential).toHaveBeenCalledTimes(1) - expect(credentialRecord.credentials.length).toBe(1) - expect(credentialRecord.credentials[0].credentialRecordType).toBe('indy') - expect(credentialRecord.credentials[0].credentialRecordId).toBe('100') - }) - }) -}) diff --git a/packages/core/src/modules/credentials/formats/index.ts b/packages/core/src/modules/credentials/formats/index.ts index 614b3559a3..fb2b300b5e 100644 --- a/packages/core/src/modules/credentials/formats/index.ts +++ b/packages/core/src/modules/credentials/formats/index.ts @@ -1,5 +1,4 @@ export * from './CredentialFormatService' export * from './CredentialFormatServiceOptions' export * from './CredentialFormat' -export * from './indy' export * from './jsonld' diff --git a/packages/core/src/modules/credentials/formats/indy/IndyCredentialFormat.ts b/packages/core/src/modules/credentials/formats/indy/IndyCredentialFormat.ts deleted file mode 100644 index 9f15c1152f..0000000000 --- a/packages/core/src/modules/credentials/formats/indy/IndyCredentialFormat.ts +++ /dev/null @@ -1,70 +0,0 @@ -import type { LinkedAttachment } from '../../../../utils/LinkedAttachment' -import type { CredentialPreviewAttributeOptions } from '../../models' -import type { CredentialFormat } from '../CredentialFormat' -import type { IndyCredProposeOptions } from './models/IndyCredPropose' -import type { Cred, CredOffer, CredReq } from 'indy-sdk' - -/** - * This defines the module payload for calling CredentialsApi.createProposal - * or CredentialsApi.negotiateOffer - */ -export interface IndyProposeCredentialFormat extends IndyCredProposeOptions { - attributes?: CredentialPreviewAttributeOptions[] - linkedAttachments?: LinkedAttachment[] -} - -/** - * This defines the module payload for calling CredentialsApi.acceptProposal - */ -export interface IndyAcceptProposalFormat { - credentialDefinitionId?: string - attributes?: CredentialPreviewAttributeOptions[] - linkedAttachments?: LinkedAttachment[] -} - -export interface IndyAcceptOfferFormat { - holderDid?: string -} - -/** - * This defines the module payload for calling CredentialsApi.offerCredential - * or CredentialsApi.negotiateProposal - */ -export interface IndyOfferCredentialFormat { - credentialDefinitionId: string - attributes: CredentialPreviewAttributeOptions[] - linkedAttachments?: LinkedAttachment[] -} - -export interface IndyIssueCredentialFormat { - credentialDefinitionId?: string - attributes?: CredentialPreviewAttributeOptions[] -} - -export interface IndyCredentialFormat extends CredentialFormat { - formatKey: 'indy' - credentialRecordType: 'indy' - credentialFormats: { - createProposal: IndyProposeCredentialFormat - acceptProposal: IndyAcceptProposalFormat - createOffer: IndyOfferCredentialFormat - acceptOffer: IndyAcceptOfferFormat - createRequest: never // cannot start from createRequest - acceptRequest: Record // empty object - } - // Format data is based on RFC 0592 - // https://github.com/hyperledger/aries-rfcs/tree/main/features/0592-indy-attachments - formatData: { - proposal: { - schema_issuer_did?: string - schema_name?: string - schema_version?: string - schema_id?: string - issuer_did?: string - cred_def_id?: string - } - offer: CredOffer - request: CredReq - credential: Cred - } -} diff --git a/packages/core/src/modules/credentials/formats/indy/IndyCredentialFormatService.ts b/packages/core/src/modules/credentials/formats/indy/IndyCredentialFormatService.ts deleted file mode 100644 index 82a3741b04..0000000000 --- a/packages/core/src/modules/credentials/formats/indy/IndyCredentialFormatService.ts +++ /dev/null @@ -1,577 +0,0 @@ -import type { AgentContext } from '../../../../agent' -import type { LinkedAttachment } from '../../../../utils/LinkedAttachment' -import type { CredentialPreviewAttributeOptions } from '../../models/CredentialPreviewAttribute' -import type { CredentialExchangeRecord } from '../../repository/CredentialExchangeRecord' -import type { CredentialFormatService } from '../CredentialFormatService' -import type { - FormatAcceptOfferOptions, - FormatAcceptProposalOptions, - FormatAcceptRequestOptions, - FormatAutoRespondCredentialOptions, - FormatAutoRespondOfferOptions, - FormatAutoRespondProposalOptions, - FormatAutoRespondRequestOptions, - FormatCreateOfferOptions, - FormatCreateOfferReturn, - FormatCreateProposalOptions, - FormatCreateProposalReturn, - CredentialFormatCreateReturn, - FormatProcessOptions, - FormatProcessCredentialOptions, -} from '../CredentialFormatServiceOptions' -import type { IndyCredentialFormat } from './IndyCredentialFormat' -import type * as Indy from 'indy-sdk' - -import { KeyType } from '../../../../crypto' -import { V1Attachment, V1AttachmentData } from '../../../../decorators/attachment/V1Attachment' -import { AriesFrameworkError } from '../../../../error' -import { JsonEncoder } from '../../../../utils/JsonEncoder' -import { JsonTransformer } from '../../../../utils/JsonTransformer' -import { MessageValidator } from '../../../../utils/MessageValidator' -import { TypedArrayEncoder } from '../../../../utils/TypedArrayEncoder' -import { getIndyDidFromVerificationMethod } from '../../../../utils/did' -import { uuid } from '../../../../utils/uuid' -import { ConnectionService } from '../../../connections' -import { DidResolverService, findVerificationMethodByKeyType } from '../../../dids' -import { IndyHolderService } from '../../../indy/services/IndyHolderService' -import { IndyIssuerService } from '../../../indy/services/IndyIssuerService' -import { IndyLedgerService } from '../../../ledger' -import { CredentialProblemReportError, CredentialProblemReportReason } from '../../errors' -import { CredentialFormatSpec } from '../../models/CredentialFormatSpec' -import { CredentialPreviewAttribute } from '../../models/CredentialPreviewAttribute' -import { CredentialMetadataKeys } from '../../repository/CredentialMetadataTypes' - -import { IndyCredentialUtils } from './IndyCredentialUtils' -import { IndyCredPropose } from './models/IndyCredPropose' - -const INDY_CRED_ABSTRACT = 'hlindy/cred-abstract@v2.0' -const INDY_CRED_REQUEST = 'hlindy/cred-req@v2.0' -const INDY_CRED_FILTER = 'hlindy/cred-filter@v2.0' -const INDY_CRED = 'hlindy/cred@v2.0' - -export class IndyCredentialFormatService implements CredentialFormatService { - public readonly formatKey = 'indy' as const - public readonly credentialRecordType = 'indy' as const - - /** - * Create a {@link AttachmentFormats} object dependent on the message type. - * - * @param options The object containing all the options for the proposed credential - * @returns object containing associated attachment, format and optionally the credential preview - * - */ - public async createProposal( - agentContext: AgentContext, - { credentialFormats, credentialRecord }: FormatCreateProposalOptions - ): Promise { - const format = new CredentialFormatSpec({ - format: INDY_CRED_FILTER, - }) - - const indyFormat = credentialFormats.indy - - if (!indyFormat) { - throw new AriesFrameworkError('Missing indy payload in createProposal') - } - - // eslint-disable-next-line @typescript-eslint/no-unused-vars - const { attributes, linkedAttachments, ...indyCredentialProposal } = indyFormat - - const proposal = new IndyCredPropose(indyCredentialProposal) - - try { - MessageValidator.validateSync(proposal) - } catch (error) { - throw new AriesFrameworkError(`Invalid proposal supplied: ${indyCredentialProposal} in Indy Format Service`) - } - - const proposalJson = JsonTransformer.toJSON(proposal) - const attachment = this.getFormatData(proposalJson, format.attachId) - - const { previewAttributes } = this.getCredentialLinkedAttachments( - indyFormat.attributes, - indyFormat.linkedAttachments - ) - - // Set the metadata - credentialRecord.metadata.set(CredentialMetadataKeys.IndyCredential, { - schemaId: proposal.schemaId, - credentialDefinitionId: proposal.credentialDefinitionId, - }) - - return { format, attachment, previewAttributes } - } - - public async processProposal(agentContext: AgentContext, { attachment }: FormatProcessOptions): Promise { - const proposalJson = attachment.getDataAsJson() - - // fromJSON also validates - JsonTransformer.fromJSON(proposalJson, IndyCredPropose) - } - - public async acceptProposal( - agentContext: AgentContext, - { - attachId, - credentialFormats, - credentialRecord, - proposalAttachment, - }: FormatAcceptProposalOptions - ): Promise { - const indyFormat = credentialFormats?.indy - - const credentialProposal = JsonTransformer.fromJSON(proposalAttachment.getDataAsJson(), IndyCredPropose) - - const credentialDefinitionId = indyFormat?.credentialDefinitionId ?? credentialProposal.credentialDefinitionId - const attributes = indyFormat?.attributes ?? credentialRecord.credentialAttributes - - if (!credentialDefinitionId) { - throw new AriesFrameworkError( - 'No credentialDefinitionId in proposal or provided as input to accept proposal method.' - ) - } - - if (!attributes) { - throw new AriesFrameworkError('No attributes in proposal or provided as input to accept proposal method.') - } - - const { format, attachment, previewAttributes } = await this.createIndyOffer(agentContext, { - credentialRecord, - attachId, - attributes, - credentialDefinitionId: credentialDefinitionId, - linkedAttachments: indyFormat?.linkedAttachments, - }) - - return { format, attachment, previewAttributes } - } - - /** - * Create a credential attachment format for a credential request. - * - * @param options The object containing all the options for the credential offer - * @returns object containing associated attachment, formats and offersAttach elements - * - */ - public async createOffer( - agentContext: AgentContext, - { credentialFormats, credentialRecord, attachId }: FormatCreateOfferOptions - ): Promise { - const indyFormat = credentialFormats.indy - - if (!indyFormat) { - throw new AriesFrameworkError('Missing indy credentialFormat data') - } - - const { format, attachment, previewAttributes } = await this.createIndyOffer(agentContext, { - credentialRecord, - attachId, - attributes: indyFormat.attributes, - credentialDefinitionId: indyFormat.credentialDefinitionId, - linkedAttachments: indyFormat.linkedAttachments, - }) - - return { format, attachment, previewAttributes } - } - - public async processOffer(agentContext: AgentContext, { attachment, credentialRecord }: FormatProcessOptions) { - agentContext.config.logger.debug(`Processing indy credential offer for credential record ${credentialRecord.id}`) - - const credOffer = attachment.getDataAsJson() - - if (!credOffer.schema_id || !credOffer.cred_def_id) { - throw new CredentialProblemReportError('Invalid credential offer', { - problemCode: CredentialProblemReportReason.IssuanceAbandoned, - }) - } - } - - public async acceptOffer( - agentContext: AgentContext, - { credentialFormats, credentialRecord, attachId, offerAttachment }: FormatAcceptOfferOptions - ): Promise { - const indyFormat = credentialFormats?.indy - - const indyLedgerService = agentContext.dependencyManager.resolve(IndyLedgerService) - const indyHolderService = agentContext.dependencyManager.resolve(IndyHolderService) - - const holderDid = indyFormat?.holderDid ?? (await this.getIndyHolderDid(agentContext, credentialRecord)) - - const credentialOffer = offerAttachment.getDataAsJson() - const credentialDefinition = await indyLedgerService.getCredentialDefinition( - agentContext, - credentialOffer.cred_def_id - ) - - const [credentialRequest, credentialRequestMetadata] = await indyHolderService.createCredentialRequest( - agentContext, - { - holderDid, - credentialOffer, - credentialDefinition, - } - ) - - credentialRecord.metadata.set(CredentialMetadataKeys.IndyRequest, credentialRequestMetadata) - credentialRecord.metadata.set(CredentialMetadataKeys.IndyCredential, { - credentialDefinitionId: credentialOffer.cred_def_id, - schemaId: credentialOffer.schema_id, - }) - - const format = new CredentialFormatSpec({ - attachId, - format: INDY_CRED_REQUEST, - }) - - const attachment = this.getFormatData(credentialRequest, format.attachId) - return { format, attachment } - } - - /** - * Starting from a request is not supported for indy credentials, this method only throws an error. - */ - public async createRequest(): Promise { - throw new AriesFrameworkError('Starting from a request is not supported for indy credentials') - } - - /** - * We don't have any models to validate an indy request object, for now this method does nothing - */ - // eslint-disable-next-line @typescript-eslint/no-unused-vars - public async processRequest(agentContext: AgentContext, options: FormatProcessOptions): Promise { - // not needed for Indy - } - - public async acceptRequest( - agentContext: AgentContext, - { credentialRecord, attachId, offerAttachment, requestAttachment }: FormatAcceptRequestOptions - ): Promise { - // Assert credential attributes - const credentialAttributes = credentialRecord.credentialAttributes - if (!credentialAttributes) { - throw new CredentialProblemReportError( - `Missing required credential attribute values on credential record with id ${credentialRecord.id}`, - { problemCode: CredentialProblemReportReason.IssuanceAbandoned } - ) - } - - const indyIssuerService = agentContext.dependencyManager.resolve(IndyIssuerService) - - const credentialOffer = offerAttachment?.getDataAsJson() - const credentialRequest = requestAttachment.getDataAsJson() - - if (!credentialOffer || !credentialRequest) { - throw new AriesFrameworkError('Missing indy credential offer or credential request in createCredential') - } - - const [credential, credentialRevocationId] = await indyIssuerService.createCredential(agentContext, { - credentialOffer, - credentialRequest, - credentialValues: IndyCredentialUtils.convertAttributesToValues(credentialAttributes), - }) - - if (credential.rev_reg_id) { - credentialRecord.metadata.add(CredentialMetadataKeys.IndyCredential, { - indyCredentialRevocationId: credentialRevocationId, - indyRevocationRegistryId: credential.rev_reg_id, - }) - } - - const format = new CredentialFormatSpec({ - attachId, - format: INDY_CRED, - }) - - const attachment = this.getFormatData(credential, format.attachId) - return { format, attachment } - } - - /** - * Processes an incoming credential - retrieve metadata, retrieve payload and store it in the Indy wallet - * @param options the issue credential message wrapped inside this object - * @param credentialRecord the credential exchange record for this credential - */ - public async processCredential( - agentContext: AgentContext, - { credentialRecord, attachment }: FormatProcessCredentialOptions - ): Promise { - const credentialRequestMetadata = credentialRecord.metadata.get(CredentialMetadataKeys.IndyRequest) - - const indyLedgerService = agentContext.dependencyManager.resolve(IndyLedgerService) - const indyHolderService = agentContext.dependencyManager.resolve(IndyHolderService) - - if (!credentialRequestMetadata) { - throw new CredentialProblemReportError( - `Missing required request metadata for credential with id ${credentialRecord.id}`, - { problemCode: CredentialProblemReportReason.IssuanceAbandoned } - ) - } - - const indyCredential = attachment.getDataAsJson() - const credentialDefinition = await indyLedgerService.getCredentialDefinition( - agentContext, - indyCredential.cred_def_id - ) - const revocationRegistry = indyCredential.rev_reg_id - ? await indyLedgerService.getRevocationRegistryDefinition(agentContext, indyCredential.rev_reg_id) - : null - - if (!credentialRecord.credentialAttributes) { - throw new AriesFrameworkError( - 'Missing credential attributes on credential record. Unable to check credential attributes' - ) - } - - // assert the credential values match the offer values - const recordCredentialValues = IndyCredentialUtils.convertAttributesToValues(credentialRecord.credentialAttributes) - IndyCredentialUtils.assertValuesMatch(indyCredential.values, recordCredentialValues) - - const credentialId = await indyHolderService.storeCredential(agentContext, { - credentialId: uuid(), - credentialRequestMetadata, - credential: indyCredential, - credentialDefinition, - revocationRegistryDefinition: revocationRegistry?.revocationRegistryDefinition, - }) - - // If the credential is revocable, store the revocation identifiers in the credential record - if (indyCredential.rev_reg_id) { - const credential = await indyHolderService.getCredential(agentContext, credentialId) - - credentialRecord.metadata.add(CredentialMetadataKeys.IndyCredential, { - indyCredentialRevocationId: credential.cred_rev_id, - indyRevocationRegistryId: indyCredential.rev_reg_id, - }) - } - - credentialRecord.credentials.push({ - credentialRecordType: this.credentialRecordType, - credentialRecordId: credentialId, - }) - } - - public supportsFormat(format: string): boolean { - const supportedFormats = [INDY_CRED_ABSTRACT, INDY_CRED_REQUEST, INDY_CRED_FILTER, INDY_CRED] - - return supportedFormats.includes(format) - } - - /** - * Gets the attachment object for a given attachId. We need to get out the correct attachId for - * indy and then find the corresponding attachment (if there is one) - * @param formats the formats object containing the attachId - * @param messageAttachments the attachments containing the payload - * @returns The Attachment if found or undefined - * - */ - public getAttachment(formats: CredentialFormatSpec[], messageAttachments: V1Attachment[]): V1Attachment | undefined { - const supportedAttachmentIds = formats.filter((f) => this.supportsFormat(f.format)).map((f) => f.attachId) - const supportedAttachments = messageAttachments.filter((attachment) => - supportedAttachmentIds.includes(attachment.id) - ) - - return supportedAttachments[0] - } - - public async deleteCredentialById(agentContext: AgentContext, credentialRecordId: string): Promise { - const indyHolderService = agentContext.dependencyManager.resolve(IndyHolderService) - - await indyHolderService.deleteCredential(agentContext, credentialRecordId) - } - - public shouldAutoRespondToProposal( - agentContext: AgentContext, - { offerAttachment, proposalAttachment }: FormatAutoRespondProposalOptions - ) { - const credentialProposalJson = proposalAttachment.getDataAsJson() - const credentialProposal = JsonTransformer.fromJSON(credentialProposalJson, IndyCredPropose) - - const credentialOfferJson = offerAttachment.getDataAsJson() - - // We want to make sure the credential definition matches. - // TODO: If no credential definition is present on the proposal, we could check whether the other fields - // of the proposal match with the credential definition id. - return credentialProposal.credentialDefinitionId === credentialOfferJson.cred_def_id - } - - public shouldAutoRespondToOffer( - agentContext: AgentContext, - { offerAttachment, proposalAttachment }: FormatAutoRespondOfferOptions - ) { - const credentialProposalJson = proposalAttachment.getDataAsJson() - const credentialProposal = JsonTransformer.fromJSON(credentialProposalJson, IndyCredPropose) - - const credentialOfferJson = offerAttachment.getDataAsJson() - - // We want to make sure the credential definition matches. - // TODO: If no credential definition is present on the proposal, we could check whether the other fields - // of the proposal match with the credential definition id. - return credentialProposal.credentialDefinitionId === credentialOfferJson.cred_def_id - } - - public shouldAutoRespondToRequest( - agentContext: AgentContext, - { offerAttachment, requestAttachment }: FormatAutoRespondRequestOptions - ) { - const credentialOfferJson = offerAttachment.getDataAsJson() - const credentialRequestJson = requestAttachment.getDataAsJson() - - return credentialOfferJson.cred_def_id == credentialRequestJson.cred_def_id - } - - public shouldAutoRespondToCredential( - agentContext: AgentContext, - { credentialRecord, requestAttachment, credentialAttachment }: FormatAutoRespondCredentialOptions - ) { - const credentialJson = credentialAttachment.getDataAsJson() - const credentialRequestJson = requestAttachment.getDataAsJson() - - // make sure the credential definition matches - if (credentialJson.cred_def_id !== credentialRequestJson.cred_def_id) return false - - // If we don't have any attributes stored we can't compare so always return false. - if (!credentialRecord.credentialAttributes) return false - const attributeValues = IndyCredentialUtils.convertAttributesToValues(credentialRecord.credentialAttributes) - - // check whether the values match the values in the record - return IndyCredentialUtils.checkValuesMatch(attributeValues, credentialJson.values) - } - - private async createIndyOffer( - agentContext: AgentContext, - { - credentialRecord, - attachId, - credentialDefinitionId, - attributes, - linkedAttachments, - }: { - credentialDefinitionId: string - credentialRecord: CredentialExchangeRecord - attachId?: string - attributes: CredentialPreviewAttributeOptions[] - linkedAttachments?: LinkedAttachment[] - } - ): Promise { - const indyIssuerService = agentContext.dependencyManager.resolve(IndyIssuerService) - - // if the proposal has an attachment Id use that, otherwise the generated id of the formats object - const format = new CredentialFormatSpec({ - attachId: attachId, - format: INDY_CRED_ABSTRACT, - }) - - const offer = await indyIssuerService.createCredentialOffer(agentContext, credentialDefinitionId) - - const { previewAttributes } = this.getCredentialLinkedAttachments(attributes, linkedAttachments) - if (!previewAttributes) { - throw new AriesFrameworkError('Missing required preview attributes for indy offer') - } - - await this.assertPreviewAttributesMatchSchemaAttributes(agentContext, offer, previewAttributes) - - credentialRecord.metadata.set(CredentialMetadataKeys.IndyCredential, { - schemaId: offer.schema_id, - credentialDefinitionId: offer.cred_def_id, - }) - - const attachment = this.getFormatData(offer, format.attachId) - - return { format, attachment, previewAttributes } - } - - private async assertPreviewAttributesMatchSchemaAttributes( - agentContext: AgentContext, - offer: Indy.CredOffer, - attributes: CredentialPreviewAttribute[] - ): Promise { - const indyLedgerService = agentContext.dependencyManager.resolve(IndyLedgerService) - - const schema = await indyLedgerService.getSchema(agentContext, offer.schema_id) - - IndyCredentialUtils.checkAttributesMatch(schema, attributes) - } - - private async getIndyHolderDid(agentContext: AgentContext, credentialRecord: CredentialExchangeRecord) { - const connectionService = agentContext.dependencyManager.resolve(ConnectionService) - const didResolver = agentContext.dependencyManager.resolve(DidResolverService) - - // If we have a connection id we try to extract the did from the connection did document. - if (credentialRecord.connectionId) { - const connection = await connectionService.getById(agentContext, credentialRecord.connectionId) - if (!connection.did) { - throw new AriesFrameworkError(`Connection record ${connection.id} has no 'did'`) - } - const resolved = await didResolver.resolve(agentContext, connection.did) - - if (resolved.didDocument) { - const verificationMethod = await findVerificationMethodByKeyType( - 'Ed25519VerificationKey2018', - resolved.didDocument - ) - - if (verificationMethod) { - return getIndyDidFromVerificationMethod(verificationMethod) - } - } - } - - // If it wasn't successful to extract the did from the connection, we'll create a new key (e.g. if using connection-less) - // FIXME: we already create a did for the exchange when using connection-less, but this is on a higher level. We should look at - // a way to reuse this key, but for now this is easier. - const key = await agentContext.wallet.createKey({ keyType: KeyType.Ed25519 }) - const did = TypedArrayEncoder.toBase58(key.publicKey.slice(0, 16)) - - return did - } - - /** - * Get linked attachments for indy format from a proposal message. This allows attachments - * to be copied across to old style credential records - * - * @param options ProposeCredentialOptions object containing (optionally) the linked attachments - * @return array of linked attachments or undefined if none present - */ - private getCredentialLinkedAttachments( - attributes?: CredentialPreviewAttributeOptions[], - linkedAttachments?: LinkedAttachment[] - ): { - attachments?: V1Attachment[] - previewAttributes?: CredentialPreviewAttribute[] - } { - if (!linkedAttachments && !attributes) { - return {} - } - - let previewAttributes = attributes?.map((attribute) => new CredentialPreviewAttribute(attribute)) ?? [] - let attachments: V1Attachment[] | undefined - - if (linkedAttachments) { - // there are linked attachments so transform into the attribute field of the CredentialPreview object for - // this proposal - previewAttributes = IndyCredentialUtils.createAndLinkAttachmentsToPreview(linkedAttachments, previewAttributes) - attachments = linkedAttachments.map((linkedAttachment) => linkedAttachment.attachment) - } - - return { attachments, previewAttributes } - } - - /** - * Returns an object of type {@link V1Attachment} for use in credential exchange messages. - * It looks up the correct format identifier and encodes the data as a base64 attachment. - * - * @param data The data to include in the attach object - * @param id the attach id from the formats component of the message - */ - public getFormatData(data: unknown, id: string): V1Attachment { - const attachment = new V1Attachment({ - id, - mimeType: 'application/json', - data: new V1AttachmentData({ - base64: JsonEncoder.toBase64(data), - }), - }) - - return attachment - } -} diff --git a/packages/core/src/modules/credentials/formats/indy/IndyCredentialUtils.ts b/packages/core/src/modules/credentials/formats/indy/IndyCredentialUtils.ts deleted file mode 100644 index 34042333e8..0000000000 --- a/packages/core/src/modules/credentials/formats/indy/IndyCredentialUtils.ts +++ /dev/null @@ -1,207 +0,0 @@ -import type { LinkedAttachment } from '../../../../utils/LinkedAttachment' -import type { CredValues, Schema } from 'indy-sdk' - -import BigNumber from 'bn.js' - -import { AriesFrameworkError } from '../../../../error/AriesFrameworkError' -import { Hasher } from '../../../../utils' -import { encodeAttachment } from '../../../../utils/attachment' -import { Buffer } from '../../../../utils/buffer' -import { isBoolean, isNumber, isString } from '../../../../utils/type' -import { CredentialPreviewAttribute } from '../../models/CredentialPreviewAttribute' - -export class IndyCredentialUtils { - /** - * Adds attribute(s) to the credential preview that is linked to the given attachment(s) - * - * @param attachments a list of the attachments that need to be linked to a credential - * @param preview the credential previews where the new linked credential has to be appended to - * - * @returns a modified version of the credential preview with the linked credentials - * */ - public static createAndLinkAttachmentsToPreview( - attachments: LinkedAttachment[], - previewAttributes: CredentialPreviewAttribute[] - ) { - const credentialPreviewAttributeNames = previewAttributes.map((attribute) => attribute.name) - const newPreviewAttributes = [...previewAttributes] - - attachments.forEach((linkedAttachment) => { - if (credentialPreviewAttributeNames.includes(linkedAttachment.attributeName)) { - throw new AriesFrameworkError( - `linkedAttachment ${linkedAttachment.attributeName} already exists in the preview` - ) - } else { - const credentialPreviewAttribute = new CredentialPreviewAttribute({ - name: linkedAttachment.attributeName, - mimeType: linkedAttachment.attachment.mimeType, - value: encodeAttachment(linkedAttachment.attachment), - }) - newPreviewAttributes.push(credentialPreviewAttribute) - } - }) - - return newPreviewAttributes - } - - /** - * Converts int value to string - * Converts string value: - * - hash with sha256, - * - convert to byte array and reverse it - * - convert it to BigInteger and return as a string - * @param attributes - * - * @returns CredValues - */ - public static convertAttributesToValues(attributes: CredentialPreviewAttribute[]): CredValues { - return attributes.reduce((credentialValues, attribute) => { - return { - [attribute.name]: { - raw: attribute.value, - encoded: IndyCredentialUtils.encode(attribute.value), - }, - ...credentialValues, - } - }, {}) - } - - /** - * Check whether the values of two credentials match (using {@link assertValuesMatch}) - * - * @returns a boolean whether the values are equal - * - */ - public static checkValuesMatch(firstValues: CredValues, secondValues: CredValues): boolean { - try { - this.assertValuesMatch(firstValues, secondValues) - return true - } catch { - return false - } - } - - /** - * Assert two credential values objects match. - * - * @param firstValues The first values object - * @param secondValues The second values object - * - * @throws If not all values match - */ - public static assertValuesMatch(firstValues: CredValues, secondValues: CredValues) { - const firstValuesKeys = Object.keys(firstValues) - const secondValuesKeys = Object.keys(secondValues) - - if (firstValuesKeys.length !== secondValuesKeys.length) { - throw new Error( - `Number of values in first entry (${firstValuesKeys.length}) does not match number of values in second entry (${secondValuesKeys.length})` - ) - } - - for (const key of firstValuesKeys) { - const firstValue = firstValues[key] - const secondValue = secondValues[key] - - if (!secondValue) { - throw new Error(`Second cred values object has no value for key '${key}'`) - } - - if (firstValue.encoded !== secondValue.encoded) { - throw new Error(`Encoded credential values for key '${key}' do not match`) - } - - if (firstValue.raw !== secondValue.raw) { - throw new Error(`Raw credential values for key '${key}' do not match`) - } - } - } - - /** - * Check whether the raw value matches the encoded version according to the encoding format described in Aries RFC 0037 - * Use this method to ensure the received proof (over the encoded) value is the same as the raw value of the data. - * - * @param raw - * @param encoded - * @returns Whether raw and encoded value match - * - * @see https://github.com/hyperledger/aries-framework-dotnet/blob/a18bef91e5b9e4a1892818df7408e2383c642dfa/src/Hyperledger.Aries/Utils/CredentialUtils.cs#L78-L89 - * @see https://github.com/hyperledger/aries-rfcs/blob/be4ad0a6fb2823bb1fc109364c96f077d5d8dffa/features/0037-present-proof/README.md#verifying-claims-of-indy-based-verifiable-credentials - */ - public static checkValidEncoding(raw: unknown, encoded: string) { - return encoded === IndyCredentialUtils.encode(raw) - } - - /** - * Encode value according to the encoding format described in Aries RFC 0036/0037 - * - * @param value - * @returns Encoded version of value - * - * @see https://github.com/hyperledger/aries-cloudagent-python/blob/0000f924a50b6ac5e6342bff90e64864672ee935/aries_cloudagent/messaging/util.py#L106-L136 - * @see https://github.com/hyperledger/aries-rfcs/blob/be4ad0a6fb2823bb1fc109364c96f077d5d8dffa/features/0037-present-proof/README.md#verifying-claims-of-indy-based-verifiable-credentials - * @see https://github.com/hyperledger/aries-rfcs/blob/be4ad0a6fb2823bb1fc109364c96f077d5d8dffa/features/0036-issue-credential/README.md#encoding-claims-for-indy-based-verifiable-credentials - */ - public static encode(value: unknown) { - const isEmpty = (value: unknown) => isString(value) && value === '' - - // If bool return bool as number string - if (isBoolean(value)) { - return Number(value).toString() - } - - // If value is int32 return as number string - if (isNumber(value) && this.isInt32(value)) { - return value.toString() - } - - // If value is an int32 number string return as number string - if ( - isString(value) && - !isEmpty(value) && - !isNaN(Number(value)) && - this.isNumeric(value) && - this.isInt32(Number(value)) - ) { - return Number(value).toString() - } - - if (isNumber(value)) { - value = value.toString() - } - - // If value is null we must use the string value 'None' - if (value === null || value === undefined) { - value = 'None' - } - - return new BigNumber(Hasher.hash(Buffer.from(value as string), 'sha2-256')).toString() - } - - public static checkAttributesMatch(schema: Schema, attributes: CredentialPreviewAttribute[]) { - const schemaAttributes = schema.attrNames - const credAttributes = attributes.map((a) => a.name) - - const difference = credAttributes - .filter((x) => !schemaAttributes.includes(x)) - .concat(schemaAttributes.filter((x) => !credAttributes.includes(x))) - - if (difference.length > 0) { - throw new AriesFrameworkError( - `The credential preview attributes do not match the schema attributes (difference is: ${difference}, needs: ${schemaAttributes})` - ) - } - } - - private static isInt32(number: number) { - const minI32 = -2147483648 - const maxI32 = 2147483647 - - // Check if number is integer and in range of int32 - return Number.isInteger(number) && number >= minI32 && number <= maxI32 - } - - private static isNumeric(value: string) { - return /^-?\d+$/.test(value) - } -} diff --git a/packages/core/src/modules/credentials/formats/indy/index.ts b/packages/core/src/modules/credentials/formats/indy/index.ts deleted file mode 100644 index f79b7ee9c2..0000000000 --- a/packages/core/src/modules/credentials/formats/indy/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export * from './models' -export * from './IndyCredentialFormatService' -export * from './IndyCredentialFormat' diff --git a/packages/core/src/modules/credentials/formats/indy/models/IndyCredential.ts b/packages/core/src/modules/credentials/formats/indy/models/IndyCredential.ts deleted file mode 100644 index 7c1addefb4..0000000000 --- a/packages/core/src/modules/credentials/formats/indy/models/IndyCredential.ts +++ /dev/null @@ -1,34 +0,0 @@ -import type * as Indy from 'indy-sdk' - -import { Expose, Type } from 'class-transformer' -import { IsInstance, IsOptional, ValidateNested } from 'class-validator' - -import { JsonTransformer } from '../../../../../utils/JsonTransformer' - -import { IndyCredentialInfo } from './IndyCredentialInfo' -import { IndyRevocationInterval } from './IndyRevocationInterval' - -export class IndyCredential { - public constructor(options: IndyCredential) { - if (options) { - this.credentialInfo = options.credentialInfo - this.interval = options.interval - } - } - - @Expose({ name: 'cred_info' }) - @Type(() => IndyCredentialInfo) - @ValidateNested() - @IsInstance(IndyCredentialInfo) - public credentialInfo!: IndyCredentialInfo - - @IsOptional() - @Type(() => IndyRevocationInterval) - @ValidateNested() - @IsInstance(IndyRevocationInterval) - public interval?: IndyRevocationInterval - - public toJSON(): Indy.IndyCredential { - return JsonTransformer.toJSON(this) as unknown as Indy.IndyCredential - } -} diff --git a/packages/core/src/modules/credentials/formats/indy/models/IndyCredentialInfo.ts b/packages/core/src/modules/credentials/formats/indy/models/IndyCredentialInfo.ts deleted file mode 100644 index fcd83e23bc..0000000000 --- a/packages/core/src/modules/credentials/formats/indy/models/IndyCredentialInfo.ts +++ /dev/null @@ -1,51 +0,0 @@ -import type { IndyCredentialInfo as IndySDKCredentialInfo } from 'indy-sdk' - -import { Expose } from 'class-transformer' -import { IsOptional, IsString } from 'class-validator' - -import { JsonTransformer } from '../../../../../utils' - -export class IndyCredentialInfo { - public constructor(options: IndyCredentialInfo) { - if (options) { - this.referent = options.referent - this.attributes = options.attributes - this.schemaId = options.schemaId - this.credentialDefinitionId = options.credentialDefinitionId - this.revocationRegistryId = options.revocationRegistryId - this.credentialRevocationId = options.credentialRevocationId - } - } - - /** - * Credential ID in the wallet - */ - @IsString() - public referent!: string - - @Expose({ name: 'attrs' }) - @IsString({ each: true }) - public attributes!: Record - - @Expose({ name: 'schema_id' }) - @IsString() - public schemaId!: string - - @Expose({ name: 'cred_def_id' }) - @IsString() - public credentialDefinitionId!: string - - @Expose({ name: 'rev_reg_id' }) - @IsString() - @IsOptional() - public revocationRegistryId?: string - - @Expose({ name: 'cred_rev_id' }) - @IsString() - @IsOptional() - public credentialRevocationId?: string - - public toJSON(): IndySDKCredentialInfo { - return JsonTransformer.toJSON(this) as unknown as IndySDKCredentialInfo - } -} diff --git a/packages/core/src/modules/credentials/formats/indy/models/IndyCredentialView.ts b/packages/core/src/modules/credentials/formats/indy/models/IndyCredentialView.ts deleted file mode 100644 index e96dd32b23..0000000000 --- a/packages/core/src/modules/credentials/formats/indy/models/IndyCredentialView.ts +++ /dev/null @@ -1,24 +0,0 @@ -import type { V1Attachment } from '../../../../../decorators/attachment/V1Attachment' - -export interface IndyCredentialViewModel { - metadata?: IndyCredentialViewMetadata | null - claims: Record - attachments?: V1Attachment[] -} - -export interface IndyCredentialViewMetadata { - credentialDefinitionId?: string - schemaId?: string -} - -export class IndyCredentialView { - public constructor(options: IndyCredentialViewModel) { - this.metadata = options.metadata ?? {} - this.claims = options.claims - this.attachments = options.attachments - } - - public metadata: IndyCredentialViewMetadata - public claims: Record - public attachments?: V1Attachment[] -} diff --git a/packages/core/src/modules/credentials/formats/indy/models/__tests__/IndyCredentialView.test.ts b/packages/core/src/modules/credentials/formats/indy/models/__tests__/IndyCredentialView.test.ts deleted file mode 100644 index 840099d41d..0000000000 --- a/packages/core/src/modules/credentials/formats/indy/models/__tests__/IndyCredentialView.test.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { IndyCredentialView } from '../IndyCredentialView' - -describe('CredentialInfo', () => { - it('should return the correct property values', () => { - const claims = { - name: 'Timo', - date_of_birth: '1998-07-29', - 'country-of-residence': 'The Netherlands', - 'street name': 'Test street', - age: '22', - } - const metadata = { - credentialDefinitionId: 'Th7MpTaRZVRYnPiabds81Y:3:CL:17:TAG', - schemaId: 'TL1EaPFCZ8Si5aUrqScBDt:2:test-schema-1599055118161:1.0', - } - const credentialInfo = new IndyCredentialView({ - claims, - metadata, - }) - - expect(credentialInfo.claims).toEqual(claims) - expect(credentialInfo.metadata).toEqual(metadata) - }) -}) diff --git a/packages/core/src/modules/credentials/formats/indy/models/index.ts b/packages/core/src/modules/credentials/formats/indy/models/index.ts deleted file mode 100644 index 8f63220f10..0000000000 --- a/packages/core/src/modules/credentials/formats/indy/models/index.ts +++ /dev/null @@ -1,5 +0,0 @@ -export * from './IndyCredential' -export * from './IndyCredentialInfo' -export * from './IndyRevocationInterval' -export * from './IndyCredentialView' -export * from './IndyCredPropose' diff --git a/packages/core/src/modules/credentials/formats/jsonld/JsonLdCredentialFormatService.ts b/packages/core/src/modules/credentials/formats/jsonld/JsonLdCredentialFormatService.ts index 77300d1482..ace6416ee1 100644 --- a/packages/core/src/modules/credentials/formats/jsonld/JsonLdCredentialFormatService.ts +++ b/packages/core/src/modules/credentials/formats/jsonld/JsonLdCredentialFormatService.ts @@ -1,28 +1,28 @@ -import type { AgentContext } from '../../../../agent' -import type { CredentialFormatService } from '../CredentialFormatService' -import type { - FormatAcceptOfferOptions, - FormatAcceptProposalOptions, - FormatAcceptRequestOptions, - FormatAutoRespondOfferOptions, - FormatAutoRespondProposalOptions, - FormatAutoRespondRequestOptions, - FormatCreateOfferOptions, - FormatCreateOfferReturn, - FormatCreateProposalOptions, - FormatCreateProposalReturn, - FormatCreateRequestOptions, - CredentialFormatCreateReturn, - FormatProcessCredentialOptions, - FormatProcessOptions, - FormatAutoRespondCredentialOptions, -} from '../CredentialFormatServiceOptions' import type { JsonLdCredentialFormat, JsonCredential, JsonLdFormatDataCredentialDetail, JsonLdFormatDataVerifiableCredential, } from './JsonLdCredentialFormat' +import type { AgentContext } from '../../../../agent' +import type { CredentialFormatService } from '../CredentialFormatService' +import type { + CredentialFormatAcceptOfferOptions, + CredentialFormatAcceptProposalOptions, + CredentialFormatAcceptRequestOptions, + CredentialFormatAutoRespondOfferOptions, + CredentialFormatAutoRespondProposalOptions, + CredentialFormatAutoRespondRequestOptions, + CredentialFormatCreateOfferOptions, + CredentialFormatCreateOfferReturn, + CredentialFormatCreateProposalOptions, + CredentialFormatCreateProposalReturn, + CredentialFormatCreateRequestOptions, + CredentialFormatCreateReturn, + CredentialFormatProcessCredentialOptions, + CredentialFormatProcessOptions, + CredentialFormatAutoRespondCredentialOptions, +} from '../CredentialFormatServiceOptions' import { V1Attachment, V1AttachmentData } from '../../../../decorators/attachment/V1Attachment' import { AriesFrameworkError } from '../../../../error' @@ -37,7 +37,7 @@ import { CredentialFormatSpec } from '../../models/CredentialFormatSpec' import { JsonLdCredentialDetail } from './JsonLdCredentialDetail' const JSONLD_VC_DETAIL = 'aries/ld-proof-vc-detail@v1.0' -const JSONLD_VC = 'aries/ld-proof-vc@1.0' +const JSONLD_VC = 'aries/ld-proof-vc@v1.0' export class JsonLdCredentialFormatService implements CredentialFormatService { public readonly formatKey = 'jsonld' as const @@ -52,8 +52,8 @@ export class JsonLdCredentialFormatService implements CredentialFormatService - ): Promise { + { credentialFormats }: CredentialFormatCreateProposalOptions + ): Promise { const format = new CredentialFormatSpec({ format: JSONLD_VC_DETAIL, }) @@ -67,7 +67,7 @@ export class JsonLdCredentialFormatService implements CredentialFormatService { + public async processProposal( + agentContext: AgentContext, + { attachment }: CredentialFormatProcessOptions + ): Promise { const credProposalJson = attachment.getDataAsJson() if (!credProposalJson) { @@ -88,11 +91,11 @@ export class JsonLdCredentialFormatService implements CredentialFormatService - ): Promise { + { attachmentId, proposalAttachment }: CredentialFormatAcceptProposalOptions + ): Promise { // if the offer has an attachment Id use that, otherwise the generated id of the formats object const format = new CredentialFormatSpec({ - attachId, + attachmentId, format: JSONLD_VC_DETAIL, }) @@ -101,7 +104,7 @@ export class JsonLdCredentialFormatService implements CredentialFormatService - ): Promise { + { credentialFormats, attachmentId }: CredentialFormatCreateOfferOptions + ): Promise { // if the offer has an attachment Id use that, otherwise the generated id of the formats object const format = new CredentialFormatSpec({ - attachId, + attachmentId, format: JSONLD_VC_DETAIL, }) @@ -131,12 +134,12 @@ export class JsonLdCredentialFormatService implements CredentialFormatService() if (!credentialOfferJson) { @@ -148,7 +151,7 @@ export class JsonLdCredentialFormatService implements CredentialFormatService + { attachmentId, offerAttachment }: CredentialFormatAcceptOfferOptions ): Promise { const credentialOffer = offerAttachment.getDataAsJson() @@ -156,11 +159,11 @@ export class JsonLdCredentialFormatService implements CredentialFormatService + { credentialFormats }: CredentialFormatCreateRequestOptions ): Promise { const jsonLdFormat = credentialFormats?.jsonld @@ -188,12 +191,15 @@ export class JsonLdCredentialFormatService implements CredentialFormatService { + public async processRequest( + agentContext: AgentContext, + { attachment }: CredentialFormatProcessOptions + ): Promise { const requestJson = attachment.getDataAsJson() if (!requestJson) { @@ -206,7 +212,7 @@ export class JsonLdCredentialFormatService implements CredentialFormatService + { credentialFormats, attachmentId, requestAttachment }: CredentialFormatAcceptRequestOptions ): Promise { const w3cCredentialService = agentContext.dependencyManager.resolve(W3cCredentialService) @@ -222,7 +228,7 @@ export class JsonLdCredentialFormatService implements CredentialFormatService { const w3cCredentialService = agentContext.dependencyManager.resolve(W3cCredentialService) @@ -385,30 +391,30 @@ export class JsonLdCredentialFormatService implements CredentialFormatService() const w3cCredential = JsonTransformer.fromJSON(credentialJson, W3cVerifiableCredential) @@ -432,7 +438,7 @@ export class JsonLdCredentialFormatService implements CredentialFormatService const DidResolverServiceMock = DidResolverService as jest.Mock @@ -113,26 +106,11 @@ const mockCredentialRecord = ({ id?: string credentialAttributes?: CredentialPreviewAttribute[] } = {}) => { - const offerOptions: V2OfferCredentialMessageOptions = { - id: '', - formats: [ - { - attachId: INDY_CREDENTIAL_OFFER_ATTACHMENT_ID, - format: 'hlindy/cred-abstract@v2.0', - }, - ], - comment: 'some comment', - credentialPreview: credentialPreview, - offerAttachments: [offerAttachment], - replacementId: undefined, - } - const offerMessage = new V2OfferCredentialMessage(offerOptions) - const credentialRecord = new CredentialExchangeRecord({ id, credentialAttributes: credentialAttributes || credentialPreview.attributes, state: state || CredentialState.OfferSent, - threadId: threadId ?? offerMessage.id, + threadId: threadId ?? 'add7e1a0-109e-4f37-9caa-cfd0fcdfe540', connectionId: connectionId ?? '123', tags, protocolVersion: 'v2', @@ -220,7 +198,7 @@ describe('JsonLd CredentialFormatService', () => { }) expect(format).toMatchObject({ - attachId: expect.any(String), + attachmentId: expect.any(String), format: 'aries/ld-proof-vc-detail@v1.0', }) }) @@ -255,7 +233,7 @@ describe('JsonLd CredentialFormatService', () => { expect(previewAttributes).toBeUndefined() expect(format).toMatchObject({ - attachId: expect.any(String), + attachmentId: expect.any(String), format: 'aries/ld-proof-vc-detail@v1.0', }) }) @@ -294,7 +272,7 @@ describe('JsonLd CredentialFormatService', () => { }, }) expect(format).toMatchObject({ - attachId: expect.any(String), + attachmentId: expect.any(String), format: 'aries/ld-proof-vc-detail@v1.0', }) }) @@ -362,8 +340,8 @@ describe('JsonLd CredentialFormatService', () => { }, }) expect(format).toMatchObject({ - attachId: expect.any(String), - format: 'aries/ld-proof-vc@1.0', + attachmentId: expect.any(String), + format: 'aries/ld-proof-vc@v1.0', }) }) }) @@ -552,7 +530,7 @@ describe('JsonLd CredentialFormatService', () => { }) // indirectly test areCredentialsEqual as black box rather than expose that method in the API - let areCredentialsEqual = jsonLdFormatService.shouldAutoRespondToProposal(agentContext, { + let areCredentialsEqual = await jsonLdFormatService.shouldAutoRespondToProposal(agentContext, { credentialRecord, proposalAttachment: message1, offerAttachment: message2, @@ -570,7 +548,7 @@ describe('JsonLd CredentialFormatService', () => { base64: JsonEncoder.toBase64(inputDoc2), }) - areCredentialsEqual = jsonLdFormatService.shouldAutoRespondToProposal(agentContext, { + areCredentialsEqual = await jsonLdFormatService.shouldAutoRespondToProposal(agentContext, { credentialRecord, proposalAttachment: message1, offerAttachment: message2, diff --git a/packages/core/src/modules/credentials/models/CredentialFormatSpec.ts b/packages/core/src/modules/credentials/models/CredentialFormatSpec.ts index 85519942dd..621b0f54b4 100644 --- a/packages/core/src/modules/credentials/models/CredentialFormatSpec.ts +++ b/packages/core/src/modules/credentials/models/CredentialFormatSpec.ts @@ -4,21 +4,21 @@ import { IsString } from 'class-validator' import { uuid } from '../../../utils/uuid' export interface CredentialFormatSpecOptions { - attachId?: string + attachmentId?: string format: string } export class CredentialFormatSpec { public constructor(options: CredentialFormatSpecOptions) { if (options) { - this.attachId = options.attachId ?? uuid() + this.attachmentId = options.attachmentId ?? uuid() this.format = options.format } } @Expose({ name: 'attach_id' }) @IsString() - public attachId!: string + public attachmentId!: string @IsString() public format!: string diff --git a/packages/core/src/modules/credentials/models/CredentialPreviewAttribute.ts b/packages/core/src/modules/credentials/models/CredentialPreviewAttribute.ts index 89c3397b09..0f341785c4 100644 --- a/packages/core/src/modules/credentials/models/CredentialPreviewAttribute.ts +++ b/packages/core/src/modules/credentials/models/CredentialPreviewAttribute.ts @@ -35,5 +35,5 @@ export class CredentialPreviewAttribute { } export interface CredentialPreviewOptions { - attributes: CredentialPreviewAttribute[] + attributes: CredentialPreviewAttributeOptions[] } diff --git a/packages/core/src/modules/credentials/errors/CredentialProblemReportReason.ts b/packages/core/src/modules/credentials/models/CredentialProblemReportReason.ts similarity index 100% rename from packages/core/src/modules/credentials/errors/CredentialProblemReportReason.ts rename to packages/core/src/modules/credentials/models/CredentialProblemReportReason.ts diff --git a/packages/core/src/modules/credentials/models/CredentialState.ts b/packages/core/src/modules/credentials/models/CredentialState.ts index d1618aafa3..225f755b2f 100644 --- a/packages/core/src/modules/credentials/models/CredentialState.ts +++ b/packages/core/src/modules/credentials/models/CredentialState.ts @@ -14,4 +14,5 @@ export enum CredentialState { CredentialIssued = 'credential-issued', CredentialReceived = 'credential-received', Done = 'done', + Abandoned = 'abandoned', } diff --git a/packages/core/src/modules/credentials/models/index.ts b/packages/core/src/modules/credentials/models/index.ts index 0db2bca14c..bec3b0ce2f 100644 --- a/packages/core/src/modules/credentials/models/index.ts +++ b/packages/core/src/modules/credentials/models/index.ts @@ -3,3 +3,4 @@ export * from './CredentialPreviewAttribute' export * from './CredentialAutoAcceptType' export * from './CredentialFormatSpec' export * from './CredentialState' +export * from './CredentialProblemReportReason' diff --git a/packages/core/src/modules/credentials/protocol/BaseCredentialProtocol.ts b/packages/core/src/modules/credentials/protocol/BaseCredentialProtocol.ts index 0382f5f578..f6dd69645a 100644 --- a/packages/core/src/modules/credentials/protocol/BaseCredentialProtocol.ts +++ b/packages/core/src/modules/credentials/protocol/BaseCredentialProtocol.ts @@ -1,3 +1,19 @@ +import type { CredentialProtocol } from './CredentialProtocol' +import type { + CreateCredentialProposalOptions, + CredentialProtocolMsgReturnType, + DeleteCredentialOptions, + AcceptCredentialProposalOptions, + NegotiateCredentialProposalOptions, + CreateCredentialOfferOptions, + NegotiateCredentialOfferOptions, + CreateCredentialRequestOptions, + AcceptCredentialOfferOptions, + AcceptCredentialRequestOptions, + AcceptCredentialOptions, + GetCredentialFormatDataReturn, + CreateCredentialProblemReportOptions, +} from './CredentialProtocolOptions' import type { AgentContext } from '../../../agent' import type { FeatureRegistry } from '../../../agent/FeatureRegistry' import type { InboundMessageContext } from '../../../agent/models/InboundMessageContext' @@ -6,24 +22,8 @@ import type { DependencyManager } from '../../../plugins' import type { Query } from '../../../storage/StorageService' import type { ProblemReportMessage } from '../../problem-reports' import type { CredentialStateChangedEvent } from '../CredentialEvents' -import type { - CreateProposalOptions, - CredentialProtocolMsgReturnType, - DeleteCredentialOptions, - AcceptProposalOptions, - NegotiateProposalOptions, - CreateOfferOptions, - NegotiateOfferOptions, - CreateRequestOptions, - AcceptOfferOptions, - AcceptRequestOptions, - AcceptCredentialOptions, - GetFormatDataReturn, - CreateProblemReportOptions, -} from '../CredentialProtocolOptions' import type { CredentialFormatService, ExtractCredentialFormats } from '../formats' import type { CredentialExchangeRecord } from '../repository' -import type { CredentialProtocol } from './CredentialProtocol' import { EventEmitter } from '../../../agent/EventEmitter' import { DidCommMessageRepository } from '../../../storage' @@ -39,98 +39,97 @@ import { CredentialRepository } from '../repository' export abstract class BaseCredentialProtocol implements CredentialProtocol { - abstract readonly version: string + public abstract readonly version: string protected abstract getFormatServiceForRecordType(credentialRecordType: string): CFs[number] // methods for proposal - abstract createProposal( + public abstract createProposal( agentContext: AgentContext, - options: CreateProposalOptions + options: CreateCredentialProposalOptions ): Promise> - abstract processProposal(messageContext: InboundMessageContext): Promise - abstract acceptProposal( + public abstract processProposal( + messageContext: InboundMessageContext + ): Promise + public abstract acceptProposal( agentContext: AgentContext, - options: AcceptProposalOptions + options: AcceptCredentialProposalOptions ): Promise> - abstract negotiateProposal( + public abstract negotiateProposal( agentContext: AgentContext, - options: NegotiateProposalOptions + options: NegotiateCredentialProposalOptions ): Promise> // methods for offer - abstract createOffer( + public abstract createOffer( agentContext: AgentContext, - options: CreateOfferOptions + options: CreateCredentialOfferOptions ): Promise> - abstract processOffer(messageContext: InboundMessageContext): Promise - abstract acceptOffer( + public abstract processOffer( + messageContext: InboundMessageContext + ): Promise + public abstract acceptOffer( agentContext: AgentContext, - options: AcceptOfferOptions + options: AcceptCredentialOfferOptions ): Promise> - abstract negotiateOffer( + public abstract negotiateOffer( agentContext: AgentContext, - options: NegotiateOfferOptions + options: NegotiateCredentialOfferOptions ): Promise> // methods for request - abstract createRequest( + public abstract createRequest( agentContext: AgentContext, - options: CreateRequestOptions + options: CreateCredentialRequestOptions ): Promise> - abstract processRequest(messageContext: InboundMessageContext): Promise - abstract acceptRequest( + public abstract processRequest( + messageContext: InboundMessageContext + ): Promise + public abstract acceptRequest( agentContext: AgentContext, - options: AcceptRequestOptions + options: AcceptCredentialRequestOptions ): Promise> // methods for issue - abstract processCredential(messageContext: InboundMessageContext): Promise - abstract acceptCredential( + public abstract processCredential( + messageContext: InboundMessageContext + ): Promise + public abstract acceptCredential( agentContext: AgentContext, options: AcceptCredentialOptions ): Promise> // methods for ack - abstract processAck(messageContext: InboundMessageContext): Promise + public abstract processAck(messageContext: InboundMessageContext): Promise // methods for problem-report - abstract createProblemReport(agentContext: AgentContext, options: CreateProblemReportOptions): ProblemReportMessage + public abstract createProblemReport( + agentContext: AgentContext, + options: CreateCredentialProblemReportOptions + ): Promise> - abstract findProposalMessage( + public abstract findProposalMessage( agentContext: AgentContext, credentialExchangeId: string ): Promise - abstract findOfferMessage(agentContext: AgentContext, credentialExchangeId: string): Promise - abstract findRequestMessage( + public abstract findOfferMessage( agentContext: AgentContext, credentialExchangeId: string ): Promise - abstract findCredentialMessage( + public abstract findRequestMessage( agentContext: AgentContext, credentialExchangeId: string ): Promise - abstract getFormatData( + public abstract findCredentialMessage( agentContext: AgentContext, credentialExchangeId: string - ): Promise>> - - abstract register(dependencyManager: DependencyManager, featureRegistry: FeatureRegistry): void - - /** - * Decline a credential offer - * @param credentialRecord The credential to be declined - */ - public async declineOffer( + ): Promise + public abstract getFormatData( agentContext: AgentContext, - credentialRecord: CredentialExchangeRecord - ): Promise { - credentialRecord.assertState(CredentialState.OfferReceived) - - await this.updateState(agentContext, credentialRecord, CredentialState.Declined) + credentialExchangeId: string + ): Promise>> - return credentialRecord - } + public abstract register(dependencyManager: DependencyManager, featureRegistry: FeatureRegistry): void /** * Process a received credential {@link ProblemReportMessage}. @@ -145,17 +144,17 @@ export abstract class BaseCredentialProtocol { + public findById(agentContext: AgentContext, proofRecordId: string): Promise { const credentialRepository = agentContext.dependencyManager.resolve(CredentialRepository) - return credentialRepository.findById(agentContext, connectionId) + return credentialRepository.findById(agentContext, proofRecordId) } public async delete( diff --git a/packages/core/src/modules/credentials/protocol/CredentialProtocol.ts b/packages/core/src/modules/credentials/protocol/CredentialProtocol.ts index f40c6c07c8..f547d9f3de 100644 --- a/packages/core/src/modules/credentials/protocol/CredentialProtocol.ts +++ b/packages/core/src/modules/credentials/protocol/CredentialProtocol.ts @@ -1,3 +1,18 @@ +import type { + CreateCredentialProposalOptions, + CredentialProtocolMsgReturnType, + DeleteCredentialOptions, + AcceptCredentialProposalOptions, + NegotiateCredentialProposalOptions, + CreateCredentialOfferOptions, + NegotiateCredentialOfferOptions, + CreateCredentialRequestOptions, + AcceptCredentialOfferOptions, + AcceptCredentialRequestOptions, + AcceptCredentialOptions, + GetCredentialFormatDataReturn, + CreateCredentialProblemReportOptions, +} from './CredentialProtocolOptions' import type { AgentContext } from '../../../agent' import type { FeatureRegistry } from '../../../agent/FeatureRegistry' import type { InboundMessageContext } from '../../../agent/models/InboundMessageContext' @@ -5,21 +20,6 @@ import type { DidCommV1Message } from '../../../didcomm' import type { DependencyManager } from '../../../plugins' import type { Query } from '../../../storage/StorageService' import type { ProblemReportMessage } from '../../problem-reports' -import type { - CreateProposalOptions, - CredentialProtocolMsgReturnType, - DeleteCredentialOptions, - AcceptProposalOptions, - NegotiateProposalOptions, - CreateOfferOptions, - NegotiateOfferOptions, - CreateRequestOptions, - AcceptOfferOptions, - AcceptRequestOptions, - AcceptCredentialOptions, - GetFormatDataReturn, - CreateProblemReportOptions, -} from '../CredentialProtocolOptions' import type { CredentialFormatService, ExtractCredentialFormats } from '../formats' import type { CredentialState } from '../models/CredentialState' import type { CredentialExchangeRecord } from '../repository' @@ -30,42 +30,42 @@ export interface CredentialProtocol + options: CreateCredentialProposalOptions ): Promise> processProposal(messageContext: InboundMessageContext): Promise acceptProposal( agentContext: AgentContext, - options: AcceptProposalOptions + options: AcceptCredentialProposalOptions ): Promise> negotiateProposal( agentContext: AgentContext, - options: NegotiateProposalOptions + options: NegotiateCredentialProposalOptions ): Promise> // methods for offer createOffer( agentContext: AgentContext, - options: CreateOfferOptions + options: CreateCredentialOfferOptions ): Promise> processOffer(messageContext: InboundMessageContext): Promise acceptOffer( agentContext: AgentContext, - options: AcceptOfferOptions + options: AcceptCredentialOfferOptions ): Promise> negotiateOffer( agentContext: AgentContext, - options: NegotiateOfferOptions + options: NegotiateCredentialOfferOptions ): Promise> // methods for request createRequest( agentContext: AgentContext, - options: CreateRequestOptions + options: CreateCredentialRequestOptions ): Promise> processRequest(messageContext: InboundMessageContext): Promise acceptRequest( agentContext: AgentContext, - options: AcceptRequestOptions + options: AcceptCredentialRequestOptions ): Promise> // methods for issue @@ -79,7 +79,11 @@ export interface CredentialProtocol): Promise // methods for problem-report - createProblemReport(agentContext: AgentContext, options: CreateProblemReportOptions): ProblemReportMessage + createProblemReport( + agentContext: AgentContext, + options: CreateCredentialProblemReportOptions + ): Promise> + processProblemReport(messageContext: InboundMessageContext): Promise findProposalMessage(agentContext: AgentContext, credentialExchangeId: string): Promise findOfferMessage(agentContext: AgentContext, credentialExchangeId: string): Promise @@ -88,13 +92,7 @@ export interface CredentialProtocol>> - - declineOffer( - agentContext: AgentContext, - credentialRecord: CredentialExchangeRecord - ): Promise - processProblemReport(messageContext: InboundMessageContext): Promise + ): Promise>> // Repository methods updateState( @@ -102,13 +100,13 @@ export interface CredentialProtocol - getById(agentContext: AgentContext, credentialRecordId: string): Promise + getById(agentContext: AgentContext, credentialExchangeId: string): Promise getAll(agentContext: AgentContext): Promise findAllByQuery( agentContext: AgentContext, query: Query ): Promise - findById(agentContext: AgentContext, connectionId: string): Promise + findById(agentContext: AgentContext, credentialExchangeId: string): Promise delete( agentContext: AgentContext, credentialRecord: CredentialExchangeRecord, diff --git a/packages/core/src/modules/credentials/CredentialProtocolOptions.ts b/packages/core/src/modules/credentials/protocol/CredentialProtocolOptions.ts similarity index 57% rename from packages/core/src/modules/credentials/CredentialProtocolOptions.ts rename to packages/core/src/modules/credentials/protocol/CredentialProtocolOptions.ts index be44346340..b8ba5b9761 100644 --- a/packages/core/src/modules/credentials/CredentialProtocolOptions.ts +++ b/packages/core/src/modules/credentials/protocol/CredentialProtocolOptions.ts @@ -1,16 +1,15 @@ -import type { DidCommV1Message } from '../../didcomm' -import type { FlatArray } from '../../types' -import type { ConnectionRecord } from '../connections/repository/ConnectionRecord' +import type { CredentialProtocol } from './CredentialProtocol' +import type { DidCommV1Message } from '../../../didcomm' +import type { ConnectionRecord } from '../../connections/repository/ConnectionRecord' import type { CredentialFormat, CredentialFormatPayload, CredentialFormatService, ExtractCredentialFormats, -} from './formats' -import type { CredentialPreviewAttributeOptions } from './models' -import type { AutoAcceptCredential } from './models/CredentialAutoAcceptType' -import type { CredentialProtocol } from './protocol/CredentialProtocol' -import type { CredentialExchangeRecord } from './repository/CredentialExchangeRecord' +} from '../formats' +import type { CredentialPreviewAttributeOptions } from '../models' +import type { AutoAcceptCredential } from '../models/CredentialAutoAcceptType' +import type { CredentialExchangeRecord } from '../repository/CredentialExchangeRecord' /** * Get the format data payload for a specific message from a list of CredentialFormat interfaces and a message @@ -21,7 +20,7 @@ import type { CredentialExchangeRecord } from './repository/CredentialExchangeRe * @example * ``` * - * type OfferFormatData = FormatDataMessagePayload<[IndyCredentialFormat, JsonLdCredentialFormat], 'offer'> + * type OfferFormatData = CredentialFormatDataMessagePayload<[IndyCredentialFormat, JsonLdCredentialFormat], 'createOffer'> * * // equal to * type OfferFormatData = { @@ -34,7 +33,7 @@ import type { CredentialExchangeRecord } from './repository/CredentialExchangeRe * } * ``` */ -export type FormatDataMessagePayload< +export type CredentialFormatDataMessagePayload< CFs extends CredentialFormat[] = CredentialFormat[], M extends keyof CredentialFormat['formatData'] = keyof CredentialFormat['formatData'] > = { @@ -42,14 +41,15 @@ export type FormatDataMessagePayload< } /** - * Infer an array of {@link CredentialFormatService} types based on a {@link CredentialProtocol}. + * Infer the {@link CredentialFormat} types based on an array of {@link CredentialProtocol} types. * - * It does this by extracting the `CredentialFormatServices` generic from the `CredentialProtocol`. + * It does this by extracting the `CredentialFormatServices` generic from the `CredentialProtocol`, and + * then extracting the `CredentialFormat` generic from each of the `CredentialFormatService` types. * * @example * ``` * // TheCredentialFormatServices is now equal to [IndyCredentialFormatService] - * type TheCredentialFormatServices = ExtractCredentialFormatServices + * type TheCredentialFormatServices = CredentialFormatsFromProtocols<[V1CredentialProtocol]> * ``` * * Because the `V1CredentialProtocol` is defined as follows: @@ -58,27 +58,14 @@ export type FormatDataMessagePayload< * } * ``` */ -export type ExtractCredentialFormatServices = Type extends CredentialProtocol - ? CredentialFormatServices +export type CredentialFormatsFromProtocols = Type[number] extends CredentialProtocol< + infer CredentialFormatServices +> + ? CredentialFormatServices extends CredentialFormatService[] + ? ExtractCredentialFormats + : never : never -/** - * Infer an array of {@link CredentialFormat} types based on an array of {@link CredentialProtocol} types. - * - * This is based on {@link ExtractCredentialFormatServices}, but allows to handle arrays. - */ -export type CFsFromCPs = _CFsFromCPs extends CredentialFormat[] - ? _CFsFromCPs - : [] - -/** - * Utility type for {@link ExtractCredentialFormatServicesFromCredentialProtocols} to reduce duplication. - * Should not be used directly. - */ -type _CFsFromCPs = FlatArray<{ - [CP in keyof CPs]: ExtractCredentialFormats> -}>[] - /** * Get format data return value. Each key holds a mapping of credential format key to format data. * @@ -93,66 +80,66 @@ type _CFsFromCPs = FlatArray<{ * } * ``` */ -export type GetFormatDataReturn = { +export type GetCredentialFormatDataReturn = { proposalAttributes?: CredentialPreviewAttributeOptions[] - proposal?: FormatDataMessagePayload - offer?: FormatDataMessagePayload + proposal?: CredentialFormatDataMessagePayload + offer?: CredentialFormatDataMessagePayload offerAttributes?: CredentialPreviewAttributeOptions[] - request?: FormatDataMessagePayload - credential?: FormatDataMessagePayload + request?: CredentialFormatDataMessagePayload + credential?: CredentialFormatDataMessagePayload } -export interface CreateProposalOptions { - connection: ConnectionRecord +export interface CreateCredentialProposalOptions { + connectionRecord: ConnectionRecord credentialFormats: CredentialFormatPayload, 'createProposal'> autoAcceptCredential?: AutoAcceptCredential comment?: string } -export interface AcceptProposalOptions { +export interface AcceptCredentialProposalOptions { credentialRecord: CredentialExchangeRecord credentialFormats?: CredentialFormatPayload, 'acceptProposal'> autoAcceptCredential?: AutoAcceptCredential comment?: string } -export interface NegotiateProposalOptions { +export interface NegotiateCredentialProposalOptions { credentialRecord: CredentialExchangeRecord credentialFormats: CredentialFormatPayload, 'createOffer'> autoAcceptCredential?: AutoAcceptCredential comment?: string } -export interface CreateOfferOptions { +export interface CreateCredentialOfferOptions { // Create offer can also be used for connection-less, so connection is optional - connection?: ConnectionRecord + connectionRecord?: ConnectionRecord credentialFormats: CredentialFormatPayload, 'createOffer'> autoAcceptCredential?: AutoAcceptCredential comment?: string } -export interface AcceptOfferOptions { +export interface AcceptCredentialOfferOptions { credentialRecord: CredentialExchangeRecord credentialFormats?: CredentialFormatPayload, 'acceptOffer'> autoAcceptCredential?: AutoAcceptCredential comment?: string } -export interface NegotiateOfferOptions { +export interface NegotiateCredentialOfferOptions { credentialRecord: CredentialExchangeRecord credentialFormats: CredentialFormatPayload, 'createProposal'> autoAcceptCredential?: AutoAcceptCredential comment?: string } -export interface CreateRequestOptions { - connection: ConnectionRecord +export interface CreateCredentialRequestOptions { + connectionRecord: ConnectionRecord credentialFormats: CredentialFormatPayload, 'createRequest'> autoAcceptCredential?: AutoAcceptCredential comment?: string } -export interface AcceptRequestOptions { +export interface AcceptCredentialRequestOptions { credentialRecord: CredentialExchangeRecord credentialFormats?: CredentialFormatPayload, 'acceptRequest'> autoAcceptCredential?: AutoAcceptCredential @@ -163,8 +150,9 @@ export interface AcceptCredentialOptions { credentialRecord: CredentialExchangeRecord } -export interface CreateProblemReportOptions { - message: string +export interface CreateCredentialProblemReportOptions { + credentialRecord: CredentialExchangeRecord + description: string } export interface CredentialProtocolMsgReturnType { diff --git a/packages/core/src/modules/credentials/protocol/index.ts b/packages/core/src/modules/credentials/protocol/index.ts index 6433655022..cb3d5c3b51 100644 --- a/packages/core/src/modules/credentials/protocol/index.ts +++ b/packages/core/src/modules/credentials/protocol/index.ts @@ -1,3 +1,11 @@ -export * from './v1' export * from './v2' export * from './revocation-notification' +import * as CredentialProtocolOptions from './CredentialProtocolOptions' + +export { CredentialProtocol } from './CredentialProtocol' +// NOTE: ideally we don't export the BaseCredentialProtocol, but as the V1CredentialProtocol is defined in the +// anoncreds package, we need to export it. We should at some point look at creating a core package which can be used for +// sharing internal types, and when you want to build you own modules, and an agent package, which is the one you use when +// consuming the framework +export { BaseCredentialProtocol } from './BaseCredentialProtocol' +export { CredentialProtocolOptions } diff --git a/packages/core/src/modules/credentials/protocol/revocation-notification/services/RevocationNotificationService.ts b/packages/core/src/modules/credentials/protocol/revocation-notification/services/RevocationNotificationService.ts index f3636c689a..6a641d9457 100644 --- a/packages/core/src/modules/credentials/protocol/revocation-notification/services/RevocationNotificationService.ts +++ b/packages/core/src/modules/credentials/protocol/revocation-notification/services/RevocationNotificationService.ts @@ -5,8 +5,8 @@ import type { RevocationNotificationReceivedEvent } from '../../../CredentialEve import type { V1RevocationNotificationMessage } from '../messages/V1RevocationNotificationMessage' import type { V2RevocationNotificationMessage } from '../messages/V2RevocationNotificationMessage' -import { Dispatcher } from '../../../../../agent/Dispatcher' import { EventEmitter } from '../../../../../agent/EventEmitter' +import { MessageHandlerRegistry } from '../../../../../agent/MessageHandlerRegistry' import { InjectionSymbols } from '../../../../../constants' import { AriesFrameworkError } from '../../../../../error/AriesFrameworkError' import { Logger } from '../../../../../logger' @@ -22,31 +22,30 @@ import { v1ThreadRegex, v2IndyRevocationFormat, v2IndyRevocationIdentifierRegex export class RevocationNotificationService { private credentialRepository: CredentialRepository private eventEmitter: EventEmitter - private dispatcher: Dispatcher private logger: Logger public constructor( credentialRepository: CredentialRepository, eventEmitter: EventEmitter, - dispatcher: Dispatcher, + messageHandlerRegistry: MessageHandlerRegistry, @inject(InjectionSymbols.Logger) logger: Logger ) { this.credentialRepository = credentialRepository this.eventEmitter = eventEmitter - this.dispatcher = dispatcher this.logger = logger - this.registerMessageHandlers() + this.registerMessageHandlers(messageHandlerRegistry) } private async processRevocationNotification( agentContext: AgentContext, - indyRevocationRegistryId: string, - indyCredentialRevocationId: string, + anonCredsRevocationRegistryId: string, + anonCredsCredentialRevocationId: string, connection: ConnectionRecord, comment?: string ) { - const query = { indyRevocationRegistryId, indyCredentialRevocationId, connectionId: connection.id } + // TODO: can we extract support for this revocation notification handler to the anoncreds module? + const query = { anonCredsRevocationRegistryId, anonCredsCredentialRevocationId, connectionId: connection.id } this.logger.trace(`Getting record by query for revocation notification:`, query) const credentialRecord = await this.credentialRepository.getSingleByQuery(agentContext, query) @@ -88,14 +87,14 @@ export class RevocationNotificationService { ) } - const [, , indyRevocationRegistryId, indyCredentialRevocationId] = threadIdGroups + const [, , anonCredsRevocationRegistryId, anonCredsCredentialRevocationId] = threadIdGroups const comment = messageContext.message.comment const connection = messageContext.assertReadyConnection() await this.processRevocationNotification( messageContext.agentContext, - indyRevocationRegistryId, - indyCredentialRevocationId, + anonCredsRevocationRegistryId, + anonCredsCredentialRevocationId, connection, comment ) @@ -131,13 +130,13 @@ export class RevocationNotificationService { ) } - const [, indyRevocationRegistryId, indyCredentialRevocationId] = credentialIdGroups + const [, anonCredsRevocationRegistryId, anonCredsCredentialRevocationId] = credentialIdGroups const comment = messageContext.message.comment const connection = messageContext.assertReadyConnection() await this.processRevocationNotification( messageContext.agentContext, - indyRevocationRegistryId, - indyCredentialRevocationId, + anonCredsRevocationRegistryId, + anonCredsCredentialRevocationId, connection, comment ) @@ -146,8 +145,8 @@ export class RevocationNotificationService { } } - private registerMessageHandlers() { - this.dispatcher.registerMessageHandler(new V1RevocationNotificationHandler(this)) - this.dispatcher.registerMessageHandler(new V2RevocationNotificationHandler(this)) + private registerMessageHandlers(messageHandlerRegistry: MessageHandlerRegistry) { + messageHandlerRegistry.registerMessageHandler(new V1RevocationNotificationHandler(this)) + messageHandlerRegistry.registerMessageHandler(new V2RevocationNotificationHandler(this)) } } diff --git a/packages/core/src/modules/credentials/protocol/revocation-notification/services/__tests__/RevocationNotificationService.test.ts b/packages/core/src/modules/credentials/protocol/revocation-notification/services/__tests__/RevocationNotificationService.test.ts index c0b3e57904..90d07d81ae 100644 --- a/packages/core/src/modules/credentials/protocol/revocation-notification/services/__tests__/RevocationNotificationService.test.ts +++ b/packages/core/src/modules/credentials/protocol/revocation-notification/services/__tests__/RevocationNotificationService.test.ts @@ -1,3 +1,4 @@ +import type { AnonCredsCredentialMetadata } from '../../../../../../../../anoncreds/src/utils/metadata' import type { AgentContext } from '../../../../../../agent' import type { RevocationNotificationReceivedEvent } from '../../../../CredentialEvents' @@ -5,11 +6,10 @@ import { Subject } from 'rxjs' import { CredentialExchangeRecord, CredentialState, InboundMessageContext } from '../../../../../..' import { getAgentConfig, getAgentContext, getMockConnection, mockFunction } from '../../../../../../../tests/helpers' -import { Dispatcher } from '../../../../../../agent/Dispatcher' import { EventEmitter } from '../../../../../../agent/EventEmitter' +import { MessageHandlerRegistry } from '../../../../../../agent/MessageHandlerRegistry' import { DidExchangeState } from '../../../../../connections' import { CredentialEventTypes } from '../../../../CredentialEvents' -import { CredentialMetadataKeys } from '../../../../repository' import { CredentialRepository } from '../../../../repository/CredentialRepository' import { V1RevocationNotificationMessage, V2RevocationNotificationMessage } from '../../messages' import { RevocationNotificationService } from '../RevocationNotificationService' @@ -18,9 +18,9 @@ jest.mock('../../../../repository/CredentialRepository') const CredentialRepositoryMock = CredentialRepository as jest.Mock const credentialRepository = new CredentialRepositoryMock() -jest.mock('../../../../../../agent/Dispatcher') -const DispatcherMock = Dispatcher as jest.Mock -const dispatcher = new DispatcherMock() +jest.mock('../../../../../../agent/MessageHandlerRegistry') +const MessageHandlerRegistryMock = MessageHandlerRegistry as jest.Mock +const messageHandlerRegistry = new MessageHandlerRegistryMock() const connection = getMockConnection({ state: DidExchangeState.Completed, @@ -32,9 +32,7 @@ describe('RevocationNotificationService', () => { let eventEmitter: EventEmitter beforeEach(() => { - const agentConfig = getAgentConfig('RevocationNotificationService', { - indyLedgers: [], - }) + const agentConfig = getAgentConfig('RevocationNotificationService') agentContext = getAgentContext() @@ -42,7 +40,7 @@ describe('RevocationNotificationService', () => { revocationNotificationService = new RevocationNotificationService( credentialRepository, eventEmitter, - dispatcher, + messageHandlerRegistry, agentConfig.logger ) }) @@ -72,16 +70,19 @@ describe('RevocationNotificationService', () => { state: CredentialState.Done, }) - credentialRecord.metadata.set(CredentialMetadataKeys.IndyCredential, { - indyRevocationRegistryId: + const metadata = { + revocationRegistryId: 'AsB27X6KRrJFsqZ3unNAH6:4:AsB27X6KRrJFsqZ3unNAH6:3:cl:48187:default:CL_ACCUM:3b24a9b0-a979-41e0-9964-2292f2b1b7e9', - indyCredentialRevocationId: '1', - }) + credentialRevocationId: '1', + } satisfies AnonCredsCredentialMetadata + + // Set required tags + credentialRecord.setTag('anonCredsRevocationRegistryId', metadata.revocationRegistryId) + credentialRecord.setTag('anonCredsCredentialRevocationId', metadata.credentialRevocationId) mockFunction(credentialRepository.getSingleByQuery).mockResolvedValueOnce(credentialRecord) - const { indyRevocationRegistryId, indyCredentialRevocationId } = credentialRecord.getTags() - const revocationNotificationThreadId = `indy::${indyRevocationRegistryId}::${indyCredentialRevocationId}` + const revocationNotificationThreadId = `indy::${metadata.revocationRegistryId}::${metadata.credentialRevocationId}` const revocationNotificationMessage = new V1RevocationNotificationMessage({ issueThread: revocationNotificationThreadId, comment: 'Credential has been revoked', @@ -182,15 +183,14 @@ describe('RevocationNotificationService', () => { state: CredentialState.Done, }) - credentialRecord.metadata.set(CredentialMetadataKeys.IndyCredential, { - indyRevocationRegistryId: + const metadata = { + revocationRegistryId: 'AsB27X6KRrJFsqZ3unNAH6:4:AsB27X6KRrJFsqZ3unNAH6:3:cl:48187:default:CL_ACCUM:3b24a9b0-a979-41e0-9964-2292f2b1b7e9', - indyCredentialRevocationId: '1', - }) + credentialRevocationId: '1', + } satisfies AnonCredsCredentialMetadata mockFunction(credentialRepository.getSingleByQuery).mockResolvedValueOnce(credentialRecord) - const { indyRevocationRegistryId, indyCredentialRevocationId } = credentialRecord.getTags() - const revocationNotificationCredentialId = `${indyRevocationRegistryId}::${indyCredentialRevocationId}` + const revocationNotificationCredentialId = `${metadata.revocationRegistryId}::${metadata.credentialRevocationId}` const revocationNotificationMessage = new V2RevocationNotificationMessage({ credentialId: revocationNotificationCredentialId, diff --git a/packages/core/src/modules/credentials/protocol/v2/CredentialFormatCoordinator.ts b/packages/core/src/modules/credentials/protocol/v2/CredentialFormatCoordinator.ts index e319d8a83b..c48d4815f0 100644 --- a/packages/core/src/modules/credentials/protocol/v2/CredentialFormatCoordinator.ts +++ b/packages/core/src/modules/credentials/protocol/v2/CredentialFormatCoordinator.ts @@ -568,6 +568,6 @@ export class CredentialFormatCoordinator if (!format) throw new AriesFrameworkError(`No attachment found for service ${credentialFormatService.formatKey}`) - return format.attachId + return format.attachmentId } } diff --git a/packages/core/src/modules/credentials/protocol/v2/V2CredentialProtocol.ts b/packages/core/src/modules/credentials/protocol/v2/V2CredentialProtocol.ts index 87d43a2fa2..0b77df9dca 100644 --- a/packages/core/src/modules/credentials/protocol/v2/V2CredentialProtocol.ts +++ b/packages/core/src/modules/credentials/protocol/v2/V2CredentialProtocol.ts @@ -5,21 +5,6 @@ import type { InboundMessageContext } from '../../../../agent/models/InboundMess import type { DidCommV1Message } from '../../../../didcomm' import type { DependencyManager } from '../../../../plugins' import type { ProblemReportMessage } from '../../../problem-reports' -import type { - AcceptCredentialOptions, - AcceptOfferOptions, - AcceptProposalOptions, - AcceptRequestOptions, - CreateOfferOptions, - CreateProposalOptions, - CreateRequestOptions, - CredentialProtocolMsgReturnType, - FormatDataMessagePayload, - CreateProblemReportOptions, - GetFormatDataReturn, - NegotiateOfferOptions, - NegotiateProposalOptions, -} from '../../CredentialProtocolOptions' import type { CredentialFormat, CredentialFormatPayload, @@ -27,6 +12,22 @@ import type { ExtractCredentialFormats, } from '../../formats' import type { CredentialFormatSpec } from '../../models/CredentialFormatSpec' +import type { CredentialProtocol } from '../CredentialProtocol' +import type { + AcceptCredentialOptions, + AcceptCredentialOfferOptions, + AcceptCredentialProposalOptions, + AcceptCredentialRequestOptions, + CreateCredentialOfferOptions, + CreateCredentialProposalOptions, + CreateCredentialRequestOptions, + CredentialProtocolMsgReturnType, + CredentialFormatDataMessagePayload, + CreateCredentialProblemReportOptions, + GetCredentialFormatDataReturn, + NegotiateCredentialOfferOptions, + NegotiateCredentialProposalOptions, +} from '../CredentialProtocolOptions' import { Protocol } from '../../../../agent/models/features/Protocol' import { AriesFrameworkError } from '../../../../error' @@ -35,8 +36,7 @@ import { uuid } from '../../../../utils/uuid' import { AckStatus } from '../../../common' import { ConnectionService } from '../../../connections' import { CredentialsModuleConfig } from '../../CredentialsModuleConfig' -import { CredentialProblemReportReason } from '../../errors' -import { AutoAcceptCredential, CredentialState } from '../../models' +import { AutoAcceptCredential, CredentialProblemReportReason, CredentialState } from '../../models' import { CredentialExchangeRecord, CredentialRepository } from '../../repository' import { composeAutoAccept } from '../../util/composeAutoAccept' import { arePreviewAttributesEqual } from '../../util/previewAttributes' @@ -64,9 +64,10 @@ export interface V2CredentialProtocolConfig extends BaseCredentialProtocol { +export class V2CredentialProtocol + extends BaseCredentialProtocol + implements CredentialProtocol +{ private credentialFormatCoordinator = new CredentialFormatCoordinator() private credentialFormats: CFs @@ -95,7 +96,7 @@ export class V2CredentialProtocol< new V2CredentialProblemReportHandler(this), ]) - // Register Issue Credential V1 in feature registry, with supported roles + // Register Issue Credential V2 in feature registry, with supported roles featureRegistry.register( new Protocol({ id: 'https://didcomm.org/issue-credential/2.0', @@ -113,7 +114,7 @@ export class V2CredentialProtocol< */ public async createProposal( agentContext: AgentContext, - { connection, credentialFormats, comment, autoAcceptCredential }: CreateProposalOptions + { connectionRecord, credentialFormats, comment, autoAcceptCredential }: CreateCredentialProposalOptions ): Promise> { agentContext.config.logger.debug('Get the Format Service and Create Proposal Message') @@ -125,7 +126,7 @@ export class V2CredentialProtocol< } const credentialRecord = new CredentialExchangeRecord({ - connectionId: connection.id, + connectionId: connectionRecord.id, threadId: uuid(), state: CredentialState.ProposalSent, autoAcceptCredential, @@ -169,7 +170,7 @@ export class V2CredentialProtocol< connection?.id ) - const formatServices = this.getFormatServicesFromMessage(agentContext, proposalMessage.formats) + const formatServices = this.getFormatServicesFromMessage(proposalMessage.formats) if (formatServices.length === 0) { throw new AriesFrameworkError(`Unable to process proposal. No supported formats`) } @@ -230,7 +231,7 @@ export class V2CredentialProtocol< public async acceptProposal( agentContext: AgentContext, - { credentialRecord, credentialFormats, autoAcceptCredential, comment }: AcceptProposalOptions + { credentialRecord, credentialFormats, autoAcceptCredential, comment }: AcceptCredentialProposalOptions ): Promise> { // Assert credentialRecord.assertProtocolVersion('v2') @@ -249,7 +250,7 @@ export class V2CredentialProtocol< messageClass: V2ProposeCredentialMessage, }) - formatServices = this.getFormatServicesFromMessage(agentContext, proposalMessage.formats) + formatServices = this.getFormatServicesFromMessage(proposalMessage.formats) } // If the format services list is still empty, throw an error as we don't support any @@ -277,13 +278,13 @@ export class V2CredentialProtocol< * Negotiate a credential proposal as issuer (by sending a credential offer message) to the connection * associated with the credential record. * - * @param options configuration for the offer see {@link NegotiateProposalOptions} + * @param options configuration for the offer see {@link NegotiateCredentialProposalOptions} * @returns Credential exchange record associated with the credential offer * */ public async negotiateProposal( agentContext: AgentContext, - { credentialRecord, credentialFormats, autoAcceptCredential, comment }: NegotiateProposalOptions + { credentialRecord, credentialFormats, autoAcceptCredential, comment }: NegotiateCredentialProposalOptions ): Promise> { // Assert credentialRecord.assertProtocolVersion('v2') @@ -324,7 +325,7 @@ export class V2CredentialProtocol< */ public async createOffer( agentContext: AgentContext, - { credentialFormats, autoAcceptCredential, comment, connection }: CreateOfferOptions + { credentialFormats, autoAcceptCredential, comment, connectionRecord }: CreateCredentialOfferOptions ): Promise> { const credentialRepository = agentContext.dependencyManager.resolve(CredentialRepository) @@ -334,7 +335,7 @@ export class V2CredentialProtocol< } const credentialRecord = new CredentialExchangeRecord({ - connectionId: connection?.id, + connectionId: connectionRecord?.id, threadId: uuid(), state: CredentialState.OfferSent, autoAcceptCredential, @@ -380,7 +381,7 @@ export class V2CredentialProtocol< connection?.id ) - const formatServices = this.getFormatServicesFromMessage(agentContext, offerMessage.formats) + const formatServices = this.getFormatServicesFromMessage(offerMessage.formats) if (formatServices.length === 0) { throw new AriesFrameworkError(`Unable to process offer. No supported formats`) } @@ -441,7 +442,7 @@ export class V2CredentialProtocol< public async acceptOffer( agentContext: AgentContext, - { credentialRecord, autoAcceptCredential, comment, credentialFormats }: AcceptOfferOptions + { credentialRecord, autoAcceptCredential, comment, credentialFormats }: AcceptCredentialOfferOptions ) { const didCommMessageRepository = agentContext.dependencyManager.resolve(DidCommMessageRepository) @@ -460,7 +461,7 @@ export class V2CredentialProtocol< messageClass: V2OfferCredentialMessage, }) - formatServices = this.getFormatServicesFromMessage(agentContext, offerMessage.formats) + formatServices = this.getFormatServicesFromMessage(offerMessage.formats) } // If the format services list is still empty, throw an error as we don't support any @@ -494,7 +495,7 @@ export class V2CredentialProtocol< */ public async negotiateOffer( agentContext: AgentContext, - { credentialRecord, credentialFormats, autoAcceptCredential, comment }: NegotiateOfferOptions + { credentialRecord, credentialFormats, autoAcceptCredential, comment }: NegotiateCredentialOfferOptions ): Promise> { // Assert credentialRecord.assertProtocolVersion('v2') @@ -531,7 +532,7 @@ export class V2CredentialProtocol< */ public async createRequest( agentContext: AgentContext, - { credentialFormats, autoAcceptCredential, comment, connection }: CreateRequestOptions + { credentialFormats, autoAcceptCredential, comment, connectionRecord }: CreateCredentialRequestOptions ): Promise> { const credentialRepository = agentContext.dependencyManager.resolve(CredentialRepository) @@ -541,7 +542,7 @@ export class V2CredentialProtocol< } const credentialRecord = new CredentialExchangeRecord({ - connectionId: connection.id, + connectionId: connectionRecord.id, threadId: uuid(), state: CredentialState.RequestSent, autoAcceptCredential, @@ -591,7 +592,7 @@ export class V2CredentialProtocol< connection?.id ) - const formatServices = this.getFormatServicesFromMessage(agentContext, requestMessage.formats) + const formatServices = this.getFormatServicesFromMessage(requestMessage.formats) if (formatServices.length === 0) { throw new AriesFrameworkError(`Unable to process request. No supported formats`) } @@ -654,7 +655,7 @@ export class V2CredentialProtocol< public async acceptRequest( agentContext: AgentContext, - { credentialRecord, autoAcceptCredential, comment, credentialFormats }: AcceptRequestOptions + { credentialRecord, autoAcceptCredential, comment, credentialFormats }: AcceptCredentialRequestOptions ) { const didCommMessageRepository = agentContext.dependencyManager.resolve(DidCommMessageRepository) @@ -673,7 +674,7 @@ export class V2CredentialProtocol< messageClass: V2RequestCredentialMessage, }) - formatServices = this.getFormatServicesFromMessage(agentContext, requestMessage.formats) + formatServices = this.getFormatServicesFromMessage(requestMessage.formats) } // If the format services list is still empty, throw an error as we don't support any @@ -697,7 +698,7 @@ export class V2CredentialProtocol< } /** - * Process a received {@link IssueCredentialMessage}. This will not accept the credential + * Process a received {@link V2IssueCredentialMessage}. This will not accept the credential * or send a credential acknowledgement. It will only update the existing credential record with * the information from the issue credential message. Use {@link createAck} * after calling this method to create a credential acknowledgement. @@ -740,7 +741,7 @@ export class V2CredentialProtocol< previousSentMessage: requestMessage, }) - const formatServices = this.getFormatServicesFromMessage(agentContext, requestMessage.formats) + const formatServices = this.getFormatServicesFromMessage(credentialMessage.formats) if (formatServices.length === 0) { throw new AriesFrameworkError(`Unable to process credential. No supported formats`) } @@ -837,13 +838,20 @@ export class V2CredentialProtocol< * @returns a {@link V2CredentialProblemReportMessage} * */ - public createProblemReport(agentContext: AgentContext, options: CreateProblemReportOptions): ProblemReportMessage { - return new V2CredentialProblemReportMessage({ + public async createProblemReport( + agentContext: AgentContext, + { credentialRecord, description }: CreateCredentialProblemReportOptions + ): Promise> { + const message = new V2CredentialProblemReportMessage({ description: { - en: options.message, + en: description, code: CredentialProblemReportReason.IssuanceAbandoned, }, }) + + message.setThread({ threadId: credentialRecord.threadId }) + + return { credentialRecord, message } } // AUTO ACCEPT METHODS @@ -872,7 +880,7 @@ export class V2CredentialProtocol< // NOTE: we take the formats from the offerMessage so we always check all services that we last sent // Otherwise we'll only check the formats from the proposal, which could be different from the formats // we use. - const formatServices = this.getFormatServicesFromMessage(agentContext, offerMessage.formats) + const formatServices = this.getFormatServicesFromMessage(offerMessage.formats) for (const formatService of formatServices) { const offerAttachment = this.credentialFormatCoordinator.getAttachmentForService( @@ -887,7 +895,7 @@ export class V2CredentialProtocol< proposalMessage.proposalAttachments ) - const shouldAutoRespondToFormat = formatService.shouldAutoRespondToProposal(agentContext, { + const shouldAutoRespondToFormat = await formatService.shouldAutoRespondToProposal(agentContext, { credentialRecord, offerAttachment, proposalAttachment, @@ -926,7 +934,6 @@ export class V2CredentialProtocol< credentialRecord.autoAcceptCredential, credentialsModuleConfig.autoAcceptCredentials ) - // Handle always / never cases if (autoAccept === AutoAcceptCredential.Always) return true if (autoAccept === AutoAcceptCredential.Never) return false @@ -937,7 +944,7 @@ export class V2CredentialProtocol< // NOTE: we take the formats from the proposalMessage so we always check all services that we last sent // Otherwise we'll only check the formats from the offer, which could be different from the formats // we use. - const formatServices = this.getFormatServicesFromMessage(agentContext, proposalMessage.formats) + const formatServices = this.getFormatServicesFromMessage(proposalMessage.formats) for (const formatService of formatServices) { const offerAttachment = this.credentialFormatCoordinator.getAttachmentForService( @@ -952,7 +959,7 @@ export class V2CredentialProtocol< proposalMessage.proposalAttachments ) - const shouldAutoRespondToFormat = formatService.shouldAutoRespondToOffer(agentContext, { + const shouldAutoRespondToFormat = await formatService.shouldAutoRespondToOffer(agentContext, { credentialRecord, offerAttachment, proposalAttachment, @@ -1001,7 +1008,7 @@ export class V2CredentialProtocol< // NOTE: we take the formats from the offerMessage so we always check all services that we last sent // Otherwise we'll only check the formats from the request, which could be different from the formats // we use. - const formatServices = this.getFormatServicesFromMessage(agentContext, offerMessage.formats) + const formatServices = this.getFormatServicesFromMessage(offerMessage.formats) for (const formatService of formatServices) { const offerAttachment = this.credentialFormatCoordinator.getAttachmentForService( @@ -1024,7 +1031,7 @@ export class V2CredentialProtocol< requestMessage.requestAttachments ) - const shouldAutoRespondToFormat = formatService.shouldAutoRespondToRequest(agentContext, { + const shouldAutoRespondToFormat = await formatService.shouldAutoRespondToRequest(agentContext, { credentialRecord, offerAttachment, requestAttachment, @@ -1066,7 +1073,7 @@ export class V2CredentialProtocol< // NOTE: we take the formats from the requestMessage so we always check all services that we last sent // Otherwise we'll only check the formats from the credential, which could be different from the formats // we use. - const formatServices = this.getFormatServicesFromMessage(agentContext, requestMessage.formats) + const formatServices = this.getFormatServicesFromMessage(requestMessage.formats) for (const formatService of formatServices) { const offerAttachment = offerMessage @@ -1097,7 +1104,7 @@ export class V2CredentialProtocol< credentialMessage.credentialAttachments ) - const shouldAutoRespondToFormat = formatService.shouldAutoRespondToCredential(agentContext, { + const shouldAutoRespondToFormat = await formatService.shouldAutoRespondToCredential(agentContext, { credentialRecord, offerAttachment, credentialAttachment, @@ -1150,7 +1157,7 @@ export class V2CredentialProtocol< public async getFormatData( agentContext: AgentContext, credentialExchangeId: string - ): Promise>> { + ): Promise>> { // TODO: we could looking at fetching all record using a single query and then filtering based on the type of the message. const [proposalMessage, offerMessage, requestMessage, credentialMessage] = await Promise.all([ this.findProposalMessage(agentContext, credentialExchangeId), @@ -1168,7 +1175,7 @@ export class V2CredentialProtocol< credential: [credentialMessage?.formats, credentialMessage?.credentialAttachments], } as const - const formatData: GetFormatDataReturn = { + const formatData: GetCredentialFormatDataReturn = { proposalAttributes: proposalMessage?.credentialPreview?.attributes, offerAttributes: offerMessage?.credentialPreview?.attributes, } @@ -1179,8 +1186,8 @@ export class V2CredentialProtocol< if (!formats || !attachments) continue // Find all format services associated with the message - const formatServices = this.getFormatServicesFromMessage(agentContext, formats) - const messageFormatData: FormatDataMessagePayload = {} + const formatServices = this.getFormatServicesFromMessage(formats) + const messageFormatData: CredentialFormatDataMessagePayload = {} // Loop through all of the format services, for each we will extract the attachment data and assign this to the object // using the unique format key (e.g. indy) @@ -1190,7 +1197,7 @@ export class V2CredentialProtocol< messageFormatData[formatService.formatKey] = attachment.getDataAsJson() } - formatData[messageKey as Exclude] = + formatData[messageKey as Exclude] = messageFormatData } @@ -1202,10 +1209,7 @@ export class V2CredentialProtocol< * @param messageFormats the format objects containing the format name (eg indy) * @return the credential format service objects in an array - derived from format object keys */ - private getFormatServicesFromMessage( - agentContext: AgentContext, - messageFormats: CredentialFormatSpec[] - ): CredentialFormatService[] { + private getFormatServicesFromMessage(messageFormats: CredentialFormatSpec[]): CredentialFormatService[] { const formatServices = new Set() for (const msg of messageFormats) { diff --git a/packages/core/src/modules/credentials/protocol/v2/__tests__/V2CredentialProtocolCred.test.ts b/packages/core/src/modules/credentials/protocol/v2/__tests__/V2CredentialProtocolCred.test.ts index ecda50c38d..57af30f5f9 100644 --- a/packages/core/src/modules/credentials/protocol/v2/__tests__/V2CredentialProtocolCred.test.ts +++ b/packages/core/src/modules/credentials/protocol/v2/__tests__/V2CredentialProtocolCred.test.ts @@ -1,7 +1,13 @@ -import type { IndyCredentialViewMetadata } from '../../../../..' +/* eslint-disable @typescript-eslint/no-unused-vars */ import type { AgentContext } from '../../../../../agent' import type { GetAgentMessageOptions } from '../../../../../storage' import type { CredentialStateChangedEvent } from '../../../CredentialEvents' +import type { + CredentialFormat, + CredentialFormatAcceptRequestOptions, + CredentialFormatCreateOfferOptions, + CredentialFormatService, +} from '../../../formats' import type { CredentialPreviewAttribute } from '../../../models/CredentialPreviewAttribute' import type { CustomCredentialTags } from '../../../repository/CredentialExchangeRecord' @@ -9,7 +15,6 @@ import { Subject } from 'rxjs' import { AriesFrameworkError, CredentialFormatSpec } from '../../../../..' import { getAgentConfig, getAgentContext, getMockConnection, mockFunction } from '../../../../../../tests/helpers' -import { Dispatcher } from '../../../../../agent/Dispatcher' import { EventEmitter } from '../../../../../agent/EventEmitter' import { InboundMessageContext } from '../../../../../agent/models/InboundMessageContext' import { V1Attachment, V1AttachmentData } from '../../../../../decorators/attachment/V1Attachment' @@ -19,20 +24,14 @@ import { JsonEncoder } from '../../../../../utils/JsonEncoder' import { AckStatus } from '../../../../common/messages/AckMessage' import { DidExchangeState } from '../../../../connections' import { ConnectionService } from '../../../../connections/services/ConnectionService' -import { RoutingService } from '../../../../routing/services/RoutingService' import { CredentialEventTypes } from '../../../CredentialEvents' import { credReq } from '../../../__tests__/fixtures' -import { CredentialProblemReportReason } from '../../../errors/CredentialProblemReportReason' -import { IndyCredentialFormatService } from '../../../formats' -import { IndyCredentialUtils } from '../../../formats/indy/IndyCredentialUtils' -import { JsonLdCredentialFormatService } from '../../../formats/jsonld/JsonLdCredentialFormatService' +import { CredentialProblemReportReason } from '../../../models/CredentialProblemReportReason' import { CredentialState } from '../../../models/CredentialState' import { CredentialExchangeRecord } from '../../../repository/CredentialExchangeRecord' -import { CredentialMetadataKeys } from '../../../repository/CredentialMetadataTypes' import { CredentialRepository } from '../../../repository/CredentialRepository' -import { V1CredentialPreview } from '../../v1/messages/V1CredentialPreview' import { V2CredentialProtocol } from '../V2CredentialProtocol' -import { V2ProposeCredentialMessage } from '../messages' +import { V2CredentialPreview, V2ProposeCredentialMessage } from '../messages' import { V2CredentialAckMessage } from '../messages/V2CredentialAckMessage' import { V2CredentialProblemReportMessage } from '../messages/V2CredentialProblemReportMessage' import { V2IssueCredentialMessage } from '../messages/V2IssueCredentialMessage' @@ -41,8 +40,6 @@ import { V2RequestCredentialMessage } from '../messages/V2RequestCredentialMessa // Mock classes jest.mock('../../../repository/CredentialRepository') -jest.mock('../../../formats/jsonld/JsonLdCredentialFormatService') -jest.mock('../../../formats/indy/IndyCredentialFormatService') jest.mock('../../../../../storage/didcomm/DidCommMessageRepository') jest.mock('../../../../routing/services/RoutingService') jest.mock('../../../../connections/services/ConnectionService') @@ -50,32 +47,13 @@ jest.mock('../../../../../agent/Dispatcher') // Mock typed object const CredentialRepositoryMock = CredentialRepository as jest.Mock -const IndyCredentialFormatServiceMock = IndyCredentialFormatService as jest.Mock -const JsonLdCredentialFormatServiceMock = JsonLdCredentialFormatService as jest.Mock const DidCommMessageRepositoryMock = DidCommMessageRepository as jest.Mock -const RoutingServiceMock = RoutingService as jest.Mock const ConnectionServiceMock = ConnectionService as jest.Mock -const DispatcherMock = Dispatcher as jest.Mock const credentialRepository = new CredentialRepositoryMock() const didCommMessageRepository = new DidCommMessageRepositoryMock() -const routingService = new RoutingServiceMock() -const indyCredentialFormatService = new IndyCredentialFormatServiceMock() -const jsonLdCredentialFormatService = new JsonLdCredentialFormatServiceMock() -const dispatcher = new DispatcherMock() const connectionService = new ConnectionServiceMock() -// eslint-disable-next-line @typescript-eslint/ban-ts-comment -// @ts-ignore -indyCredentialFormatService.formatKey = 'indy' -// eslint-disable-next-line @typescript-eslint/ban-ts-comment -// @ts-ignore -indyCredentialFormatService.credentialRecordType = 'indy' - -// eslint-disable-next-line @typescript-eslint/ban-ts-comment -// @ts-ignore -jsonLdCredentialFormatService.formatKey = 'jsonld' - const agentConfig = getAgentConfig('V2CredentialProtocolCredTest') const eventEmitter = new EventEmitter(agentConfig.agentDependencies, new Subject()) @@ -83,8 +61,6 @@ const agentContext = getAgentContext({ registerInstances: [ [CredentialRepository, credentialRepository], [DidCommMessageRepository, didCommMessageRepository], - [RoutingService, routingService], - [Dispatcher, dispatcher], [ConnectionService, connectionService], [EventEmitter, eventEmitter], ], @@ -96,11 +72,6 @@ const connection = getMockConnection({ state: DidExchangeState.Completed, }) -const credentialPreview = V1CredentialPreview.fromRecord({ - name: 'John', - age: '99', -}) - const offerAttachment = new V1Attachment({ id: 'offer-attachment-id', mimeType: 'application/json', @@ -123,13 +94,13 @@ const credentialAttachment = new V1Attachment({ mimeType: 'application/json', data: new V1AttachmentData({ base64: JsonEncoder.toBase64({ - values: IndyCredentialUtils.convertAttributesToValues(credentialPreview.attributes), + values: {}, }), }), }) const requestFormat = new CredentialFormatSpec({ - attachId: 'request-attachment-id', + attachmentId: 'request-attachment-id', format: 'hlindy/cred-filter@v2.0', }) @@ -143,17 +114,17 @@ const proposalAttachment = new V1Attachment({ }) const offerFormat = new CredentialFormatSpec({ - attachId: 'offer-attachment-id', + attachmentId: 'offer-attachment-id', format: 'hlindy/cred-abstract@v2.0', }) const proposalFormat = new CredentialFormatSpec({ - attachId: 'proposal-attachment-id', + attachmentId: 'proposal-attachment-id', format: 'hlindy/cred-abstract@v2.0', }) const credentialFormat = new CredentialFormatSpec({ - attachId: 'credential-attachment-id', + attachmentId: 'credential-attachment-id', format: 'hlindy/cred@v2.0', }) @@ -170,7 +141,9 @@ credentialRequestMessage.setThread({ threadId: 'somethreadid' }) const credentialOfferMessage = new V2OfferCredentialMessage({ formats: [offerFormat], comment: 'some comment', - credentialPreview: credentialPreview, + credentialPreview: new V2CredentialPreview({ + attributes: [], + }), offerAttachments: [offerAttachment], }) const credentialIssueMessage = new V2IssueCredentialMessage({ @@ -207,7 +180,6 @@ const getAgentMessageMock = async (agentContext: AgentContext, options: GetAgent // object to test our service would behave correctly. We use type assertion for `offer` attribute to `any`. const mockCredentialRecord = ({ state, - metadata, threadId, connectionId, tags, @@ -215,7 +187,6 @@ const mockCredentialRecord = ({ credentialAttributes, }: { state?: CredentialState - metadata?: IndyCredentialViewMetadata & { indyRequest: Record } tags?: CustomCredentialTags threadId?: string connectionId?: string @@ -224,13 +195,13 @@ const mockCredentialRecord = ({ } = {}) => { const credentialRecord = new CredentialExchangeRecord({ id, - credentialAttributes: credentialAttributes || credentialPreview.attributes, + credentialAttributes: credentialAttributes, state: state || CredentialState.OfferSent, threadId: threadId || 'thread-id', connectionId: connectionId ?? '123', credentials: [ { - credentialRecordType: 'indy', + credentialRecordType: 'test', credentialRecordId: '123456', }, ], @@ -238,25 +209,37 @@ const mockCredentialRecord = ({ protocolVersion: 'v2', }) - if (metadata?.indyRequest) { - credentialRecord.metadata.set(CredentialMetadataKeys.IndyRequest, { ...metadata.indyRequest }) - } - - if (metadata?.schemaId) { - credentialRecord.metadata.add(CredentialMetadataKeys.IndyCredential, { - schemaId: metadata.schemaId, - }) - } - - if (metadata?.credentialDefinitionId) { - credentialRecord.metadata.add(CredentialMetadataKeys.IndyCredential, { - credentialDefinitionId: metadata.credentialDefinitionId, - }) - } - return credentialRecord } +interface TestCredentialFormat extends CredentialFormat { + formatKey: 'test' + credentialRecordType: 'test' +} + +type TestCredentialFormatService = CredentialFormatService + +export const testCredentialFormatService = { + credentialRecordType: 'test', + formatKey: 'test', + supportsFormat: (_format: string) => true, + createOffer: async ( + _agentContext: AgentContext, + _options: CredentialFormatCreateOfferOptions + ) => ({ + attachment: offerAttachment, + format: offerFormat, + }), + acceptRequest: async ( + _agentContext: AgentContext, + _options: CredentialFormatAcceptRequestOptions + ) => ({ attachment: credentialAttachment, format: credentialFormat }), + deleteCredentialById: jest.fn(), + processCredential: jest.fn(), + acceptOffer: () => ({ attachment: requestAttachment, format: requestFormat }), + processRequest: jest.fn(), +} as unknown as TestCredentialFormatService + describe('credentialProtocol', () => { let credentialProtocol: V2CredentialProtocol @@ -272,7 +255,7 @@ describe('credentialProtocol', () => { ]) credentialProtocol = new V2CredentialProtocol({ - credentialFormats: [indyCredentialFormatService, jsonLdCredentialFormatService], + credentialFormats: [testCredentialFormatService], }) }) @@ -281,28 +264,17 @@ describe('credentialProtocol', () => { }) describe('acceptOffer', () => { - test(`updates state to ${CredentialState.RequestSent}, set request metadata`, async () => { + test(`updates state to ${CredentialState.RequestSent}`, async () => { const credentialRecord = mockCredentialRecord({ state: CredentialState.OfferReceived, threadId: 'fd9c5ddb-ec11-4acd-bc32-540736249746', connectionId: 'b1e2f039-aa39-40be-8643-6ce2797b5190', }) - mockFunction(indyCredentialFormatService.supportsFormat).mockReturnValue(true) - mockFunction(indyCredentialFormatService.acceptOffer).mockResolvedValue({ - attachment: requestAttachment, - format: requestFormat, - }) - // when await credentialProtocol.acceptOffer(agentContext, { credentialRecord, - credentialFormats: { - indy: { - attributes: credentialPreview.attributes, - credentialDefinitionId: 'Th7MpTaRZVRYnPiabds81Y:3:CL:17:TAG', - }, - }, + credentialFormats: {}, }) // then @@ -325,12 +297,6 @@ describe('credentialProtocol', () => { connectionId: 'b1e2f039-aa39-40be-8643-6ce2797b5190', }) - mockFunction(indyCredentialFormatService.supportsFormat).mockReturnValue(true) - mockFunction(indyCredentialFormatService.acceptOffer).mockResolvedValue({ - attachment: requestAttachment, - format: requestFormat, - }) - // when const { message: credentialRequest } = await credentialProtocol.acceptOffer(agentContext, { credentialRecord, @@ -365,8 +331,6 @@ describe('credentialProtocol', () => { describe('processRequest', () => { test(`updates state to ${CredentialState.RequestReceived}, set request and returns credential record`, async () => { - mockFunction(indyCredentialFormatService.supportsFormat).mockReturnValue(true) - const credentialRecord = mockCredentialRecord({ state: CredentialState.OfferSent }) const messageContext = new InboundMessageContext(credentialRequestMessage, { connection, @@ -389,8 +353,6 @@ describe('credentialProtocol', () => { }) test(`emits stateChange event from ${CredentialState.OfferSent} to ${CredentialState.RequestReceived}`, async () => { - mockFunction(indyCredentialFormatService.supportsFormat).mockReturnValue(true) - const credentialRecord = mockCredentialRecord({ state: CredentialState.OfferSent }) const messageContext = new InboundMessageContext(credentialRequestMessage, { connection, @@ -421,8 +383,6 @@ describe('credentialProtocol', () => { const validState = CredentialState.OfferSent const invalidCredentialStates = Object.values(CredentialState).filter((state) => state !== validState) test(`throws an error when state transition is invalid`, async () => { - mockFunction(indyCredentialFormatService.supportsFormat).mockReturnValue(true) - const messageContext = new InboundMessageContext(credentialRequestMessage, { connection, agentContext, @@ -443,12 +403,6 @@ describe('credentialProtocol', () => { describe('acceptRequest', () => { test(`updates state to ${CredentialState.CredentialIssued}`, async () => { - mockFunction(indyCredentialFormatService.supportsFormat).mockReturnValue(true) - mockFunction(indyCredentialFormatService.acceptRequest).mockResolvedValue({ - attachment: credentialAttachment, - format: credentialFormat, - }) - const credentialRecord = mockCredentialRecord({ state: CredentialState.RequestReceived, connectionId: 'b1e2f039-aa39-40be-8643-6ce2797b5190', @@ -470,12 +424,6 @@ describe('credentialProtocol', () => { }) test(`emits stateChange event from ${CredentialState.RequestReceived} to ${CredentialState.CredentialIssued}`, async () => { - mockFunction(indyCredentialFormatService.supportsFormat).mockReturnValue(true) - mockFunction(indyCredentialFormatService.acceptRequest).mockResolvedValue({ - attachment: credentialAttachment, - format: credentialFormat, - }) - const credentialRecord = mockCredentialRecord({ state: CredentialState.RequestReceived, connectionId: 'b1e2f039-aa39-40be-8643-6ce2797b5190', @@ -509,12 +457,6 @@ describe('credentialProtocol', () => { }) test('returns credential response message base on credential request message', async () => { - mockFunction(indyCredentialFormatService.supportsFormat).mockReturnValue(true) - mockFunction(indyCredentialFormatService.acceptRequest).mockResolvedValue({ - attachment: credentialAttachment, - format: credentialFormat, - }) - const credentialRecord = mockCredentialRecord({ state: CredentialState.RequestReceived, connectionId: 'b1e2f039-aa39-40be-8643-6ce2797b5190', @@ -547,8 +489,6 @@ describe('credentialProtocol', () => { describe('processCredential', () => { test('finds credential record by thread ID and saves credential attachment into the wallet', async () => { - mockFunction(indyCredentialFormatService.supportsFormat).mockReturnValue(true) - const credentialRecord = mockCredentialRecord({ state: CredentialState.RequestSent, }) @@ -561,10 +501,7 @@ describe('credentialProtocol', () => { // given mockFunction(credentialRepository.getSingleByQuery).mockResolvedValue(credentialRecord) - // when - const record = await credentialProtocol.processCredential(messageContext) - - expect(record.credentialAttributes?.length).toBe(2) + await credentialProtocol.processCredential(messageContext) }) }) @@ -687,22 +624,25 @@ describe('credentialProtocol', () => { }) describe('createProblemReport', () => { - test('returns problem report message base once get error', () => { + test('returns problem report message base once get error', async () => { // given const credentialRecord = mockCredentialRecord({ state: CredentialState.OfferReceived, threadId: 'somethreadid', connectionId: 'b1e2f039-aa39-40be-8643-6ce2797b5190', }) - const message = 'Indy error' + const description = 'Indy error' mockFunction(credentialRepository.getById).mockResolvedValue(credentialRecord) // when - const credentialProblemReportMessage = credentialProtocol.createProblemReport(agentContext, { message }) + const { message } = await credentialProtocol.createProblemReport(agentContext, { + description, + credentialRecord, + }) - credentialProblemReportMessage.setThread({ threadId: 'somethreadid' }) + message.setThread({ threadId: 'somethreadid' }) // then - expect(credentialProblemReportMessage.toJSON()).toMatchObject({ + expect(message.toJSON()).toMatchObject({ '@id': expect.any(String), '@type': 'https://didcomm.org/issue-credential/2.0/problem-report', '~thread': { @@ -710,21 +650,21 @@ describe('credentialProtocol', () => { }, description: { code: CredentialProblemReportReason.IssuanceAbandoned, - en: message, + en: description, }, }) }) }) describe('processProblemReport', () => { - const credentialProblemReportMessage = new V2CredentialProblemReportMessage({ + const message = new V2CredentialProblemReportMessage({ description: { en: 'Indy error', code: CredentialProblemReportReason.IssuanceAbandoned, }, }) - credentialProblemReportMessage.setThread({ threadId: 'somethreadid' }) - const messageContext = new InboundMessageContext(credentialProblemReportMessage, { + message.setThread({ threadId: 'somethreadid' }) + const messageContext = new InboundMessageContext(message, { connection, agentContext, }) @@ -813,8 +753,8 @@ describe('credentialProtocol', () => { expect(repositoryDeleteSpy).toHaveBeenNthCalledWith(1, agentContext, credentialRecord) }) - it('should call deleteCredentialById in indyCredentialFormatService if deleteAssociatedCredential is true', async () => { - const deleteCredentialMock = mockFunction(indyCredentialFormatService.deleteCredentialById) + it('should call deleteCredentialById in testCredentialFormatService if deleteAssociatedCredential is true', async () => { + const deleteCredentialMock = mockFunction(testCredentialFormatService.deleteCredentialById) const credentialRecord = mockCredentialRecord() mockFunction(credentialRepository.getById).mockResolvedValue(credentialRecord) @@ -831,8 +771,8 @@ describe('credentialProtocol', () => { ) }) - it('should not call deleteCredentialById in indyCredentialFormatService if deleteAssociatedCredential is false', async () => { - const deleteCredentialMock = mockFunction(indyCredentialFormatService.deleteCredentialById) + it('should not call deleteCredentialById in testCredentialFormatService if deleteAssociatedCredential is false', async () => { + const deleteCredentialMock = mockFunction(testCredentialFormatService.deleteCredentialById) const credentialRecord = mockCredentialRecord() mockFunction(credentialRepository.getById).mockResolvedValue(credentialRecord) @@ -846,7 +786,7 @@ describe('credentialProtocol', () => { }) it('deleteAssociatedCredentials should default to true', async () => { - const deleteCredentialMock = mockFunction(indyCredentialFormatService.deleteCredentialById) + const deleteCredentialMock = mockFunction(testCredentialFormatService.deleteCredentialById) const credentialRecord = mockCredentialRecord() mockFunction(credentialRepository.getById).mockResolvedValue(credentialRecord) @@ -860,7 +800,7 @@ describe('credentialProtocol', () => { ) }) it('deleteAssociatedDidCommMessages should default to true', async () => { - const deleteCredentialMock = mockFunction(indyCredentialFormatService.deleteCredentialById) + const deleteCredentialMock = mockFunction(testCredentialFormatService.deleteCredentialById) const credentialRecord = mockCredentialRecord() mockFunction(credentialRepository.getById).mockResolvedValue(credentialRecord) @@ -875,68 +815,4 @@ describe('credentialProtocol', () => { expect(didCommMessageRepository.delete).toHaveBeenCalledTimes(3) }) }) - - describe('declineOffer', () => { - test(`updates state to ${CredentialState.Declined}`, async () => { - const credentialRecord = mockCredentialRecord({ - state: CredentialState.OfferReceived, - }) - - // when - await credentialProtocol.declineOffer(agentContext, credentialRecord) - - // then - - expect(credentialRepository.update).toHaveBeenNthCalledWith( - 1, - agentContext, - expect.objectContaining({ - state: CredentialState.Declined, - }) - ) - }) - - test(`emits stateChange event from ${CredentialState.OfferReceived} to ${CredentialState.Declined}`, async () => { - const credentialRecord = mockCredentialRecord({ - state: CredentialState.OfferReceived, - }) - - const eventListenerMock = jest.fn() - eventEmitter.on(CredentialEventTypes.CredentialStateChanged, eventListenerMock) - - // given - mockFunction(credentialRepository.getSingleByQuery).mockResolvedValue(credentialRecord) - - // when - await credentialProtocol.declineOffer(agentContext, credentialRecord) - - // then - expect(eventListenerMock).toHaveBeenCalledTimes(1) - const [[event]] = eventListenerMock.mock.calls - expect(event).toMatchObject({ - type: 'CredentialStateChanged', - metadata: { - contextCorrelationId: 'mock', - }, - payload: { - previousState: CredentialState.OfferReceived, - credentialRecord: expect.objectContaining({ - state: CredentialState.Declined, - }), - }, - }) - }) - - const validState = CredentialState.OfferReceived - const invalidCredentialStates = Object.values(CredentialState).filter((state) => state !== validState) - test(`throws an error when state transition is invalid`, async () => { - await Promise.all( - invalidCredentialStates.map(async (state) => { - await expect( - credentialProtocol.declineOffer(agentContext, mockCredentialRecord({ state })) - ).rejects.toThrowError(`Credential record is in invalid state ${state}. Valid states are: ${validState}.`) - }) - ) - }) - }) }) diff --git a/packages/core/src/modules/credentials/protocol/v2/__tests__/V2CredentialProtocolOffer.test.ts b/packages/core/src/modules/credentials/protocol/v2/__tests__/V2CredentialProtocolOffer.test.ts index 000cd5d524..8e37ba3ea5 100644 --- a/packages/core/src/modules/credentials/protocol/v2/__tests__/V2CredentialProtocolOffer.test.ts +++ b/packages/core/src/modules/credentials/protocol/v2/__tests__/V2CredentialProtocolOffer.test.ts @@ -1,5 +1,8 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +import type { AgentContext } from '../../../../../agent' import type { CredentialStateChangedEvent } from '../../../CredentialEvents' -import type { CreateOfferOptions } from '../../../CredentialProtocolOptions' +import type { CredentialFormat, CredentialFormatCreateOfferOptions, CredentialFormatService } from '../../../formats' +import type { CreateCredentialOfferOptions } from '../../CredentialProtocolOptions' import { Subject } from 'rxjs' @@ -12,25 +15,70 @@ import { DidCommMessageRepository } from '../../../../../storage' import { JsonTransformer } from '../../../../../utils' import { DidExchangeState } from '../../../../connections' import { ConnectionService } from '../../../../connections/services/ConnectionService' -import { IndyLedgerService } from '../../../../ledger/services' import { RoutingService } from '../../../../routing/services/RoutingService' import { CredentialEventTypes } from '../../../CredentialEvents' -import { credDef, schema } from '../../../__tests__/fixtures' -import { IndyCredentialFormatService } from '../../../formats/indy/IndyCredentialFormatService' -import { JsonLdCredentialFormatService } from '../../../formats/jsonld/JsonLdCredentialFormatService' import { CredentialFormatSpec } from '../../../models' import { CredentialState } from '../../../models/CredentialState' import { CredentialExchangeRecord } from '../../../repository/CredentialExchangeRecord' import { CredentialRepository } from '../../../repository/CredentialRepository' -import { V1CredentialPreview } from '../../v1/messages/V1CredentialPreview' import { V2CredentialProtocol } from '../V2CredentialProtocol' +import { V2CredentialPreview } from '../messages' import { V2OfferCredentialMessage } from '../messages/V2OfferCredentialMessage' +const offerFormat = new CredentialFormatSpec({ + attachmentId: 'offer-attachment-id', + format: 'hlindy/cred-abstract@v2.0', +}) + +const offerAttachment = new V1Attachment({ + id: 'offer-attachment-id', + mimeType: 'application/json', + data: new V1AttachmentData({ + base64: + 'eyJzY2hlbWFfaWQiOiJhYWEiLCJjcmVkX2RlZl9pZCI6IlRoN01wVGFSWlZSWW5QaWFiZHM4MVk6MzpDTDoxNzpUQUciLCJub25jZSI6Im5vbmNlIiwia2V5X2NvcnJlY3RuZXNzX3Byb29mIjp7fX0', + }), +}) + +interface TestCredentialFormat extends CredentialFormat { + formatKey: 'test' + credentialRecordType: 'test' +} + +type TestCredentialFormatService = CredentialFormatService + +export const testCredentialFormatService = { + credentialRecordType: 'test', + formatKey: 'test', + supportsFormat: (_format: string) => true, + createOffer: async ( + _agentContext: AgentContext, + _options: CredentialFormatCreateOfferOptions + ) => ({ + attachment: offerAttachment, + format: offerFormat, + previewAttributes: [ + { + mimeType: 'text/plain', + name: 'name', + value: 'John', + }, + { + mimeType: 'text/plain', + name: 'age', + value: '99', + }, + ], + }), + acceptRequest: jest.fn(), + deleteCredentialById: jest.fn(), + processCredential: jest.fn(), + acceptOffer: jest.fn(), + processRequest: jest.fn(), + processOffer: jest.fn(), +} as unknown as TestCredentialFormatService + // Mock classes jest.mock('../../../repository/CredentialRepository') -jest.mock('../../../../ledger/services/IndyLedgerService') -jest.mock('../../../formats/indy/IndyCredentialFormatService') -jest.mock('../../../formats/jsonld/JsonLdCredentialFormatService') jest.mock('../../../../../storage/didcomm/DidCommMessageRepository') jest.mock('../../../../routing/services/RoutingService') jest.mock('../../../../connections/services/ConnectionService') @@ -38,9 +86,6 @@ jest.mock('../../../../../agent/Dispatcher') // Mock typed object const CredentialRepositoryMock = CredentialRepository as jest.Mock -const IndyLedgerServiceMock = IndyLedgerService as jest.Mock -const IndyCredentialFormatServiceMock = IndyCredentialFormatService as jest.Mock -const JsonLdCredentialFormatServiceMock = JsonLdCredentialFormatService as jest.Mock const DidCommMessageRepositoryMock = DidCommMessageRepository as jest.Mock const RoutingServiceMock = RoutingService as jest.Mock const ConnectionServiceMock = ConnectionService as jest.Mock @@ -49,19 +94,9 @@ const DispatcherMock = Dispatcher as jest.Mock const credentialRepository = new CredentialRepositoryMock() const didCommMessageRepository = new DidCommMessageRepositoryMock() const routingService = new RoutingServiceMock() -const indyLedgerService = new IndyLedgerServiceMock() -const indyCredentialFormatService = new IndyCredentialFormatServiceMock() -const jsonLdCredentialFormatService = new JsonLdCredentialFormatServiceMock() const dispatcher = new DispatcherMock() const connectionService = new ConnectionServiceMock() -// eslint-disable-next-line @typescript-eslint/ban-ts-comment -// @ts-ignore -indyCredentialFormatService.formatKey = 'indy' - -// eslint-disable-next-line @typescript-eslint/ban-ts-comment -// @ts-ignore -jsonLdCredentialFormatService.formatKey = 'jsonld' const agentConfig = getAgentConfig('V2CredentialProtocolOfferTest') const eventEmitter = new EventEmitter(agentConfig.agentDependencies, new Subject()) @@ -70,7 +105,6 @@ const agentContext = getAgentContext({ [CredentialRepository, credentialRepository], [DidCommMessageRepository, didCommMessageRepository], [RoutingService, routingService], - [IndyLedgerService, indyLedgerService], [Dispatcher, dispatcher], [ConnectionService, connectionService], [EventEmitter, eventEmitter], @@ -78,40 +112,20 @@ const agentContext = getAgentContext({ agentConfig, }) -const connection = getMockConnection({ +const connectionRecord = getMockConnection({ id: '123', state: DidExchangeState.Completed, }) -const credentialPreview = V1CredentialPreview.fromRecord({ - name: 'John', - age: '99', -}) -const offerFormat = new CredentialFormatSpec({ - attachId: 'offer-attachment-id', - format: 'hlindy/cred-abstract@v2.0', -}) - -const offerAttachment = new V1Attachment({ - id: 'offer-attachment-id', - mimeType: 'application/json', - data: new V1AttachmentData({ - base64: - 'eyJzY2hlbWFfaWQiOiJhYWEiLCJjcmVkX2RlZl9pZCI6IlRoN01wVGFSWlZSWW5QaWFiZHM4MVk6MzpDTDoxNzpUQUciLCJub25jZSI6Im5vbmNlIiwia2V5X2NvcnJlY3RuZXNzX3Byb29mIjp7fX0', - }), -}) - describe('V2CredentialProtocolOffer', () => { let credentialProtocol: V2CredentialProtocol beforeEach(async () => { // mock function implementations - mockFunction(connectionService.getById).mockResolvedValue(connection) - mockFunction(indyLedgerService.getCredentialDefinition).mockResolvedValue(credDef) - mockFunction(indyLedgerService.getSchema).mockResolvedValue(schema) + mockFunction(connectionService.getById).mockResolvedValue(connectionRecord) credentialProtocol = new V2CredentialProtocol({ - credentialFormats: [indyCredentialFormatService, jsonLdCredentialFormatService], + credentialFormats: [testCredentialFormatService], }) }) @@ -120,24 +134,15 @@ describe('V2CredentialProtocolOffer', () => { }) describe('createOffer', () => { - const offerOptions: CreateOfferOptions<[IndyCredentialFormatService]> = { + const offerOptions: CreateCredentialOfferOptions<[TestCredentialFormatService]> = { comment: 'some comment', - connection, + connectionRecord, credentialFormats: { - indy: { - attributes: credentialPreview.attributes, - credentialDefinitionId: 'Th7MpTaRZVRYnPiabds81Y:3:CL:17:TAG', - }, + test: {}, }, } test(`creates credential record in ${CredentialState.OfferSent} state with offer, thread ID`, async () => { - mockFunction(indyCredentialFormatService.supportsFormat).mockReturnValue(true) - mockFunction(indyCredentialFormatService.createOffer).mockResolvedValue({ - attachment: offerAttachment, - format: offerFormat, - }) - // when await credentialProtocol.createOffer(agentContext, offerOptions) @@ -150,18 +155,12 @@ describe('V2CredentialProtocolOffer', () => { id: expect.any(String), createdAt: expect.any(Date), state: CredentialState.OfferSent, - connectionId: connection.id, + connectionId: connectionRecord.id, }) ) }) test(`emits stateChange event with a new credential in ${CredentialState.OfferSent} state`, async () => { - mockFunction(indyCredentialFormatService.supportsFormat).mockReturnValue(true) - mockFunction(indyCredentialFormatService.createOffer).mockResolvedValue({ - attachment: offerAttachment, - format: offerFormat, - }) - const eventListenerMock = jest.fn() eventEmitter.on(CredentialEventTypes.CredentialStateChanged, eventListenerMock) @@ -182,13 +181,6 @@ describe('V2CredentialProtocolOffer', () => { }) test('returns credential offer message', async () => { - mockFunction(indyCredentialFormatService.supportsFormat).mockReturnValue(true) - mockFunction(indyCredentialFormatService.createOffer).mockResolvedValue({ - attachment: offerAttachment, - format: offerFormat, - previewAttributes: credentialPreview.attributes, - }) - const { message: credentialOffer } = await credentialProtocol.createOffer(agentContext, offerOptions) expect(credentialOffer.toJSON()).toMatchObject({ @@ -220,15 +212,18 @@ describe('V2CredentialProtocolOffer', () => { const credentialOfferMessage = new V2OfferCredentialMessage({ formats: [offerFormat], comment: 'some comment', - credentialPreview, + credentialPreview: new V2CredentialPreview({ + attributes: [], + }), offerAttachments: [offerAttachment], }) - const messageContext = new InboundMessageContext(credentialOfferMessage, { agentContext, connection }) + const messageContext = new InboundMessageContext(credentialOfferMessage, { + agentContext, + connection: connectionRecord, + }) test(`creates and return credential record in ${CredentialState.OfferReceived} state with offer, thread ID`, async () => { - mockFunction(indyCredentialFormatService.supportsFormat).mockReturnValue(true) - // when await credentialProtocol.processOffer(messageContext) @@ -241,15 +236,13 @@ describe('V2CredentialProtocolOffer', () => { id: expect.any(String), createdAt: expect.any(Date), threadId: credentialOfferMessage.id, - connectionId: connection.id, + connectionId: connectionRecord.id, state: CredentialState.OfferReceived, }) ) }) test(`emits stateChange event with ${CredentialState.OfferReceived}`, async () => { - mockFunction(indyCredentialFormatService.supportsFormat).mockReturnValue(true) - const eventListenerMock = jest.fn() eventEmitter.on(CredentialEventTypes.CredentialStateChanged, eventListenerMock) diff --git a/packages/core/src/modules/credentials/protocol/v2/__tests__/v2-connectionless-credentials.e2e.test.ts b/packages/core/src/modules/credentials/protocol/v2/__tests__/v2-connectionless-credentials.e2e.test.ts index 0c62263961..1fcbc44ed8 100644 --- a/packages/core/src/modules/credentials/protocol/v2/__tests__/v2-connectionless-credentials.e2e.test.ts +++ b/packages/core/src/modules/credentials/protocol/v2/__tests__/v2-connectionless-credentials.e2e.test.ts @@ -1,4 +1,5 @@ import type { SubjectMessage } from '../../../../../../../../tests/transport/SubjectInboundTransport' +import type { AnonCredsTestsAgent } from '../../../../../../../anoncreds/tests/legacyAnonCredsSetup' import type { CredentialStateChangedEvent } from '../../../CredentialEvents' import type { AcceptCredentialOfferOptions, AcceptCredentialRequestOptions } from '../../../CredentialsApiOptions' @@ -6,7 +7,11 @@ import { ReplaySubject, Subject } from 'rxjs' import { SubjectInboundTransport } from '../../../../../../../../tests/transport/SubjectInboundTransport' import { SubjectOutboundTransport } from '../../../../../../../../tests/transport/SubjectOutboundTransport' -import { prepareForIssuance, waitForCredentialRecordSubject, getAgentOptions } from '../../../../../../tests/helpers' +import { + getLegacyAnonCredsModules, + prepareForAnonCredsIssuance, +} from '../../../../../../../anoncreds/tests/legacyAnonCredsSetup' +import { waitForCredentialRecordSubject, getAgentOptions } from '../../../../../../tests/helpers' import testLogger from '../../../../../../tests/logger' import { Agent } from '../../../../../agent/Agent' import { CredentialEventTypes } from '../../../CredentialEvents' @@ -15,13 +20,21 @@ import { CredentialState } from '../../../models/CredentialState' import { CredentialExchangeRecord } from '../../../repository/CredentialExchangeRecord' import { V2CredentialPreview } from '../messages' -const faberAgentOptions = getAgentOptions('Faber connection-less Credentials V2', { - endpoints: ['rxjs:faber'], -}) - -const aliceAgentOptions = getAgentOptions('Alice connection-less Credentials V2', { - endpoints: ['rxjs:alice'], -}) +const faberAgentOptions = getAgentOptions( + 'Faber connection-less Credentials V2', + { + endpoints: ['rxjs:faber'], + }, + getLegacyAnonCredsModules() +) + +const aliceAgentOptions = getAgentOptions( + 'Alice connection-less Credentials V2', + { + endpoints: ['rxjs:alice'], + }, + getLegacyAnonCredsModules() +) const credentialPreview = V2CredentialPreview.fromRecord({ name: 'John', @@ -29,8 +42,8 @@ const credentialPreview = V2CredentialPreview.fromRecord({ }) describe('V2 Connectionless Credentials', () => { - let faberAgent: Agent - let aliceAgent: Agent + let faberAgent: AnonCredsTestsAgent + let aliceAgent: AnonCredsTestsAgent let faberReplay: ReplaySubject let aliceReplay: ReplaySubject let credentialDefinitionId: string @@ -53,8 +66,16 @@ describe('V2 Connectionless Credentials', () => { aliceAgent.registerOutboundTransport(new SubjectOutboundTransport(subjectMap)) await aliceAgent.initialize() - const { definition } = await prepareForIssuance(faberAgent, ['name', 'age']) - credentialDefinitionId = definition.id + // Create link secret for alice + await aliceAgent.modules.anoncreds.createLinkSecret({ + linkSecretId: 'default', + setAsDefault: true, + }) + + const { credentialDefinition } = await prepareForAnonCredsIssuance(faberAgent, { + attributeNames: ['name', 'age'], + }) + credentialDefinitionId = credentialDefinition.credentialDefinitionId faberReplay = new ReplaySubject() aliceReplay = new ReplaySubject() @@ -144,14 +165,14 @@ describe('V2 Connectionless Credentials', () => { createdAt: expect.any(Date), metadata: { data: { - '_internal/indyCredential': { + '_anoncreds/credential': { credentialDefinitionId, }, }, }, credentials: [ { - credentialRecordType: 'indy', + credentialRecordType: 'anoncreds', credentialRecordId: expect.any(String), }, ], @@ -165,7 +186,7 @@ describe('V2 Connectionless Credentials', () => { createdAt: expect.any(Date), metadata: { data: { - '_internal/indyCredential': { + '_anoncreds/credential': { credentialDefinitionId, }, }, @@ -225,14 +246,14 @@ describe('V2 Connectionless Credentials', () => { createdAt: expect.any(Date), metadata: { data: { - '_internal/indyCredential': { + '_anoncreds/credential': { credentialDefinitionId: credentialDefinitionId, }, }, }, credentials: [ { - credentialRecordType: 'indy', + credentialRecordType: 'anoncreds', credentialRecordId: expect.any(String), }, ], diff --git a/packages/core/src/modules/credentials/protocol/v2/__tests__/v2-credentials-auto-accept.e2e.test.ts b/packages/core/src/modules/credentials/protocol/v2/__tests__/v2-credentials-auto-accept.e2e.test.ts index 0bd25c345e..88422a79c4 100644 --- a/packages/core/src/modules/credentials/protocol/v2/__tests__/v2-credentials-auto-accept.e2e.test.ts +++ b/packages/core/src/modules/credentials/protocol/v2/__tests__/v2-credentials-auto-accept.e2e.test.ts @@ -1,23 +1,24 @@ -import type { Agent } from '../../../../../agent/Agent' -import type { ConnectionRecord } from '../../../../connections' -import type { AcceptCredentialOfferOptions, AcceptCredentialProposalOptions } from '../../../CredentialsApiOptions' -import type { Schema } from 'indy-sdk' +import type { AnonCredsTestsAgent } from '../../../../../../../anoncreds/tests/legacyAnonCredsSetup' +import type { EventReplaySubject } from '../../../../../../tests' -import { setupCredentialTests, waitForCredentialRecord } from '../../../../../../tests/helpers' +import { setupAnonCredsTests } from '../../../../../../../anoncreds/tests/legacyAnonCredsSetup' +import { waitForCredentialRecord, waitForCredentialRecordSubject } from '../../../../../../tests/helpers' import testLogger from '../../../../../../tests/logger' -import { JsonTransformer } from '../../../../../utils/JsonTransformer' import { AutoAcceptCredential } from '../../../models/CredentialAutoAcceptType' import { CredentialState } from '../../../models/CredentialState' import { CredentialExchangeRecord } from '../../../repository/CredentialExchangeRecord' import { V2CredentialPreview } from '../messages/V2CredentialPreview' -describe('v2 credentials', () => { - let faberAgent: Agent - let aliceAgent: Agent - let credDefId: string - let schema: Schema - let faberConnection: ConnectionRecord - let aliceConnection: ConnectionRecord +describe('V2 Credentials Auto Accept', () => { + let faberAgent: AnonCredsTestsAgent + let faberReplay: EventReplaySubject + let aliceAgent: AnonCredsTestsAgent + let aliceReplay: EventReplaySubject + let credentialDefinitionId: string + let schemaId: string + let faberConnectionId: string + let aliceConnectionId: string + const credentialPreview = V2CredentialPreview.fromRecord({ name: 'John', age: '99', @@ -31,13 +32,23 @@ describe('v2 credentials', () => { profile_picture: 'another profile picture', }) - describe('Auto accept on `always`', () => { + describe("Auto accept on 'always'", () => { beforeAll(async () => { - ;({ faberAgent, aliceAgent, credDefId, schema, faberConnection, aliceConnection } = await setupCredentialTests( - 'faber agent: always v2', - 'alice agent: always v2', - AutoAcceptCredential.Always - )) + ;({ + issuerAgent: faberAgent, + issuerReplay: faberReplay, + holderAgent: aliceAgent, + holderReplay: aliceReplay, + credentialDefinitionId, + schemaId, + issuerHolderConnectionId: faberConnectionId, + holderIssuerConnectionId: aliceConnectionId, + } = await setupAnonCredsTests({ + issuerName: 'faber agent: always v2', + holderName: 'alice agent: always v2', + autoAcceptCredentials: AutoAcceptCredential.Always, + attributeNames: ['name', 'age', 'x-ray', 'profile_picture'], + })) }) afterAll(async () => { @@ -47,35 +58,31 @@ describe('v2 credentials', () => { await aliceAgent.wallet.delete() }) - test('Alice starts with V2 credential proposal to Faber, both with autoAcceptCredential on `always`', async () => { - testLogger.test('Alice begins listening for credential') - const aliceCredReceivedPromise = waitForCredentialRecord(aliceAgent, { - state: CredentialState.CredentialReceived, - }) - - testLogger.test('Faber begins listening for credential ack') - const faberCredAckPromise = waitForCredentialRecord(faberAgent, { - state: CredentialState.Done, - }) - + test("Alice starts with V2 credential proposal to Faber, both with autoAcceptCredential on 'always'", async () => { testLogger.test('Alice sends credential proposal to Faber') - await aliceAgent.credentials.proposeCredential({ - connectionId: aliceConnection.id, + let aliceCredentialRecord = await aliceAgent.credentials.proposeCredential({ + connectionId: aliceConnectionId, protocolVersion: 'v2', credentialFormats: { indy: { attributes: credentialPreview.attributes, - credentialDefinitionId: credDefId, + credentialDefinitionId: credentialDefinitionId, }, }, comment: 'v2 propose credential test', }) testLogger.test('Alice waits for credential from Faber') - let aliceCredentialRecord = await aliceCredReceivedPromise + aliceCredentialRecord = await waitForCredentialRecordSubject(aliceReplay, { + state: CredentialState.Done, + threadId: aliceCredentialRecord.threadId, + }) testLogger.test('Faber waits for credential ack from Alice') - aliceCredentialRecord = await faberCredAckPromise + await waitForCredentialRecordSubject(faberReplay, { + state: CredentialState.Done, + threadId: aliceCredentialRecord.threadId, + }) expect(aliceCredentialRecord).toMatchObject({ type: CredentialExchangeRecord.type, @@ -83,9 +90,9 @@ describe('v2 credentials', () => { createdAt: expect.any(Date), metadata: { data: { - '_internal/indyCredential': { - schemaId: schema.id, - credentialDefinitionId: credDefId, + '_anoncreds/credential': { + schemaId, + credentialDefinitionId: credentialDefinitionId, }, }, }, @@ -93,48 +100,42 @@ describe('v2 credentials', () => { }) }) - test('Faber starts with V2 credential offer to Alice, both with autoAcceptCredential on `always`', async () => { - testLogger.test('Alice begins listening for credential') - const aliceCredReceivedPromise = waitForCredentialRecord(aliceAgent, { - state: CredentialState.CredentialReceived, - }) - - testLogger.test('Faber begins listening for credential ack') - const faberCredAckPromise = waitForCredentialRecord(faberAgent, { - state: CredentialState.Done, - }) - + test("Faber starts with V2 credential offer to Alice, both with autoAcceptCredential on 'always'", async () => { testLogger.test('Faber sends credential offer to Alice') - const schemaId = schema.id - await faberAgent.credentials.offerCredential({ + let faberCredentialRecord = await faberAgent.credentials.offerCredential({ comment: 'some comment about credential', - connectionId: faberConnection.id, + connectionId: faberConnectionId, credentialFormats: { indy: { attributes: credentialPreview.attributes, - credentialDefinitionId: credDefId, + credentialDefinitionId: credentialDefinitionId, }, }, protocolVersion: 'v2', }) + testLogger.test('Alice waits for credential from Faber') - const aliceCredentialRecord = await aliceCredReceivedPromise + const aliceCredentialRecord = await waitForCredentialRecordSubject(aliceReplay, { + state: CredentialState.CredentialReceived, + threadId: faberCredentialRecord.threadId, + }) + expect(aliceCredentialRecord).toMatchObject({ type: CredentialExchangeRecord.type, id: expect.any(String), createdAt: expect.any(Date), metadata: { data: { - '_internal/indyRequest': expect.any(Object), - '_internal/indyCredential': { + '_anoncreds/credentialRequest': expect.any(Object), + '_anoncreds/credential': { schemaId, - credentialDefinitionId: credDefId, + credentialDefinitionId: credentialDefinitionId, }, }, }, credentials: [ { - credentialRecordType: 'indy', + credentialRecordType: 'anoncreds', credentialRecordId: expect.any(String), }, ], @@ -142,7 +143,10 @@ describe('v2 credentials', () => { }) testLogger.test('Faber waits for credential ack from Alice') - const faberCredentialRecord: CredentialExchangeRecord = await faberCredAckPromise + faberCredentialRecord = await waitForCredentialRecordSubject(faberReplay, { + state: CredentialState.Done, + threadId: faberCredentialRecord.threadId, + }) expect(faberCredentialRecord).toMatchObject({ type: CredentialExchangeRecord.type, id: expect.any(String), @@ -152,13 +156,24 @@ describe('v2 credentials', () => { }) }) - describe('Auto accept on `contentApproved`', () => { + describe("Auto accept on 'contentApproved'", () => { + // FIXME: we don't need to set up the agent and create all schemas/credential definitions again, just change the auto accept credential setting beforeAll(async () => { - ;({ faberAgent, aliceAgent, credDefId, schema, faberConnection, aliceConnection } = await setupCredentialTests( - 'faber agent: contentApproved v2', - 'alice agent: contentApproved v2', - AutoAcceptCredential.ContentApproved - )) + ;({ + issuerAgent: faberAgent, + issuerReplay: faberReplay, + holderAgent: aliceAgent, + holderReplay: aliceReplay, + credentialDefinitionId, + schemaId, + issuerHolderConnectionId: faberConnectionId, + holderIssuerConnectionId: aliceConnectionId, + } = await setupAnonCredsTests({ + issuerName: 'Faber Agent: Always V2', + holderName: 'Alice Agent: Always V2', + autoAcceptCredentials: AutoAcceptCredential.ContentApproved, + attributeNames: ['name', 'age', 'x-ray', 'profile_picture'], + })) }) afterAll(async () => { @@ -168,90 +183,80 @@ describe('v2 credentials', () => { await aliceAgent.wallet.delete() }) - test('Alice starts with V2 credential proposal to Faber, both with autoAcceptCredential on `contentApproved`', async () => { + test("Alice starts with V2 credential proposal to Faber, both with autoAcceptCredential on 'contentApproved'", async () => { testLogger.test('Alice sends credential proposal to Faber') - const schemaId = schema.id - - testLogger.test('Faber starts listening for credential proposal from Alice') - const faberPropReceivedPromise = waitForCredentialRecord(faberAgent, { - state: CredentialState.ProposalReceived, - }) - await aliceAgent.credentials.proposeCredential({ - connectionId: aliceConnection.id, + let aliceCredentialRecord = await aliceAgent.credentials.proposeCredential({ + connectionId: aliceConnectionId, protocolVersion: 'v2', credentialFormats: { indy: { attributes: credentialPreview.attributes, - credentialDefinitionId: credDefId, + credentialDefinitionId: credentialDefinitionId, }, }, }) testLogger.test('Faber waits for credential proposal from Alice') - const faberPropReceivedRecord = await faberPropReceivedPromise - - const aliceCredReceivedPromise = waitForCredentialRecord(aliceAgent, { - threadId: faberPropReceivedRecord.threadId, - state: CredentialState.CredentialReceived, - }) - - const faberCredAckPromise = waitForCredentialRecord(faberAgent, { - threadId: faberPropReceivedRecord.threadId, - state: CredentialState.Done, + let faberCredentialRecord = await waitForCredentialRecordSubject(faberReplay, { + state: CredentialState.ProposalReceived, + threadId: aliceCredentialRecord.threadId, }) - const options: AcceptCredentialProposalOptions = { - credentialRecordId: faberPropReceivedRecord.id, + testLogger.test('Faber sends credential offer to Alice') + await faberAgent.credentials.acceptProposal({ + credentialRecordId: faberCredentialRecord.id, comment: 'V2 Indy Offer', credentialFormats: { indy: { - credentialDefinitionId: credDefId, + credentialDefinitionId: credentialDefinitionId, attributes: credentialPreview.attributes, }, }, - } - testLogger.test('Faber sends credential offer to Alice') - options.credentialRecordId = faberPropReceivedRecord.id - await faberAgent.credentials.acceptProposal(options) + }) testLogger.test('Alice waits for credential from Faber') - const aliceCredReceivedRecord = await aliceCredReceivedPromise + aliceCredentialRecord = await waitForCredentialRecordSubject(aliceReplay, { + state: CredentialState.Done, + threadId: faberCredentialRecord.threadId, + }) - expect(aliceCredReceivedRecord).toMatchObject({ + faberCredentialRecord = await waitForCredentialRecord(faberAgent, { + threadId: faberCredentialRecord.threadId, + state: CredentialState.Done, + }) + + expect(aliceCredentialRecord).toMatchObject({ type: CredentialExchangeRecord.type, id: expect.any(String), createdAt: expect.any(Date), metadata: { data: { - '_internal/indyRequest': expect.any(Object), - '_internal/indyCredential': { + '_anoncreds/credentialRequest': expect.any(Object), + '_anoncreds/credential': { schemaId, - credentialDefinitionId: credDefId, + credentialDefinitionId: credentialDefinitionId, }, }, }, credentials: [ { - credentialRecordType: 'indy', + credentialRecordType: 'anoncreds', credentialRecordId: expect.any(String), }, ], - state: CredentialState.CredentialReceived, + state: CredentialState.Done, }) - testLogger.test('Faber waits for credential ack from Alice') - const faberCredAckRecord = await faberCredAckPromise - - expect(faberCredAckRecord).toMatchObject({ + expect(faberCredentialRecord).toMatchObject({ type: CredentialExchangeRecord.type, id: expect.any(String), createdAt: expect.any(Date), metadata: { data: { - '_internal/indyCredential': { + '_anoncreds/credential': { schemaId, - credentialDefinitionId: credDefId, + credentialDefinitionId: credentialDefinitionId, }, }, }, @@ -259,86 +264,77 @@ describe('v2 credentials', () => { }) }) - test('Faber starts with V2 credential offer to Alice, both with autoAcceptCredential on `contentApproved`', async () => { - testLogger.test('Alice starts listening for credential offer from Faber') - const aliceOfferReceivedPromise = waitForCredentialRecord(aliceAgent, { - state: CredentialState.OfferReceived, - }) - + test("Faber starts with V2 credential offer to Alice, both with autoAcceptCredential on 'contentApproved'", async () => { testLogger.test('Faber sends credential offer to Alice') - const schemaId = schema.id - await faberAgent.credentials.offerCredential({ + let faberCredentialRecord = await faberAgent.credentials.offerCredential({ comment: 'some comment about credential', - connectionId: faberConnection.id, + connectionId: faberConnectionId, credentialFormats: { indy: { attributes: credentialPreview.attributes, - credentialDefinitionId: credDefId, + credentialDefinitionId: credentialDefinitionId, }, }, protocolVersion: 'v2', }) testLogger.test('Alice waits for credential offer from Faber') - const aliceOfferReceivedRecord = await aliceOfferReceivedPromise - - expect(JsonTransformer.toJSON(aliceOfferReceivedRecord)).toMatchObject({ + let aliceCredentialRecord = await waitForCredentialRecordSubject(aliceReplay, { state: CredentialState.OfferReceived, + threadId: faberCredentialRecord.threadId, }) // below values are not in json object - expect(aliceOfferReceivedRecord.id).not.toBeNull() - expect(aliceOfferReceivedRecord.getTags()).toEqual({ - threadId: aliceOfferReceivedRecord.threadId, - state: aliceOfferReceivedRecord.state, - connectionId: aliceConnection.id, + expect(aliceCredentialRecord.id).not.toBeNull() + expect(aliceCredentialRecord.getTags()).toEqual({ + threadId: aliceCredentialRecord.threadId, + state: aliceCredentialRecord.state, + connectionId: aliceConnectionId, credentialIds: [], }) testLogger.test('Alice received credential offer from Faber') - testLogger.test('Alice starts listening for credential from Faber') - const aliceCredReceivedPromise = waitForCredentialRecord(aliceAgent, { - state: CredentialState.CredentialReceived, + testLogger.test('alice sends credential request to faber') + await aliceAgent.credentials.acceptOffer({ + credentialRecordId: aliceCredentialRecord.id, }) - const faberCredAckPromise = waitForCredentialRecord(faberAgent, { + testLogger.test('Alice waits for credential from Faber') + aliceCredentialRecord = await waitForCredentialRecordSubject(aliceReplay, { state: CredentialState.Done, + threadId: faberCredentialRecord.threadId, }) - const acceptOfferOptions: AcceptCredentialOfferOptions = { - credentialRecordId: aliceOfferReceivedRecord.id, - } - testLogger.test('alice sends credential request to faber') - await aliceAgent.credentials.acceptOffer(acceptOfferOptions) - - testLogger.test('Alice waits for credential from Faber') - const aliceCredReceivedRecord = await aliceCredReceivedPromise - expect(aliceCredReceivedRecord).toMatchObject({ + expect(aliceCredentialRecord).toMatchObject({ type: CredentialExchangeRecord.type, id: expect.any(String), createdAt: expect.any(Date), metadata: { data: { - '_internal/indyRequest': expect.any(Object), - '_internal/indyCredential': { + '_anoncreds/credentialRequest': expect.any(Object), + '_anoncreds/credential': { schemaId, - credentialDefinitionId: credDefId, + credentialDefinitionId: credentialDefinitionId, }, }, }, credentials: [ { - credentialRecordType: 'indy', + credentialRecordType: 'anoncreds', credentialRecordId: expect.any(String), }, ], - state: CredentialState.CredentialReceived, + state: CredentialState.Done, }) testLogger.test('Faber waits for credential ack from Alice') - const faberCredAckRecord = await faberCredAckPromise - expect(faberCredAckRecord).toMatchObject({ + faberCredentialRecord = await waitForCredentialRecordSubject(faberReplay, { + threadId: faberCredentialRecord.threadId, + state: CredentialState.Done, + }) + + expect(faberCredentialRecord).toMatchObject({ type: CredentialExchangeRecord.type, id: expect.any(String), createdAt: expect.any(Date), @@ -346,122 +342,99 @@ describe('v2 credentials', () => { }) }) - test('Alice starts with V2 credential proposal to Faber, both have autoAcceptCredential on `contentApproved` and attributes did change', async () => { - testLogger.test('Faber starts listening for proposal from Alice') - const faberPropReceivedPromise = waitForCredentialRecord(faberAgent, { - state: CredentialState.ProposalReceived, - }) - + test("Alice starts with V2 credential proposal to Faber, both have autoAcceptCredential on 'contentApproved' and attributes did change", async () => { testLogger.test('Alice sends credential proposal to Faber') - const aliceCredProposal = await aliceAgent.credentials.proposeCredential({ - connectionId: aliceConnection.id, + let aliceCredentialRecord = await aliceAgent.credentials.proposeCredential({ + connectionId: aliceConnectionId, protocolVersion: 'v2', credentialFormats: { indy: { attributes: credentialPreview.attributes, - credentialDefinitionId: credDefId, + credentialDefinitionId: credentialDefinitionId, }, }, comment: 'v2 propose credential test', }) - expect(aliceCredProposal.state).toBe(CredentialState.ProposalSent) + expect(aliceCredentialRecord.state).toBe(CredentialState.ProposalSent) testLogger.test('Faber waits for credential proposal from Alice') - const faberPropReceivedRecord = await faberPropReceivedPromise - - testLogger.test('Alice starts listening for credential offer from Faber') - const aliceOfferReceivedPromise = waitForCredentialRecord(aliceAgent, { - state: CredentialState.OfferReceived, + let faberCredentialRecord = await waitForCredentialRecordSubject(faberReplay, { + state: CredentialState.ProposalReceived, + threadId: aliceCredentialRecord.threadId, }) testLogger.test('Faber negotiated proposal, sending credential offer to Alice') - const faberOfferSentRecord = await faberAgent.credentials.negotiateProposal({ - credentialRecordId: faberPropReceivedRecord.id, + faberCredentialRecord = await faberAgent.credentials.negotiateProposal({ + credentialRecordId: faberCredentialRecord.id, credentialFormats: { indy: { - credentialDefinitionId: credDefId, + credentialDefinitionId: credentialDefinitionId, attributes: newCredentialPreview.attributes, }, }, }) testLogger.test('Alice waits for credential offer from Faber') - const aliceOfferReceivedRecord = await aliceOfferReceivedPromise + aliceCredentialRecord = await waitForCredentialRecordSubject(aliceReplay, { + state: CredentialState.OfferReceived, + threadId: faberCredentialRecord.threadId, + }) // below values are not in json object - expect(aliceOfferReceivedRecord.id).not.toBeNull() - expect(aliceOfferReceivedRecord.getTags()).toEqual({ - threadId: aliceOfferReceivedRecord.threadId, - state: aliceOfferReceivedRecord.state, - connectionId: aliceConnection.id, + expect(aliceCredentialRecord.id).not.toBeNull() + expect(aliceCredentialRecord.getTags()).toEqual({ + threadId: aliceCredentialRecord.threadId, + state: aliceCredentialRecord.state, + connectionId: aliceConnectionId, credentialIds: [], }) - - // Check if the state of the credential records did not change - const faberRecord = await faberAgent.credentials.getById(faberOfferSentRecord.id) - faberRecord.assertState(CredentialState.OfferSent) - - const aliceRecord = await aliceAgent.credentials.getById(aliceOfferReceivedRecord.id) - aliceRecord.assertState(CredentialState.OfferReceived) }) - test('Faber starts with V2 credential offer to Alice, both have autoAcceptCredential on `contentApproved` and attributes did change', async () => { - testLogger.test('Alice starts listening for offer from Faber') - const aliceCredentialExchangeRecordPromise = waitForCredentialRecord(aliceAgent, { - state: CredentialState.OfferReceived, - }) - + test("Faber starts with V2 credential offer to Alice, both have autoAcceptCredential on 'contentApproved' and attributes did change", async () => { testLogger.test('Faber sends credential offer to Alice') - await faberAgent.credentials.offerCredential({ + const faberCredentialRecord = await faberAgent.credentials.offerCredential({ comment: 'some comment about credential', - connectionId: faberConnection.id, + connectionId: faberConnectionId, credentialFormats: { indy: { attributes: credentialPreview.attributes, - credentialDefinitionId: credDefId, + credentialDefinitionId: credentialDefinitionId, }, }, protocolVersion: 'v2', }) testLogger.test('Alice waits for credential offer from Faber') - const aliceOfferReceivedRecord = await aliceCredentialExchangeRecordPromise + const aliceCredentialRecord = await waitForCredentialRecordSubject(aliceReplay, { + state: CredentialState.OfferReceived, + threadId: faberCredentialRecord.threadId, + }) // below values are not in json object - expect(aliceOfferReceivedRecord.id).not.toBeNull() - expect(aliceOfferReceivedRecord.getTags()).toEqual({ - threadId: aliceOfferReceivedRecord.threadId, - state: aliceOfferReceivedRecord.state, - connectionId: aliceConnection.id, + expect(aliceCredentialRecord.id).not.toBeNull() + expect(aliceCredentialRecord.getTags()).toEqual({ + threadId: aliceCredentialRecord.threadId, + state: aliceCredentialRecord.state, + connectionId: aliceConnectionId, credentialIds: [], }) - testLogger.test('Faber starts listening for proposal received') - const faberProposalReceivedPromise = waitForCredentialRecord(faberAgent, { - state: CredentialState.ProposalReceived, - }) - testLogger.test('Alice sends credential request to Faber') - const aliceCredRequestRecord = await aliceAgent.credentials.negotiateOffer({ - credentialRecordId: aliceOfferReceivedRecord.id, + await aliceAgent.credentials.negotiateOffer({ + credentialRecordId: aliceCredentialRecord.id, credentialFormats: { indy: { attributes: newCredentialPreview.attributes, - credentialDefinitionId: credDefId, + credentialDefinitionId: credentialDefinitionId, }, }, comment: 'v2 propose credential test', }) - testLogger.test('Faber waits for credential proposal from Alice') - const faberCredProposalRecord = await faberProposalReceivedPromise - - // Check if the state of fabers credential record did not change - const faberRecord = await faberAgent.credentials.getById(faberCredProposalRecord.id) - faberRecord.assertState(CredentialState.ProposalReceived) - - const aliceRecord = await aliceAgent.credentials.getById(aliceCredRequestRecord.id) - aliceRecord.assertState(CredentialState.ProposalSent) + await waitForCredentialRecordSubject(faberReplay, { + state: CredentialState.ProposalReceived, + threadId: aliceCredentialRecord.threadId, + }) }) }) }) diff --git a/packages/core/src/modules/credentials/protocol/v2/__tests__/v2-credentials.e2e.test.ts b/packages/core/src/modules/credentials/protocol/v2/__tests__/v2-credentials.e2e.test.ts index c3b73fd6ef..202368c089 100644 --- a/packages/core/src/modules/credentials/protocol/v2/__tests__/v2-credentials.e2e.test.ts +++ b/packages/core/src/modules/credentials/protocol/v2/__tests__/v2-credentials.e2e.test.ts @@ -1,27 +1,25 @@ -import type { Agent } from '../../../../../agent/Agent' -import type { ConnectionRecord } from '../../../../connections' -import type { CredentialStateChangedEvent } from '../../../CredentialEvents' -import type { IndyCredPropose } from '../../../formats/indy/models/IndyCredPropose' -import type { ReplaySubject } from 'rxjs' +import type { AnonCredsHolderService } from '../../../../../../../anoncreds/src' +import type { LegacyIndyProposeCredentialFormat } from '../../../../../../../anoncreds/src/formats/LegacyIndyCredentialFormat' +import type { AnonCredsTestsAgent } from '../../../../../../../anoncreds/tests/legacyAnonCredsSetup' +import type { EventReplaySubject } from '../../../../../../tests' +import { AnonCredsHolderServiceSymbol } from '../../../../../../../anoncreds/src' import { - issueCredential, - setupCredentialTests, - waitForCredentialRecord, - waitForCredentialRecordSubject, -} from '../../../../../../tests/helpers' + issueLegacyAnonCredsCredential, + setupAnonCredsTests, +} from '../../../../../../../anoncreds/tests/legacyAnonCredsSetup' +import { waitForCredentialRecord, waitForCredentialRecordSubject } from '../../../../../../tests' import testLogger from '../../../../../../tests/logger' import { DidCommMessageRepository } from '../../../../../storage' import { JsonTransformer } from '../../../../../utils' -import { IndyHolderService } from '../../../../indy/services/IndyHolderService' import { CredentialState } from '../../../models/CredentialState' import { CredentialExchangeRecord } from '../../../repository/CredentialExchangeRecord' import { + V2CredentialPreview, V2IssueCredentialMessage, + V2OfferCredentialMessage, V2ProposeCredentialMessage, V2RequestCredentialMessage, - V2CredentialPreview, - V2OfferCredentialMessage, } from '../messages' const credentialPreview = V2CredentialPreview.fromRecord({ @@ -32,17 +30,16 @@ const credentialPreview = V2CredentialPreview.fromRecord({ }) describe('v2 credentials', () => { - let faberAgent: Agent - let aliceAgent: Agent - let credDefId: string - let faberConnection: ConnectionRecord - let aliceConnection: ConnectionRecord - let aliceCredentialRecord: CredentialExchangeRecord - let faberCredentialRecord: CredentialExchangeRecord - let faberReplay: ReplaySubject - let aliceReplay: ReplaySubject - - let credPropose: IndyCredPropose + let faberAgent: AnonCredsTestsAgent + let aliceAgent: AnonCredsTestsAgent + let credentialDefinitionId: string + let faberConnectionId: string + let aliceConnectionId: string + + let faberReplay: EventReplaySubject + let aliceReplay: EventReplaySubject + + let indyCredentialProposal: LegacyIndyProposeCredentialFormat const newCredentialPreview = V2CredentialPreview.fromRecord({ name: 'John', @@ -52,11 +49,22 @@ describe('v2 credentials', () => { }) beforeAll(async () => { - ;({ faberAgent, aliceAgent, credDefId, faberConnection, aliceConnection, faberReplay, aliceReplay } = - await setupCredentialTests('Faber Agent Credentials v2', 'Alice Agent Credentials v2')) - - credPropose = { - credentialDefinitionId: credDefId, + ;({ + issuerAgent: faberAgent, + issuerReplay: faberReplay, + holderAgent: aliceAgent, + holderReplay: aliceReplay, + credentialDefinitionId, + issuerHolderConnectionId: faberConnectionId, + holderIssuerConnectionId: aliceConnectionId, + } = await setupAnonCredsTests({ + issuerName: 'Faber Agent Credentials v2', + holderName: 'Alice Agent Credentials v2', + attributeNames: ['name', 'age', 'x-ray', 'profile_picture'], + })) + + indyCredentialProposal = { + credentialDefinitionId: credentialDefinitionId, schemaIssuerDid: 'GMm4vMw8LLrLJjp81kRRLp', schemaName: 'ahoy', schemaVersion: '1.0', @@ -76,7 +84,7 @@ describe('v2 credentials', () => { testLogger.test('Alice sends (v2) credential proposal to Faber') const credentialExchangeRecord = await aliceAgent.credentials.proposeCredential({ - connectionId: aliceConnection.id, + connectionId: aliceConnectionId, protocolVersion: 'v2', credentialFormats: { indy: { @@ -93,14 +101,14 @@ describe('v2 credentials', () => { }) expect(credentialExchangeRecord).toMatchObject({ - connectionId: aliceConnection.id, + connectionId: aliceConnectionId, protocolVersion: 'v2', state: CredentialState.ProposalSent, threadId: expect.any(String), }) testLogger.test('Faber waits for credential proposal from Alice') - faberCredentialRecord = await waitForCredentialRecordSubject(faberReplay, { + let faberCredentialRecord = await waitForCredentialRecordSubject(faberReplay, { threadId: credentialExchangeRecord.threadId, state: CredentialState.ProposalReceived, }) @@ -111,14 +119,14 @@ describe('v2 credentials', () => { comment: 'V2 Indy Proposal', credentialFormats: { indy: { - credentialDefinitionId: credDefId, + credentialDefinitionId: credentialDefinitionId, attributes: credentialPreview.attributes, }, }, }) testLogger.test('Alice waits for credential offer from Faber') - aliceCredentialRecord = await waitForCredentialRecordSubject(aliceReplay, { + let aliceCredentialRecord = await waitForCredentialRecordSubject(aliceReplay, { threadId: faberCredentialRecord.threadId, state: CredentialState.OfferReceived, }) @@ -180,7 +188,7 @@ describe('v2 credentials', () => { }) expect(offerCredentialExchangeRecord).toMatchObject({ - connectionId: aliceConnection.id, + connectionId: aliceConnectionId, protocolVersion: 'v2', state: CredentialState.RequestSent, threadId: expect.any(String), @@ -216,34 +224,36 @@ describe('v2 credentials', () => { }) test('Faber issues credential which is then deleted from Alice`s wallet', async () => { - const { holderCredential } = await issueCredential({ + const { holderCredentialExchangeRecord } = await issueLegacyAnonCredsCredential({ issuerAgent: faberAgent, - issuerConnectionId: faberConnection.id, + issuerReplay: faberReplay, + issuerHolderConnectionId: faberConnectionId, holderAgent: aliceAgent, - credentialTemplate: { - credentialDefinitionId: credDefId, + holderReplay: aliceReplay, + offer: { + credentialDefinitionId: credentialDefinitionId, attributes: credentialPreview.attributes, }, }) // test that delete credential removes from both repository and wallet - // latter is tested by spying on holder service (Indy) to + // latter is tested by spying on holder service to // see if deleteCredential is called - const holderService = aliceAgent.dependencyManager.resolve(IndyHolderService) + const holderService = aliceAgent.dependencyManager.resolve(AnonCredsHolderServiceSymbol) const deleteCredentialSpy = jest.spyOn(holderService, 'deleteCredential') - await aliceAgent.credentials.deleteById(holderCredential.id, { + await aliceAgent.credentials.deleteById(holderCredentialExchangeRecord.id, { deleteAssociatedCredentials: true, deleteAssociatedDidCommMessages: true, }) expect(deleteCredentialSpy).toHaveBeenNthCalledWith( 1, aliceAgent.context, - holderCredential.credentials[0].credentialRecordId + holderCredentialExchangeRecord.credentials[0].credentialRecordId ) - return expect(aliceAgent.credentials.getById(holderCredential.id)).rejects.toThrowError( - `CredentialRecord: record with id ${holderCredential.id} not found.` + return expect(aliceAgent.credentials.getById(holderCredentialExchangeRecord.id)).rejects.toThrowError( + `CredentialRecord: record with id ${holderCredentialExchangeRecord.id} not found.` ) }) @@ -256,11 +266,11 @@ describe('v2 credentials', () => { testLogger.test('Alice sends credential proposal to Faber') let aliceCredentialExchangeRecord = await aliceAgent.credentials.proposeCredential({ - connectionId: aliceConnection.id, + connectionId: aliceConnectionId, protocolVersion: 'v2', credentialFormats: { indy: { - ...credPropose, + ...indyCredentialProposal, attributes: credentialPreview.attributes, }, }, @@ -280,7 +290,7 @@ describe('v2 credentials', () => { credentialRecordId: faberCredentialRecord.id, credentialFormats: { indy: { - credentialDefinitionId: credDefId, + credentialDefinitionId: credentialDefinitionId, attributes: newCredentialPreview.attributes, }, }, @@ -306,7 +316,7 @@ describe('v2 credentials', () => { credentialRecordId: aliceCredentialRecord.id, credentialFormats: { indy: { - ...credPropose, + ...indyCredentialProposal, attributes: newCredentialPreview.attributes, }, }, @@ -326,7 +336,7 @@ describe('v2 credentials', () => { credentialRecordId: faberCredentialRecord.id, credentialFormats: { indy: { - credentialDefinitionId: credDefId, + credentialDefinitionId: credentialDefinitionId, attributes: newCredentialPreview.attributes, }, }, @@ -341,7 +351,7 @@ describe('v2 credentials', () => { }) expect(offerCredentialExchangeRecord).toMatchObject({ - connectionId: aliceConnection.id, + connectionId: aliceConnectionId, state: CredentialState.RequestSent, protocolVersion: 'v2', threadId: aliceCredentialExchangeRecord.threadId, @@ -391,18 +401,18 @@ describe('v2 credentials', () => { testLogger.test('Faber sends credential offer to Alice') let faberCredentialRecord = await faberAgent.credentials.offerCredential({ comment: 'some comment about credential', - connectionId: faberConnection.id, + connectionId: faberConnectionId, credentialFormats: { indy: { attributes: credentialPreview.attributes, - credentialDefinitionId: credDefId, + credentialDefinitionId: credentialDefinitionId, }, }, protocolVersion: 'v2', }) testLogger.test('Alice waits for credential offer from Faber') - aliceCredentialRecord = await aliceCredentialRecordPromise + let aliceCredentialRecord = await aliceCredentialRecordPromise let faberCredentialRecordPromise = waitForCredentialRecord(faberAgent, { threadId: aliceCredentialRecord.threadId, @@ -413,7 +423,7 @@ describe('v2 credentials', () => { credentialRecordId: aliceCredentialRecord.id, credentialFormats: { indy: { - ...credPropose, + ...indyCredentialProposal, attributes: newCredentialPreview.attributes, }, }, @@ -432,7 +442,7 @@ describe('v2 credentials', () => { credentialRecordId: faberCredentialRecord.id, credentialFormats: { indy: { - credentialDefinitionId: credDefId, + credentialDefinitionId: credentialDefinitionId, attributes: newCredentialPreview.attributes, }, }, @@ -451,7 +461,7 @@ describe('v2 credentials', () => { credentialRecordId: aliceCredentialRecord.id, credentialFormats: { indy: { - ...credPropose, + ...indyCredentialProposal, attributes: newCredentialPreview.attributes, }, }, @@ -473,7 +483,7 @@ describe('v2 credentials', () => { comment: 'V2 Indy Proposal', credentialFormats: { indy: { - credentialDefinitionId: credDefId, + credentialDefinitionId: credentialDefinitionId, attributes: credentialPreview.attributes, }, }, @@ -492,7 +502,7 @@ describe('v2 credentials', () => { }) expect(offerCredentialExchangeRecord).toMatchObject({ - connectionId: aliceConnection.id, + connectionId: aliceConnectionId, state: CredentialState.RequestSent, protocolVersion: 'v2', }) @@ -630,18 +640,18 @@ describe('v2 credentials', () => { testLogger.test('Faber sends credential offer to Alice') const faberCredentialExchangeRecord = await faberAgent.credentials.offerCredential({ comment: 'some comment about credential', - connectionId: faberConnection.id, + connectionId: faberConnectionId, credentialFormats: { indy: { attributes: credentialPreview.attributes, - credentialDefinitionId: credDefId, + credentialDefinitionId: credentialDefinitionId, }, }, protocolVersion: 'v2', }) testLogger.test('Alice waits for credential offer from Faber') - aliceCredentialRecord = await waitForCredentialRecordSubject(aliceReplay, { + let aliceCredentialRecord = await waitForCredentialRecordSubject(aliceReplay, { threadId: faberCredentialExchangeRecord.threadId, state: CredentialState.OfferReceived, }) diff --git a/packages/core/src/modules/credentials/protocol/v2/__tests__/v2.ldproof.connectionless-credentials.test.ts b/packages/core/src/modules/credentials/protocol/v2/__tests__/v2.ldproof.connectionless-credentials.test.ts index e8085a05f2..7472cca215 100644 --- a/packages/core/src/modules/credentials/protocol/v2/__tests__/v2.ldproof.connectionless-credentials.test.ts +++ b/packages/core/src/modules/credentials/protocol/v2/__tests__/v2.ldproof.connectionless-credentials.test.ts @@ -1,69 +1,16 @@ -import type { SubjectMessage } from '../../../../../../../../tests/transport/SubjectInboundTransport' -import type { Wallet } from '../../../../../wallet' -import type { CredentialStateChangedEvent } from '../../../CredentialEvents' -import type { JsonCredential, JsonLdCredentialDetailFormat } from '../../../formats/jsonld/JsonLdCredentialFormat' +import type { EventReplaySubject, JsonLdTestsAgent } from '../../../../../../tests' import type { V2OfferCredentialMessage } from '../messages/V2OfferCredentialMessage' -import { ReplaySubject, Subject } from 'rxjs' - -import { SubjectInboundTransport } from '../../../../../../../../tests/transport/SubjectInboundTransport' -import { SubjectOutboundTransport } from '../../../../../../../../tests/transport/SubjectOutboundTransport' -import { getAgentOptions, prepareForIssuance, waitForCredentialRecordSubject } from '../../../../../../tests/helpers' +import { setupJsonLdTests, waitForCredentialRecordSubject } from '../../../../../../tests' import testLogger from '../../../../../../tests/logger' -import { Agent } from '../../../../../agent/Agent' -import { InjectionSymbols } from '../../../../../constants' import { KeyType } from '../../../../../crypto' -import { JsonEncoder } from '../../../../../utils/JsonEncoder' -import { W3cVcModule } from '../../../../vc' -import { customDocumentLoader } from '../../../../vc/__tests__/documentLoader' +import { TypedArrayEncoder } from '../../../../../utils' import { CREDENTIALS_CONTEXT_V1_URL } from '../../../../vc/constants' -import { CredentialEventTypes } from '../../../CredentialEvents' -import { CredentialsModule } from '../../../CredentialsModule' -import { JsonLdCredentialFormatService } from '../../../formats' import { CredentialState } from '../../../models' import { CredentialExchangeRecord } from '../../../repository' -import { V2CredentialProtocol } from '../V2CredentialProtocol' - -const faberAgentOptions = getAgentOptions( - 'Faber LD connection-less Credentials V2', - { - endpoints: ['rxjs:faber'], - }, - { - credentials: new CredentialsModule({ - credentialProtocols: [new V2CredentialProtocol({ credentialFormats: [new JsonLdCredentialFormatService()] })], - }), - w3cVc: new W3cVcModule({ - documentLoader: customDocumentLoader, - }), - } -) - -const aliceAgentOptions = getAgentOptions( - 'Alice LD connection-less Credentials V2', - { - endpoints: ['rxjs:alice'], - }, - { - credentials: new CredentialsModule({ - credentialProtocols: [new V2CredentialProtocol({ credentialFormats: [new JsonLdCredentialFormatService()] })], - }), - w3cVc: new W3cVcModule({ - documentLoader: customDocumentLoader, - }), - } -) - -let wallet -let signCredentialOptions: JsonLdCredentialDetailFormat -describe('credentials', () => { - let faberAgent: Agent - let aliceAgent: Agent - let faberReplay: ReplaySubject - let aliceReplay: ReplaySubject - const seed = 'testseed000000000000000000000001' - const TEST_LD_DOCUMENT: JsonCredential = { +const signCredentialOptions = { + credential: { '@context': [CREDENTIALS_CONTEXT_V1_URL, 'https://www.w3.org/2018/credentials/examples/v1'], type: ['VerifiableCredential', 'UniversityDegreeCredential'], issuer: 'did:key:z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL', @@ -74,47 +21,35 @@ describe('credentials', () => { name: 'Bachelor of Science and Arts', }, }, - } + }, + options: { + proofType: 'Ed25519Signature2018', + proofPurpose: 'assertionMethod', + }, +} + +describe('credentials', () => { + let faberAgent: JsonLdTestsAgent + let faberReplay: EventReplaySubject + let aliceAgent: JsonLdTestsAgent + let aliceReplay: EventReplaySubject + beforeEach(async () => { - const faberMessages = new Subject() - const aliceMessages = new Subject() - - const subjectMap = { - 'rxjs:faber': faberMessages, - 'rxjs:alice': aliceMessages, - } - faberAgent = new Agent(faberAgentOptions) - faberAgent.registerInboundTransport(new SubjectInboundTransport(faberMessages)) - faberAgent.registerOutboundTransport(new SubjectOutboundTransport(subjectMap)) - await faberAgent.initialize() - - aliceAgent = new Agent(aliceAgentOptions) - aliceAgent.registerInboundTransport(new SubjectInboundTransport(aliceMessages)) - aliceAgent.registerOutboundTransport(new SubjectOutboundTransport(subjectMap)) - await aliceAgent.initialize() - - await prepareForIssuance(faberAgent, ['name', 'age']) - - faberReplay = new ReplaySubject() - aliceReplay = new ReplaySubject() - - faberAgent.events - .observable(CredentialEventTypes.CredentialStateChanged) - .subscribe(faberReplay) - aliceAgent.events - .observable(CredentialEventTypes.CredentialStateChanged) - .subscribe(aliceReplay) - wallet = faberAgent.injectionContainer.resolve(InjectionSymbols.Wallet) - - await wallet.createKey({ seed, keyType: KeyType.Ed25519 }) - - signCredentialOptions = { - credential: TEST_LD_DOCUMENT, - options: { - proofType: 'Ed25519Signature2018', - proofPurpose: 'assertionMethod', - }, - } + ;({ + issuerAgent: faberAgent, + issuerReplay: faberReplay, + holderAgent: aliceAgent, + holderReplay: aliceReplay, + } = await setupJsonLdTests({ + issuerName: 'Faber LD connection-less Credentials V2', + holderName: 'Alice LD connection-less Credentials V2', + createConnections: false, + })) + + await faberAgent.context.wallet.createKey({ + privateKey: TypedArrayEncoder.fromString('testseed000000000000000000000001'), + keyType: KeyType.Ed25519, + }) }) afterEach(async () => { @@ -136,36 +71,34 @@ describe('credentials', () => { protocolVersion: 'v2', }) - const offerMsg = message as V2OfferCredentialMessage - const attachment = offerMsg?.offerAttachments[0] - - if (attachment.data.base64) { - expect(JsonEncoder.fromBase64(attachment.data.base64)).toMatchObject({ - credential: { - '@context': ['https://www.w3.org/2018/credentials/v1', 'https://www.w3.org/2018/credentials/examples/v1'], - type: ['VerifiableCredential', 'UniversityDegreeCredential'], - issuer: 'did:key:z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL', - issuanceDate: '2017-10-22T12:23:48Z', - credentialSubject: { - degree: { - name: 'Bachelor of Science and Arts', - type: 'BachelorDegree', - }, + const offerMessage = message as V2OfferCredentialMessage + const attachment = offerMessage?.offerAttachments[0] + + expect(attachment?.getDataAsJson()).toMatchObject({ + credential: { + '@context': ['https://www.w3.org/2018/credentials/v1', 'https://www.w3.org/2018/credentials/examples/v1'], + type: ['VerifiableCredential', 'UniversityDegreeCredential'], + issuer: 'did:key:z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL', + issuanceDate: '2017-10-22T12:23:48Z', + credentialSubject: { + degree: { + name: 'Bachelor of Science and Arts', + type: 'BachelorDegree', }, }, - options: { - proofType: 'Ed25519Signature2018', - proofPurpose: 'assertionMethod', - }, - }) - } + }, + options: { + proofType: 'Ed25519Signature2018', + proofPurpose: 'assertionMethod', + }, + }) - const { message: offerMessage } = await faberAgent.oob.createLegacyConnectionlessInvitation({ + const { message: connectionlessOfferMessage } = await faberAgent.oob.createLegacyConnectionlessInvitation({ recordId: faberCredentialRecord.id, message, domain: 'https://a-domain.com', }) - await aliceAgent.receiveMessage(offerMessage.toJSON()) + await aliceAgent.receiveMessage(connectionlessOfferMessage.toJSON()) let aliceCredentialRecord = await waitForCredentialRecordSubject(aliceReplay, { threadId: faberCredentialRecord.threadId, diff --git a/packages/core/src/modules/credentials/protocol/v2/__tests__/v2.ldproof.credentials-auto-accept.test.ts b/packages/core/src/modules/credentials/protocol/v2/__tests__/v2.ldproof.credentials-auto-accept.test.ts index 3870b22bb3..84792602cd 100644 --- a/packages/core/src/modules/credentials/protocol/v2/__tests__/v2.ldproof.credentials-auto-accept.test.ts +++ b/packages/core/src/modules/credentials/protocol/v2/__tests__/v2.ldproof.credentials-auto-accept.test.ts @@ -1,58 +1,59 @@ -import type { CredentialTestsAgent } from '../../../../../../tests/helpers' -import type { Wallet } from '../../../../../wallet' -import type { ConnectionRecord } from '../../../../connections' -import type { JsonCredential, JsonLdCredentialDetailFormat } from '../../../formats/jsonld/JsonLdCredentialFormat' +import type { JsonLdTestsAgent } from '../../../../../../tests' -import { setupCredentialTests, waitForCredentialRecord } from '../../../../../../tests/helpers' +import { setupJsonLdTests } from '../../../../../../tests' +import { waitForCredentialRecord } from '../../../../../../tests/helpers' import testLogger from '../../../../../../tests/logger' -import { InjectionSymbols } from '../../../../../constants' import { KeyType } from '../../../../../crypto' import { AriesFrameworkError } from '../../../../../error/AriesFrameworkError' +import { TypedArrayEncoder } from '../../../../../utils' import { CREDENTIALS_CONTEXT_V1_URL } from '../../../../vc/constants' import { AutoAcceptCredential, CredentialState } from '../../../models' import { CredentialExchangeRecord } from '../../../repository/CredentialExchangeRecord' -const TEST_LD_DOCUMENT: JsonCredential = { - '@context': [CREDENTIALS_CONTEXT_V1_URL, 'https://www.w3.org/2018/credentials/examples/v1'], - type: ['VerifiableCredential', 'UniversityDegreeCredential'], - issuer: 'did:key:z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL', - issuanceDate: '2017-10-22T12:23:48Z', - credentialSubject: { - degree: { - type: 'BachelorDegree', - name: 'Bachelor of Science and Arts', +const signCredentialOptions = { + credential: { + '@context': [CREDENTIALS_CONTEXT_V1_URL, 'https://www.w3.org/2018/credentials/examples/v1'], + type: ['VerifiableCredential', 'UniversityDegreeCredential'], + issuer: 'did:key:z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL', + issuanceDate: '2017-10-22T12:23:48Z', + credentialSubject: { + degree: { + type: 'BachelorDegree', + name: 'Bachelor of Science and Arts', + }, }, }, + options: { + proofType: 'Ed25519Signature2018', + proofPurpose: 'assertionMethod', + }, } -describe('credentials', () => { - let faberAgent: CredentialTestsAgent - let aliceAgent: CredentialTestsAgent - let faberConnection: ConnectionRecord - let aliceConnection: ConnectionRecord - let aliceCredentialRecord: CredentialExchangeRecord - let signCredentialOptions: JsonLdCredentialDetailFormat - let wallet - const seed = 'testseed000000000000000000000001' - - describe('Auto accept on `always`', () => { +describe('V2 Credentials - JSON-LD - Auto Accept Always', () => { + let faberAgent: JsonLdTestsAgent + let aliceAgent: JsonLdTestsAgent + let faberConnectionId: string + let aliceConnectionId: string + + describe("Auto accept on 'always'", () => { beforeAll(async () => { - ;({ faberAgent, aliceAgent, faberConnection, aliceConnection } = await setupCredentialTests( - 'faber agent: always v2 jsonld', - 'alice agent: always v2 jsonld', - AutoAcceptCredential.Always - )) - - wallet = faberAgent.injectionContainer.resolve(InjectionSymbols.Wallet) - await wallet.createKey({ seed, keyType: KeyType.Ed25519 }) - signCredentialOptions = { - credential: TEST_LD_DOCUMENT, - options: { - proofType: 'Ed25519Signature2018', - proofPurpose: 'assertionMethod', - }, - } + ;({ + issuerAgent: faberAgent, + holderAgent: aliceAgent, + issuerHolderConnectionId: faberConnectionId, + holderIssuerConnectionId: aliceConnectionId, + } = await setupJsonLdTests({ + issuerName: 'faber agent: always v2 jsonld', + holderName: 'alice agent: always v2 jsonld', + autoAcceptCredentials: AutoAcceptCredential.Always, + })) + + await faberAgent.context.wallet.createKey({ + privateKey: TypedArrayEncoder.fromString('testseed000000000000000000000001'), + keyType: KeyType.Ed25519, + }) }) + afterAll(async () => { await faberAgent.shutdown() await faberAgent.wallet.delete() @@ -60,11 +61,11 @@ describe('credentials', () => { await aliceAgent.wallet.delete() }) - test('Alice starts with V2 credential proposal to Faber, both with autoAcceptCredential on `always`', async () => { + test("Alice starts with V2 credential proposal to Faber, both with autoAcceptCredential on 'always'", async () => { testLogger.test('Alice sends credential proposal to Faber') const aliceCredentialExchangeRecord = await aliceAgent.credentials.proposeCredential({ - connectionId: aliceConnection.id, + connectionId: aliceConnectionId, protocolVersion: 'v2', credentialFormats: { jsonld: signCredentialOptions, @@ -74,7 +75,7 @@ describe('credentials', () => { testLogger.test('Alice waits for credential from Faber') - aliceCredentialRecord = await waitForCredentialRecord(aliceAgent, { + let aliceCredentialRecord = await waitForCredentialRecord(aliceAgent, { threadId: aliceCredentialExchangeRecord.threadId, state: CredentialState.CredentialReceived, }) @@ -92,19 +93,19 @@ describe('credentials', () => { state: CredentialState.Done, }) }) - test('Faber starts with V2 credential offer to Alice, both with autoAcceptCredential on `always`', async () => { + test("Faber starts with V2 credential offer to Alice, both with autoAcceptCredential on 'always'", async () => { testLogger.test('Faber sends V2 credential offer to Alice as start of protocol process') const faberCredentialExchangeRecord: CredentialExchangeRecord = await faberAgent.credentials.offerCredential({ comment: 'some comment about credential', - connectionId: faberConnection.id, + connectionId: faberConnectionId, credentialFormats: { jsonld: signCredentialOptions, }, protocolVersion: 'v2', }) testLogger.test('Alice waits for credential from Faber') - aliceCredentialRecord = await waitForCredentialRecord(aliceAgent, { + let aliceCredentialRecord = await waitForCredentialRecord(aliceAgent, { threadId: faberCredentialExchangeRecord.threadId, state: CredentialState.OfferReceived, }) @@ -135,22 +136,23 @@ describe('credentials', () => { }) }) - describe('Auto accept on `contentApproved`', () => { + describe("Auto accept on 'contentApproved'", () => { beforeAll(async () => { - ;({ faberAgent, aliceAgent, faberConnection, aliceConnection } = await setupCredentialTests( - 'faber agent: content-approved v2 jsonld', - 'alice agent: content-approved v2 jsonld', - AutoAcceptCredential.ContentApproved - )) - wallet = faberAgent.injectionContainer.resolve(InjectionSymbols.Wallet) - await wallet.createKey({ seed, keyType: KeyType.Ed25519 }) - signCredentialOptions = { - credential: TEST_LD_DOCUMENT, - options: { - proofType: 'Ed25519Signature2018', - proofPurpose: 'assertionMethod', - }, - } + ;({ + issuerAgent: faberAgent, + holderAgent: aliceAgent, + issuerHolderConnectionId: faberConnectionId, + holderIssuerConnectionId: aliceConnectionId, + } = await setupJsonLdTests({ + issuerName: 'faber agent: ContentApproved v2 jsonld', + holderName: 'alice agent: ContentApproved v2 jsonld', + autoAcceptCredentials: AutoAcceptCredential.ContentApproved, + })) + + await faberAgent.context.wallet.createKey({ + privateKey: TypedArrayEncoder.fromString('testseed000000000000000000000001'), + keyType: KeyType.Ed25519, + }) }) afterAll(async () => { @@ -160,10 +162,10 @@ describe('credentials', () => { await aliceAgent.wallet.delete() }) - test('Alice starts with V2 credential proposal to Faber, both with autoAcceptCredential on `contentApproved`', async () => { + test("Alice starts with V2 credential proposal to Faber, both with autoAcceptCredential on 'contentApproved'", async () => { testLogger.test('Alice sends credential proposal to Faber') const aliceCredentialExchangeRecord = await aliceAgent.credentials.proposeCredential({ - connectionId: aliceConnection.id, + connectionId: aliceConnectionId, protocolVersion: 'v2', credentialFormats: { jsonld: signCredentialOptions, @@ -184,7 +186,7 @@ describe('credentials', () => { }) testLogger.test('Alice waits for credential from Faber') - aliceCredentialRecord = await waitForCredentialRecord(aliceAgent, { + const aliceCredentialRecord = await waitForCredentialRecord(aliceAgent, { threadId: faberCredentialExchangeRecord.threadId, state: CredentialState.CredentialReceived, }) @@ -212,12 +214,12 @@ describe('credentials', () => { state: CredentialState.Done, }) }) - test('Faber starts with V2 credential offer to Alice, both with autoAcceptCredential on `contentApproved`', async () => { + test("Faber starts with V2 credential offer to Alice, both with autoAcceptCredential on 'contentApproved'", async () => { testLogger.test('Faber sends credential offer to Alice') let faberCredentialExchangeRecord = await faberAgent.credentials.offerCredential({ comment: 'some comment about credential', - connectionId: faberConnection.id, + connectionId: faberConnectionId, credentialFormats: { jsonld: signCredentialOptions, }, @@ -225,7 +227,7 @@ describe('credentials', () => { }) testLogger.test('Alice waits for credential offer from Faber') - aliceCredentialRecord = await waitForCredentialRecord(aliceAgent, { + let aliceCredentialRecord = await waitForCredentialRecord(aliceAgent, { threadId: faberCredentialExchangeRecord.threadId, state: CredentialState.OfferReceived, }) @@ -235,7 +237,7 @@ describe('credentials', () => { expect(aliceCredentialRecord.getTags()).toEqual({ threadId: aliceCredentialRecord.threadId, state: aliceCredentialRecord.state, - connectionId: aliceConnection.id, + connectionId: aliceConnectionId, credentialIds: [], }) expect(aliceCredentialRecord.type).toBe(CredentialExchangeRecord.type) @@ -278,19 +280,19 @@ describe('credentials', () => { state: CredentialState.Done, }) }) - test('Faber starts with V2 credential offer to Alice, both have autoAcceptCredential on `contentApproved` and attributes did change', async () => { + test("Faber starts with V2 credential offer to Alice, both have autoAcceptCredential on 'contentApproved' and attributes did change", async () => { testLogger.test('Faber sends credential offer to Alice') const faberCredentialExchangeRecord: CredentialExchangeRecord = await faberAgent.credentials.offerCredential({ comment: 'some comment about credential', - connectionId: faberConnection.id, + connectionId: faberConnectionId, credentialFormats: { jsonld: signCredentialOptions, }, protocolVersion: 'v2', }) testLogger.test('Alice waits for credential from Faber') - aliceCredentialRecord = await waitForCredentialRecord(aliceAgent, { + let aliceCredentialRecord = await waitForCredentialRecord(aliceAgent, { threadId: faberCredentialExchangeRecord.threadId, state: CredentialState.OfferReceived, }) @@ -300,7 +302,7 @@ describe('credentials', () => { expect(aliceCredentialRecord.getTags()).toEqual({ threadId: aliceCredentialRecord.threadId, state: aliceCredentialRecord.state, - connectionId: aliceConnection.id, + connectionId: aliceConnectionId, credentialIds: [], }) expect(aliceCredentialRecord.type).toBe(CredentialExchangeRecord.type) @@ -310,7 +312,17 @@ describe('credentials', () => { const aliceExchangeCredentialRecord = await aliceAgent.credentials.negotiateOffer({ credentialRecordId: aliceCredentialRecord.id, credentialFormats: { - jsonld: signCredentialOptions, + // Send a different object + jsonld: { + ...signCredentialOptions, + credential: { + ...signCredentialOptions.credential, + credentialSubject: { + ...signCredentialOptions.credential.credentialSubject, + name: 'Different Property', + }, + }, + }, }, comment: 'v2 propose credential test', }) @@ -328,10 +340,11 @@ describe('credentials', () => { aliceCredentialRecord = await aliceAgent.credentials.getById(aliceCredentialRecord.id) aliceCredentialRecord.assertState(CredentialState.ProposalSent) }) - test('Alice starts with V2 credential proposal to Faber, both have autoAcceptCredential on `contentApproved` and attributes did change', async () => { + + test("Alice starts with V2 credential proposal to Faber, both have autoAcceptCredential on 'contentApproved' and attributes did change", async () => { testLogger.test('Alice sends credential proposal to Faber') const aliceCredentialExchangeRecord = await aliceAgent.credentials.proposeCredential({ - connectionId: aliceConnection.id, + connectionId: aliceConnectionId, protocolVersion: 'v2', credentialFormats: { jsonld: signCredentialOptions, @@ -348,7 +361,17 @@ describe('credentials', () => { await faberAgent.credentials.negotiateProposal({ credentialRecordId: faberCredentialRecord.id, credentialFormats: { - jsonld: signCredentialOptions, + // Send a different object + jsonld: { + ...signCredentialOptions, + credential: { + ...signCredentialOptions.credential, + credentialSubject: { + ...signCredentialOptions.credential.credentialSubject, + name: 'Different Property', + }, + }, + }, }, }) @@ -364,7 +387,7 @@ describe('credentials', () => { expect(record.getTags()).toEqual({ threadId: record.threadId, state: record.state, - connectionId: aliceConnection.id, + connectionId: aliceConnectionId, credentialIds: [], }) expect(record.type).toBe(CredentialExchangeRecord.type) diff --git a/packages/core/src/modules/credentials/protocol/v2/__tests__/v2.ldproof.credentials.propose-offerED25519.test.ts b/packages/core/src/modules/credentials/protocol/v2/__tests__/v2.ldproof.credentials.propose-offerED25519.test.ts index 3d4d757554..3a7b6dac2e 100644 --- a/packages/core/src/modules/credentials/protocol/v2/__tests__/v2.ldproof.credentials.propose-offerED25519.test.ts +++ b/packages/core/src/modules/credentials/protocol/v2/__tests__/v2.ldproof.credentials.propose-offerED25519.test.ts @@ -1,30 +1,53 @@ -import type { Awaited } from '../../../../../types' -import type { Wallet } from '../../../../../wallet' -import type { ConnectionRecord } from '../../../../connections' -import type { JsonCredential, JsonLdCredentialDetailFormat } from '../../../formats/jsonld/JsonLdCredentialFormat' - -import { setupCredentialTests, waitForCredentialRecord } from '../../../../../../tests/helpers' -import testLogger from '../../../../../../tests/logger' -import { InjectionSymbols } from '../../../../../constants' +import type { EventReplaySubject } from '../../../../../../tests' + +import { randomUUID } from 'crypto' + +import { + LegacyIndyCredentialFormatService, + LegacyIndyProofFormatService, + V1CredentialProtocol, + V1ProofProtocol, + AnonCredsModule, +} from '../../../../../../../anoncreds/src' +import { prepareForAnonCredsIssuance } from '../../../../../../../anoncreds/tests/legacyAnonCredsSetup' +import { + IndySdkAnonCredsRegistry, + IndySdkIndyDidRegistrar, + IndySdkIndyDidResolver, + IndySdkModule, + IndySdkSovDidResolver, +} from '../../../../../../../indy-sdk/src' +import { indySdk } from '../../../../../../../indy-sdk/tests/setupIndySdkModule' +import { + setupEventReplaySubjects, + setupSubjectTransports, + genesisPath, + taaAcceptanceMechanism, + taaVersion, + getAgentOptions, + waitForCredentialRecordSubject, + testLogger, + makeConnection, +} from '../../../../../../tests' +import { Agent } from '../../../../../agent/Agent' import { KeyType } from '../../../../../crypto' -import { DidCommMessageRepository } from '../../../../../storage' +import { TypedArrayEncoder } from '../../../../../utils' import { JsonTransformer } from '../../../../../utils/JsonTransformer' +import { CacheModule, InMemoryLruCache } from '../../../../cache' +import { DidsModule, KeyDidRegistrar, KeyDidResolver } from '../../../../dids' +import { ProofEventTypes, ProofsModule, V2ProofProtocol } from '../../../../proofs' +import { W3cCredentialsModule } from '../../../../vc' +import { customDocumentLoader } from '../../../../vc/__tests__/documentLoader' +import { CredentialEventTypes } from '../../../CredentialEvents' +import { CredentialsModule } from '../../../CredentialsModule' +import { JsonLdCredentialFormatService } from '../../../formats' import { CredentialState } from '../../../models' import { CredentialExchangeRecord } from '../../../repository/CredentialExchangeRecord' +import { V2CredentialProtocol } from '../V2CredentialProtocol' import { V2CredentialPreview } from '../messages' -import { V2IssueCredentialMessage } from '../messages/V2IssueCredentialMessage' -import { V2OfferCredentialMessage } from '../messages/V2OfferCredentialMessage' -describe('credentials', () => { - let faberAgent: Awaited>['faberAgent'] - let aliceAgent: Awaited>['aliceAgent'] - let aliceConnection: ConnectionRecord - let aliceCredentialRecord: CredentialExchangeRecord - let faberCredentialRecord: CredentialExchangeRecord - - let didCommMessageRepository: DidCommMessageRepository - - const inputDocAsJson: JsonCredential = { +const signCredentialOptions = { + credential: { '@context': [ 'https://www.w3.org/2018/credentials/v1', 'https://w3id.org/citizenship/v1', @@ -52,28 +75,115 @@ describe('credentials', () => { birthCountry: 'Bahamas', birthDate: '1958-07-17', }, - } + }, + options: { + proofType: 'Ed25519Signature2018', + proofPurpose: 'assertionMethod', + }, +} + +const indyCredentialFormat = new LegacyIndyCredentialFormatService() +const jsonLdCredentialFormat = new JsonLdCredentialFormatService() +const indyProofFormat = new LegacyIndyProofFormatService() + +const getIndyJsonLdModules = () => + ({ + credentials: new CredentialsModule({ + credentialProtocols: [ + new V1CredentialProtocol({ indyCredentialFormat }), + new V2CredentialProtocol({ + credentialFormats: [indyCredentialFormat, jsonLdCredentialFormat], + }), + ], + }), + proofs: new ProofsModule({ + proofProtocols: [ + new V1ProofProtocol({ indyProofFormat }), + new V2ProofProtocol({ + proofFormats: [indyProofFormat], + }), + ], + }), + anoncreds: new AnonCredsModule({ + registries: [new IndySdkAnonCredsRegistry()], + }), + dids: new DidsModule({ + resolvers: [new IndySdkSovDidResolver(), new IndySdkIndyDidResolver(), new KeyDidResolver()], + registrars: [new IndySdkIndyDidRegistrar(), new KeyDidRegistrar()], + }), + indySdk: new IndySdkModule({ + indySdk, + networks: [ + { + isProduction: false, + genesisPath, + id: randomUUID(), + indyNamespace: `pool:localtest`, + transactionAuthorAgreement: { version: taaVersion, acceptanceMechanism: taaAcceptanceMechanism }, + }, + ], + }), + cache: new CacheModule({ + cache: new InMemoryLruCache({ limit: 100 }), + }), + w3cCredentials: new W3cCredentialsModule({ + documentLoader: customDocumentLoader, + }), + } as const) + +// TODO: extract these very specific tests to the jsonld format +describe('V2 Credentials - JSON-LD - Ed25519', () => { + let faberAgent: Agent> + let faberReplay: EventReplaySubject + let aliceAgent: Agent> + let aliceReplay: EventReplaySubject + let aliceConnectionId: string + let credentialDefinitionId: string - let signCredentialOptions: JsonLdCredentialDetailFormat + beforeAll(async () => { + faberAgent = new Agent( + getAgentOptions( + 'Faber Agent Indy/JsonLD', + { + endpoints: ['rxjs:faber'], + }, + getIndyJsonLdModules() + ) + ) + aliceAgent = new Agent( + getAgentOptions( + 'Alice Agent Indy/JsonLD', + { + endpoints: ['rxjs:alice'], + }, + getIndyJsonLdModules() + ) + ) + + setupSubjectTransports([faberAgent, aliceAgent]) + ;[faberReplay, aliceReplay] = setupEventReplaySubjects( + [faberAgent, aliceAgent], + [CredentialEventTypes.CredentialStateChanged, ProofEventTypes.ProofStateChanged] + ) + await faberAgent.initialize() + await aliceAgent.initialize() + ;[, { id: aliceConnectionId }] = await makeConnection(faberAgent, aliceAgent) + + // Create link secret for alice + await aliceAgent.modules.anoncreds.createLinkSecret({ + linkSecretId: 'default', + setAsDefault: true, + }) - let wallet - const seed = 'testseed000000000000000000000001' - let credDefId: string + const { credentialDefinition } = await prepareForAnonCredsIssuance(faberAgent, { + attributeNames: ['name', 'age', 'profile_picture', 'x-ray'], + }) + credentialDefinitionId = credentialDefinition.credentialDefinitionId - beforeAll(async () => { - ;({ faberAgent, aliceAgent, credDefId, aliceConnection } = await setupCredentialTests( - 'Faber Agent Credentials LD', - 'Alice Agent Credentials LD' - )) - wallet = faberAgent.injectionContainer.resolve(InjectionSymbols.Wallet) - await wallet.createKey({ seed, keyType: KeyType.Ed25519 }) - signCredentialOptions = { - credential: inputDocAsJson, - options: { - proofType: 'Ed25519Signature2018', - proofPurpose: 'assertionMethod', - }, - } + await faberAgent.context.wallet.createKey({ + privateKey: TypedArrayEncoder.fromString('testseed000000000000000000000001'), + keyType: KeyType.Ed25519, + }) }) afterAll(async () => { @@ -86,8 +196,8 @@ describe('credentials', () => { test('Alice starts with V2 (ld format, Ed25519 signature) credential proposal to Faber', async () => { testLogger.test('Alice sends (v2 jsonld) credential proposal to Faber') - const credentialExchangeRecord: CredentialExchangeRecord = await aliceAgent.credentials.proposeCredential({ - connectionId: aliceConnection.id, + const credentialExchangeRecord = await aliceAgent.credentials.proposeCredential({ + connectionId: aliceConnectionId, protocolVersion: 'v2', credentialFormats: { jsonld: signCredentialOptions, @@ -95,13 +205,13 @@ describe('credentials', () => { comment: 'v2 propose credential test for W3C Credentials', }) - expect(credentialExchangeRecord.connectionId).toEqual(aliceConnection.id) + expect(credentialExchangeRecord.connectionId).toEqual(aliceConnectionId) expect(credentialExchangeRecord.protocolVersion).toEqual('v2') expect(credentialExchangeRecord.state).toEqual(CredentialState.ProposalSent) expect(credentialExchangeRecord.threadId).not.toBeNull() testLogger.test('Faber waits for credential proposal from Alice') - faberCredentialRecord = await waitForCredentialRecord(faberAgent, { + let faberCredentialRecord = await waitForCredentialRecordSubject(faberReplay, { threadId: credentialExchangeRecord.threadId, state: CredentialState.ProposalReceived, }) @@ -113,18 +223,12 @@ describe('credentials', () => { }) testLogger.test('Alice waits for credential offer from Faber') - aliceCredentialRecord = await waitForCredentialRecord(aliceAgent, { + let aliceCredentialRecord = await waitForCredentialRecordSubject(aliceReplay, { threadId: faberCredentialRecord.threadId, state: CredentialState.OfferReceived, }) - didCommMessageRepository = faberAgent.dependencyManager.resolve(DidCommMessageRepository) - - const offerMessage = await didCommMessageRepository.findAgentMessage(aliceAgent.context, { - associatedRecordId: aliceCredentialRecord.id, - messageClass: V2OfferCredentialMessage, - }) - + const offerMessage = await aliceAgent.credentials.findOfferMessage(aliceCredentialRecord.id) expect(JsonTransformer.toJSON(offerMessage)).toMatchObject({ '@type': 'https://didcomm.org/issue-credential/2.0/offer-credential', '@id': expect.any(String), @@ -162,93 +266,87 @@ describe('credentials', () => { expect(aliceCredentialRecord.id).not.toBeNull() expect(aliceCredentialRecord.type).toBe(CredentialExchangeRecord.type) - if (aliceCredentialRecord.connectionId) { - const offerCredentialExchangeRecord = await aliceAgent.credentials.acceptOffer({ - credentialRecordId: aliceCredentialRecord.id, - credentialFormats: { - jsonld: {}, + const offerCredentialExchangeRecord = await aliceAgent.credentials.acceptOffer({ + credentialRecordId: aliceCredentialRecord.id, + credentialFormats: { + jsonld: {}, + }, + }) + + expect(offerCredentialExchangeRecord.connectionId).toEqual(aliceConnectionId) + expect(offerCredentialExchangeRecord.protocolVersion).toEqual('v2') + expect(offerCredentialExchangeRecord.state).toEqual(CredentialState.RequestSent) + expect(offerCredentialExchangeRecord.threadId).not.toBeNull() + + testLogger.test('Faber waits for credential request from Alice') + await waitForCredentialRecordSubject(faberReplay, { + threadId: aliceCredentialRecord.threadId, + state: CredentialState.RequestReceived, + }) + + testLogger.test('Faber sends credential to Alice') + + await faberAgent.credentials.acceptRequest({ + credentialRecordId: faberCredentialRecord.id, + comment: 'V2 Indy Credential', + }) + + testLogger.test('Alice waits for credential from Faber') + aliceCredentialRecord = await waitForCredentialRecordSubject(aliceReplay, { + threadId: faberCredentialRecord.threadId, + state: CredentialState.CredentialReceived, + }) + + testLogger.test('Alice sends credential ack to Faber') + await aliceAgent.credentials.acceptCredential({ credentialRecordId: aliceCredentialRecord.id }) + + testLogger.test('Faber waits for credential ack from Alice') + faberCredentialRecord = await waitForCredentialRecordSubject(faberReplay, { + threadId: faberCredentialRecord.threadId, + state: CredentialState.Done, + }) + expect(aliceCredentialRecord).toMatchObject({ + type: CredentialExchangeRecord.type, + id: expect.any(String), + createdAt: expect.any(Date), + threadId: expect.any(String), + connectionId: expect.any(String), + state: CredentialState.CredentialReceived, + }) + + const credentialMessage = await faberAgent.credentials.findCredentialMessage(faberCredentialRecord.id) + expect(JsonTransformer.toJSON(credentialMessage)).toMatchObject({ + '@type': 'https://didcomm.org/issue-credential/2.0/issue-credential', + '@id': expect.any(String), + comment: 'V2 Indy Credential', + formats: [ + { + attach_id: expect.any(String), + format: 'aries/ld-proof-vc@v1.0', }, - }) - - expect(offerCredentialExchangeRecord.connectionId).toEqual(aliceConnection.id) - expect(offerCredentialExchangeRecord.protocolVersion).toEqual('v2') - expect(offerCredentialExchangeRecord.state).toEqual(CredentialState.RequestSent) - expect(offerCredentialExchangeRecord.threadId).not.toBeNull() - - testLogger.test('Faber waits for credential request from Alice') - await waitForCredentialRecord(faberAgent, { - threadId: aliceCredentialRecord.threadId, - state: CredentialState.RequestReceived, - }) - - testLogger.test('Faber sends credential to Alice') - - await faberAgent.credentials.acceptRequest({ - credentialRecordId: faberCredentialRecord.id, - comment: 'V2 Indy Credential', - }) - - testLogger.test('Alice waits for credential from Faber') - aliceCredentialRecord = await waitForCredentialRecord(aliceAgent, { - threadId: faberCredentialRecord.threadId, - state: CredentialState.CredentialReceived, - }) - - testLogger.test('Alice sends credential ack to Faber') - await aliceAgent.credentials.acceptCredential({ credentialRecordId: aliceCredentialRecord.id }) - - testLogger.test('Faber waits for credential ack from Alice') - faberCredentialRecord = await waitForCredentialRecord(faberAgent, { - threadId: faberCredentialRecord.threadId, - state: CredentialState.Done, - }) - expect(aliceCredentialRecord).toMatchObject({ - type: CredentialExchangeRecord.type, - id: expect.any(String), - createdAt: expect.any(Date), - threadId: expect.any(String), - connectionId: expect.any(String), - state: CredentialState.CredentialReceived, - }) - - const credentialMessage = await didCommMessageRepository.findAgentMessage(faberAgent.context, { - associatedRecordId: faberCredentialRecord.id, - messageClass: V2IssueCredentialMessage, - }) - - expect(JsonTransformer.toJSON(credentialMessage)).toMatchObject({ - '@type': 'https://didcomm.org/issue-credential/2.0/issue-credential', - '@id': expect.any(String), - comment: 'V2 Indy Credential', - formats: [ - { - attach_id: expect.any(String), - format: 'aries/ld-proof-vc@1.0', - }, - ], - 'credentials~attach': [ - { - '@id': expect.any(String), - 'mime-type': 'application/json', - data: expect.any(Object), - lastmod_time: undefined, - byte_count: undefined, - }, - ], - '~thread': { - thid: expect.any(String), - pthid: undefined, - sender_order: undefined, - received_orders: undefined, + ], + 'credentials~attach': [ + { + '@id': expect.any(String), + 'mime-type': 'application/json', + data: expect.any(Object), + lastmod_time: undefined, + byte_count: undefined, }, - '~please_ack': { on: ['RECEIPT'] }, - '~service': undefined, - '~attach': undefined, - '~timing': undefined, - '~transport': undefined, - '~l10n': undefined, - }) - } + ], + '~thread': { + thid: expect.any(String), + pthid: undefined, + sender_order: undefined, + received_orders: undefined, + }, + '~please_ack': { on: ['RECEIPT'] }, + '~service': undefined, + '~attach': undefined, + '~timing': undefined, + '~transport': undefined, + '~l10n': undefined, + }) }) test('Multiple Formats: Alice starts with V2 (both ld and indy formats) credential proposal to Faber', async () => { @@ -260,35 +358,34 @@ describe('credentials', () => { 'x-ray': 'some x-ray', profile_picture: 'profile picture', }) - const testAttributes = { - attributes: credentialPreview.attributes, - schemaIssuerDid: 'GMm4vMw8LLrLJjp81kRRLp', - schemaName: 'ahoy', - schemaVersion: '1.0', - schemaId: 'q7ATwTYbQDgiigVijUAej:2:test:1.0', - issuerDid: 'GMm4vMw8LLrLJjp81kRRLp', - credentialDefinitionId: 'GMm4vMw8LLrLJjp81kRRLp:3:CL:12:tag', - } testLogger.test('Alice sends (v2, Indy) credential proposal to Faber') - const credentialExchangeRecord: CredentialExchangeRecord = await aliceAgent.credentials.proposeCredential({ - connectionId: aliceConnection.id, + const credentialExchangeRecord = await aliceAgent.credentials.proposeCredential({ + connectionId: aliceConnectionId, protocolVersion: 'v2', credentialFormats: { - indy: testAttributes, + indy: { + attributes: credentialPreview.attributes, + schemaIssuerDid: 'GMm4vMw8LLrLJjp81kRRLp', + schemaName: 'ahoy', + schemaVersion: '1.0', + schemaId: 'q7ATwTYbQDgiigVijUAej:2:test:1.0', + issuerDid: 'GMm4vMw8LLrLJjp81kRRLp', + credentialDefinitionId: 'GMm4vMw8LLrLJjp81kRRLp:3:CL:12:tag', + }, jsonld: signCredentialOptions, }, comment: 'v2 propose credential test', }) - expect(credentialExchangeRecord.connectionId).toEqual(aliceConnection.id) + expect(credentialExchangeRecord.connectionId).toEqual(aliceConnectionId) expect(credentialExchangeRecord.protocolVersion).toEqual('v2') expect(credentialExchangeRecord.state).toEqual(CredentialState.ProposalSent) expect(credentialExchangeRecord.threadId).not.toBeNull() testLogger.test('Faber waits for credential proposal from Alice') - faberCredentialRecord = await waitForCredentialRecord(faberAgent, { + let faberCredentialRecord = await waitForCredentialRecordSubject(faberReplay, { threadId: credentialExchangeRecord.threadId, state: CredentialState.ProposalReceived, }) @@ -300,7 +397,7 @@ describe('credentials', () => { comment: 'V2 W3C & INDY Proposals', credentialFormats: { indy: { - credentialDefinitionId: credDefId, + credentialDefinitionId, attributes: credentialPreview.attributes, }, jsonld: {}, // this is to ensure both services are formatted @@ -308,21 +405,14 @@ describe('credentials', () => { }) testLogger.test('Alice waits for credential offer from Faber') - aliceCredentialRecord = await waitForCredentialRecord(aliceAgent, { + let aliceCredentialRecord = await waitForCredentialRecordSubject(aliceReplay, { threadId: faberCredentialRecord.threadId, state: CredentialState.OfferReceived, }) - // didCommMessageRepository = faberAgent.injectionContainer.resolve(DidCommMessageRepository) - didCommMessageRepository = faberAgent.dependencyManager.resolve(DidCommMessageRepository) - - const offerMessage = await didCommMessageRepository.findAgentMessage(faberAgent.context, { - associatedRecordId: faberCredentialRecord.id, - messageClass: V2OfferCredentialMessage, - }) - - const credOfferJson = offerMessage?.offerAttachments[1].getDataAsJson() - expect(credOfferJson).toMatchObject({ + const offerMessage = await faberAgent.credentials.findOfferMessage(faberCredentialRecord.id) + const credentialOfferJson = offerMessage?.offerAttachments[1].getDataAsJson() + expect(credentialOfferJson).toMatchObject({ credential: { '@context': [ 'https://www.w3.org/2018/credentials/v1', @@ -408,139 +498,132 @@ describe('credentials', () => { expect(aliceCredentialRecord.id).not.toBeNull() expect(aliceCredentialRecord.type).toBe(CredentialExchangeRecord.type) - if (aliceCredentialRecord.connectionId) { - const offerCredentialExchangeRecord: CredentialExchangeRecord = await aliceAgent.credentials.acceptOffer({ - credentialRecordId: aliceCredentialRecord.id, - }) - - expect(offerCredentialExchangeRecord.connectionId).toEqual(aliceConnection.id) - expect(offerCredentialExchangeRecord.protocolVersion).toEqual('v2') - expect(offerCredentialExchangeRecord.state).toEqual(CredentialState.RequestSent) - expect(offerCredentialExchangeRecord.threadId).not.toBeNull() - - testLogger.test('Faber waits for credential request from Alice') - await waitForCredentialRecord(faberAgent, { - threadId: aliceCredentialRecord.threadId, - state: CredentialState.RequestReceived, - }) - - testLogger.test('Faber sends credential to Alice') - - await faberAgent.credentials.acceptRequest({ - credentialRecordId: faberCredentialRecord.id, - comment: 'V2 Indy Credential', - }) - - testLogger.test('Alice waits for credential from Faber') - aliceCredentialRecord = await waitForCredentialRecord(aliceAgent, { - threadId: faberCredentialRecord.threadId, - state: CredentialState.CredentialReceived, - }) - - testLogger.test('Alice sends credential ack to Faber') - await aliceAgent.credentials.acceptCredential({ credentialRecordId: aliceCredentialRecord.id }) - - testLogger.test('Faber waits for credential ack from Alice') - faberCredentialRecord = await waitForCredentialRecord(faberAgent, { - threadId: faberCredentialRecord.threadId, - state: CredentialState.Done, - }) - expect(aliceCredentialRecord).toMatchObject({ - type: CredentialExchangeRecord.type, - id: expect.any(String), - createdAt: expect.any(Date), - threadId: expect.any(String), - connectionId: expect.any(String), - state: CredentialState.CredentialReceived, - }) - - const credentialMessage = await didCommMessageRepository.getAgentMessage(faberAgent.context, { - associatedRecordId: faberCredentialRecord.id, - messageClass: V2IssueCredentialMessage, - }) - - const w3cCredential = credentialMessage.credentialAttachments[1].getDataAsJson() - - expect(w3cCredential).toMatchObject({ - context: [ - 'https://www.w3.org/2018/credentials/v1', - 'https://w3id.org/citizenship/v1', - 'https://w3id.org/security/bbs/v1', - ], - id: 'https://issuer.oidp.uscis.gov/credentials/83627465', - type: ['VerifiableCredential', 'PermanentResidentCard'], - issuer: 'did:key:z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL', - issuanceDate: '2019-12-03T12:19:52Z', - expirationDate: '2029-12-03T12:19:52Z', - identifier: '83627465', - name: 'Permanent Resident Card', - credentialSubject: { - id: 'did:example:b34ca6cd37bbf23', - type: ['PermanentResident', 'Person'], - givenName: 'JOHN', - familyName: 'SMITH', - gender: 'Male', - image: 'data:image/png;base64,iVBORw0KGgokJggg==', - residentSince: '2015-01-01', - description: 'Government of Example Permanent Resident Card.', - lprCategory: 'C09', - lprNumber: '999-999-999', - commuterClassification: 'C1', - birthCountry: 'Bahamas', - birthDate: '1958-07-17', + const offerCredentialExchangeRecord = await aliceAgent.credentials.acceptOffer({ + credentialRecordId: aliceCredentialRecord.id, + }) + + expect(offerCredentialExchangeRecord.connectionId).toEqual(aliceConnectionId) + expect(offerCredentialExchangeRecord.protocolVersion).toEqual('v2') + expect(offerCredentialExchangeRecord.state).toEqual(CredentialState.RequestSent) + expect(offerCredentialExchangeRecord.threadId).not.toBeNull() + + testLogger.test('Faber waits for credential request from Alice') + await waitForCredentialRecordSubject(faberReplay, { + threadId: aliceCredentialRecord.threadId, + state: CredentialState.RequestReceived, + }) + + testLogger.test('Faber sends credential to Alice') + + await faberAgent.credentials.acceptRequest({ + credentialRecordId: faberCredentialRecord.id, + comment: 'V2 Indy Credential', + }) + + testLogger.test('Alice waits for credential from Faber') + aliceCredentialRecord = await waitForCredentialRecordSubject(aliceReplay, { + threadId: faberCredentialRecord.threadId, + state: CredentialState.CredentialReceived, + }) + + testLogger.test('Alice sends credential ack to Faber') + await aliceAgent.credentials.acceptCredential({ credentialRecordId: aliceCredentialRecord.id }) + + testLogger.test('Faber waits for credential ack from Alice') + faberCredentialRecord = await waitForCredentialRecordSubject(faberReplay, { + threadId: faberCredentialRecord.threadId, + state: CredentialState.Done, + }) + expect(aliceCredentialRecord).toMatchObject({ + type: CredentialExchangeRecord.type, + id: expect.any(String), + createdAt: expect.any(Date), + threadId: expect.any(String), + connectionId: expect.any(String), + state: CredentialState.CredentialReceived, + }) + + const credentialMessage = await faberAgent.credentials.findCredentialMessage(faberCredentialRecord.id) + const w3cCredential = credentialMessage?.credentialAttachments[1].getDataAsJson() + expect(w3cCredential).toMatchObject({ + '@context': [ + 'https://www.w3.org/2018/credentials/v1', + 'https://w3id.org/citizenship/v1', + 'https://w3id.org/security/bbs/v1', + ], + id: 'https://issuer.oidp.uscis.gov/credentials/83627465', + type: ['VerifiableCredential', 'PermanentResidentCard'], + issuer: 'did:key:z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL', + issuanceDate: '2019-12-03T12:19:52Z', + expirationDate: '2029-12-03T12:19:52Z', + identifier: '83627465', + name: 'Permanent Resident Card', + credentialSubject: { + id: 'did:example:b34ca6cd37bbf23', + type: ['PermanentResident', 'Person'], + givenName: 'JOHN', + familyName: 'SMITH', + gender: 'Male', + image: 'data:image/png;base64,iVBORw0KGgokJggg==', + residentSince: '2015-01-01', + description: 'Government of Example Permanent Resident Card.', + lprCategory: 'C09', + lprNumber: '999-999-999', + commuterClassification: 'C1', + birthCountry: 'Bahamas', + birthDate: '1958-07-17', + }, + proof: { + type: 'Ed25519Signature2018', + created: expect.any(String), + verificationMethod: + 'did:key:z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL#z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL', + proofPurpose: 'assertionMethod', + }, + }) + + expect(JsonTransformer.toJSON(credentialMessage)).toMatchObject({ + '@type': 'https://didcomm.org/issue-credential/2.0/issue-credential', + '@id': expect.any(String), + comment: 'V2 Indy Credential', + formats: [ + { + attach_id: expect.any(String), + format: 'hlindy/cred@v2.0', }, - proof: { - type: 'Ed25519Signature2018', - created: expect.any(String), - verificationMethod: - 'did:key:z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL#z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL', - proofPurpose: 'assertionMethod', + { + attach_id: expect.any(String), + format: 'aries/ld-proof-vc@v1.0', }, - }) - - expect(JsonTransformer.toJSON(credentialMessage)).toMatchObject({ - '@type': 'https://didcomm.org/issue-credential/2.0/issue-credential', - '@id': expect.any(String), - comment: 'V2 Indy Credential', - formats: [ - { - attach_id: expect.any(String), - format: 'hlindy/cred@v2.0', - }, - { - attach_id: expect.any(String), - format: 'aries/ld-proof-vc@1.0', - }, - ], - 'credentials~attach': [ - { - '@id': expect.any(String), - 'mime-type': 'application/json', - data: expect.any(Object), - lastmod_time: undefined, - byte_count: undefined, - }, - { - '@id': expect.any(String), - 'mime-type': 'application/json', - data: expect.any(Object), - lastmod_time: undefined, - byte_count: undefined, - }, - ], - '~thread': { - thid: expect.any(String), - pthid: undefined, - sender_order: undefined, - received_orders: undefined, + ], + 'credentials~attach': [ + { + '@id': expect.any(String), + 'mime-type': 'application/json', + data: expect.any(Object), + lastmod_time: undefined, + byte_count: undefined, }, - '~please_ack': { on: ['RECEIPT'] }, - '~service': undefined, - '~attach': undefined, - '~timing': undefined, - '~transport': undefined, - '~l10n': undefined, - }) - } + { + '@id': expect.any(String), + 'mime-type': 'application/json', + data: expect.any(Object), + lastmod_time: undefined, + byte_count: undefined, + }, + ], + '~thread': { + thid: expect.any(String), + pthid: undefined, + sender_order: undefined, + received_orders: undefined, + }, + '~please_ack': { on: ['RECEIPT'] }, + '~service': undefined, + '~attach': undefined, + '~timing': undefined, + '~transport': undefined, + '~l10n': undefined, + }) }) }) diff --git a/packages/core/src/modules/credentials/protocol/v2/errors/V2CredentialProblemReportError.ts b/packages/core/src/modules/credentials/protocol/v2/errors/V2CredentialProblemReportError.ts new file mode 100644 index 0000000000..0db9672621 --- /dev/null +++ b/packages/core/src/modules/credentials/protocol/v2/errors/V2CredentialProblemReportError.ts @@ -0,0 +1,23 @@ +import type { ProblemReportErrorOptions } from '../../../../problem-reports' +import type { CredentialProblemReportReason } from '../../../models/CredentialProblemReportReason' + +import { ProblemReportError } from '../../../../problem-reports/errors/ProblemReportError' +import { V2CredentialProblemReportMessage } from '../messages/V2CredentialProblemReportMessage' + +export interface V2CredentialProblemReportErrorOptions extends ProblemReportErrorOptions { + problemCode: CredentialProblemReportReason +} + +export class V2CredentialProblemReportError extends ProblemReportError { + public problemReport: V2CredentialProblemReportMessage + + public constructor(message: string, { problemCode }: V2CredentialProblemReportErrorOptions) { + super(message, { problemCode }) + this.problemReport = new V2CredentialProblemReportMessage({ + description: { + en: message, + code: problemCode, + }, + }) + } +} diff --git a/packages/core/src/modules/credentials/protocol/v2/errors/index.ts b/packages/core/src/modules/credentials/protocol/v2/errors/index.ts new file mode 100644 index 0000000000..846017e442 --- /dev/null +++ b/packages/core/src/modules/credentials/protocol/v2/errors/index.ts @@ -0,0 +1 @@ +export { V2CredentialProblemReportError, V2CredentialProblemReportErrorOptions } from './V2CredentialProblemReportError' diff --git a/packages/core/src/modules/credentials/protocol/v2/handlers/V2OfferCredentialHandler.ts b/packages/core/src/modules/credentials/protocol/v2/handlers/V2OfferCredentialHandler.ts index f23f53d2c0..8320314f0a 100644 --- a/packages/core/src/modules/credentials/protocol/v2/handlers/V2OfferCredentialHandler.ts +++ b/packages/core/src/modules/credentials/protocol/v2/handlers/V2OfferCredentialHandler.ts @@ -32,8 +32,7 @@ export class V2OfferCredentialHandler implements MessageHandler { private async acceptOffer( credentialRecord: CredentialExchangeRecord, - messageContext: MessageHandlerInboundMessage, - offerMessage?: V2OfferCredentialMessage + messageContext: MessageHandlerInboundMessage ) { messageContext.agentContext.config.logger.info(`Automatically sending request with autoAccept`) @@ -46,7 +45,7 @@ export class V2OfferCredentialHandler implements MessageHandler { connection: messageContext.connection, associatedRecord: credentialRecord, }) - } else if (offerMessage?.service) { + } else if (messageContext.message?.service) { const routingService = messageContext.agentContext.dependencyManager.resolve(RoutingService) const routing = await routingService.getRouting(messageContext.agentContext) const ourService = new ServiceDecorator({ @@ -54,7 +53,7 @@ export class V2OfferCredentialHandler implements MessageHandler { recipientKeys: [routing.recipientKey.publicKeyBase58], routingKeys: routing.routingKeys.map((key) => key.publicKeyBase58), }) - const recipientService = offerMessage.service + const recipientService = messageContext.message.service const { message } = await this.credentialProtocol.acceptOffer(messageContext.agentContext, { credentialRecord, diff --git a/packages/core/src/modules/credentials/protocol/v2/index.ts b/packages/core/src/modules/credentials/protocol/v2/index.ts index c6d6213662..f50d673645 100644 --- a/packages/core/src/modules/credentials/protocol/v2/index.ts +++ b/packages/core/src/modules/credentials/protocol/v2/index.ts @@ -1,2 +1,3 @@ export * from './V2CredentialProtocol' export * from './messages' +export * from './errors' diff --git a/packages/core/src/modules/credentials/protocol/v2/messages/V2CredentialPreview.ts b/packages/core/src/modules/credentials/protocol/v2/messages/V2CredentialPreview.ts index d566faa1a0..ea78448593 100644 --- a/packages/core/src/modules/credentials/protocol/v2/messages/V2CredentialPreview.ts +++ b/packages/core/src/modules/credentials/protocol/v2/messages/V2CredentialPreview.ts @@ -17,7 +17,7 @@ import { CredentialPreviewAttribute } from '../../../models/CredentialPreviewAtt export class V2CredentialPreview { public constructor(options: CredentialPreviewOptions) { if (options) { - this.attributes = options.attributes + this.attributes = options.attributes.map((a) => new CredentialPreviewAttribute(a)) } } diff --git a/packages/core/src/modules/credentials/protocol/v2/messages/V2IssueCredentialMessage.ts b/packages/core/src/modules/credentials/protocol/v2/messages/V2IssueCredentialMessage.ts index 12ef537dca..16ab1c44aa 100644 --- a/packages/core/src/modules/credentials/protocol/v2/messages/V2IssueCredentialMessage.ts +++ b/packages/core/src/modules/credentials/protocol/v2/messages/V2IssueCredentialMessage.ts @@ -6,7 +6,7 @@ import { DidCommV1Message } from '../../../../../didcomm' import { IsValidMessageType, parseMessageType } from '../../../../../utils/messageType' import { CredentialFormatSpec } from '../../../models' -export interface V2IssueCredentialMessageProps { +export interface V2IssueCredentialMessageOptions { id?: string comment?: string formats: CredentialFormatSpec[] @@ -14,7 +14,7 @@ export interface V2IssueCredentialMessageProps { } export class V2IssueCredentialMessage extends DidCommV1Message { - public constructor(options: V2IssueCredentialMessageProps) { + public constructor(options: V2IssueCredentialMessageOptions) { super() if (options) { @@ -48,6 +48,6 @@ export class V2IssueCredentialMessage extends DidCommV1Message { public credentialAttachments!: V1Attachment[] public getCredentialAttachmentById(id: string): V1Attachment | undefined { - return this.credentialAttachments.find((attachment) => attachment.id == id) + return this.credentialAttachments.find((attachment) => attachment.id === id) } } diff --git a/packages/core/src/modules/credentials/protocol/v2/messages/V2OfferCredentialMessage.ts b/packages/core/src/modules/credentials/protocol/v2/messages/V2OfferCredentialMessage.ts index 6805107572..793cb9450e 100644 --- a/packages/core/src/modules/credentials/protocol/v2/messages/V2OfferCredentialMessage.ts +++ b/packages/core/src/modules/credentials/protocol/v2/messages/V2OfferCredentialMessage.ts @@ -64,6 +64,6 @@ export class V2OfferCredentialMessage extends DidCommV1Message { public replacementId?: string public getOfferAttachmentById(id: string): V1Attachment | undefined { - return this.offerAttachments.find((attachment) => attachment.id == id) + return this.offerAttachments.find((attachment) => attachment.id === id) } } diff --git a/packages/core/src/modules/credentials/protocol/v2/messages/V2ProposeCredentialMessage.ts b/packages/core/src/modules/credentials/protocol/v2/messages/V2ProposeCredentialMessage.ts index d5914574b7..2837b36f7b 100644 --- a/packages/core/src/modules/credentials/protocol/v2/messages/V2ProposeCredentialMessage.ts +++ b/packages/core/src/modules/credentials/protocol/v2/messages/V2ProposeCredentialMessage.ts @@ -8,7 +8,7 @@ import { CredentialFormatSpec } from '../../../models' import { V2CredentialPreview } from './V2CredentialPreview' -export interface V2ProposeCredentialMessageProps { +export interface V2ProposeCredentialMessageOptions { id?: string formats: CredentialFormatSpec[] proposalAttachments: V1Attachment[] @@ -18,21 +18,22 @@ export interface V2ProposeCredentialMessageProps { } export class V2ProposeCredentialMessage extends DidCommV1Message { - public constructor(props: V2ProposeCredentialMessageProps) { + public constructor(options: V2ProposeCredentialMessageOptions) { super() - if (props) { - this.id = props.id ?? this.generateId() - this.comment = props.comment - this.credentialPreview = props.credentialPreview - this.formats = props.formats - this.proposalAttachments = props.proposalAttachments - this.appendedAttachments = props.attachments + if (options) { + this.id = options.id ?? this.generateId() + this.comment = options.comment + this.credentialPreview = options.credentialPreview + this.formats = options.formats + this.proposalAttachments = options.proposalAttachments + this.appendedAttachments = options.attachments } } @Type(() => CredentialFormatSpec) - @ValidateNested() + @ValidateNested({ each: true }) @IsArray() + @IsInstance(CredentialFormatSpec, { each: true }) public formats!: CredentialFormatSpec[] @IsValidMessageType(V2ProposeCredentialMessage.type) @@ -64,6 +65,6 @@ export class V2ProposeCredentialMessage extends DidCommV1Message { public comment?: string public getProposalAttachmentById(id: string): V1Attachment | undefined { - return this.proposalAttachments.find((attachment) => attachment.id == id) + return this.proposalAttachments.find((attachment) => attachment.id === id) } } diff --git a/packages/core/src/modules/credentials/protocol/v2/messages/V2RequestCredentialMessage.ts b/packages/core/src/modules/credentials/protocol/v2/messages/V2RequestCredentialMessage.ts index ef226908a7..1b7b31af5c 100644 --- a/packages/core/src/modules/credentials/protocol/v2/messages/V2RequestCredentialMessage.ts +++ b/packages/core/src/modules/credentials/protocol/v2/messages/V2RequestCredentialMessage.ts @@ -52,6 +52,6 @@ export class V2RequestCredentialMessage extends DidCommV1Message { public comment?: string public getRequestAttachmentById(id: string): V1Attachment | undefined { - return this.requestAttachments.find((attachment) => attachment.id == id) + return this.requestAttachments.find((attachment) => attachment.id === id) } } diff --git a/packages/core/src/modules/credentials/repository/CredentialExchangeRecord.ts b/packages/core/src/modules/credentials/repository/CredentialExchangeRecord.ts index 97355084d3..bfd7220b26 100644 --- a/packages/core/src/modules/credentials/repository/CredentialExchangeRecord.ts +++ b/packages/core/src/modules/credentials/repository/CredentialExchangeRecord.ts @@ -2,7 +2,6 @@ import type { TagsBase } from '../../../storage/BaseRecord' import type { AutoAcceptCredential } from '../models/CredentialAutoAcceptType' import type { CredentialState } from '../models/CredentialState' import type { RevocationNotification } from '../models/RevocationNotification' -import type { CredentialMetadata } from './CredentialMetadataTypes' import { Type } from 'class-transformer' @@ -10,11 +9,8 @@ import { V1Attachment } from '../../../decorators/attachment/V1Attachment' import { AriesFrameworkError } from '../../../error' import { BaseRecord } from '../../../storage/BaseRecord' import { uuid } from '../../../utils/uuid' -import { IndyCredentialView } from '../formats/indy/models/IndyCredentialView' import { CredentialPreviewAttribute } from '../models/CredentialPreviewAttribute' -import { CredentialMetadataKeys } from './CredentialMetadataTypes' - export interface CredentialExchangeRecordProps { id?: string createdAt?: Date @@ -38,8 +34,6 @@ export type DefaultCredentialTags = { connectionId?: string state: CredentialState credentialIds: string[] - indyRevocationRegistryId?: string - indyCredentialRevocationId?: string } export interface CredentialRecordBinding { @@ -47,11 +41,7 @@ export interface CredentialRecordBinding { credentialRecordId: string } -export class CredentialExchangeRecord extends BaseRecord< - DefaultCredentialTags, - CustomCredentialTags, - CredentialMetadata -> { +export class CredentialExchangeRecord extends BaseRecord { public connectionId?: string public threadId!: string public state!: CredentialState @@ -92,7 +82,6 @@ export class CredentialExchangeRecord extends BaseRecord< } public getTags() { - const metadata = this.metadata.get(CredentialMetadataKeys.IndyCredential) const ids = this.credentials.map((c) => c.credentialRecordId) return { @@ -101,29 +90,9 @@ export class CredentialExchangeRecord extends BaseRecord< connectionId: this.connectionId, state: this.state, credentialIds: ids, - indyRevocationRegistryId: metadata?.indyRevocationRegistryId, - indyCredentialRevocationId: metadata?.indyCredentialRevocationId, } } - public getCredentialInfo(): IndyCredentialView | null { - if (!this.credentialAttributes) return null - - const claims = this.credentialAttributes.reduce( - (accumulator, current) => ({ - ...accumulator, - [current.name]: current.value, - }), - {} - ) - - return new IndyCredentialView({ - claims, - attachments: this.linkedAttachments, - metadata: this.metadata.data, - }) - } - public assertProtocolVersion(version: string) { if (this.protocolVersion != version) { throw new AriesFrameworkError( diff --git a/packages/core/src/modules/credentials/repository/CredentialMetadataTypes.ts b/packages/core/src/modules/credentials/repository/CredentialMetadataTypes.ts deleted file mode 100644 index 7c645333cb..0000000000 --- a/packages/core/src/modules/credentials/repository/CredentialMetadataTypes.ts +++ /dev/null @@ -1,16 +0,0 @@ -import type { CredReqMetadata } from 'indy-sdk' - -export enum CredentialMetadataKeys { - IndyCredential = '_internal/indyCredential', - IndyRequest = '_internal/indyRequest', -} - -export type CredentialMetadata = { - [CredentialMetadataKeys.IndyCredential]: { - schemaId?: string - credentialDefinitionId?: string - indyRevocationRegistryId?: string - indyCredentialRevocationId?: string - } - [CredentialMetadataKeys.IndyRequest]: CredReqMetadata -} diff --git a/packages/core/src/modules/credentials/repository/__tests__/CredentialExchangeRecord.test.ts b/packages/core/src/modules/credentials/repository/__tests__/CredentialExchangeRecord.test.ts deleted file mode 100644 index 688f21bad1..0000000000 --- a/packages/core/src/modules/credentials/repository/__tests__/CredentialExchangeRecord.test.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { CredentialPreviewAttribute } from '../../models/CredentialPreviewAttribute' -import { CredentialState } from '../../models/CredentialState' -import { CredentialExchangeRecord } from '../CredentialExchangeRecord' -import { CredentialMetadataKeys } from '../CredentialMetadataTypes' - -describe('CredentialExchangeRecord', () => { - describe('getCredentialInfo()', () => { - test('creates credential info object from credential record data', () => { - const credentialRecord = new CredentialExchangeRecord({ - connectionId: '28790bfe-1345-4c64-b21a-7d98982b3894', - threadId: 'threadId', - state: CredentialState.Done, - credentialAttributes: [ - new CredentialPreviewAttribute({ - name: 'age', - value: '25', - }), - ], - protocolVersion: 'v1', - }) - - credentialRecord.metadata.set(CredentialMetadataKeys.IndyCredential, { - credentialDefinitionId: 'Th7MpTaRZVRYnPiabds81Y:3:CL:17:TAG', - schemaId: 'TL1EaPFCZ8Si5aUrqScBDt:2:test-schema-1599055118161:1.0', - }) - - const credentialInfo = credentialRecord.getCredentialInfo() - - expect(credentialInfo).toEqual({ - claims: { - age: '25', - }, - metadata: { - '_internal/indyCredential': { - credentialDefinitionId: 'Th7MpTaRZVRYnPiabds81Y:3:CL:17:TAG', - schemaId: 'TL1EaPFCZ8Si5aUrqScBDt:2:test-schema-1599055118161:1.0', - }, - }, - }) - }) - }) -}) diff --git a/packages/core/src/modules/credentials/repository/index.ts b/packages/core/src/modules/credentials/repository/index.ts index b7b986ad3e..980f320cfd 100644 --- a/packages/core/src/modules/credentials/repository/index.ts +++ b/packages/core/src/modules/credentials/repository/index.ts @@ -1,3 +1,2 @@ export * from './CredentialExchangeRecord' export * from './CredentialRepository' -export * from './CredentialMetadataTypes' diff --git a/packages/core/src/modules/credentials/util/composeAutoAccept.ts b/packages/core/src/modules/credentials/util/composeAutoAccept.ts index 55b3e70362..ace6fdf80c 100644 --- a/packages/core/src/modules/credentials/util/composeAutoAccept.ts +++ b/packages/core/src/modules/credentials/util/composeAutoAccept.ts @@ -6,7 +6,6 @@ import { AutoAcceptCredential } from '../models/CredentialAutoAcceptType' * - Otherwise the agent config * - Otherwise {@link AutoAcceptCredential.Never} is returned */ - export function composeAutoAccept( recordConfig: AutoAcceptCredential | undefined, agentConfig: AutoAcceptCredential | undefined diff --git a/packages/core/src/modules/didcomm/services/DidCommDocumentService.ts b/packages/core/src/modules/didcomm/services/DidCommDocumentService.ts index 4877113fa0..04cff6aceb 100644 --- a/packages/core/src/modules/didcomm/services/DidCommDocumentService.ts +++ b/packages/core/src/modules/didcomm/services/DidCommDocumentService.ts @@ -5,7 +5,7 @@ import type { ResolvedDidCommService } from '../types' import { AgentConfig } from '../../../agent/AgentConfig' import { KeyType } from '../../../crypto' import { injectable } from '../../../plugins' -import { DidResolverService } from '../../dids' +import { DidCommV2Service, DidResolverService } from '../../dids' import { DidCommV1Service, IndyAgentService, keyReferenceToKey } from '../../dids/domain' import { verkeyToInstanceOfKey } from '../../dids/helpers' import { findMatchingEd25519Key } from '../util/matchingEd25519Key' @@ -64,6 +64,22 @@ export class DidCommDocumentService { routingKeys, serviceEndpoint: didCommService.serviceEndpoint, }) + } else if (didCommService instanceof DidCommV2Service) { + // Resolve dids to DIDDocs to retrieve routingKeys + const routingKeys = [] + for (const routingKey of didCommService.routingKeys ?? []) { + const routingDidDocument = await this.didResolverService.resolveDidDocument(agentContext, routingKey) + routingKeys.push(keyReferenceToKey(routingDidDocument, routingKey)) + } + + // DidCommV2Service has keys encoded as key references + + didCommServices.push({ + id: didCommService.id, + routingKeys, + serviceEndpoint: didCommService.serviceEndpoint, + recipientKeys: [], + }) } } diff --git a/packages/core/src/modules/dids/DidsApi.ts b/packages/core/src/modules/dids/DidsApi.ts index 59134e5f6d..e20ef573e2 100644 --- a/packages/core/src/modules/dids/DidsApi.ts +++ b/packages/core/src/modules/dids/DidsApi.ts @@ -1,3 +1,4 @@ +import type { ImportDidOptions } from './DidsApiOptions' import type { DidCreateOptions, DidCreateResult, @@ -9,7 +10,9 @@ import type { } from './types' import { AgentContext } from '../../agent' +import { AriesFrameworkError } from '../../error' import { injectable } from '../../plugins' +import { WalletKeyExistsError } from '../../wallet/error' import { DidsModuleConfig } from './DidsModuleConfig' import { DidRepository } from './repository' @@ -96,7 +99,77 @@ export class DidsApi { * * You can call `${@link DidsModule.resolve} to resolve the did document based on the did itself. */ - public getCreatedDids({ method }: { method?: string } = {}) { - return this.didRepository.getCreatedDids(this.agentContext, { method }) + public getCreatedDids({ method, did }: { method?: string; did?: string } = {}) { + return this.didRepository.getCreatedDids(this.agentContext, { method, did }) + } + + /** + * Import an existing did that was created outside of the DidsApi. This will create a `DidRecord` for the did + * and will allow the did to be used in other parts of the agent. If you need to create a new did document, + * you can use the {@link DidsApi.create} method to create and register the did. + * + * If no `didDocument` is provided, the did document will be resolved using the did resolver. You can optionally provide a list + * of private key buffer with the respective private key bytes. These keys will be stored in the wallet, and allows you to use the + * did for other operations. Providing keys that already exist in the wallet is allowed, and those keys will be skipped from being + * added to the wallet. + * + * By default, this method will throw an error if the did already exists in the wallet. You can override this behavior by setting + * the `overwrite` option to `true`. This will update the did document in the record, and allows you to update the did over time. + */ + public async import({ did, didDocument, privateKeys = [], overwrite }: ImportDidOptions) { + if (didDocument && didDocument.id !== did) { + throw new AriesFrameworkError(`Did document id ${didDocument.id} does not match did ${did}`) + } + + const existingDidRecord = await this.didRepository.findCreatedDid(this.agentContext, did) + if (existingDidRecord && !overwrite) { + throw new AriesFrameworkError( + `A created did ${did} already exists. If you want to override the existing did, set the 'overwrite' option to update the did.` + ) + } + + if (!didDocument) { + didDocument = await this.resolveDidDocument(did) + } + + // Loop over all private keys and store them in the wallet. We don't check whether the keys are actually associated + // with the did document, this is up to the user. + for (const key of privateKeys) { + try { + // We can't check whether the key already exists in the wallet, but we can try to create it and catch the error + // if the key already exists. + await this.agentContext.wallet.createKey({ + keyType: key.keyType, + privateKey: key.privateKey, + }) + } catch (error) { + if (error instanceof WalletKeyExistsError) { + // If the error is a WalletKeyExistsError, we can ignore it. This means the key + // already exists in the wallet. We don't want to throw an error in this case. + } else { + throw error + } + } + } + + // Update existing did record + if (existingDidRecord) { + existingDidRecord.didDocument = didDocument + existingDidRecord.setTags({ + recipientKeyFingerprints: didDocument.recipientKeys.map((key) => key.fingerprint), + }) + + await this.didRepository.update(this.agentContext, existingDidRecord) + return + } + + // Create new did record + await this.didRepository.storeCreatedDid(this.agentContext, { + did, + didDocument, + tags: { + recipientKeyFingerprints: didDocument.recipientKeys.map((key) => key.fingerprint), + }, + }) } } diff --git a/packages/core/src/modules/dids/DidsApiOptions.ts b/packages/core/src/modules/dids/DidsApiOptions.ts new file mode 100644 index 0000000000..8561296a66 --- /dev/null +++ b/packages/core/src/modules/dids/DidsApiOptions.ts @@ -0,0 +1,33 @@ +import type { DidDocument } from './domain' +import type { KeyType } from '../../crypto' +import type { Buffer } from '../../utils' + +interface PrivateKey { + keyType: KeyType + privateKey: Buffer +} + +export interface ImportDidOptions { + /** + * The did to import. + */ + did: string + + /** + * Optional did document to import. If not provided, the did document will be resolved using the did resolver. + */ + didDocument?: DidDocument + + /** + * List of private keys associated with the did document that should be stored in the wallet. + */ + privateKeys?: PrivateKey[] + + /** + * Whether to overwrite an existing did record if it exists. If set to false, + * an error will be thrown if the did record already exists. + * + * @default false + */ + overwrite?: boolean +} diff --git a/packages/core/src/modules/dids/DidsModule.ts b/packages/core/src/modules/dids/DidsModule.ts index cf438e3ae8..a82dabeb8f 100644 --- a/packages/core/src/modules/dids/DidsModule.ts +++ b/packages/core/src/modules/dids/DidsModule.ts @@ -1,5 +1,5 @@ -import type { DependencyManager, Module } from '../../plugins' import type { DidsModuleConfigOptions } from './DidsModuleConfig' +import type { DependencyManager, Module } from '../../plugins' import { DidsApi } from './DidsApi' import { DidsModuleConfig } from './DidsModuleConfig' diff --git a/packages/core/src/modules/dids/DidsModuleConfig.ts b/packages/core/src/modules/dids/DidsModuleConfig.ts index 24acbf38bf..8e065657b4 100644 --- a/packages/core/src/modules/dids/DidsModuleConfig.ts +++ b/packages/core/src/modules/dids/DidsModuleConfig.ts @@ -2,12 +2,12 @@ import type { DidRegistrar, DidResolver } from './domain' import { KeyDidRegistrar, - IndySdkSovDidRegistrar, PeerDidRegistrar, KeyDidResolver, PeerDidResolver, - IndySdkSovDidResolver, WebDidResolver, + JwkDidRegistrar, + JwkDidResolver, } from './methods' /** @@ -23,7 +23,7 @@ export interface DidsModuleConfigOptions { * registered, as it is needed for the connections and out of band module to function. Other did methods can be * disabled. * - * @default [KeyDidRegistrar, IndySdkSovDidRegistrar, PeerDidRegistrar] + * @default [KeyDidRegistrar, PeerDidRegistrar, JwkDidRegistrar] */ registrars?: DidRegistrar[] @@ -35,7 +35,7 @@ export interface DidsModuleConfigOptions { * registered, as it is needed for the connections and out of band module to function. Other did methods can be * disabled. * - * @default [IndySdkSovDidResolver, WebDidResolver, KeyDidResolver, PeerDidResolver] + * @default [WebDidResolver, KeyDidResolver, PeerDidResolver, JwkDidResolver] */ resolvers?: DidResolver[] } @@ -54,11 +54,7 @@ export class DidsModuleConfig { // This prevents creating new instances every time this property is accessed if (this._registrars) return this._registrars - let registrars = this.options.registrars ?? [ - new KeyDidRegistrar(), - new IndySdkSovDidRegistrar(), - new PeerDidRegistrar(), - ] + let registrars = this.options.registrars ?? [new KeyDidRegistrar(), new PeerDidRegistrar(), new JwkDidRegistrar()] // Add peer did registrar if it is not included yet if (!registrars.find((registrar) => registrar instanceof PeerDidRegistrar)) { @@ -80,10 +76,10 @@ export class DidsModuleConfig { if (this._resolvers) return this._resolvers let resolvers = this.options.resolvers ?? [ - new IndySdkSovDidResolver(), new WebDidResolver(), new KeyDidResolver(), new PeerDidResolver(), + new JwkDidResolver(), ] // Add peer did resolver if it is not included yet diff --git a/packages/core/src/modules/dids/__tests__/DidsApi.test.ts b/packages/core/src/modules/dids/__tests__/DidsApi.test.ts new file mode 100644 index 0000000000..41993e9d43 --- /dev/null +++ b/packages/core/src/modules/dids/__tests__/DidsApi.test.ts @@ -0,0 +1,230 @@ +import { IndySdkModule } from '../../../../../indy-sdk/src' +import { indySdk } from '../../../../tests' +import { getAgentOptions } from '../../../../tests/helpers' +import { Agent } from '../../../agent/Agent' + +import { DidDocument, DidDocumentService, KeyType, TypedArrayEncoder } from '@aries-framework/core' + +const agentOptions = getAgentOptions( + 'DidsApi', + {}, + { + indySdk: new IndySdkModule({ + indySdk, + }), + } +) + +const agent = new Agent(agentOptions) + +describe('DidsApi', () => { + beforeAll(async () => { + await agent.initialize() + }) + + afterAll(async () => { + await agent.shutdown() + await agent.wallet.delete() + }) + + test('import an existing did without providing a did document', async () => { + const createKeySpy = jest.spyOn(agent.context.wallet, 'createKey') + + // Private key is for public key associated with did:key did + const privateKey = TypedArrayEncoder.fromString('a-sample-seed-of-32-bytes-in-tot') + const did = 'did:key:z6MkjEayvPpjVJKFLirX8SomBTPDboHm1XSCkUev2M4siQty' + + expect(await agent.dids.getCreatedDids({ did })).toHaveLength(0) + + await agent.dids.import({ + did, + privateKeys: [ + { + privateKey, + keyType: KeyType.Ed25519, + }, + ], + }) + + expect(createKeySpy).toHaveBeenCalledWith({ + privateKey, + keyType: KeyType.Ed25519, + }) + + const createdDids = await agent.dids.getCreatedDids({ + did, + }) + expect(createdDids).toHaveLength(1) + + expect(createdDids[0].getTags()).toEqual({ + did, + legacyUnqualifiedDid: undefined, + method: 'key', + methodSpecificIdentifier: 'z6MkjEayvPpjVJKFLirX8SomBTPDboHm1XSCkUev2M4siQty', + role: 'created', + }) + + expect(createdDids[0].toJSON()).toMatchObject({ + did, + didDocument: { + '@context': [ + 'https://w3id.org/did/v1', + 'https://w3id.org/security/suites/ed25519-2018/v1', + 'https://w3id.org/security/suites/x25519-2019/v1', + ], + id: 'did:key:z6MkjEayvPpjVJKFLirX8SomBTPDboHm1XSCkUev2M4siQty', + + verificationMethod: [ + { + id: 'did:key:z6MkjEayvPpjVJKFLirX8SomBTPDboHm1XSCkUev2M4siQty#z6MkjEayvPpjVJKFLirX8SomBTPDboHm1XSCkUev2M4siQty', + type: 'Ed25519VerificationKey2018', + controller: 'did:key:z6MkjEayvPpjVJKFLirX8SomBTPDboHm1XSCkUev2M4siQty', + publicKeyBase58: '5nKwL9aJ9kpnEE1pSsqvLMqDnE1ubeBr4TjzC56roC7b', + }, + ], + + authentication: [ + 'did:key:z6MkjEayvPpjVJKFLirX8SomBTPDboHm1XSCkUev2M4siQty#z6MkjEayvPpjVJKFLirX8SomBTPDboHm1XSCkUev2M4siQty', + ], + assertionMethod: [ + 'did:key:z6MkjEayvPpjVJKFLirX8SomBTPDboHm1XSCkUev2M4siQty#z6MkjEayvPpjVJKFLirX8SomBTPDboHm1XSCkUev2M4siQty', + ], + keyAgreement: [ + { + id: 'did:key:z6MkjEayvPpjVJKFLirX8SomBTPDboHm1XSCkUev2M4siQty#z6LSd6ed6s6HGsVsDL9vyx3s1Vi2jQYsX9TqjqVFam2oz776', + type: 'X25519KeyAgreementKey2019', + controller: 'did:key:z6MkjEayvPpjVJKFLirX8SomBTPDboHm1XSCkUev2M4siQty', + publicKeyBase58: '2RUTaZHRBQn87wnATJXuguVYtG1kpYHgrrma6JPHGjLL', + }, + ], + capabilityInvocation: [ + 'did:key:z6MkjEayvPpjVJKFLirX8SomBTPDboHm1XSCkUev2M4siQty#z6MkjEayvPpjVJKFLirX8SomBTPDboHm1XSCkUev2M4siQty', + ], + capabilityDelegation: [ + 'did:key:z6MkjEayvPpjVJKFLirX8SomBTPDboHm1XSCkUev2M4siQty#z6MkjEayvPpjVJKFLirX8SomBTPDboHm1XSCkUev2M4siQty', + ], + }, + }) + }) + + test('import an existing did with providing a did document', async () => { + const createKeySpy = jest.spyOn(agent.context.wallet, 'createKey') + + // Private key is for public key associated with did:key did + const privateKey = TypedArrayEncoder.fromString('a-new-sample-seed-of-32-bytes-in') + const did = 'did:peer:0z6Mkhu3G8viiebsWmCiSgWiQoCZrTeuX76oLDow81YNYvJQM' + + expect(await agent.dids.getCreatedDids({ did })).toHaveLength(0) + + await agent.dids.import({ + did, + didDocument: new DidDocument({ + id: did, + }), + privateKeys: [ + { + privateKey, + keyType: KeyType.Ed25519, + }, + ], + }) + + expect(createKeySpy).toHaveBeenCalledWith({ + privateKey, + keyType: KeyType.Ed25519, + }) + + const createdDids = await agent.dids.getCreatedDids({ + did, + }) + expect(createdDids).toHaveLength(1) + + expect(createdDids[0].getTags()).toEqual({ + did, + legacyUnqualifiedDid: undefined, + method: 'peer', + methodSpecificIdentifier: '0z6Mkhu3G8viiebsWmCiSgWiQoCZrTeuX76oLDow81YNYvJQM', + role: 'created', + }) + + expect(createdDids[0].toJSON()).toMatchObject({ + did, + didDocument: { + id: did, + }, + }) + }) + + test('can only overwrite if overwrite option is set', async () => { + const did = 'did:example:123' + const didDocument = new DidDocument({ id: did }) + const didDocument2 = new DidDocument({ + id: did, + service: [new DidDocumentService({ id: 'did:example:123#service', type: 'test', serviceEndpoint: 'test' })], + }) + + expect(await agent.dids.getCreatedDids({ did })).toHaveLength(0) + + // First import, should work + await agent.dids.import({ + did, + didDocument, + }) + + expect(await agent.dids.getCreatedDids({ did })).toHaveLength(1) + expect( + agent.dids.import({ + did, + didDocument: didDocument2, + }) + ).rejects.toThrowError( + "A created did did:example:123 already exists. If you want to override the existing did, set the 'overwrite' option to update the did." + ) + + // Should not have stored the updated record + const createdDids = await agent.dids.getCreatedDids({ did }) + expect(createdDids[0].didDocument?.service).toBeUndefined() + + // Should work, overwrite is set + await agent.dids.import({ + did, + didDocument: didDocument2, + overwrite: true, + }) + + // Should not have stored the updated record + const createdDidsOverwrite = await agent.dids.getCreatedDids({ did }) + expect(createdDidsOverwrite[0].didDocument?.service).toHaveLength(1) + }) + + test('providing privateKeys that already exist is allowd', async () => { + const privateKey = TypedArrayEncoder.fromString('another-samples-seed-of-32-bytes') + + const did = 'did:example:456' + const didDocument = new DidDocument({ id: did }) + + await agent.dids.import({ + did, + didDocument, + privateKeys: [ + { + keyType: KeyType.Ed25519, + privateKey, + }, + ], + }) + + // Provide the same key again, should work + await agent.dids.import({ + did, + didDocument, + overwrite: true, + privateKeys: [ + { + keyType: KeyType.Ed25519, + privateKey, + }, + ], + }) + }) +}) diff --git a/packages/core/src/modules/dids/__tests__/DidsModuleConfig.test.ts b/packages/core/src/modules/dids/__tests__/DidsModuleConfig.test.ts index 53e5ed3203..ef3dc3dc66 100644 --- a/packages/core/src/modules/dids/__tests__/DidsModuleConfig.test.ts +++ b/packages/core/src/modules/dids/__tests__/DidsModuleConfig.test.ts @@ -1,15 +1,15 @@ import type { DidRegistrar, DidResolver } from '../domain' +import { DidsModuleConfig } from '../DidsModuleConfig' import { KeyDidRegistrar, - IndySdkSovDidRegistrar, PeerDidRegistrar, KeyDidResolver, PeerDidResolver, - IndySdkSovDidResolver, WebDidResolver, -} from '..' -import { DidsModuleConfig } from '../DidsModuleConfig' + JwkDidRegistrar, + JwkDidResolver, +} from '../methods' describe('DidsModuleConfig', () => { test('sets default values', () => { @@ -17,14 +17,14 @@ describe('DidsModuleConfig', () => { expect(config.registrars).toEqual([ expect.any(KeyDidRegistrar), - expect.any(IndySdkSovDidRegistrar), expect.any(PeerDidRegistrar), + expect.any(JwkDidRegistrar), ]) expect(config.resolvers).toEqual([ - expect.any(IndySdkSovDidResolver), expect.any(WebDidResolver), expect.any(KeyDidResolver), expect.any(PeerDidResolver), + expect.any(JwkDidResolver), ]) }) diff --git a/packages/core/src/modules/dids/__tests__/__fixtures__/didKeyP256.json b/packages/core/src/modules/dids/__tests__/__fixtures__/didKeyP256.json new file mode 100644 index 0000000000..5465e191de --- /dev/null +++ b/packages/core/src/modules/dids/__tests__/__fixtures__/didKeyP256.json @@ -0,0 +1,32 @@ +{ + "@context": ["https://w3id.org/did/v1", "https://w3id.org/security/suites/jws-2020/v1"], + "id": "did:key:zDnaerx9CtbPJ1q36T5Ln5wYt3MQYeGRG5ehnPAmxcf5mDZpv", + "verificationMethod": [ + { + "id": "did:key:zDnaerx9CtbPJ1q36T5Ln5wYt3MQYeGRG5ehnPAmxcf5mDZpv#zDnaerx9CtbPJ1q36T5Ln5wYt3MQYeGRG5ehnPAmxcf5mDZpv", + "type": "JsonWebKey2020", + "controller": "did:key:zDnaerx9CtbPJ1q36T5Ln5wYt3MQYeGRG5ehnPAmxcf5mDZpv", + "publicKeyJwk": { + "kty": "EC", + "crv": "P-256", + "x": "igrFmi0whuihKnj9R3Om1SoMph72wUGeFaBbzG2vzns", + "y": "efsX5b10x8yjyrj4ny3pGfLcY7Xby1KzgqOdqnsrJIM" + } + } + ], + "assertionMethod": [ + "did:key:zDnaerx9CtbPJ1q36T5Ln5wYt3MQYeGRG5ehnPAmxcf5mDZpv#zDnaerx9CtbPJ1q36T5Ln5wYt3MQYeGRG5ehnPAmxcf5mDZpv" + ], + "authentication": [ + "did:key:zDnaerx9CtbPJ1q36T5Ln5wYt3MQYeGRG5ehnPAmxcf5mDZpv#zDnaerx9CtbPJ1q36T5Ln5wYt3MQYeGRG5ehnPAmxcf5mDZpv" + ], + "capabilityInvocation": [ + "did:key:zDnaerx9CtbPJ1q36T5Ln5wYt3MQYeGRG5ehnPAmxcf5mDZpv#zDnaerx9CtbPJ1q36T5Ln5wYt3MQYeGRG5ehnPAmxcf5mDZpv" + ], + "capabilityDelegation": [ + "did:key:zDnaerx9CtbPJ1q36T5Ln5wYt3MQYeGRG5ehnPAmxcf5mDZpv#zDnaerx9CtbPJ1q36T5Ln5wYt3MQYeGRG5ehnPAmxcf5mDZpv" + ], + "keyAgreement": [ + "did:key:zDnaerx9CtbPJ1q36T5Ln5wYt3MQYeGRG5ehnPAmxcf5mDZpv#zDnaerx9CtbPJ1q36T5Ln5wYt3MQYeGRG5ehnPAmxcf5mDZpv" + ] +} diff --git a/packages/core/src/modules/dids/__tests__/__fixtures__/didKeyP384.json b/packages/core/src/modules/dids/__tests__/__fixtures__/didKeyP384.json new file mode 100644 index 0000000000..b5249b1afc --- /dev/null +++ b/packages/core/src/modules/dids/__tests__/__fixtures__/didKeyP384.json @@ -0,0 +1,32 @@ +{ + "@context": ["https://w3id.org/did/v1", "https://w3id.org/security/suites/jws-2020/v1"], + "id": "did:key:z82Lm1MpAkeJcix9K8TMiLd5NMAhnwkjjCBeWHXyu3U4oT2MVJJKXkcVBgjGhnLBn2Kaau9", + "verificationMethod": [ + { + "id": "did:key:z82Lm1MpAkeJcix9K8TMiLd5NMAhnwkjjCBeWHXyu3U4oT2MVJJKXkcVBgjGhnLBn2Kaau9#z82Lm1MpAkeJcix9K8TMiLd5NMAhnwkjjCBeWHXyu3U4oT2MVJJKXkcVBgjGhnLBn2Kaau9", + "type": "JsonWebKey2020", + "controller": "did:key:z82Lm1MpAkeJcix9K8TMiLd5NMAhnwkjjCBeWHXyu3U4oT2MVJJKXkcVBgjGhnLBn2Kaau9", + "publicKeyJwk": { + "kty": "EC", + "crv": "P-384", + "x": "lInTxl8fjLKp_UCrxI0WDklahi-7-_6JbtiHjiRvMvhedhKVdHBfi2HCY8t_QJyc", + "y": "y6N1IC-2mXxHreETBW7K3mBcw0qGr3CWHCs-yl09yCQRLcyfGv7XhqAngHOu51Zv" + } + } + ], + "assertionMethod": [ + "did:key:z82Lm1MpAkeJcix9K8TMiLd5NMAhnwkjjCBeWHXyu3U4oT2MVJJKXkcVBgjGhnLBn2Kaau9#z82Lm1MpAkeJcix9K8TMiLd5NMAhnwkjjCBeWHXyu3U4oT2MVJJKXkcVBgjGhnLBn2Kaau9" + ], + "authentication": [ + "did:key:z82Lm1MpAkeJcix9K8TMiLd5NMAhnwkjjCBeWHXyu3U4oT2MVJJKXkcVBgjGhnLBn2Kaau9#z82Lm1MpAkeJcix9K8TMiLd5NMAhnwkjjCBeWHXyu3U4oT2MVJJKXkcVBgjGhnLBn2Kaau9" + ], + "capabilityInvocation": [ + "did:key:z82Lm1MpAkeJcix9K8TMiLd5NMAhnwkjjCBeWHXyu3U4oT2MVJJKXkcVBgjGhnLBn2Kaau9#z82Lm1MpAkeJcix9K8TMiLd5NMAhnwkjjCBeWHXyu3U4oT2MVJJKXkcVBgjGhnLBn2Kaau9" + ], + "capabilityDelegation": [ + "did:key:z82Lm1MpAkeJcix9K8TMiLd5NMAhnwkjjCBeWHXyu3U4oT2MVJJKXkcVBgjGhnLBn2Kaau9#z82Lm1MpAkeJcix9K8TMiLd5NMAhnwkjjCBeWHXyu3U4oT2MVJJKXkcVBgjGhnLBn2Kaau9" + ], + "keyAgreement": [ + "did:key:z82Lm1MpAkeJcix9K8TMiLd5NMAhnwkjjCBeWHXyu3U4oT2MVJJKXkcVBgjGhnLBn2Kaau9#z82Lm1MpAkeJcix9K8TMiLd5NMAhnwkjjCBeWHXyu3U4oT2MVJJKXkcVBgjGhnLBn2Kaau9" + ] +} diff --git a/packages/core/src/modules/dids/__tests__/__fixtures__/didKeyP521.json b/packages/core/src/modules/dids/__tests__/__fixtures__/didKeyP521.json new file mode 100644 index 0000000000..bafea05578 --- /dev/null +++ b/packages/core/src/modules/dids/__tests__/__fixtures__/didKeyP521.json @@ -0,0 +1,32 @@ +{ + "@context": ["https://w3id.org/did/v1", "https://w3id.org/security/suites/jws-2020/v1"], + "id": "did:key:z2J9gaYxrKVpdoG9A4gRnmpnRCcxU6agDtFVVBVdn1JedouoZN7SzcyREXXzWgt3gGiwpoHq7K68X4m32D8HgzG8wv3sY5j7", + "verificationMethod": [ + { + "id": "did:key:z2J9gaYxrKVpdoG9A4gRnmpnRCcxU6agDtFVVBVdn1JedouoZN7SzcyREXXzWgt3gGiwpoHq7K68X4m32D8HgzG8wv3sY5j7#z2J9gaYxrKVpdoG9A4gRnmpnRCcxU6agDtFVVBVdn1JedouoZN7SzcyREXXzWgt3gGiwpoHq7K68X4m32D8HgzG8wv3sY5j7", + "type": "JsonWebKey2020", + "controller": "did:key:z2J9gaYxrKVpdoG9A4gRnmpnRCcxU6agDtFVVBVdn1JedouoZN7SzcyREXXzWgt3gGiwpoHq7K68X4m32D8HgzG8wv3sY5j7", + "publicKeyJwk": { + "kty": "EC", + "crv": "P-521", + "x": "ASUHPMyichQ0QbHZ9ofNx_l4y7luncn5feKLo3OpJ2nSbZoC7mffolj5uy7s6KSKXFmnNWxGJ42IOrjZ47qqwqyS", + "y": "AW9ziIC4ZQQVSNmLlp59yYKrjRY0_VqO-GOIYQ9tYpPraBKUloEId6cI_vynCzlZWZtWpgOM3HPhYEgawQ703RjC" + } + } + ], + "assertionMethod": [ + "did:key:z2J9gaYxrKVpdoG9A4gRnmpnRCcxU6agDtFVVBVdn1JedouoZN7SzcyREXXzWgt3gGiwpoHq7K68X4m32D8HgzG8wv3sY5j7#z2J9gaYxrKVpdoG9A4gRnmpnRCcxU6agDtFVVBVdn1JedouoZN7SzcyREXXzWgt3gGiwpoHq7K68X4m32D8HgzG8wv3sY5j7" + ], + "authentication": [ + "did:key:z2J9gaYxrKVpdoG9A4gRnmpnRCcxU6agDtFVVBVdn1JedouoZN7SzcyREXXzWgt3gGiwpoHq7K68X4m32D8HgzG8wv3sY5j7#z2J9gaYxrKVpdoG9A4gRnmpnRCcxU6agDtFVVBVdn1JedouoZN7SzcyREXXzWgt3gGiwpoHq7K68X4m32D8HgzG8wv3sY5j7" + ], + "capabilityInvocation": [ + "did:key:z2J9gaYxrKVpdoG9A4gRnmpnRCcxU6agDtFVVBVdn1JedouoZN7SzcyREXXzWgt3gGiwpoHq7K68X4m32D8HgzG8wv3sY5j7#z2J9gaYxrKVpdoG9A4gRnmpnRCcxU6agDtFVVBVdn1JedouoZN7SzcyREXXzWgt3gGiwpoHq7K68X4m32D8HgzG8wv3sY5j7" + ], + "capabilityDelegation": [ + "did:key:z2J9gaYxrKVpdoG9A4gRnmpnRCcxU6agDtFVVBVdn1JedouoZN7SzcyREXXzWgt3gGiwpoHq7K68X4m32D8HgzG8wv3sY5j7#z2J9gaYxrKVpdoG9A4gRnmpnRCcxU6agDtFVVBVdn1JedouoZN7SzcyREXXzWgt3gGiwpoHq7K68X4m32D8HgzG8wv3sY5j7" + ], + "keyAgreement": [ + "did:key:z2J9gaYxrKVpdoG9A4gRnmpnRCcxU6agDtFVVBVdn1JedouoZN7SzcyREXXzWgt3gGiwpoHq7K68X4m32D8HgzG8wv3sY5j7#z2J9gaYxrKVpdoG9A4gRnmpnRCcxU6agDtFVVBVdn1JedouoZN7SzcyREXXzWgt3gGiwpoHq7K68X4m32D8HgzG8wv3sY5j7" + ] +} diff --git a/packages/core/src/modules/dids/__tests__/dids-registrar.e2e.test.ts b/packages/core/src/modules/dids/__tests__/dids-registrar.e2e.test.ts index ea77eb7131..8b512f26eb 100644 --- a/packages/core/src/modules/dids/__tests__/dids-registrar.e2e.test.ts +++ b/packages/core/src/modules/dids/__tests__/dids-registrar.e2e.test.ts @@ -1,29 +1,23 @@ -import type { Wallet } from '../../../wallet' import type { KeyDidCreateOptions } from '../methods/key/KeyDidRegistrar' import type { PeerDidNumAlgo0CreateOptions } from '../methods/peer/PeerDidRegistrar' -import type { SovDidCreateOptions } from '../methods/sov/IndySdkSovDidRegistrar' -import { convertPublicKeyToX25519, generateKeyPairFromSeed } from '@stablelib/ed25519' - -import { genesisPath, getAgentOptions } from '../../../../tests/helpers' +import { IndySdkModule } from '../../../../../indy-sdk/src' +import { indySdk } from '../../../../tests' +import { getAgentOptions } from '../../../../tests/helpers' import { Agent } from '../../../agent/Agent' -import { InjectionSymbols } from '../../../constants' import { KeyType } from '../../../crypto' import { JsonTransformer, TypedArrayEncoder } from '../../../utils' -import { indyDidFromPublicKeyBase58 } from '../../../utils/did' import { PeerDidNumAlgo } from '../methods/peer/didPeer' -const agentOptions = getAgentOptions('Faber Dids Registrar', { - indyLedgers: [ - { - id: `localhost`, - isProduction: false, - genesisPath, - indyNamespace: 'localhost', - transactionAuthorAgreement: { version: '1', acceptanceMechanism: 'accept' }, - }, - ], -}) +const agentOptions = getAgentOptions( + 'Faber Dids Registrar', + {}, + { + indySdk: new IndySdkModule({ + indySdk, + }), + } +) describe('dids', () => { let agent: Agent @@ -45,7 +39,7 @@ describe('dids', () => { keyType: KeyType.Ed25519, }, secret: { - seed: '96213c3d7fc8d4d6754c7a0fd969598e', + privateKey: TypedArrayEncoder.fromString('96213c3d7fc8d4d6754c7a0fd969598e'), }, }) @@ -95,12 +89,14 @@ describe('dids', () => { ], id: 'did:key:z6MkpGR4gs4Rc3Zph4vj8wRnjnAxgAPSxcR8MAVKutWspQzc', }, - secret: { seed: '96213c3d7fc8d4d6754c7a0fd969598e' }, + secret: { privateKey: TypedArrayEncoder.fromString('96213c3d7fc8d4d6754c7a0fd969598e') }, }, }) }) it('should create a did:peer did', async () => { + const privateKey = TypedArrayEncoder.fromString('e008ef10b7c163114b3857542b3736eb') + const did = await agent.dids.create({ method: 'peer', options: { @@ -108,7 +104,7 @@ describe('dids', () => { numAlgo: PeerDidNumAlgo.InceptionKeyWithoutDoc, }, secret: { - seed: 'e008ef10b7c163114b3857542b3736eb', + privateKey, }, }) @@ -158,109 +154,7 @@ describe('dids', () => { ], id: 'did:peer:0z6Mkuo91yRhTWDrFkdNBcLXAbvtUiq2J9E4QQcfYZt4hevkh', }, - secret: { seed: 'e008ef10b7c163114b3857542b3736eb' }, - }, - }) - }) - - it('should create a did:sov did', async () => { - // Generate a seed and the indy did. This allows us to create a new did every time - // but still check if the created output document is as expected. - const seed = Array(32 + 1) - .join((Math.random().toString(36) + '00000000000000000').slice(2, 18)) - .slice(0, 32) - - const publicKeyEd25519 = generateKeyPairFromSeed(TypedArrayEncoder.fromString(seed)).publicKey - const x25519PublicKeyBase58 = TypedArrayEncoder.toBase58(convertPublicKeyToX25519(publicKeyEd25519)) - const ed25519PublicKeyBase58 = TypedArrayEncoder.toBase58(publicKeyEd25519) - const indyDid = indyDidFromPublicKeyBase58(ed25519PublicKeyBase58) - - const wallet = agent.injectionContainer.resolve(InjectionSymbols.Wallet) - // eslint-disable-next-line @typescript-eslint/no-non-null-asserted-optional-chain, @typescript-eslint/no-non-null-assertion - const submitterDid = `did:sov:${wallet.publicDid?.did!}` - - const did = await agent.dids.create({ - method: 'sov', - options: { - submitterDid, - alias: 'Alias', - endpoints: { - endpoint: 'https://example.com/endpoint', - types: ['DIDCommMessaging', 'did-communication', 'endpoint'], - routingKeys: ['a-routing-key'], - }, - }, - secret: { - seed, - }, - }) - - expect(JsonTransformer.toJSON(did)).toMatchObject({ - didDocumentMetadata: { - qualifiedIndyDid: `did:indy:localhost:${indyDid}`, - }, - didRegistrationMetadata: { - didIndyNamespace: 'localhost', - }, - didState: { - state: 'finished', - did: `did:sov:${indyDid}`, - didDocument: { - '@context': [ - 'https://w3id.org/did/v1', - 'https://w3id.org/security/suites/ed25519-2018/v1', - 'https://w3id.org/security/suites/x25519-2019/v1', - 'https://didcomm.org/messaging/contexts/v2', - ], - alsoKnownAs: undefined, - controller: undefined, - verificationMethod: [ - { - id: `did:sov:${indyDid}#key-1`, - type: 'Ed25519VerificationKey2018', - controller: `did:sov:${indyDid}`, - publicKeyBase58: ed25519PublicKeyBase58, - }, - { - id: `did:sov:${indyDid}#key-agreement-1`, - type: 'X25519KeyAgreementKey2019', - controller: `did:sov:${indyDid}`, - publicKeyBase58: x25519PublicKeyBase58, - }, - ], - service: [ - { - id: `did:sov:${indyDid}#endpoint`, - serviceEndpoint: 'https://example.com/endpoint', - type: 'endpoint', - }, - { - accept: ['didcomm/aip2;env=rfc19'], - id: `did:sov:${indyDid}#did-communication`, - priority: 0, - recipientKeys: [`did:sov:${indyDid}#key-agreement-1`], - routingKeys: ['a-routing-key'], - serviceEndpoint: 'https://example.com/endpoint', - type: 'did-communication', - }, - { - accept: ['didcomm/v2'], - id: `did:sov:${indyDid}#didcomm-1`, - routingKeys: ['a-routing-key'], - serviceEndpoint: 'https://example.com/endpoint', - type: 'DIDCommMessaging', - }, - ], - authentication: [`did:sov:${indyDid}#key-1`], - assertionMethod: [`did:sov:${indyDid}#key-1`], - keyAgreement: [`did:sov:${indyDid}#key-agreement-1`], - capabilityInvocation: undefined, - capabilityDelegation: undefined, - id: `did:sov:${indyDid}`, - }, - secret: { - seed, - }, + secret: { privateKey }, }, }) }) diff --git a/packages/core/src/modules/dids/__tests__/dids-resolver.e2e.test.ts b/packages/core/src/modules/dids/__tests__/dids-resolver.e2e.test.ts index 09d64b2b70..3e46ada4f0 100644 --- a/packages/core/src/modules/dids/__tests__/dids-resolver.e2e.test.ts +++ b/packages/core/src/modules/dids/__tests__/dids-resolver.e2e.test.ts @@ -1,16 +1,23 @@ -import type { SovDidCreateOptions } from '../methods' - +import { IndySdkModule } from '../../../../../indy-sdk/src' +import { indySdk } from '../../../../tests' import { getAgentOptions } from '../../../../tests/helpers' import { Agent } from '../../../agent/Agent' -import { AriesFrameworkError } from '../../../error' import { JsonTransformer } from '../../../utils' -import { sleep } from '../../../utils/sleep' -describe('dids', () => { - let agent: Agent +const agent = new Agent( + getAgentOptions( + 'Faber Dids', + {}, + { + indySdk: new IndySdkModule({ + indySdk, + }), + } + ) +) +describe('dids', () => { beforeAll(async () => { - agent = new Agent(getAgentOptions('Faber Dids')) await agent.initialize() }) @@ -19,64 +26,6 @@ describe('dids', () => { await agent.wallet.delete() }) - it('should resolve a did:sov did', async () => { - const publicDid = agent.publicDid?.did - - if (!publicDid) throw new Error('Agent has no public did') - - const createResult = await agent.dids.create({ - method: 'sov', - options: { - submitterDid: `did:sov:${publicDid}`, - alias: 'Alias', - role: 'TRUSTEE', - }, - }) - - // Terrible, but the did can't be immediately resolved, so we need to wait a bit - await sleep(1000) - - if (!createResult.didState.did) throw new AriesFrameworkError('Unable to register did') - const didResult = await agent.dids.resolve(createResult.didState.did) - - expect(JsonTransformer.toJSON(didResult)).toMatchObject({ - didDocument: { - '@context': [ - 'https://w3id.org/did/v1', - 'https://w3id.org/security/suites/ed25519-2018/v1', - 'https://w3id.org/security/suites/x25519-2019/v1', - ], - id: createResult.didState.did, - alsoKnownAs: undefined, - controller: undefined, - verificationMethod: [ - { - type: 'Ed25519VerificationKey2018', - controller: createResult.didState.did, - id: `${createResult.didState.did}#key-1`, - publicKeyBase58: expect.any(String), - }, - { - controller: createResult.didState.did, - type: 'X25519KeyAgreementKey2019', - id: `${createResult.didState.did}#key-agreement-1`, - publicKeyBase58: expect.any(String), - }, - ], - capabilityDelegation: undefined, - capabilityInvocation: undefined, - authentication: [`${createResult.didState.did}#key-1`], - assertionMethod: [`${createResult.didState.did}#key-1`], - keyAgreement: [`${createResult.didState.did}#key-agreement-1`], - service: undefined, - }, - didDocumentMetadata: {}, - didResolutionMetadata: { - contentType: 'application/did+ld+json', - }, - }) - }) - it('should resolve a did:key did', async () => { const did = await agent.dids.resolve('did:key:z6Mkk7yqnGF3YwTrLpqrW6PGsKci7dNqh1CjnvMbzrMerSeL') diff --git a/packages/core/src/modules/dids/__tests__/peer-did.test.ts b/packages/core/src/modules/dids/__tests__/peer-did.test.ts index 516471dae2..3bf77d9d7f 100644 --- a/packages/core/src/modules/dids/__tests__/peer-did.test.ts +++ b/packages/core/src/modules/dids/__tests__/peer-did.test.ts @@ -1,15 +1,17 @@ import type { AgentContext } from '../../../agent' +import type { Wallet } from '../../../wallet' import { Subject } from 'rxjs' +import { InMemoryStorageService } from '../../../../../../tests/InMemoryStorageService' +import { IndySdkWallet } from '../../../../../indy-sdk/src' +import { indySdk } from '../../../../../indy-sdk/tests/setupIndySdkModule' import { getAgentConfig, getAgentContext } from '../../../../tests/helpers' import { EventEmitter } from '../../../agent/EventEmitter' import { InjectionSymbols } from '../../../constants' import { Key, KeyType } from '../../../crypto' import { KeyProviderRegistry } from '../../../crypto/key-provider' -import { IndyStorageService } from '../../../storage/IndyStorageService' -import { JsonTransformer } from '../../../utils' -import { IndyWallet } from '../../../wallet/IndyWallet' +import { JsonTransformer, TypedArrayEncoder } from '../../../utils' import { DidsModuleConfig } from '../DidsModuleConfig' import { DidCommV1Service, DidDocument, DidDocumentBuilder } from '../domain' import { DidDocumentRole } from '../domain/DidDocumentRole' @@ -29,13 +31,13 @@ describe('peer dids', () => { let didRepository: DidRepository let didResolverService: DidResolverService - let wallet: IndyWallet + let wallet: Wallet let agentContext: AgentContext let eventEmitter: EventEmitter beforeEach(async () => { - wallet = new IndyWallet(config.agentDependencies, config.logger, new KeyProviderRegistry([])) - const storageService = new IndyStorageService(config.agentDependencies) + wallet = new IndySdkWallet(indySdk, config.logger, new KeyProviderRegistry([])) + const storageService = new InMemoryStorageService() eventEmitter = new EventEmitter(config.agentDependencies, new Subject()) didRepository = new DidRepository(storageService, eventEmitter) @@ -46,8 +48,7 @@ describe('peer dids', () => { [InjectionSymbols.StorageService, storageService], ], }) - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - await wallet.createAndOpen(config.walletConfig!) + await wallet.createAndOpen(config.walletConfig) didResolverService = new DidResolverService( config.logger, @@ -62,9 +63,12 @@ describe('peer dids', () => { test('create a peer did method 1 document from ed25519 keys with a service', async () => { // The following scenario show how we could create a key and create a did document from it for DID Exchange - const ed25519Key = await wallet.createKey({ seed: 'astringoftotalin32characterslong', keyType: KeyType.Ed25519 }) + const ed25519Key = await wallet.createKey({ + privateKey: TypedArrayEncoder.fromString('astringoftotalin32characterslong'), + keyType: KeyType.Ed25519, + }) const mediatorEd25519Key = await wallet.createKey({ - seed: 'anotherstringof32characterslong1', + privateKey: TypedArrayEncoder.fromString('anotherstringof32characterslong1'), keyType: KeyType.Ed25519, }) diff --git a/packages/core/src/modules/dids/domain/DidDocument.ts b/packages/core/src/modules/dids/domain/DidDocument.ts index 5316933952..c4e14c9bfb 100644 --- a/packages/core/src/modules/dids/domain/DidDocument.ts +++ b/packages/core/src/modules/dids/domain/DidDocument.ts @@ -7,8 +7,8 @@ import { KeyType, Key } from '../../../crypto' import { JsonTransformer } from '../../../utils/JsonTransformer' import { IsStringOrStringArray } from '../../../utils/transformers' -import { getKeyDidMappingByVerificationMethod } from './key-type' -import { IndyAgentService, ServiceTransformer, DidCommV1Service } from './service' +import { getKeyFromVerificationMethod } from './key-type' +import { IndyAgentService, ServiceTransformer, DidCommV1Service, DidCommV2Service } from './service' import { VerificationMethodTransformer, VerificationMethod, IsStringOrVerificationMethod } from './verificationMethod' export type DidPurpose = @@ -169,8 +169,8 @@ export class DidDocument { * Get all DIDComm services ordered by priority descending. This means the highest * priority will be the first entry. */ - public get didCommServices(): Array { - const didCommServiceTypes = [IndyAgentService.type, DidCommV1Service.type] + public get didCommServices(): Array { + const didCommServiceTypes = [IndyAgentService.type, DidCommV1Service.type, DidCommV2Service.type] const services = (this.service?.filter((service) => didCommServiceTypes.includes(service.type)) ?? []) as Array< IndyAgentService | DidCommV1Service > @@ -210,7 +210,6 @@ export function keyReferenceToKey(didDocument: DidDocument, keyId: string) { // for didcomm. In the future we should update this to only be allowed for IndyAgent and DidCommV1 services // as didcomm v2 doesn't have this issue anymore const verificationMethod = didDocument.dereferenceKey(keyId, ['authentication', 'keyAgreement']) - const { getKeyFromVerificationMethod } = getKeyDidMappingByVerificationMethod(verificationMethod) const key = getKeyFromVerificationMethod(verificationMethod) return key @@ -249,3 +248,25 @@ export async function findVerificationMethodByKeyType( return null } + +export function getAuthenticationKeys(didDocument: DidDocument) { + return ( + didDocument.authentication?.map((authentication) => { + const verificationMethod = + typeof authentication === 'string' ? didDocument.dereferenceVerificationMethod(authentication) : authentication + const key = getKeyFromVerificationMethod(verificationMethod) + return key + }) ?? [] + ) +} + +export function getAgreementKeys(didDocument: DidDocument) { + return ( + didDocument.keyAgreement?.map((keyAgreement) => { + const verificationMethod = + typeof keyAgreement === 'string' ? didDocument.dereferenceVerificationMethod(keyAgreement) : keyAgreement + const key = getKeyFromVerificationMethod(verificationMethod) + return key + }) ?? [] + ) +} diff --git a/packages/core/src/modules/dids/domain/key-type/__tests__/jwk.test.ts b/packages/core/src/modules/dids/domain/key-type/__tests__/jwk.test.ts new file mode 100644 index 0000000000..aa186aef56 --- /dev/null +++ b/packages/core/src/modules/dids/domain/key-type/__tests__/jwk.test.ts @@ -0,0 +1,42 @@ +import { Key } from '../../../../../crypto/Key' +import { JsonTransformer } from '../../../../../utils' +import didKeyP256Fixture from '../../../__tests__/__fixtures__/didKeyP256.json' +import { VerificationMethod } from '../../verificationMethod' +import { VERIFICATION_METHOD_TYPE_JSON_WEB_KEY_2020 } from '../../verificationMethod/JsonWebKey2020' +import { keyDidJsonWebKey } from '../keyDidJsonWebKey' + +const TEST_P256_FINGERPRINT = 'zDnaerx9CtbPJ1q36T5Ln5wYt3MQYeGRG5ehnPAmxcf5mDZpv' +const TEST_P256_DID = `did:key:${TEST_P256_FINGERPRINT}` + +describe('keyDidJsonWebKey', () => { + it('should return a valid verification method', async () => { + const key = Key.fromFingerprint(TEST_P256_FINGERPRINT) + const verificationMethods = keyDidJsonWebKey.getVerificationMethods(TEST_P256_DID, key) + + expect(JsonTransformer.toJSON(verificationMethods)).toMatchObject([didKeyP256Fixture.verificationMethod[0]]) + }) + + it('supports no verification method type', () => { + expect(keyDidJsonWebKey.supportedVerificationMethodTypes).toMatchObject([ + VERIFICATION_METHOD_TYPE_JSON_WEB_KEY_2020, + ]) + }) + + it('returns key for JsonWebKey2020 verification method', () => { + const verificationMethod = JsonTransformer.fromJSON(didKeyP256Fixture.verificationMethod[0], VerificationMethod) + + const key = keyDidJsonWebKey.getKeyFromVerificationMethod(verificationMethod) + + expect(key.fingerprint).toBe(TEST_P256_FINGERPRINT) + }) + + it('throws an error if an invalid verification method is passed', () => { + const verificationMethod = JsonTransformer.fromJSON(didKeyP256Fixture.verificationMethod[0], VerificationMethod) + + verificationMethod.type = 'SomeRandomType' + + expect(() => keyDidJsonWebKey.getKeyFromVerificationMethod(verificationMethod)).toThrowError( + 'Invalid verification method passed' + ) + }) +}) diff --git a/packages/core/src/modules/dids/domain/key-type/bls12381g1.ts b/packages/core/src/modules/dids/domain/key-type/bls12381g1.ts index 6ac241f5d9..aee5774210 100644 --- a/packages/core/src/modules/dids/domain/key-type/bls12381g1.ts +++ b/packages/core/src/modules/dids/domain/key-type/bls12381g1.ts @@ -1,5 +1,5 @@ -import type { VerificationMethod } from '../verificationMethod' import type { KeyDidMapping } from './keyDidMapping' +import type { VerificationMethod } from '../verificationMethod' import { KeyType } from '../../../../crypto' import { Key } from '../../../../crypto/Key' diff --git a/packages/core/src/modules/dids/domain/key-type/bls12381g2.ts b/packages/core/src/modules/dids/domain/key-type/bls12381g2.ts index f7cc4b2a6f..e980f9d142 100644 --- a/packages/core/src/modules/dids/domain/key-type/bls12381g2.ts +++ b/packages/core/src/modules/dids/domain/key-type/bls12381g2.ts @@ -1,5 +1,5 @@ -import type { VerificationMethod } from '../verificationMethod' import type { KeyDidMapping } from './keyDidMapping' +import type { VerificationMethod } from '../verificationMethod' import { KeyType } from '../../../../crypto' import { Key } from '../../../../crypto/Key' diff --git a/packages/core/src/modules/dids/domain/key-type/ed25519.ts b/packages/core/src/modules/dids/domain/key-type/ed25519.ts index 4098d230b5..4d96a43e6c 100644 --- a/packages/core/src/modules/dids/domain/key-type/ed25519.ts +++ b/packages/core/src/modules/dids/domain/key-type/ed25519.ts @@ -1,10 +1,9 @@ -import type { VerificationMethod } from '../verificationMethod' import type { KeyDidMapping } from './keyDidMapping' +import type { VerificationMethod } from '../verificationMethod' import { convertPublicKeyToX25519 } from '@stablelib/ed25519' -import { KeyType } from '../../../../crypto' -import { Key } from '../../../../crypto/Key' +import { Key, KeyType } from '../../../../crypto' export const VERIFICATION_METHOD_TYPE_ED25519_VERIFICATION_KEY_2018 = 'Ed25519VerificationKey2018' export const VERIFICATION_METHOD_TYPE_ED25519_VERIFICATION_KEY_2020 = 'Ed25519VerificationKey2020' @@ -33,6 +32,7 @@ export const keyDidEd25519: KeyDidMapping = { ) { return Key.fromPublicKeyBase58(verificationMethod.publicKeyBase58, KeyType.Ed25519) } + if ( verificationMethod.type === VERIFICATION_METHOD_TYPE_ED25519_VERIFICATION_KEY_2020 && verificationMethod.publicKeyMultibase diff --git a/packages/core/src/modules/dids/domain/key-type/index.ts b/packages/core/src/modules/dids/domain/key-type/index.ts index edb319be90..29a61e8d0d 100644 --- a/packages/core/src/modules/dids/domain/key-type/index.ts +++ b/packages/core/src/modules/dids/domain/key-type/index.ts @@ -1,4 +1,4 @@ -export { getKeyDidMappingByKeyType, getKeyDidMappingByVerificationMethod } from './keyDidMapping' +export { getKeyDidMappingByKeyType, getKeyFromVerificationMethod } from './keyDidMapping' export * from './bls12381g2' export * from './bls12381g1' diff --git a/packages/core/src/modules/dids/domain/key-type/keyDidJsonWebKey.ts b/packages/core/src/modules/dids/domain/key-type/keyDidJsonWebKey.ts new file mode 100644 index 0000000000..3dd7c19d05 --- /dev/null +++ b/packages/core/src/modules/dids/domain/key-type/keyDidJsonWebKey.ts @@ -0,0 +1,20 @@ +import type { KeyDidMapping } from './keyDidMapping' +import type { VerificationMethod } from '../verificationMethod' + +import { Key } from '../../../../crypto' +import { AriesFrameworkError } from '../../../../error' +import { getJsonWebKey2020VerificationMethod } from '../verificationMethod' +import { VERIFICATION_METHOD_TYPE_JSON_WEB_KEY_2020, isJsonWebKey2020 } from '../verificationMethod/JsonWebKey2020' + +export const keyDidJsonWebKey: KeyDidMapping = { + supportedVerificationMethodTypes: [VERIFICATION_METHOD_TYPE_JSON_WEB_KEY_2020], + getVerificationMethods: (did, key) => [getJsonWebKey2020VerificationMethod({ did, key })], + + getKeyFromVerificationMethod: (verificationMethod: VerificationMethod) => { + if (!isJsonWebKey2020(verificationMethod) || !verificationMethod.publicKeyJwk) { + throw new AriesFrameworkError('Invalid verification method passed') + } + + return Key.fromJwk(verificationMethod.publicKeyJwk) + }, +} diff --git a/packages/core/src/modules/dids/domain/key-type/keyDidMapping.ts b/packages/core/src/modules/dids/domain/key-type/keyDidMapping.ts index 713817d1bb..1449892e9b 100644 --- a/packages/core/src/modules/dids/domain/key-type/keyDidMapping.ts +++ b/packages/core/src/modules/dids/domain/key-type/keyDidMapping.ts @@ -1,12 +1,15 @@ -import type { Key } from '../../../../crypto/Key' import type { VerificationMethod } from '../verificationMethod' import { KeyType } from '../../../../crypto' +import { Key } from '../../../../crypto/Key' +import { AriesFrameworkError } from '../../../../error' +import { isJsonWebKey2020, VERIFICATION_METHOD_TYPE_JSON_WEB_KEY_2020 } from '../verificationMethod/JsonWebKey2020' import { keyDidBls12381g1 } from './bls12381g1' import { keyDidBls12381g1g2 } from './bls12381g1g2' import { keyDidBls12381g2 } from './bls12381g2' import { keyDidEd25519 } from './ed25519' +import { keyDidJsonWebKey } from './keyDidJsonWebKey' import { keyDidX25519 } from './x25519' export interface KeyDidMapping { @@ -22,6 +25,9 @@ const keyDidMapping: Record = { [KeyType.Bls12381g1]: keyDidBls12381g1, [KeyType.Bls12381g2]: keyDidBls12381g2, [KeyType.Bls12381g1g2]: keyDidBls12381g1g2, + [KeyType.P256]: keyDidJsonWebKey, + [KeyType.P384]: keyDidJsonWebKey, + [KeyType.P521]: keyDidJsonWebKey, } /** @@ -60,12 +66,23 @@ export function getKeyDidMappingByKeyType(keyType: KeyType) { return keyDid } -export function getKeyDidMappingByVerificationMethod(verificationMethod: VerificationMethod) { - const keyDid = verificationMethodKeyDidMapping[verificationMethod.type] +export function getKeyFromVerificationMethod(verificationMethod: VerificationMethod) { + // This is a special verification method, as it supports basically all key types. + if (isJsonWebKey2020(verificationMethod)) { + // TODO: move this validation to another place + if (!verificationMethod.publicKeyJwk) { + throw new AriesFrameworkError( + `Missing publicKeyJwk on verification method with type ${VERIFICATION_METHOD_TYPE_JSON_WEB_KEY_2020}` + ) + } + + return Key.fromJwk(verificationMethod.publicKeyJwk) + } + const keyDid = verificationMethodKeyDidMapping[verificationMethod.type] if (!keyDid) { throw new Error(`Unsupported key did from verification method type '${verificationMethod.type}'`) } - return keyDid + return keyDid.getKeyFromVerificationMethod(verificationMethod) } diff --git a/packages/core/src/modules/dids/domain/key-type/x25519.ts b/packages/core/src/modules/dids/domain/key-type/x25519.ts index 5ce7ff0683..399029928e 100644 --- a/packages/core/src/modules/dids/domain/key-type/x25519.ts +++ b/packages/core/src/modules/dids/domain/key-type/x25519.ts @@ -1,5 +1,5 @@ -import type { VerificationMethod } from '../verificationMethod' import type { KeyDidMapping } from './keyDidMapping' +import type { VerificationMethod } from '../verificationMethod' import { KeyType } from '../../../../crypto' import { Key } from '../../../../crypto/Key' diff --git a/packages/core/src/modules/dids/domain/keyDidDocument.ts b/packages/core/src/modules/dids/domain/keyDidDocument.ts index 537cb97d3d..af97546202 100644 --- a/packages/core/src/modules/dids/domain/keyDidDocument.ts +++ b/packages/core/src/modules/dids/domain/keyDidDocument.ts @@ -1,7 +1,9 @@ +import type { DidDocument } from './DidDocument' import type { VerificationMethod } from './verificationMethod/VerificationMethod' import { KeyType, Key } from '../../../crypto' -import { SECURITY_CONTEXT_BBS_URL, SECURITY_X25519_CONTEXT_URL } from '../../vc/constants' +import { AriesFrameworkError } from '../../../error' +import { SECURITY_CONTEXT_BBS_URL, SECURITY_JWS_CONTEXT_URL, SECURITY_X25519_CONTEXT_URL } from '../../vc/constants' import { ED25519_SUITE_CONTEXT_URL_2018 } from '../../vc/signature-suites/ed25519/constants' import { DidDocumentBuilder } from './DidDocumentBuilder' @@ -10,13 +12,17 @@ import { getBls12381g1g2VerificationMethod } from './key-type/bls12381g1g2' import { getBls12381g2VerificationMethod } from './key-type/bls12381g2' import { convertPublicKeyToX25519, getEd25519VerificationMethod } from './key-type/ed25519' import { getX25519VerificationMethod } from './key-type/x25519' +import { getJsonWebKey2020VerificationMethod } from './verificationMethod' -const didDocumentKeyTypeMapping = { +const didDocumentKeyTypeMapping: Record DidDocument> = { [KeyType.Ed25519]: getEd25519DidDoc, [KeyType.X25519]: getX25519DidDoc, [KeyType.Bls12381g1]: getBls12381g1DidDoc, [KeyType.Bls12381g2]: getBls12381g2DidDoc, [KeyType.Bls12381g1g2]: getBls12381g1g2DidDoc, + [KeyType.P256]: getJsonWebKey2020DidDocument, + [KeyType.P384]: getJsonWebKey2020DidDocument, + [KeyType.P521]: getJsonWebKey2020DidDocument, } export function getDidDocumentForKey(did: string, key: Key) { @@ -54,6 +60,31 @@ function getBls12381g1g2DidDoc(did: string, key: Key) { return didDocumentBuilder.addContext(SECURITY_CONTEXT_BBS_URL).build() } +export function getJsonWebKey2020DidDocument(did: string, key: Key) { + const verificationMethod = getJsonWebKey2020VerificationMethod({ did, key }) + + const didDocumentBuilder = new DidDocumentBuilder(did) + didDocumentBuilder.addContext(SECURITY_JWS_CONTEXT_URL).addVerificationMethod(verificationMethod) + + if (!key.supportsEncrypting && !key.supportsSigning) { + throw new AriesFrameworkError('Key must support at least signing or encrypting') + } + + if (key.supportsSigning) { + didDocumentBuilder + .addAuthentication(verificationMethod.id) + .addAssertionMethod(verificationMethod.id) + .addCapabilityDelegation(verificationMethod.id) + .addCapabilityInvocation(verificationMethod.id) + } + + if (key.supportsEncrypting) { + didDocumentBuilder.addKeyAgreement(verificationMethod.id) + } + + return didDocumentBuilder.build() +} + function getEd25519DidDoc(did: string, key: Key) { const verificationMethod = getEd25519VerificationMethod({ id: `${did}#${key.fingerprint}`, key, controller: did }) diff --git a/packages/core/src/modules/dids/domain/verificationMethod/JsonWebKey2020.ts b/packages/core/src/modules/dids/domain/verificationMethod/JsonWebKey2020.ts new file mode 100644 index 0000000000..a007f1d1fc --- /dev/null +++ b/packages/core/src/modules/dids/domain/verificationMethod/JsonWebKey2020.ts @@ -0,0 +1,36 @@ +import type { VerificationMethod } from './VerificationMethod' +import type { Jwk } from '../../../../crypto' + +import { Key } from '../../../../crypto' + +export const VERIFICATION_METHOD_TYPE_JSON_WEB_KEY_2020 = 'JsonWebKey2020' + +type JwkOrKey = { jwk: Jwk; key?: never } | { key: Key; jwk?: never } +type GetJsonWebKey2020VerificationMethodOptions = { + did: string + + verificationMethodId?: string +} & JwkOrKey + +export function getJsonWebKey2020VerificationMethod({ + did, + key, + jwk, + verificationMethodId, +}: GetJsonWebKey2020VerificationMethodOptions) { + if (!verificationMethodId) { + const k = key ?? Key.fromJwk(jwk) + verificationMethodId = `${did}#${k.fingerprint}` + } + + return { + id: verificationMethodId, + type: VERIFICATION_METHOD_TYPE_JSON_WEB_KEY_2020, + controller: did, + publicKeyJwk: jwk ?? key.toJwk(), + } +} + +export function isJsonWebKey2020(verificationMethod: VerificationMethod) { + return verificationMethod.type === VERIFICATION_METHOD_TYPE_JSON_WEB_KEY_2020 +} diff --git a/packages/core/src/modules/dids/domain/verificationMethod/VerificationMethod.ts b/packages/core/src/modules/dids/domain/verificationMethod/VerificationMethod.ts index a86bd58978..632596fd57 100644 --- a/packages/core/src/modules/dids/domain/verificationMethod/VerificationMethod.ts +++ b/packages/core/src/modules/dids/domain/verificationMethod/VerificationMethod.ts @@ -1,3 +1,5 @@ +import type { Jwk } from '../../../../crypto' + import { IsString, IsOptional } from 'class-validator' export interface VerificationMethodOptions { @@ -6,7 +8,7 @@ export interface VerificationMethodOptions { controller: string publicKeyBase58?: string publicKeyBase64?: string - publicKeyJwk?: Record + publicKeyJwk?: Jwk publicKeyHex?: string publicKeyMultibase?: string publicKeyPem?: string @@ -48,8 +50,8 @@ export class VerificationMethod { @IsString() public publicKeyBase64?: string - // TODO: define JWK structure, we don't support JWK yet - public publicKeyJwk?: Record + // TODO: validation of JWK + public publicKeyJwk?: Jwk @IsOptional() @IsString() diff --git a/packages/core/src/modules/dids/domain/verificationMethod/index.ts b/packages/core/src/modules/dids/domain/verificationMethod/index.ts index 2bfdad4059..b263061277 100644 --- a/packages/core/src/modules/dids/domain/verificationMethod/index.ts +++ b/packages/core/src/modules/dids/domain/verificationMethod/index.ts @@ -1,4 +1,10 @@ +import { getJsonWebKey2020VerificationMethod } from './JsonWebKey2020' import { VerificationMethod } from './VerificationMethod' import { VerificationMethodTransformer, IsStringOrVerificationMethod } from './VerificationMethodTransformer' -export { VerificationMethod, VerificationMethodTransformer, IsStringOrVerificationMethod } +export { + VerificationMethod, + VerificationMethodTransformer, + IsStringOrVerificationMethod, + getJsonWebKey2020VerificationMethod, +} diff --git a/packages/core/src/modules/dids/index.ts b/packages/core/src/modules/dids/index.ts index 9ad363c0a2..5f1677eb88 100644 --- a/packages/core/src/modules/dids/index.ts +++ b/packages/core/src/modules/dids/index.ts @@ -1,6 +1,7 @@ export * from './types' export * from './domain' export * from './DidsApi' +export * from './DidsApiOptions' export * from './repository' export * from './services' export * from './DidsModule' diff --git a/packages/core/src/modules/dids/methods/index.ts b/packages/core/src/modules/dids/methods/index.ts index ebacc7f2c2..4faee9c44b 100644 --- a/packages/core/src/modules/dids/methods/index.ts +++ b/packages/core/src/modules/dids/methods/index.ts @@ -1,4 +1,4 @@ export * from './key' export * from './peer' -export * from './sov' export * from './web' +export * from './jwk' diff --git a/packages/core/src/modules/dids/methods/jwk/DidJwk.ts b/packages/core/src/modules/dids/methods/jwk/DidJwk.ts new file mode 100644 index 0000000000..f83b956e24 --- /dev/null +++ b/packages/core/src/modules/dids/methods/jwk/DidJwk.ts @@ -0,0 +1,55 @@ +import type { Jwk } from '../../../../crypto' + +import { Key } from '../../../../crypto/Key' +import { JsonEncoder } from '../../../../utils' +import { parseDid } from '../../domain/parse' + +import { getDidJwkDocument } from './didJwkDidDocument' + +export class DidJwk { + public readonly did: string + + private constructor(did: string) { + this.did = did + } + + public get allowsEncrypting() { + return this.jwk.use === 'enc' || this.key.supportsEncrypting + } + + public get allowsSigning() { + return this.jwk.use === 'sig' || this.key.supportsSigning + } + + public static fromDid(did: string) { + // We create a `Key` instance form the jwk, as that validates the jwk + const parsed = parseDid(did) + const jwk = JsonEncoder.fromBase64(parsed.id) as Jwk + Key.fromJwk(jwk) + + return new DidJwk(did) + } + + public static fromJwk(jwk: Jwk) { + // We create a `Key` instance form the jwk, as that validates the jwk + Key.fromJwk(jwk) + const did = `did:jwk:${JsonEncoder.toBase64URL(jwk)}` + + return new DidJwk(did) + } + + public get key() { + return Key.fromJwk(this.jwk) + } + + public get jwk() { + const parsed = parseDid(this.did) + const jwk = JsonEncoder.fromBase64(parsed.id) as Jwk + + return jwk + } + + public get didDocument() { + return getDidJwkDocument(this) + } +} diff --git a/packages/core/src/modules/dids/methods/jwk/JwkDidRegistrar.ts b/packages/core/src/modules/dids/methods/jwk/JwkDidRegistrar.ts new file mode 100644 index 0000000000..c5fc9b01e0 --- /dev/null +++ b/packages/core/src/modules/dids/methods/jwk/JwkDidRegistrar.ts @@ -0,0 +1,118 @@ +import type { AgentContext } from '../../../../agent' +import type { KeyType } from '../../../../crypto' +import type { Buffer } from '../../../../utils' +import type { DidRegistrar } from '../../domain/DidRegistrar' +import type { DidCreateOptions, DidCreateResult, DidDeactivateResult, DidUpdateResult } from '../../types' + +import { DidDocumentRole } from '../../domain/DidDocumentRole' +import { DidRepository, DidRecord } from '../../repository' + +import { DidJwk } from './DidJwk' + +export class JwkDidRegistrar implements DidRegistrar { + public readonly supportedMethods = ['jwk'] + + public async create(agentContext: AgentContext, options: JwkDidCreateOptions): Promise { + const didRepository = agentContext.dependencyManager.resolve(DidRepository) + + const keyType = options.options.keyType + const seed = options.secret?.seed + const privateKey = options.secret?.privateKey + + if (!keyType) { + return { + didDocumentMetadata: {}, + didRegistrationMetadata: {}, + didState: { + state: 'failed', + reason: 'Missing key type', + }, + } + } + + try { + const key = await agentContext.wallet.createKey({ + keyType, + seed, + privateKey, + }) + + const didJwk = DidJwk.fromJwk(key.toJwk()) + + // Save the did so we know we created it and can issue with it + const didRecord = new DidRecord({ + did: didJwk.did, + role: DidDocumentRole.Created, + }) + await didRepository.save(agentContext, didRecord) + + return { + didDocumentMetadata: {}, + didRegistrationMetadata: {}, + didState: { + state: 'finished', + did: didJwk.did, + didDocument: didJwk.didDocument, + secret: { + // FIXME: the uni-registrar creates the seed in the registrar method + // if it doesn't exist so the seed can always be returned. Currently + // we can only return it if the seed was passed in by the user. Once + // we have a secure method for generating seeds we should use the same + // approach + seed: options.secret?.seed, + privateKey: options.secret?.privateKey, + }, + }, + } + } catch (error) { + return { + didDocumentMetadata: {}, + didRegistrationMetadata: {}, + didState: { + state: 'failed', + reason: `unknownError: ${error.message}`, + }, + } + } + } + + public async update(): Promise { + return { + didDocumentMetadata: {}, + didRegistrationMetadata: {}, + didState: { + state: 'failed', + reason: `notSupported: cannot update did:jwk did`, + }, + } + } + + public async deactivate(): Promise { + return { + didDocumentMetadata: {}, + didRegistrationMetadata: {}, + didState: { + state: 'failed', + reason: `notSupported: cannot deactivate did:jwk did`, + }, + } + } +} + +export interface JwkDidCreateOptions extends DidCreateOptions { + method: 'jwk' + // For now we don't support creating a did:jwk with a did or did document + did?: never + didDocument?: never + options: { + keyType: KeyType + } + secret?: { + seed?: Buffer + privateKey?: Buffer + } +} + +// Update and Deactivate not supported for did:jwk +export type JwkDidUpdateOptions = never +export type JwkDidDeactivateOptions = never diff --git a/packages/core/src/modules/dids/methods/jwk/JwkDidResolver.ts b/packages/core/src/modules/dids/methods/jwk/JwkDidResolver.ts new file mode 100644 index 0000000000..8207814895 --- /dev/null +++ b/packages/core/src/modules/dids/methods/jwk/JwkDidResolver.ts @@ -0,0 +1,32 @@ +import type { AgentContext } from '../../../../agent' +import type { DidResolver } from '../../domain/DidResolver' +import type { DidResolutionResult } from '../../types' + +import { DidJwk } from './DidJwk' + +export class JwkDidResolver implements DidResolver { + public readonly supportedMethods = ['jwk'] + + public async resolve(agentContext: AgentContext, did: string): Promise { + const didDocumentMetadata = {} + + try { + const didDocument = DidJwk.fromDid(did).didDocument + + return { + didDocument, + didDocumentMetadata, + didResolutionMetadata: { contentType: 'application/did+ld+json' }, + } + } catch (error) { + return { + didDocument: null, + didDocumentMetadata, + didResolutionMetadata: { + error: 'notFound', + message: `resolver_error: Unable to resolve did '${did}': ${error}`, + }, + } + } + } +} diff --git a/packages/core/src/modules/dids/methods/jwk/__tests__/DidJwk.test.ts b/packages/core/src/modules/dids/methods/jwk/__tests__/DidJwk.test.ts new file mode 100644 index 0000000000..406abe86a8 --- /dev/null +++ b/packages/core/src/modules/dids/methods/jwk/__tests__/DidJwk.test.ts @@ -0,0 +1,23 @@ +import { DidJwk } from '../DidJwk' + +import { p256DidJwkEyJjcnYi0iFixture } from './__fixtures__/p256DidJwkEyJjcnYi0i' +import { x25519DidJwkEyJrdHkiOiJFixture } from './__fixtures__/x25519DidJwkEyJrdHkiOiJ' + +describe('DidJwk', () => { + it('creates a DidJwk instance from a did', async () => { + const documentTypes = [p256DidJwkEyJjcnYi0iFixture, x25519DidJwkEyJrdHkiOiJFixture] + + for (const documentType of documentTypes) { + const didKey = DidJwk.fromDid(documentType.id) + + expect(didKey.didDocument.toJSON()).toMatchObject(documentType) + } + }) + + it('creates a DidJwk instance from a jwk instance', async () => { + const didJwk = DidJwk.fromJwk(p256DidJwkEyJjcnYi0iFixture.verificationMethod[0].publicKeyJwk) + + expect(didJwk.did).toBe(p256DidJwkEyJjcnYi0iFixture.id) + expect(didJwk.didDocument.toJSON()).toMatchObject(p256DidJwkEyJjcnYi0iFixture) + }) +}) diff --git a/packages/core/src/modules/dids/methods/jwk/__tests__/JwkDidRegistrar.test.ts b/packages/core/src/modules/dids/methods/jwk/__tests__/JwkDidRegistrar.test.ts new file mode 100644 index 0000000000..dc4f246b99 --- /dev/null +++ b/packages/core/src/modules/dids/methods/jwk/__tests__/JwkDidRegistrar.test.ts @@ -0,0 +1,193 @@ +import type { Wallet } from '../../../../../wallet' + +import { getAgentContext, mockFunction } from '../../../../../../tests/helpers' +import { KeyType } from '../../../../../crypto' +import { Key } from '../../../../../crypto/Key' +import { TypedArrayEncoder } from '../../../../../utils' +import { JsonTransformer } from '../../../../../utils/JsonTransformer' +import { WalletError } from '../../../../../wallet/error' +import { DidDocumentRole } from '../../../domain/DidDocumentRole' +import { DidRepository } from '../../../repository/DidRepository' +import { JwkDidRegistrar } from '../JwkDidRegistrar' + +jest.mock('../../../repository/DidRepository') +const DidRepositoryMock = DidRepository as jest.Mock + +const walletMock = { + createKey: jest.fn(() => + Key.fromJwk({ + crv: 'P-256', + kty: 'EC', + x: 'acbIQiuMs3i8_uszEjJ2tpTtRM4EU3yz91PH6CdH2V0', + y: '_KcyLj9vWMptnmKtm46GqDz8wf74I5LKgrl2GzH3nSE', + }) + ), +} as unknown as Wallet + +const didRepositoryMock = new DidRepositoryMock() +const jwkDidRegistrar = new JwkDidRegistrar() + +const agentContext = getAgentContext({ + wallet: walletMock, + registerInstances: [[DidRepository, didRepositoryMock]], +}) + +describe('DidRegistrar', () => { + afterEach(() => { + jest.clearAllMocks() + }) + + describe('JwkDidRegistrar', () => { + it('should correctly create a did:jwk document using P256 key type', async () => { + const privateKey = TypedArrayEncoder.fromString('96213c3d7fc8d4d6754c712fd969598e') + + const result = await jwkDidRegistrar.create(agentContext, { + method: 'jwk', + options: { + keyType: KeyType.P256, + }, + secret: { + privateKey, + }, + }) + + expect(JsonTransformer.toJSON(result)).toMatchObject({ + didDocumentMetadata: {}, + didRegistrationMetadata: {}, + didState: { + state: 'finished', + did: 'did:jwk:eyJrdHkiOiJFQyIsImNydiI6IlAtMjU2IiwieCI6ImFjYklRaXVNczNpOF91c3pFakoydHBUdFJNNEVVM3l6OTFQSDZDZEgyVjAiLCJ5IjoiX0tjeUxqOXZXTXB0bm1LdG00NkdxRHo4d2Y3NEk1TEtncmwyR3pIM25TRSJ9', + didDocument: { + '@context': ['https://w3id.org/did/v1', 'https://w3id.org/security/suites/jws-2020/v1'], + id: 'did:jwk:eyJrdHkiOiJFQyIsImNydiI6IlAtMjU2IiwieCI6ImFjYklRaXVNczNpOF91c3pFakoydHBUdFJNNEVVM3l6OTFQSDZDZEgyVjAiLCJ5IjoiX0tjeUxqOXZXTXB0bm1LdG00NkdxRHo4d2Y3NEk1TEtncmwyR3pIM25TRSJ9', + verificationMethod: [ + { + id: 'did:jwk:eyJrdHkiOiJFQyIsImNydiI6IlAtMjU2IiwieCI6ImFjYklRaXVNczNpOF91c3pFakoydHBUdFJNNEVVM3l6OTFQSDZDZEgyVjAiLCJ5IjoiX0tjeUxqOXZXTXB0bm1LdG00NkdxRHo4d2Y3NEk1TEtncmwyR3pIM25TRSJ9#0', + type: 'JsonWebKey2020', + controller: + 'did:jwk:eyJrdHkiOiJFQyIsImNydiI6IlAtMjU2IiwieCI6ImFjYklRaXVNczNpOF91c3pFakoydHBUdFJNNEVVM3l6OTFQSDZDZEgyVjAiLCJ5IjoiX0tjeUxqOXZXTXB0bm1LdG00NkdxRHo4d2Y3NEk1TEtncmwyR3pIM25TRSJ9', + publicKeyJwk: { + crv: 'P-256', + kty: 'EC', + x: 'acbIQiuMs3i8_uszEjJ2tpTtRM4EU3yz91PH6CdH2V0', + y: '_KcyLj9vWMptnmKtm46GqDz8wf74I5LKgrl2GzH3nSE', + }, + }, + ], + assertionMethod: [ + 'did:jwk:eyJrdHkiOiJFQyIsImNydiI6IlAtMjU2IiwieCI6ImFjYklRaXVNczNpOF91c3pFakoydHBUdFJNNEVVM3l6OTFQSDZDZEgyVjAiLCJ5IjoiX0tjeUxqOXZXTXB0bm1LdG00NkdxRHo4d2Y3NEk1TEtncmwyR3pIM25TRSJ9#0', + ], + authentication: [ + 'did:jwk:eyJrdHkiOiJFQyIsImNydiI6IlAtMjU2IiwieCI6ImFjYklRaXVNczNpOF91c3pFakoydHBUdFJNNEVVM3l6OTFQSDZDZEgyVjAiLCJ5IjoiX0tjeUxqOXZXTXB0bm1LdG00NkdxRHo4d2Y3NEk1TEtncmwyR3pIM25TRSJ9#0', + ], + capabilityInvocation: [ + 'did:jwk:eyJrdHkiOiJFQyIsImNydiI6IlAtMjU2IiwieCI6ImFjYklRaXVNczNpOF91c3pFakoydHBUdFJNNEVVM3l6OTFQSDZDZEgyVjAiLCJ5IjoiX0tjeUxqOXZXTXB0bm1LdG00NkdxRHo4d2Y3NEk1TEtncmwyR3pIM25TRSJ9#0', + ], + capabilityDelegation: [ + 'did:jwk:eyJrdHkiOiJFQyIsImNydiI6IlAtMjU2IiwieCI6ImFjYklRaXVNczNpOF91c3pFakoydHBUdFJNNEVVM3l6OTFQSDZDZEgyVjAiLCJ5IjoiX0tjeUxqOXZXTXB0bm1LdG00NkdxRHo4d2Y3NEk1TEtncmwyR3pIM25TRSJ9#0', + ], + keyAgreement: [ + 'did:jwk:eyJrdHkiOiJFQyIsImNydiI6IlAtMjU2IiwieCI6ImFjYklRaXVNczNpOF91c3pFakoydHBUdFJNNEVVM3l6OTFQSDZDZEgyVjAiLCJ5IjoiX0tjeUxqOXZXTXB0bm1LdG00NkdxRHo4d2Y3NEk1TEtncmwyR3pIM25TRSJ9#0', + ], + }, + secret: { + privateKey, + }, + }, + }) + + expect(walletMock.createKey).toHaveBeenCalledWith({ keyType: KeyType.P256, privateKey }) + }) + + it('should return an error state if no key type is provided', async () => { + const result = await jwkDidRegistrar.create(agentContext, { + method: 'jwk', + // @ts-expect-error - key type is required in interface + options: {}, + }) + + expect(JsonTransformer.toJSON(result)).toMatchObject({ + didDocumentMetadata: {}, + didRegistrationMetadata: {}, + didState: { + state: 'failed', + reason: 'Missing key type', + }, + }) + }) + + it('should return an error state if a key creation error is thrown', async () => { + mockFunction(walletMock.createKey).mockRejectedValueOnce(new WalletError('Invalid private key provided')) + const result = await jwkDidRegistrar.create(agentContext, { + method: 'jwk', + options: { + keyType: KeyType.P256, + }, + secret: { + privateKey: TypedArrayEncoder.fromString('invalid'), + }, + }) + + expect(JsonTransformer.toJSON(result)).toMatchObject({ + didDocumentMetadata: {}, + didRegistrationMetadata: {}, + didState: { + state: 'failed', + reason: expect.stringContaining('Invalid private key provided'), + }, + }) + }) + + it('should store the did document', async () => { + const privateKey = TypedArrayEncoder.fromString('96213c3d7fc8d4d6754c712fd969598e') + const did = + 'did:jwk:eyJrdHkiOiJFQyIsImNydiI6IlAtMjU2IiwieCI6ImFjYklRaXVNczNpOF91c3pFakoydHBUdFJNNEVVM3l6OTFQSDZDZEgyVjAiLCJ5IjoiX0tjeUxqOXZXTXB0bm1LdG00NkdxRHo4d2Y3NEk1TEtncmwyR3pIM25TRSJ9' + + await jwkDidRegistrar.create(agentContext, { + method: 'jwk', + + options: { + keyType: KeyType.P256, + }, + secret: { + privateKey, + }, + }) + + expect(didRepositoryMock.save).toHaveBeenCalledTimes(1) + const [, didRecord] = mockFunction(didRepositoryMock.save).mock.calls[0] + + expect(didRecord).toMatchObject({ + did, + role: DidDocumentRole.Created, + didDocument: undefined, + }) + }) + + it('should return an error state when calling update', async () => { + const result = await jwkDidRegistrar.update() + + expect(result).toEqual({ + didDocumentMetadata: {}, + didRegistrationMetadata: {}, + didState: { + state: 'failed', + reason: `notSupported: cannot update did:jwk did`, + }, + }) + }) + + it('should return an error state when calling deactivate', async () => { + const result = await jwkDidRegistrar.deactivate() + + expect(result).toEqual({ + didDocumentMetadata: {}, + didRegistrationMetadata: {}, + didState: { + state: 'failed', + reason: `notSupported: cannot deactivate did:jwk did`, + }, + }) + }) + }) +}) diff --git a/packages/core/src/modules/dids/methods/jwk/__tests__/JwkDidResolver.test.ts b/packages/core/src/modules/dids/methods/jwk/__tests__/JwkDidResolver.test.ts new file mode 100644 index 0000000000..28dc34d497 --- /dev/null +++ b/packages/core/src/modules/dids/methods/jwk/__tests__/JwkDidResolver.test.ts @@ -0,0 +1,34 @@ +import type { AgentContext } from '../../../../../agent' + +import { getAgentContext } from '../../../../../../tests/helpers' +import { JsonTransformer } from '../../../../../utils/JsonTransformer' +import { DidJwk } from '../DidJwk' +import { JwkDidResolver } from '../JwkDidResolver' + +import { p256DidJwkEyJjcnYi0iFixture } from './__fixtures__/p256DidJwkEyJjcnYi0i' + +describe('DidResolver', () => { + describe('JwkDidResolver', () => { + let keyDidResolver: JwkDidResolver + let agentContext: AgentContext + + beforeEach(() => { + keyDidResolver = new JwkDidResolver() + agentContext = getAgentContext() + }) + + it('should correctly resolve a did:jwk document', async () => { + const fromDidSpy = jest.spyOn(DidJwk, 'fromDid') + const result = await keyDidResolver.resolve(agentContext, p256DidJwkEyJjcnYi0iFixture.id) + + expect(JsonTransformer.toJSON(result)).toMatchObject({ + didDocument: p256DidJwkEyJjcnYi0iFixture, + didDocumentMetadata: {}, + didResolutionMetadata: { contentType: 'application/did+ld+json' }, + }) + expect(result.didDocument) + expect(fromDidSpy).toHaveBeenCalledTimes(1) + expect(fromDidSpy).toHaveBeenCalledWith(p256DidJwkEyJjcnYi0iFixture.id) + }) + }) +}) diff --git a/packages/core/src/modules/dids/methods/jwk/__tests__/__fixtures__/p256DidJwkEyJjcnYi0i.ts b/packages/core/src/modules/dids/methods/jwk/__tests__/__fixtures__/p256DidJwkEyJjcnYi0i.ts new file mode 100644 index 0000000000..d086154f38 --- /dev/null +++ b/packages/core/src/modules/dids/methods/jwk/__tests__/__fixtures__/p256DidJwkEyJjcnYi0i.ts @@ -0,0 +1,33 @@ +export const p256DidJwkEyJjcnYi0iFixture = { + '@context': ['https://w3id.org/did/v1', 'https://w3id.org/security/suites/jws-2020/v1'], + id: 'did:jwk:eyJjcnYiOiJQLTI1NiIsImt0eSI6IkVDIiwieCI6ImFjYklRaXVNczNpOF91c3pFakoydHBUdFJNNEVVM3l6OTFQSDZDZEgyVjAiLCJ5IjoiX0tjeUxqOXZXTXB0bm1LdG00NkdxRHo4d2Y3NEk1TEtncmwyR3pIM25TRSJ9', + verificationMethod: [ + { + id: 'did:jwk:eyJjcnYiOiJQLTI1NiIsImt0eSI6IkVDIiwieCI6ImFjYklRaXVNczNpOF91c3pFakoydHBUdFJNNEVVM3l6OTFQSDZDZEgyVjAiLCJ5IjoiX0tjeUxqOXZXTXB0bm1LdG00NkdxRHo4d2Y3NEk1TEtncmwyR3pIM25TRSJ9#0', + type: 'JsonWebKey2020', + controller: + 'did:jwk:eyJjcnYiOiJQLTI1NiIsImt0eSI6IkVDIiwieCI6ImFjYklRaXVNczNpOF91c3pFakoydHBUdFJNNEVVM3l6OTFQSDZDZEgyVjAiLCJ5IjoiX0tjeUxqOXZXTXB0bm1LdG00NkdxRHo4d2Y3NEk1TEtncmwyR3pIM25TRSJ9', + publicKeyJwk: { + crv: 'P-256', + kty: 'EC', + x: 'acbIQiuMs3i8_uszEjJ2tpTtRM4EU3yz91PH6CdH2V0', + y: '_KcyLj9vWMptnmKtm46GqDz8wf74I5LKgrl2GzH3nSE', + }, + }, + ], + assertionMethod: [ + 'did:jwk:eyJjcnYiOiJQLTI1NiIsImt0eSI6IkVDIiwieCI6ImFjYklRaXVNczNpOF91c3pFakoydHBUdFJNNEVVM3l6OTFQSDZDZEgyVjAiLCJ5IjoiX0tjeUxqOXZXTXB0bm1LdG00NkdxRHo4d2Y3NEk1TEtncmwyR3pIM25TRSJ9#0', + ], + authentication: [ + 'did:jwk:eyJjcnYiOiJQLTI1NiIsImt0eSI6IkVDIiwieCI6ImFjYklRaXVNczNpOF91c3pFakoydHBUdFJNNEVVM3l6OTFQSDZDZEgyVjAiLCJ5IjoiX0tjeUxqOXZXTXB0bm1LdG00NkdxRHo4d2Y3NEk1TEtncmwyR3pIM25TRSJ9#0', + ], + capabilityInvocation: [ + 'did:jwk:eyJjcnYiOiJQLTI1NiIsImt0eSI6IkVDIiwieCI6ImFjYklRaXVNczNpOF91c3pFakoydHBUdFJNNEVVM3l6OTFQSDZDZEgyVjAiLCJ5IjoiX0tjeUxqOXZXTXB0bm1LdG00NkdxRHo4d2Y3NEk1TEtncmwyR3pIM25TRSJ9#0', + ], + capabilityDelegation: [ + 'did:jwk:eyJjcnYiOiJQLTI1NiIsImt0eSI6IkVDIiwieCI6ImFjYklRaXVNczNpOF91c3pFakoydHBUdFJNNEVVM3l6OTFQSDZDZEgyVjAiLCJ5IjoiX0tjeUxqOXZXTXB0bm1LdG00NkdxRHo4d2Y3NEk1TEtncmwyR3pIM25TRSJ9#0', + ], + keyAgreement: [ + 'did:jwk:eyJjcnYiOiJQLTI1NiIsImt0eSI6IkVDIiwieCI6ImFjYklRaXVNczNpOF91c3pFakoydHBUdFJNNEVVM3l6OTFQSDZDZEgyVjAiLCJ5IjoiX0tjeUxqOXZXTXB0bm1LdG00NkdxRHo4d2Y3NEk1TEtncmwyR3pIM25TRSJ9#0', + ], +} as const diff --git a/packages/core/src/modules/dids/methods/jwk/__tests__/__fixtures__/x25519DidJwkEyJrdHkiOiJ.ts b/packages/core/src/modules/dids/methods/jwk/__tests__/__fixtures__/x25519DidJwkEyJrdHkiOiJ.ts new file mode 100644 index 0000000000..dba397342f --- /dev/null +++ b/packages/core/src/modules/dids/methods/jwk/__tests__/__fixtures__/x25519DidJwkEyJrdHkiOiJ.ts @@ -0,0 +1,21 @@ +export const x25519DidJwkEyJrdHkiOiJFixture = { + '@context': ['https://w3id.org/did/v1', 'https://w3id.org/security/suites/jws-2020/v1'], + id: 'did:jwk:eyJrdHkiOiJPS1AiLCJjcnYiOiJYMjU1MTkiLCJ1c2UiOiJlbmMiLCJ4IjoiM3A3YmZYdDl3YlRUVzJIQzdPUTFOei1EUThoYmVHZE5yZngtRkctSUswOCJ9', + verificationMethod: [ + { + id: 'did:jwk:eyJrdHkiOiJPS1AiLCJjcnYiOiJYMjU1MTkiLCJ1c2UiOiJlbmMiLCJ4IjoiM3A3YmZYdDl3YlRUVzJIQzdPUTFOei1EUThoYmVHZE5yZngtRkctSUswOCJ9#0', + type: 'JsonWebKey2020', + controller: + 'did:jwk:eyJrdHkiOiJPS1AiLCJjcnYiOiJYMjU1MTkiLCJ1c2UiOiJlbmMiLCJ4IjoiM3A3YmZYdDl3YlRUVzJIQzdPUTFOei1EUThoYmVHZE5yZngtRkctSUswOCJ9', + publicKeyJwk: { + kty: 'OKP', + crv: 'X25519', + use: 'enc', + x: '3p7bfXt9wbTTW2HC7OQ1Nz-DQ8hbeGdNrfx-FG-IK08', + }, + }, + ], + keyAgreement: [ + 'did:jwk:eyJrdHkiOiJPS1AiLCJjcnYiOiJYMjU1MTkiLCJ1c2UiOiJlbmMiLCJ4IjoiM3A3YmZYdDl3YlRUVzJIQzdPUTFOei1EUThoYmVHZE5yZngtRkctSUswOCJ9#0', + ], +} as const diff --git a/packages/core/src/modules/dids/methods/jwk/didJwkDidDocument.ts b/packages/core/src/modules/dids/methods/jwk/didJwkDidDocument.ts new file mode 100644 index 0000000000..3906929375 --- /dev/null +++ b/packages/core/src/modules/dids/methods/jwk/didJwkDidDocument.ts @@ -0,0 +1,35 @@ +import type { DidJwk } from './DidJwk' + +import { AriesFrameworkError } from '../../../../error' +import { SECURITY_JWS_CONTEXT_URL } from '../../../vc/constants' +import { getJsonWebKey2020VerificationMethod, DidDocumentBuilder } from '../../domain' + +export function getDidJwkDocument(didJwk: DidJwk) { + if (!didJwk.allowsEncrypting && !didJwk.allowsSigning) { + throw new AriesFrameworkError('At least one of allowsSigning or allowsEncrypting must be enabled') + } + + const verificationMethod = getJsonWebKey2020VerificationMethod({ + did: didJwk.did, + jwk: didJwk.jwk, + verificationMethodId: `${didJwk.did}#0`, + }) + + const didDocumentBuilder = new DidDocumentBuilder(didJwk.did) + .addContext(SECURITY_JWS_CONTEXT_URL) + .addVerificationMethod(verificationMethod) + + if (didJwk.allowsSigning) { + didDocumentBuilder + .addAuthentication(verificationMethod.id) + .addAssertionMethod(verificationMethod.id) + .addCapabilityDelegation(verificationMethod.id) + .addCapabilityInvocation(verificationMethod.id) + } + + if (didJwk.allowsEncrypting) { + didDocumentBuilder.addKeyAgreement(verificationMethod.id) + } + + return didDocumentBuilder.build() +} diff --git a/packages/core/src/modules/dids/methods/jwk/index.ts b/packages/core/src/modules/dids/methods/jwk/index.ts new file mode 100644 index 0000000000..e377f85f95 --- /dev/null +++ b/packages/core/src/modules/dids/methods/jwk/index.ts @@ -0,0 +1,3 @@ +export { DidJwk } from './DidJwk' +export * from './JwkDidRegistrar' +export * from './JwkDidResolver' diff --git a/packages/core/src/modules/dids/methods/key/KeyDidRegistrar.ts b/packages/core/src/modules/dids/methods/key/KeyDidRegistrar.ts index 6a4def3cd2..d4c8c239c9 100644 --- a/packages/core/src/modules/dids/methods/key/KeyDidRegistrar.ts +++ b/packages/core/src/modules/dids/methods/key/KeyDidRegistrar.ts @@ -1,5 +1,6 @@ import type { AgentContext } from '../../../../agent' import type { KeyType } from '../../../../crypto' +import type { Buffer } from '../../../../utils' import type { DidRegistrar } from '../../domain/DidRegistrar' import type { DidCreateOptions, DidCreateResult, DidDeactivateResult, DidUpdateResult } from '../../types' @@ -16,6 +17,7 @@ export class KeyDidRegistrar implements DidRegistrar { const keyType = options.options.keyType const seed = options.secret?.seed + const privateKey = options.secret?.privateKey if (!keyType) { return { @@ -28,21 +30,11 @@ export class KeyDidRegistrar implements DidRegistrar { } } - if (seed && (typeof seed !== 'string' || seed.length !== 32)) { - return { - didDocumentMetadata: {}, - didRegistrationMetadata: {}, - didState: { - state: 'failed', - reason: 'Invalid seed provided', - }, - } - } - try { const key = await agentContext.wallet.createKey({ keyType, seed, + privateKey, }) const didKey = new DidKey(key) @@ -68,6 +60,7 @@ export class KeyDidRegistrar implements DidRegistrar { // we have a secure method for generating seeds we should use the same // approach seed: options.secret?.seed, + privateKey: options.secret?.privateKey, }, }, } @@ -115,7 +108,8 @@ export interface KeyDidCreateOptions extends DidCreateOptions { keyType: KeyType } secret?: { - seed?: string + seed?: Buffer + privateKey?: Buffer } } diff --git a/packages/core/src/modules/dids/methods/key/__tests__/DidKey.test.ts b/packages/core/src/modules/dids/methods/key/__tests__/DidKey.test.ts index bacfb3f1a9..a9a854cb1a 100644 --- a/packages/core/src/modules/dids/methods/key/__tests__/DidKey.test.ts +++ b/packages/core/src/modules/dids/methods/key/__tests__/DidKey.test.ts @@ -4,12 +4,24 @@ import didKeyBls12381g1 from '../../../__tests__/__fixtures__/didKeyBls12381g1.j import didKeyBls12381g1g2 from '../../../__tests__/__fixtures__/didKeyBls12381g1g2.json' import didKeyBls12381g2 from '../../../__tests__/__fixtures__/didKeyBls12381g2.json' import didKeyEd25519 from '../../../__tests__/__fixtures__/didKeyEd25519.json' +import didKeyP256 from '../../../__tests__/__fixtures__/didKeyP256.json' +import didKeyP384 from '../../../__tests__/__fixtures__/didKeyP384.json' +import didKeyP521 from '../../../__tests__/__fixtures__/didKeyP521.json' import didKeyX25519 from '../../../__tests__/__fixtures__/didKeyX25519.json' import { DidKey } from '../DidKey' describe('DidKey', () => { it('creates a DidKey instance from a did', async () => { - const documentTypes = [didKeyX25519, didKeyEd25519, didKeyBls12381g1, didKeyBls12381g2, didKeyBls12381g1g2] + const documentTypes = [ + didKeyX25519, + didKeyEd25519, + didKeyBls12381g1, + didKeyBls12381g2, + didKeyBls12381g1g2, + didKeyP256, + didKeyP384, + didKeyP521, + ] for (const documentType of documentTypes) { const didKey = DidKey.fromDid(documentType.id) diff --git a/packages/core/src/modules/dids/methods/key/__tests__/KeyDidRegistrar.test.ts b/packages/core/src/modules/dids/methods/key/__tests__/KeyDidRegistrar.test.ts index e859dcf795..d3bf481409 100644 --- a/packages/core/src/modules/dids/methods/key/__tests__/KeyDidRegistrar.test.ts +++ b/packages/core/src/modules/dids/methods/key/__tests__/KeyDidRegistrar.test.ts @@ -3,7 +3,9 @@ import type { Wallet } from '../../../../../wallet' import { getAgentContext, mockFunction } from '../../../../../../tests/helpers' import { KeyType } from '../../../../../crypto' import { Key } from '../../../../../crypto/Key' +import { TypedArrayEncoder } from '../../../../../utils' import { JsonTransformer } from '../../../../../utils/JsonTransformer' +import { WalletError } from '../../../../../wallet/error' import { DidDocumentRole } from '../../../domain/DidDocumentRole' import { DidRepository } from '../../../repository/DidRepository' import { KeyDidRegistrar } from '../KeyDidRegistrar' @@ -32,7 +34,7 @@ describe('DidRegistrar', () => { describe('KeyDidRegistrar', () => { it('should correctly create a did:key document using Ed25519 key type', async () => { - const seed = '96213c3d7fc8d4d6754c712fd969598e' + const privateKey = TypedArrayEncoder.fromString('96213c3d7fc8d4d6754c712fd969598e') const result = await keyDidRegistrar.create(agentContext, { method: 'key', @@ -40,7 +42,7 @@ describe('DidRegistrar', () => { keyType: KeyType.Ed25519, }, secret: { - seed, + privateKey, }, }) @@ -52,12 +54,12 @@ describe('DidRegistrar', () => { did: 'did:key:z6MksLeew51QS6Ca6tVKM56LQNbxCNVcLHv4xXj4jMkAhPWU', didDocument: didKeyz6MksLeFixture, secret: { - seed: '96213c3d7fc8d4d6754c712fd969598e', + privateKey, }, }, }) - expect(walletMock.createKey).toHaveBeenCalledWith({ keyType: KeyType.Ed25519, seed }) + expect(walletMock.createKey).toHaveBeenCalledWith({ keyType: KeyType.Ed25519, privateKey }) }) it('should return an error state if no key type is provided', async () => { @@ -77,15 +79,15 @@ describe('DidRegistrar', () => { }) }) - it('should return an error state if an invalid seed is provided', async () => { + it('should return an error state if a key creation error is thrown', async () => { + mockFunction(walletMock.createKey).mockRejectedValueOnce(new WalletError('Invalid private key provided')) const result = await keyDidRegistrar.create(agentContext, { method: 'key', - options: { keyType: KeyType.Ed25519, }, secret: { - seed: 'invalid', + privateKey: TypedArrayEncoder.fromString('invalid'), }, }) @@ -94,13 +96,13 @@ describe('DidRegistrar', () => { didRegistrationMetadata: {}, didState: { state: 'failed', - reason: 'Invalid seed provided', + reason: expect.stringContaining('Invalid private key provided'), }, }) }) it('should store the did document', async () => { - const seed = '96213c3d7fc8d4d6754c712fd969598e' + const privateKey = TypedArrayEncoder.fromString('96213c3d7fc8d4d6754c712fd969598e') const did = 'did:key:z6MksLeew51QS6Ca6tVKM56LQNbxCNVcLHv4xXj4jMkAhPWU' await keyDidRegistrar.create(agentContext, { @@ -110,7 +112,7 @@ describe('DidRegistrar', () => { keyType: KeyType.Ed25519, }, secret: { - seed, + privateKey, }, }) diff --git a/packages/core/src/modules/dids/methods/peer/PeerDidRegistrar.ts b/packages/core/src/modules/dids/methods/peer/PeerDidRegistrar.ts index 057ed96c9d..fcae22119e 100644 --- a/packages/core/src/modules/dids/methods/peer/PeerDidRegistrar.ts +++ b/packages/core/src/modules/dids/methods/peer/PeerDidRegistrar.ts @@ -1,5 +1,6 @@ import type { AgentContext } from '../../../../agent' import type { KeyType } from '../../../../crypto' +import type { Buffer } from '../../../../utils' import type { DidRegistrar } from '../../domain/DidRegistrar' import type { DidCreateOptions, DidCreateResult, DidDeactivateResult, DidUpdateResult } from '../../types' @@ -27,6 +28,7 @@ export class PeerDidRegistrar implements DidRegistrar { if (isPeerDidNumAlgo0CreateOptions(options)) { const keyType = options.options.keyType const seed = options.secret?.seed + const privateKey = options.secret?.privateKey if (!keyType) { return { @@ -39,20 +41,10 @@ export class PeerDidRegistrar implements DidRegistrar { } } - if (seed && (typeof seed !== 'string' || seed.length !== 32)) { - return { - didDocumentMetadata: {}, - didRegistrationMetadata: {}, - didState: { - state: 'failed', - reason: 'Invalid seed provided', - }, - } - } - const key = await agentContext.wallet.createKey({ keyType, seed, + privateKey, }) // TODO: validate did:peer document @@ -106,6 +98,7 @@ export class PeerDidRegistrar implements DidRegistrar { // we have a secure method for generating seeds we should use the same // approach seed: options.secret?.seed, + privateKey: options.secret?.privateKey, }, }, } @@ -170,7 +163,8 @@ export interface PeerDidNumAlgo0CreateOptions extends DidCreateOptions { numAlgo: PeerDidNumAlgo.InceptionKeyWithoutDoc } secret?: { - seed?: string + seed?: Buffer + privateKey?: Buffer } } diff --git a/packages/core/src/modules/dids/methods/peer/__tests__/PeerDidRegistrar.test.ts b/packages/core/src/modules/dids/methods/peer/__tests__/PeerDidRegistrar.test.ts index bb87c21854..4fcae60ba7 100644 --- a/packages/core/src/modules/dids/methods/peer/__tests__/PeerDidRegistrar.test.ts +++ b/packages/core/src/modules/dids/methods/peer/__tests__/PeerDidRegistrar.test.ts @@ -3,7 +3,9 @@ import type { Wallet } from '../../../../../wallet' import { getAgentContext, mockFunction } from '../../../../../../tests/helpers' import { KeyType } from '../../../../../crypto' import { Key } from '../../../../../crypto/Key' +import { TypedArrayEncoder } from '../../../../../utils' import { JsonTransformer } from '../../../../../utils/JsonTransformer' +import { WalletError } from '../../../../../wallet/error' import { DidCommV1Service, DidDocumentBuilder } from '../../../domain' import { DidDocumentRole } from '../../../domain/DidDocumentRole' import { getEd25519VerificationMethod } from '../../../domain/key-type/ed25519' @@ -32,7 +34,7 @@ describe('DidRegistrar', () => { describe('PeerDidRegistrar', () => { describe('did:peer:0', () => { it('should correctly create a did:peer:0 document using Ed25519 key type', async () => { - const seed = '96213c3d7fc8d4d6754c712fd969598e' + const privateKey = TypedArrayEncoder.fromString('96213c3d7fc8d4d6754c712fd969598e') const result = await peerDidRegistrar.create(agentContext, { method: 'peer', @@ -41,7 +43,7 @@ describe('DidRegistrar', () => { numAlgo: PeerDidNumAlgo.InceptionKeyWithoutDoc, }, secret: { - seed, + privateKey, }, }) @@ -53,7 +55,7 @@ describe('DidRegistrar', () => { did: 'did:peer:0z6MksLeew51QS6Ca6tVKM56LQNbxCNVcLHv4xXj4jMkAhPWU', didDocument: didPeer0z6MksLeFixture, secret: { - seed: '96213c3d7fc8d4d6754c712fd969598e', + privateKey, }, }, }) @@ -78,7 +80,9 @@ describe('DidRegistrar', () => { }) }) - it('should return an error state if an invalid seed is provided', async () => { + it('should return an error state if a key creation error is thrown', async () => { + mockFunction(walletMock.createKey).mockRejectedValueOnce(new WalletError('Invalid private key provided')) + const result = await peerDidRegistrar.create(agentContext, { method: 'peer', options: { @@ -86,7 +90,7 @@ describe('DidRegistrar', () => { numAlgo: PeerDidNumAlgo.InceptionKeyWithoutDoc, }, secret: { - seed: 'invalid', + privateKey: TypedArrayEncoder.fromString('invalid'), }, }) @@ -95,13 +99,13 @@ describe('DidRegistrar', () => { didRegistrationMetadata: {}, didState: { state: 'failed', - reason: 'Invalid seed provided', + reason: expect.stringContaining('Invalid private key provided'), }, }) }) it('should store the did without the did document', async () => { - const seed = '96213c3d7fc8d4d6754c712fd969598e' + const privateKey = TypedArrayEncoder.fromString('96213c3d7fc8d4d6754c712fd969598e') const did = 'did:peer:0z6MksLeew51QS6Ca6tVKM56LQNbxCNVcLHv4xXj4jMkAhPWU' await peerDidRegistrar.create(agentContext, { @@ -111,7 +115,7 @@ describe('DidRegistrar', () => { numAlgo: PeerDidNumAlgo.InceptionKeyWithoutDoc, }, secret: { - seed, + privateKey, }, }) diff --git a/packages/core/src/modules/dids/methods/peer/peerDidNumAlgo2.ts b/packages/core/src/modules/dids/methods/peer/peerDidNumAlgo2.ts index d3c68f768e..d0054d6d20 100644 --- a/packages/core/src/modules/dids/methods/peer/peerDidNumAlgo2.ts +++ b/packages/core/src/modules/dids/methods/peer/peerDidNumAlgo2.ts @@ -6,7 +6,7 @@ import { Key } from '../../../../crypto' import { JsonEncoder, JsonTransformer } from '../../../../utils' import { DidCommV1Service, DidCommV2Service, DidDocumentService } from '../../domain' import { DidDocumentBuilder } from '../../domain/DidDocumentBuilder' -import { getKeyDidMappingByKeyType, getKeyDidMappingByVerificationMethod } from '../../domain/key-type' +import { getKeyFromVerificationMethod, getKeyDidMappingByKeyType } from '../../domain/key-type' import { parseDid } from '../../domain/parse' import { DidKey } from '../key' @@ -123,7 +123,6 @@ export function didDocumentToNumAlgo2Did(didDocument: DidDocument) { // Transform als verification methods into a fingerprint (multibase, multicodec) const encoded = dereferenced.map((entry) => { - const { getKeyFromVerificationMethod } = getKeyDidMappingByVerificationMethod(entry) const key = getKeyFromVerificationMethod(entry) // Encode as '.PurposeFingerprint' diff --git a/packages/core/src/modules/dids/methods/sov/IndySdkSovDidRegistrar.ts b/packages/core/src/modules/dids/methods/sov/IndySdkSovDidRegistrar.ts deleted file mode 100644 index f3307644fc..0000000000 --- a/packages/core/src/modules/dids/methods/sov/IndySdkSovDidRegistrar.ts +++ /dev/null @@ -1,246 +0,0 @@ -import type { AgentContext } from '../../../../agent' -import type { IndyEndpointAttrib, IndyPool } from '../../../ledger' -import type { DidRegistrar } from '../../domain/DidRegistrar' -import type { DidCreateOptions, DidCreateResult, DidDeactivateResult, DidUpdateResult } from '../../types' -import type * as Indy from 'indy-sdk' - -import { IndySdkError } from '../../../../error' -import { injectable } from '../../../../plugins' -import { isIndyError } from '../../../../utils/indyError' -import { assertIndyWallet } from '../../../../wallet/util/assertIndyWallet' -import { IndyPoolService } from '../../../ledger' -import { DidDocumentRole } from '../../domain/DidDocumentRole' -import { DidRecord, DidRepository } from '../../repository' - -import { addServicesFromEndpointsAttrib, sovDidDocumentFromDid } from './util' - -@injectable() -export class IndySdkSovDidRegistrar implements DidRegistrar { - public readonly supportedMethods = ['sov'] - - public async create(agentContext: AgentContext, options: SovDidCreateOptions): Promise { - const indy = agentContext.config.agentDependencies.indy - const indyPoolService = agentContext.dependencyManager.resolve(IndyPoolService) - const didRepository = agentContext.dependencyManager.resolve(DidRepository) - - const { alias, role, submitterDid, indyNamespace } = options.options - const seed = options.secret?.seed - - if (seed && (typeof seed !== 'string' || seed.length !== 32)) { - return { - didDocumentMetadata: {}, - didRegistrationMetadata: {}, - didState: { - state: 'failed', - reason: 'Invalid seed provided', - }, - } - } - - if (!submitterDid.startsWith('did:sov:')) { - return { - didDocumentMetadata: {}, - didRegistrationMetadata: {}, - didState: { - state: 'failed', - reason: 'Submitter did must be a valid did:sov did', - }, - } - } - - try { - // NOTE: we need to use the createAndStoreMyDid method from indy to create the did - // If we just create a key and handle the creating of the did ourselves, indy will throw a - // WalletItemNotFound when it needs to sign ledger transactions using this did. This means we need - // to rely directly on the indy SDK, as we don't want to expose a create method just for. - // FIXME: once askar/indy-vdr is supported we need to adjust this to work with both indy-sdk and askar - assertIndyWallet(agentContext.wallet) - const [unqualifiedIndyDid, verkey] = await indy.createAndStoreMyDid(agentContext.wallet.handle, { - seed, - }) - - const qualifiedSovDid = `did:sov:${unqualifiedIndyDid}` - const unqualifiedSubmitterDid = submitterDid.replace('did:sov:', '') - - // TODO: it should be possible to pass the pool used for writing to the indy ledger service. - // The easiest way to do this would be to make the submitterDid a fully qualified did, including the indy namespace. - const pool = indyPoolService.getPoolForNamespace(indyNamespace) - await this.registerPublicDid(agentContext, unqualifiedSubmitterDid, unqualifiedIndyDid, verkey, alias, pool, role) - - // Create did document - const didDocumentBuilder = sovDidDocumentFromDid(qualifiedSovDid, verkey) - - // Add services if endpoints object was passed. - if (options.options.endpoints) { - await this.setEndpointsForDid(agentContext, unqualifiedIndyDid, options.options.endpoints, pool) - addServicesFromEndpointsAttrib( - didDocumentBuilder, - qualifiedSovDid, - options.options.endpoints, - `${qualifiedSovDid}#key-agreement-1` - ) - } - - // Build did document. - const didDocument = didDocumentBuilder.build() - - const didIndyNamespace = pool.config.indyNamespace - const qualifiedIndyDid = `did:indy:${didIndyNamespace}:${unqualifiedIndyDid}` - - // Save the did so we know we created it and can issue with it - const didRecord = new DidRecord({ - did: qualifiedSovDid, - role: DidDocumentRole.Created, - tags: { - recipientKeyFingerprints: didDocument.recipientKeys.map((key) => key.fingerprint), - qualifiedIndyDid, - }, - }) - await didRepository.save(agentContext, didRecord) - - return { - didDocumentMetadata: { - qualifiedIndyDid, - }, - didRegistrationMetadata: { - didIndyNamespace, - }, - didState: { - state: 'finished', - did: qualifiedSovDid, - didDocument, - secret: { - // FIXME: the uni-registrar creates the seed in the registrar method - // if it doesn't exist so the seed can always be returned. Currently - // we can only return it if the seed was passed in by the user. Once - // we have a secure method for generating seeds we should use the same - // approach - seed: options.secret?.seed, - }, - }, - } - } catch (error) { - return { - didDocumentMetadata: {}, - didRegistrationMetadata: {}, - didState: { - state: 'failed', - reason: `unknownError: ${error.message}`, - }, - } - } - } - - public async update(): Promise { - return { - didDocumentMetadata: {}, - didRegistrationMetadata: {}, - didState: { - state: 'failed', - reason: `notImplemented: updating did:sov not implemented yet`, - }, - } - } - - public async deactivate(): Promise { - return { - didDocumentMetadata: {}, - didRegistrationMetadata: {}, - didState: { - state: 'failed', - reason: `notImplemented: deactivating did:sov not implemented yet`, - }, - } - } - - public async registerPublicDid( - agentContext: AgentContext, - submitterDid: string, - targetDid: string, - verkey: string, - alias: string, - pool: IndyPool, - role?: Indy.NymRole - ) { - const indyPoolService = agentContext.dependencyManager.resolve(IndyPoolService) - const indy = agentContext.config.agentDependencies.indy - - try { - agentContext.config.logger.debug(`Register public did '${targetDid}' on ledger '${pool.id}'`) - - const request = await indy.buildNymRequest(submitterDid, targetDid, verkey, alias, role || null) - - const response = await indyPoolService.submitWriteRequest(agentContext, pool, request, submitterDid) - - agentContext.config.logger.debug(`Registered public did '${targetDid}' on ledger '${pool.id}'`, { - response, - }) - - return targetDid - } catch (error) { - agentContext.config.logger.error(`Error registering public did '${targetDid}' on ledger '${pool.id}'`, { - error, - submitterDid, - targetDid, - verkey, - alias, - role, - pool: pool.id, - }) - - throw error - } - } - - public async setEndpointsForDid( - agentContext: AgentContext, - did: string, - endpoints: IndyEndpointAttrib, - pool: IndyPool - ): Promise { - const indyPoolService = agentContext.dependencyManager.resolve(IndyPoolService) - const indy = agentContext.config.agentDependencies.indy - - try { - agentContext.config.logger.debug(`Set endpoints for did '${did}' on ledger '${pool.id}'`, endpoints) - - const request = await indy.buildAttribRequest(did, did, null, { endpoint: endpoints }, null) - - const response = await indyPoolService.submitWriteRequest(agentContext, pool, request, did) - agentContext.config.logger.debug(`Successfully set endpoints for did '${did}' on ledger '${pool.id}'`, { - response, - endpoints, - }) - } catch (error) { - agentContext.config.logger.error(`Error setting endpoints for did '${did}' on ledger '${pool.id}'`, { - error, - did, - endpoints, - }) - - throw isIndyError(error) ? new IndySdkError(error) : error - } - } -} - -export interface SovDidCreateOptions extends DidCreateOptions { - method: 'sov' - did?: undefined - // As did:sov is so limited, we require everything needed to construct the did document to be passed - // through the options object. Once we support did:indy we can allow the didDocument property. - didDocument?: never - options: { - alias: string - role?: Indy.NymRole - endpoints?: IndyEndpointAttrib - indyNamespace?: string - submitterDid: string - } - secret?: { - seed?: string - } -} - -// Update and Deactivate not supported for did:sov -export type IndyDidUpdateOptions = never -export type IndyDidDeactivateOptions = never diff --git a/packages/core/src/modules/dids/methods/sov/IndySdkSovDidResolver.ts b/packages/core/src/modules/dids/methods/sov/IndySdkSovDidResolver.ts deleted file mode 100644 index bae98c5587..0000000000 --- a/packages/core/src/modules/dids/methods/sov/IndySdkSovDidResolver.ts +++ /dev/null @@ -1,88 +0,0 @@ -import type { AgentContext } from '../../../../agent' -import type { IndyEndpointAttrib } from '../../../ledger' -import type { DidResolver } from '../../domain/DidResolver' -import type { DidResolutionResult, ParsedDid } from '../../types' - -import { IndySdkError } from '../../../../error' -import { injectable } from '../../../../plugins' -import { isIndyError } from '../../../../utils/indyError' -import { IndyPoolService } from '../../../ledger' - -import { addServicesFromEndpointsAttrib, sovDidDocumentFromDid } from './util' - -@injectable() -export class IndySdkSovDidResolver implements DidResolver { - public readonly supportedMethods = ['sov'] - - public async resolve(agentContext: AgentContext, did: string, parsed: ParsedDid): Promise { - const didDocumentMetadata = {} - - try { - const nym = await this.getPublicDid(agentContext, parsed.id) - const endpoints = await this.getEndpointsForDid(agentContext, parsed.id) - - const keyAgreementId = `${parsed.did}#key-agreement-1` - const builder = sovDidDocumentFromDid(parsed.did, nym.verkey) - addServicesFromEndpointsAttrib(builder, parsed.did, endpoints, keyAgreementId) - - return { - didDocument: builder.build(), - didDocumentMetadata, - didResolutionMetadata: { contentType: 'application/did+ld+json' }, - } - } catch (error) { - return { - didDocument: null, - didDocumentMetadata, - didResolutionMetadata: { - error: 'notFound', - message: `resolver_error: Unable to resolve did '${did}': ${error}`, - }, - } - } - } - - private async getPublicDid(agentContext: AgentContext, did: string) { - const indyPoolService = agentContext.dependencyManager.resolve(IndyPoolService) - - // Getting the pool for a did also retrieves the DID. We can just use that - const { did: didResponse } = await indyPoolService.getPoolForDid(agentContext, did) - - return didResponse - } - - private async getEndpointsForDid(agentContext: AgentContext, did: string) { - const indyPoolService = agentContext.dependencyManager.resolve(IndyPoolService) - const indy = agentContext.config.agentDependencies.indy - - const { pool } = await indyPoolService.getPoolForDid(agentContext, did) - - try { - agentContext.config.logger.debug(`Get endpoints for did '${did}' from ledger '${pool.id}'`) - - const request = await indy.buildGetAttribRequest(null, did, 'endpoint', null, null) - - agentContext.config.logger.debug(`Submitting get endpoint ATTRIB request for did '${did}' to ledger '${pool.id}'`) - const response = await indyPoolService.submitReadRequest(pool, request) - - if (!response.result.data) return {} - - const endpoints = JSON.parse(response.result.data as string)?.endpoint as IndyEndpointAttrib - agentContext.config.logger.debug( - `Got endpoints '${JSON.stringify(endpoints)}' for did '${did}' from ledger '${pool.id}'`, - { - response, - endpoints, - } - ) - - return endpoints ?? {} - } catch (error) { - agentContext.config.logger.error(`Error retrieving endpoints for did '${did}' from ledger '${pool.id}'`, { - error, - }) - - throw isIndyError(error) ? new IndySdkError(error) : error - } - } -} diff --git a/packages/core/src/modules/dids/methods/sov/__tests__/IndySdkSovDidRegistrar.test.ts b/packages/core/src/modules/dids/methods/sov/__tests__/IndySdkSovDidRegistrar.test.ts deleted file mode 100644 index c1c5b95df9..0000000000 --- a/packages/core/src/modules/dids/methods/sov/__tests__/IndySdkSovDidRegistrar.test.ts +++ /dev/null @@ -1,373 +0,0 @@ -import type { AgentConfig } from '../../../../../agent/AgentConfig' -import type { Wallet } from '../../../../../wallet' -import type { IndyPool } from '../../../../ledger' -import type * as Indy from 'indy-sdk' - -import { getAgentConfig, getAgentContext, mockFunction, mockProperty } from '../../../../../../tests/helpers' -import { KeyProviderRegistry } from '../../../../../crypto/key-provider' -import { JsonTransformer } from '../../../../../utils/JsonTransformer' -import { IndyWallet } from '../../../../../wallet/IndyWallet' -import { IndyPoolService } from '../../../../ledger/services/IndyPoolService' -import { DidDocumentRole } from '../../../domain/DidDocumentRole' -import { DidRepository } from '../../../repository/DidRepository' -import { IndySdkSovDidRegistrar } from '../IndySdkSovDidRegistrar' - -jest.mock('../../../repository/DidRepository') -const DidRepositoryMock = DidRepository as jest.Mock - -jest.mock('../../../../ledger/services/IndyPoolService') -const IndyPoolServiceMock = IndyPoolService as jest.Mock -const indyPoolServiceMock = new IndyPoolServiceMock() -mockFunction(indyPoolServiceMock.getPoolForNamespace).mockReturnValue({ - config: { id: 'pool1', indyNamespace: 'pool1' }, -} as IndyPool) - -const agentConfig = getAgentConfig('IndySdkSovDidRegistrar') -const createDidMock = jest.fn(async () => ['R1xKJw17sUoXhejEpugMYJ', 'E6D1m3eERqCueX4ZgMCY14B4NceAr6XP2HyVqt55gDhu']) - -const wallet = new IndyWallet(agentConfig.agentDependencies, agentConfig.logger, new KeyProviderRegistry([])) -mockProperty(wallet, 'handle', 10) - -const didRepositoryMock = new DidRepositoryMock() -const agentContext = getAgentContext({ - wallet, - registerInstances: [ - [DidRepository, didRepositoryMock], - [IndyPoolService, indyPoolServiceMock], - ], - agentConfig: { - ...agentConfig, - agentDependencies: { - ...agentConfig.agentDependencies, - indy: { createAndStoreMyDid: createDidMock } as unknown as typeof Indy, - }, - } as AgentConfig, -}) - -const indySdkSovDidRegistrar = new IndySdkSovDidRegistrar() - -describe('DidRegistrar', () => { - describe('IndySdkSovDidRegistrar', () => { - afterEach(() => { - jest.clearAllMocks() - }) - - it('should return an error state if an invalid seed is provided', async () => { - const result = await indySdkSovDidRegistrar.create(agentContext, { - method: 'sov', - - options: { - submitterDid: 'did:sov:BzCbsNYhMrjHiqZDTUASHg', - alias: 'Hello', - }, - secret: { - seed: 'invalid', - }, - }) - - expect(JsonTransformer.toJSON(result)).toMatchObject({ - didDocumentMetadata: {}, - didRegistrationMetadata: {}, - didState: { - state: 'failed', - reason: 'Invalid seed provided', - }, - }) - }) - - it('should return an error state if the wallet is not an indy wallet', async () => { - const agentContext = getAgentContext({ - wallet: {} as unknown as Wallet, - agentConfig, - }) - - const result = await indySdkSovDidRegistrar.create(agentContext, { - method: 'sov', - - options: { - submitterDid: 'did:sov:BzCbsNYhMrjHiqZDTUASHg', - alias: 'Hello', - }, - secret: { - seed: '12345678901234567890123456789012', - }, - }) - - expect(JsonTransformer.toJSON(result)).toMatchObject({ - didDocumentMetadata: {}, - didRegistrationMetadata: {}, - didState: { - state: 'failed', - reason: 'unknownError: Expected wallet to be instance of IndyWallet, found Object', - }, - }) - }) - - it('should return an error state if the submitter did is not qualified with did:sov', async () => { - const result = await indySdkSovDidRegistrar.create(agentContext, { - method: 'sov', - options: { - submitterDid: 'BzCbsNYhMrjHiqZDTUASHg', - alias: 'Hello', - }, - }) - - expect(JsonTransformer.toJSON(result)).toMatchObject({ - didDocumentMetadata: {}, - didRegistrationMetadata: {}, - didState: { - state: 'failed', - reason: 'Submitter did must be a valid did:sov did', - }, - }) - }) - - it('should correctly create a did:sov document without services', async () => { - const seed = '96213c3d7fc8d4d6754c712fd969598e' - - const registerPublicDidSpy = jest.spyOn(indySdkSovDidRegistrar, 'registerPublicDid') - registerPublicDidSpy.mockImplementationOnce(() => Promise.resolve('R1xKJw17sUoXhejEpugMYJ')) - - const result = await indySdkSovDidRegistrar.create(agentContext, { - method: 'sov', - options: { - alias: 'Hello', - submitterDid: 'did:sov:BzCbsNYhMrjHiqZDTUASHg', - role: 'STEWARD', - }, - secret: { - seed, - }, - }) - - expect(registerPublicDidSpy).toHaveBeenCalledWith( - agentContext, - // Unqualified submitter did - 'BzCbsNYhMrjHiqZDTUASHg', - // Unqualified created indy did - 'R1xKJw17sUoXhejEpugMYJ', - // Verkey - 'E6D1m3eERqCueX4ZgMCY14B4NceAr6XP2HyVqt55gDhu', - // Alias - 'Hello', - // Pool - { config: { id: 'pool1', indyNamespace: 'pool1' } }, - // Role - 'STEWARD' - ) - expect(JsonTransformer.toJSON(result)).toMatchObject({ - didDocumentMetadata: { - qualifiedIndyDid: 'did:indy:pool1:R1xKJw17sUoXhejEpugMYJ', - }, - didRegistrationMetadata: { - didIndyNamespace: 'pool1', - }, - didState: { - state: 'finished', - did: 'did:sov:R1xKJw17sUoXhejEpugMYJ', - didDocument: { - '@context': [ - 'https://w3id.org/did/v1', - 'https://w3id.org/security/suites/ed25519-2018/v1', - 'https://w3id.org/security/suites/x25519-2019/v1', - ], - id: 'did:sov:R1xKJw17sUoXhejEpugMYJ', - verificationMethod: [ - { - id: 'did:sov:R1xKJw17sUoXhejEpugMYJ#key-1', - type: 'Ed25519VerificationKey2018', - controller: 'did:sov:R1xKJw17sUoXhejEpugMYJ', - publicKeyBase58: 'E6D1m3eERqCueX4ZgMCY14B4NceAr6XP2HyVqt55gDhu', - }, - { - id: 'did:sov:R1xKJw17sUoXhejEpugMYJ#key-agreement-1', - type: 'X25519KeyAgreementKey2019', - controller: 'did:sov:R1xKJw17sUoXhejEpugMYJ', - publicKeyBase58: 'Fbv17ZbnUSbafsiUBJbdGeC62M8v8GEscVMMcE59mRPt', - }, - ], - authentication: ['did:sov:R1xKJw17sUoXhejEpugMYJ#key-1'], - assertionMethod: ['did:sov:R1xKJw17sUoXhejEpugMYJ#key-1'], - keyAgreement: ['did:sov:R1xKJw17sUoXhejEpugMYJ#key-agreement-1'], - }, - secret: { - seed, - }, - }, - }) - }) - - it('should correctly create a did:sov document with services', async () => { - const seed = '96213c3d7fc8d4d6754c712fd969598e' - - const registerPublicDidSpy = jest.spyOn(indySdkSovDidRegistrar, 'registerPublicDid') - registerPublicDidSpy.mockImplementationOnce(() => Promise.resolve('R1xKJw17sUoXhejEpugMYJ')) - - const setEndpointsForDidSpy = jest.spyOn(indySdkSovDidRegistrar, 'setEndpointsForDid') - setEndpointsForDidSpy.mockImplementationOnce(() => Promise.resolve(undefined)) - - const result = await indySdkSovDidRegistrar.create(agentContext, { - method: 'sov', - options: { - alias: 'Hello', - submitterDid: 'did:sov:BzCbsNYhMrjHiqZDTUASHg', - role: 'STEWARD', - endpoints: { - endpoint: 'https://example.com/endpoint', - routingKeys: ['key-1'], - types: ['DIDCommMessaging', 'did-communication', 'endpoint'], - }, - }, - secret: { - seed, - }, - }) - - expect(registerPublicDidSpy).toHaveBeenCalledWith( - agentContext, - // Unqualified submitter did - 'BzCbsNYhMrjHiqZDTUASHg', - // Unqualified created indy did - 'R1xKJw17sUoXhejEpugMYJ', - // Verkey - 'E6D1m3eERqCueX4ZgMCY14B4NceAr6XP2HyVqt55gDhu', - // Alias - 'Hello', - // Pool - { config: { id: 'pool1', indyNamespace: 'pool1' } }, - // Role - 'STEWARD' - ) - expect(JsonTransformer.toJSON(result)).toMatchObject({ - didDocumentMetadata: { - qualifiedIndyDid: 'did:indy:pool1:R1xKJw17sUoXhejEpugMYJ', - }, - didRegistrationMetadata: { - didIndyNamespace: 'pool1', - }, - didState: { - state: 'finished', - did: 'did:sov:R1xKJw17sUoXhejEpugMYJ', - didDocument: { - '@context': [ - 'https://w3id.org/did/v1', - 'https://w3id.org/security/suites/ed25519-2018/v1', - 'https://w3id.org/security/suites/x25519-2019/v1', - 'https://didcomm.org/messaging/contexts/v2', - ], - id: 'did:sov:R1xKJw17sUoXhejEpugMYJ', - verificationMethod: [ - { - id: 'did:sov:R1xKJw17sUoXhejEpugMYJ#key-1', - type: 'Ed25519VerificationKey2018', - controller: 'did:sov:R1xKJw17sUoXhejEpugMYJ', - publicKeyBase58: 'E6D1m3eERqCueX4ZgMCY14B4NceAr6XP2HyVqt55gDhu', - }, - { - id: 'did:sov:R1xKJw17sUoXhejEpugMYJ#key-agreement-1', - type: 'X25519KeyAgreementKey2019', - controller: 'did:sov:R1xKJw17sUoXhejEpugMYJ', - publicKeyBase58: 'Fbv17ZbnUSbafsiUBJbdGeC62M8v8GEscVMMcE59mRPt', - }, - ], - service: [ - { - id: 'did:sov:R1xKJw17sUoXhejEpugMYJ#endpoint', - serviceEndpoint: 'https://example.com/endpoint', - type: 'endpoint', - }, - { - id: 'did:sov:R1xKJw17sUoXhejEpugMYJ#did-communication', - serviceEndpoint: 'https://example.com/endpoint', - type: 'did-communication', - priority: 0, - recipientKeys: ['did:sov:R1xKJw17sUoXhejEpugMYJ#key-agreement-1'], - routingKeys: ['key-1'], - accept: ['didcomm/aip2;env=rfc19'], - }, - { - id: 'did:sov:R1xKJw17sUoXhejEpugMYJ#didcomm-1', - serviceEndpoint: 'https://example.com/endpoint', - type: 'DIDCommMessaging', - routingKeys: ['key-1'], - accept: ['didcomm/v2'], - }, - ], - authentication: ['did:sov:R1xKJw17sUoXhejEpugMYJ#key-1'], - assertionMethod: ['did:sov:R1xKJw17sUoXhejEpugMYJ#key-1'], - keyAgreement: ['did:sov:R1xKJw17sUoXhejEpugMYJ#key-agreement-1'], - }, - secret: { - seed, - }, - }, - }) - }) - - it('should store the did document', async () => { - const seed = '96213c3d7fc8d4d6754c712fd969598e' - - const registerPublicDidSpy = jest.spyOn(indySdkSovDidRegistrar, 'registerPublicDid') - registerPublicDidSpy.mockImplementationOnce(() => Promise.resolve('did')) - - const setEndpointsForDidSpy = jest.spyOn(indySdkSovDidRegistrar, 'setEndpointsForDid') - setEndpointsForDidSpy.mockImplementationOnce(() => Promise.resolve(undefined)) - - await indySdkSovDidRegistrar.create(agentContext, { - method: 'sov', - options: { - alias: 'Hello', - submitterDid: 'did:sov:BzCbsNYhMrjHiqZDTUASHg', - role: 'STEWARD', - endpoints: { - endpoint: 'https://example.com/endpoint', - routingKeys: ['key-1'], - types: ['DIDCommMessaging', 'did-communication', 'endpoint'], - }, - }, - secret: { - seed, - }, - }) - - expect(didRepositoryMock.save).toHaveBeenCalledTimes(1) - const [, didRecord] = mockFunction(didRepositoryMock.save).mock.calls[0] - - expect(didRecord).toMatchObject({ - did: 'did:sov:R1xKJw17sUoXhejEpugMYJ', - role: DidDocumentRole.Created, - _tags: { - recipientKeyFingerprints: ['z6LSrH6AdsQeZuKKmG6Ehx7abEQZsVg2psR2VU536gigUoAe'], - qualifiedIndyDid: 'did:indy:pool1:R1xKJw17sUoXhejEpugMYJ', - }, - didDocument: undefined, - }) - }) - - it('should return an error state when calling update', async () => { - const result = await indySdkSovDidRegistrar.update() - - expect(result).toEqual({ - didDocumentMetadata: {}, - didRegistrationMetadata: {}, - didState: { - state: 'failed', - reason: `notImplemented: updating did:sov not implemented yet`, - }, - }) - }) - - it('should return an error state when calling deactivate', async () => { - const result = await indySdkSovDidRegistrar.deactivate() - - expect(result).toEqual({ - didDocumentMetadata: {}, - didRegistrationMetadata: {}, - didState: { - state: 'failed', - reason: `notImplemented: deactivating did:sov not implemented yet`, - }, - }) - }) - }) -}) diff --git a/packages/core/src/modules/dids/methods/sov/__tests__/IndySdkSovDidResolver.test.ts b/packages/core/src/modules/dids/methods/sov/__tests__/IndySdkSovDidResolver.test.ts deleted file mode 100644 index f931774ac7..0000000000 --- a/packages/core/src/modules/dids/methods/sov/__tests__/IndySdkSovDidResolver.test.ts +++ /dev/null @@ -1,123 +0,0 @@ -import type { IndyPool } from '../../../../ledger' -import type { IndyEndpointAttrib } from '../../../../ledger/services/IndyLedgerService' -import type { GetNymResponse } from 'indy-sdk' - -import { getAgentConfig, getAgentContext, mockFunction, mockProperty } from '../../../../../../tests/helpers' -import { KeyProviderRegistry } from '../../../../../crypto/key-provider' -import { JsonTransformer } from '../../../../../utils/JsonTransformer' -import { IndyWallet } from '../../../../../wallet/IndyWallet' -import { IndyPoolService } from '../../../../ledger/services/IndyPoolService' -import didSovR1xKJw17sUoXhejEpugMYJFixture from '../../../__tests__/__fixtures__/didSovR1xKJw17sUoXhejEpugMYJ.json' -import didSovWJz9mHyW9BZksioQnRsrAoFixture from '../../../__tests__/__fixtures__/didSovWJz9mHyW9BZksioQnRsrAo.json' -import { parseDid } from '../../../domain/parse' -import { IndySdkSovDidResolver } from '../IndySdkSovDidResolver' - -jest.mock('../../../../ledger/services/IndyPoolService') -const IndyPoolServiceMock = IndyPoolService as jest.Mock -const indyPoolServiceMock = new IndyPoolServiceMock() -mockFunction(indyPoolServiceMock.getPoolForNamespace).mockReturnValue({ - config: { id: 'pool1', indyNamespace: 'pool1' }, -} as IndyPool) - -const agentConfig = getAgentConfig('IndySdkSovDidResolver') - -const wallet = new IndyWallet(agentConfig.agentDependencies, agentConfig.logger, new KeyProviderRegistry([])) -mockProperty(wallet, 'handle', 10) - -const agentContext = getAgentContext({ - agentConfig, - registerInstances: [[IndyPoolService, indyPoolServiceMock]], -}) - -const indySdkSovDidResolver = new IndySdkSovDidResolver() - -describe('DidResolver', () => { - describe('IndySdkSovDidResolver', () => { - it('should correctly resolve a did:sov document', async () => { - const did = 'did:sov:R1xKJw17sUoXhejEpugMYJ' - - const nymResponse: GetNymResponse = { - did: 'R1xKJw17sUoXhejEpugMYJ', - verkey: 'E6D1m3eERqCueX4ZgMCY14B4NceAr6XP2HyVqt55gDhu', - role: 'ENDORSER', - } - - const endpoints: IndyEndpointAttrib = { - endpoint: 'https://ssi.com', - profile: 'https://profile.com', - hub: 'https://hub.com', - } - - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - jest.spyOn(indySdkSovDidResolver, 'getPublicDid').mockResolvedValue(nymResponse) - - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - jest.spyOn(indySdkSovDidResolver, 'getEndpointsForDid').mockResolvedValue(endpoints) - - const result = await indySdkSovDidResolver.resolve(agentContext, did, parseDid(did)) - - expect(JsonTransformer.toJSON(result)).toMatchObject({ - didDocument: didSovR1xKJw17sUoXhejEpugMYJFixture, - didDocumentMetadata: {}, - didResolutionMetadata: { - contentType: 'application/did+ld+json', - }, - }) - }) - - it('should resolve a did:sov document with routingKeys and types entries in the attrib', async () => { - const did = 'did:sov:WJz9mHyW9BZksioQnRsrAo' - - const nymResponse: GetNymResponse = { - did: 'WJz9mHyW9BZksioQnRsrAo', - verkey: 'GyYtYWU1vjwd5PFJM4VSX5aUiSV3TyZMuLBJBTQvfdF8', - role: 'ENDORSER', - } - - const endpoints: IndyEndpointAttrib = { - endpoint: 'https://agent.com', - types: ['endpoint', 'did-communication', 'DIDCommMessaging'], - routingKeys: ['routingKey1', 'routingKey2'], - } - - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - jest.spyOn(indySdkSovDidResolver, 'getPublicDid').mockResolvedValue(nymResponse) - - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - jest.spyOn(indySdkSovDidResolver, 'getEndpointsForDid').mockResolvedValue(endpoints) - - const result = await indySdkSovDidResolver.resolve(agentContext, did, parseDid(did)) - - expect(JsonTransformer.toJSON(result)).toMatchObject({ - didDocument: didSovWJz9mHyW9BZksioQnRsrAoFixture, - didDocumentMetadata: {}, - didResolutionMetadata: { - contentType: 'application/did+ld+json', - }, - }) - }) - - it('should return did resolution metadata with error if the indy ledger service throws an error', async () => { - const did = 'did:sov:R1xKJw17sUoXhejEpugMYJ' - - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - jest.spyOn(indySdkSovDidResolver, 'getPublicDid').mockRejectedValue(new Error('Error retrieving did')) - - const result = await indySdkSovDidResolver.resolve(agentContext, did, parseDid(did)) - - expect(result).toMatchObject({ - didDocument: null, - didDocumentMetadata: {}, - didResolutionMetadata: { - error: 'notFound', - message: `resolver_error: Unable to resolve did 'did:sov:R1xKJw17sUoXhejEpugMYJ': Error: Error retrieving did`, - }, - }) - }) - }) -}) diff --git a/packages/core/src/modules/dids/methods/sov/index.ts b/packages/core/src/modules/dids/methods/sov/index.ts deleted file mode 100644 index 13a8e8aa2f..0000000000 --- a/packages/core/src/modules/dids/methods/sov/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from './IndySdkSovDidRegistrar' -export * from './IndySdkSovDidResolver' diff --git a/packages/core/src/modules/dids/repository/DidRecord.ts b/packages/core/src/modules/dids/repository/DidRecord.ts index 752088323b..5f8b9cc375 100644 --- a/packages/core/src/modules/dids/repository/DidRecord.ts +++ b/packages/core/src/modules/dids/repository/DidRecord.ts @@ -1,5 +1,5 @@ -import type { TagsBase } from '../../../storage/BaseRecord' import type { DidRecordMetadata } from './didRecordMetadataTypes' +import type { TagsBase } from '../../../storage/BaseRecord' import { Type } from 'class-transformer' import { IsEnum, ValidateNested } from 'class-validator' @@ -21,7 +21,7 @@ export interface DidRecordProps { tags?: CustomDidTags } -interface CustomDidTags extends TagsBase { +export interface CustomDidTags extends TagsBase { recipientKeyFingerprints?: string[] } diff --git a/packages/core/src/modules/dids/repository/DidRepository.ts b/packages/core/src/modules/dids/repository/DidRepository.ts index 80fad9cf52..11a6c60b9a 100644 --- a/packages/core/src/modules/dids/repository/DidRepository.ts +++ b/packages/core/src/modules/dids/repository/DidRepository.ts @@ -1,5 +1,7 @@ +import type { CustomDidTags } from './DidRecord' import type { AgentContext } from '../../../agent' import type { Key } from '../../../crypto' +import type { DidDocument } from '../domain' import { EventEmitter } from '../../../agent/EventEmitter' import { InjectionSymbols } from '../../../constants' @@ -57,10 +59,43 @@ export class DidRepository extends Repository { return this.findSingleByQuery(agentContext, { did: createdDid, role: DidDocumentRole.Created }) } - public getCreatedDids(agentContext: AgentContext, { method }: { method?: string }) { + public getCreatedDids(agentContext: AgentContext, { method, did }: { method?: string; did?: string }) { return this.findByQuery(agentContext, { role: DidDocumentRole.Created, method, + did, }) } + + public async storeCreatedDid(agentContext: AgentContext, { did, didDocument, tags }: StoreDidOptions) { + const didRecord = new DidRecord({ + did, + didDocument, + role: DidDocumentRole.Created, + tags, + }) + + await this.save(agentContext, didRecord) + + return didRecord + } + + public async storeReceivedDid(agentContext: AgentContext, { did, didDocument, tags }: StoreDidOptions) { + const didRecord = new DidRecord({ + did, + didDocument, + role: DidDocumentRole.Received, + tags, + }) + + await this.save(agentContext, didRecord) + + return didRecord + } +} + +interface StoreDidOptions { + did: string + didDocument?: DidDocument + tags?: CustomDidTags } diff --git a/packages/core/src/modules/dids/services/DidResolverService.ts b/packages/core/src/modules/dids/services/DidResolverService.ts index e23206cc9a..7f97d3f9d1 100644 --- a/packages/core/src/modules/dids/services/DidResolverService.ts +++ b/packages/core/src/modules/dids/services/DidResolverService.ts @@ -46,7 +46,10 @@ export class DidResolverService { if (!resolver) { return { ...result, - didResolutionMetadata: { error: 'unsupportedDidMethod' }, + didResolutionMetadata: { + error: 'unsupportedDidMethod', + message: `No did resolver registered for did method ${parsed.method}`, + }, } } diff --git a/packages/core/src/modules/dids/services/__tests__/DidResolverService.test.ts b/packages/core/src/modules/dids/services/__tests__/DidResolverService.test.ts index 81f250f294..00b17ad458 100644 --- a/packages/core/src/modules/dids/services/__tests__/DidResolverService.test.ts +++ b/packages/core/src/modules/dids/services/__tests__/DidResolverService.test.ts @@ -65,6 +65,7 @@ describe('DidResolverService', () => { didDocumentMetadata: {}, didResolutionMetadata: { error: 'unsupportedDidMethod', + message: 'No did resolver registered for did method example', }, }) }) diff --git a/packages/core/src/modules/dids/types.ts b/packages/core/src/modules/dids/types.ts index 257260f2e8..359616a818 100644 --- a/packages/core/src/modules/dids/types.ts +++ b/packages/core/src/modules/dids/types.ts @@ -1,5 +1,5 @@ import type { DidDocument } from './domain' -import type { DIDResolutionOptions, ParsedDID, DIDDocumentMetadata, DIDResolutionMetadata } from 'did-resolver' +import type { DIDDocumentMetadata, DIDResolutionMetadata, DIDResolutionOptions, ParsedDID } from 'did-resolver' export type ParsedDid = ParsedDID export type DidResolutionOptions = DIDResolutionOptions @@ -36,13 +36,20 @@ export interface DidOperationStateFailed { reason: string } -export interface DidOperationState { - state: 'action' | 'wait' +export interface DidOperationStateWait { + state: 'wait' did?: string secret?: DidRegistrationSecretOptions didDocument?: DidDocument } +export interface DidOperationStateActionBase { + state: 'action' + action: string + did?: string + secret?: DidRegistrationSecretOptions + didDocument?: DidDocument +} export interface DidCreateOptions { method?: string did?: string @@ -51,9 +58,11 @@ export interface DidCreateOptions { didDocument?: DidDocument } -export interface DidCreateResult { +export interface DidCreateResult< + DidOperationStateAction extends DidOperationStateActionBase = DidOperationStateActionBase +> { jobId?: string - didState: DidOperationState | DidOperationStateFinished | DidOperationStateFailed + didState: DidOperationStateWait | DidOperationStateAction | DidOperationStateFinished | DidOperationStateFailed didRegistrationMetadata: DidRegistrationMetadata didDocumentMetadata: DidResolutionMetadata } @@ -68,7 +77,7 @@ export interface DidUpdateOptions { export interface DidUpdateResult { jobId?: string - didState: DidOperationState | DidOperationStateFinished | DidOperationStateFailed + didState: DidOperationStateWait | DidOperationStateActionBase | DidOperationStateFinished | DidOperationStateFailed didRegistrationMetadata: DidRegistrationMetadata didDocumentMetadata: DidResolutionMetadata } @@ -81,7 +90,7 @@ export interface DidDeactivateOptions { export interface DidDeactivateResult { jobId?: string - didState: DidOperationState | DidOperationStateFinished | DidOperationStateFailed + didState: DidOperationStateWait | DidOperationStateActionBase | DidOperationStateFinished | DidOperationStateFailed didRegistrationMetadata: DidRegistrationMetadata didDocumentMetadata: DidResolutionMetadata } diff --git a/packages/core/src/modules/discover-features/DiscoverFeaturesApi.ts b/packages/core/src/modules/discover-features/DiscoverFeaturesApi.ts index 94e376f08d..3d074f9d18 100644 --- a/packages/core/src/modules/discover-features/DiscoverFeaturesApi.ts +++ b/packages/core/src/modules/discover-features/DiscoverFeaturesApi.ts @@ -1,4 +1,3 @@ -import type { Feature } from '../../agent/models' import type { DiscloseFeaturesOptions, QueryFeaturesOptions, @@ -6,6 +5,7 @@ import type { } from './DiscoverFeaturesApiOptions' import type { DiscoverFeaturesDisclosureReceivedEvent } from './DiscoverFeaturesEvents' import type { DiscoverFeaturesService } from './services' +import type { Feature } from '../../agent/models' import { firstValueFrom, of, ReplaySubject, Subject } from 'rxjs' import { catchError, filter, map, takeUntil, timeout } from 'rxjs/operators' @@ -80,7 +80,7 @@ export class DiscoverFeaturesApi< throw new AriesFrameworkError(`No discover features service registered for protocol version ${protocolVersion}`) } - return this.serviceMap[protocolVersion] + return this.serviceMap[protocolVersion] as DiscoverFeaturesService } /** diff --git a/packages/core/src/modules/discover-features/DiscoverFeaturesApiOptions.ts b/packages/core/src/modules/discover-features/DiscoverFeaturesApiOptions.ts index 7cdcc18cb4..11bdf538b7 100644 --- a/packages/core/src/modules/discover-features/DiscoverFeaturesApiOptions.ts +++ b/packages/core/src/modules/discover-features/DiscoverFeaturesApiOptions.ts @@ -1,5 +1,5 @@ -import type { FeatureQueryOptions } from '../../agent/models' import type { DiscoverFeaturesService } from './services' +import type { FeatureQueryOptions } from '../../agent/models' /** * Get the supported protocol versions based on the provided discover features services. diff --git a/packages/core/src/modules/discover-features/DiscoverFeaturesModule.ts b/packages/core/src/modules/discover-features/DiscoverFeaturesModule.ts index 79caa885f9..bd97e12ec4 100644 --- a/packages/core/src/modules/discover-features/DiscoverFeaturesModule.ts +++ b/packages/core/src/modules/discover-features/DiscoverFeaturesModule.ts @@ -1,6 +1,6 @@ +import type { DiscoverFeaturesModuleConfigOptions } from './DiscoverFeaturesModuleConfig' import type { FeatureRegistry } from '../../agent/FeatureRegistry' import type { DependencyManager, Module } from '../../plugins' -import type { DiscoverFeaturesModuleConfigOptions } from './DiscoverFeaturesModuleConfig' import { Protocol } from '../../agent/models' diff --git a/packages/core/src/modules/discover-features/__tests__/v1-discover-features.e2e.test.ts b/packages/core/src/modules/discover-features/__tests__/v1-discover-features.e2e.test.ts index 19e48cd386..68fb1a0103 100644 --- a/packages/core/src/modules/discover-features/__tests__/v1-discover-features.e2e.test.ts +++ b/packages/core/src/modules/discover-features/__tests__/v1-discover-features.e2e.test.ts @@ -1,47 +1,47 @@ -import type { SubjectMessage } from '../../../../../../tests/transport/SubjectInboundTransport' import type { ConnectionRecord } from '../../connections' import type { DiscoverFeaturesDisclosureReceivedEvent, DiscoverFeaturesQueryReceivedEvent, } from '../DiscoverFeaturesEvents' -import { ReplaySubject, Subject } from 'rxjs' +import { ReplaySubject } from 'rxjs' -import { SubjectInboundTransport } from '../../../../../../tests/transport/SubjectInboundTransport' -import { SubjectOutboundTransport } from '../../../../../../tests/transport/SubjectOutboundTransport' +import { getIndySdkModules } from '../../../../../indy-sdk/tests/setupIndySdkModule' +import { setupSubjectTransports } from '../../../../tests' import { getAgentOptions, makeConnection } from '../../../../tests/helpers' import { Agent } from '../../../agent/Agent' import { DiscoverFeaturesEventTypes } from '../DiscoverFeaturesEvents' import { waitForDisclosureSubject, waitForQuerySubject } from './helpers' +const faberAgentOptions = getAgentOptions( + 'Faber Discover Features V1 E2E', + { + endpoints: ['rxjs:faber'], + }, + getIndySdkModules() +) + +const aliceAgentOptions = getAgentOptions( + 'Alice Discover Features V1 E2E', + { + endpoints: ['rxjs:alice'], + }, + getIndySdkModules() +) + describe('v1 discover features', () => { let faberAgent: Agent let aliceAgent: Agent let faberConnection: ConnectionRecord beforeAll(async () => { - const faberMessages = new Subject() - const aliceMessages = new Subject() - const subjectMap = { - 'rxjs:faber': faberMessages, - 'rxjs:alice': aliceMessages, - } - const faberAgentOptions = getAgentOptions('Faber Discover Features V1 E2E', { - endpoints: ['rxjs:faber'], - }) - - const aliceAgentOptions = getAgentOptions('Alice Discover Features V1 E2E', { - endpoints: ['rxjs:alice'], - }) faberAgent = new Agent(faberAgentOptions) - faberAgent.registerInboundTransport(new SubjectInboundTransport(faberMessages)) - faberAgent.registerOutboundTransport(new SubjectOutboundTransport(subjectMap)) - await faberAgent.initialize() - aliceAgent = new Agent(aliceAgentOptions) - aliceAgent.registerInboundTransport(new SubjectInboundTransport(aliceMessages)) - aliceAgent.registerOutboundTransport(new SubjectOutboundTransport(subjectMap)) + + setupSubjectTransports([faberAgent, aliceAgent]) + + await faberAgent.initialize() await aliceAgent.initialize() ;[faberConnection] = await makeConnection(faberAgent, aliceAgent) }) @@ -53,7 +53,7 @@ describe('v1 discover features', () => { await aliceAgent.wallet.delete() }) - test('Faber asks Alice for issue credential protocol support', async () => { + test('Faber asks Alice for revocation notification protocol support', async () => { const faberReplay = new ReplaySubject() const aliceReplay = new ReplaySubject() @@ -67,14 +67,14 @@ describe('v1 discover features', () => { await faberAgent.discovery.queryFeatures({ connectionId: faberConnection.id, protocolVersion: 'v1', - queries: [{ featureType: 'protocol', match: 'https://didcomm.org/issue-credential/*' }], + queries: [{ featureType: 'protocol', match: 'https://didcomm.org/revocation_notification/*' }], }) const query = await waitForQuerySubject(aliceReplay, { timeoutMs: 10000 }) expect(query).toMatchObject({ protocolVersion: 'v1', - queries: [{ featureType: 'protocol', match: 'https://didcomm.org/issue-credential/*' }], + queries: [{ featureType: 'protocol', match: 'https://didcomm.org/revocation_notification/*' }], }) const disclosure = await waitForDisclosureSubject(faberReplay, { timeoutMs: 10000 }) @@ -82,24 +82,24 @@ describe('v1 discover features', () => { expect(disclosure).toMatchObject({ protocolVersion: 'v1', disclosures: [ - { type: 'protocol', id: 'https://didcomm.org/issue-credential/1.0', roles: ['holder', 'issuer'] }, - { type: 'protocol', id: 'https://didcomm.org/issue-credential/2.0', roles: ['holder', 'issuer'] }, + { type: 'protocol', id: 'https://didcomm.org/revocation_notification/1.0', roles: ['holder'] }, + { type: 'protocol', id: 'https://didcomm.org/revocation_notification/2.0', roles: ['holder'] }, ], }) }) - test('Faber asks Alice for issue credential protocol support synchronously', async () => { + test('Faber asks Alice for revocation notification protocol support synchronously', async () => { const matchingFeatures = await faberAgent.discovery.queryFeatures({ connectionId: faberConnection.id, protocolVersion: 'v1', - queries: [{ featureType: 'protocol', match: 'https://didcomm.org/issue-credential/*' }], + queries: [{ featureType: 'protocol', match: 'https://didcomm.org/revocation_notification/*' }], awaitDisclosures: true, }) expect(matchingFeatures).toMatchObject({ features: [ - { type: 'protocol', id: 'https://didcomm.org/issue-credential/1.0', roles: ['holder', 'issuer'] }, - { type: 'protocol', id: 'https://didcomm.org/issue-credential/2.0', roles: ['holder', 'issuer'] }, + { type: 'protocol', id: 'https://didcomm.org/revocation_notification/1.0', roles: ['holder'] }, + { type: 'protocol', id: 'https://didcomm.org/revocation_notification/2.0', roles: ['holder'] }, ], }) }) diff --git a/packages/core/src/modules/discover-features/__tests__/v2-discover-features.e2e.test.ts b/packages/core/src/modules/discover-features/__tests__/v2-discover-features.e2e.test.ts index 20e2d72e2b..f5a4b9f782 100644 --- a/packages/core/src/modules/discover-features/__tests__/v2-discover-features.e2e.test.ts +++ b/packages/core/src/modules/discover-features/__tests__/v2-discover-features.e2e.test.ts @@ -1,14 +1,13 @@ -import type { SubjectMessage } from '../../../../../../tests/transport/SubjectInboundTransport' import type { ConnectionRecord } from '../../connections' import type { DiscoverFeaturesDisclosureReceivedEvent, DiscoverFeaturesQueryReceivedEvent, } from '../DiscoverFeaturesEvents' -import { ReplaySubject, Subject } from 'rxjs' +import { ReplaySubject } from 'rxjs' -import { SubjectInboundTransport } from '../../../../../../tests/transport/SubjectInboundTransport' -import { SubjectOutboundTransport } from '../../../../../../tests/transport/SubjectOutboundTransport' +import { getIndySdkModules } from '../../../../../indy-sdk/tests/setupIndySdkModule' +import { setupSubjectTransports } from '../../../../tests' import { getAgentOptions, makeConnection } from '../../../../tests/helpers' import { Agent } from '../../../agent/Agent' import { GoalCode, Feature } from '../../../agent/models' @@ -16,6 +15,22 @@ import { DiscoverFeaturesEventTypes } from '../DiscoverFeaturesEvents' import { waitForDisclosureSubject, waitForQuerySubject } from './helpers' +const faberAgentOptions = getAgentOptions( + 'Faber Discover Features V2 E2E', + { + endpoints: ['rxjs:faber'], + }, + getIndySdkModules() +) + +const aliceAgentOptions = getAgentOptions( + 'Alice Discover Features V2 E2E', + { + endpoints: ['rxjs:alice'], + }, + getIndySdkModules() +) + describe('v2 discover features', () => { let faberAgent: Agent let aliceAgent: Agent @@ -23,27 +38,11 @@ describe('v2 discover features', () => { let faberConnection: ConnectionRecord beforeAll(async () => { - const faberMessages = new Subject() - const aliceMessages = new Subject() - const subjectMap = { - 'rxjs:faber': faberMessages, - 'rxjs:alice': aliceMessages, - } - const faberAgentOptions = getAgentOptions('Faber Discover Features V2 E2E', { - endpoints: ['rxjs:faber'], - }) - - const aliceAgentOptions = getAgentOptions('Alice Discover Features V2 E2E', { - endpoints: ['rxjs:alice'], - }) faberAgent = new Agent(faberAgentOptions) - faberAgent.registerInboundTransport(new SubjectInboundTransport(faberMessages)) - faberAgent.registerOutboundTransport(new SubjectOutboundTransport(subjectMap)) - await faberAgent.initialize() - aliceAgent = new Agent(aliceAgentOptions) - aliceAgent.registerInboundTransport(new SubjectInboundTransport(aliceMessages)) - aliceAgent.registerOutboundTransport(new SubjectOutboundTransport(subjectMap)) + setupSubjectTransports([faberAgent, aliceAgent]) + + await faberAgent.initialize() await aliceAgent.initialize() ;[faberConnection, aliceConnection] = await makeConnection(faberAgent, aliceAgent) }) @@ -70,14 +69,14 @@ describe('v2 discover features', () => { await faberAgent.discovery.queryFeatures({ connectionId: faberConnection.id, protocolVersion: 'v2', - queries: [{ featureType: 'protocol', match: 'https://didcomm.org/issue-credential/*' }], + queries: [{ featureType: 'protocol', match: 'https://didcomm.org/revocation_notification/*' }], }) const query = await waitForQuerySubject(aliceReplay, { timeoutMs: 10000 }) expect(query).toMatchObject({ protocolVersion: 'v2', - queries: [{ featureType: 'protocol', match: 'https://didcomm.org/issue-credential/*' }], + queries: [{ featureType: 'protocol', match: 'https://didcomm.org/revocation_notification/*' }], }) const disclosure = await waitForDisclosureSubject(faberReplay, { timeoutMs: 10000 }) @@ -85,8 +84,8 @@ describe('v2 discover features', () => { expect(disclosure).toMatchObject({ protocolVersion: 'v2', disclosures: [ - { type: 'protocol', id: 'https://didcomm.org/issue-credential/1.0', roles: ['holder', 'issuer'] }, - { type: 'protocol', id: 'https://didcomm.org/issue-credential/2.0', roles: ['holder', 'issuer'] }, + { type: 'protocol', id: 'https://didcomm.org/revocation_notification/1.0', roles: ['holder'] }, + { type: 'protocol', id: 'https://didcomm.org/revocation_notification/2.0', roles: ['holder'] }, ], }) }) @@ -220,14 +219,14 @@ describe('v2 discover features', () => { const matchingFeatures = await faberAgent.discovery.queryFeatures({ connectionId: faberConnection.id, protocolVersion: 'v2', - queries: [{ featureType: 'protocol', match: 'https://didcomm.org/issue-credential/*' }], + queries: [{ featureType: 'protocol', match: 'https://didcomm.org/revocation_notification/*' }], awaitDisclosures: true, }) expect(matchingFeatures).toMatchObject({ features: [ - { type: 'protocol', id: 'https://didcomm.org/issue-credential/1.0', roles: ['holder', 'issuer'] }, - { type: 'protocol', id: 'https://didcomm.org/issue-credential/2.0', roles: ['holder', 'issuer'] }, + { type: 'protocol', id: 'https://didcomm.org/revocation_notification/1.0', roles: ['holder'] }, + { type: 'protocol', id: 'https://didcomm.org/revocation_notification/2.0', roles: ['holder'] }, ], }) }) diff --git a/packages/core/src/modules/discover-features/protocol/v1/V1DiscoverFeaturesService.ts b/packages/core/src/modules/discover-features/protocol/v1/V1DiscoverFeaturesService.ts index ebe2a22006..e4718fbe6b 100644 --- a/packages/core/src/modules/discover-features/protocol/v1/V1DiscoverFeaturesService.ts +++ b/packages/core/src/modules/discover-features/protocol/v1/V1DiscoverFeaturesService.ts @@ -10,9 +10,9 @@ import type { DiscoverFeaturesProtocolMsgReturnType, } from '../../DiscoverFeaturesServiceOptions' -import { Dispatcher } from '../../../../agent/Dispatcher' import { EventEmitter } from '../../../../agent/EventEmitter' import { FeatureRegistry } from '../../../../agent/FeatureRegistry' +import { MessageHandlerRegistry } from '../../../../agent/MessageHandlerRegistry' import { Protocol } from '../../../../agent/models' import { InjectionSymbols } from '../../../../constants' import { AriesFrameworkError } from '../../../../error' @@ -30,13 +30,13 @@ export class V1DiscoverFeaturesService extends DiscoverFeaturesService { public constructor( featureRegistry: FeatureRegistry, eventEmitter: EventEmitter, - dispatcher: Dispatcher, + messageHandlerRegistry: MessageHandlerRegistry, @inject(InjectionSymbols.Logger) logger: Logger, discoverFeaturesConfig: DiscoverFeaturesModuleConfig ) { - super(featureRegistry, eventEmitter, dispatcher, logger, discoverFeaturesConfig) + super(featureRegistry, eventEmitter, logger, discoverFeaturesConfig) - this.registerMessageHandlers(dispatcher) + this.registerMessageHandlers(messageHandlerRegistry) } /** @@ -44,9 +44,9 @@ export class V1DiscoverFeaturesService extends DiscoverFeaturesService { */ public readonly version = 'v1' - private registerMessageHandlers(dispatcher: Dispatcher) { - dispatcher.registerMessageHandler(new V1DiscloseMessageHandler(this)) - dispatcher.registerMessageHandler(new V1QueryMessageHandler(this)) + private registerMessageHandlers(messageHandlerRegistry: MessageHandlerRegistry) { + messageHandlerRegistry.registerMessageHandler(new V1DiscloseMessageHandler(this)) + messageHandlerRegistry.registerMessageHandler(new V1QueryMessageHandler(this)) } public async createQuery( diff --git a/packages/core/src/modules/discover-features/protocol/v1/__tests__/V1DiscoverFeaturesService.test.ts b/packages/core/src/modules/discover-features/protocol/v1/__tests__/V1DiscoverFeaturesService.test.ts index 133a2b3442..03db2cf74a 100644 --- a/packages/core/src/modules/discover-features/protocol/v1/__tests__/V1DiscoverFeaturesService.test.ts +++ b/packages/core/src/modules/discover-features/protocol/v1/__tests__/V1DiscoverFeaturesService.test.ts @@ -7,9 +7,9 @@ import type { DiscoverFeaturesProtocolMsgReturnType } from '../../../DiscoverFea import { Subject } from 'rxjs' import { agentDependencies, getAgentContext, getMockConnection } from '../../../../../../tests/helpers' -import { Dispatcher } from '../../../../../agent/Dispatcher' import { EventEmitter } from '../../../../../agent/EventEmitter' import { FeatureRegistry } from '../../../../../agent/FeatureRegistry' +import { MessageHandlerRegistry } from '../../../../../agent/MessageHandlerRegistry' import { Protocol } from '../../../../../agent/models' import { InboundMessageContext } from '../../../../../agent/models/InboundMessageContext' import { ConsoleLogger } from '../../../../../logger/ConsoleLogger' @@ -19,8 +19,8 @@ import { DiscoverFeaturesModuleConfig } from '../../../DiscoverFeaturesModuleCon import { V1DiscoverFeaturesService } from '../V1DiscoverFeaturesService' import { V1DiscloseMessage, V1QueryMessage } from '../messages' -jest.mock('../../../../../agent/Dispatcher') -const DispatcherMock = Dispatcher as jest.Mock +jest.mock('../../../../../agent/MessageHandlerRegistry') +const MessageHandlerRegistryMock = MessageHandlerRegistry as jest.Mock const eventEmitter = new EventEmitter(agentDependencies, new Subject()) const featureRegistry = new FeatureRegistry() featureRegistry.register(new Protocol({ id: 'https://didcomm.org/connections/1.0' })) @@ -36,7 +36,7 @@ describe('V1DiscoverFeaturesService - auto accept queries', () => { const discoverFeaturesService = new V1DiscoverFeaturesService( featureRegistry, eventEmitter, - new DispatcherMock(), + new MessageHandlerRegistryMock(), new LoggerMock(), discoverFeaturesModuleConfig ) @@ -239,7 +239,7 @@ describe('V1DiscoverFeaturesService - auto accept disabled', () => { const discoverFeaturesService = new V1DiscoverFeaturesService( featureRegistry, eventEmitter, - new DispatcherMock(), + new MessageHandlerRegistry(), new LoggerMock(), discoverFeaturesModuleConfig ) diff --git a/packages/core/src/modules/discover-features/protocol/v2/V2DiscoverFeaturesService.ts b/packages/core/src/modules/discover-features/protocol/v2/V2DiscoverFeaturesService.ts index 2e007ae142..0196a351c4 100644 --- a/packages/core/src/modules/discover-features/protocol/v2/V2DiscoverFeaturesService.ts +++ b/packages/core/src/modules/discover-features/protocol/v2/V2DiscoverFeaturesService.ts @@ -9,9 +9,9 @@ import type { CreateDisclosureOptions, } from '../../DiscoverFeaturesServiceOptions' -import { Dispatcher } from '../../../../agent/Dispatcher' import { EventEmitter } from '../../../../agent/EventEmitter' import { FeatureRegistry } from '../../../../agent/FeatureRegistry' +import { MessageHandlerRegistry } from '../../../../agent/MessageHandlerRegistry' import { InjectionSymbols } from '../../../../constants' import { Logger } from '../../../../logger' import { inject, injectable } from '../../../../plugins' @@ -27,12 +27,12 @@ export class V2DiscoverFeaturesService extends DiscoverFeaturesService { public constructor( featureRegistry: FeatureRegistry, eventEmitter: EventEmitter, - dispatcher: Dispatcher, + messageHandlerRegistry: MessageHandlerRegistry, @inject(InjectionSymbols.Logger) logger: Logger, discoverFeaturesModuleConfig: DiscoverFeaturesModuleConfig ) { - super(featureRegistry, eventEmitter, dispatcher, logger, discoverFeaturesModuleConfig) - this.registerMessageHandlers(dispatcher) + super(featureRegistry, eventEmitter, logger, discoverFeaturesModuleConfig) + this.registerMessageHandlers(messageHandlerRegistry) } /** @@ -40,9 +40,9 @@ export class V2DiscoverFeaturesService extends DiscoverFeaturesService { */ public readonly version = 'v2' - private registerMessageHandlers(dispatcher: Dispatcher) { - dispatcher.registerMessageHandler(new V2DisclosuresMessageHandler(this)) - dispatcher.registerMessageHandler(new V2QueriesMessageHandler(this)) + private registerMessageHandlers(messageHandlerRegistry: MessageHandlerRegistry) { + messageHandlerRegistry.registerMessageHandler(new V2DisclosuresMessageHandler(this)) + messageHandlerRegistry.registerMessageHandler(new V2QueriesMessageHandler(this)) } public async createQuery( diff --git a/packages/core/src/modules/discover-features/protocol/v2/__tests__/V2DiscoverFeaturesService.test.ts b/packages/core/src/modules/discover-features/protocol/v2/__tests__/V2DiscoverFeaturesService.test.ts index 9669c9a63f..897fe5d1b4 100644 --- a/packages/core/src/modules/discover-features/protocol/v2/__tests__/V2DiscoverFeaturesService.test.ts +++ b/packages/core/src/modules/discover-features/protocol/v2/__tests__/V2DiscoverFeaturesService.test.ts @@ -7,9 +7,9 @@ import type { DiscoverFeaturesProtocolMsgReturnType } from '../../../DiscoverFea import { Subject } from 'rxjs' import { agentDependencies, getAgentContext, getMockConnection } from '../../../../../../tests/helpers' -import { Dispatcher } from '../../../../../agent/Dispatcher' import { EventEmitter } from '../../../../../agent/EventEmitter' import { FeatureRegistry } from '../../../../../agent/FeatureRegistry' +import { MessageHandlerRegistry } from '../../../../../agent/MessageHandlerRegistry' import { InboundMessageContext, Protocol, GoalCode } from '../../../../../agent/models' import { ConsoleLogger } from '../../../../../logger/ConsoleLogger' import { DidExchangeState } from '../../../../connections' @@ -18,8 +18,8 @@ import { DiscoverFeaturesModuleConfig } from '../../../DiscoverFeaturesModuleCon import { V2DiscoverFeaturesService } from '../V2DiscoverFeaturesService' import { V2DisclosuresMessage, V2QueriesMessage } from '../messages' -jest.mock('../../../../../agent/Dispatcher') -const DispatcherMock = Dispatcher as jest.Mock +jest.mock('../../../../../agent/MessageHandlerRegistry') +const MessageHandlerRegistryMock = MessageHandlerRegistry as jest.Mock const eventEmitter = new EventEmitter(agentDependencies, new Subject()) const featureRegistry = new FeatureRegistry() featureRegistry.register(new Protocol({ id: 'https://didcomm.org/connections/1.0' })) @@ -38,7 +38,7 @@ describe('V2DiscoverFeaturesService - auto accept queries', () => { const discoverFeaturesService = new V2DiscoverFeaturesService( featureRegistry, eventEmitter, - new DispatcherMock(), + new MessageHandlerRegistryMock(), new LoggerMock(), discoverFeaturesModuleConfig ) @@ -250,7 +250,7 @@ describe('V2DiscoverFeaturesService - auto accept disabled', () => { const discoverFeaturesService = new V2DiscoverFeaturesService( featureRegistry, eventEmitter, - new DispatcherMock(), + new MessageHandlerRegistryMock(), new LoggerMock(), discoverFeaturesModuleConfig ) diff --git a/packages/core/src/modules/discover-features/services/DiscoverFeaturesService.ts b/packages/core/src/modules/discover-features/services/DiscoverFeaturesService.ts index 188fd2f6a5..dfe4bd0f32 100644 --- a/packages/core/src/modules/discover-features/services/DiscoverFeaturesService.ts +++ b/packages/core/src/modules/discover-features/services/DiscoverFeaturesService.ts @@ -1,4 +1,3 @@ -import type { Dispatcher } from '../../../agent/Dispatcher' import type { EventEmitter } from '../../../agent/EventEmitter' import type { FeatureRegistry } from '../../../agent/FeatureRegistry' import type { InboundMessageContext } from '../../../agent/models/InboundMessageContext' @@ -14,33 +13,32 @@ import type { export abstract class DiscoverFeaturesService { protected featureRegistry: FeatureRegistry protected eventEmitter: EventEmitter - protected dispatcher: Dispatcher protected logger: Logger protected discoverFeaturesModuleConfig: DiscoverFeaturesModuleConfig public constructor( featureRegistry: FeatureRegistry, eventEmitter: EventEmitter, - dispatcher: Dispatcher, logger: Logger, discoverFeaturesModuleConfig: DiscoverFeaturesModuleConfig ) { this.featureRegistry = featureRegistry this.eventEmitter = eventEmitter - this.dispatcher = dispatcher this.logger = logger this.discoverFeaturesModuleConfig = discoverFeaturesModuleConfig } - abstract readonly version: string + public abstract readonly version: string - abstract createQuery(options: CreateQueryOptions): Promise> - abstract processQuery( + public abstract createQuery( + options: CreateQueryOptions + ): Promise> + public abstract processQuery( messageContext: InboundMessageContext ): Promise | void> - abstract createDisclosure( + public abstract createDisclosure( options: CreateDisclosureOptions ): Promise> - abstract processDisclosure(messageContext: InboundMessageContext): Promise + public abstract processDisclosure(messageContext: InboundMessageContext): Promise } diff --git a/packages/core/src/modules/generic-records/GenericRecordsApi.ts b/packages/core/src/modules/generic-records/GenericRecordsApi.ts index 56efe6667e..a995d7b4c5 100644 --- a/packages/core/src/modules/generic-records/GenericRecordsApi.ts +++ b/packages/core/src/modules/generic-records/GenericRecordsApi.ts @@ -1,5 +1,5 @@ -import type { Query } from '../../storage/StorageService' import type { GenericRecord, SaveGenericRecordOption } from './repository/GenericRecord' +import type { Query } from '../../storage/StorageService' import { AgentContext } from '../../agent' import { InjectionSymbols } from '../../constants' diff --git a/packages/core/src/modules/indy/IndyModule.ts b/packages/core/src/modules/indy/IndyModule.ts deleted file mode 100644 index 563a853874..0000000000 --- a/packages/core/src/modules/indy/IndyModule.ts +++ /dev/null @@ -1,16 +0,0 @@ -import type { DependencyManager, Module } from '../../plugins' - -import { IndyRevocationService, IndyUtilitiesService } from './services' -import { IndyHolderService } from './services/IndyHolderService' -import { IndyIssuerService } from './services/IndyIssuerService' -import { IndyVerifierService } from './services/IndyVerifierService' - -export class IndyModule implements Module { - public register(dependencyManager: DependencyManager) { - dependencyManager.registerSingleton(IndyIssuerService) - dependencyManager.registerSingleton(IndyHolderService) - dependencyManager.registerSingleton(IndyVerifierService) - dependencyManager.registerSingleton(IndyRevocationService) - dependencyManager.registerSingleton(IndyUtilitiesService) - } -} diff --git a/packages/core/src/modules/indy/__tests__/IndyModule.test.ts b/packages/core/src/modules/indy/__tests__/IndyModule.test.ts deleted file mode 100644 index edad08f2d6..0000000000 --- a/packages/core/src/modules/indy/__tests__/IndyModule.test.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { DependencyManager } from '../../../plugins/DependencyManager' -import { IndyModule } from '../IndyModule' -import { - IndyHolderService, - IndyIssuerService, - IndyVerifierService, - IndyRevocationService, - IndyUtilitiesService, -} from '../services' - -jest.mock('../../../plugins/DependencyManager') -const DependencyManagerMock = DependencyManager as jest.Mock - -const dependencyManager = new DependencyManagerMock() - -describe('IndyModule', () => { - test('registers dependencies on the dependency manager', () => { - new IndyModule().register(dependencyManager) - - expect(dependencyManager.registerSingleton).toHaveBeenCalledTimes(5) - expect(dependencyManager.registerSingleton).toHaveBeenCalledWith(IndyHolderService) - expect(dependencyManager.registerSingleton).toHaveBeenCalledWith(IndyIssuerService) - expect(dependencyManager.registerSingleton).toHaveBeenCalledWith(IndyRevocationService) - expect(dependencyManager.registerSingleton).toHaveBeenCalledWith(IndyVerifierService) - expect(dependencyManager.registerSingleton).toHaveBeenCalledWith(IndyUtilitiesService) - }) -}) diff --git a/packages/core/src/modules/indy/index.ts b/packages/core/src/modules/indy/index.ts deleted file mode 100644 index 5b289c3de8..0000000000 --- a/packages/core/src/modules/indy/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from './services' -export * from './IndyModule' diff --git a/packages/core/src/modules/indy/repository/AnonCredsCredentialDefinitionRecord.ts b/packages/core/src/modules/indy/repository/AnonCredsCredentialDefinitionRecord.ts deleted file mode 100644 index 699abb6148..0000000000 --- a/packages/core/src/modules/indy/repository/AnonCredsCredentialDefinitionRecord.ts +++ /dev/null @@ -1,31 +0,0 @@ -import type { CredDef } from 'indy-sdk' - -import { BaseRecord } from '../../../storage/BaseRecord' -import { uuid } from '../../../utils/uuid' - -export interface AnonCredsCredentialDefinitionRecordProps { - credentialDefinition: CredDef -} - -export class AnonCredsCredentialDefinitionRecord extends BaseRecord { - public static readonly type = 'AnonCredsCredentialDefinitionRecord' - public readonly type = AnonCredsCredentialDefinitionRecord.type - - public readonly credentialDefinition!: CredDef - - public constructor(props: AnonCredsCredentialDefinitionRecordProps) { - super() - - if (props) { - this.id = uuid() - this.credentialDefinition = props.credentialDefinition - } - } - - public getTags() { - return { - ...this._tags, - credentialDefinitionId: this.credentialDefinition.id, - } - } -} diff --git a/packages/core/src/modules/indy/repository/AnonCredsSchemaRecord.ts b/packages/core/src/modules/indy/repository/AnonCredsSchemaRecord.ts deleted file mode 100644 index 70eb12df38..0000000000 --- a/packages/core/src/modules/indy/repository/AnonCredsSchemaRecord.ts +++ /dev/null @@ -1,43 +0,0 @@ -import type { Schema } from 'indy-sdk' - -import { BaseRecord } from '../../../storage/BaseRecord' -import { didFromSchemaId } from '../../../utils/did' -import { uuid } from '../../../utils/uuid' - -export interface AnonCredsSchemaRecordProps { - schema: Schema - id?: string -} - -export type DefaultAnonCredsSchemaTags = { - schemaId: string - schemaIssuerDid: string - schemaName: string - schemaVersion: string -} - -export class AnonCredsSchemaRecord extends BaseRecord { - public static readonly type = 'AnonCredsSchemaRecord' - public readonly type = AnonCredsSchemaRecord.type - - public readonly schema!: Schema - - public constructor(props: AnonCredsSchemaRecordProps) { - super() - - if (props) { - this.id = props.id ?? uuid() - this.schema = props.schema - } - } - - public getTags() { - return { - ...this._tags, - schemaId: this.schema.id, - schemaIssuerDid: didFromSchemaId(this.schema.id), - schemaName: this.schema.name, - schemaVersion: this.schema.version, - } - } -} diff --git a/packages/core/src/modules/indy/services/IndyHolderService.ts b/packages/core/src/modules/indy/services/IndyHolderService.ts deleted file mode 100644 index a53f2b7049..0000000000 --- a/packages/core/src/modules/indy/services/IndyHolderService.ts +++ /dev/null @@ -1,294 +0,0 @@ -import type { AgentContext } from '../../../agent' -import type { RequestedCredentials } from '../../proofs/formats/indy/models/RequestedCredentials' -import type * as Indy from 'indy-sdk' - -import { AgentDependencies } from '../../../agent/AgentDependencies' -import { InjectionSymbols } from '../../../constants' -import { IndySdkError } from '../../../error/IndySdkError' -import { Logger } from '../../../logger' -import { injectable, inject } from '../../../plugins' -import { isIndyError } from '../../../utils/indyError' -import { assertIndyWallet } from '../../../wallet/util/assertIndyWallet' - -import { IndyRevocationService } from './IndyRevocationService' - -@injectable() -export class IndyHolderService { - private indy: typeof Indy - private logger: Logger - private indyRevocationService: IndyRevocationService - - public constructor( - indyRevocationService: IndyRevocationService, - @inject(InjectionSymbols.Logger) logger: Logger, - @inject(InjectionSymbols.AgentDependencies) agentDependencies: AgentDependencies - ) { - this.indy = agentDependencies.indy - this.indyRevocationService = indyRevocationService - this.logger = logger - } - - /** - * Creates an Indy Proof in response to a proof request. Will create revocation state if the proof request requests proof of non-revocation - * - * @param proofRequest a Indy proof request - * @param requestedCredentials the requested credentials to use for the proof creation - * @param schemas schemas to use in proof creation - * @param credentialDefinitions credential definitions to use in proof creation - * @throws {Error} if there is an error during proof generation or revocation state generation - * @returns a promise of Indy Proof - * - * @todo support attribute non_revoked fields - */ - public async createProof( - agentContext: AgentContext, - { proofRequest, requestedCredentials, schemas, credentialDefinitions }: CreateProofOptions - ): Promise { - assertIndyWallet(agentContext.wallet) - try { - this.logger.debug('Creating Indy Proof') - const revocationStates: Indy.RevStates = await this.indyRevocationService.createRevocationState( - agentContext, - proofRequest, - requestedCredentials - ) - - const indyProof: Indy.IndyProof = await this.indy.proverCreateProof( - agentContext.wallet.handle, - proofRequest, - requestedCredentials.toJSON(), - agentContext.wallet.masterSecretId, - schemas, - credentialDefinitions, - revocationStates - ) - - this.logger.trace('Created Indy Proof', { - indyProof, - }) - - return indyProof - } catch (error) { - this.logger.error(`Error creating Indy Proof`, { - error, - proofRequest, - requestedCredentials, - }) - - throw isIndyError(error) ? new IndySdkError(error) : error - } - } - - /** - * Store a credential in the wallet. - * - * @returns The credential id - */ - public async storeCredential( - agentContext: AgentContext, - { - credentialRequestMetadata, - credential, - credentialDefinition, - credentialId, - revocationRegistryDefinition, - }: StoreCredentialOptions - ): Promise { - assertIndyWallet(agentContext.wallet) - try { - return await this.indy.proverStoreCredential( - agentContext.wallet.handle, - credentialId ?? null, - credentialRequestMetadata, - credential, - credentialDefinition, - revocationRegistryDefinition ?? null - ) - } catch (error) { - this.logger.error(`Error storing Indy Credential '${credentialId}'`, { - error, - }) - - throw isIndyError(error) ? new IndySdkError(error) : error - } - } - - /** - * Get a credential stored in the wallet by id. - * - * @param credentialId the id (referent) of the credential - * @throws {Error} if the credential is not found - * @returns the credential - * - * @todo handle record not found - */ - public async getCredential( - agentContext: AgentContext, - credentialId: Indy.CredentialId - ): Promise { - assertIndyWallet(agentContext.wallet) - try { - return await this.indy.proverGetCredential(agentContext.wallet.handle, credentialId) - } catch (error) { - this.logger.error(`Error getting Indy Credential '${credentialId}'`, { - error, - }) - - throw isIndyError(error) ? new IndySdkError(error) : error - } - } - - /** - * Create a credential request for the given credential offer. - * - * @returns The credential request and the credential request metadata - */ - public async createCredentialRequest( - agentContext: AgentContext, - { holderDid, credentialOffer, credentialDefinition }: CreateCredentialRequestOptions - ): Promise<[Indy.CredReq, Indy.CredReqMetadata]> { - assertIndyWallet(agentContext.wallet) - try { - return await this.indy.proverCreateCredentialReq( - agentContext.wallet.handle, - holderDid, - credentialOffer, - credentialDefinition, - agentContext.wallet.masterSecretId - ) - } catch (error) { - this.logger.error(`Error creating Indy Credential Request`, { - error, - credentialOffer, - }) - - throw isIndyError(error) ? new IndySdkError(error) : error - } - } - - /** - * Retrieve the credentials that are available for an attribute referent in the proof request. - * - * @param proofRequest The proof request to retrieve the credentials for - * @param attributeReferent An attribute referent from the proof request to retrieve the credentials for - * @param start Starting index - * @param limit Maximum number of records to return - * - * @returns List of credentials that are available for building a proof for the given proof request - * - */ - public async getCredentialsForProofRequest( - agentContext: AgentContext, - { proofRequest, attributeReferent, start = 0, limit = 256, extraQuery }: GetCredentialForProofRequestOptions - ): Promise { - assertIndyWallet(agentContext.wallet) - try { - // Open indy credential search - const searchHandle = await this.indy.proverSearchCredentialsForProofReq( - agentContext.wallet.handle, - proofRequest, - extraQuery ?? null - ) - - try { - // Make sure database cursors start at 'start' (bit ugly, but no way around in indy) - if (start > 0) { - await this.fetchCredentialsForReferent(searchHandle, attributeReferent, start) - } - - // Fetch the credentials - const credentials = await this.fetchCredentialsForReferent(searchHandle, attributeReferent, limit) - - // TODO: sort the credentials (irrevocable first) - return credentials - } finally { - // Always close search - await this.indy.proverCloseCredentialsSearchForProofReq(searchHandle) - } - } catch (error) { - if (isIndyError(error)) { - throw new IndySdkError(error) - } - - throw error - } - } - - /** - * Delete a credential stored in the wallet by id. - * - * @param credentialId the id (referent) of the credential - * - */ - public async deleteCredential(agentContext: AgentContext, credentialId: Indy.CredentialId): Promise { - assertIndyWallet(agentContext.wallet) - try { - return await this.indy.proverDeleteCredential(agentContext.wallet.handle, credentialId) - } catch (error) { - this.logger.error(`Error deleting Indy Credential from Wallet`, { - error, - }) - - throw isIndyError(error) ? new IndySdkError(error) : error - } - } - - private async fetchCredentialsForReferent(searchHandle: number, referent: string, limit?: number) { - try { - let credentials: Indy.IndyCredential[] = [] - - // Allow max of 256 per fetch operation - const chunk = limit ? Math.min(256, limit) : 256 - - // Loop while limit not reached (or no limit specified) - while (!limit || credentials.length < limit) { - // Retrieve credentials - const credentialsJson = await this.indy.proverFetchCredentialsForProofReq(searchHandle, referent, chunk) - credentials = [...credentials, ...credentialsJson] - - // If the number of credentials returned is less than chunk - // It means we reached the end of the iterator (no more credentials) - if (credentialsJson.length < chunk) { - return credentials - } - } - - return credentials - } catch (error) { - this.logger.error(`Error Fetching Indy Credentials For Referent`, { - error, - }) - - throw isIndyError(error) ? new IndySdkError(error) : error - } - } -} - -export interface GetCredentialForProofRequestOptions { - proofRequest: Indy.IndyProofRequest - attributeReferent: string - start?: number - limit?: number - extraQuery?: Indy.ReferentWalletQuery -} - -export interface CreateCredentialRequestOptions { - holderDid: string - credentialOffer: Indy.CredOffer - credentialDefinition: Indy.CredDef -} - -export interface StoreCredentialOptions { - credentialRequestMetadata: Indy.CredReqMetadata - credential: Indy.Cred - credentialDefinition: Indy.CredDef - credentialId?: Indy.CredentialId - revocationRegistryDefinition?: Indy.RevocRegDef -} - -export interface CreateProofOptions { - proofRequest: Indy.IndyProofRequest - requestedCredentials: RequestedCredentials - schemas: Indy.Schemas - credentialDefinitions: Indy.CredentialDefs -} diff --git a/packages/core/src/modules/indy/services/IndyIssuerService.ts b/packages/core/src/modules/indy/services/IndyIssuerService.ts deleted file mode 100644 index 58e9917cf0..0000000000 --- a/packages/core/src/modules/indy/services/IndyIssuerService.ts +++ /dev/null @@ -1,166 +0,0 @@ -import type { AgentContext } from '../../../agent' -import type { - Cred, - CredDef, - CredDefId, - CredOffer, - CredReq, - CredRevocId, - CredValues, - default as Indy, - Schema, -} from 'indy-sdk' - -import { AgentDependencies } from '../../../agent/AgentDependencies' -import { InjectionSymbols } from '../../../constants' -import { AriesFrameworkError } from '../../../error/AriesFrameworkError' -import { IndySdkError } from '../../../error/IndySdkError' -import { injectable, inject } from '../../../plugins' -import { isIndyError } from '../../../utils/indyError' -import { assertIndyWallet } from '../../../wallet/util/assertIndyWallet' - -import { IndyUtilitiesService } from './IndyUtilitiesService' - -@injectable() -export class IndyIssuerService { - private indy: typeof Indy - private indyUtilitiesService: IndyUtilitiesService - - public constructor( - indyUtilitiesService: IndyUtilitiesService, - @inject(InjectionSymbols.AgentDependencies) agentDependencies: AgentDependencies - ) { - this.indy = agentDependencies.indy - this.indyUtilitiesService = indyUtilitiesService - } - - /** - * Create a new credential schema. - * - * @returns the schema. - */ - public async createSchema( - agentContext: AgentContext, - { originDid, name, version, attributes }: CreateSchemaOptions - ): Promise { - assertIndyWallet(agentContext.wallet) - try { - const [, schema] = await this.indy.issuerCreateSchema(originDid, name, version, attributes) - - return schema - } catch (error) { - throw isIndyError(error) ? new IndySdkError(error) : error - } - } - - /** - * Create a new credential definition and store it in the wallet. - * - * @returns the credential definition. - */ - public async createCredentialDefinition( - agentContext: AgentContext, - { - issuerDid, - schema, - tag = 'default', - signatureType = 'CL', - supportRevocation = false, - }: CreateCredentialDefinitionOptions - ): Promise { - assertIndyWallet(agentContext.wallet) - try { - const [, credentialDefinition] = await this.indy.issuerCreateAndStoreCredentialDef( - agentContext.wallet.handle, - issuerDid, - schema, - tag, - signatureType, - { - support_revocation: supportRevocation, - } - ) - - return credentialDefinition - } catch (error) { - throw isIndyError(error) ? new IndySdkError(error) : error - } - } - - /** - * Create a credential offer for the given credential definition id. - * - * @param credentialDefinitionId The credential definition to create an offer for - * @returns The created credential offer - */ - public async createCredentialOffer(agentContext: AgentContext, credentialDefinitionId: CredDefId) { - assertIndyWallet(agentContext.wallet) - try { - return await this.indy.issuerCreateCredentialOffer(agentContext.wallet.handle, credentialDefinitionId) - } catch (error) { - throw isIndyError(error) ? new IndySdkError(error) : error - } - } - - /** - * Create a credential. - * - * @returns Credential and revocation id - */ - public async createCredential( - agentContext: AgentContext, - { - credentialOffer, - credentialRequest, - credentialValues, - revocationRegistryId, - tailsFilePath, - }: CreateCredentialOptions - ): Promise<[Cred, CredRevocId]> { - assertIndyWallet(agentContext.wallet) - try { - // Indy SDK requires tailsReaderHandle. Use null if no tailsFilePath is present - const tailsReaderHandle = tailsFilePath ? await this.indyUtilitiesService.createTailsReader(tailsFilePath) : 0 - - if (revocationRegistryId || tailsFilePath) { - throw new AriesFrameworkError('Revocation not supported yet') - } - - const [credential, credentialRevocationId] = await this.indy.issuerCreateCredential( - agentContext.wallet.handle, - credentialOffer, - credentialRequest, - credentialValues, - revocationRegistryId ?? null, - tailsReaderHandle - ) - - return [credential, credentialRevocationId] - } catch (error) { - throw isIndyError(error) ? new IndySdkError(error) : error - } - } -} - -export interface CreateCredentialDefinitionOptions { - issuerDid: string - schema: Schema - tag?: string - signatureType?: 'CL' - supportRevocation?: boolean -} - -export interface CreateCredentialOptions { - credentialOffer: CredOffer - credentialRequest: CredReq - credentialValues: CredValues - revocationRegistryId?: string - tailsFilePath?: string -} - -export interface CreateSchemaOptions { - originDid: string - name: string - version: string - attributes: string[] -} diff --git a/packages/core/src/modules/indy/services/IndyRevocationService.ts b/packages/core/src/modules/indy/services/IndyRevocationService.ts deleted file mode 100644 index 52c666d3ca..0000000000 --- a/packages/core/src/modules/indy/services/IndyRevocationService.ts +++ /dev/null @@ -1,198 +0,0 @@ -import type { AgentContext } from '../../../agent' -import type { IndyRevocationInterval } from '../../credentials' -import type { RequestedCredentials } from '../../proofs/formats/indy/models/RequestedCredentials' -import type { default as Indy } from 'indy-sdk' - -import { AgentDependencies } from '../../../agent/AgentDependencies' -import { InjectionSymbols } from '../../../constants' -import { AriesFrameworkError } from '../../../error/AriesFrameworkError' -import { IndySdkError } from '../../../error/IndySdkError' -import { Logger } from '../../../logger' -import { injectable, inject } from '../../../plugins' -import { isIndyError } from '../../../utils/indyError' -import { IndyLedgerService } from '../../ledger' - -import { IndyUtilitiesService } from './IndyUtilitiesService' - -enum RequestReferentType { - Attribute = 'attribute', - Predicate = 'predicate', - SelfAttestedAttribute = 'self-attested-attribute', -} -@injectable() -export class IndyRevocationService { - private indy: typeof Indy - private indyUtilitiesService: IndyUtilitiesService - private ledgerService: IndyLedgerService - private logger: Logger - - public constructor( - indyUtilitiesService: IndyUtilitiesService, - ledgerService: IndyLedgerService, - @inject(InjectionSymbols.AgentDependencies) agentDependencies: AgentDependencies, - @inject(InjectionSymbols.Logger) logger: Logger - ) { - this.indy = agentDependencies.indy - this.indyUtilitiesService = indyUtilitiesService - this.logger = logger - this.ledgerService = ledgerService - } - - public async createRevocationState( - agentContext: AgentContext, - proofRequest: Indy.IndyProofRequest, - requestedCredentials: RequestedCredentials - ): Promise { - try { - this.logger.debug(`Creating Revocation State(s) for proof request`, { - proofRequest, - requestedCredentials, - }) - const revocationStates: Indy.RevStates = {} - const referentCredentials = [] - - //Retrieve information for referents and push to single array - for (const [referent, requestedCredential] of Object.entries(requestedCredentials.requestedAttributes)) { - referentCredentials.push({ - referent, - credentialInfo: requestedCredential.credentialInfo, - type: RequestReferentType.Attribute, - }) - } - for (const [referent, requestedCredential] of Object.entries(requestedCredentials.requestedPredicates)) { - referentCredentials.push({ - referent, - credentialInfo: requestedCredential.credentialInfo, - type: RequestReferentType.Predicate, - }) - } - - for (const { referent, credentialInfo, type } of referentCredentials) { - if (!credentialInfo) { - throw new AriesFrameworkError( - `Credential for referent '${referent} does not have credential info for revocation state creation` - ) - } - - // Prefer referent-specific revocation interval over global revocation interval - const referentRevocationInterval = - type === RequestReferentType.Predicate - ? proofRequest.requested_predicates[referent].non_revoked - : proofRequest.requested_attributes[referent].non_revoked - const requestRevocationInterval = referentRevocationInterval ?? proofRequest.non_revoked - const credentialRevocationId = credentialInfo.credentialRevocationId - const revocationRegistryId = credentialInfo.revocationRegistryId - - // If revocation interval is present and the credential is revocable then create revocation state - if (requestRevocationInterval && credentialRevocationId && revocationRegistryId) { - this.logger.trace( - `Presentation is requesting proof of non revocation for ${type} referent '${referent}', creating revocation state for credential`, - { - requestRevocationInterval, - credentialRevocationId, - revocationRegistryId, - } - ) - - this.assertRevocationInterval(requestRevocationInterval) - - const { revocationRegistryDefinition } = await this.ledgerService.getRevocationRegistryDefinition( - agentContext, - revocationRegistryId - ) - - const { revocationRegistryDelta, deltaTimestamp } = await this.ledgerService.getRevocationRegistryDelta( - agentContext, - revocationRegistryId, - requestRevocationInterval?.to, - 0 - ) - - const { tailsLocation, tailsHash } = revocationRegistryDefinition.value - const tails = await this.indyUtilitiesService.downloadTails(tailsHash, tailsLocation) - - const revocationState = await this.indy.createRevocationState( - tails, - revocationRegistryDefinition, - revocationRegistryDelta, - deltaTimestamp, - credentialRevocationId - ) - const timestamp = revocationState.timestamp - - if (!revocationStates[revocationRegistryId]) { - revocationStates[revocationRegistryId] = {} - } - revocationStates[revocationRegistryId][timestamp] = revocationState - } - } - - this.logger.debug(`Created Revocation States for Proof Request`, { - revocationStates, - }) - - return revocationStates - } catch (error) { - this.logger.error(`Error creating Indy Revocation State for Proof Request`, { - error, - proofRequest, - requestedCredentials, - }) - - throw isIndyError(error) ? new IndySdkError(error) : error - } - } - - // Get revocation status for credential (given a from-to) - // Note from-to interval details: https://github.com/hyperledger/indy-hipe/blob/master/text/0011-cred-revocation/README.md#indy-node-revocation-registry-intervals - public async getRevocationStatus( - agentContext: AgentContext, - credentialRevocationId: string, - revocationRegistryDefinitionId: string, - requestRevocationInterval: IndyRevocationInterval - ): Promise<{ revoked: boolean; deltaTimestamp: number }> { - this.logger.trace( - `Fetching Credential Revocation Status for Credential Revocation Id '${credentialRevocationId}' with revocation interval with to '${requestRevocationInterval.to}' & from '${requestRevocationInterval.from}'` - ) - - this.assertRevocationInterval(requestRevocationInterval) - - const { revocationRegistryDelta, deltaTimestamp } = await this.ledgerService.getRevocationRegistryDelta( - agentContext, - revocationRegistryDefinitionId, - requestRevocationInterval.to, - 0 - ) - - const revoked: boolean = revocationRegistryDelta.value.revoked?.includes(parseInt(credentialRevocationId)) || false - this.logger.trace( - `Credential with Credential Revocation Id '${credentialRevocationId}' is ${ - revoked ? '' : 'not ' - }revoked with revocation interval with to '${requestRevocationInterval.to}' & from '${ - requestRevocationInterval.from - }'` - ) - - return { - revoked, - deltaTimestamp, - } - } - - // TODO: Add Test - // Check revocation interval in accordance with https://github.com/hyperledger/aries-rfcs/blob/main/concepts/0441-present-proof-best-practices/README.md#semantics-of-non-revocation-interval-endpoints - private assertRevocationInterval(requestRevocationInterval: IndyRevocationInterval) { - if (!requestRevocationInterval.to) { - throw new AriesFrameworkError(`Presentation requests proof of non-revocation with no 'to' value specified`) - } - - if ( - (requestRevocationInterval.from || requestRevocationInterval.from === 0) && - requestRevocationInterval.to !== requestRevocationInterval.from - ) { - throw new AriesFrameworkError( - `Presentation requests proof of non-revocation with an interval from: '${requestRevocationInterval.from}' that does not match the interval to: '${requestRevocationInterval.to}', as specified in Aries RFC 0441` - ) - } - } -} diff --git a/packages/core/src/modules/indy/services/IndyUtilitiesService.ts b/packages/core/src/modules/indy/services/IndyUtilitiesService.ts deleted file mode 100644 index eef01ccfd2..0000000000 --- a/packages/core/src/modules/indy/services/IndyUtilitiesService.ts +++ /dev/null @@ -1,88 +0,0 @@ -import type { BlobReaderHandle, default as Indy } from 'indy-sdk' - -import { AgentDependencies } from '../../../agent/AgentDependencies' -import { InjectionSymbols } from '../../../constants' -import { AriesFrameworkError } from '../../../error' -import { IndySdkError } from '../../../error/IndySdkError' -import { Logger } from '../../../logger' -import { injectable, inject } from '../../../plugins' -import { FileSystem } from '../../../storage/FileSystem' -import { isIndyError } from '../../../utils/indyError' -import { getDirFromFilePath } from '../../../utils/path' - -@injectable() -export class IndyUtilitiesService { - private indy: typeof Indy - private logger: Logger - private fileSystem: FileSystem - - public constructor( - @inject(InjectionSymbols.Logger) logger: Logger, - @inject(InjectionSymbols.FileSystem) fileSystem: FileSystem, - @inject(InjectionSymbols.AgentDependencies) agentDependencies: AgentDependencies - ) { - this.indy = agentDependencies.indy - this.logger = logger - this.fileSystem = fileSystem - } - - /** - * Get a handler for the blob storage tails file reader. - * - * @param tailsFilePath The path of the tails file - * @returns The blob storage reader handle - */ - public async createTailsReader(tailsFilePath: string): Promise { - try { - this.logger.debug(`Opening tails reader at path ${tailsFilePath}`) - const tailsFileExists = await this.fileSystem.exists(tailsFilePath) - - // Extract directory from path (should also work with windows paths) - const dirname = getDirFromFilePath(tailsFilePath) - - if (!tailsFileExists) { - throw new AriesFrameworkError(`Tails file does not exist at path ${tailsFilePath}`) - } - - const tailsReaderConfig = { - base_dir: dirname, - } - - const tailsReader = await this.indy.openBlobStorageReader('default', tailsReaderConfig) - this.logger.debug(`Opened tails reader at path ${tailsFilePath}`) - return tailsReader - } catch (error) { - if (isIndyError(error)) { - throw new IndySdkError(error) - } - - throw error - } - } - - public async downloadTails(hash: string, tailsLocation: string): Promise { - try { - this.logger.debug(`Checking to see if tails file for URL ${tailsLocation} has been stored in the FileSystem`) - const filePath = `${this.fileSystem.basePath}/afj/tails/${hash}` - - const tailsExists = await this.fileSystem.exists(filePath) - this.logger.debug(`Tails file for ${tailsLocation} ${tailsExists ? 'is stored' : 'is not stored'} at ${filePath}`) - if (!tailsExists) { - this.logger.debug(`Retrieving tails file from URL ${tailsLocation}`) - - await this.fileSystem.downloadToFile(tailsLocation, filePath) - this.logger.debug(`Saved tails file to FileSystem at path ${filePath}`) - - //TODO: Validate Tails File Hash - } - - this.logger.debug(`Tails file for URL ${tailsLocation} is stored in the FileSystem, opening tails reader`) - return this.createTailsReader(filePath) - } catch (error) { - this.logger.error(`Error while retrieving tails file from URL ${tailsLocation}`, { - error, - }) - throw isIndyError(error) ? new IndySdkError(error) : error - } - } -} diff --git a/packages/core/src/modules/indy/services/IndyVerifierService.ts b/packages/core/src/modules/indy/services/IndyVerifierService.ts deleted file mode 100644 index b6cc387c31..0000000000 --- a/packages/core/src/modules/indy/services/IndyVerifierService.ts +++ /dev/null @@ -1,85 +0,0 @@ -import type { AgentContext } from '../../../agent' -import type * as Indy from 'indy-sdk' - -import { AgentDependencies } from '../../../agent/AgentDependencies' -import { InjectionSymbols } from '../../../constants' -import { IndySdkError } from '../../../error' -import { injectable, inject } from '../../../plugins' -import { isIndyError } from '../../../utils/indyError' -import { IndyLedgerService } from '../../ledger/services/IndyLedgerService' - -@injectable() -export class IndyVerifierService { - private indy: typeof Indy - private ledgerService: IndyLedgerService - - public constructor( - ledgerService: IndyLedgerService, - @inject(InjectionSymbols.AgentDependencies) agentDependencies: AgentDependencies - ) { - this.indy = agentDependencies.indy - this.ledgerService = ledgerService - } - - public async verifyProof( - agentContext: AgentContext, - { proofRequest, proof, schemas, credentialDefinitions }: VerifyProofOptions - ): Promise { - try { - const { revocationRegistryDefinitions, revocationRegistryStates } = await this.getRevocationRegistries( - agentContext, - proof - ) - - return await this.indy.verifierVerifyProof( - proofRequest, - proof, - schemas, - credentialDefinitions, - revocationRegistryDefinitions, - revocationRegistryStates - ) - } catch (error) { - throw isIndyError(error) ? new IndySdkError(error) : error - } - } - - private async getRevocationRegistries(agentContext: AgentContext, proof: Indy.IndyProof) { - const revocationRegistryDefinitions: Indy.RevocRegDefs = {} - const revocationRegistryStates: Indy.RevStates = Object.create(null) - for (const identifier of proof.identifiers) { - const revocationRegistryId = identifier.rev_reg_id - const timestamp = identifier.timestamp - - //Fetch Revocation Registry Definition if not already fetched - if (revocationRegistryId && !revocationRegistryDefinitions[revocationRegistryId]) { - const { revocationRegistryDefinition } = await this.ledgerService.getRevocationRegistryDefinition( - agentContext, - revocationRegistryId - ) - revocationRegistryDefinitions[revocationRegistryId] = revocationRegistryDefinition - } - - //Fetch Revocation Registry by Timestamp if not already fetched - if (revocationRegistryId && timestamp && !revocationRegistryStates[revocationRegistryId]?.[timestamp]) { - if (!revocationRegistryStates[revocationRegistryId]) { - revocationRegistryStates[revocationRegistryId] = Object.create(null) - } - const { revocationRegistry } = await this.ledgerService.getRevocationRegistry( - agentContext, - revocationRegistryId, - timestamp - ) - revocationRegistryStates[revocationRegistryId][timestamp] = revocationRegistry - } - } - return { revocationRegistryDefinitions, revocationRegistryStates } - } -} - -export interface VerifyProofOptions { - proofRequest: Indy.IndyProofRequest - proof: Indy.IndyProof - schemas: Indy.Schemas - credentialDefinitions: Indy.CredentialDefs -} diff --git a/packages/core/src/modules/indy/services/__mocks__/IndyHolderService.ts b/packages/core/src/modules/indy/services/__mocks__/IndyHolderService.ts deleted file mode 100644 index 35afdc14ab..0000000000 --- a/packages/core/src/modules/indy/services/__mocks__/IndyHolderService.ts +++ /dev/null @@ -1,20 +0,0 @@ -import type { CreateCredentialRequestOptions, StoreCredentialOptions } from '../IndyHolderService' - -export const IndyHolderService = jest.fn(() => ({ - storeCredential: jest.fn((_, { credentialId }: StoreCredentialOptions) => - Promise.resolve(credentialId ?? 'some-random-uuid') - ), - deleteCredential: jest.fn(() => Promise.resolve()), - createCredentialRequest: jest.fn((_, { holderDid, credentialDefinition }: CreateCredentialRequestOptions) => - Promise.resolve([ - { - prover_did: holderDid, - cred_def_id: credentialDefinition.id, - blinded_ms: {}, - blinded_ms_correctness_proof: {}, - nonce: 'nonce', - }, - { cred_req: 'meta-data' }, - ]) - ), -})) diff --git a/packages/core/src/modules/indy/services/__mocks__/IndyIssuerService.ts b/packages/core/src/modules/indy/services/__mocks__/IndyIssuerService.ts deleted file mode 100644 index 823e961a15..0000000000 --- a/packages/core/src/modules/indy/services/__mocks__/IndyIssuerService.ts +++ /dev/null @@ -1,25 +0,0 @@ -export const IndyIssuerService = jest.fn(() => ({ - createCredential: jest.fn(() => - Promise.resolve([ - { - schema_id: 'schema_id', - cred_def_id: 'cred_def_id', - rev_reg_def_id: 'rev_reg_def_id', - values: {}, - signature: 'signature', - signature_correctness_proof: 'signature_correctness_proof', - }, - '1', - ]) - ), - - createCredentialOffer: jest.fn((_, credentialDefinitionId: string) => - Promise.resolve({ - schema_id: 'aaa', - cred_def_id: credentialDefinitionId, - // Fields below can depend on Cred Def type - nonce: 'nonce', - key_correctness_proof: {}, - }) - ), -})) diff --git a/packages/core/src/modules/indy/services/__mocks__/IndyVerifierService.ts b/packages/core/src/modules/indy/services/__mocks__/IndyVerifierService.ts deleted file mode 100644 index 483a384f67..0000000000 --- a/packages/core/src/modules/indy/services/__mocks__/IndyVerifierService.ts +++ /dev/null @@ -1 +0,0 @@ -export const IndyVerifierService = jest.fn(() => ({})) diff --git a/packages/core/src/modules/indy/services/index.ts b/packages/core/src/modules/indy/services/index.ts deleted file mode 100644 index fa01eaf419..0000000000 --- a/packages/core/src/modules/indy/services/index.ts +++ /dev/null @@ -1,5 +0,0 @@ -export * from './IndyHolderService' -export * from './IndyIssuerService' -export * from './IndyVerifierService' -export * from './IndyUtilitiesService' -export * from './IndyRevocationService' diff --git a/packages/core/src/modules/ledger/LedgerApi.ts b/packages/core/src/modules/ledger/LedgerApi.ts deleted file mode 100644 index bb836cf50d..0000000000 --- a/packages/core/src/modules/ledger/LedgerApi.ts +++ /dev/null @@ -1,217 +0,0 @@ -import type { IndyPoolConfig } from './IndyPool' -import type { SchemaTemplate, CredentialDefinitionTemplate } from './services' -import type { CredDef, NymRole, Schema } from 'indy-sdk' - -import { AgentContext } from '../../agent' -import { AriesFrameworkError } from '../../error' -import { IndySdkError } from '../../error/IndySdkError' -import { injectable } from '../../plugins' -import { isIndyError } from '../../utils/indyError' -import { - getLegacyCredentialDefinitionId, - getLegacySchemaId, - getQualifiedIndyCredentialDefinitionId, - getQualifiedIndySchemaId, -} from '../../utils/indyIdentifiers' -import { AnonCredsCredentialDefinitionRecord } from '../indy/repository/AnonCredsCredentialDefinitionRecord' -import { AnonCredsCredentialDefinitionRepository } from '../indy/repository/AnonCredsCredentialDefinitionRepository' -import { AnonCredsSchemaRecord } from '../indy/repository/AnonCredsSchemaRecord' -import { AnonCredsSchemaRepository } from '../indy/repository/AnonCredsSchemaRepository' - -import { LedgerModuleConfig } from './LedgerModuleConfig' -import { IndyLedgerService } from './services' - -@injectable() -export class LedgerApi { - public config: LedgerModuleConfig - - private ledgerService: IndyLedgerService - private agentContext: AgentContext - private anonCredsCredentialDefinitionRepository: AnonCredsCredentialDefinitionRepository - private anonCredsSchemaRepository: AnonCredsSchemaRepository - - public constructor( - ledgerService: IndyLedgerService, - agentContext: AgentContext, - anonCredsCredentialDefinitionRepository: AnonCredsCredentialDefinitionRepository, - anonCredsSchemaRepository: AnonCredsSchemaRepository, - config: LedgerModuleConfig - ) { - this.ledgerService = ledgerService - this.agentContext = agentContext - this.anonCredsCredentialDefinitionRepository = anonCredsCredentialDefinitionRepository - this.anonCredsSchemaRepository = anonCredsSchemaRepository - this.config = config - } - - public setPools(poolConfigs: IndyPoolConfig[]) { - return this.ledgerService.setPools(poolConfigs) - } - - /** - * Connect to all the ledger pools - */ - public async connectToPools() { - await this.ledgerService.connectToPools() - } - - /** - * @deprecated use agent.dids.create instead - */ - public async registerPublicDid(did: string, verkey: string, alias: string, role?: NymRole) { - const myPublicDid = this.agentContext.wallet.publicDid?.did - - if (!myPublicDid) { - throw new AriesFrameworkError('Agent has no public DID.') - } - - return this.ledgerService.registerPublicDid(this.agentContext, myPublicDid, did, verkey, alias, role) - } - - /** - * @deprecated use agent.dids.resolve instead - */ - public async getPublicDid(did: string) { - return this.ledgerService.getPublicDid(this.agentContext, did) - } - - public async getSchema(id: string) { - return this.ledgerService.getSchema(this.agentContext, id) - } - - public async registerSchema(schema: SchemaTemplate): Promise { - const did = this.agentContext.wallet.publicDid?.did - - if (!did) { - throw new AriesFrameworkError('Agent has no public DID.') - } - - const schemaId = getLegacySchemaId(did, schema.name, schema.version) - - // Generate the qualified ID - const qualifiedIdentifier = getQualifiedIndySchemaId(this.ledgerService.getDidIndyWriteNamespace(), schemaId) - - // Try find the schema in the wallet - const schemaRecord = await this.anonCredsSchemaRepository.findById(this.agentContext, qualifiedIdentifier) - // Schema in wallet - if (schemaRecord) { - // Transform qualified to unqualified - return { - ...schemaRecord.schema, - id: schemaId, - } - } - - const schemaFromLedger = await this.findBySchemaIdOnLedger(schemaId) - - if (schemaFromLedger) return schemaFromLedger - const createdSchema = await this.ledgerService.registerSchema(this.agentContext, did, schema) - - const anonCredsSchema = new AnonCredsSchemaRecord({ - schema: { ...createdSchema, id: qualifiedIdentifier }, - }) - await this.anonCredsSchemaRepository.save(this.agentContext, anonCredsSchema) - - return createdSchema - } - - private async findBySchemaIdOnLedger(schemaId: string) { - try { - return await this.ledgerService.getSchema(this.agentContext, schemaId) - } catch (e) { - if (e instanceof IndySdkError && isIndyError(e.cause, 'LedgerNotFound')) return null - - throw e - } - } - - private async findByCredentialDefinitionIdOnLedger(credentialDefinitionId: string): Promise { - try { - return await this.ledgerService.getCredentialDefinition(this.agentContext, credentialDefinitionId) - } catch (e) { - if (e instanceof IndySdkError && isIndyError(e.cause, 'LedgerNotFound')) return null - - throw e - } - } - - public async registerCredentialDefinition( - credentialDefinitionTemplate: Omit - ) { - const did = this.agentContext.wallet.publicDid?.did - - if (!did) { - throw new AriesFrameworkError('Agent has no public DID.') - } - - // Construct credential definition ID - const credentialDefinitionId = getLegacyCredentialDefinitionId( - did, - credentialDefinitionTemplate.schema.seqNo, - credentialDefinitionTemplate.tag - ) - - // Construct qualified identifier - const qualifiedIdentifier = getQualifiedIndyCredentialDefinitionId( - this.ledgerService.getDidIndyWriteNamespace(), - credentialDefinitionId - ) - - // Check if the credential exists in wallet. If so, return it - const credentialDefinitionRecord = await this.anonCredsCredentialDefinitionRepository.findById( - this.agentContext, - qualifiedIdentifier - ) - - // Credential Definition in wallet - if (credentialDefinitionRecord) { - // Transform qualified to unqualified - return { - ...credentialDefinitionRecord.credentialDefinition, - id: credentialDefinitionId, - } - } - - // Check for the credential on the ledger. - const credentialDefinitionOnLedger = await this.findByCredentialDefinitionIdOnLedger(credentialDefinitionId) - if (credentialDefinitionOnLedger) { - throw new AriesFrameworkError( - `No credential definition record found and credential definition ${credentialDefinitionId} already exists on the ledger.` - ) - } - - // Register the credential - const registeredDefinition = await this.ledgerService.registerCredentialDefinition(this.agentContext, did, { - ...credentialDefinitionTemplate, - signatureType: 'CL', - }) - // Replace the unqualified with qualified Identifier in anonCred - const anonCredCredential = new AnonCredsCredentialDefinitionRecord({ - credentialDefinition: { ...registeredDefinition, id: qualifiedIdentifier }, - }) - await this.anonCredsCredentialDefinitionRepository.save(this.agentContext, anonCredCredential) - - return registeredDefinition - } - - public async getCredentialDefinition(id: string) { - return this.ledgerService.getCredentialDefinition(this.agentContext, id) - } - - public async getRevocationRegistryDefinition(revocationRegistryDefinitionId: string) { - return this.ledgerService.getRevocationRegistryDefinition(this.agentContext, revocationRegistryDefinitionId) - } - - public async getRevocationRegistryDelta( - revocationRegistryDefinitionId: string, - fromSeconds = 0, - toSeconds = new Date().getTime() - ) { - return this.ledgerService.getRevocationRegistryDelta( - this.agentContext, - revocationRegistryDefinitionId, - fromSeconds, - toSeconds - ) - } -} diff --git a/packages/core/src/modules/ledger/LedgerModule.ts b/packages/core/src/modules/ledger/LedgerModule.ts deleted file mode 100644 index 8bb9a3de82..0000000000 --- a/packages/core/src/modules/ledger/LedgerModule.ts +++ /dev/null @@ -1,37 +0,0 @@ -import type { DependencyManager, Module } from '../../plugins' -import type { LedgerModuleConfigOptions } from './LedgerModuleConfig' - -import { AnonCredsCredentialDefinitionRepository } from '../indy/repository/AnonCredsCredentialDefinitionRepository' -import { AnonCredsSchemaRepository } from '../indy/repository/AnonCredsSchemaRepository' - -import { LedgerApi } from './LedgerApi' -import { LedgerModuleConfig } from './LedgerModuleConfig' -import { IndyLedgerService, IndyPoolService } from './services' - -export class LedgerModule implements Module { - public readonly config: LedgerModuleConfig - public readonly api = LedgerApi - - public constructor(config?: LedgerModuleConfigOptions) { - this.config = new LedgerModuleConfig(config) - } - - /** - * Registers the dependencies of the ledger module on the dependency manager. - */ - public register(dependencyManager: DependencyManager) { - // Api - dependencyManager.registerContextScoped(LedgerApi) - - // Config - dependencyManager.registerInstance(LedgerModuleConfig, this.config) - - // Services - dependencyManager.registerSingleton(IndyLedgerService) - dependencyManager.registerSingleton(IndyPoolService) - - // Repositories - dependencyManager.registerSingleton(AnonCredsCredentialDefinitionRepository) - dependencyManager.registerSingleton(AnonCredsSchemaRepository) - } -} diff --git a/packages/core/src/modules/ledger/LedgerModuleConfig.ts b/packages/core/src/modules/ledger/LedgerModuleConfig.ts deleted file mode 100644 index 12c9d99fc0..0000000000 --- a/packages/core/src/modules/ledger/LedgerModuleConfig.ts +++ /dev/null @@ -1,43 +0,0 @@ -import type { IndyPoolConfig } from './IndyPool' - -/** - * LedgerModuleConfigOptions defines the interface for the options of the RecipientModuleConfig class. - * This can contain optional parameters that have default values in the config class itself. - */ -export interface LedgerModuleConfigOptions { - /** - * Whether to automatically connect to all {@link LedgerModuleConfigOptions.indyLedgers} on startup. - * This will be done asynchronously, so the initialization of the agent won't be impacted. However, - * this does mean there may be unneeded connections to the ledger. - * - * @default true - */ - connectToIndyLedgersOnStartup?: boolean - - /** - * Array of configurations of indy ledgers to connect to. Each item in the list must include either the `genesisPath` or `genesisTransactions` property. - * - * The first ledger in the list will be used for writing transactions to the ledger. - * - * @default [] - */ - indyLedgers?: IndyPoolConfig[] -} - -export class LedgerModuleConfig { - private options: LedgerModuleConfigOptions - - public constructor(options?: LedgerModuleConfigOptions) { - this.options = options ?? {} - } - - /** See {@link LedgerModuleConfigOptions.connectToIndyLedgersOnStartup} */ - public get connectToIndyLedgersOnStartup() { - return this.options.connectToIndyLedgersOnStartup ?? true - } - - /** See {@link LedgerModuleConfigOptions.indyLedgers} */ - public get indyLedgers() { - return this.options.indyLedgers ?? [] - } -} diff --git a/packages/core/src/modules/ledger/__tests__/LedgerApi.test.ts b/packages/core/src/modules/ledger/__tests__/LedgerApi.test.ts deleted file mode 100644 index 6b226bca38..0000000000 --- a/packages/core/src/modules/ledger/__tests__/LedgerApi.test.ts +++ /dev/null @@ -1,395 +0,0 @@ -import type { AgentContext } from '../../../agent/context/AgentContext' -import type { IndyPoolConfig } from '../IndyPool' -import type { CredentialDefinitionTemplate } from '../services/IndyLedgerService' -import type * as Indy from 'indy-sdk' - -import { getAgentConfig, getAgentContext, mockFunction, mockProperty } from '../../../../tests/helpers' -import { KeyProviderRegistry } from '../../../crypto/key-provider' -import { AriesFrameworkError } from '../../../error/AriesFrameworkError' -import { getLegacySchemaId, getLegacyCredentialDefinitionId } from '../../../utils' -import { IndyWallet } from '../../../wallet/IndyWallet' -import { AnonCredsCredentialDefinitionRecord } from '../../indy/repository/AnonCredsCredentialDefinitionRecord' -import { AnonCredsCredentialDefinitionRepository } from '../../indy/repository/AnonCredsCredentialDefinitionRepository' -import { AnonCredsSchemaRecord } from '../../indy/repository/AnonCredsSchemaRecord' -import { AnonCredsSchemaRepository } from '../../indy/repository/AnonCredsSchemaRepository' -import { LedgerApi } from '../LedgerApi' -import { LedgerModuleConfig } from '../LedgerModuleConfig' -import { IndyLedgerService } from '../services/IndyLedgerService' - -jest.mock('../services/IndyLedgerService') -const IndyLedgerServiceMock = IndyLedgerService as jest.Mock - -jest.mock('../../indy/repository/AnonCredsCredentialDefinitionRepository') -const AnonCredsCredentialDefinitionRepositoryMock = - AnonCredsCredentialDefinitionRepository as jest.Mock -jest.mock('../../indy/repository/AnonCredsSchemaRepository') -const AnonCredsSchemaRepositoryMock = AnonCredsSchemaRepository as jest.Mock - -const did = 'Y5bj4SjCiTM9PgeheKAiXx' - -const schemaId = 'Y5bj4SjCiTM9PgeheKAiXx:2:awesomeSchema:1' - -const schema: Indy.Schema = { - id: schemaId, - attrNames: ['hello', 'world'], - name: 'awesomeSchema', - version: '1', - ver: '1', - seqNo: 99, -} - -const credentialDefinition = { - schema: schema, - tag: 'someTag', - signatureType: 'CL', - supportRevocation: true, -} - -const schemaIdQualified = 'did:indy:sovrin:Y5bj4SjCiTM9PgeheKAiXx/anoncreds/v0/SCHEMA/awesomeSchema/1' -const schemaIdGenerated = getLegacySchemaId(did, schema.name, schema.version) -const qualifiedDidCred = 'did:indy:sovrin:Y5bj4SjCiTM9PgeheKAiXx/anoncreds/v0/CLAIM_DEF/99/someTag' - -const credDef: Indy.CredDef = { - id: qualifiedDidCred, - schemaId: schemaIdQualified, - type: 'CL', - tag: 'someTag', - value: { - primary: credentialDefinition as Record, - revocation: true, - }, - ver: '1', -} - -const credentialDefinitionTemplate: Omit = { - schema: { ...schema, id: schemaIdQualified }, - tag: 'someTag', - supportRevocation: true, -} - -const revocRegDef: Indy.RevocRegDef = { - id: 'abcde', - revocDefType: 'CL_ACCUM', - tag: 'someTag', - credDefId: 'abcde', - value: { - issuanceType: 'ISSUANCE_BY_DEFAULT', - maxCredNum: 3, - tailsHash: 'abcde', - tailsLocation: 'xyz', - publicKeys: ['abcde', 'fghijk'], - }, - ver: 'abcde', -} - -const credentialDefinitionId = getLegacyCredentialDefinitionId( - did, - credentialDefinitionTemplate.schema.seqNo, - credentialDefinitionTemplate.tag -) - -const pools: IndyPoolConfig[] = [ - { - id: '7Tqg6BwSSWapxgUDm9KKgg', - indyNamespace: 'sovrin', - isProduction: true, - genesisTransactions: 'xxx', - transactionAuthorAgreement: { version: '1', acceptanceMechanism: 'accept' }, - }, -] - -describe('LedgerApi', () => { - let wallet: IndyWallet - let ledgerService: IndyLedgerService - let anonCredsCredentialDefinitionRepository: AnonCredsCredentialDefinitionRepository - let anonCredsSchemaRepository: AnonCredsSchemaRepository - let ledgerApi: LedgerApi - let agentContext: AgentContext - - const contextCorrelationId = 'mock' - const agentConfig = getAgentConfig('LedgerApiTest', { - indyLedgers: pools, - }) - - beforeEach(async () => { - wallet = new IndyWallet(agentConfig.agentDependencies, agentConfig.logger, new KeyProviderRegistry([])) - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - await wallet.createAndOpen(agentConfig.walletConfig!) - }) - - afterEach(async () => { - await wallet.delete() - }) - - beforeEach(async () => { - ledgerService = new IndyLedgerServiceMock() - - agentContext = getAgentContext({ - wallet, - agentConfig, - contextCorrelationId, - }) - - anonCredsCredentialDefinitionRepository = new AnonCredsCredentialDefinitionRepositoryMock() - anonCredsSchemaRepository = new AnonCredsSchemaRepositoryMock() - - ledgerApi = new LedgerApi( - ledgerService, - agentContext, - anonCredsCredentialDefinitionRepository, - anonCredsSchemaRepository, - new LedgerModuleConfig() - ) - }) - - describe('LedgerApi', () => { - // Connect to pools - describe('connectToPools', () => { - it('should connect to all pools', async () => { - mockFunction(ledgerService.connectToPools).mockResolvedValue([1, 2, 4]) - await expect(ledgerApi.connectToPools()).resolves.toBeUndefined() - expect(ledgerService.connectToPools).toHaveBeenCalled() - }) - }) - - // Register public did - describe('registerPublicDid', () => { - it('should register a public DID', async () => { - mockFunction(ledgerService.registerPublicDid).mockResolvedValueOnce(did) - mockProperty(wallet, 'publicDid', { did: did, verkey: 'abcde' }) - await expect(ledgerApi.registerPublicDid(did, 'abcde', 'someAlias')).resolves.toEqual(did) - expect(ledgerService.registerPublicDid).toHaveBeenCalledWith( - agentContext, - did, - did, - 'abcde', - 'someAlias', - undefined - ) - }) - - it('should throw an error if the DID cannot be registered because there is no public did', async () => { - const did = 'Y5bj4SjCiTM9PgeheKAiXx' - mockProperty(wallet, 'publicDid', undefined) - await expect(ledgerApi.registerPublicDid(did, 'abcde', 'someAlias')).rejects.toThrowError(AriesFrameworkError) - }) - }) - - // Get public DID - describe('getPublicDid', () => { - it('should return the public DID if there is one', async () => { - const nymResponse: Indy.GetNymResponse = { did: 'Y5bj4SjCiTM9PgeheKAiXx', verkey: 'abcde', role: 'STEWARD' } - mockProperty(wallet, 'publicDid', { did: nymResponse.did, verkey: nymResponse.verkey }) - mockFunction(ledgerService.getPublicDid).mockResolvedValueOnce(nymResponse) - await expect(ledgerApi.getPublicDid(nymResponse.did)).resolves.toEqual(nymResponse) - expect(ledgerService.getPublicDid).toHaveBeenCalledWith(agentContext, nymResponse.did) - }) - }) - - // Get schema - describe('getSchema', () => { - it('should return the schema by id if there is one', async () => { - mockFunction(ledgerService.getSchema).mockResolvedValueOnce(schema) - await expect(ledgerApi.getSchema(schemaId)).resolves.toEqual(schema) - expect(ledgerService.getSchema).toHaveBeenCalledWith(agentContext, schemaId) - }) - - it('should throw an error if no schema for the id exists', async () => { - mockFunction(ledgerService.getSchema).mockRejectedValueOnce( - new AriesFrameworkError('Error retrieving schema abcd from ledger 1') - ) - await expect(ledgerApi.getSchema(schemaId)).rejects.toThrowError(AriesFrameworkError) - expect(ledgerService.getSchema).toHaveBeenCalledWith(agentContext, schemaId) - }) - }) - - describe('registerSchema', () => { - it('should throw an error if there is no public DID', async () => { - mockProperty(wallet, 'publicDid', undefined) - await expect(ledgerApi.registerSchema({ ...schema, attributes: ['hello', 'world'] })).rejects.toThrowError( - AriesFrameworkError - ) - }) - - it('should return the schema from anonCreds when it already exists', async () => { - mockProperty(wallet, 'publicDid', { did: did, verkey: 'abcde' }) - mockFunction(anonCredsSchemaRepository.findById).mockResolvedValueOnce( - new AnonCredsSchemaRecord({ schema: { ...schema, id: schemaIdQualified } }) - ) - mockFunction(ledgerService.getDidIndyWriteNamespace).mockReturnValueOnce(pools[0].indyNamespace) - // eslint-disable-next-line @typescript-eslint/no-unused-vars - const { id, ...schemaWithoutId } = schema - await expect(ledgerApi.registerSchema({ ...schema, attributes: ['hello', 'world'] })).resolves.toMatchObject({ - ...schema, - id: schema.id, - }) - expect(anonCredsSchemaRepository.findById).toHaveBeenCalledWith(agentContext, schemaIdQualified) - }) - - it('should return the schema from the ledger when it already exists', async () => { - mockProperty(wallet, 'publicDid', { did: did, verkey: 'abcde' }) - jest - // eslint-disable-next-line @typescript-eslint/no-explicit-any - .spyOn(LedgerApi.prototype as any, 'findBySchemaIdOnLedger') - .mockResolvedValueOnce(new AnonCredsSchemaRecord({ schema: schema })) - mockProperty(ledgerApi, 'config', { - connectToIndyLedgersOnStartup: true, - indyLedgers: pools, - } as LedgerModuleConfig) - await expect(ledgerApi.registerSchema({ ...schema, attributes: ['hello', 'world'] })).resolves.toHaveProperty( - 'schema', - { ...schema } - ) - // eslint-disable-next-line @typescript-eslint/no-explicit-any - expect(jest.spyOn(LedgerApi.prototype as any, 'findBySchemaIdOnLedger')).toHaveBeenCalledWith(schemaIdGenerated) - }) - - it('should return the schema after registering it', async () => { - mockProperty(wallet, 'publicDid', { did: did, verkey: 'abcde' }) - mockFunction(ledgerService.registerSchema).mockResolvedValueOnce(schema) - mockProperty(ledgerApi, 'config', { - connectToIndyLedgersOnStartup: true, - indyLedgers: pools, - } as LedgerModuleConfig) - await expect(ledgerApi.registerSchema({ ...schema, attributes: ['hello', 'world'] })).resolves.toEqual(schema) - expect(ledgerService.registerSchema).toHaveBeenCalledWith(agentContext, did, { - ...schema, - attributes: ['hello', 'world'], - }) - }) - }) - - describe('registerCredentialDefinition', () => { - it('should throw an error if there si no public DID', async () => { - mockProperty(wallet, 'publicDid', undefined) - await expect(ledgerApi.registerCredentialDefinition(credentialDefinitionTemplate)).rejects.toThrowError( - AriesFrameworkError - ) - }) - - it('should return the credential definition from the wallet if it already exists', async () => { - mockProperty(wallet, 'publicDid', { did: did, verkey: 'abcde' }) - const anonCredsCredentialDefinitionRecord: AnonCredsCredentialDefinitionRecord = - new AnonCredsCredentialDefinitionRecord({ - credentialDefinition: credDef, - }) - mockFunction(anonCredsCredentialDefinitionRepository.findById).mockResolvedValueOnce( - anonCredsCredentialDefinitionRecord - ) - mockProperty(ledgerApi, 'config', { - connectToIndyLedgersOnStartup: true, - indyLedgers: pools, - } as LedgerModuleConfig) - mockFunction(ledgerService.getDidIndyWriteNamespace).mockReturnValueOnce(pools[0].indyNamespace) - await expect(ledgerApi.registerCredentialDefinition(credentialDefinitionTemplate)).resolves.toHaveProperty( - 'value.primary', - credentialDefinition - ) - expect(anonCredsCredentialDefinitionRepository.findById).toHaveBeenCalledWith(agentContext, qualifiedDidCred) - }) - - it('should throw an exception if the definition already exists on the ledger', async () => { - mockProperty(wallet, 'publicDid', { did: did, verkey: 'abcde' }) - jest - // eslint-disable-next-line @typescript-eslint/no-explicit-any - .spyOn(LedgerApi.prototype as any, 'findByCredentialDefinitionIdOnLedger') - .mockResolvedValueOnce({ credentialDefinition: credentialDefinition }) - mockProperty(ledgerApi, 'config', { - connectToIndyLedgersOnStartup: true, - indyLedgers: pools, - } as LedgerModuleConfig) - await expect(ledgerApi.registerCredentialDefinition(credentialDefinitionTemplate)).rejects.toThrowError( - AriesFrameworkError - ) - // eslint-disable-next-line @typescript-eslint/no-explicit-any - expect(jest.spyOn(LedgerApi.prototype as any, 'findByCredentialDefinitionIdOnLedger')).toHaveBeenCalledWith( - credentialDefinitionId - ) - }) - - it('should register the credential successfully if it is neither in the wallet and neither on the ledger', async () => { - mockProperty(wallet, 'publicDid', { did: did, verkey: 'abcde' }) - mockFunction(ledgerService.registerCredentialDefinition).mockResolvedValueOnce(credDef) - mockProperty(ledgerApi, 'config', { - connectToIndyLedgersOnStartup: true, - indyLedgers: pools, - } as LedgerModuleConfig) - await expect(ledgerApi.registerCredentialDefinition(credentialDefinitionTemplate)).resolves.toEqual(credDef) - expect(ledgerService.registerCredentialDefinition).toHaveBeenCalledWith(agentContext, did, { - ...credentialDefinitionTemplate, - signatureType: 'CL', - }) - }) - }) - - describe('getCredentialDefinition', () => { - it('should return the credential definition given the id', async () => { - mockProperty(wallet, 'publicDid', { did: did, verkey: 'abcde' }) - mockFunction(ledgerService.getCredentialDefinition).mockResolvedValue(credDef) - await expect(ledgerApi.getCredentialDefinition(credDef.id)).resolves.toEqual(credDef) - expect(ledgerService.getCredentialDefinition).toHaveBeenCalledWith(agentContext, credDef.id) - }) - - it('should throw an error if there is no credential definition for the given id', async () => { - mockProperty(wallet, 'publicDid', { did: did, verkey: 'abcde' }) - mockFunction(ledgerService.getCredentialDefinition).mockRejectedValueOnce(new AriesFrameworkError('')) - await expect(ledgerApi.getCredentialDefinition(credDef.id)).rejects.toThrowError(AriesFrameworkError) - expect(ledgerService.getCredentialDefinition).toHaveBeenCalledWith(agentContext, credDef.id) - }) - }) - - describe('getRevocationRegistryDefinition', () => { - it('should return the ParseRevocationRegistryDefinitionTemplate for a valid revocationRegistryDefinitionId', async () => { - const parseRevocationRegistryDefinitionTemplate = { - revocationRegistryDefinition: revocRegDef, - revocationRegistryDefinitionTxnTime: 12345678, - } - mockFunction(ledgerService.getRevocationRegistryDefinition).mockResolvedValue( - parseRevocationRegistryDefinitionTemplate - ) - await expect(ledgerApi.getRevocationRegistryDefinition(revocRegDef.id)).resolves.toBe( - parseRevocationRegistryDefinitionTemplate - ) - expect(ledgerService.getRevocationRegistryDefinition).toHaveBeenLastCalledWith(agentContext, revocRegDef.id) - }) - - it('should throw an error if the ParseRevocationRegistryDefinitionTemplate does not exists', async () => { - mockFunction(ledgerService.getRevocationRegistryDefinition).mockRejectedValueOnce(new AriesFrameworkError('')) - await expect(ledgerApi.getRevocationRegistryDefinition('abcde')).rejects.toThrowError(AriesFrameworkError) - expect(ledgerService.getRevocationRegistryDefinition).toHaveBeenCalledWith(agentContext, revocRegDef.id) - }) - }) - - describe('getRevocationRegistryDelta', () => { - it('should return the ParseRevocationRegistryDeltaTemplate', async () => { - const revocRegDelta = { - value: { - prevAccum: 'prev', - accum: 'accum', - issued: [1, 2, 3], - revoked: [4, 5, 6], - }, - ver: 'ver', - } - const parseRevocationRegistryDeltaTemplate = { - revocationRegistryDelta: revocRegDelta, - deltaTimestamp: 12345678, - } - - mockFunction(ledgerService.getRevocationRegistryDelta).mockResolvedValueOnce( - parseRevocationRegistryDeltaTemplate - ) - await expect(ledgerApi.getRevocationRegistryDelta('12345')).resolves.toEqual( - parseRevocationRegistryDeltaTemplate - ) - expect(ledgerService.getRevocationRegistryDelta).toHaveBeenCalledTimes(1) - }) - - it('should throw an error if the delta cannot be obtained', async () => { - mockFunction(ledgerService.getRevocationRegistryDelta).mockRejectedValueOnce(new AriesFrameworkError('')) - await expect(ledgerApi.getRevocationRegistryDelta('abcde1234')).rejects.toThrowError(AriesFrameworkError) - expect(ledgerService.getRevocationRegistryDelta).toHaveBeenCalledTimes(1) - }) - }) - }) -}) diff --git a/packages/core/src/modules/ledger/__tests__/LedgerModule.test.ts b/packages/core/src/modules/ledger/__tests__/LedgerModule.test.ts deleted file mode 100644 index b258bd5416..0000000000 --- a/packages/core/src/modules/ledger/__tests__/LedgerModule.test.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { DependencyManager } from '../../../plugins/DependencyManager' -import { AnonCredsCredentialDefinitionRepository } from '../../indy/repository/AnonCredsCredentialDefinitionRepository' -import { AnonCredsSchemaRepository } from '../../indy/repository/AnonCredsSchemaRepository' -import { LedgerApi } from '../LedgerApi' -import { LedgerModule } from '../LedgerModule' -import { IndyLedgerService, IndyPoolService } from '../services' - -jest.mock('../../../plugins/DependencyManager') -const DependencyManagerMock = DependencyManager as jest.Mock - -const dependencyManager = new DependencyManagerMock() - -describe('LedgerModule', () => { - test('registers dependencies on the dependency manager', () => { - new LedgerModule().register(dependencyManager) - - expect(dependencyManager.registerContextScoped).toHaveBeenCalledTimes(1) - expect(dependencyManager.registerContextScoped).toHaveBeenCalledWith(LedgerApi) - - expect(dependencyManager.registerSingleton).toHaveBeenCalledTimes(4) - expect(dependencyManager.registerSingleton).toHaveBeenCalledWith(IndyLedgerService) - expect(dependencyManager.registerSingleton).toHaveBeenCalledWith(IndyPoolService) - expect(dependencyManager.registerSingleton).toHaveBeenCalledWith(AnonCredsCredentialDefinitionRepository) - expect(dependencyManager.registerSingleton).toHaveBeenCalledWith(AnonCredsSchemaRepository) - }) -}) diff --git a/packages/core/src/modules/ledger/error/LedgerError.ts b/packages/core/src/modules/ledger/error/LedgerError.ts deleted file mode 100644 index 1ee8589cf9..0000000000 --- a/packages/core/src/modules/ledger/error/LedgerError.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { AriesFrameworkError } from '../../../error/AriesFrameworkError' - -export class LedgerError extends AriesFrameworkError { - public constructor(message: string, { cause }: { cause?: Error } = {}) { - super(message, { cause }) - } -} diff --git a/packages/core/src/modules/ledger/index.ts b/packages/core/src/modules/ledger/index.ts deleted file mode 100644 index fc65f390db..0000000000 --- a/packages/core/src/modules/ledger/index.ts +++ /dev/null @@ -1,4 +0,0 @@ -export * from './services' -export * from './LedgerApi' -export * from './IndyPool' -export * from './LedgerModule' diff --git a/packages/core/src/modules/ledger/ledgerUtil.ts b/packages/core/src/modules/ledger/ledgerUtil.ts deleted file mode 100644 index 62e75f1e72..0000000000 --- a/packages/core/src/modules/ledger/ledgerUtil.ts +++ /dev/null @@ -1,9 +0,0 @@ -import type * as Indy from 'indy-sdk' - -export function isLedgerRejectResponse(response: Indy.LedgerResponse): response is Indy.LedgerRejectResponse { - return response.op === 'REJECT' -} - -export function isLedgerReqnackResponse(response: Indy.LedgerResponse): response is Indy.LedgerReqnackResponse { - return response.op === 'REQNACK' -} diff --git a/packages/core/src/modules/ledger/services/IndyLedgerService.ts b/packages/core/src/modules/ledger/services/IndyLedgerService.ts deleted file mode 100644 index 41c5bea34c..0000000000 --- a/packages/core/src/modules/ledger/services/IndyLedgerService.ts +++ /dev/null @@ -1,503 +0,0 @@ -import type { AgentContext } from '../../../agent' -import type { IndyPoolConfig } from '../IndyPool' -import type { CredDef, default as Indy, NymRole, Schema } from 'indy-sdk' - -import { AgentDependencies } from '../../../agent/AgentDependencies' -import { InjectionSymbols } from '../../../constants' -import { IndySdkError } from '../../../error/IndySdkError' -import { Logger } from '../../../logger' -import { injectable, inject } from '../../../plugins' -import { - didFromCredentialDefinitionId, - didFromRevocationRegistryDefinitionId, - didFromSchemaId, -} from '../../../utils/did' -import { isIndyError } from '../../../utils/indyError' -import { IndyIssuerService } from '../../indy/services/IndyIssuerService' - -import { IndyPoolService } from './IndyPoolService' - -@injectable() -export class IndyLedgerService { - private indy: typeof Indy - private logger: Logger - - private indyIssuer: IndyIssuerService - private indyPoolService: IndyPoolService - - public constructor( - @inject(InjectionSymbols.AgentDependencies) agentDependencies: AgentDependencies, - @inject(InjectionSymbols.Logger) logger: Logger, - indyIssuer: IndyIssuerService, - indyPoolService: IndyPoolService - ) { - this.indy = agentDependencies.indy - this.logger = logger - this.indyIssuer = indyIssuer - this.indyPoolService = indyPoolService - } - - public setPools(poolConfigs: IndyPoolConfig[]) { - return this.indyPoolService.setPools(poolConfigs) - } - - /** - * @deprecated - */ - public getDidIndyWriteNamespace(): string { - return this.indyPoolService.ledgerWritePool.config.indyNamespace - } - - public async connectToPools() { - return this.indyPoolService.connectToPools() - } - - /** - * @deprecated - */ - public async registerPublicDid( - agentContext: AgentContext, - submitterDid: string, - targetDid: string, - verkey: string, - alias: string, - role?: NymRole - ) { - const pool = this.indyPoolService.getPoolForNamespace() - - try { - this.logger.debug(`Register public did '${targetDid}' on ledger '${pool.id}'`) - - const request = await this.indy.buildNymRequest(submitterDid, targetDid, verkey, alias, role || null) - - const response = await this.indyPoolService.submitWriteRequest(agentContext, pool, request, submitterDid) - - this.logger.debug(`Registered public did '${targetDid}' on ledger '${pool.id}'`, { - response, - }) - - return targetDid - } catch (error) { - this.logger.error(`Error registering public did '${targetDid}' on ledger '${pool.id}'`, { - error, - submitterDid, - targetDid, - verkey, - alias, - role, - pool: pool.id, - }) - - throw error - } - } - - /** - * @deprecated - */ - public async getPublicDid(agentContext: AgentContext, did: string) { - // Getting the pool for a did also retrieves the DID. We can just use that - const { did: didResponse } = await this.indyPoolService.getPoolForDid(agentContext, did) - - return didResponse - } - - /** - * @deprecated - */ - public async setEndpointsForDid( - agentContext: AgentContext, - did: string, - endpoints: IndyEndpointAttrib - ): Promise { - const pool = this.indyPoolService.getPoolForNamespace() - - try { - this.logger.debug(`Set endpoints for did '${did}' on ledger '${pool.id}'`, endpoints) - - const request = await this.indy.buildAttribRequest(did, did, null, { endpoint: endpoints }, null) - - const response = await this.indyPoolService.submitWriteRequest(agentContext, pool, request, did) - this.logger.debug(`Successfully set endpoints for did '${did}' on ledger '${pool.id}'`, { - response, - endpoints, - }) - } catch (error) { - this.logger.error(`Error setting endpoints for did '${did}' on ledger '${pool.id}'`, { - error, - did, - endpoints, - }) - - throw isIndyError(error) ? new IndySdkError(error) : error - } - } - - /** - * @deprecated - */ - public async getEndpointsForDid(agentContext: AgentContext, did: string) { - const { pool } = await this.indyPoolService.getPoolForDid(agentContext, did) - - try { - this.logger.debug(`Get endpoints for did '${did}' from ledger '${pool.id}'`) - - const request = await this.indy.buildGetAttribRequest(null, did, 'endpoint', null, null) - - this.logger.debug(`Submitting get endpoint ATTRIB request for did '${did}' to ledger '${pool.id}'`) - const response = await this.indyPoolService.submitReadRequest(pool, request) - - if (!response.result.data) return {} - - const endpoints = JSON.parse(response.result.data as string)?.endpoint as IndyEndpointAttrib - this.logger.debug(`Got endpoints '${JSON.stringify(endpoints)}' for did '${did}' from ledger '${pool.id}'`, { - response, - endpoints, - }) - - return endpoints ?? {} - } catch (error) { - this.logger.error(`Error retrieving endpoints for did '${did}' from ledger '${pool.id}'`, { - error, - }) - - throw isIndyError(error) ? new IndySdkError(error) : error - } - } - - public async registerSchema( - agentContext: AgentContext, - did: string, - schemaTemplate: SchemaTemplate - ): Promise { - const pool = this.indyPoolService.getPoolForNamespace() - - try { - this.logger.debug(`Register schema on ledger '${pool.id}' with did '${did}'`, schemaTemplate) - const { name, attributes, version } = schemaTemplate - const schema = await this.indyIssuer.createSchema(agentContext, { originDid: did, name, version, attributes }) - - const request = await this.indy.buildSchemaRequest(did, schema) - - const response = await this.indyPoolService.submitWriteRequest(agentContext, pool, request, did) - this.logger.debug(`Registered schema '${schema.id}' on ledger '${pool.id}'`, { - response, - schema, - }) - - schema.seqNo = response.result.txnMetadata.seqNo - - return schema - } catch (error) { - this.logger.error(`Error registering schema for did '${did}' on ledger '${pool.id}'`, { - error, - did, - schemaTemplate, - }) - - throw isIndyError(error) ? new IndySdkError(error) : error - } - } - - public async getSchema(agentContext: AgentContext, schemaId: string) { - const did = didFromSchemaId(schemaId) - const { pool } = await this.indyPoolService.getPoolForDid(agentContext, did) - - try { - this.logger.debug(`Getting schema '${schemaId}' from ledger '${pool.id}'`) - - const request = await this.indy.buildGetSchemaRequest(null, schemaId) - - this.logger.trace(`Submitting get schema request for schema '${schemaId}' to ledger '${pool.id}'`) - const response = await this.indyPoolService.submitReadRequest(pool, request) - - this.logger.trace(`Got un-parsed schema '${schemaId}' from ledger '${pool.id}'`, { - response, - }) - - const [, schema] = await this.indy.parseGetSchemaResponse(response) - this.logger.debug(`Got schema '${schemaId}' from ledger '${pool.id}'`, { - schema, - }) - - return schema - } catch (error) { - this.logger.error(`Error retrieving schema '${schemaId}' from ledger '${pool.id}'`, { - error, - schemaId, - }) - - throw isIndyError(error) ? new IndySdkError(error) : error - } - } - - public async registerCredentialDefinition( - agentContext: AgentContext, - did: string, - credentialDefinitionTemplate: CredentialDefinitionTemplate - ): Promise { - const pool = this.indyPoolService.getPoolForNamespace() - - try { - this.logger.debug( - `Registering credential definition on ledger '${pool.id}' with did '${did}'`, - credentialDefinitionTemplate - ) - const { schema, tag, signatureType, supportRevocation } = credentialDefinitionTemplate - - const credentialDefinition = await this.indyIssuer.createCredentialDefinition(agentContext, { - issuerDid: did, - schema, - tag, - signatureType, - supportRevocation, - }) - - const request = await this.indy.buildCredDefRequest(did, credentialDefinition) - - const response = await this.indyPoolService.submitWriteRequest(agentContext, pool, request, did) - - this.logger.debug(`Registered credential definition '${credentialDefinition.id}' on ledger '${pool.id}'`, { - response, - credentialDefinition: credentialDefinition, - }) - - return credentialDefinition - } catch (error) { - this.logger.error( - `Error registering credential definition for schema '${credentialDefinitionTemplate.schema.id}' on ledger '${pool.id}'`, - { - error, - did, - credentialDefinitionTemplate, - } - ) - - throw isIndyError(error) ? new IndySdkError(error) : error - } - } - - public async getCredentialDefinition(agentContext: AgentContext, credentialDefinitionId: string) { - const did = didFromCredentialDefinitionId(credentialDefinitionId) - const { pool } = await this.indyPoolService.getPoolForDid(agentContext, did) - - this.logger.debug(`Using ledger '${pool.id}' to retrieve credential definition '${credentialDefinitionId}'`) - - try { - const request = await this.indy.buildGetCredDefRequest(null, credentialDefinitionId) - - this.logger.trace( - `Submitting get credential definition request for credential definition '${credentialDefinitionId}' to ledger '${pool.id}'` - ) - - const response = await this.indyPoolService.submitReadRequest(pool, request) - this.logger.trace(`Got un-parsed credential definition '${credentialDefinitionId}' from ledger '${pool.id}'`, { - response, - }) - - const [, credentialDefinition] = await this.indy.parseGetCredDefResponse(response) - this.logger.debug(`Got credential definition '${credentialDefinitionId}' from ledger '${pool.id}'`, { - credentialDefinition, - }) - - return credentialDefinition - } catch (error) { - this.logger.error(`Error retrieving credential definition '${credentialDefinitionId}' from ledger '${pool.id}'`, { - error, - credentialDefinitionId, - pool: pool.id, - }) - - throw isIndyError(error) ? new IndySdkError(error) : error - } - } - - public async getRevocationRegistryDefinition( - agentContext: AgentContext, - revocationRegistryDefinitionId: string - ): Promise { - const did = didFromRevocationRegistryDefinitionId(revocationRegistryDefinitionId) - const { pool } = await this.indyPoolService.getPoolForDid(agentContext, did) - - this.logger.debug( - `Using ledger '${pool.id}' to retrieve revocation registry definition '${revocationRegistryDefinitionId}'` - ) - try { - //TODO - implement a cache - this.logger.trace( - `Revocation Registry Definition '${revocationRegistryDefinitionId}' not cached, retrieving from ledger` - ) - - const request = await this.indy.buildGetRevocRegDefRequest(null, revocationRegistryDefinitionId) - - this.logger.trace( - `Submitting get revocation registry definition request for revocation registry definition '${revocationRegistryDefinitionId}' to ledger` - ) - const response = await this.indyPoolService.submitReadRequest(pool, request) - this.logger.trace( - `Got un-parsed revocation registry definition '${revocationRegistryDefinitionId}' from ledger '${pool.id}'`, - { - response, - } - ) - - const [, revocationRegistryDefinition] = await this.indy.parseGetRevocRegDefResponse(response) - - this.logger.debug(`Got revocation registry definition '${revocationRegistryDefinitionId}' from ledger`, { - revocationRegistryDefinition, - }) - - return { revocationRegistryDefinition, revocationRegistryDefinitionTxnTime: response.result.txnTime } - } catch (error) { - this.logger.error( - `Error retrieving revocation registry definition '${revocationRegistryDefinitionId}' from ledger`, - { - error, - revocationRegistryDefinitionId: revocationRegistryDefinitionId, - pool: pool.id, - } - ) - throw error - } - } - - // Retrieves the accumulated state of a revocation registry by id given a revocation interval from & to (used primarily for proof creation) - public async getRevocationRegistryDelta( - agentContext: AgentContext, - revocationRegistryDefinitionId: string, - to: number = new Date().getTime(), - from = 0 - ): Promise { - //TODO - implement a cache - const did = didFromRevocationRegistryDefinitionId(revocationRegistryDefinitionId) - const { pool } = await this.indyPoolService.getPoolForDid(agentContext, did) - - this.logger.debug( - `Using ledger '${pool.id}' to retrieve revocation registry delta with revocation registry definition id: '${revocationRegistryDefinitionId}'`, - { - to, - from, - } - ) - - try { - const request = await this.indy.buildGetRevocRegDeltaRequest(null, revocationRegistryDefinitionId, from, to) - - this.logger.trace( - `Submitting get revocation registry delta request for revocation registry '${revocationRegistryDefinitionId}' to ledger` - ) - - const response = await this.indyPoolService.submitReadRequest(pool, request) - this.logger.trace( - `Got revocation registry delta unparsed-response '${revocationRegistryDefinitionId}' from ledger`, - { - response, - } - ) - - const [, revocationRegistryDelta, deltaTimestamp] = await this.indy.parseGetRevocRegDeltaResponse(response) - - this.logger.debug(`Got revocation registry delta '${revocationRegistryDefinitionId}' from ledger`, { - revocationRegistryDelta, - deltaTimestamp, - to, - from, - }) - - return { revocationRegistryDelta, deltaTimestamp } - } catch (error) { - this.logger.error( - `Error retrieving revocation registry delta '${revocationRegistryDefinitionId}' from ledger, potentially revocation interval ends before revocation registry creation?"`, - { - error, - revocationRegistryId: revocationRegistryDefinitionId, - pool: pool.id, - } - ) - throw error - } - } - - // Retrieves the accumulated state of a revocation registry by id given a timestamp (used primarily for verification) - public async getRevocationRegistry( - agentContext: AgentContext, - revocationRegistryDefinitionId: string, - timestamp: number - ): Promise { - //TODO - implement a cache - const did = didFromRevocationRegistryDefinitionId(revocationRegistryDefinitionId) - const { pool } = await this.indyPoolService.getPoolForDid(agentContext, did) - - this.logger.debug( - `Using ledger '${pool.id}' to retrieve revocation registry accumulated state with revocation registry definition id: '${revocationRegistryDefinitionId}'`, - { - timestamp, - } - ) - - try { - const request = await this.indy.buildGetRevocRegRequest(null, revocationRegistryDefinitionId, timestamp) - - this.logger.trace( - `Submitting get revocation registry request for revocation registry '${revocationRegistryDefinitionId}' to ledger` - ) - const response = await this.indyPoolService.submitReadRequest(pool, request) - this.logger.trace( - `Got un-parsed revocation registry '${revocationRegistryDefinitionId}' from ledger '${pool.id}'`, - { - response, - } - ) - - const [, revocationRegistry, ledgerTimestamp] = await this.indy.parseGetRevocRegResponse(response) - this.logger.debug(`Got revocation registry '${revocationRegistryDefinitionId}' from ledger`, { - ledgerTimestamp, - revocationRegistry, - }) - - return { revocationRegistry, ledgerTimestamp } - } catch (error) { - this.logger.error(`Error retrieving revocation registry '${revocationRegistryDefinitionId}' from ledger`, { - error, - revocationRegistryId: revocationRegistryDefinitionId, - pool: pool.id, - }) - throw error - } - } -} - -export interface SchemaTemplate { - name: string - version: string - attributes: string[] -} - -export interface CredentialDefinitionTemplate { - schema: Schema - tag: string - signatureType: 'CL' - supportRevocation: boolean -} - -export interface ParseRevocationRegistryDefinitionTemplate { - revocationRegistryDefinition: Indy.RevocRegDef - revocationRegistryDefinitionTxnTime: number -} - -export interface ParseRevocationRegistryDeltaTemplate { - revocationRegistryDelta: Indy.RevocRegDelta - deltaTimestamp: number -} - -export interface ParseRevocationRegistryTemplate { - revocationRegistry: Indy.RevocReg - ledgerTimestamp: number -} - -export interface IndyEndpointAttrib { - endpoint?: string - types?: Array<'endpoint' | 'did-communication' | 'DIDCommMessaging'> - routingKeys?: string[] - [key: string]: unknown -} diff --git a/packages/core/src/modules/ledger/services/index.ts b/packages/core/src/modules/ledger/services/index.ts deleted file mode 100644 index e0399c9afe..0000000000 --- a/packages/core/src/modules/ledger/services/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from './IndyLedgerService' -export * from './IndyPoolService' diff --git "a/packages/core/src/modules/message-p\303\254ckup/MessagePickupApi.ts" "b/packages/core/src/modules/message-p\303\254ckup/MessagePickupApi.ts" new file mode 100644 index 0000000000..653fafd7d7 --- /dev/null +++ "b/packages/core/src/modules/message-p\303\254ckup/MessagePickupApi.ts" @@ -0,0 +1,96 @@ +import type { + PickupMessagesOptions, + PickupMessagesReturnType, + QueueMessageOptions, + QueueMessageReturnType, +} from './MessagePickupApiOptions' +import type { V1MessagePickupProtocol, V2MessagePickupProtocol } from './protocol' +import type { MessagePickupProtocol } from './protocol/MessagePickupProtocol' +import type { MessageRepository } from '../../storage/MessageRepository' + +import { AgentContext } from '../../agent' +import { MessageSender } from '../../agent/MessageSender' +import { OutboundMessageContext } from '../../agent/models' +import { InjectionSymbols } from '../../constants' +import { AriesFrameworkError } from '../../error' +import { injectable } from '../../plugins' +import { ConnectionService } from '../connections/services' + +import { MessagePickupModuleConfig } from './MessagePickupModuleConfig' + +export interface MessagePickupApi { + queueMessage(options: QueueMessageOptions): Promise + pickupMessages(options: PickupMessagesOptions): Promise +} + +@injectable() +export class MessagePickupApi + implements MessagePickupApi +{ + public config: MessagePickupModuleConfig + + private messageSender: MessageSender + private agentContext: AgentContext + private connectionService: ConnectionService + + public constructor( + messageSender: MessageSender, + agentContext: AgentContext, + connectionService: ConnectionService, + config: MessagePickupModuleConfig + ) { + this.messageSender = messageSender + this.connectionService = connectionService + this.agentContext = agentContext + this.config = config + } + + private getProtocol(protocolVersion: MPP): MessagePickupProtocol { + const protocol = this.config.protocols.find((protocol) => protocol.version === protocolVersion) + + if (!protocol) { + throw new AriesFrameworkError(`No message pickup protocol registered for protocol version ${protocolVersion}`) + } + + return protocol + } + + /** + * Add an encrypted message to the message pickup queue + * + * @param options: connectionId associated to the message and the encrypted message itself + */ + public async queueMessage(options: QueueMessageOptions): Promise { + const connectionRecord = await this.connectionService.getById(this.agentContext, options.connectionId) + + const messageRepository = this.agentContext.dependencyManager.resolve( + InjectionSymbols.MessageRepository + ) + + await messageRepository.add(connectionRecord.id, options.message) + } + + /** + * Pickup queued messages from a message holder. It attempts to retrieve all current messages from the + * queue, receiving up to `batchSize` messages per batch retrieval. + * + * @param options connectionId, protocol version to use and batch size + */ + public async pickupMessages(options: PickupMessagesOptions): Promise { + const connectionRecord = await this.connectionService.getById(this.agentContext, options.connectionId) + + const protocol = this.getProtocol(options.protocolVersion) + const { message } = await protocol.pickupMessages(this.agentContext, { + connectionRecord, + batchSize: options.batchSize, + recipientKey: options.recipientKey, + }) + + await this.messageSender.sendMessage( + new OutboundMessageContext(message, { + agentContext: this.agentContext, + connection: connectionRecord, + }) + ) + } +} diff --git "a/packages/core/src/modules/message-p\303\254ckup/MessagePickupApiOptions.ts" "b/packages/core/src/modules/message-p\303\254ckup/MessagePickupApiOptions.ts" new file mode 100644 index 0000000000..6ed2074cd3 --- /dev/null +++ "b/packages/core/src/modules/message-p\303\254ckup/MessagePickupApiOptions.ts" @@ -0,0 +1,23 @@ +import type { MessagePickupProtocol } from './protocol/MessagePickupProtocol' +import type { EncryptedMessage } from '../../didcomm/types' + +/** + * Get the supported protocol versions based on the provided discover features services. + */ +export type MessagePickupProtocolVersionType = MPPs[number]['version'] + +export interface QueueMessageOptions { + connectionId: string + message: EncryptedMessage +} + +export interface PickupMessagesOptions { + connectionId: string + protocolVersion: MessagePickupProtocolVersionType + recipientKey?: string + batchSize?: number +} + +export type QueueMessageReturnType = void + +export type PickupMessagesReturnType = void diff --git "a/packages/core/src/modules/message-p\303\254ckup/MessagePickupModule.ts" "b/packages/core/src/modules/message-p\303\254ckup/MessagePickupModule.ts" new file mode 100644 index 0000000000..5cf4540625 --- /dev/null +++ "b/packages/core/src/modules/message-p\303\254ckup/MessagePickupModule.ts" @@ -0,0 +1,60 @@ +import type { MessagePickupModuleConfigOptions } from './MessagePickupModuleConfig' +import type { MessagePickupProtocol } from './protocol/MessagePickupProtocol' +import type { FeatureRegistry } from '../../agent/FeatureRegistry' +import type { ApiModule, DependencyManager } from '../../plugins' +import type { Optional } from '../../utils' +import type { Constructor } from '../../utils/mixins' + +import { InjectionSymbols } from '../../constants' + +import { MessagePickupApi } from './MessagePickupApi' +import { MessagePickupModuleConfig } from './MessagePickupModuleConfig' +import { V1MessagePickupProtocol, V2MessagePickupProtocol } from './protocol' + +/** + * Default protocols that will be registered if the `protocols` property is not configured. + */ +export type DefaultMessagePickupProtocols = [V1MessagePickupProtocol, V2MessagePickupProtocol] + +// MessagePickupModuleOptions makes the protocols property optional from the config, as it will set it when not provided. +export type MessagePickupModuleOptions = Optional< + MessagePickupModuleConfigOptions, + 'protocols' +> + +export class MessagePickupModule + implements ApiModule +{ + public readonly config: MessagePickupModuleConfig + + // Infer Api type from the config + public readonly api: Constructor> = MessagePickupApi + + public constructor(config?: MessagePickupModuleOptions) { + this.config = new MessagePickupModuleConfig({ + ...config, + protocols: config?.protocols ?? [new V1MessagePickupProtocol(), new V2MessagePickupProtocol()], + }) as MessagePickupModuleConfig + } + + /** + * Registers the dependencies of the question answer module on the dependency manager. + */ + public register(dependencyManager: DependencyManager, featureRegistry: FeatureRegistry) { + // Api + dependencyManager.registerContextScoped(MessagePickupApi) + + // Config + dependencyManager.registerInstance(MessagePickupModuleConfig, this.config) + + // Message repository + if (this.config.messageRepository) { + dependencyManager.registerInstance(InjectionSymbols.MessageRepository, this.config.messageRepository) + } + + // Protocol needs to register feature registry items and handlers + for (const protocol of this.config.protocols) { + protocol.register(dependencyManager, featureRegistry) + } + } +} diff --git "a/packages/core/src/modules/message-p\303\254ckup/MessagePickupModuleConfig.ts" "b/packages/core/src/modules/message-p\303\254ckup/MessagePickupModuleConfig.ts" new file mode 100644 index 0000000000..d755c082e3 --- /dev/null +++ "b/packages/core/src/modules/message-p\303\254ckup/MessagePickupModuleConfig.ts" @@ -0,0 +1,57 @@ +import type { MessagePickupProtocol } from './protocol/MessagePickupProtocol' +import type { MessageRepository } from '../../storage/MessageRepository' + +/** + * MessagePickupModuleConfigOptions defines the interface for the options of the MessagePickupModuleConfig class. + * This can contain optional parameters that have default values in the config class itself. + */ +export interface MessagePickupModuleConfigOptions { + /** + * Maximum number of messages to retrieve in a single batch message pickup + * + * @default 10 + */ + maximumBatchSize?: number + + /** + * Message pickup protocols to make available to the message pickup module. Only one protocol should be registered for each + * protocol version. + * + * When not provided, V1MessagePickupProtocol and V2MessagePickupProtocol` are registered by default. + * + * @default + * ``` + * [V1MessagePickupProtocol, V2MessagePickupProtocol] + * ``` + */ + protocols: MessagePickupProtocols + + /** + * Allows to specify a custom pickup message queue. It defaults to an in-memory repository + * + */ + messageRepository?: MessageRepository +} + +export class MessagePickupModuleConfig { + private options: MessagePickupModuleConfigOptions + + public constructor(options: MessagePickupModuleConfigOptions) { + this.options = options + } + + /** See {@link MessagePickupModuleConfig.maximumBatchSize} */ + public get maximumBatchSize() { + return this.options.maximumBatchSize ?? 10 + } + + /** See {@link MessagePickupModuleConfig.protocols} */ + public get protocols() { + return this.options.protocols + } + + /** See {@link MessagePickupModuleConfig.protocols} */ + public get messageRepository() { + return this.options.messageRepository + } +} diff --git "a/packages/core/src/modules/message-p\303\254ckup/__tests__/MessagePickupModule.test.ts" "b/packages/core/src/modules/message-p\303\254ckup/__tests__/MessagePickupModule.test.ts" new file mode 100644 index 0000000000..141d8f81af --- /dev/null +++ "b/packages/core/src/modules/message-p\303\254ckup/__tests__/MessagePickupModule.test.ts" @@ -0,0 +1,42 @@ +import { FeatureRegistry } from '../../../agent/FeatureRegistry' +import { Protocol } from '../../../agent/models' +import { DependencyManager } from '../../../plugins/DependencyManager' +import { MessagePickupApi } from '../MessagePickupApi' +import { MessagePickupModule } from '../MessagePickupModule' +import { MessagePickupModuleConfig } from '../MessagePickupModuleConfig' + +jest.mock('../../../plugins/DependencyManager') +const DependencyManagerMock = DependencyManager as jest.Mock + +jest.mock('../../../agent/FeatureRegistry') +const FeatureRegistryMock = FeatureRegistry as jest.Mock + +const dependencyManager = new DependencyManagerMock() +const featureRegistry = new FeatureRegistryMock() + +describe('MessagePickupModule', () => { + test('registers dependencies on the dependency manager', () => { + const module = new MessagePickupModule() + module.register(dependencyManager, featureRegistry) + + expect(dependencyManager.registerContextScoped).toHaveBeenCalledTimes(1) + expect(dependencyManager.registerContextScoped).toHaveBeenCalledWith(MessagePickupApi) + + expect(dependencyManager.registerInstance).toHaveBeenCalledTimes(1) + expect(dependencyManager.registerInstance).toHaveBeenCalledWith(MessagePickupModuleConfig, module.config) + + expect(featureRegistry.register).toHaveBeenCalledTimes(2) + expect(featureRegistry.register).toHaveBeenCalledWith( + new Protocol({ + id: 'https://didcomm.org/messagepickup/1.0', + roles: ['message_holder', 'recipient', 'batch_sender', 'batch_recipient'], + }) + ) + expect(featureRegistry.register).toHaveBeenCalledWith( + new Protocol({ + id: 'https://didcomm.org/messagepickup/2.0', + roles: ['mediator', 'recipient'], + }) + ) + }) +}) diff --git a/packages/core/src/modules/routing/__tests__/pickup.test.ts "b/packages/core/src/modules/message-p\303\254ckup/__tests__/pickup.test.ts" similarity index 67% rename from packages/core/src/modules/routing/__tests__/pickup.test.ts rename to "packages/core/src/modules/message-p\303\254ckup/__tests__/pickup.test.ts" index f11674717a..174ff25fd5 100644 --- a/packages/core/src/modules/routing/__tests__/pickup.test.ts +++ "b/packages/core/src/modules/message-p\303\254ckup/__tests__/pickup.test.ts" @@ -5,37 +5,36 @@ import { Subject } from 'rxjs' import { SubjectInboundTransport } from '../../../../../../tests/transport/SubjectInboundTransport' import { SubjectOutboundTransport } from '../../../../../../tests/transport/SubjectOutboundTransport' -import { getAgentOptions, waitForBasicMessage } from '../../../../tests/helpers' +import { getIndySdkModules } from '../../../../../indy-sdk/tests/setupIndySdkModule' +import { getAgentOptions, waitForBasicMessage, waitForTrustPingReceivedEvent } from '../../../../tests/helpers' import { Agent } from '../../../agent/Agent' import { HandshakeProtocol } from '../../connections' -import { MediatorPickupStrategy } from '../MediatorPickupStrategy' -const recipientOptions = getAgentOptions('Mediation: Recipient Pickup', { - autoAcceptConnections: true, - indyLedgers: [], -}) -const mediatorOptions = getAgentOptions('Mediation: Mediator Pickup', { - autoAcceptConnections: true, - endpoints: ['rxjs:mediator'], - indyLedgers: [], -}) +const recipientOptions = getAgentOptions('Mediation: Recipient Pickup', {}, getIndySdkModules()) +const mediatorOptions = getAgentOptions( + 'Mediation: Mediator Pickup', + { + endpoints: ['wss://mediator'], + }, + getIndySdkModules() +) describe('E2E Pick Up protocol', () => { let recipientAgent: Agent let mediatorAgent: Agent afterEach(async () => { - await recipientAgent?.shutdown() - await recipientAgent?.wallet.delete() - await mediatorAgent?.shutdown() - await mediatorAgent?.wallet.delete() + await recipientAgent.shutdown() + await recipientAgent.wallet.delete() + await mediatorAgent.shutdown() + await mediatorAgent.wallet.delete() }) test('E2E Pick Up V1 protocol', async () => { const mediatorMessages = new Subject() const subjectMap = { - 'rxjs:mediator': mediatorMessages, + 'wss://mediator': mediatorMessages, } // Initialize mediatorReceived message @@ -73,10 +72,20 @@ describe('E2E Pick Up protocol', () => { mediatorRecipientConnection = await mediatorAgent.connections.returnWhenIsConnected(mediatorRecipientConnection.id) + // Now they are connected, reinitialize recipient agent in order to lose the session (as with SubjectTransport it remains open) + await recipientAgent.shutdown() + + recipientAgent = new Agent(recipientOptions) + recipientAgent.registerOutboundTransport(new SubjectOutboundTransport(subjectMap)) + await recipientAgent.initialize() + const message = 'hello pickup V1' await mediatorAgent.basicMessages.sendMessage(mediatorRecipientConnection.id, message) - await recipientAgent.mediationRecipient.pickupMessages(recipientMediatorConnection) + await recipientAgent.messagePickup.pickupMessages({ + connectionId: recipientMediatorConnection.id, + protocolVersion: 'v1', + }) const basicMessage = await waitForBasicMessage(recipientAgent, { content: message, @@ -88,8 +97,12 @@ describe('E2E Pick Up protocol', () => { test('E2E Pick Up V2 protocol', async () => { const mediatorMessages = new Subject() + // FIXME: we harcoded that pickup of messages MUST be using ws(s) scheme when doing implicit pickup + // For liver delivery we need a duplex transport. however that means we can't test it with the subject transport. Using wss here to 'hack' this. We should + // extend the API to allow custom schemes (or maybe add a `supportsDuplex` transport / `supportMultiReturnMessages`) + // For pickup v2 pickup message (which we're testing here) we could just as well use `http` as it is just request/response. const subjectMap = { - 'rxjs:mediator': mediatorMessages, + 'wss://mediator': mediatorMessages, } // Initialize mediatorReceived message @@ -128,14 +141,23 @@ describe('E2E Pick Up protocol', () => { mediatorRecipientConnection = await mediatorAgent.connections.returnWhenIsConnected(mediatorRecipientConnection.id) const message = 'hello pickup V2' - await mediatorAgent.basicMessages.sendMessage(mediatorRecipientConnection.id, message) - await recipientAgent.mediationRecipient.pickupMessages(recipientMediatorConnection, MediatorPickupStrategy.PickUpV2) + await mediatorAgent.basicMessages.sendMessage(mediatorRecipientConnection.id, message) - const basicMessage = await waitForBasicMessage(recipientAgent, { + const basicMessagePromise = waitForBasicMessage(recipientAgent, { content: message, }) + const trustPingPromise = waitForTrustPingReceivedEvent(mediatorAgent, {}) + await recipientAgent.messagePickup.pickupMessages({ + connectionId: recipientMediatorConnection.id, + protocolVersion: 'v2', + }) + const basicMessage = await basicMessagePromise expect(basicMessage.content).toBe(message) + + // Wait for trust ping to be received and stop message pickup + await trustPingPromise + await recipientAgent.mediationRecipient.stopMessagePickup() }) }) diff --git "a/packages/core/src/modules/message-p\303\254ckup/index.ts" "b/packages/core/src/modules/message-p\303\254ckup/index.ts" new file mode 100644 index 0000000000..b4745b6037 --- /dev/null +++ "b/packages/core/src/modules/message-p\303\254ckup/index.ts" @@ -0,0 +1,5 @@ +export * from './MessagePickupApi' +export * from './MessagePickupApiOptions' +export * from './MessagePickupModule' +export * from './MessagePickupModuleConfig' +export * from './protocol' diff --git "a/packages/core/src/modules/message-p\303\254ckup/protocol/BaseMessagePickupProtocol.ts" "b/packages/core/src/modules/message-p\303\254ckup/protocol/BaseMessagePickupProtocol.ts" new file mode 100644 index 0000000000..eaa0d71bfd --- /dev/null +++ "b/packages/core/src/modules/message-p\303\254ckup/protocol/BaseMessagePickupProtocol.ts" @@ -0,0 +1,21 @@ +import type { MessagePickupProtocol } from './MessagePickupProtocol' +import type { PickupMessagesProtocolOptions, PickupMessagesProtocolReturnType } from './MessagePickupProtocolOptions' +import type { AgentContext } from '../../../agent' +import type { FeatureRegistry } from '../../../agent/FeatureRegistry' +import type { DidCommV1Message } from '../../../didcomm' +import type { DependencyManager } from '../../../plugins' + +/** + * Base implementation of the MessagePickupProtocol that can be used as a foundation for implementing + * the MessagePickupProtocol interface. + */ +export abstract class BaseMessagePickupProtocol implements MessagePickupProtocol { + public abstract readonly version: string + + public abstract pickupMessages( + agentContext: AgentContext, + options: PickupMessagesProtocolOptions + ): Promise> + + public abstract register(dependencyManager: DependencyManager, featureRegistry: FeatureRegistry): void +} diff --git "a/packages/core/src/modules/message-p\303\254ckup/protocol/MessagePickupProtocol.ts" "b/packages/core/src/modules/message-p\303\254ckup/protocol/MessagePickupProtocol.ts" new file mode 100644 index 0000000000..469a27cac8 --- /dev/null +++ "b/packages/core/src/modules/message-p\303\254ckup/protocol/MessagePickupProtocol.ts" @@ -0,0 +1,16 @@ +import type { PickupMessagesProtocolOptions, PickupMessagesProtocolReturnType } from './MessagePickupProtocolOptions' +import type { AgentContext } from '../../../agent' +import type { FeatureRegistry } from '../../../agent/FeatureRegistry' +import type { DidCommV1Message } from '../../../didcomm' +import type { DependencyManager } from '../../../plugins' + +export interface MessagePickupProtocol { + readonly version: string + + pickupMessages( + agentContext: AgentContext, + options: PickupMessagesProtocolOptions + ): Promise> + + register(dependencyManager: DependencyManager, featureRegistry: FeatureRegistry): void +} diff --git "a/packages/core/src/modules/message-p\303\254ckup/protocol/MessagePickupProtocolOptions.ts" "b/packages/core/src/modules/message-p\303\254ckup/protocol/MessagePickupProtocolOptions.ts" new file mode 100644 index 0000000000..cf2034c356 --- /dev/null +++ "b/packages/core/src/modules/message-p\303\254ckup/protocol/MessagePickupProtocolOptions.ts" @@ -0,0 +1,12 @@ +import type { DidCommV1Message } from '../../../didcomm' +import type { ConnectionRecord } from '../../connections' + +export interface PickupMessagesProtocolOptions { + connectionRecord: ConnectionRecord + recipientKey?: string + batchSize?: number +} + +export type PickupMessagesProtocolReturnType = { + message: MessageType +} diff --git a/packages/core/src/modules/routing/protocol/pickup/index.ts "b/packages/core/src/modules/message-p\303\254ckup/protocol/index.ts" similarity index 100% rename from packages/core/src/modules/routing/protocol/pickup/index.ts rename to "packages/core/src/modules/message-p\303\254ckup/protocol/index.ts" diff --git "a/packages/core/src/modules/message-p\303\254ckup/protocol/v1/V1MessagePickupProtocol.ts" "b/packages/core/src/modules/message-p\303\254ckup/protocol/v1/V1MessagePickupProtocol.ts" new file mode 100644 index 0000000000..6271c7dceb --- /dev/null +++ "b/packages/core/src/modules/message-p\303\254ckup/protocol/v1/V1MessagePickupProtocol.ts" @@ -0,0 +1,85 @@ +import type { AgentContext } from '../../../../agent' +import type { FeatureRegistry } from '../../../../agent/FeatureRegistry' +import type { InboundMessageContext } from '../../../../agent/models/InboundMessageContext' +import type { DidCommV1Message } from '../../../../didcomm' +import type { DependencyManager } from '../../../../plugins' +import type { MessageRepository } from '../../../../storage/MessageRepository' +import type { PickupMessagesProtocolOptions, PickupMessagesProtocolReturnType } from '../MessagePickupProtocolOptions' + +import { OutboundMessageContext, Protocol } from '../../../../agent/models' +import { InjectionSymbols } from '../../../../constants' +import { injectable } from '../../../../plugins' +import { MessagePickupModuleConfig } from '../../MessagePickupModuleConfig' +import { BaseMessagePickupProtocol } from '../BaseMessagePickupProtocol' + +import { V1BatchHandler, V1BatchPickupHandler } from './handlers' +import { V1BatchMessage, BatchMessageMessage, V1BatchPickupMessage } from './messages' + +@injectable() +export class V1MessagePickupProtocol extends BaseMessagePickupProtocol { + public constructor() { + super() + } + + /** + * The version of the message pickup protocol this class supports + */ + public readonly version = 'v1' as const + + /** + * Registers the protocol implementation (handlers, feature registry) on the agent. + */ + public register(dependencyManager: DependencyManager, featureRegistry: FeatureRegistry): void { + dependencyManager.registerMessageHandlers([new V1BatchPickupHandler(this), new V1BatchHandler()]) + + featureRegistry.register( + new Protocol({ + id: 'https://didcomm.org/messagepickup/1.0', + roles: ['message_holder', 'recipient', 'batch_sender', 'batch_recipient'], + }) + ) + } + + public async pickupMessages( + agentContext: AgentContext, + options: PickupMessagesProtocolOptions + ): Promise> { + const { connectionRecord, batchSize } = options + connectionRecord.assertReady() + + const config = agentContext.dependencyManager.resolve(MessagePickupModuleConfig) + const message = new V1BatchPickupMessage({ + batchSize: batchSize ?? config.maximumBatchSize, + }) + + return { message } + } + + public async processBatchPickup(messageContext: InboundMessageContext) { + // Assert ready connection + const connection = messageContext.assertReadyConnection() + + const { message } = messageContext + + const messageRepository = messageContext.agentContext.dependencyManager.resolve( + InjectionSymbols.MessageRepository + ) + + const messages = await messageRepository.takeFromQueue(connection.id, message.batchSize) + + // TODO: each message should be stored with an id. to be able to conform to the id property + // of batch message + const batchMessages = messages.map( + (msg) => + new BatchMessageMessage({ + message: msg, + }) + ) + + const batchMessage = new V1BatchMessage({ + messages: batchMessages, + }) + + return new OutboundMessageContext(batchMessage, { agentContext: messageContext.agentContext, connection }) + } +} diff --git "a/packages/core/src/modules/message-p\303\254ckup/protocol/v1/handlers/V1BatchHandler.ts" "b/packages/core/src/modules/message-p\303\254ckup/protocol/v1/handlers/V1BatchHandler.ts" new file mode 100644 index 0000000000..071711f9e3 --- /dev/null +++ "b/packages/core/src/modules/message-p\303\254ckup/protocol/v1/handlers/V1BatchHandler.ts" @@ -0,0 +1,28 @@ +import type { AgentMessageReceivedEvent } from '../../../../../agent/Events' +import type { MessageHandler, MessageHandlerInboundMessage } from '../../../../../agent/MessageHandler' + +import { EventEmitter } from '../../../../../agent/EventEmitter' +import { AgentEventTypes } from '../../../../../agent/Events' +import { V1BatchMessage } from '../messages' + +export class V1BatchHandler implements MessageHandler { + public supportedMessages = [V1BatchMessage] + + public async handle(messageContext: MessageHandlerInboundMessage) { + const { message } = messageContext + const eventEmitter = messageContext.agentContext.dependencyManager.resolve(EventEmitter) + + messageContext.assertReadyConnection() + + const forwardedMessages = message.messages + forwardedMessages.forEach((message) => { + eventEmitter.emit(messageContext.agentContext, { + type: AgentEventTypes.AgentMessageReceived, + payload: { + message: message.message, + contextCorrelationId: messageContext.agentContext.contextCorrelationId, + }, + }) + }) + } +} diff --git "a/packages/core/src/modules/message-p\303\254ckup/protocol/v1/handlers/V1BatchPickupHandler.ts" "b/packages/core/src/modules/message-p\303\254ckup/protocol/v1/handlers/V1BatchPickupHandler.ts" new file mode 100644 index 0000000000..d9eee7c4d9 --- /dev/null +++ "b/packages/core/src/modules/message-p\303\254ckup/protocol/v1/handlers/V1BatchPickupHandler.ts" @@ -0,0 +1,19 @@ +import type { MessageHandler, MessageHandlerInboundMessage } from '../../../../../agent/MessageHandler' +import type { V1MessagePickupProtocol } from '../V1MessagePickupProtocol' + +import { V1BatchPickupMessage } from '../messages' + +export class V1BatchPickupHandler implements MessageHandler { + private messagePickupService: V1MessagePickupProtocol + public supportedMessages = [V1BatchPickupMessage] + + public constructor(messagePickupService: V1MessagePickupProtocol) { + this.messagePickupService = messagePickupService + } + + public async handle(messageContext: MessageHandlerInboundMessage) { + messageContext.assertReadyConnection() + + return this.messagePickupService.processBatchPickup(messageContext) + } +} diff --git "a/packages/core/src/modules/message-p\303\254ckup/protocol/v1/handlers/index.ts" "b/packages/core/src/modules/message-p\303\254ckup/protocol/v1/handlers/index.ts" new file mode 100644 index 0000000000..b8aef88046 --- /dev/null +++ "b/packages/core/src/modules/message-p\303\254ckup/protocol/v1/handlers/index.ts" @@ -0,0 +1,2 @@ +export * from './V1BatchHandler' +export * from './V1BatchPickupHandler' diff --git "a/packages/core/src/modules/message-p\303\254ckup/protocol/v1/index.ts" "b/packages/core/src/modules/message-p\303\254ckup/protocol/v1/index.ts" new file mode 100644 index 0000000000..abf43d6b2a --- /dev/null +++ "b/packages/core/src/modules/message-p\303\254ckup/protocol/v1/index.ts" @@ -0,0 +1,2 @@ +export * from './V1MessagePickupProtocol' +export * from './messages' diff --git a/packages/core/src/modules/routing/protocol/pickup/v1/messages/BatchMessage.ts "b/packages/core/src/modules/message-p\303\254ckup/protocol/v1/messages/V1BatchMessage.ts" similarity index 74% rename from packages/core/src/modules/routing/protocol/pickup/v1/messages/BatchMessage.ts rename to "packages/core/src/modules/message-p\303\254ckup/protocol/v1/messages/V1BatchMessage.ts" index b638a26fcb..02a1131e8f 100644 --- a/packages/core/src/modules/routing/protocol/pickup/v1/messages/BatchMessage.ts +++ "b/packages/core/src/modules/message-p\303\254ckup/protocol/v1/messages/V1BatchMessage.ts" @@ -1,11 +1,11 @@ import { Type, Expose } from 'class-transformer' import { Matches, IsArray, ValidateNested, IsObject, IsInstance } from 'class-validator' -import { DidCommV1Message } from '../../../../../../didcomm' -import { EncryptedMessage } from '../../../../../../didcomm/types' -import { MessageIdRegExp } from '../../../../../../didcomm/validation' -import { IsValidMessageType, parseMessageType } from '../../../../../../utils/messageType' -import { uuid } from '../../../../../../utils/uuid' +import { DidCommV1Message } from '../../../../../didcomm' +import { EncryptedMessage } from '../../../../../didcomm/types' +import { MessageIdRegExp } from '../../../../../didcomm/validation' +import { IsValidMessageType, parseMessageType } from '../../../../../utils/messageType' +import { uuid } from '../../../../../utils/uuid' export class BatchMessageMessage { public constructor(options: { id?: string; message: EncryptedMessage }) { @@ -32,7 +32,7 @@ export interface BatchMessageOptions { * * @see https://github.com/hyperledger/aries-rfcs/blob/master/features/0212-pickup/README.md#batch */ -export class BatchMessage extends DidCommV1Message { +export class V1BatchMessage extends DidCommV1Message { public constructor(options: BatchMessageOptions) { super() @@ -42,8 +42,8 @@ export class BatchMessage extends DidCommV1Message { } } - @IsValidMessageType(BatchMessage.type) - public readonly type = BatchMessage.type.messageTypeUri + @IsValidMessageType(V1BatchMessage.type) + public readonly type = V1BatchMessage.type.messageTypeUri public static readonly type = parseMessageType('https://didcomm.org/messagepickup/1.0/batch') @Type(() => BatchMessageMessage) diff --git a/packages/core/src/modules/routing/protocol/pickup/v1/messages/BatchPickupMessage.ts "b/packages/core/src/modules/message-p\303\254ckup/protocol/v1/messages/V1BatchPickupMessage.ts" similarity index 77% rename from packages/core/src/modules/routing/protocol/pickup/v1/messages/BatchPickupMessage.ts rename to "packages/core/src/modules/message-p\303\254ckup/protocol/v1/messages/V1BatchPickupMessage.ts" index 3337dbc056..0a846ea2dc 100644 --- a/packages/core/src/modules/routing/protocol/pickup/v1/messages/BatchPickupMessage.ts +++ "b/packages/core/src/modules/message-p\303\254ckup/protocol/v1/messages/V1BatchPickupMessage.ts" @@ -1,8 +1,8 @@ import { Expose } from 'class-transformer' import { IsInt } from 'class-validator' -import { DidCommV1Message } from '../../../../../../didcomm' -import { IsValidMessageType, parseMessageType } from '../../../../../../utils/messageType' +import { DidCommV1Message } from '../../../../../didcomm' +import { IsValidMessageType, parseMessageType } from '../../../../../utils/messageType' export interface BatchPickupMessageOptions { id?: string @@ -14,7 +14,7 @@ export interface BatchPickupMessageOptions { * * @see https://github.com/hyperledger/aries-rfcs/blob/master/features/0212-pickup/README.md#batch-pickup */ -export class BatchPickupMessage extends DidCommV1Message { +export class V1BatchPickupMessage extends DidCommV1Message { /** * Create new BatchPickupMessage instance. * @@ -29,8 +29,8 @@ export class BatchPickupMessage extends DidCommV1Message { } } - @IsValidMessageType(BatchPickupMessage.type) - public readonly type = BatchPickupMessage.type.messageTypeUri + @IsValidMessageType(V1BatchPickupMessage.type) + public readonly type = V1BatchPickupMessage.type.messageTypeUri public static readonly type = parseMessageType('https://didcomm.org/messagepickup/1.0/batch-pickup') @IsInt() diff --git "a/packages/core/src/modules/message-p\303\254ckup/protocol/v1/messages/index.ts" "b/packages/core/src/modules/message-p\303\254ckup/protocol/v1/messages/index.ts" new file mode 100644 index 0000000000..19c16cf1d8 --- /dev/null +++ "b/packages/core/src/modules/message-p\303\254ckup/protocol/v1/messages/index.ts" @@ -0,0 +1,2 @@ +export * from './V1BatchMessage' +export * from './V1BatchPickupMessage' diff --git "a/packages/core/src/modules/message-p\303\254ckup/protocol/v2/V2MessagePickupProtocol.ts" "b/packages/core/src/modules/message-p\303\254ckup/protocol/v2/V2MessagePickupProtocol.ts" new file mode 100644 index 0000000000..4c581f96b3 --- /dev/null +++ "b/packages/core/src/modules/message-p\303\254ckup/protocol/v2/V2MessagePickupProtocol.ts" @@ -0,0 +1,253 @@ +import type { AgentContext } from '../../../../agent' +import type { AgentMessageReceivedEvent } from '../../../../agent/Events' +import type { FeatureRegistry } from '../../../../agent/FeatureRegistry' +import type { InboundMessageContext } from '../../../../agent/models/InboundMessageContext' +import type { DidCommV1Message } from '../../../../didcomm' +import type { EncryptedMessage } from '../../../../didcomm/types' +import type { DependencyManager } from '../../../../plugins' +import type { MessageRepository } from '../../../../storage/MessageRepository' +import type { PickupMessagesProtocolOptions, PickupMessagesProtocolReturnType } from '../MessagePickupProtocolOptions' + +import { EventEmitter } from '../../../../agent/EventEmitter' +import { AgentEventTypes } from '../../../../agent/Events' +import { MessageSender } from '../../../../agent/MessageSender' +import { OutboundMessageContext, Protocol } from '../../../../agent/models' +import { InjectionSymbols } from '../../../../constants' +import { V1Attachment } from '../../../../decorators/attachment/V1Attachment' +import { AriesFrameworkError } from '../../../../error' +import { injectable } from '../../../../plugins' +import { ConnectionService } from '../../../connections' +import { ProblemReportError } from '../../../problem-reports' +import { RoutingProblemReportReason } from '../../../routing/error' +import { MessagePickupModuleConfig } from '../../MessagePickupModuleConfig' +import { BaseMessagePickupProtocol } from '../BaseMessagePickupProtocol' + +import { + V2DeliveryRequestHandler, + V2MessageDeliveryHandler, + V2MessagesReceivedHandler, + V2StatusHandler, + V2StatusRequestHandler, +} from './handlers' +import { + V2MessageDeliveryMessage, + V2StatusMessage, + V2DeliveryRequestMessage, + V2MessagesReceivedMessage, + V2StatusRequestMessage, +} from './messages' + +@injectable() +export class V2MessagePickupProtocol extends BaseMessagePickupProtocol { + public constructor() { + super() + } + + /** + * The version of the message pickup protocol this class supports + */ + public readonly version = 'v2' as const + + /** + * Registers the protocol implementation (handlers, feature registry) on the agent. + */ + public register(dependencyManager: DependencyManager, featureRegistry: FeatureRegistry): void { + dependencyManager.registerMessageHandlers([ + new V2StatusRequestHandler(this), + new V2DeliveryRequestHandler(this), + new V2MessagesReceivedHandler(this), + new V2StatusHandler(this), + new V2MessageDeliveryHandler(this), + ]) + + featureRegistry.register( + new Protocol({ + id: 'https://didcomm.org/messagepickup/2.0', + roles: ['mediator', 'recipient'], + }) + ) + } + + public async pickupMessages( + agentContext: AgentContext, + options: PickupMessagesProtocolOptions + ): Promise> { + const { connectionRecord, recipientKey } = options + connectionRecord.assertReady() + + const message = new V2StatusRequestMessage({ + recipientKey, + }) + + return { message } + } + + public async processStatusRequest(messageContext: InboundMessageContext) { + // Assert ready connection + const connection = messageContext.assertReadyConnection() + + const messageRepository = messageContext.agentContext.dependencyManager.resolve( + InjectionSymbols.MessageRepository + ) + + if (messageContext.message.recipientKey) { + throw new AriesFrameworkError('recipient_key parameter not supported') + } + + const statusMessage = new V2StatusMessage({ + threadId: messageContext.message.threadId, + messageCount: await messageRepository.getAvailableMessageCount(connection.id), + }) + + return new OutboundMessageContext(statusMessage, { agentContext: messageContext.agentContext, connection }) + } + + public async processDeliveryRequest(messageContext: InboundMessageContext) { + // Assert ready connection + const connection = messageContext.assertReadyConnection() + + if (messageContext.message.recipientKey) { + throw new AriesFrameworkError('recipient_key parameter not supported') + } + + const { message } = messageContext + + const messageRepository = messageContext.agentContext.dependencyManager.resolve( + InjectionSymbols.MessageRepository + ) + + // Get available messages from queue, but don't delete them + const messages = await messageRepository.takeFromQueue(connection.id, message.limit, true) + + // TODO: each message should be stored with an id. to be able to conform to the id property + // of delivery message + const attachments = messages.map( + (msg) => + new V1Attachment({ + data: { + json: msg, + }, + }) + ) + + const outboundMessageContext = + messages.length > 0 + ? new V2MessageDeliveryMessage({ + threadId: messageContext.message.threadId, + attachments, + }) + : new V2StatusMessage({ + threadId: messageContext.message.threadId, + messageCount: 0, + }) + + return new OutboundMessageContext(outboundMessageContext, { agentContext: messageContext.agentContext, connection }) + } + + public async processMessagesReceived(messageContext: InboundMessageContext) { + // Assert ready connection + const connection = messageContext.assertReadyConnection() + + const { message } = messageContext + + const messageRepository = messageContext.agentContext.dependencyManager.resolve( + InjectionSymbols.MessageRepository + ) + + // TODO: Add Queued Message ID + await messageRepository.takeFromQueue( + connection.id, + message.messageIdList ? message.messageIdList.length : undefined + ) + + const statusMessage = new V2StatusMessage({ + threadId: messageContext.message.threadId, + messageCount: await messageRepository.getAvailableMessageCount(connection.id), + }) + + return new OutboundMessageContext(statusMessage, { agentContext: messageContext.agentContext, connection }) + } + + public async processStatus(messageContext: InboundMessageContext) { + const connection = messageContext.assertReadyConnection() + const { message: statusMessage } = messageContext + const { messageCount, recipientKey } = statusMessage + + const connectionService = messageContext.agentContext.dependencyManager.resolve(ConnectionService) + const messageSender = messageContext.agentContext.dependencyManager.resolve(MessageSender) + const messagePickupModuleConfig = messageContext.agentContext.dependencyManager.resolve(MessagePickupModuleConfig) + + //No messages to be sent + if (messageCount === 0) { + const { message, connectionRecord } = await connectionService.createTrustPing( + messageContext.agentContext, + connection, + { + responseRequested: false, + } + ) + + // FIXME: check where this flow fits, as it seems very particular for the AFJ-ACA-Py combination + const websocketSchemes = ['ws', 'wss'] + + await messageSender.sendMessage( + new OutboundMessageContext(message, { + agentContext: messageContext.agentContext, + connection: connectionRecord, + }), + { + transportPriority: { + schemes: websocketSchemes, + restrictive: true, + // TODO: add keepAlive: true to enforce through the public api + // we need to keep the socket alive. It already works this way, but would + // be good to make more explicit from the public facing API. + // This would also make it easier to change the internal API later on. + // keepAlive: true, + }, + } + ) + + return null + } + const { maximumBatchSize: maximumMessagePickup } = messagePickupModuleConfig + const limit = messageCount < maximumMessagePickup ? messageCount : maximumMessagePickup + + const deliveryRequestMessage = new V2DeliveryRequestMessage({ + limit, + recipientKey, + }) + + return deliveryRequestMessage + } + + public async processDelivery(messageContext: InboundMessageContext) { + messageContext.assertReadyConnection() + + const { appendedAttachments } = messageContext.message + + const eventEmitter = messageContext.agentContext.dependencyManager.resolve(EventEmitter) + + if (!appendedAttachments) + throw new ProblemReportError('Error processing attachments', { + problemCode: RoutingProblemReportReason.ErrorProcessingAttachments, + }) + + const ids: string[] = [] + for (const attachment of appendedAttachments) { + ids.push(attachment.id) + + eventEmitter.emit(messageContext.agentContext, { + type: AgentEventTypes.AgentMessageReceived, + payload: { + message: attachment.getDataAsJson(), + contextCorrelationId: messageContext.agentContext.contextCorrelationId, + }, + }) + } + + return new V2MessagesReceivedMessage({ + messageIdList: ids, + }) + } +} diff --git "a/packages/core/src/modules/message-p\303\254ckup/protocol/v2/__tests__/V2MessagePickupProtocol.test.ts" "b/packages/core/src/modules/message-p\303\254ckup/protocol/v2/__tests__/V2MessagePickupProtocol.test.ts" new file mode 100644 index 0000000000..46fe53bfb9 --- /dev/null +++ "b/packages/core/src/modules/message-p\303\254ckup/protocol/v2/__tests__/V2MessagePickupProtocol.test.ts" @@ -0,0 +1,409 @@ +import type { EncryptedMessage } from '../../../../../didcomm' + +import { getAgentContext, getMockConnection, mockFunction } from '../../../../../../tests/helpers' +import { EventEmitter } from '../../../../../agent/EventEmitter' +import { AgentEventTypes } from '../../../../../agent/Events' +import { MessageSender } from '../../../../../agent/MessageSender' +import { InboundMessageContext } from '../../../../../agent/models/InboundMessageContext' +import { InjectionSymbols } from '../../../../../constants' +import { V1Attachment } from '../../../../../decorators/attachment/V1Attachment' +import { AriesFrameworkError } from '../../../../../error' +import { InMemoryMessageRepository } from '../../../../../storage/InMemoryMessageRepository' +import { uuid } from '../../../../../utils/uuid' +import { DidExchangeState } from '../../../../connections' +import { TrustPingMessage } from '../../../../connections/protocols/trust-ping/v1/messages/TrustPingMessage' +import { ConnectionService } from '../../../../connections/services/ConnectionService' +import { MessagePickupModuleConfig } from '../../../MessagePickupModuleConfig' +import { V1MessagePickupProtocol } from '../../v1' +import { V2MessagePickupProtocol } from '../V2MessagePickupProtocol' +import { + V2DeliveryRequestMessage, + V2MessageDeliveryMessage, + V2MessagesReceivedMessage, + V2StatusMessage, + V2StatusRequestMessage, +} from '../messages' + +const mockConnection = getMockConnection({ + state: DidExchangeState.Completed, +}) + +// Mock classes +jest.mock('../../../../../storage/InMemoryMessageRepository') +jest.mock('../../../../../agent/EventEmitter') +jest.mock('../../../../../agent/MessageSender') +jest.mock('../../../../connections/services/ConnectionService') + +// Mock typed object +const InMessageRepositoryMock = InMemoryMessageRepository as jest.Mock +const EventEmitterMock = EventEmitter as jest.Mock +const MessageSenderMock = MessageSender as jest.Mock +const ConnectionServiceMock = ConnectionService as jest.Mock + +const messagePickupModuleConfig = new MessagePickupModuleConfig({ + maximumBatchSize: 10, + protocols: [new V1MessagePickupProtocol(), new V2MessagePickupProtocol()], +}) +const messageSender = new MessageSenderMock() +const eventEmitter = new EventEmitterMock() +const connectionService = new ConnectionServiceMock() +const messageRepository = new InMessageRepositoryMock() + +const agentContext = getAgentContext({ + registerInstances: [ + [InjectionSymbols.MessageRepository, messageRepository], + [EventEmitter, eventEmitter], + [MessageSender, messageSender], + [ConnectionService, connectionService], + [MessagePickupModuleConfig, messagePickupModuleConfig], + ], +}) + +const encryptedMessage: EncryptedMessage = { + protected: 'base64url', + iv: 'base64url', + ciphertext: 'base64url', + tag: 'base64url', + recipients: [], +} +const queuedMessages = [encryptedMessage, encryptedMessage, encryptedMessage] + +describe('V2MessagePickupService', () => { + let pickupProtocol: V2MessagePickupProtocol + + beforeEach(async () => { + pickupProtocol = new V2MessagePickupProtocol() + }) + + describe('processStatusRequest', () => { + test('no available messages in queue', async () => { + mockFunction(messageRepository.getAvailableMessageCount).mockResolvedValue(0) + + const statusRequest = new V2StatusRequestMessage({}) + + const messageContext = new InboundMessageContext(statusRequest, { connection: mockConnection, agentContext }) + + const { connection, message } = await pickupProtocol.processStatusRequest(messageContext) + + expect(connection).toEqual(mockConnection) + expect(message).toEqual( + new V2StatusMessage({ + id: message.id, + threadId: statusRequest.threadId, + messageCount: 0, + }) + ) + expect(messageRepository.getAvailableMessageCount).toHaveBeenCalledWith(mockConnection.id) + }) + + test('multiple messages in queue', async () => { + mockFunction(messageRepository.getAvailableMessageCount).mockResolvedValue(5) + const statusRequest = new V2StatusRequestMessage({}) + + const messageContext = new InboundMessageContext(statusRequest, { connection: mockConnection, agentContext }) + + const { connection, message } = await pickupProtocol.processStatusRequest(messageContext) + + expect(connection).toEqual(mockConnection) + expect(message).toEqual( + new V2StatusMessage({ + id: message.id, + threadId: statusRequest.threadId, + messageCount: 5, + }) + ) + expect(messageRepository.getAvailableMessageCount).toHaveBeenCalledWith(mockConnection.id) + }) + + test('status request specifying recipient key', async () => { + mockFunction(messageRepository.getAvailableMessageCount).mockResolvedValue(10) + + const statusRequest = new V2StatusRequestMessage({ + recipientKey: 'recipientKey', + }) + + const messageContext = new InboundMessageContext(statusRequest, { connection: mockConnection, agentContext }) + + await expect(pickupProtocol.processStatusRequest(messageContext)).rejects.toThrowError( + 'recipient_key parameter not supported' + ) + }) + }) + + describe('processDeliveryRequest', () => { + test('no available messages in queue', async () => { + mockFunction(messageRepository.takeFromQueue).mockReturnValue([]) + + const deliveryRequest = new V2DeliveryRequestMessage({ limit: 10 }) + + const messageContext = new InboundMessageContext(deliveryRequest, { connection: mockConnection, agentContext }) + + const { connection, message } = await pickupProtocol.processDeliveryRequest(messageContext) + + expect(connection).toEqual(mockConnection) + expect(message).toEqual( + new V2StatusMessage({ + id: message.id, + threadId: deliveryRequest.threadId, + messageCount: 0, + }) + ) + expect(messageRepository.takeFromQueue).toHaveBeenCalledWith(mockConnection.id, 10, true) + }) + + test('less messages in queue than limit', async () => { + mockFunction(messageRepository.takeFromQueue).mockReturnValue(queuedMessages) + + const deliveryRequest = new V2DeliveryRequestMessage({ limit: 10 }) + + const messageContext = new InboundMessageContext(deliveryRequest, { connection: mockConnection, agentContext }) + + const { connection, message } = await pickupProtocol.processDeliveryRequest(messageContext) + + expect(connection).toEqual(mockConnection) + expect(message).toBeInstanceOf(V2MessageDeliveryMessage) + expect(message.threadId).toEqual(deliveryRequest.threadId) + expect(message.appendedAttachments?.length).toEqual(3) + expect(message.appendedAttachments).toEqual( + expect.arrayContaining( + queuedMessages.map((msg) => + expect.objectContaining({ + data: { + json: msg, + }, + }) + ) + ) + ) + expect(messageRepository.takeFromQueue).toHaveBeenCalledWith(mockConnection.id, 10, true) + }) + + test('more messages in queue than limit', async () => { + mockFunction(messageRepository.takeFromQueue).mockReturnValue(queuedMessages.slice(0, 2)) + + const deliveryRequest = new V2DeliveryRequestMessage({ limit: 2 }) + + const messageContext = new InboundMessageContext(deliveryRequest, { connection: mockConnection, agentContext }) + + const { connection, message } = await pickupProtocol.processDeliveryRequest(messageContext) + + expect(connection).toEqual(mockConnection) + expect(message).toBeInstanceOf(V2MessageDeliveryMessage) + expect(message.threadId).toEqual(deliveryRequest.threadId) + expect(message.appendedAttachments?.length).toEqual(2) + expect(message.appendedAttachments).toEqual( + expect.arrayContaining( + queuedMessages.slice(0, 2).map((msg) => + expect.objectContaining({ + data: { + json: msg, + }, + }) + ) + ) + ) + expect(messageRepository.takeFromQueue).toHaveBeenCalledWith(mockConnection.id, 2, true) + }) + + test('delivery request specifying recipient key', async () => { + mockFunction(messageRepository.takeFromQueue).mockReturnValue(queuedMessages) + + const statusRequest = new V2DeliveryRequestMessage({ + limit: 10, + recipientKey: 'recipientKey', + }) + + const messageContext = new InboundMessageContext(statusRequest, { connection: mockConnection, agentContext }) + + await expect(pickupProtocol.processStatusRequest(messageContext)).rejects.toThrowError( + 'recipient_key parameter not supported' + ) + }) + }) + + describe('processMessagesReceived', () => { + test('messages received partially', async () => { + mockFunction(messageRepository.takeFromQueue).mockReturnValue(queuedMessages) + mockFunction(messageRepository.getAvailableMessageCount).mockResolvedValue(4) + + const messagesReceived = new V2MessagesReceivedMessage({ + messageIdList: ['1', '2'], + }) + + const messageContext = new InboundMessageContext(messagesReceived, { connection: mockConnection, agentContext }) + + const { connection, message } = await pickupProtocol.processMessagesReceived(messageContext) + + expect(connection).toEqual(mockConnection) + expect(message).toEqual( + new V2StatusMessage({ + id: message.id, + threadId: messagesReceived.threadId, + messageCount: 4, + }) + ) + expect(messageRepository.getAvailableMessageCount).toHaveBeenCalledWith(mockConnection.id) + expect(messageRepository.takeFromQueue).toHaveBeenCalledWith(mockConnection.id, 2) + }) + + test('all messages have been received', async () => { + mockFunction(messageRepository.takeFromQueue).mockReturnValue(queuedMessages) + mockFunction(messageRepository.getAvailableMessageCount).mockResolvedValue(0) + + const messagesReceived = new V2MessagesReceivedMessage({ + messageIdList: ['1', '2'], + }) + + const messageContext = new InboundMessageContext(messagesReceived, { connection: mockConnection, agentContext }) + + const { connection, message } = await pickupProtocol.processMessagesReceived(messageContext) + + expect(connection).toEqual(mockConnection) + expect(message).toEqual( + new V2StatusMessage({ + id: message.id, + threadId: messagesReceived.threadId, + messageCount: 0, + }) + ) + + expect(messageRepository.getAvailableMessageCount).toHaveBeenCalledWith(mockConnection.id) + expect(messageRepository.takeFromQueue).toHaveBeenCalledWith(mockConnection.id, 2) + }) + }) + + describe('pickupMessages', () => { + it('creates a status request message', async () => { + const { message: statusRequestMessage } = await pickupProtocol.pickupMessages(agentContext, { + connectionRecord: mockConnection, + recipientKey: 'a-key', + }) + + expect(statusRequestMessage).toMatchObject({ + id: expect.any(String), + recipientKey: 'a-key', + }) + }) + }) + + describe('processStatus', () => { + it('if status request has a message count of zero returns nothing', async () => { + const status = new V2StatusMessage({ + threadId: uuid(), + messageCount: 0, + }) + + mockFunction(connectionService.createTrustPing).mockResolvedValueOnce({ + message: new TrustPingMessage({}), + connectionRecord: mockConnection, + }) + + const messageContext = new InboundMessageContext(status, { connection: mockConnection, agentContext }) + const deliveryRequestMessage = await pickupProtocol.processStatus(messageContext) + expect(deliveryRequestMessage).toBeNull() + }) + + it('if it has a message count greater than zero return a valid delivery request', async () => { + const status = new V2StatusMessage({ + threadId: uuid(), + messageCount: 1, + }) + const messageContext = new InboundMessageContext(status, { connection: mockConnection, agentContext }) + + const deliveryRequestMessage = await pickupProtocol.processStatus(messageContext) + expect(deliveryRequestMessage) + expect(deliveryRequestMessage).toEqual(new V2DeliveryRequestMessage({ id: deliveryRequestMessage?.id, limit: 1 })) + }) + }) + + describe('processDelivery', () => { + it('if the delivery has no attachments expect an error', async () => { + const messageContext = new InboundMessageContext({} as V2MessageDeliveryMessage, { + connection: mockConnection, + agentContext, + }) + + await expect(pickupProtocol.processDelivery(messageContext)).rejects.toThrowError( + new AriesFrameworkError('Error processing attachments') + ) + }) + + it('should return a message received with an message id list in it', async () => { + const messageDeliveryMessage = new V2MessageDeliveryMessage({ + threadId: uuid(), + attachments: [ + new V1Attachment({ + id: '1', + data: { + json: { + a: 'value', + }, + }, + }), + ], + }) + const messageContext = new InboundMessageContext(messageDeliveryMessage, { + connection: mockConnection, + agentContext, + }) + + const messagesReceivedMessage = await pickupProtocol.processDelivery(messageContext) + + expect(messagesReceivedMessage).toEqual( + new V2MessagesReceivedMessage({ + id: messagesReceivedMessage.id, + messageIdList: ['1'], + }) + ) + }) + + it('calls the event emitter for each message', async () => { + // This is to not take into account events previously emitted + jest.clearAllMocks() + + const messageDeliveryMessage = new V2MessageDeliveryMessage({ + threadId: uuid(), + attachments: [ + new V1Attachment({ + id: '1', + data: { + json: { + first: 'value', + }, + }, + }), + new V1Attachment({ + id: '2', + data: { + json: { + second: 'value', + }, + }, + }), + ], + }) + const messageContext = new InboundMessageContext(messageDeliveryMessage, { + connection: mockConnection, + agentContext, + }) + + await pickupProtocol.processDelivery(messageContext) + + expect(eventEmitter.emit).toHaveBeenCalledTimes(2) + expect(eventEmitter.emit).toHaveBeenNthCalledWith(1, agentContext, { + type: AgentEventTypes.AgentMessageReceived, + payload: { + message: { first: 'value' }, + contextCorrelationId: agentContext.contextCorrelationId, + }, + }) + expect(eventEmitter.emit).toHaveBeenNthCalledWith(2, agentContext, { + type: AgentEventTypes.AgentMessageReceived, + payload: { + message: { second: 'value' }, + contextCorrelationId: agentContext.contextCorrelationId, + }, + }) + }) + }) +}) diff --git "a/packages/core/src/modules/message-p\303\254ckup/protocol/v2/handlers/V2DeliveryRequestHandler.ts" "b/packages/core/src/modules/message-p\303\254ckup/protocol/v2/handlers/V2DeliveryRequestHandler.ts" new file mode 100644 index 0000000000..b935dcd512 --- /dev/null +++ "b/packages/core/src/modules/message-p\303\254ckup/protocol/v2/handlers/V2DeliveryRequestHandler.ts" @@ -0,0 +1,19 @@ +import type { MessageHandler } from '../../../../../agent/MessageHandler' +import type { InboundMessageContext } from '../../../../../agent/models/InboundMessageContext' +import type { V2MessagePickupProtocol } from '../V2MessagePickupProtocol' + +import { V2DeliveryRequestMessage } from '../messages' + +export class V2DeliveryRequestHandler implements MessageHandler { + public supportedMessages = [V2DeliveryRequestMessage] + private messagePickupService: V2MessagePickupProtocol + + public constructor(messagePickupService: V2MessagePickupProtocol) { + this.messagePickupService = messagePickupService + } + + public async handle(messageContext: InboundMessageContext) { + messageContext.assertReadyConnection() + return this.messagePickupService.processDeliveryRequest(messageContext) + } +} diff --git "a/packages/core/src/modules/message-p\303\254ckup/protocol/v2/handlers/V2MessageDeliveryHandler.ts" "b/packages/core/src/modules/message-p\303\254ckup/protocol/v2/handlers/V2MessageDeliveryHandler.ts" new file mode 100644 index 0000000000..918b3f37b8 --- /dev/null +++ "b/packages/core/src/modules/message-p\303\254ckup/protocol/v2/handlers/V2MessageDeliveryHandler.ts" @@ -0,0 +1,27 @@ +import type { MessageHandler } from '../../../../../agent/MessageHandler' +import type { InboundMessageContext } from '../../../../../agent/models/InboundMessageContext' +import type { V2MessagePickupProtocol } from '../V2MessagePickupProtocol' + +import { OutboundMessageContext } from '../../../../../agent/models' +import { V2MessageDeliveryMessage } from '../messages/V2MessageDeliveryMessage' + +export class V2MessageDeliveryHandler implements MessageHandler { + public supportedMessages = [V2MessageDeliveryMessage] + private messagePickupService: V2MessagePickupProtocol + + public constructor(messagePickupService: V2MessagePickupProtocol) { + this.messagePickupService = messagePickupService + } + + public async handle(messageContext: InboundMessageContext) { + const connection = messageContext.assertReadyConnection() + const deliveryReceivedMessage = await this.messagePickupService.processDelivery(messageContext) + + if (deliveryReceivedMessage) { + return new OutboundMessageContext(deliveryReceivedMessage, { + agentContext: messageContext.agentContext, + connection, + }) + } + } +} diff --git "a/packages/core/src/modules/message-p\303\254ckup/protocol/v2/handlers/V2MessagesReceivedHandler.ts" "b/packages/core/src/modules/message-p\303\254ckup/protocol/v2/handlers/V2MessagesReceivedHandler.ts" new file mode 100644 index 0000000000..5820c4878c --- /dev/null +++ "b/packages/core/src/modules/message-p\303\254ckup/protocol/v2/handlers/V2MessagesReceivedHandler.ts" @@ -0,0 +1,19 @@ +import type { MessageHandler } from '../../../../../agent/MessageHandler' +import type { InboundMessageContext } from '../../../../../agent/models/InboundMessageContext' +import type { V2MessagePickupProtocol } from '../V2MessagePickupProtocol' + +import { V2MessagesReceivedMessage } from '../messages' + +export class V2MessagesReceivedHandler implements MessageHandler { + public supportedMessages = [V2MessagesReceivedMessage] + private messagePickupService: V2MessagePickupProtocol + + public constructor(messagePickupService: V2MessagePickupProtocol) { + this.messagePickupService = messagePickupService + } + + public async handle(messageContext: InboundMessageContext) { + messageContext.assertReadyConnection() + return this.messagePickupService.processMessagesReceived(messageContext) + } +} diff --git "a/packages/core/src/modules/message-p\303\254ckup/protocol/v2/handlers/V2StatusHandler.ts" "b/packages/core/src/modules/message-p\303\254ckup/protocol/v2/handlers/V2StatusHandler.ts" new file mode 100644 index 0000000000..0e4d1467f2 --- /dev/null +++ "b/packages/core/src/modules/message-p\303\254ckup/protocol/v2/handlers/V2StatusHandler.ts" @@ -0,0 +1,27 @@ +import type { MessageHandler } from '../../../../../agent/MessageHandler' +import type { InboundMessageContext } from '../../../../../agent/models/InboundMessageContext' +import type { V2MessagePickupProtocol } from '../V2MessagePickupProtocol' + +import { OutboundMessageContext } from '../../../../../agent/models' +import { V2StatusMessage } from '../messages' + +export class V2StatusHandler implements MessageHandler { + public supportedMessages = [V2StatusMessage] + private messagePickupService: V2MessagePickupProtocol + + public constructor(messagePickupService: V2MessagePickupProtocol) { + this.messagePickupService = messagePickupService + } + + public async handle(messageContext: InboundMessageContext) { + const connection = messageContext.assertReadyConnection() + const deliveryRequestMessage = await this.messagePickupService.processStatus(messageContext) + + if (deliveryRequestMessage) { + return new OutboundMessageContext(deliveryRequestMessage, { + agentContext: messageContext.agentContext, + connection, + }) + } + } +} diff --git "a/packages/core/src/modules/message-p\303\254ckup/protocol/v2/handlers/V2StatusRequestHandler.ts" "b/packages/core/src/modules/message-p\303\254ckup/protocol/v2/handlers/V2StatusRequestHandler.ts" new file mode 100644 index 0000000000..b9e365b8a4 --- /dev/null +++ "b/packages/core/src/modules/message-p\303\254ckup/protocol/v2/handlers/V2StatusRequestHandler.ts" @@ -0,0 +1,19 @@ +import type { MessageHandler } from '../../../../../agent/MessageHandler' +import type { InboundMessageContext } from '../../../../../agent/models/InboundMessageContext' +import type { V2MessagePickupProtocol } from '../V2MessagePickupProtocol' + +import { V2StatusRequestMessage } from '../messages' + +export class V2StatusRequestHandler implements MessageHandler { + public supportedMessages = [V2StatusRequestMessage] + private messagePickupService: V2MessagePickupProtocol + + public constructor(messagePickupService: V2MessagePickupProtocol) { + this.messagePickupService = messagePickupService + } + + public async handle(messageContext: InboundMessageContext) { + messageContext.assertReadyConnection() + return this.messagePickupService.processStatusRequest(messageContext) + } +} diff --git "a/packages/core/src/modules/message-p\303\254ckup/protocol/v2/handlers/index.ts" "b/packages/core/src/modules/message-p\303\254ckup/protocol/v2/handlers/index.ts" new file mode 100644 index 0000000000..5f54b56ac7 --- /dev/null +++ "b/packages/core/src/modules/message-p\303\254ckup/protocol/v2/handlers/index.ts" @@ -0,0 +1,5 @@ +export * from './V2DeliveryRequestHandler' +export * from './V2MessageDeliveryHandler' +export * from './V2MessagesReceivedHandler' +export * from './V2StatusHandler' +export * from './V2StatusRequestHandler' diff --git "a/packages/core/src/modules/message-p\303\254ckup/protocol/v2/index.ts" "b/packages/core/src/modules/message-p\303\254ckup/protocol/v2/index.ts" new file mode 100644 index 0000000000..90567cdaf4 --- /dev/null +++ "b/packages/core/src/modules/message-p\303\254ckup/protocol/v2/index.ts" @@ -0,0 +1,2 @@ +export * from './V2MessagePickupProtocol' +export * from './messages' diff --git a/packages/core/src/modules/routing/protocol/pickup/v2/messages/DeliveryRequestMessage.ts "b/packages/core/src/modules/message-p\303\254ckup/protocol/v2/messages/V2DeliveryRequestMessage.ts" similarity index 59% rename from packages/core/src/modules/routing/protocol/pickup/v2/messages/DeliveryRequestMessage.ts rename to "packages/core/src/modules/message-p\303\254ckup/protocol/v2/messages/V2DeliveryRequestMessage.ts" index a711b86469..bf2e1e4194 100644 --- a/packages/core/src/modules/routing/protocol/pickup/v2/messages/DeliveryRequestMessage.ts +++ "b/packages/core/src/modules/message-p\303\254ckup/protocol/v2/messages/V2DeliveryRequestMessage.ts" @@ -1,18 +1,18 @@ import { Expose } from 'class-transformer' import { IsInt, IsOptional, IsString } from 'class-validator' -import { ReturnRouteTypes } from '../../../../../../decorators/transport/TransportDecorator' -import { DidCommV1Message } from '../../../../../../didcomm' -import { IsValidMessageType, parseMessageType } from '../../../../../../utils/messageType' +import { ReturnRouteTypes } from '../../../../../decorators/transport/TransportDecorator' +import { DidCommV1Message } from '../../../../../didcomm' +import { IsValidMessageType, parseMessageType } from '../../../../../utils/messageType' -export interface DeliveryRequestMessageOptions { +export interface V2DeliveryRequestMessageOptions { id?: string recipientKey?: string limit: number } -export class DeliveryRequestMessage extends DidCommV1Message { - public constructor(options: DeliveryRequestMessageOptions) { +export class V2DeliveryRequestMessage extends DidCommV1Message { + public constructor(options: V2DeliveryRequestMessageOptions) { super() if (options) { @@ -23,8 +23,8 @@ export class DeliveryRequestMessage extends DidCommV1Message { this.setReturnRouting(ReturnRouteTypes.all) } - @IsValidMessageType(DeliveryRequestMessage.type) - public readonly type = DeliveryRequestMessage.type.messageTypeUri + @IsValidMessageType(V2DeliveryRequestMessage.type) + public readonly type = V2DeliveryRequestMessage.type.messageTypeUri public static readonly type = parseMessageType('https://didcomm.org/messagepickup/2.0/delivery-request') @IsString() diff --git a/packages/core/src/modules/routing/protocol/pickup/v2/messages/MessageDeliveryMessage.ts "b/packages/core/src/modules/message-p\303\254ckup/protocol/v2/messages/V2MessageDeliveryMessage.ts" similarity index 57% rename from packages/core/src/modules/routing/protocol/pickup/v2/messages/MessageDeliveryMessage.ts rename to "packages/core/src/modules/message-p\303\254ckup/protocol/v2/messages/V2MessageDeliveryMessage.ts" index c9fc0ea06f..4cb803fae2 100644 --- a/packages/core/src/modules/routing/protocol/pickup/v2/messages/MessageDeliveryMessage.ts +++ "b/packages/core/src/modules/message-p\303\254ckup/protocol/v2/messages/V2MessageDeliveryMessage.ts" @@ -1,21 +1,21 @@ -import type { V1Attachment } from '../../../../../../decorators/attachment/V1Attachment' +import type { V1Attachment } from '../../../../../decorators/attachment/V1Attachment' import { Expose } from 'class-transformer' import { IsOptional, IsString } from 'class-validator' -import { ReturnRouteTypes } from '../../../../../../decorators/transport/TransportDecorator' -import { DidCommV1Message } from '../../../../../../didcomm' -import { IsValidMessageType, parseMessageType } from '../../../../../../utils/messageType' +import { ReturnRouteTypes } from '../../../../../decorators/transport/TransportDecorator' +import { DidCommV1Message } from '../../../../../didcomm' +import { IsValidMessageType, parseMessageType } from '../../../../../utils/messageType' -export interface MessageDeliveryMessageOptions { +export interface V2MessageDeliveryMessageOptions { id?: string recipientKey?: string threadId: string attachments: V1Attachment[] } -export class MessageDeliveryMessage extends DidCommV1Message { - public constructor(options: MessageDeliveryMessageOptions) { +export class V2MessageDeliveryMessage extends DidCommV1Message { + public constructor(options: V2MessageDeliveryMessageOptions) { super() if (options) { @@ -29,8 +29,8 @@ export class MessageDeliveryMessage extends DidCommV1Message { this.setReturnRouting(ReturnRouteTypes.all) } - @IsValidMessageType(MessageDeliveryMessage.type) - public readonly type = MessageDeliveryMessage.type.messageTypeUri + @IsValidMessageType(V2MessageDeliveryMessage.type) + public readonly type = V2MessageDeliveryMessage.type.messageTypeUri public static readonly type = parseMessageType('https://didcomm.org/messagepickup/2.0/delivery') @IsString() diff --git a/packages/core/src/modules/routing/protocol/pickup/v2/messages/MessagesReceivedMessage.ts "b/packages/core/src/modules/message-p\303\254ckup/protocol/v2/messages/V2MessagesReceivedMessage.ts" similarity index 55% rename from packages/core/src/modules/routing/protocol/pickup/v2/messages/MessagesReceivedMessage.ts rename to "packages/core/src/modules/message-p\303\254ckup/protocol/v2/messages/V2MessagesReceivedMessage.ts" index 570705fa0c..849aeeb7b1 100644 --- a/packages/core/src/modules/routing/protocol/pickup/v2/messages/MessagesReceivedMessage.ts +++ "b/packages/core/src/modules/message-p\303\254ckup/protocol/v2/messages/V2MessagesReceivedMessage.ts" @@ -1,17 +1,17 @@ import { Expose } from 'class-transformer' import { IsArray, IsOptional } from 'class-validator' -import { ReturnRouteTypes } from '../../../../../../decorators/transport/TransportDecorator' -import { DidCommV1Message } from '../../../../../../didcomm' -import { IsValidMessageType, parseMessageType } from '../../../../../../utils/messageType' +import { ReturnRouteTypes } from '../../../../../decorators/transport/TransportDecorator' +import { DidCommV1Message } from '../../../../../didcomm' +import { IsValidMessageType, parseMessageType } from '../../../../../utils/messageType' -export interface MessagesReceivedMessageOptions { +export interface V2MessagesReceivedMessageOptions { id?: string messageIdList: string[] } -export class MessagesReceivedMessage extends DidCommV1Message { - public constructor(options: MessagesReceivedMessageOptions) { +export class V2MessagesReceivedMessage extends DidCommV1Message { + public constructor(options: V2MessagesReceivedMessageOptions) { super() if (options) { @@ -21,8 +21,8 @@ export class MessagesReceivedMessage extends DidCommV1Message { this.setReturnRouting(ReturnRouteTypes.all) } - @IsValidMessageType(MessagesReceivedMessage.type) - public readonly type = MessagesReceivedMessage.type.messageTypeUri + @IsValidMessageType(V2MessagesReceivedMessage.type) + public readonly type = V2MessagesReceivedMessage.type.messageTypeUri public static readonly type = parseMessageType('https://didcomm.org/messagepickup/2.0/messages-received') @IsArray() diff --git a/packages/core/src/modules/routing/protocol/pickup/v2/messages/StatusMessage.ts "b/packages/core/src/modules/message-p\303\254ckup/protocol/v2/messages/V2StatusMessage.ts" similarity index 79% rename from packages/core/src/modules/routing/protocol/pickup/v2/messages/StatusMessage.ts rename to "packages/core/src/modules/message-p\303\254ckup/protocol/v2/messages/V2StatusMessage.ts" index 5f14be1e88..f9df0b4090 100644 --- a/packages/core/src/modules/routing/protocol/pickup/v2/messages/StatusMessage.ts +++ "b/packages/core/src/modules/message-p\303\254ckup/protocol/v2/messages/V2StatusMessage.ts" @@ -1,12 +1,12 @@ import { Expose, Transform } from 'class-transformer' import { IsBoolean, IsDate, IsInt, IsOptional, IsString } from 'class-validator' -import { ReturnRouteTypes } from '../../../../../../decorators/transport/TransportDecorator' -import { DidCommV1Message } from '../../../../../../didcomm' -import { IsValidMessageType, parseMessageType } from '../../../../../../utils/messageType' -import { DateParser } from '../../../../../../utils/transformers' +import { ReturnRouteTypes } from '../../../../../decorators/transport/TransportDecorator' +import { DidCommV1Message } from '../../../../../didcomm' +import { IsValidMessageType, parseMessageType } from '../../../../../utils/messageType' +import { DateParser } from '../../../../../utils/transformers' -export interface StatusMessageOptions { +export interface V2StatusMessageOptions { id?: string recipientKey?: string threadId: string @@ -18,8 +18,8 @@ export interface StatusMessageOptions { liveDelivery?: boolean } -export class StatusMessage extends DidCommV1Message { - public constructor(options: StatusMessageOptions) { +export class V2StatusMessage extends DidCommV1Message { + public constructor(options: V2StatusMessageOptions) { super() if (options) { this.id = options.id || this.generateId() @@ -37,8 +37,8 @@ export class StatusMessage extends DidCommV1Message { this.setReturnRouting(ReturnRouteTypes.all) } - @IsValidMessageType(StatusMessage.type) - public readonly type = StatusMessage.type.messageTypeUri + @IsValidMessageType(V2StatusMessage.type) + public readonly type = V2StatusMessage.type.messageTypeUri public static readonly type = parseMessageType('https://didcomm.org/messagepickup/2.0/status') @IsString() diff --git a/packages/core/src/modules/routing/protocol/pickup/v2/messages/StatusRequestMessage.ts "b/packages/core/src/modules/message-p\303\254ckup/protocol/v2/messages/V2StatusRequestMessage.ts" similarity index 59% rename from packages/core/src/modules/routing/protocol/pickup/v2/messages/StatusRequestMessage.ts rename to "packages/core/src/modules/message-p\303\254ckup/protocol/v2/messages/V2StatusRequestMessage.ts" index 195c89edcf..9a94f099ff 100644 --- a/packages/core/src/modules/routing/protocol/pickup/v2/messages/StatusRequestMessage.ts +++ "b/packages/core/src/modules/message-p\303\254ckup/protocol/v2/messages/V2StatusRequestMessage.ts" @@ -1,16 +1,16 @@ import { Expose } from 'class-transformer' import { IsOptional, IsString } from 'class-validator' -import { DidCommV1Message } from '../../../../../../didcomm' -import { IsValidMessageType, parseMessageType } from '../../../../../../utils/messageType' +import { DidCommV1Message } from '../../../../../didcomm' +import { IsValidMessageType, parseMessageType } from '../../../../../utils/messageType' -export interface StatusRequestMessageOptions { +export interface V2StatusRequestMessageOptions { id?: string recipientKey?: string } -export class StatusRequestMessage extends DidCommV1Message { - public constructor(options: StatusRequestMessageOptions) { +export class V2StatusRequestMessage extends DidCommV1Message { + public constructor(options: V2StatusRequestMessageOptions) { super() if (options) { @@ -19,8 +19,8 @@ export class StatusRequestMessage extends DidCommV1Message { } } - @IsValidMessageType(StatusRequestMessage.type) - public readonly type = StatusRequestMessage.type.messageTypeUri + @IsValidMessageType(V2StatusRequestMessage.type) + public readonly type = V2StatusRequestMessage.type.messageTypeUri public static readonly type = parseMessageType('https://didcomm.org/messagepickup/2.0/status-request') @IsString() diff --git "a/packages/core/src/modules/message-p\303\254ckup/protocol/v2/messages/index.ts" "b/packages/core/src/modules/message-p\303\254ckup/protocol/v2/messages/index.ts" new file mode 100644 index 0000000000..4746216ec0 --- /dev/null +++ "b/packages/core/src/modules/message-p\303\254ckup/protocol/v2/messages/index.ts" @@ -0,0 +1,5 @@ +export * from './V2DeliveryRequestMessage' +export * from './V2MessageDeliveryMessage' +export * from './V2MessagesReceivedMessage' +export * from './V2StatusMessage' +export * from './V2StatusRequestMessage' diff --git a/packages/core/src/modules/oob/OutOfBandApi.ts b/packages/core/src/modules/oob/OutOfBandApi.ts index 2c87636ce5..dd988ee4ad 100644 --- a/packages/core/src/modules/oob/OutOfBandApi.ts +++ b/packages/core/src/modules/oob/OutOfBandApi.ts @@ -1,9 +1,9 @@ +import type { HandshakeReusedEvent } from './domain/OutOfBandEvents' import type { AgentMessageReceivedEvent } from '../../agent/Events' import type { V1Attachment } from '../../decorators/attachment/V1Attachment' import type { DidCommV1Message, PlaintextMessage } from '../../didcomm' import type { Query } from '../../storage/StorageService' import type { ConnectionInvitationMessage, ConnectionRecord, Routing } from '../connections' -import type { HandshakeReusedEvent } from './domain/OutOfBandEvents' import { catchError, EmptyError, first, firstValueFrom, map, of, timeout } from 'rxjs' @@ -23,7 +23,7 @@ import { inject, injectable } from '../../plugins' import { DidCommMessageRepository, DidCommMessageRole } from '../../storage' import { JsonEncoder, JsonTransformer } from '../../utils' import { parseMessageType, supportsIncomingMessageType } from '../../utils/messageType' -import { parseInvitationUrl, parseInvitationShortUrl } from '../../utils/parseInvitation' +import { parseInvitationShortUrl } from '../../utils/parseInvitation' import { ConnectionsApi, DidExchangeState, HandshakeProtocol } from '../connections' import { DidCommDocumentService } from '../didcomm' import { DidKey } from '../dids' @@ -70,7 +70,7 @@ export interface CreateLegacyInvitationConfig { routing?: Routing } -export interface ReceiveOutOfBandInvitationConfig { +interface BaseReceiveOutOfBandInvitationConfig { label?: string alias?: string imageUrl?: string @@ -79,6 +79,15 @@ export interface ReceiveOutOfBandInvitationConfig { reuseConnection?: boolean routing?: Routing acceptInvitationTimeoutMs?: number + isImplicit?: boolean +} + +export type ReceiveOutOfBandInvitationConfig = Omit + +export interface ReceiveOutOfBandImplicitInvitationConfig + extends Omit { + did: string + handshakeProtocols?: HandshakeProtocol[] } @injectable() @@ -266,9 +275,10 @@ export class OutOfBandApi { recordId: string message: Message domain: string + routing?: Routing }): Promise<{ message: Message; invitationUrl: string }> { // Create keys (and optionally register them at the mediator) - const routing = await this.routingService.getRouting(this.agentContext) + const routing = config.routing ?? (await this.routingService.getRouting(this.agentContext)) // Set the service on the message config.message.service = new ServiceDecorator({ @@ -338,13 +348,52 @@ export class OutOfBandApi { invitation: OutOfBandInvitation | ConnectionInvitationMessage | V2OutOfBandInvitation, config: ReceiveOutOfBandInvitationConfig = {} ): Promise<{ outOfBandRecord?: OutOfBandRecord; connectionRecord?: ConnectionRecord }> { + return this._receiveInvitation(invitation, config) + } + + /** + * Creates inbound out-of-band record from an implicit invitation, given as a public DID the agent + * should be capable of resolving. It automatically passes out-of-band invitation for further + * processing to `acceptInvitation` method. If you don't want to do that you can set + * `autoAcceptInvitation` attribute in `config` parameter to `false` and accept the message later by + * calling `acceptInvitation`. + * + * It supports both OOB (Aries RFC 0434: Out-of-Band Protocol 1.1) and Connection Invitation + * (0160: Connection Protocol). Handshake protocol to be used depends on handshakeProtocols + * (DID Exchange by default) + * + * Agent role: receiver (invitee) + * + * @param config config for creating and handling invitation + * + * @returns out-of-band record and connection record if one has been created. + */ + public async receiveImplicitInvitation(config: ReceiveOutOfBandImplicitInvitationConfig) { + const invitation = new OutOfBandInvitation({ + id: config.did, + label: config.label ?? '', + services: [config.did], + handshakeProtocols: config.handshakeProtocols ?? [HandshakeProtocol.DidExchange], + }) + + return this._receiveInvitation(invitation, { ...config, isImplicit: true }) + } + + /** + * Internal receive invitation method, for both explicit and implicit OOB invitations + */ + private async _receiveInvitation( + invitation: OutOfBandInvitation | ConnectionInvitationMessage | V2OutOfBandInvitation, + config: BaseReceiveOutOfBandInvitationConfig = {} + ): Promise<{ outOfBandRecord?: OutOfBandRecord; connectionRecord?: ConnectionRecord }> { + const { routing } = config + const autoAcceptInvitation = config.autoAcceptInvitation ?? true const autoAcceptConnection = config.autoAcceptConnection ?? true const reuseConnection = config.reuseConnection ?? false const label = config.label ?? this.agentContext.config.label const alias = config.alias const imageUrl = config.imageUrl ?? this.agentContext.config.connectionImageUrl - const { routing } = config let outOfBandRecord: OutOfBandRecord | null @@ -372,15 +421,19 @@ export class OutOfBandApi { ) } - // Make sure we haven't received this invitation before. (it's fine if we created it, that means we're connecting with ourselves - ;[outOfBandRecord] = await this.outOfBandService.findAllByQuery(this.agentContext, { - invitationId: outOfBandInvitation.id, - role: OutOfBandRole.Receiver, - }) - if (outOfBandRecord) { - throw new AriesFrameworkError( - `An out of band record with invitation ${outOfBandInvitation.id} has already been received. Invitations should have a unique id.` - ) + // Make sure we haven't received this invitation before + // It's fine if we created it (means that we are connnecting to ourselves) or if it's an implicit + // invitation (it allows to connect multiple times to the same public did) + if (!config.isImplicit) { + const existingOobRecordsFromThisId = await this.outOfBandService.findAllByQuery(this.agentContext, { + invitationId: outOfBandInvitation.id, + role: OutOfBandRole.Receiver, + }) + if (existingOobRecordsFromThisId.length > 0) { + throw new AriesFrameworkError( + `An out of band record with invitation ${outOfBandInvitation.id} has already been received. Invitations should have a unique id.` + ) + } } const recipientKeyFingerprints: string[] = [] diff --git a/packages/core/src/modules/oob/__tests__/OutOfBandService.test.ts b/packages/core/src/modules/oob/__tests__/OutOfBandService.test.ts index 842df68d93..1710421615 100644 --- a/packages/core/src/modules/oob/__tests__/OutOfBandService.test.ts +++ b/packages/core/src/modules/oob/__tests__/OutOfBandService.test.ts @@ -1,11 +1,7 @@ -import type { AgentContext } from '../../../agent' -import type { Wallet } from '../../../wallet/Wallet' - import { Subject } from 'rxjs' import { agentDependencies, - getAgentConfig, getAgentContext, getMockConnection, getMockOutOfBand, @@ -14,9 +10,7 @@ import { import { EventEmitter } from '../../../agent/EventEmitter' import { InboundMessageContext } from '../../../agent/models/InboundMessageContext' import { KeyType, Key } from '../../../crypto' -import { KeyProviderRegistry } from '../../../crypto/key-provider' import { AriesFrameworkError } from '../../../error' -import { IndyWallet } from '../../../wallet/IndyWallet' import { DidExchangeState } from '../../connections/models' import { OutOfBandEventTypes } from '../domain/OutOfBandEvents' import { OutOfBandRole } from '../domain/OutOfBandRole' @@ -31,24 +25,12 @@ const OutOfBandRepositoryMock = OutOfBandRepository as jest.Mock { - const agentConfig = getAgentConfig('OutOfBandServiceTest') - let wallet: Wallet let outOfBandRepository: OutOfBandRepository let outOfBandService: OutOfBandService let eventEmitter: EventEmitter - let agentContext: AgentContext - - beforeAll(async () => { - wallet = new IndyWallet(agentConfig.agentDependencies, agentConfig.logger, new KeyProviderRegistry([])) - agentContext = getAgentContext() - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - await wallet.createAndOpen(agentConfig.walletConfig!) - }) - - afterAll(async () => { - await wallet.delete() - }) beforeEach(async () => { eventEmitter = new EventEmitter(agentDependencies, new Subject()) diff --git a/packages/core/src/modules/oob/__tests__/connect-to-self.e2e.test.ts b/packages/core/src/modules/oob/__tests__/connect-to-self.e2e.test.ts index d99e4e9df8..30f2a2649f 100644 --- a/packages/core/src/modules/oob/__tests__/connect-to-self.e2e.test.ts +++ b/packages/core/src/modules/oob/__tests__/connect-to-self.e2e.test.ts @@ -5,15 +5,20 @@ import { Subject } from 'rxjs' import { SubjectInboundTransport } from '../../../../../../tests/transport/SubjectInboundTransport' import { SubjectOutboundTransport } from '../../../../../../tests/transport/SubjectOutboundTransport' +import { getIndySdkModules } from '../../../../../indy-sdk/tests/setupIndySdkModule' import { getAgentOptions } from '../../../../tests/helpers' import { HandshakeProtocol, DidExchangeState } from '../../connections' import { OutOfBandState } from '../domain/OutOfBandState' import { Agent } from '@aries-framework/core' -const faberAgentOptions = getAgentOptions('Faber Agent OOB Connect to Self', { - endpoints: ['rxjs:faber'], -}) +const faberAgentOptions = getAgentOptions( + 'Faber Agent OOB Connect to Self', + { + endpoints: ['rxjs:faber'], + }, + getIndySdkModules() +) describe('out of band', () => { let faberAgent: Agent diff --git a/packages/core/src/modules/oob/__tests__/implicit.e2e.test.ts b/packages/core/src/modules/oob/__tests__/implicit.e2e.test.ts new file mode 100644 index 0000000000..626eb9573d --- /dev/null +++ b/packages/core/src/modules/oob/__tests__/implicit.e2e.test.ts @@ -0,0 +1,216 @@ +/* eslint-disable @typescript-eslint/no-non-null-assertion */ +import type { IndySdkIndyDidCreateOptions } from '@aries-framework/indy-sdk' + +import { getLegacyAnonCredsModules } from '../../../../../anoncreds/tests/legacyAnonCredsSetup' +import { setupSubjectTransports } from '../../../../tests' +import { + getAgentOptions, + importExistingIndyDidFromPrivateKey, + publicDidSeed, + waitForConnectionRecord, +} from '../../../../tests/helpers' +import { Agent } from '../../../agent/Agent' +import { TypedArrayEncoder } from '../../../utils' +import { sleep } from '../../../utils/sleep' +import { DidExchangeState, HandshakeProtocol } from '../../connections' + +const faberAgentOptions = getAgentOptions( + 'Faber Agent OOB Implicit', + { + endpoints: ['rxjs:faber'], + }, + getLegacyAnonCredsModules() +) +const aliceAgentOptions = getAgentOptions( + 'Alice Agent OOB Implicit', + { + endpoints: ['rxjs:alice'], + }, + getLegacyAnonCredsModules() +) + +describe('out of band implicit', () => { + let faberAgent: Agent + let aliceAgent: Agent + let unqualifiedSubmitterDid: string + + beforeAll(async () => { + faberAgent = new Agent(faberAgentOptions) + aliceAgent = new Agent(aliceAgentOptions) + + setupSubjectTransports([faberAgent, aliceAgent]) + await faberAgent.initialize() + await aliceAgent.initialize() + + unqualifiedSubmitterDid = await importExistingIndyDidFromPrivateKey( + faberAgent, + TypedArrayEncoder.fromString(publicDidSeed) + ) + }) + + afterAll(async () => { + await faberAgent.shutdown() + await faberAgent.wallet.delete() + await aliceAgent.shutdown() + await aliceAgent.wallet.delete() + }) + + afterEach(async () => { + const connections = await faberAgent.connections.getAll() + for (const connection of connections) { + await faberAgent.connections.deleteById(connection.id) + } + + jest.resetAllMocks() + }) + + test(`make a connection with ${HandshakeProtocol.DidExchange} based on implicit OOB invitation`, async () => { + const publicDid = await createPublicDid(faberAgent, unqualifiedSubmitterDid, 'rxjs:faber') + expect(publicDid).toBeDefined() + + let { connectionRecord: aliceFaberConnection } = await aliceAgent.oob.receiveImplicitInvitation({ + did: publicDid!, + alias: 'Faber public', + label: 'Alice', + handshakeProtocols: [HandshakeProtocol.DidExchange], + }) + + // Wait for a connection event in faber agent and accept the request + let faberAliceConnection = await waitForConnectionRecord(faberAgent, { state: DidExchangeState.RequestReceived }) + await faberAgent.connections.acceptRequest(faberAliceConnection.id) + faberAliceConnection = await faberAgent.connections.returnWhenIsConnected(faberAliceConnection!.id) + expect(faberAliceConnection.state).toBe(DidExchangeState.Completed) + + // Alice should now be connected + aliceFaberConnection = await aliceAgent.connections.returnWhenIsConnected(aliceFaberConnection!.id) + expect(aliceFaberConnection.state).toBe(DidExchangeState.Completed) + + expect(aliceFaberConnection).toBeConnectedWith(faberAliceConnection) + expect(faberAliceConnection).toBeConnectedWith(aliceFaberConnection) + expect(faberAliceConnection.theirLabel).toBe('Alice') + expect(aliceFaberConnection.alias).toBe('Faber public') + expect(aliceFaberConnection.invitationDid).toBe(publicDid) + + // It is possible for an agent to check if it has already a connection to a certain public entity + expect(await aliceAgent.connections.findByInvitationDid(publicDid!)).toEqual([aliceFaberConnection]) + }) + + test(`make a connection with ${HandshakeProtocol.Connections} based on implicit OOB invitation`, async () => { + const publicDid = await createPublicDid(faberAgent, unqualifiedSubmitterDid, 'rxjs:faber') + expect(publicDid).toBeDefined() + + let { connectionRecord: aliceFaberConnection } = await aliceAgent.oob.receiveImplicitInvitation({ + did: publicDid!, + alias: 'Faber public', + label: 'Alice', + handshakeProtocols: [HandshakeProtocol.Connections], + }) + + // Wait for a connection event in faber agent and accept the request + let faberAliceConnection = await waitForConnectionRecord(faberAgent, { state: DidExchangeState.RequestReceived }) + await faberAgent.connections.acceptRequest(faberAliceConnection.id) + faberAliceConnection = await faberAgent.connections.returnWhenIsConnected(faberAliceConnection!.id) + expect(faberAliceConnection.state).toBe(DidExchangeState.Completed) + + // Alice should now be connected + aliceFaberConnection = await aliceAgent.connections.returnWhenIsConnected(aliceFaberConnection!.id) + expect(aliceFaberConnection.state).toBe(DidExchangeState.Completed) + + expect(aliceFaberConnection).toBeConnectedWith(faberAliceConnection) + expect(faberAliceConnection).toBeConnectedWith(aliceFaberConnection) + expect(faberAliceConnection.theirLabel).toBe('Alice') + expect(aliceFaberConnection.alias).toBe('Faber public') + expect(aliceFaberConnection.invitationDid).toBe(publicDid) + + // It is possible for an agent to check if it has already a connection to a certain public entity + expect(await aliceAgent.connections.findByInvitationDid(publicDid!)).toEqual([aliceFaberConnection]) + }) + + test(`receive an implicit invitation using an unresolvable did`, async () => { + await expect( + aliceAgent.oob.receiveImplicitInvitation({ + did: 'did:sov:ZSEqSci581BDZCFPa29ScB', + alias: 'Faber public', + label: 'Alice', + handshakeProtocols: [HandshakeProtocol.DidExchange], + }) + ).rejects.toThrowError(/Unable to resolve did/) + }) + + test(`create two connections using the same implicit invitation`, async () => { + const publicDid = await createPublicDid(faberAgent, unqualifiedSubmitterDid, 'rxjs:faber') + expect(publicDid).toBeDefined() + + let { connectionRecord: aliceFaberConnection } = await aliceAgent.oob.receiveImplicitInvitation({ + did: publicDid!, + alias: 'Faber public', + label: 'Alice', + handshakeProtocols: [HandshakeProtocol.Connections], + }) + + // Wait for a connection event in faber agent and accept the request + let faberAliceConnection = await waitForConnectionRecord(faberAgent, { state: DidExchangeState.RequestReceived }) + await faberAgent.connections.acceptRequest(faberAliceConnection.id) + faberAliceConnection = await faberAgent.connections.returnWhenIsConnected(faberAliceConnection!.id) + expect(faberAliceConnection.state).toBe(DidExchangeState.Completed) + + // Alice should now be connected + aliceFaberConnection = await aliceAgent.connections.returnWhenIsConnected(aliceFaberConnection!.id) + expect(aliceFaberConnection.state).toBe(DidExchangeState.Completed) + + expect(aliceFaberConnection).toBeConnectedWith(faberAliceConnection) + expect(faberAliceConnection).toBeConnectedWith(aliceFaberConnection) + expect(faberAliceConnection.theirLabel).toBe('Alice') + expect(aliceFaberConnection.alias).toBe('Faber public') + expect(aliceFaberConnection.invitationDid).toBe(publicDid) + + // Repeat implicit invitation procedure + let { connectionRecord: aliceFaberNewConnection } = await aliceAgent.oob.receiveImplicitInvitation({ + did: publicDid!, + alias: 'Faber public New', + label: 'Alice New', + handshakeProtocols: [HandshakeProtocol.Connections], + }) + + // Wait for a connection event in faber agent + let faberAliceNewConnection = await waitForConnectionRecord(faberAgent, { state: DidExchangeState.RequestReceived }) + await faberAgent.connections.acceptRequest(faberAliceNewConnection.id) + faberAliceNewConnection = await faberAgent.connections.returnWhenIsConnected(faberAliceNewConnection!.id) + expect(faberAliceNewConnection.state).toBe(DidExchangeState.Completed) + + // Alice should now be connected + aliceFaberNewConnection = await aliceAgent.connections.returnWhenIsConnected(aliceFaberNewConnection!.id) + expect(aliceFaberNewConnection.state).toBe(DidExchangeState.Completed) + + expect(aliceFaberNewConnection).toBeConnectedWith(faberAliceNewConnection) + expect(faberAliceNewConnection).toBeConnectedWith(aliceFaberNewConnection) + expect(faberAliceNewConnection.theirLabel).toBe('Alice New') + expect(aliceFaberNewConnection.alias).toBe('Faber public New') + expect(aliceFaberNewConnection.invitationDid).toBe(publicDid) + + // Both connections will be associated to the same invitation did + const connectionsFromFaberPublicDid = await aliceAgent.connections.findByInvitationDid(publicDid!) + expect(connectionsFromFaberPublicDid).toHaveLength(2) + expect(connectionsFromFaberPublicDid).toEqual( + expect.arrayContaining([aliceFaberConnection, aliceFaberNewConnection]) + ) + }) +}) + +async function createPublicDid(agent: Agent, unqualifiedSubmitterDid: string, endpoint: string) { + const createResult = await agent.dids.create({ + method: 'indy', + options: { + submitterDid: `did:indy:pool:localtest:${unqualifiedSubmitterDid}`, + alias: 'Alias', + endpoints: { + endpoint, + types: ['DIDCommMessaging', 'did-communication', 'endpoint'], + }, + }, + }) + + await sleep(1000) + + return createResult.didState.did +} diff --git a/packages/core/src/modules/oob/domain/OutOfBandEvents.ts b/packages/core/src/modules/oob/domain/OutOfBandEvents.ts index a3936cc784..15561062b5 100644 --- a/packages/core/src/modules/oob/domain/OutOfBandEvents.ts +++ b/packages/core/src/modules/oob/domain/OutOfBandEvents.ts @@ -1,7 +1,7 @@ +import type { OutOfBandState } from './OutOfBandState' import type { BaseEvent } from '../../../agent/Events' import type { ConnectionRecord } from '../../connections' import type { OutOfBandRecord } from '../repository' -import type { OutOfBandState } from './OutOfBandState' export enum OutOfBandEventTypes { OutOfBandStateChanged = 'OutOfBandStateChanged', diff --git a/packages/core/src/modules/oob/protocols/v1/OutOfBandService.ts b/packages/core/src/modules/oob/protocols/v1/OutOfBandService.ts index 362df38574..d781d7e253 100644 --- a/packages/core/src/modules/oob/protocols/v1/OutOfBandService.ts +++ b/packages/core/src/modules/oob/protocols/v1/OutOfBandService.ts @@ -3,21 +3,31 @@ import type { InboundMessageContext } from '../../../../agent/models/InboundMess import type { Key } from '../../../../crypto' import type { Query } from '../../../../storage/StorageService' import type { ConnectionRecord } from '../../../connections' +import type { HandshakeProtocol } from '../../../connections/models' import type { HandshakeReusedEvent, OutOfBandStateChangedEvent } from '../../domain/OutOfBandEvents' -import type { OutOfBandRecord } from '../../repository' import { EventEmitter } from '../../../../agent/EventEmitter' import { AriesFrameworkError } from '../../../../error' import { injectable } from '../../../../plugins' import { JsonTransformer } from '../../../../utils' +import { DidsApi } from '../../../dids' +import { parseDid } from '../../../dids/domain/parse' import { OutOfBandEventTypes } from '../../domain/OutOfBandEvents' import { OutOfBandRole } from '../../domain/OutOfBandRole' import { OutOfBandState } from '../../domain/OutOfBandState' -import { OutOfBandRepository } from '../../repository' +import { OutOfBandRecord, OutOfBandRepository } from '../../repository' -import { HandshakeReuseMessage } from './messages' +import { HandshakeReuseMessage, OutOfBandInvitation } from './messages' import { HandshakeReuseAcceptedMessage } from './messages/HandshakeReuseAcceptedMessage' +export interface CreateFromImplicitInvitationConfig { + did: string + threadId: string + handshakeProtocols: HandshakeProtocol[] + autoAcceptConnection?: boolean + recipientKey: Key +} + @injectable() export class OutOfBandService { private outOfBandRepository: OutOfBandRepository @@ -28,6 +38,51 @@ export class OutOfBandService { this.eventEmitter = eventEmitter } + /** + * Creates an Out of Band record from a Connection/DIDExchange request started by using + * a publicly resolvable DID this agent can control + */ + public async createFromImplicitInvitation( + agentContext: AgentContext, + config: CreateFromImplicitInvitationConfig + ): Promise { + const { did, threadId, handshakeProtocols, autoAcceptConnection, recipientKey } = config + + // Verify it is a valid did and it is present in the wallet + const publicDid = parseDid(did) + const didsApi = agentContext.dependencyManager.resolve(DidsApi) + const [createdDid] = await didsApi.getCreatedDids({ did: publicDid.did }) + if (!createdDid) { + throw new AriesFrameworkError(`Referenced public did ${did} not found.`) + } + + // Recreate an 'implicit invitation' matching the parameters used by the invitee when + // initiating the flow + const outOfBandInvitation = new OutOfBandInvitation({ + id: did, + label: '', + services: [did], + handshakeProtocols, + }) + + outOfBandInvitation.setThread({ threadId }) + + const outOfBandRecord = new OutOfBandRecord({ + role: OutOfBandRole.Sender, + state: OutOfBandState.AwaitResponse, + reusable: true, + autoAcceptConnection: autoAcceptConnection ?? false, + outOfBandInvitation, + tags: { + recipientKeyFingerprints: [recipientKey.fingerprint], + }, + }) + + await this.save(agentContext, outOfBandRecord) + this.emitStateChangedEvent(agentContext, outOfBandRecord, null) + return outOfBandRecord + } + public async processHandshakeReuse(messageContext: InboundMessageContext) { const reuseMessage = messageContext.message const parentThreadId = reuseMessage.thread?.parentThreadId @@ -172,10 +227,11 @@ export class OutOfBandService { }) } - public async findByCreatedInvitationId(agentContext: AgentContext, createdInvitationId: string) { + public async findByCreatedInvitationId(agentContext: AgentContext, createdInvitationId: string, threadId?: string) { return this.outOfBandRepository.findSingleByQuery(agentContext, { invitationId: createdInvitationId, role: OutOfBandRole.Sender, + threadId, }) } diff --git a/packages/core/src/modules/oob/protocols/v1/messages/HandshakeReuseMessage.ts b/packages/core/src/modules/oob/protocols/v1/messages/HandshakeReuseMessage.ts index 5ec3edaeee..505494c168 100644 --- a/packages/core/src/modules/oob/protocols/v1/messages/HandshakeReuseMessage.ts +++ b/packages/core/src/modules/oob/protocols/v1/messages/HandshakeReuseMessage.ts @@ -13,7 +13,6 @@ export class HandshakeReuseMessage extends DidCommV1Message { if (options) { this.id = options.id ?? this.generateId() this.setThread({ - threadId: this.id, parentThreadId: options.parentThreadId, }) } diff --git a/packages/core/src/modules/oob/protocols/v2/V2OutOfBandService.ts b/packages/core/src/modules/oob/protocols/v2/V2OutOfBandService.ts index 351a417f1b..eaf28fb727 100644 --- a/packages/core/src/modules/oob/protocols/v2/V2OutOfBandService.ts +++ b/packages/core/src/modules/oob/protocols/v2/V2OutOfBandService.ts @@ -87,8 +87,8 @@ export class V2OutOfBandService { private async createDid(agentContext: AgentContext, params: CreateDidParams = {}): Promise { // Create keys - const authentication = await agentContext.wallet.createKey({ seed: params.seed, keyType: KeyType.Ed25519 }) - const keyAgreement = await agentContext.wallet.createKey({ seed: params.seed, keyType: KeyType.X25519 }) + const authentication = await agentContext.wallet.createKey({ keyType: KeyType.Ed25519 }) + const keyAgreement = await agentContext.wallet.createKey({ keyType: KeyType.X25519 }) // Build services const services = this.prepareServices(params.routing) diff --git a/packages/core/src/modules/oob/repository/OutOfBandRecord.ts b/packages/core/src/modules/oob/repository/OutOfBandRecord.ts index fc30f0db50..8fe666ec56 100644 --- a/packages/core/src/modules/oob/repository/OutOfBandRecord.ts +++ b/packages/core/src/modules/oob/repository/OutOfBandRecord.ts @@ -15,6 +15,7 @@ type DefaultOutOfBandRecordTags = { role: OutOfBandRole state: OutOfBandState invitationId?: string + threadId?: string } interface CustomOutOfBandRecordTags extends TagsBase { @@ -35,6 +36,7 @@ export interface OutOfBandRecordProps { reusable?: boolean mediatorId?: string reuseConnectionId?: string + threadId?: string } export class OutOfBandRecord extends BaseRecord { @@ -81,6 +83,7 @@ export class OutOfBandRecord extends BaseRecord { state: OutOfBandState.Done, role: OutOfBandRole.Receiver, invitationId: 'a-message-id', + threadId: 'a-message-id', recipientKeyFingerprints: ['z6MkmjY8GnV5i9YTDtPETC2uUAW6ejw3nk5mXF5yci5ab7th'], }) }) diff --git a/packages/core/src/modules/proofs/ProofEvents.ts b/packages/core/src/modules/proofs/ProofEvents.ts index 20394c56a9..86b0e7673c 100644 --- a/packages/core/src/modules/proofs/ProofEvents.ts +++ b/packages/core/src/modules/proofs/ProofEvents.ts @@ -1,6 +1,6 @@ -import type { BaseEvent } from '../../agent/Events' import type { ProofState } from './models/ProofState' import type { ProofExchangeRecord } from './repository' +import type { BaseEvent } from '../../agent/Events' export enum ProofEventTypes { ProofStateChanged = 'ProofStateChanged', diff --git a/packages/core/src/modules/proofs/ProofResponseCoordinator.ts b/packages/core/src/modules/proofs/ProofResponseCoordinator.ts deleted file mode 100644 index 77c9a04fd5..0000000000 --- a/packages/core/src/modules/proofs/ProofResponseCoordinator.ts +++ /dev/null @@ -1,93 +0,0 @@ -import type { AgentContext } from '../../agent/context/AgentContext' -import type { ProofExchangeRecord } from './repository' - -import { injectable } from '../../plugins' - -import { ProofService } from './ProofService' -import { AutoAcceptProof } from './models/ProofAutoAcceptType' - -/** - * This class handles all the automation with all the messages in the present proof protocol - * Every function returns `true` if it should automate the flow and `false` if not - */ -@injectable() -export class ProofResponseCoordinator { - private proofService: ProofService - - public constructor(proofService: ProofService) { - this.proofService = proofService - } - - /** - * Returns the proof auto accept config based on priority: - * - The record config takes first priority - * - Otherwise the agent config - * - Otherwise {@link AutoAcceptProof.Never} is returned - */ - private static composeAutoAccept( - recordConfig: AutoAcceptProof | undefined, - agentConfig: AutoAcceptProof | undefined - ) { - return recordConfig ?? agentConfig ?? AutoAcceptProof.Never - } - - /** - * Checks whether it should automatically respond to a proposal - */ - public async shouldAutoRespondToProposal(agentContext: AgentContext, proofRecord: ProofExchangeRecord) { - const autoAccept = ProofResponseCoordinator.composeAutoAccept( - proofRecord.autoAcceptProof, - agentContext.config.autoAcceptProofs - ) - - if (autoAccept === AutoAcceptProof.Always) { - return true - } - - if (autoAccept === AutoAcceptProof.ContentApproved) { - return this.proofService.shouldAutoRespondToProposal(agentContext, proofRecord) - } - - return false - } - - /** - * Checks whether it should automatically respond to a request - */ - public async shouldAutoRespondToRequest(agentContext: AgentContext, proofRecord: ProofExchangeRecord) { - const autoAccept = ProofResponseCoordinator.composeAutoAccept( - proofRecord.autoAcceptProof, - agentContext.config.autoAcceptProofs - ) - - if (autoAccept === AutoAcceptProof.Always) { - return true - } - - if (autoAccept === AutoAcceptProof.ContentApproved) { - return this.proofService.shouldAutoRespondToRequest(agentContext, proofRecord) - } - - return false - } - - /** - * Checks whether it should automatically respond to a presentation of proof - */ - public async shouldAutoRespondToPresentation(agentContext: AgentContext, proofRecord: ProofExchangeRecord) { - const autoAccept = ProofResponseCoordinator.composeAutoAccept( - proofRecord.autoAcceptProof, - agentContext.config.autoAcceptProofs - ) - - if (autoAccept === AutoAcceptProof.Always) { - return true - } - - if (autoAccept === AutoAcceptProof.ContentApproved) { - return this.proofService.shouldAutoRespondToPresentation(agentContext, proofRecord) - } - - return false - } -} diff --git a/packages/core/src/modules/proofs/ProofService.ts b/packages/core/src/modules/proofs/ProofService.ts deleted file mode 100644 index 257e031112..0000000000 --- a/packages/core/src/modules/proofs/ProofService.ts +++ /dev/null @@ -1,265 +0,0 @@ -import type { AgentConfig } from '../../agent/AgentConfig' -import type { Dispatcher } from '../../agent/Dispatcher' -import type { EventEmitter } from '../../agent/EventEmitter' -import type { AgentContext } from '../../agent/context/AgentContext' -import type { InboundMessageContext } from '../../agent/models/InboundMessageContext' -import type { DidCommV1Message } from '../../didcomm' -import type { Logger } from '../../logger' -import type { DidCommMessageRepository, DidCommMessageRole } from '../../storage' -import type { Wallet } from '../../wallet/Wallet' -import type { ConnectionService } from '../connections/services' -import type { MediationRecipientService, RoutingService } from '../routing' -import type { ProofStateChangedEvent } from './ProofEvents' -import type { ProofResponseCoordinator } from './ProofResponseCoordinator' -import type { ProofFormat } from './formats/ProofFormat' -import type { CreateProblemReportOptions } from './formats/models/ProofFormatServiceOptions' -import type { - CreateAckOptions, - CreatePresentationOptions, - CreateProofRequestFromProposalOptions, - CreateProposalAsResponseOptions, - CreateProposalOptions, - CreateRequestAsResponseOptions, - CreateRequestOptions, - DeleteProofOptions, - FormatRequestedCredentialReturn, - FormatRetrievedCredentialOptions, - GetFormatDataReturn, - GetRequestedCredentialsForProofRequestOptions, - ProofRequestFromProposalOptions, -} from './models/ProofServiceOptions' -import type { ProofState } from './models/ProofState' -import type { ProofExchangeRecord, ProofRepository } from './repository' - -import { JsonTransformer } from '../../utils/JsonTransformer' - -import { ProofEventTypes } from './ProofEvents' - -export abstract class ProofService { - protected proofRepository: ProofRepository - protected didCommMessageRepository: DidCommMessageRepository - protected eventEmitter: EventEmitter - protected connectionService: ConnectionService - protected wallet: Wallet - protected logger: Logger - - public constructor( - agentConfig: AgentConfig, - proofRepository: ProofRepository, - connectionService: ConnectionService, - didCommMessageRepository: DidCommMessageRepository, - wallet: Wallet, - eventEmitter: EventEmitter - ) { - this.proofRepository = proofRepository - this.connectionService = connectionService - this.didCommMessageRepository = didCommMessageRepository - this.eventEmitter = eventEmitter - this.wallet = wallet - this.logger = agentConfig.logger - } - abstract readonly version: string - - public emitStateChangedEvent( - agentContext: AgentContext, - proofRecord: ProofExchangeRecord, - previousState: ProofState | null - ) { - const clonedProof = JsonTransformer.clone(proofRecord) - - this.eventEmitter.emit(agentContext, { - type: ProofEventTypes.ProofStateChanged, - payload: { - proofRecord: clonedProof, - previousState: previousState, - }, - }) - } - - /** - * Update the record to a new state and emit an state changed event. Also updates the record - * in storage. - * - * @param proofRecord The proof record to update the state for - * @param newState The state to update to - * - */ - public async updateState(agentContext: AgentContext, proofRecord: ProofExchangeRecord, newState: ProofState) { - const previousState = proofRecord.state - proofRecord.state = newState - await this.proofRepository.update(agentContext, proofRecord) - - this.emitStateChangedEvent(agentContext, proofRecord, previousState) - } - - public update(agentContext: AgentContext, proofRecord: ProofExchangeRecord) { - return this.proofRepository.update(agentContext, proofRecord) - } - - /** - * 1. Assert (connection ready, record state) - * 2. Create proposal message - * 3. loop through all formats from ProposeProofOptions and call format service - * 4. Create and store proof record - * 5. Store proposal message - * 6. Return proposal message + proof record - */ - abstract createProposal( - agentContext: AgentContext, - options: CreateProposalOptions - ): Promise<{ proofRecord: ProofExchangeRecord; message: DidCommV1Message }> - - /** - * Create a proposal message in response to a received proof request message - * - * 1. assert record state - * 2. Create proposal message - * 3. loop through all formats from ProposeProofOptions and call format service - * 4. Update proof record - * 5. Create or update proposal message - * 6. Return proposal message + proof record - */ - abstract createProposalAsResponse( - agentContext: AgentContext, - options: CreateProposalAsResponseOptions - ): Promise<{ proofRecord: ProofExchangeRecord; message: DidCommV1Message }> - - /** - * Process a received proposal message (does not accept yet) - * - * 1. Find proof record by thread and connection id - * - * Two flows possible: - * - Proof record already exist - * 2. Assert state - * 3. Save or update proposal message in storage (didcomm message record) - * 4. Loop through all format services to process proposal message - * 5. Update & return record - * - * - Proof record does not exist yet - * 2. Create record - * 3. Save proposal message - * 4. Loop through all format services to process proposal message - * 5. Save & return record - */ - abstract processProposal(messageContext: InboundMessageContext): Promise - - abstract createRequest( - agentContext: AgentContext, - options: CreateRequestOptions - ): Promise<{ proofRecord: ProofExchangeRecord; message: DidCommV1Message }> - - abstract createRequestAsResponse( - agentContext: AgentContext, - options: CreateRequestAsResponseOptions - ): Promise<{ proofRecord: ProofExchangeRecord; message: DidCommV1Message }> - - abstract processRequest(messageContext: InboundMessageContext): Promise - - abstract createPresentation( - agentContext: AgentContext, - options: CreatePresentationOptions - ): Promise<{ proofRecord: ProofExchangeRecord; message: DidCommV1Message }> - - abstract processPresentation(messageContext: InboundMessageContext): Promise - - abstract createAck( - agentContext: AgentContext, - options: CreateAckOptions - ): Promise<{ proofRecord: ProofExchangeRecord; message: DidCommV1Message }> - - abstract processAck(messageContext: InboundMessageContext): Promise - - abstract createProblemReport( - agentContext: AgentContext, - options: CreateProblemReportOptions - ): Promise<{ proofRecord: ProofExchangeRecord; message: DidCommV1Message }> - - abstract processProblemReport(messageContext: InboundMessageContext): Promise - - public abstract shouldAutoRespondToProposal( - agentContext: AgentContext, - proofRecord: ProofExchangeRecord - ): Promise - - public abstract shouldAutoRespondToRequest( - agentContext: AgentContext, - proofRecord: ProofExchangeRecord - ): Promise - - public abstract shouldAutoRespondToPresentation( - agentContext: AgentContext, - proofRecord: ProofExchangeRecord - ): Promise - - public abstract registerMessageHandlers( - dispatcher: Dispatcher, - agentConfig: AgentConfig, - proofResponseCoordinator: ProofResponseCoordinator, - mediationRecipientService: MediationRecipientService, - routingService: RoutingService - ): void - - public abstract findProposalMessage( - agentContext: AgentContext, - proofRecordId: string - ): Promise - public abstract findRequestMessage( - agentContext: AgentContext, - proofRecordId: string - ): Promise - public abstract findPresentationMessage( - agentContext: AgentContext, - proofRecordId: string - ): Promise - - public async saveOrUpdatePresentationMessage( - agentContext: AgentContext, - options: { - proofRecord: ProofExchangeRecord - message: DidCommV1Message - role: DidCommMessageRole - } - ): Promise { - await this.didCommMessageRepository.saveOrUpdateAgentMessage(agentContext, { - associatedRecordId: options.proofRecord.id, - agentMessage: options.message, - role: options.role, - }) - } - - public async delete( - agentContext: AgentContext, - proofRecord: ProofExchangeRecord, - options?: DeleteProofOptions - ): Promise { - await this.proofRepository.delete(agentContext, proofRecord) - - const deleteAssociatedDidCommMessages = options?.deleteAssociatedDidCommMessages ?? true - - if (deleteAssociatedDidCommMessages) { - const didCommMessages = await this.didCommMessageRepository.findByQuery(agentContext, { - associatedRecordId: proofRecord.id, - }) - for (const didCommMessage of didCommMessages) { - await this.didCommMessageRepository.delete(agentContext, didCommMessage) - } - } - } - - public abstract getRequestedCredentialsForProofRequest( - agentContext: AgentContext, - options: GetRequestedCredentialsForProofRequestOptions - ): Promise> - - public abstract autoSelectCredentialsForProofRequest( - options: FormatRetrievedCredentialOptions - ): Promise> - - public abstract createProofRequestFromProposal( - agentContext: AgentContext, - options: CreateProofRequestFromProposalOptions - ): Promise> - - public abstract getFormatData(agentContext: AgentContext, proofRecordId: string): Promise> -} diff --git a/packages/core/src/modules/proofs/ProofsApi.ts b/packages/core/src/modules/proofs/ProofsApi.ts index bd482a201a..d0c4906c13 100644 --- a/packages/core/src/modules/proofs/ProofsApi.ts +++ b/packages/core/src/modules/proofs/ProofsApi.ts @@ -1,93 +1,78 @@ -import type { DidCommV1Message } from '../../didcomm/versions/v1' -import type { Query } from '../../storage/StorageService' -import type { ProofService } from './ProofService' import type { - AcceptProofPresentationOptions, + AcceptProofOptions, AcceptProofProposalOptions, + AcceptProofRequestOptions, CreateProofRequestOptions, + DeleteProofOptions, FindProofPresentationMessageReturn, FindProofProposalMessageReturn, FindProofRequestMessageReturn, + GetCredentialsForProofRequestOptions, + GetCredentialsForProofRequestReturn, + GetProofFormatDataReturn, + NegotiateProofProposalOptions, + NegotiateProofRequestOptions, ProposeProofOptions, RequestProofOptions, - ProofServiceMap, - NegotiateRequestOptions, - NegotiateProposalOptions, + SelectCredentialsForProofRequestOptions, + SelectCredentialsForProofRequestReturn, + SendProofProblemReportOptions, + DeclineProofRequestOptions, } from './ProofsApiOptions' -import type { ProofFormat } from './formats/ProofFormat' -import type { IndyProofFormat } from './formats/indy/IndyProofFormat' -import type { - AutoSelectCredentialsForProofRequestOptions, - GetRequestedCredentialsForProofRequest, -} from './models/ModuleOptions' -import type { - CreatePresentationOptions, - CreateProposalOptions, - CreateRequestOptions, - CreateRequestAsResponseOptions, - CreateProofRequestFromProposalOptions, - FormatRequestedCredentialReturn, - FormatRetrievedCredentialOptions, - DeleteProofOptions, - GetFormatDataReturn, - CreateProposalAsResponseOptions, -} from './models/ProofServiceOptions' +import type { ProofProtocol } from './protocol/ProofProtocol' +import type { ProofFormatsFromProtocols } from './protocol/ProofProtocolOptions' import type { ProofExchangeRecord } from './repository/ProofExchangeRecord' +import type { DidCommV1Message } from '../../didcomm/versions/v1/DidCommV1Message' +import type { Query } from '../../storage/StorageService' -import { inject, injectable } from 'tsyringe' +import { injectable } from 'tsyringe' -import { AgentConfig } from '../../agent/AgentConfig' -import { Dispatcher } from '../../agent/Dispatcher' import { MessageSender } from '../../agent/MessageSender' import { AgentContext } from '../../agent/context/AgentContext' import { OutboundMessageContext } from '../../agent/models' -import { InjectionSymbols } from '../../constants' import { ServiceDecorator } from '../../decorators/service/ServiceDecorator' import { AriesFrameworkError } from '../../error' -import { Logger } from '../../logger' +import { DidCommMessageRepository } from '../../storage' import { DidCommMessageRole } from '../../storage/didcomm/DidCommMessageRole' import { ConnectionService } from '../connections/services/ConnectionService' -import { MediationRecipientService } from '../routing/services/MediationRecipientService' import { RoutingService } from '../routing/services/RoutingService' -import { ProofResponseCoordinator } from './ProofResponseCoordinator' +import { ProofsModuleConfig } from './ProofsModuleConfig' import { ProofState } from './models/ProofState' -import { V1ProofService } from './protocol/v1/V1ProofService' -import { V2ProofService } from './protocol/v2/V2ProofService' import { ProofRepository } from './repository/ProofRepository' -export interface ProofsApi[]> { +export interface ProofsApi { // Proposal methods - proposeProof(options: ProposeProofOptions): Promise - acceptProposal(options: AcceptProofProposalOptions): Promise - negotiateProposal(options: NegotiateProposalOptions): Promise + proposeProof(options: ProposeProofOptions): Promise + acceptProposal(options: AcceptProofProposalOptions): Promise + negotiateProposal(options: NegotiateProofProposalOptions): Promise // Request methods - requestProof(options: RequestProofOptions): Promise - acceptRequest(options: AcceptProofPresentationOptions): Promise - declineRequest(proofRecordId: string): Promise - negotiateRequest(options: NegotiateRequestOptions): Promise + requestProof(options: RequestProofOptions): Promise + acceptRequest(options: AcceptProofRequestOptions): Promise + declineRequest(options: DeclineProofRequestOptions): Promise + negotiateRequest(options: NegotiateProofRequestOptions): Promise // Present - acceptPresentation(proofRecordId: string): Promise + acceptPresentation(options: AcceptProofOptions): Promise // out of band - createRequest(options: CreateProofRequestOptions): Promise<{ + createRequest(options: CreateProofRequestOptions): Promise<{ message: DidCommV1Message proofRecord: ProofExchangeRecord }> // Auto Select - autoSelectCredentialsForProofRequest( - options: AutoSelectCredentialsForProofRequestOptions - ): Promise> + selectCredentialsForRequest( + options: SelectCredentialsForProofRequestOptions + ): Promise> - // Get Requested Credentials - getRequestedCredentialsForProofRequest( - options: AutoSelectCredentialsForProofRequestOptions - ): Promise> + // Get credentials for request + getCredentialsForRequest( + options: GetCredentialsForProofRequestOptions + ): Promise> - sendProblemReport(proofRecordId: string, message: string): Promise + sendProblemReport(options: SendProofProblemReportOptions): Promise // Record Methods getAll(): Promise @@ -96,107 +81,87 @@ export interface ProofsApi deleteById(proofId: string, options?: DeleteProofOptions): Promise update(proofRecord: ProofExchangeRecord): Promise - getFormatData(proofRecordId: string): Promise> + getFormatData(proofRecordId: string): Promise>> // DidComm Message Records - findProposalMessage(proofRecordId: string): Promise> - findRequestMessage(proofRecordId: string): Promise> - findPresentationMessage(proofRecordId: string): Promise> + findProposalMessage(proofRecordId: string): Promise> + findRequestMessage(proofRecordId: string): Promise> + findPresentationMessage(proofRecordId: string): Promise> } @injectable() -export class ProofsApi< - PFs extends ProofFormat[] = [IndyProofFormat], - PSs extends ProofService[] = [V1ProofService, V2ProofService] -> implements ProofsApi -{ +export class ProofsApi implements ProofsApi { + /** + * Configuration for the proofs module + */ + public readonly config: ProofsModuleConfig + private connectionService: ConnectionService private messageSender: MessageSender private routingService: RoutingService private proofRepository: ProofRepository + private didCommMessageRepository: DidCommMessageRepository private agentContext: AgentContext - private agentConfig: AgentConfig - private logger: Logger - private serviceMap: ProofServiceMap public constructor( - dispatcher: Dispatcher, - mediationRecipientService: MediationRecipientService, messageSender: MessageSender, connectionService: ConnectionService, agentContext: AgentContext, - agentConfig: AgentConfig, - routingService: RoutingService, - @inject(InjectionSymbols.Logger) logger: Logger, proofRepository: ProofRepository, - v1Service: V1ProofService, - v2Service: V2ProofService + routingService: RoutingService, + didCommMessageRepository: DidCommMessageRepository, + config: ProofsModuleConfig ) { this.messageSender = messageSender this.connectionService = connectionService this.proofRepository = proofRepository this.agentContext = agentContext - this.agentConfig = agentConfig this.routingService = routingService - this.logger = logger - // Dynamically build service map. This will be extracted once services are registered dynamically - this.serviceMap = [v1Service, v2Service].reduce( - (serviceMap, service) => ({ - ...serviceMap, - [service.version]: service, - }), - {} - ) as ProofServiceMap - - this.logger.debug(`Initializing Proofs Module for agent ${this.agentContext.config.label}`) - - this.registerMessageHandlers(dispatcher, mediationRecipientService) + this.didCommMessageRepository = didCommMessageRepository + this.config = config } - public getService(protocolVersion: PVT): ProofService { - if (!this.serviceMap[protocolVersion]) { - throw new AriesFrameworkError(`No proof service registered for protocol version ${protocolVersion}`) + private getProtocol(protocolVersion: PVT): ProofProtocol { + const proofProtocol = this.config.proofProtocols.find((protocol) => protocol.version === protocolVersion) + + if (!proofProtocol) { + throw new AriesFrameworkError(`No proof protocol registered for protocol version ${protocolVersion}`) } - return this.serviceMap[protocolVersion] + return proofProtocol } /** * Initiate a new presentation exchange as prover by sending a presentation proposal message * to the connection with the specified connection id. * - * @param options multiple properties like protocol version, connection id, proof format (indy/ presentation exchange) - * to include in the message - * @returns Proof record associated with the sent proposal message + * @param options configuration to use for the proposal + * @returns Proof exchange record associated with the sent proposal message */ - public async proposeProof(options: ProposeProofOptions): Promise { - const service = this.getService(options.protocolVersion) - - const { connectionId } = options + public async proposeProof(options: ProposeProofOptions): Promise { + const protocol = this.getProtocol(options.protocolVersion) - const connection = await this.connectionService.getById(this.agentContext, connectionId) + const connectionRecord = await this.connectionService.getById(this.agentContext, options.connectionId) // Assert - connection.assertReady() + connectionRecord.assertReady() - const proposalOptions: CreateProposalOptions = { - connectionRecord: connection, + const { message, proofRecord } = await protocol.createProposal(this.agentContext, { + connectionRecord, proofFormats: options.proofFormats, autoAcceptProof: options.autoAcceptProof, goalCode: options.goalCode, comment: options.comment, parentThreadId: options.parentThreadId, - } - - const { message, proofRecord } = await service.createProposal(this.agentContext, proposalOptions) + }) const outboundMessageContext = new OutboundMessageContext(message, { agentContext: this.agentContext, - connection, + connection: connectionRecord, associatedRecord: proofRecord, }) - await this.messageSender.sendMessage(outboundMessageContext) + await this.messageSender.sendMessage(outboundMessageContext) return proofRecord } @@ -204,53 +169,42 @@ export class ProofsApi< * Accept a presentation proposal as verifier (by sending a presentation request message) to the connection * associated with the proof record. * - * @param options multiple properties like proof record id, additional configuration for creating the request - * @returns Proof record associated with the presentation request + * @param options config object for accepting the proposal + * @returns Proof exchange record associated with the presentation request */ - public async acceptProposal(options: AcceptProofProposalOptions): Promise { - const { proofRecordId } = options - - const proofRecord = await this.getById(proofRecordId) - - const service = this.getService(proofRecord.protocolVersion) + public async acceptProposal(options: AcceptProofProposalOptions): Promise { + const proofRecord = await this.getById(options.proofRecordId) if (!proofRecord.connectionId) { throw new AriesFrameworkError( - `No connectionId found for proof record '${proofRecord.id}'. Connection-less issuance does not support presentation proposal or negotiation.` + `No connectionId found for proof record '${proofRecord.id}'. Connection-less verification does not support presentation proposal or negotiation.` ) } - const connection = await this.connectionService.getById(this.agentContext, proofRecord.connectionId) + // with version we can get the protocol + const protocol = this.getProtocol(proofRecord.protocolVersion) + const connectionRecord = await this.connectionService.getById(this.agentContext, proofRecord.connectionId) // Assert - connection.assertReady() + connectionRecord.assertReady() - const proofRequestFromProposalOptions: CreateProofRequestFromProposalOptions = { + const { message } = await protocol.acceptProposal(this.agentContext, { proofRecord, - } - - const proofRequest = await service.createProofRequestFromProposal( - this.agentContext, - proofRequestFromProposalOptions - ) - - const requestOptions: CreateRequestAsResponseOptions = { - proofRecord: proofRecord, - proofFormats: proofRequest.proofFormats, + proofFormats: options.proofFormats, goalCode: options.goalCode, - willConfirm: options.willConfirm ?? true, + willConfirm: options.willConfirm, comment: options.comment, - } - - const { message } = await service.createRequestAsResponse(this.agentContext, requestOptions) + autoAcceptProof: options.autoAcceptProof, + }) + // send the message const outboundMessageContext = new OutboundMessageContext(message, { agentContext: this.agentContext, - connection, + connection: connectionRecord, associatedRecord: proofRecord, }) - await this.messageSender.sendMessage(outboundMessageContext) + await this.messageSender.sendMessage(outboundMessageContext) return proofRecord } @@ -262,35 +216,33 @@ export class ProofsApi< * specifying which credentials to use for the proof * @returns Proof record associated with the sent request message */ - public async negotiateProposal(options: NegotiateProposalOptions): Promise { - const { proofRecordId } = options - - const proofRecord = await this.getById(proofRecordId) - - const service = this.getService(proofRecord.protocolVersion) + public async negotiateProposal(options: NegotiateProofProposalOptions): Promise { + const proofRecord = await this.getById(options.proofRecordId) if (!proofRecord.connectionId) { throw new AriesFrameworkError( - `No connectionId found for proof record '${proofRecord.id}'. Connection-less issuance does not support negotiation.` + `No connectionId found for proof record '${proofRecord.id}'. Connection-less verification does not support negotiation.` ) } - const connection = await this.connectionService.getById(this.agentContext, proofRecord.connectionId) + const protocol = this.getProtocol(proofRecord.protocolVersion) + const connectionRecord = await this.connectionService.getById(this.agentContext, proofRecord.connectionId) // Assert - connection.assertReady() + connectionRecord.assertReady() - const requestOptions: CreateRequestAsResponseOptions = { + const { message } = await protocol.negotiateProposal(this.agentContext, { proofRecord, proofFormats: options.proofFormats, autoAcceptProof: options.autoAcceptProof, comment: options.comment, - } - const { message } = await service.createRequestAsResponse(this.agentContext, requestOptions) + goalCode: options.goalCode, + willConfirm: options.willConfirm, + }) const outboundMessageContext = new OutboundMessageContext(message, { agentContext: this.agentContext, - connection, + connection: connectionRecord, associatedRecord: proofRecord, }) await this.messageSender.sendMessage(outboundMessageContext) @@ -305,30 +257,30 @@ export class ProofsApi< * @param options multiple properties like connection id, protocol version, proof Formats to build the proof request * @returns Proof record associated with the sent request message */ - public async requestProof(options: RequestProofOptions): Promise { - const service = this.getService(options.protocolVersion) - - const connection = await this.connectionService.getById(this.agentContext, options.connectionId) + public async requestProof(options: RequestProofOptions): Promise { + const connectionRecord = await this.connectionService.getById(this.agentContext, options.connectionId) + const protocol = this.getProtocol(options.protocolVersion) // Assert - connection.assertReady() + connectionRecord.assertReady() - const createProofRequest: CreateRequestOptions = { - connectionRecord: connection, + const { message, proofRecord } = await protocol.createRequest(this.agentContext, { + connectionRecord, proofFormats: options.proofFormats, autoAcceptProof: options.autoAcceptProof, parentThreadId: options.parentThreadId, comment: options.comment, - } - const { message, proofRecord } = await service.createRequest(this.agentContext, createProofRequest) + goalCode: options.goalCode, + willConfirm: options.willConfirm, + }) const outboundMessageContext = new OutboundMessageContext(message, { agentContext: this.agentContext, - connection, + connection: connectionRecord, associatedRecord: proofRecord, }) - await this.messageSender.sendMessage(outboundMessageContext) + await this.messageSender.sendMessage(outboundMessageContext) return proofRecord } @@ -340,32 +292,33 @@ export class ProofsApi< * specifying which credentials to use for the proof * @returns Proof record associated with the sent presentation message */ - public async acceptRequest(options: AcceptProofPresentationOptions): Promise { - const { proofRecordId, proofFormats, comment } = options - - const record = await this.getById(proofRecordId) + public async acceptRequest(options: AcceptProofRequestOptions): Promise { + const proofRecord = await this.getById(options.proofRecordId) - const service = this.getService(record.protocolVersion) + const protocol = this.getProtocol(proofRecord.protocolVersion) - const presentationOptions: CreatePresentationOptions = { - proofFormats, - proofRecord: record, - comment, - } - const { message, proofRecord } = await service.createPresentation(this.agentContext, presentationOptions) + const requestMessage = await protocol.findRequestMessage(this.agentContext, proofRecord.id) - const requestMessage = await service.findRequestMessage(this.agentContext, proofRecord.id) + const recipientService = requestMessage?.serviceDecorator() // Use connection if present if (proofRecord.connectionId) { - const connection = await this.connectionService.getById(this.agentContext, proofRecord.connectionId) + const connectionRecord = await this.connectionService.getById(this.agentContext, proofRecord.connectionId) // Assert - connection.assertReady() + connectionRecord.assertReady() + + const { message } = await protocol.acceptRequest(this.agentContext, { + proofFormats: options.proofFormats, + proofRecord, + comment: options.comment, + autoAcceptProof: options.autoAcceptProof, + goalCode: options.goalCode, + }) const outboundMessageContext = new OutboundMessageContext(message, { agentContext: this.agentContext, - connection, + connection: connectionRecord, associatedRecord: proofRecord, }) await this.messageSender.sendMessage(outboundMessageContext) @@ -374,36 +327,39 @@ export class ProofsApi< } // Use ~service decorator otherwise - else if (requestMessage?.service) { + else if (recipientService) { // Create ~service decorator const routing = await this.routingService.getRouting(this.agentContext) - message.service = new ServiceDecorator({ + const ourService = new ServiceDecorator({ serviceEndpoint: routing.endpoints[0], recipientKeys: [routing.recipientKey.publicKeyBase58], routingKeys: routing.routingKeys.map((key) => key.publicKeyBase58), }) - const recipientService = requestMessage.service - + const { message } = await protocol.acceptRequest(this.agentContext, { + proofFormats: options.proofFormats, + proofRecord, + comment: options.comment, + autoAcceptProof: options.autoAcceptProof, + goalCode: options.goalCode, + }) // Set and save ~service decorator to record (to remember our verkey) - - await service.saveOrUpdatePresentationMessage(this.agentContext, { - proofRecord: proofRecord, - message: message, + message.service = ourService + await this.didCommMessageRepository.saveOrUpdateAgentMessage(this.agentContext, { + agentMessage: message, role: DidCommMessageRole.Sender, + associatedRecordId: proofRecord.id, }) - await this.messageSender.sendMessageToService( new OutboundMessageContext(message, { agentContext: this.agentContext, serviceParams: { service: recipientService.resolvedDidCommService, - senderKey: message.service.resolvedDidCommService.recipientKeys[0], - returnRoute: true, + senderKey: ourService.resolvedDidCommService.recipientKeys[0], + returnRoute: options.useReturnRoute ?? true, // defaults to true if missing }, }) ) - return proofRecord } // Cannot send message without connectionId or ~service decorator @@ -414,36 +370,16 @@ export class ProofsApi< } } - /** - * Initiate a new presentation exchange as verifier by sending an out of band presentation - * request message - * - * @param options multiple properties like protocol version, proof Formats to build the proof request - * @returns the message itself and the proof record associated with the sent request message - */ - public async createRequest(options: CreateProofRequestOptions): Promise<{ - message: DidCommV1Message - proofRecord: ProofExchangeRecord - }> { - const service = this.getService(options.protocolVersion) + public async declineRequest(options: DeclineProofRequestOptions): Promise { + const proofRecord = await this.getById(options.proofRecordId) + proofRecord.assertState(ProofState.RequestReceived) - const createProofRequest: CreateRequestOptions = { - proofFormats: options.proofFormats, - autoAcceptProof: options.autoAcceptProof, - comment: options.comment, - parentThreadId: options.parentThreadId, + const protocol = this.getProtocol(proofRecord.protocolVersion) + if (options.sendProblemReport) { + await this.sendProblemReport({ proofRecordId: options.proofRecordId, description: 'Request declined' }) } - return await service.createRequest(this.agentContext, createProofRequest) - } - - public async declineRequest(proofRecordId: string): Promise { - const proofRecord = await this.getById(proofRecordId) - const service = this.getService(proofRecord.protocolVersion) - - proofRecord.assertState(ProofState.RequestReceived) - - await service.updateState(this.agentContext, proofRecord, ProofState.Declined) + await protocol.updateState(this.agentContext, proofRecord, ProofState.Declined) return proofRecord } @@ -456,43 +392,62 @@ export class ProofsApi< * to include in the message * @returns Proof record associated with the sent proposal message */ - public async negotiateRequest(options: NegotiateRequestOptions): Promise { - const { proofRecordId } = options - const proofRecord = await this.getById(proofRecordId) - - const service = this.getService(proofRecord.protocolVersion) + public async negotiateRequest(options: NegotiateProofRequestOptions): Promise { + const proofRecord = await this.getById(options.proofRecordId) if (!proofRecord.connectionId) { throw new AriesFrameworkError( - `No connectionId found for proof record '${proofRecord.id}'. Connection-less issuance does not support presentation proposal or negotiation.` + `No connectionId found for proof record '${proofRecord.id}'. Connection-less verification does not support presentation proposal or negotiation.` ) } - const connection = await this.connectionService.getById(this.agentContext, proofRecord.connectionId) + const connectionRecord = await this.connectionService.getById(this.agentContext, proofRecord.connectionId) // Assert - connection.assertReady() + connectionRecord.assertReady() - const proposalOptions: CreateProposalAsResponseOptions = { + const protocol = this.getProtocol(proofRecord.protocolVersion) + const { message } = await protocol.negotiateRequest(this.agentContext, { proofRecord, proofFormats: options.proofFormats, autoAcceptProof: options.autoAcceptProof, goalCode: options.goalCode, comment: options.comment, - } - - const { message } = await service.createProposalAsResponse(this.agentContext, proposalOptions) + }) const outboundMessageContext = new OutboundMessageContext(message, { agentContext: this.agentContext, - connection, + connection: connectionRecord, associatedRecord: proofRecord, }) - await this.messageSender.sendMessage(outboundMessageContext) + await this.messageSender.sendMessage(outboundMessageContext) return proofRecord } + /** + * Initiate a new presentation exchange as verifier by sending an out of band presentation + * request message + * + * @param options multiple properties like protocol version, proof Formats to build the proof request + * @returns the message itself and the proof record associated with the sent request message + */ + public async createRequest(options: CreateProofRequestOptions): Promise<{ + message: DidCommV1Message + proofRecord: ProofExchangeRecord + }> { + const protocol = this.getProtocol(options.protocolVersion) + + return await protocol.createRequest(this.agentContext, { + proofFormats: options.proofFormats, + autoAcceptProof: options.autoAcceptProof, + comment: options.comment, + parentThreadId: options.parentThreadId, + goalCode: options.goalCode, + willConfirm: options.willConfirm, + }) + } + /** * Accept a presentation as prover (by sending a presentation acknowledgement message) to the connection * associated with the proof record. @@ -501,47 +456,54 @@ export class ProofsApi< * @returns Proof record associated with the sent presentation acknowledgement message * */ - public async acceptPresentation(proofRecordId: string): Promise { - const record = await this.getById(proofRecordId) - const service = this.getService(record.protocolVersion) - - const { message, proofRecord } = await service.createAck(this.agentContext, { - proofRecord: record, - }) - - const requestMessage = await service.findRequestMessage(this.agentContext, record.id) + public async acceptPresentation(options: AcceptProofOptions): Promise { + const proofRecord = await this.getById(options.proofRecordId) + const protocol = this.getProtocol(proofRecord.protocolVersion) - const presentationMessage = await service.findPresentationMessage(this.agentContext, record.id) + const requestMessage = await protocol.findRequestMessage(this.agentContext, proofRecord.id) + const presentationMessage = await protocol.findPresentationMessage(this.agentContext, proofRecord.id) // Use connection if present if (proofRecord.connectionId) { - const connection = await this.connectionService.getById(this.agentContext, proofRecord.connectionId) + const connectionRecord = await this.connectionService.getById(this.agentContext, proofRecord.connectionId) // Assert - connection.assertReady() + connectionRecord.assertReady() + + const { message } = await protocol.acceptPresentation(this.agentContext, { + proofRecord, + }) const outboundMessageContext = new OutboundMessageContext(message, { agentContext: this.agentContext, - connection, + connection: connectionRecord, associatedRecord: proofRecord, }) await this.messageSender.sendMessage(outboundMessageContext) + + return proofRecord } // Use ~service decorator otherwise else if (requestMessage?.service && presentationMessage?.service) { - const recipientService = presentationMessage?.service + const recipientService = presentationMessage.service const ourService = requestMessage.service + const { message } = await protocol.acceptPresentation(this.agentContext, { + proofRecord, + }) + await this.messageSender.sendMessageToService( new OutboundMessageContext(message, { agentContext: this.agentContext, serviceParams: { service: recipientService.resolvedDidCommService, senderKey: ourService.resolvedDidCommService.recipientKeys[0], - returnRoute: true, + returnRoute: false, // hard wire to be false since it's the end of the protocol so not needed here }, }) ) + + return proofRecord } // Cannot send message without credentialId or ~service decorator else { @@ -549,8 +511,6 @@ export class ProofsApi< `Cannot accept presentation without connectionId or ~service decorator on presentation message.` ) } - - return record } /** @@ -561,39 +521,34 @@ export class ProofsApi< * @param options multiple properties like proof record id and optional configuration * @returns RequestedCredentials */ - public async autoSelectCredentialsForProofRequest( - options: AutoSelectCredentialsForProofRequestOptions - ): Promise> { + public async selectCredentialsForRequest( + options: SelectCredentialsForProofRequestOptions + ): Promise> { const proofRecord = await this.getById(options.proofRecordId) - const service = this.getService(proofRecord.protocolVersion) + const protocol = this.getProtocol(proofRecord.protocolVersion) - const retrievedCredentials: FormatRetrievedCredentialOptions = - await service.getRequestedCredentialsForProofRequest(this.agentContext, { - proofRecord: proofRecord, - config: options.config, - }) - return await service.autoSelectCredentialsForProofRequest(retrievedCredentials) + return protocol.selectCredentialsForRequest(this.agentContext, { + proofFormats: options.proofFormats, + proofRecord, + }) } /** - * Create a {@link RetrievedCredentials} object. Given input proof request and presentation proposal, - * use credentials in the wallet to build indy requested credentials object for input to proof creation. - * - * If restrictions allow, self attested attributes will be used. + * Get credentials in the wallet for a received proof request. * * @param options multiple properties like proof record id and optional configuration - * @returns RetrievedCredentials object */ - public async getRequestedCredentialsForProofRequest( - options: GetRequestedCredentialsForProofRequest - ): Promise> { - const record = await this.getById(options.proofRecordId) - const service = this.getService(record.protocolVersion) - - return await service.getRequestedCredentialsForProofRequest(this.agentContext, { - proofRecord: record, - config: options.config, + public async getCredentialsForRequest( + options: GetCredentialsForProofRequestOptions + ): Promise> { + const proofRecord = await this.getById(options.proofRecordId) + + const protocol = this.getProtocol(proofRecord.protocolVersion) + + return protocol.getCredentialsForRequest(this.agentContext, { + proofRecord, + proofFormats: options.proofFormats, }) } @@ -604,37 +559,69 @@ export class ProofsApi< * @param message message to send * @returns proof record associated with the proof problem report message */ - public async sendProblemReport(proofRecordId: string, message: string): Promise { - const record = await this.getById(proofRecordId) - const service = this.getService(record.protocolVersion) - if (!record.connectionId) { - throw new AriesFrameworkError(`No connectionId found for proof record '${record.id}'.`) - } - const connection = await this.connectionService.getById(this.agentContext, record.connectionId) + public async sendProblemReport(options: SendProofProblemReportOptions): Promise { + const proofRecord = await this.getById(options.proofRecordId) - // Assert - connection.assertReady() + const protocol = this.getProtocol(proofRecord.protocolVersion) - const { message: problemReport } = await service.createProblemReport(this.agentContext, { - proofRecord: record, - description: message, - }) + const requestMessage = await protocol.findRequestMessage(this.agentContext, proofRecord.id) - const outboundMessageContext = new OutboundMessageContext(problemReport, { - agentContext: this.agentContext, - connection, - associatedRecord: record, + const { message: problemReport } = await protocol.createProblemReport(this.agentContext, { + proofRecord, + description: options.description, }) - await this.messageSender.sendMessage(outboundMessageContext) - return record + if (proofRecord.connectionId) { + const connectionRecord = await this.connectionService.getById(this.agentContext, proofRecord.connectionId) + + // Assert + connectionRecord.assertReady() + + const outboundMessageContext = new OutboundMessageContext(problemReport, { + agentContext: this.agentContext, + connection: connectionRecord, + associatedRecord: proofRecord, + }) + + await this.messageSender.sendMessage(outboundMessageContext) + return proofRecord + } else if (requestMessage?.service) { + proofRecord.assertState(ProofState.RequestReceived) + + // Create ~service decorator + const routing = await this.routingService.getRouting(this.agentContext) + const ourService = new ServiceDecorator({ + serviceEndpoint: routing.endpoints[0], + recipientKeys: [routing.recipientKey.publicKeyBase58], + routingKeys: routing.routingKeys.map((key) => key.publicKeyBase58), + }) + const recipientService = requestMessage.service + + await this.messageSender.sendMessageToService( + new OutboundMessageContext(problemReport, { + agentContext: this.agentContext, + serviceParams: { + service: recipientService.resolvedDidCommService, + senderKey: ourService.resolvedDidCommService.recipientKeys[0], + }, + }) + ) + + return proofRecord + } + // Cannot send message without connectionId or ~service decorator + else { + throw new AriesFrameworkError( + `Cannot send problem report without connectionId or ~service decorator on presentation request.` + ) + } } - public async getFormatData(proofRecordId: string): Promise> { + public async getFormatData(proofRecordId: string): Promise>> { const proofRecord = await this.getById(proofRecordId) - const service = this.getService(proofRecord.protocolVersion) + const protocol = this.getProtocol(proofRecord.protocolVersion) - return service.getFormatData(this.agentContext, proofRecordId) + return protocol.getFormatData(this.agentContext, proofRecordId) } /** @@ -685,8 +672,8 @@ export class ProofsApi< */ public async deleteById(proofId: string, options?: DeleteProofOptions) { const proofRecord = await this.getById(proofId) - const service = this.getService(proofRecord.protocolVersion) - return service.delete(this.agentContext, proofRecord, options) + const protocol = this.getProtocol(proofRecord.protocolVersion) + return protocol.delete(this.agentContext, proofRecord, options) } /** @@ -725,34 +712,21 @@ export class ProofsApi< await this.proofRepository.update(this.agentContext, proofRecord) } - public async findProposalMessage(proofRecordId: string): Promise> { + public async findProposalMessage(proofRecordId: string): Promise> { const record = await this.getById(proofRecordId) - const service = this.getService(record.protocolVersion) - return service.findProposalMessage(this.agentContext, proofRecordId) + const protocol = this.getProtocol(record.protocolVersion) + return protocol.findProposalMessage(this.agentContext, proofRecordId) as FindProofProposalMessageReturn } - public async findRequestMessage(proofRecordId: string): Promise> { + public async findRequestMessage(proofRecordId: string): Promise> { const record = await this.getById(proofRecordId) - const service = this.getService(record.protocolVersion) - return service.findRequestMessage(this.agentContext, proofRecordId) + const protocol = this.getProtocol(record.protocolVersion) + return protocol.findRequestMessage(this.agentContext, proofRecordId) as FindProofRequestMessageReturn } - public async findPresentationMessage(proofRecordId: string): Promise> { + public async findPresentationMessage(proofRecordId: string): Promise> { const record = await this.getById(proofRecordId) - const service = this.getService(record.protocolVersion) - return service.findPresentationMessage(this.agentContext, proofRecordId) - } - - private registerMessageHandlers(dispatcher: Dispatcher, mediationRecipientService: MediationRecipientService) { - for (const service of Object.values(this.serviceMap)) { - const proofService = service as ProofService - proofService.registerMessageHandlers( - dispatcher, - this.agentConfig, - new ProofResponseCoordinator(proofService), - mediationRecipientService, - this.routingService - ) - } + const protocol = this.getProtocol(record.protocolVersion) + return protocol.findPresentationMessage(this.agentContext, proofRecordId) as FindProofPresentationMessageReturn } } diff --git a/packages/core/src/modules/proofs/ProofsApiOptions.ts b/packages/core/src/modules/proofs/ProofsApiOptions.ts index 5d23a7b131..0e9911febc 100644 --- a/packages/core/src/modules/proofs/ProofsApiOptions.ts +++ b/packages/core/src/modules/proofs/ProofsApiOptions.ts @@ -1,97 +1,183 @@ -import type { ProofService } from './ProofService' -import type { ProofFormat, ProofFormatPayload } from './formats/ProofFormat' +import type { ProofFormatCredentialForRequestPayload, ProofFormatPayload } from './formats' import type { AutoAcceptProof } from './models' -import type { ProofConfig } from './models/ModuleOptions' +import type { ProofProtocol } from './protocol/ProofProtocol' +import type { + DeleteProofOptions, + GetProofFormatDataReturn, + ProofFormatsFromProtocols, +} from './protocol/ProofProtocolOptions' -/** - * Get the supported protocol versions based on the provided proof services. - */ -export type ProofsProtocolVersionType = PSs[number]['version'] -export type FindProofProposalMessageReturn = ReturnType -export type FindProofRequestMessageReturn = ReturnType -export type FindProofPresentationMessageReturn = ReturnType< - PSs[number]['findPresentationMessage'] +// re-export GetFormatDataReturn type from protocol, as it is also used in the api +export type { GetProofFormatDataReturn, DeleteProofOptions } + +export type FindProofProposalMessageReturn = ReturnType +export type FindProofRequestMessageReturn = ReturnType +export type FindProofPresentationMessageReturn = ReturnType< + PPs[number]['findPresentationMessage'] > /** - * Get the service map for usage in the proofs module. Will return a type mapping of protocol version to service. - * - * @example - * ``` - * type ServiceMap = ProofServiceMap<[IndyProofFormat], [V1ProofService]> - * - * // equal to - * type ServiceMap = { - * v1: V1ProofService - * } - * ``` + * Get the supported protocol versions based on the provided proof protocols. */ -export type ProofServiceMap[]> = { - [PS in PSs[number] as PS['version']]: ProofService +export type ProofsProtocolVersionType = PPs[number]['version'] + +interface BaseOptions { + autoAcceptProof?: AutoAcceptProof + comment?: string } -export interface ProposeProofOptions< - PFs extends ProofFormat[] = ProofFormat[], - PSs extends ProofService[] = ProofService[] -> { +/** + * Interface for ProofsApi.proposeProof. Will send a proposal. + */ +export interface ProposeProofOptions extends BaseOptions { connectionId: string - protocolVersion: ProofsProtocolVersionType - proofFormats: ProofFormatPayload - comment?: string + protocolVersion: ProofsProtocolVersionType + proofFormats: ProofFormatPayload, 'createProposal'> + goalCode?: string - autoAcceptProof?: AutoAcceptProof parentThreadId?: string } -export interface NegotiateRequestOptions { +/** + * Interface for ProofsApi.acceptProposal. Will send a request + * + * proofFormats is optional because this is an accept method + */ +export interface AcceptProofProposalOptions extends BaseOptions { proofRecordId: string - proofFormats: ProofFormatPayload - comment?: string + proofFormats?: ProofFormatPayload, 'acceptProposal'> + goalCode?: string - autoAcceptProof?: AutoAcceptProof + + /** @default true */ + willConfirm?: boolean } -export interface AcceptProofPresentationOptions { +/** + * Interface for ProofsApi.negotiateProposal. Will send a request + */ +export interface NegotiateProofProposalOptions extends BaseOptions { proofRecordId: string - comment?: string - proofFormats: ProofFormatPayload + proofFormats: ProofFormatPayload, 'createRequest'> + + goalCode?: string + + /** @default true */ + willConfirm?: boolean } -export interface AcceptProofProposalOptions { - proofRecordId: string - config?: ProofConfig +/** + * Interface for ProofsApi.createRequest. Will create an out of band request + */ +export interface CreateProofRequestOptions extends BaseOptions { + protocolVersion: ProofsProtocolVersionType + proofFormats: ProofFormatPayload, 'createRequest'> + goalCode?: string + parentThreadId?: string + + /** @default true */ willConfirm?: boolean - comment?: string } -export interface RequestProofOptions< - PFs extends ProofFormat[] = ProofFormat[], - PSs extends ProofService[] = ProofService[] -> { - protocolVersion: ProofsProtocolVersionType +/** + * Interface for ProofsApi.requestCredential. Extends CreateProofRequestOptions, will send a request + */ +export interface RequestProofOptions + extends BaseOptions, + CreateProofRequestOptions { connectionId: string - proofFormats: ProofFormatPayload - comment?: string - autoAcceptProof?: AutoAcceptProof - parentThreadId?: string } -export interface NegotiateProposalOptions { +/** + * Interface for ProofsApi.acceptRequest. Will send a presentation + */ +export interface AcceptProofRequestOptions extends BaseOptions { proofRecordId: string - proofFormats: ProofFormatPayload - comment?: string - autoAcceptProof?: AutoAcceptProof - parentThreadId?: string + + /** + * whether to enable return routing on the send presentation message. This value only + * has an effect for connectionless exchanges. + */ + useReturnRoute?: boolean + proofFormats?: ProofFormatPayload, 'acceptRequest'> + + goalCode?: string + + /** @default true */ + willConfirm?: boolean } -export interface CreateProofRequestOptions< - PFs extends ProofFormat[] = ProofFormat[], - PSs extends ProofService[] = ProofService[] -> { - protocolVersion: ProofsProtocolVersionType - proofFormats: ProofFormatPayload - comment?: string - autoAcceptProof?: AutoAcceptProof - parentThreadId?: string +/** + * Interface for ProofsApi.negotiateRequest. Will send a proposal + */ +export interface NegotiateProofRequestOptions extends BaseOptions { + proofRecordId: string + proofFormats: ProofFormatPayload, 'createProposal'> + + goalCode?: string +} + +/** + * Interface for ProofsApi.acceptPresentation. Will send an ack message + */ +export interface AcceptProofOptions { + proofRecordId: string +} + +/** + * Interface for ProofsApi.getCredentialsForRequest. Will return the credentials that match the proof request + */ +export interface GetCredentialsForProofRequestOptions { + proofRecordId: string + proofFormats?: ProofFormatCredentialForRequestPayload< + ProofFormatsFromProtocols, + 'getCredentialsForRequest', + 'input' + > +} + +export interface GetCredentialsForProofRequestReturn { + proofFormats: ProofFormatCredentialForRequestPayload< + ProofFormatsFromProtocols, + 'getCredentialsForRequest', + 'output' + > +} + +/** + * Interface for ProofsApi.selectCredentialsForRequest. Will automatically select return the first/best + * credentials that match the proof request + */ +export interface SelectCredentialsForProofRequestOptions { + proofRecordId: string + proofFormats?: ProofFormatCredentialForRequestPayload< + ProofFormatsFromProtocols, + 'getCredentialsForRequest', + 'input' + > +} + +export interface SelectCredentialsForProofRequestReturn { + proofFormats: ProofFormatCredentialForRequestPayload< + ProofFormatsFromProtocols, + 'selectCredentialsForRequest', + 'output' + > +} + +/** + * Interface for ProofsApi.sendProblemReport. Will send a problem-report message + */ +export interface SendProofProblemReportOptions { + proofRecordId: string + description: string +} + +/** + * Interface for ProofsApi.declineRequest. Decline a received proof request and optionally send a problem-report message to Verifier + */ +export interface DeclineProofRequestOptions { + proofRecordId: string + sendProblemReport?: boolean } diff --git a/packages/core/src/modules/proofs/ProofsModule.ts b/packages/core/src/modules/proofs/ProofsModule.ts index 05136c95a0..b154e6bef3 100644 --- a/packages/core/src/modules/proofs/ProofsModule.ts +++ b/packages/core/src/modules/proofs/ProofsModule.ts @@ -1,22 +1,38 @@ -import type { FeatureRegistry } from '../../agent/FeatureRegistry' -import type { DependencyManager, Module } from '../../plugins' import type { ProofsModuleConfigOptions } from './ProofsModuleConfig' - -import { Protocol } from '../../agent/models' +import type { ProofProtocol } from './protocol/ProofProtocol' +import type { FeatureRegistry } from '../../agent/FeatureRegistry' +import type { ApiModule, DependencyManager } from '../../plugins' +import type { Optional } from '../../utils' +import type { Constructor } from '../../utils/mixins' import { ProofsApi } from './ProofsApi' import { ProofsModuleConfig } from './ProofsModuleConfig' -import { IndyProofFormatService } from './formats/indy/IndyProofFormatService' -import { V1ProofService } from './protocol/v1' -import { V2ProofService } from './protocol/v2' +import { V2ProofProtocol } from './protocol' import { ProofRepository } from './repository' -export class ProofsModule implements Module { - public readonly config: ProofsModuleConfig - public readonly api = ProofsApi - - public constructor(config?: ProofsModuleConfigOptions) { - this.config = new ProofsModuleConfig(config) +/** + * Default proofProtocols that will be registered if the `proofProtocols` property is not configured. + */ +export type DefaultProofProtocols = [V2ProofProtocol<[]>] + +// ProofsModuleOptions makes the proofProtocols property optional from the config, as it will set it when not provided. +export type ProofsModuleOptions = Optional< + ProofsModuleConfigOptions, + 'proofProtocols' +> + +export class ProofsModule implements ApiModule { + public readonly config: ProofsModuleConfig + + public readonly api: Constructor> = ProofsApi + + public constructor(config?: ProofsModuleOptions) { + this.config = new ProofsModuleConfig({ + ...config, + // NOTE: the proofProtocols defaults are set in the ProofsModule rather than the ProofsModuleConfig to + // avoid dependency cycles. + proofProtocols: config?.proofProtocols ?? [new V2ProofProtocol({ proofFormats: [] })], + }) as ProofsModuleConfig } /** @@ -29,22 +45,11 @@ export class ProofsModule implements Module { // Config dependencyManager.registerInstance(ProofsModuleConfig, this.config) - // Services - dependencyManager.registerSingleton(V1ProofService) - dependencyManager.registerSingleton(V2ProofService) - // Repositories dependencyManager.registerSingleton(ProofRepository) - // Proof Formats - dependencyManager.registerSingleton(IndyProofFormatService) - - // Features - featureRegistry.register( - new Protocol({ - id: 'https://didcomm.org/present-proof/1.0', - roles: ['verifier', 'prover'], - }) - ) + for (const proofProtocol of this.config.proofProtocols) { + proofProtocol.register(dependencyManager, featureRegistry) + } } } diff --git a/packages/core/src/modules/proofs/ProofsModuleConfig.ts b/packages/core/src/modules/proofs/ProofsModuleConfig.ts index e0b12449e2..e87966ef27 100644 --- a/packages/core/src/modules/proofs/ProofsModuleConfig.ts +++ b/packages/core/src/modules/proofs/ProofsModuleConfig.ts @@ -1,27 +1,47 @@ +import type { ProofProtocol } from './protocol/ProofProtocol' + import { AutoAcceptProof } from './models/ProofAutoAcceptType' /** * ProofsModuleConfigOptions defines the interface for the options of the ProofsModuleConfig class. * This can contain optional parameters that have default values in the config class itself. */ -export interface ProofsModuleConfigOptions { +export interface ProofsModuleConfigOptions { /** * Whether to automatically accept proof messages. Applies to all present proof protocol versions. * * @default {@link AutoAcceptProof.Never} */ autoAcceptProofs?: AutoAcceptProof + + /** + * Proof protocols to make available to the proofs module. Only one proof protocol should be registered for each proof + * protocol version. + * + * When not provided, the `V2ProofProtocol` is registered by default. + * + * @default + * ``` + * [V2ProofProtocol] + * ``` + */ + proofProtocols: ProofProtocols } -export class ProofsModuleConfig { - private options: ProofsModuleConfigOptions +export class ProofsModuleConfig { + private options: ProofsModuleConfigOptions - public constructor(options?: ProofsModuleConfigOptions) { - this.options = options ?? {} + public constructor(options: ProofsModuleConfigOptions) { + this.options = options } /** See {@link ProofsModuleConfigOptions.autoAcceptProofs} */ public get autoAcceptProofs() { return this.options.autoAcceptProofs ?? AutoAcceptProof.Never } + + /** See {@link ProofsModuleConfigOptions.proofProtocols} */ + public get proofProtocols() { + return this.options.proofProtocols + } } diff --git a/packages/core/src/modules/proofs/__tests__/ProofRequest.test.ts b/packages/core/src/modules/proofs/__tests__/ProofRequest.test.ts deleted file mode 100644 index fbfab93e5f..0000000000 --- a/packages/core/src/modules/proofs/__tests__/ProofRequest.test.ts +++ /dev/null @@ -1,79 +0,0 @@ -import { ClassValidationError } from '../../../error/ClassValidationError' -import { JsonTransformer } from '../../../utils/JsonTransformer' -import { MessageValidator } from '../../../utils/MessageValidator' -import { ProofRequest } from '../formats/indy/models/ProofRequest' - -describe('ProofRequest', () => { - it('should successfully validate if the proof request JSON contains a valid structure', async () => { - const proofRequest = JsonTransformer.fromJSON( - { - name: 'ProofRequest', - version: '1.0', - nonce: '947121108704767252195123', - requested_attributes: { - First: { - name: 'Timo', - restrictions: [ - { - schema_id: 'q7ATwTYbQDgiigVijUAej:2:test:1.0', - }, - ], - }, - }, - requested_predicates: { - Second: { - name: 'Timo', - p_type: '<=', - p_value: 10, - restrictions: [ - { - schema_id: 'q7ATwTYbQDgiigVijUAej:2:test:1.0', - }, - ], - }, - }, - }, - ProofRequest - ) - - expect(() => MessageValidator.validateSync(proofRequest)).not.toThrow() - }) - - it('should throw an error if the proof request json contains an invalid structure', async () => { - const proofRequest = { - name: 'ProofRequest', - version: '1.0', - nonce: '947121108704767252195123', - requested_attributes: { - First: { - names: [], - restrictions: [ - { - schema_id: 'q7ATwTYbQDgiigVijUAej:2:test:1.0', - }, - ], - }, - }, - requested_predicates: [ - { - name: 'Timo', - p_type: '<=', - p_value: 10, - restrictions: [ - { - schema_id: 'q7ATwTYbQDgiigVijUAej:2:test:1.0', - }, - ], - }, - ], - } - - expect(() => JsonTransformer.fromJSON(proofRequest, ProofRequest)).toThrowError(ClassValidationError) - try { - JsonTransformer.fromJSON(proofRequest, ProofRequest) - } catch (e) { - const caughtError = e as ClassValidationError - expect(caughtError.validationErrors).toHaveLength(2) - } - }) -}) diff --git a/packages/core/src/modules/proofs/__tests__/ProofsModule.test.ts b/packages/core/src/modules/proofs/__tests__/ProofsModule.test.ts index 8af9a5b2c2..1126aeda52 100644 --- a/packages/core/src/modules/proofs/__tests__/ProofsModule.test.ts +++ b/packages/core/src/modules/proofs/__tests__/ProofsModule.test.ts @@ -1,33 +1,59 @@ +import type { ProofProtocol } from '../protocol/ProofProtocol' + import { FeatureRegistry } from '../../../agent/FeatureRegistry' import { DependencyManager } from '../../../plugins/DependencyManager' import { ProofsApi } from '../ProofsApi' import { ProofsModule } from '../ProofsModule' -import { IndyProofFormatService } from '../formats/indy/IndyProofFormatService' -import { V1ProofService } from '../protocol/v1/V1ProofService' -import { V2ProofService } from '../protocol/v2/V2ProofService' +import { ProofsModuleConfig } from '../ProofsModuleConfig' +import { V2ProofProtocol } from '../protocol/v2/V2ProofProtocol' import { ProofRepository } from '../repository' jest.mock('../../../plugins/DependencyManager') -const DependencyManagerMock = DependencyManager as jest.Mock +jest.mock('../../../agent/FeatureRegistry') +const DependencyManagerMock = DependencyManager as jest.Mock const dependencyManager = new DependencyManagerMock() - -jest.mock('../../../agent/FeatureRegistry') const FeatureRegistryMock = FeatureRegistry as jest.Mock - const featureRegistry = new FeatureRegistryMock() describe('ProofsModule', () => { test('registers dependencies on the dependency manager', () => { - new ProofsModule().register(dependencyManager, featureRegistry) + const proofsModule = new ProofsModule({ + proofProtocols: [], + }) + proofsModule.register(dependencyManager, featureRegistry) expect(dependencyManager.registerContextScoped).toHaveBeenCalledTimes(1) expect(dependencyManager.registerContextScoped).toHaveBeenCalledWith(ProofsApi) - expect(dependencyManager.registerSingleton).toHaveBeenCalledTimes(4) - expect(dependencyManager.registerSingleton).toHaveBeenCalledWith(V1ProofService) - expect(dependencyManager.registerSingleton).toHaveBeenCalledWith(V2ProofService) + expect(dependencyManager.registerInstance).toHaveBeenCalledTimes(1) + expect(dependencyManager.registerInstance).toHaveBeenCalledWith(ProofsModuleConfig, proofsModule.config) + + expect(dependencyManager.registerSingleton).toHaveBeenCalledTimes(1) expect(dependencyManager.registerSingleton).toHaveBeenCalledWith(ProofRepository) - expect(dependencyManager.registerSingleton).toHaveBeenCalledWith(IndyProofFormatService) + }) + + test('registers V2ProofProtocol if no proofProtocols are configured', () => { + const proofsModule = new ProofsModule() + + expect(proofsModule.config.proofProtocols).toEqual([expect.any(V2ProofProtocol)]) + }) + + test('calls register on the provided ProofProtocols', () => { + const registerMock = jest.fn() + const proofProtocol = { + register: registerMock, + } as unknown as ProofProtocol + + const proofsModule = new ProofsModule({ + proofProtocols: [proofProtocol], + }) + + expect(proofsModule.config.proofProtocols).toEqual([proofProtocol]) + + proofsModule.register(dependencyManager, featureRegistry) + + expect(registerMock).toHaveBeenCalledTimes(1) + expect(registerMock).toHaveBeenCalledWith(dependencyManager, featureRegistry) }) }) diff --git a/packages/core/src/modules/proofs/__tests__/ProofsModuleConfig.test.ts b/packages/core/src/modules/proofs/__tests__/ProofsModuleConfig.test.ts new file mode 100644 index 0000000000..35920a4f48 --- /dev/null +++ b/packages/core/src/modules/proofs/__tests__/ProofsModuleConfig.test.ts @@ -0,0 +1,26 @@ +import type { ProofProtocol } from '../protocol/ProofProtocol' + +import { ProofsModuleConfig } from '../ProofsModuleConfig' +import { AutoAcceptProof } from '../models' + +describe('ProofsModuleConfig', () => { + test('sets default values', () => { + const config = new ProofsModuleConfig({ + proofProtocols: [], + }) + + expect(config.autoAcceptProofs).toBe(AutoAcceptProof.Never) + expect(config.proofProtocols).toEqual([]) + }) + + test('sets values', () => { + const proofProtocol = jest.fn() as unknown as ProofProtocol + const config = new ProofsModuleConfig({ + autoAcceptProofs: AutoAcceptProof.Always, + proofProtocols: [proofProtocol], + }) + + expect(config.autoAcceptProofs).toBe(AutoAcceptProof.Always) + expect(config.proofProtocols).toEqual([proofProtocol]) + }) +}) diff --git a/packages/core/src/modules/proofs/__tests__/fixtures.ts b/packages/core/src/modules/proofs/__tests__/fixtures.ts deleted file mode 100644 index 10606073b8..0000000000 --- a/packages/core/src/modules/proofs/__tests__/fixtures.ts +++ /dev/null @@ -1,17 +0,0 @@ -export const credDef = { - ver: '1.0', - id: 'TL1EaPFCZ8Si5aUrqScBDt:3:CL:16:TAG', - schemaId: '16', - type: 'CL', - tag: 'TAG', - value: { - primary: { - n: '92498022445845202032348897620554299694896009176315493627722439892023558526259875239808280186111059586069456394012963552956574651629517633396592827947162983189649269173220440607665417484696688946624963596710652063849006738050417440697782608643095591808084344059908523401576738321329706597491345875134180790935098782801918369980296355919072827164363500681884641551147645504164254206270541724042784184712124576190438261715948768681331862924634233043594086219221089373455065715714369325926959533971768008691000560918594972006312159600845441063618991760512232714992293187779673708252226326233136573974603552763615191259713', - s: '10526250116244590830801226936689232818708299684432892622156345407187391699799320507237066062806731083222465421809988887959680863378202697458984451550048737847231343182195679453915452156726746705017249911605739136361885518044604626564286545453132948801604882107628140153824106426249153436206037648809856342458324897885659120708767794055147846459394129610878181859361616754832462886951623882371283575513182530118220334228417923423365966593298195040550255217053655606887026300020680355874881473255854564974899509540795154002250551880061649183753819902391970912501350100175974791776321455551753882483918632271326727061054', - r: [Object], - rctxt: - '46370806529776888197599056685386177334629311939451963919411093310852010284763705864375085256873240323432329015015526097014834809926159013231804170844321552080493355339505872140068998254185756917091385820365193200970156007391350745837300010513687490459142965515562285631984769068796922482977754955668569724352923519618227464510753980134744424528043503232724934196990461197793822566137436901258663918660818511283047475389958180983391173176526879694302021471636017119966755980327241734084462963412467297412455580500138233383229217300797768907396564522366006433982511590491966618857814545264741708965590546773466047139517', - z: '84153935869396527029518633753040092509512111365149323230260584738724940130382637900926220255597132853379358675015222072417404334537543844616589463419189203852221375511010886284448841979468767444910003114007224993233448170299654815710399828255375084265247114471334540928216537567325499206413940771681156686116516158907421215752364889506967984343660576422672840921988126699885304325384925457260272972771547695861942114712679509318179363715259460727275178310181122162544785290813713205047589943947592273130618286905125194410421355167030389500160371886870735704712739886223342214864760968555566496288314800410716250791012', - }, - }, -} diff --git a/packages/core/src/modules/proofs/__tests__/groupKeys.ts b/packages/core/src/modules/proofs/__tests__/groupKeys.ts deleted file mode 100644 index e20144792f..0000000000 --- a/packages/core/src/modules/proofs/__tests__/groupKeys.ts +++ /dev/null @@ -1,31 +0,0 @@ -import type { IndyProofFormat } from '../formats/indy/IndyProofFormat' -import type { GetFormatDataReturn } from '../models/ProofServiceOptions' - -import { AriesFrameworkError } from '../../../error' - -export function getGroupKeysFromIndyProofFormatData(formatData: GetFormatDataReturn<[IndyProofFormat]>): { - proposeKey1: string - proposeKey2: string - requestKey1: string - requestKey2: string -} { - const proofRequest = formatData.request?.indy - const proofProposal = formatData.proposal?.indy - if (!proofProposal) { - throw new AriesFrameworkError('missing indy proof proposal') - } - if (!proofRequest) { - throw new AriesFrameworkError('missing indy proof request') - } - const proposeAttributes = proofProposal.requested_attributes - const proposePredicates = proofProposal.requested_predicates - const requestAttributes = proofRequest.requested_attributes - const requestPredicates = proofRequest.requested_predicates - - const proposeKey1 = Object.keys(proposeAttributes)[1] - const proposeKey2 = Object.keys(proposePredicates)[0] - const requestKey1 = Object.keys(requestAttributes)[1] - const requestKey2 = Object.keys(requestPredicates)[0] - - return { proposeKey1, proposeKey2, requestKey1, requestKey2 } -} diff --git a/packages/core/src/modules/proofs/formats/ProofFormat.ts b/packages/core/src/modules/proofs/formats/ProofFormat.ts index 18fbba278d..c573c12579 100644 --- a/packages/core/src/modules/proofs/formats/ProofFormat.ts +++ b/packages/core/src/modules/proofs/formats/ProofFormat.ts @@ -21,21 +21,50 @@ export type ProofFormatPayload + * + * // equal to + * type SelectedCredentialsForRequest = { + * indy: { + * // ... return value for indy selected credentials ... + * }, + * presentationExchange: { + * // ... return value for presentation exchange selected credentials ... + * } + * } + * ``` + */ +export type ProofFormatCredentialForRequestPayload< + PFs extends ProofFormat[], + M extends 'selectCredentialsForRequest' | 'getCredentialsForRequest', + IO extends 'input' | 'output' +> = { + [ProofFormat in PFs[number] as ProofFormat['formatKey']]?: ProofFormat['proofFormats'][M][IO] +} + export interface ProofFormat { - formatKey: string // e.g. 'ProofManifest', cannot be shared between different formats + formatKey: string // e.g. 'presentationExchange', cannot be shared between different formats + proofFormats: { createProposal: unknown acceptProposal: unknown createRequest: unknown acceptRequest: unknown - createPresentation: unknown - acceptPresentation: unknown - createProposalAsResponse: unknown - createOutOfBandRequest: unknown - createRequestAsResponse: unknown - createProofRequestFromProposal: unknown - requestCredentials: unknown - retrieveCredentials: unknown + + getCredentialsForRequest: { + input: unknown + output: unknown + } + selectCredentialsForRequest: { + input: unknown + output: unknown + } } formatData: { proposal: unknown diff --git a/packages/core/src/modules/proofs/formats/ProofFormatConstants.ts b/packages/core/src/modules/proofs/formats/ProofFormatConstants.ts deleted file mode 100644 index 35e1ce33ab..0000000000 --- a/packages/core/src/modules/proofs/formats/ProofFormatConstants.ts +++ /dev/null @@ -1,4 +0,0 @@ -export const INDY_ATTACH_ID = 'indy' -export const V2_INDY_PRESENTATION_PROPOSAL = 'hlindy/proof-req@v2.0' -export const V2_INDY_PRESENTATION_REQUEST = 'hlindy/proof-req@v2.0' -export const V2_INDY_PRESENTATION = 'hlindy/proof@v2.0' diff --git a/packages/core/src/modules/proofs/formats/ProofFormatService.ts b/packages/core/src/modules/proofs/formats/ProofFormatService.ts index 1ce367cf33..f48db80624 100644 --- a/packages/core/src/modules/proofs/formats/ProofFormatService.ts +++ b/packages/core/src/modules/proofs/formats/ProofFormatService.ts @@ -1,79 +1,70 @@ -import type { AgentContext } from '../../../agent' -import type { AgentConfig } from '../../../agent/AgentConfig' -import type { DidCommMessageRepository } from '../../../storage' -import type { - CreateRequestAsResponseOptions, - FormatRequestedCredentialReturn, - FormatRetrievedCredentialOptions, -} from '../models/ProofServiceOptions' -import type { ProofRequestFormats } from '../models/SharedOptions' import type { ProofFormat } from './ProofFormat' -import type { IndyProofFormat } from './indy/IndyProofFormat' -import type { GetRequestedCredentialsFormat } from './indy/IndyProofFormatsServiceOptions' -import type { ProofAttachmentFormat } from './models/ProofAttachmentFormat' import type { - CreatePresentationFormatsOptions, - FormatCreateProofProposalOptions, - CreateRequestOptions, - FormatCreatePresentationOptions, - ProcessPresentationOptions, - ProcessProposalOptions, - ProcessRequestOptions, -} from './models/ProofFormatServiceOptions' - -/** - * This abstract class is the base class for any proof format - * specific service. - * - * @export - * @abstract - * @class ProofFormatService - */ -export abstract class ProofFormatService { - protected didCommMessageRepository: DidCommMessageRepository - protected agentConfig: AgentConfig - - abstract readonly formatKey: PF['formatKey'] - - public constructor(didCommMessageRepository: DidCommMessageRepository, agentConfig: AgentConfig) { - this.didCommMessageRepository = didCommMessageRepository - this.agentConfig = agentConfig - } - - abstract createProposal(options: FormatCreateProofProposalOptions): Promise - - abstract processProposal(options: ProcessProposalOptions): Promise - - abstract createRequest(options: CreateRequestOptions): Promise + ProofFormatAcceptProposalOptions, + ProofFormatAcceptRequestOptions, + ProofFormatCreateProposalOptions, + FormatCreateRequestOptions, + ProofFormatProcessPresentationOptions, + ProofFormatCreateReturn, + ProofFormatProcessOptions, + ProofFormatGetCredentialsForRequestOptions, + ProofFormatGetCredentialsForRequestReturn, + ProofFormatSelectCredentialsForRequestOptions, + ProofFormatSelectCredentialsForRequestReturn, + ProofFormatAutoRespondProposalOptions, + ProofFormatAutoRespondRequestOptions, + ProofFormatAutoRespondPresentationOptions, +} from './ProofFormatServiceOptions' +import type { AgentContext } from '../../../agent' - abstract processRequest(options: ProcessRequestOptions): Promise +export interface ProofFormatService { + formatKey: PF['formatKey'] - abstract createPresentation( + // proposal methods + createProposal( agentContext: AgentContext, - options: FormatCreatePresentationOptions - ): Promise - - abstract processPresentation(agentContext: AgentContext, options: ProcessPresentationOptions): Promise - - abstract createProofRequestFromProposal(options: CreatePresentationFormatsOptions): Promise + options: ProofFormatCreateProposalOptions + ): Promise + processProposal(agentContext: AgentContext, options: ProofFormatProcessOptions): Promise + acceptProposal( + agentContext: AgentContext, + options: ProofFormatAcceptProposalOptions + ): Promise - public abstract getRequestedCredentialsForProofRequest( + // request methods + createRequest(agentContext: AgentContext, options: FormatCreateRequestOptions): Promise + processRequest(agentContext: AgentContext, options: ProofFormatProcessOptions): Promise + acceptRequest( agentContext: AgentContext, - options: GetRequestedCredentialsFormat - ): Promise> + options: ProofFormatAcceptRequestOptions + ): Promise - public abstract autoSelectCredentialsForProofRequest( - options: FormatRetrievedCredentialOptions<[PF]> - ): Promise> + // presentation methods + processPresentation(agentContext: AgentContext, options: ProofFormatProcessPresentationOptions): Promise - abstract proposalAndRequestAreEqual( - proposalAttachments: ProofAttachmentFormat[], - requestAttachments: ProofAttachmentFormat[] - ): boolean + // credentials for request + getCredentialsForRequest( + agentContext: AgentContext, + options: ProofFormatGetCredentialsForRequestOptions + ): Promise> + selectCredentialsForRequest( + agentContext: AgentContext, + options: ProofFormatSelectCredentialsForRequestOptions + ): Promise> - abstract supportsFormat(formatIdentifier: string): boolean + // auto accept methods + shouldAutoRespondToProposal( + agentContext: AgentContext, + options: ProofFormatAutoRespondProposalOptions + ): Promise + shouldAutoRespondToRequest( + agentContext: AgentContext, + options: ProofFormatAutoRespondRequestOptions + ): Promise + shouldAutoRespondToPresentation( + agentContext: AgentContext, + options: ProofFormatAutoRespondPresentationOptions + ): Promise - abstract createRequestAsResponse( - options: CreateRequestAsResponseOptions<[IndyProofFormat]> - ): Promise + supportsFormat(formatIdentifier: string): boolean } diff --git a/packages/core/src/modules/proofs/formats/ProofFormatServiceOptions.ts b/packages/core/src/modules/proofs/formats/ProofFormatServiceOptions.ts index 41a98692d1..5a844bd44a 100644 --- a/packages/core/src/modules/proofs/formats/ProofFormatServiceOptions.ts +++ b/packages/core/src/modules/proofs/formats/ProofFormatServiceOptions.ts @@ -1,23 +1,35 @@ +import type { ProofFormat, ProofFormatCredentialForRequestPayload, ProofFormatPayload } from './ProofFormat' +import type { ProofFormatService } from './ProofFormatService' import type { V1Attachment } from '../../../decorators/attachment/V1Attachment' import type { ProofFormatSpec } from '../models/ProofFormatSpec' -import type { ProofFormat } from './ProofFormat' -import type { ProofFormatService } from './ProofFormatService' +import type { ProofExchangeRecord } from '../repository/ProofExchangeRecord' /** - * Get the service map for usage in the proofs module. Will return a type mapping of protocol version to service. + * Infer the {@link ProofFormat} based on a {@link ProofFormatService}. + * + * It does this by extracting the `ProofFormat` generic from the `ProofFormatService`. * * @example * ``` - * type FormatServiceMap = ProofFormatServiceMap<[IndyProofFormat]> + * // TheProofFormat is now equal to IndyProofFormat + * type TheProofFormat = ExtractProofFormat + * ``` * - * // equal to - * type FormatServiceMap = { - * indy: ProofFormatServiceMap + * Because the `IndyProofFormatService` is defined as follows: + * ``` + * class IndyProofFormatService implements ProofFormatService { * } * ``` */ -export type ProofFormatServiceMap = { - [PF in PFs[number] as PF['formatKey']]: ProofFormatService +export type ExtractProofFormat = Type extends ProofFormatService ? ProofFormat : never + +/** + * Infer an array of {@link ProofFormat} types based on an array of {@link ProofFormatService} types. + * + * This is based on {@link ExtractProofFormat}, but allows to handle arrays. + */ +export type ExtractProofFormats = { + [PF in keyof PFs]: ExtractProofFormat } /** @@ -29,3 +41,85 @@ export interface ProofFormatCreateReturn { format: ProofFormatSpec attachment: V1Attachment } + +/** + * Base type for all proof process methods. + */ +export interface ProofFormatProcessOptions { + attachment: V1Attachment + proofRecord: ProofExchangeRecord +} + +export interface ProofFormatProcessPresentationOptions extends ProofFormatProcessOptions { + requestAttachment: V1Attachment +} + +export interface ProofFormatCreateProposalOptions { + proofRecord: ProofExchangeRecord + proofFormats: ProofFormatPayload<[PF], 'createProposal'> + attachmentId?: string +} + +export interface ProofFormatAcceptProposalOptions { + proofRecord: ProofExchangeRecord + proofFormats?: ProofFormatPayload<[PF], 'acceptProposal'> + attachmentId?: string + + proposalAttachment: V1Attachment +} + +export interface FormatCreateRequestOptions { + proofRecord: ProofExchangeRecord + proofFormats: ProofFormatPayload<[PF], 'createRequest'> + attachmentId?: string +} + +export interface ProofFormatAcceptRequestOptions { + proofRecord: ProofExchangeRecord + proofFormats?: ProofFormatPayload<[PF], 'acceptRequest'> + attachmentId?: string + + requestAttachment: V1Attachment + proposalAttachment?: V1Attachment +} + +export interface ProofFormatGetCredentialsForRequestOptions { + proofRecord: ProofExchangeRecord + proofFormats?: ProofFormatCredentialForRequestPayload<[PF], 'getCredentialsForRequest', 'input'> + + requestAttachment: V1Attachment + proposalAttachment?: V1Attachment +} + +export type ProofFormatGetCredentialsForRequestReturn = + PF['proofFormats']['getCredentialsForRequest']['output'] + +export interface ProofFormatSelectCredentialsForRequestOptions { + proofRecord: ProofExchangeRecord + proofFormats?: ProofFormatCredentialForRequestPayload<[PF], 'selectCredentialsForRequest', 'input'> + + requestAttachment: V1Attachment + proposalAttachment?: V1Attachment +} + +export type ProofFormatSelectCredentialsForRequestReturn = + PF['proofFormats']['selectCredentialsForRequest']['output'] + +export interface ProofFormatAutoRespondProposalOptions { + proofRecord: ProofExchangeRecord + proposalAttachment: V1Attachment + requestAttachment: V1Attachment +} + +export interface ProofFormatAutoRespondRequestOptions { + proofRecord: ProofExchangeRecord + requestAttachment: V1Attachment + proposalAttachment: V1Attachment +} + +export interface ProofFormatAutoRespondPresentationOptions { + proofRecord: ProofExchangeRecord + proposalAttachment?: V1Attachment + requestAttachment: V1Attachment + presentationAttachment: V1Attachment +} diff --git a/packages/core/src/modules/proofs/formats/index.ts b/packages/core/src/modules/proofs/formats/index.ts index efb4e8a6ab..a28e77d623 100644 --- a/packages/core/src/modules/proofs/formats/index.ts +++ b/packages/core/src/modules/proofs/formats/index.ts @@ -1,6 +1,7 @@ -export * from './indy' -export * from './models' export * from './ProofFormat' -export * from './ProofFormatConstants' export * from './ProofFormatService' export * from './ProofFormatServiceOptions' + +import * as ProofFormatServiceOptions from './ProofFormatServiceOptions' + +export { ProofFormatServiceOptions } diff --git a/packages/core/src/modules/proofs/formats/indy/IndyProofFormat.ts b/packages/core/src/modules/proofs/formats/indy/IndyProofFormat.ts deleted file mode 100644 index 8d6769be1e..0000000000 --- a/packages/core/src/modules/proofs/formats/indy/IndyProofFormat.ts +++ /dev/null @@ -1,51 +0,0 @@ -import type { PresentationPreviewAttribute, PresentationPreviewPredicate } from '../../protocol/v1' -import type { ProofFormat } from '../ProofFormat' -import type { IndyRequestProofFormat } from '../indy/IndyProofFormatsServiceOptions' -import type { RequestedAttribute } from './models/RequestedAttribute' -import type { IndyRequestedCredentialsOptions } from './models/RequestedCredentials' -import type { RequestedPredicate } from './models/RequestedPredicate' -import type { IndyProof, IndyProofRequest } from 'indy-sdk' - -export interface IndyProposeProofFormat { - attributes?: PresentationPreviewAttribute[] - predicates?: PresentationPreviewPredicate[] - nonce?: string - name?: string - version?: string -} - -export interface IndyRequestedCredentialsFormat { - requestedAttributes: Record - requestedPredicates: Record - selfAttestedAttributes: Record -} - -export interface IndyRetrievedCredentialsFormat { - requestedAttributes: Record - requestedPredicates: Record -} - -export interface IndyProofFormat extends ProofFormat { - formatKey: 'indy' - proofRecordType: 'indy' - proofFormats: { - createProposal: IndyProposeProofFormat - acceptProposal: unknown - createRequest: IndyRequestProofFormat - acceptRequest: unknown - createPresentation: IndyRequestedCredentialsOptions - acceptPresentation: unknown - createProposalAsResponse: IndyProposeProofFormat - createOutOfBandRequest: unknown - createRequestAsResponse: IndyRequestProofFormat - createProofRequestFromProposal: IndyRequestProofFormat - requestCredentials: IndyRequestedCredentialsFormat - retrieveCredentials: IndyRetrievedCredentialsFormat - } - - formatData: { - proposal: IndyProofRequest - request: IndyProofRequest - presentation: IndyProof - } -} diff --git a/packages/core/src/modules/proofs/formats/indy/IndyProofFormatService.ts b/packages/core/src/modules/proofs/formats/indy/IndyProofFormatService.ts deleted file mode 100644 index fb535b8b2e..0000000000 --- a/packages/core/src/modules/proofs/formats/indy/IndyProofFormatService.ts +++ /dev/null @@ -1,744 +0,0 @@ -import type { AgentContext } from '../../../../agent' -import type { Logger } from '../../../../logger' -import type { - CreateRequestAsResponseOptions, - FormatRequestedCredentialReturn, - FormatRetrievedCredentialOptions, -} from '../../models/ProofServiceOptions' -import type { ProofRequestFormats } from '../../models/SharedOptions' -import type { PresentationPreviewAttribute } from '../../protocol/v1/models' -import type { ProofAttachmentFormat } from '../models/ProofAttachmentFormat' -import type { - CreatePresentationFormatsOptions, - CreateProofAttachmentOptions, - FormatCreateProofProposalOptions, - CreateRequestAttachmentOptions, - CreateRequestOptions, - FormatCreatePresentationOptions, - ProcessPresentationOptions, - ProcessProposalOptions, - ProcessRequestOptions, - VerifyProofOptions, -} from '../models/ProofFormatServiceOptions' -import type { IndyProofFormat, IndyProposeProofFormat } from './IndyProofFormat' -import type { GetRequestedCredentialsFormat } from './IndyProofFormatsServiceOptions' -import type { CredDef, IndyProof, Schema } from 'indy-sdk' - -import { Lifecycle, scoped } from 'tsyringe' - -import { AgentConfig } from '../../../../agent/AgentConfig' -import { V1Attachment, V1AttachmentData } from '../../../../decorators/attachment/V1Attachment' -import { AriesFrameworkError } from '../../../../error/AriesFrameworkError' -import { ConsoleLogger, LogLevel } from '../../../../logger' -import { DidCommMessageRepository } from '../../../../storage/didcomm/DidCommMessageRepository' -import { checkProofRequestForDuplicates, deepEquality } from '../../../../utils' -import { JsonEncoder } from '../../../../utils/JsonEncoder' -import { JsonTransformer } from '../../../../utils/JsonTransformer' -import { MessageValidator } from '../../../../utils/MessageValidator' -import { uuid } from '../../../../utils/uuid' -import { IndyWallet } from '../../../../wallet/IndyWallet' -import { IndyCredential, IndyCredentialInfo } from '../../../credentials' -import { IndyCredentialUtils } from '../../../credentials/formats/indy/IndyCredentialUtils' -import { IndyHolderService, IndyVerifierService, IndyRevocationService } from '../../../indy' -import { IndyLedgerService } from '../../../ledger' -import { ProofFormatSpec } from '../../models/ProofFormatSpec' -import { PartialProof, PresentationPreview } from '../../protocol/v1/models' -import { - V2_INDY_PRESENTATION_REQUEST, - V2_INDY_PRESENTATION_PROPOSAL, - V2_INDY_PRESENTATION, -} from '../ProofFormatConstants' -import { ProofFormatService } from '../ProofFormatService' - -import { InvalidEncodedValueError } from './errors/InvalidEncodedValueError' -import { MissingIndyProofMessageError } from './errors/MissingIndyProofMessageError' -import { - AttributeFilter, - ProofAttributeInfo, - ProofPredicateInfo, - RequestedAttribute, - RequestedPredicate, -} from './models' -import { ProofRequest } from './models/ProofRequest' -import { RequestedCredentials } from './models/RequestedCredentials' -import { RetrievedCredentials } from './models/RetrievedCredentials' - -@scoped(Lifecycle.ContainerScoped) -export class IndyProofFormatService extends ProofFormatService { - private indyHolderService: IndyHolderService - private indyVerifierService: IndyVerifierService - private indyRevocationService: IndyRevocationService - private ledgerService: IndyLedgerService - private logger: Logger - private wallet: IndyWallet - - public constructor( - agentConfig: AgentConfig, - indyHolderService: IndyHolderService, - indyVerifierService: IndyVerifierService, - indyRevocationService: IndyRevocationService, - ledgerService: IndyLedgerService, - didCommMessageRepository: DidCommMessageRepository, - wallet: IndyWallet - ) { - super(didCommMessageRepository, agentConfig) - this.indyHolderService = indyHolderService - this.indyVerifierService = indyVerifierService - this.indyRevocationService = indyRevocationService - this.ledgerService = ledgerService - this.wallet = wallet - this.logger = new ConsoleLogger(LogLevel.off) - } - public readonly formatKey = 'indy' as const - public readonly proofRecordType = 'indy' as const - - private createRequestAttachment(options: CreateRequestAttachmentOptions): ProofAttachmentFormat { - const format = new ProofFormatSpec({ - attachmentId: options.id, - format: V2_INDY_PRESENTATION_REQUEST, - }) - - const request = new ProofRequest(options.proofRequestOptions) - - // Assert attribute and predicate (group) names do not match - checkProofRequestForDuplicates(request) - - const attachment = new V1Attachment({ - id: options.id, - mimeType: 'application/json', - data: new V1AttachmentData({ - base64: JsonEncoder.toBase64(request), - }), - }) - return { format, attachment } - } - - private async createProofAttachment(options: CreateProofAttachmentOptions): Promise { - const format = new ProofFormatSpec({ - attachmentId: options.id, - format: V2_INDY_PRESENTATION_PROPOSAL, - }) - - const request = new ProofRequest(options.proofProposalOptions) - MessageValidator.validateSync(request) - - const attachment = new V1Attachment({ - id: options.id, - mimeType: 'application/json', - data: new V1AttachmentData({ - base64: JsonEncoder.toBase64(JsonTransformer.toJSON(request)), - }), - }) - return { format, attachment } - } - - public async createProposal(options: FormatCreateProofProposalOptions): Promise { - if (!options.formats.indy) { - throw Error('Missing indy format to create proposal attachment format') - } - const proofRequest = await this.createRequestFromPreview(options.formats.indy) - - return await this.createProofAttachment({ - id: options.id ?? uuid(), - proofProposalOptions: proofRequest, - }) - } - - public async processProposal(options: ProcessProposalOptions): Promise { - const proofProposalJson = options.proposal.attachment.getDataAsJson() - - // Assert attachment - if (!proofProposalJson) { - throw new AriesFrameworkError( - `Missing required base64 or json encoded attachment data for presentation proposal with thread id ${options.record?.threadId}` - ) - } - - const proposalMessage = JsonTransformer.fromJSON(proofProposalJson, ProofRequest) - - MessageValidator.validateSync(proposalMessage) - } - - public async createRequestAsResponse( - options: CreateRequestAsResponseOptions<[IndyProofFormat]> - ): Promise { - if (!options.proofFormats.indy) { - throw Error('Missing indy format to create proposal attachment format') - } - - const id = options.id ?? uuid() - - const format = new ProofFormatSpec({ - attachmentId: id, - format: V2_INDY_PRESENTATION_REQUEST, - }) - - const attachment = new V1Attachment({ - id: id, - mimeType: 'application/json', - data: new V1AttachmentData({ - base64: JsonEncoder.toBase64(options.proofFormats.indy), - }), - }) - return { format, attachment } - } - - public async createRequest(options: CreateRequestOptions): Promise { - if (!options.formats.indy) { - throw new AriesFrameworkError('Missing indy format to create proof request attachment format.') - } - - const indyFormat = options.formats.indy - - return this.createRequestAttachment({ - id: options.id ?? uuid(), - proofRequestOptions: { - ...indyFormat, - name: indyFormat.name ?? 'proof-request', - version: indyFormat.version ?? '1.0', - nonce: indyFormat.nonce ?? (await this.wallet.generateNonce()), - }, - }) - } - - public async processRequest(options: ProcessRequestOptions): Promise { - const proofRequestJson = options.requestAttachment.attachment.getDataAsJson() - - const proofRequest = JsonTransformer.fromJSON(proofRequestJson, ProofRequest) - - // Assert attachment - if (!proofRequest) { - throw new AriesFrameworkError( - `Missing required base64 or json encoded attachment data for presentation request with thread id ${options.record?.threadId}` - ) - } - MessageValidator.validateSync(proofRequest) - - // Assert attribute and predicate (group) names do not match - checkProofRequestForDuplicates(proofRequest) - } - - public async createPresentation( - agentContext: AgentContext, - options: FormatCreatePresentationOptions - ): Promise { - // Extract proof request from attachment - const proofRequestJson = options.attachment.getDataAsJson() ?? null - const proofRequest = JsonTransformer.fromJSON(proofRequestJson, ProofRequest) - - // verify everything is there - if (!options.proofFormats.indy) { - throw new AriesFrameworkError('Missing indy format to create proof presentation attachment format.') - } - - const requestedCredentials = new RequestedCredentials({ - requestedAttributes: options.proofFormats.indy.requestedAttributes, - requestedPredicates: options.proofFormats.indy.requestedPredicates, - selfAttestedAttributes: options.proofFormats.indy.selfAttestedAttributes, - }) - - const proof = await this.createProof(agentContext, proofRequest, requestedCredentials) - - const attachmentId = options.id ?? uuid() - - const format = new ProofFormatSpec({ - attachmentId, - format: V2_INDY_PRESENTATION, - }) - - const attachment = new V1Attachment({ - id: attachmentId, - mimeType: 'application/json', - data: new V1AttachmentData({ - base64: JsonEncoder.toBase64(proof), - }), - }) - return { format, attachment } - } - - public async processPresentation(agentContext: AgentContext, options: ProcessPresentationOptions): Promise { - const requestFormat = options.formatAttachments.request.find( - (x) => x.format.format === V2_INDY_PRESENTATION_REQUEST - ) - - if (!requestFormat) { - throw new MissingIndyProofMessageError( - 'Missing Indy Proof Request format while trying to process an Indy proof presentation.' - ) - } - - const proofFormat = options.formatAttachments.presentation.find((x) => x.format.format === V2_INDY_PRESENTATION) - - if (!proofFormat) { - throw new MissingIndyProofMessageError( - 'Missing Indy Proof Presentation format while trying to process an Indy proof presentation.' - ) - } - - return await this.verifyProof(agentContext, { request: requestFormat.attachment, proof: proofFormat.attachment }) - } - - public async verifyProof(agentContext: AgentContext, options: VerifyProofOptions): Promise { - if (!options) { - throw new AriesFrameworkError('No Indy proof was provided.') - } - const proofRequestJson = options.request.getDataAsJson() ?? null - const proofRequest = JsonTransformer.fromJSON(proofRequestJson, ProofRequest) - - const proofJson = options.proof.getDataAsJson() ?? null - - const proof = JsonTransformer.fromJSON(proofJson, PartialProof) - - for (const [referent, attribute] of proof.requestedProof.revealedAttributes.entries()) { - if (!IndyCredentialUtils.checkValidEncoding(attribute.raw, attribute.encoded)) { - throw new InvalidEncodedValueError( - `The encoded value for '${referent}' is invalid. ` + - `Expected '${IndyCredentialUtils.encode(attribute.raw)}'. ` + - `Actual '${attribute.encoded}'` - ) - } - } - - // TODO: pre verify proof json - // I'm not 100% sure how much indy does. Also if it checks whether the proof requests matches the proof - // @see https://github.com/hyperledger/aries-cloudagent-python/blob/master/aries_cloudagent/indy/sdk/verifier.py#L79-L164 - - const schemas = await this.getSchemas(agentContext, new Set(proof.identifiers.map((i) => i.schemaId))) - const credentialDefinitions = await this.getCredentialDefinitions( - agentContext, - new Set(proof.identifiers.map((i) => i.credentialDefinitionId)) - ) - - return await this.indyVerifierService.verifyProof(agentContext, { - proofRequest: proofRequest.toJSON(), - proof: proofJson, - schemas, - credentialDefinitions, - }) - } - - public supportsFormat(formatIdentifier: string): boolean { - const supportedFormats = [V2_INDY_PRESENTATION_PROPOSAL, V2_INDY_PRESENTATION_REQUEST, V2_INDY_PRESENTATION] - return supportedFormats.includes(formatIdentifier) - } - - /** - * Compare presentation attrs with request/proposal attrs (auto-accept) - * - * @param proposalAttachments attachment data from the proposal - * @param requestAttachments attachment data from the request - * @returns boolean value - */ - public proposalAndRequestAreEqual( - proposalAttachments: ProofAttachmentFormat[], - requestAttachments: ProofAttachmentFormat[] - ) { - const proposalAttachment = proposalAttachments.find( - (x) => x.format.format === V2_INDY_PRESENTATION_PROPOSAL - )?.attachment - const requestAttachment = requestAttachments.find( - (x) => x.format.format === V2_INDY_PRESENTATION_REQUEST - )?.attachment - - if (!proposalAttachment) { - throw new AriesFrameworkError('Proposal message has no attachment linked to it') - } - - if (!requestAttachment) { - throw new AriesFrameworkError('Request message has no attachment linked to it') - } - - const proposalAttachmentJson = proposalAttachment.getDataAsJson() - const proposalAttachmentData = JsonTransformer.fromJSON(proposalAttachmentJson, ProofRequest) - - const requestAttachmentJson = requestAttachment.getDataAsJson() - const requestAttachmentData = JsonTransformer.fromJSON(requestAttachmentJson, ProofRequest) - - if ( - deepEquality(proposalAttachmentData.requestedAttributes, requestAttachmentData.requestedAttributes) && - deepEquality(proposalAttachmentData.requestedPredicates, requestAttachmentData.requestedPredicates) - ) { - return true - } - - return false - } - - /** - * Build credential definitions object needed to create and verify proof objects. - * - * Creates object with `{ credentialDefinitionId: CredentialDefinition }` mapping - * - * @param credentialDefinitionIds List of credential definition ids - * @returns Object containing credential definitions for specified credential definition ids - * - */ - private async getCredentialDefinitions(agentContext: AgentContext, credentialDefinitionIds: Set) { - const credentialDefinitions: { [key: string]: CredDef } = {} - - for (const credDefId of credentialDefinitionIds) { - const credDef = await this.ledgerService.getCredentialDefinition(agentContext, credDefId) - credentialDefinitions[credDefId] = credDef - } - - return credentialDefinitions - } - - public async getRequestedCredentialsForProofRequest( - agentContext: AgentContext, - options: GetRequestedCredentialsFormat - ): Promise> { - const retrievedCredentials = new RetrievedCredentials({}) - const { attachment, presentationProposal } = options - const filterByNonRevocationRequirements = options.config?.filterByNonRevocationRequirements - - const proofRequestJson = attachment.getDataAsJson() ?? null - const proofRequest = JsonTransformer.fromJSON(proofRequestJson, ProofRequest) - - for (const [referent, requestedAttribute] of proofRequest.requestedAttributes.entries()) { - let credentialMatch: IndyCredential[] = [] - const credentials = await this.getCredentialsForProofRequest(agentContext, proofRequest, referent) - - // If we have exactly one credential, or no proposal to pick preferences - // on the credentials to use, we will use the first one - if (credentials.length === 1 || !presentationProposal) { - credentialMatch = credentials - } - // If we have a proposal we will use that to determine the credentials to use - else { - const names = requestedAttribute.names ?? [requestedAttribute.name] - - // Find credentials that matches all parameters from the proposal - credentialMatch = credentials.filter((credential) => { - const { attributes, credentialDefinitionId } = credential.credentialInfo - - // Check if credentials matches all parameters from proposal - return names.every((name) => - presentationProposal.attributes.find( - (a) => - a.name === name && - a.credentialDefinitionId === credentialDefinitionId && - (!a.value || a.value === attributes[name]) - ) - ) - }) - } - - retrievedCredentials.requestedAttributes[referent] = await Promise.all( - credentialMatch.map(async (credential: IndyCredential) => { - const { revoked, deltaTimestamp } = await this.getRevocationStatusForRequestedItem(agentContext, { - proofRequest, - requestedItem: requestedAttribute, - credential, - }) - - return new RequestedAttribute({ - credentialId: credential.credentialInfo.referent, - revealed: true, - credentialInfo: credential.credentialInfo, - timestamp: deltaTimestamp, - revoked, - }) - }) - ) - - // We only attach revoked state if non-revocation is requested. So if revoked is true it means - // the credential is not applicable to the proof request - if (filterByNonRevocationRequirements) { - retrievedCredentials.requestedAttributes[referent] = retrievedCredentials.requestedAttributes[referent].filter( - (r) => !r.revoked - ) - } - } - - for (const [referent, requestedPredicate] of proofRequest.requestedPredicates.entries()) { - const credentials = await this.getCredentialsForProofRequest(agentContext, proofRequest, referent) - - retrievedCredentials.requestedPredicates[referent] = await Promise.all( - credentials.map(async (credential) => { - const { revoked, deltaTimestamp } = await this.getRevocationStatusForRequestedItem(agentContext, { - proofRequest, - requestedItem: requestedPredicate, - credential, - }) - - return new RequestedPredicate({ - credentialId: credential.credentialInfo.referent, - credentialInfo: credential.credentialInfo, - timestamp: deltaTimestamp, - revoked, - }) - }) - ) - - // We only attach revoked state if non-revocation is requested. So if revoked is true it means - // the credential is not applicable to the proof request - if (filterByNonRevocationRequirements) { - retrievedCredentials.requestedPredicates[referent] = retrievedCredentials.requestedPredicates[referent].filter( - (r) => !r.revoked - ) - } - } - - return { - proofFormats: { - indy: retrievedCredentials, - }, - } - } - - private async getCredentialsForProofRequest( - agentContext: AgentContext, - proofRequest: ProofRequest, - attributeReferent: string - ): Promise { - const credentialsJson = await this.indyHolderService.getCredentialsForProofRequest(agentContext, { - proofRequest: proofRequest.toJSON(), - attributeReferent, - }) - - return JsonTransformer.fromJSON(credentialsJson, IndyCredential) as unknown as IndyCredential[] - } - - public async autoSelectCredentialsForProofRequest( - options: FormatRetrievedCredentialOptions<[IndyProofFormat]> - ): Promise> { - const { proofFormats } = options - const indy = proofFormats.indy - - if (!indy) { - throw new AriesFrameworkError('No indy options provided') - } - - const requestedCredentials = new RequestedCredentials({}) - - Object.keys(indy.requestedAttributes).forEach((attributeName) => { - const attributeArray = indy.requestedAttributes[attributeName] - - if (attributeArray.length === 0) { - throw new AriesFrameworkError('Unable to automatically select requested attributes.') - } else { - requestedCredentials.requestedAttributes[attributeName] = attributeArray[0] - } - }) - - Object.keys(indy.requestedPredicates).forEach((attributeName) => { - if (indy.requestedPredicates[attributeName].length === 0) { - throw new AriesFrameworkError('Unable to automatically select requested predicates.') - } else { - requestedCredentials.requestedPredicates[attributeName] = indy.requestedPredicates[attributeName][0] - } - }) - - return { - proofFormats: { - indy: requestedCredentials, - }, - } - } - - /** - * Build schemas object needed to create and verify proof objects. - * - * Creates object with `{ schemaId: Schema }` mapping - * - * @param schemaIds List of schema ids - * @returns Object containing schemas for specified schema ids - * - */ - private async getSchemas(agentContext: AgentContext, schemaIds: Set) { - const schemas: { [key: string]: Schema } = {} - - for (const schemaId of schemaIds) { - const schema = await this.ledgerService.getSchema(agentContext, schemaId) - schemas[schemaId] = schema - } - - return schemas - } - - /** - * Create indy proof from a given proof request and requested credential object. - * - * @param proofRequest The proof request to create the proof for - * @param requestedCredentials The requested credentials object specifying which credentials to use for the proof - * @returns indy proof object - */ - private async createProof( - agentContext: AgentContext, - proofRequest: ProofRequest, - requestedCredentials: RequestedCredentials - ): Promise { - const credentialObjects = await Promise.all( - [ - ...Object.values(requestedCredentials.requestedAttributes), - ...Object.values(requestedCredentials.requestedPredicates), - ].map(async (c) => { - if (c.credentialInfo) { - return c.credentialInfo - } - const credentialInfo = await this.indyHolderService.getCredential(agentContext, c.credentialId) - return JsonTransformer.fromJSON(credentialInfo, IndyCredentialInfo) - }) - ) - - const schemas = await this.getSchemas(agentContext, new Set(credentialObjects.map((c) => c.schemaId))) - const credentialDefinitions = await this.getCredentialDefinitions( - agentContext, - new Set(credentialObjects.map((c) => c.credentialDefinitionId)) - ) - - return await this.indyHolderService.createProof(agentContext, { - proofRequest: proofRequest.toJSON(), - requestedCredentials: requestedCredentials, - schemas, - credentialDefinitions, - }) - } - - public async createProofRequestFromProposal(options: CreatePresentationFormatsOptions): Promise { - const proofRequestJson = options.presentationAttachment.getDataAsJson() - - const proofRequest = JsonTransformer.fromJSON(proofRequestJson, ProofRequest) - - // Assert attachment - if (!proofRequest) { - throw new AriesFrameworkError(`Missing required base64 or json encoded attachment data for presentation request.`) - } - MessageValidator.validateSync(proofRequest) - - // Assert attribute and predicate (group) names do not match - checkProofRequestForDuplicates(proofRequest) - - return { - indy: proofRequest, - } - } - - private async getRevocationStatusForRequestedItem( - agentContext: AgentContext, - { - proofRequest, - requestedItem, - credential, - }: { - proofRequest: ProofRequest - requestedItem: ProofAttributeInfo | ProofPredicateInfo - credential: IndyCredential - } - ) { - const requestNonRevoked = requestedItem.nonRevoked ?? proofRequest.nonRevoked - const credentialRevocationId = credential.credentialInfo.credentialRevocationId - const revocationRegistryId = credential.credentialInfo.revocationRegistryId - - // If revocation interval is present and the credential is revocable then fetch the revocation status of credentials for display - if (requestNonRevoked && credentialRevocationId && revocationRegistryId) { - this.logger.trace( - `Presentation is requesting proof of non revocation, getting revocation status for credential`, - { - requestNonRevoked, - credentialRevocationId, - revocationRegistryId, - } - ) - - // Note presentation from-to's vs ledger from-to's: https://github.com/hyperledger/indy-hipe/blob/master/text/0011-cred-revocation/README.md#indy-node-revocation-registry-intervals - const status = await this.indyRevocationService.getRevocationStatus( - agentContext, - credentialRevocationId, - revocationRegistryId, - requestNonRevoked - ) - - return status - } - - return { revoked: undefined, deltaTimestamp: undefined } - } - - public async createRequestFromPreview(indyFormat: IndyProposeProofFormat): Promise { - const preview = new PresentationPreview({ - attributes: indyFormat.attributes, - predicates: indyFormat.predicates, - }) - - const proofRequest = await this.createReferentForProofRequest(indyFormat, preview) - - return proofRequest - } - - public async createReferentForProofRequest( - indyFormat: IndyProposeProofFormat, - preview: PresentationPreview - ): Promise { - const proofRequest = new ProofRequest({ - name: indyFormat.name ?? 'proof-request', - version: indyFormat.version ?? '1.0', - nonce: indyFormat.nonce ?? (await this.wallet.generateNonce()), - }) - - /** - * Create mapping of attributes by referent. This required the - * attributes to come from the same credential. - * @see https://github.com/hyperledger/aries-rfcs/blob/master/features/0037-present-proof/README.md#referent - * - * { - * "referent1": [Attribute1, Attribute2], - * "referent2": [Attribute3] - * } - */ - const attributesByReferent: Record = {} - for (const proposedAttributes of preview.attributes) { - if (!proposedAttributes.referent) proposedAttributes.referent = uuid() - - const referentAttributes = attributesByReferent[proposedAttributes.referent] - - // Referent key already exist, add to list - if (referentAttributes) { - referentAttributes.push(proposedAttributes) - } - - // Referent key does not exist yet, create new entry - else { - attributesByReferent[proposedAttributes.referent] = [proposedAttributes] - } - } - - // Transform attributes by referent to requested attributes - for (const [referent, proposedAttributes] of Object.entries(attributesByReferent)) { - // Either attributeName or attributeNames will be undefined - const attributeName = proposedAttributes.length == 1 ? proposedAttributes[0].name : undefined - const attributeNames = proposedAttributes.length > 1 ? proposedAttributes.map((a) => a.name) : undefined - - const requestedAttribute = new ProofAttributeInfo({ - name: attributeName, - names: attributeNames, - restrictions: [ - new AttributeFilter({ - credentialDefinitionId: proposedAttributes[0].credentialDefinitionId, - }), - ], - }) - - proofRequest.requestedAttributes.set(referent, requestedAttribute) - } - - // Transform proposed predicates to requested predicates - for (const proposedPredicate of preview.predicates) { - const requestedPredicate = new ProofPredicateInfo({ - name: proposedPredicate.name, - predicateType: proposedPredicate.predicate, - predicateValue: proposedPredicate.threshold, - restrictions: [ - new AttributeFilter({ - credentialDefinitionId: proposedPredicate.credentialDefinitionId, - }), - ], - }) - - proofRequest.requestedPredicates.set(uuid(), requestedPredicate) - } - - return proofRequest - } -} diff --git a/packages/core/src/modules/proofs/formats/indy/IndyProofFormatsServiceOptions.ts b/packages/core/src/modules/proofs/formats/indy/IndyProofFormatsServiceOptions.ts deleted file mode 100644 index b9b4be8d84..0000000000 --- a/packages/core/src/modules/proofs/formats/indy/IndyProofFormatsServiceOptions.ts +++ /dev/null @@ -1,38 +0,0 @@ -import type { V1Attachment } from '../../../../decorators/attachment/V1Attachment' -import type { IndyRevocationInterval } from '../../../credentials' -import type { GetRequestedCredentialsConfig } from '../../models/GetRequestedCredentialsConfig' -import type { PresentationPreview } from '../../protocol/v1/models/V1PresentationPreview' -import type { ProofExchangeRecord } from '../../repository/ProofExchangeRecord' -import type { ProofAttributeInfo } from '.././indy/models/ProofAttributeInfo' -import type { ProofPredicateInfo } from '.././indy/models/ProofPredicateInfo' -import type { IndyRequestedCredentialsFormat } from './IndyProofFormat' - -export type IndyPresentationProofFormat = IndyRequestedCredentialsFormat - -export interface IndyRequestProofFormat { - name?: string - version?: string - nonce?: string - nonRevoked?: IndyRevocationInterval - ver?: '1.0' | '2.0' - requestedAttributes?: Record | Map - requestedPredicates?: Record | Map -} - -export interface IndyVerifyProofFormat { - proofJson: V1Attachment - proofRequest: V1Attachment -} - -export interface GetRequestedCredentialsFormat { - attachment: V1Attachment - presentationProposal?: PresentationPreview - config?: GetRequestedCredentialsConfig -} - -export interface IndyProofRequestFromProposalOptions { - proofRecord: ProofExchangeRecord - name?: string - version?: string - nonce?: string -} diff --git a/packages/core/src/modules/proofs/formats/indy/errors/InvalidEncodedValueError.ts b/packages/core/src/modules/proofs/formats/indy/errors/InvalidEncodedValueError.ts deleted file mode 100644 index 84ac6e1385..0000000000 --- a/packages/core/src/modules/proofs/formats/indy/errors/InvalidEncodedValueError.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { AriesFrameworkError } from '../../../../../error/AriesFrameworkError' - -export class InvalidEncodedValueError extends AriesFrameworkError {} diff --git a/packages/core/src/modules/proofs/formats/indy/errors/MissingIndyProofMessageError.ts b/packages/core/src/modules/proofs/formats/indy/errors/MissingIndyProofMessageError.ts deleted file mode 100644 index 2ab9c3f15e..0000000000 --- a/packages/core/src/modules/proofs/formats/indy/errors/MissingIndyProofMessageError.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { AriesFrameworkError } from '../../../../../error/AriesFrameworkError' - -export class MissingIndyProofMessageError extends AriesFrameworkError {} diff --git a/packages/core/src/modules/proofs/formats/indy/errors/index.ts b/packages/core/src/modules/proofs/formats/indy/errors/index.ts deleted file mode 100644 index 0f0b302726..0000000000 --- a/packages/core/src/modules/proofs/formats/indy/errors/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from './InvalidEncodedValueError' -export * from './MissingIndyProofMessageError' diff --git a/packages/core/src/modules/proofs/formats/indy/index.ts b/packages/core/src/modules/proofs/formats/indy/index.ts deleted file mode 100644 index c94afb8629..0000000000 --- a/packages/core/src/modules/proofs/formats/indy/index.ts +++ /dev/null @@ -1,4 +0,0 @@ -export * from './errors' -export * from './models' -export * from './IndyProofFormat' -export * from './IndyProofFormatsServiceOptions' diff --git a/packages/core/src/modules/proofs/formats/indy/models/AttributeFilter.ts b/packages/core/src/modules/proofs/formats/indy/models/AttributeFilter.ts deleted file mode 100644 index b2a804ab2d..0000000000 --- a/packages/core/src/modules/proofs/formats/indy/models/AttributeFilter.ts +++ /dev/null @@ -1,145 +0,0 @@ -import { Expose, Transform, TransformationType, Type } from 'class-transformer' -import { IsInstance, IsOptional, IsString, Matches, ValidateNested } from 'class-validator' - -import { credDefIdRegex, indyDidRegex, schemaIdRegex, schemaVersionRegex } from '../../../../../utils/regex' - -export class AttributeValue { - public constructor(options: AttributeValue) { - if (options) { - this.name = options.name - this.value = options.value - } - } - - @IsString() - public name!: string - - @IsString() - public value!: string -} - -export class AttributeFilter { - public constructor(options: AttributeFilter) { - if (options) { - this.schemaId = options.schemaId - this.schemaIssuerDid = options.schemaIssuerDid - this.schemaName = options.schemaName - this.schemaVersion = options.schemaVersion - this.issuerDid = options.issuerDid - this.credentialDefinitionId = options.credentialDefinitionId - this.attributeValue = options.attributeValue - } - } - - @Expose({ name: 'schema_id' }) - @IsOptional() - @IsString() - @Matches(schemaIdRegex) - public schemaId?: string - - @Expose({ name: 'schema_issuer_did' }) - @IsOptional() - @IsString() - @Matches(indyDidRegex) - public schemaIssuerDid?: string - - @Expose({ name: 'schema_name' }) - @IsOptional() - @IsString() - public schemaName?: string - - @Expose({ name: 'schema_version' }) - @IsOptional() - @IsString() - @Matches(schemaVersionRegex, { - message: 'Version must be X.X or X.X.X', - }) - public schemaVersion?: string - - @Expose({ name: 'issuer_did' }) - @IsOptional() - @IsString() - @Matches(indyDidRegex) - public issuerDid?: string - - @Expose({ name: 'cred_def_id' }) - @IsOptional() - @IsString() - @Matches(credDefIdRegex) - public credentialDefinitionId?: string - - @IsOptional() - @Type(() => AttributeValue) - @ValidateNested() - @IsInstance(AttributeValue) - public attributeValue?: AttributeValue -} - -/** - * Decorator that transforms attribute filter to corresponding class instances. - * Needed for transformation of attribute value filter. - * - * Transforms attribute value between these formats: - * - * JSON: - * ```json - * { - * "attr::test_prop::value": "test_value" - * } - * ``` - * - * Class: - * ```json - * { - * "attributeValue": { - * "name": "test_props", - * "value": "test_value" - * } - * } - * ``` - * - * @example - * class Example { - * AttributeFilterTransformer() - * public attributeFilter?: AttributeFilter; - * } - * - * @see https://github.com/hyperledger/aries-framework-dotnet/blob/a18bef91e5b9e4a1892818df7408e2383c642dfa/src/Hyperledger.Aries/Features/PresentProof/Models/AttributeFilterConverter.cs - */ -export function AttributeFilterTransformer() { - return Transform(({ value: attributeFilter, type: transformationType }) => { - switch (transformationType) { - case TransformationType.CLASS_TO_PLAIN: - if (attributeFilter.attributeValue) { - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - attributeFilter[`attr::${attributeFilter.attributeValue.name}::value`] = attributeFilter.attributeValue.value - delete attributeFilter.attributeValue - } - - return attributeFilter - - case TransformationType.PLAIN_TO_CLASS: - for (const [key, value] of Object.entries(attributeFilter)) { - const match = new RegExp('^attr::([^:]+)::(value)$').exec(key) - - if (match) { - const attributeValue = new AttributeValue({ - name: match[1], - value: value as string, - }) - - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - delete attributeFilter[key] - attributeFilter.attributeValue = attributeValue - - return attributeFilter - } - } - return attributeFilter - default: - return attributeFilter - } - }) -} diff --git a/packages/core/src/modules/proofs/formats/indy/models/PredicateType.ts b/packages/core/src/modules/proofs/formats/indy/models/PredicateType.ts deleted file mode 100644 index f5dda2fc14..0000000000 --- a/packages/core/src/modules/proofs/formats/indy/models/PredicateType.ts +++ /dev/null @@ -1,6 +0,0 @@ -export enum PredicateType { - LessThan = '<', - LessThanOrEqualTo = '<=', - GreaterThan = '>', - GreaterThanOrEqualTo = '>=', -} diff --git a/packages/core/src/modules/proofs/formats/indy/models/ProofAttributeInfo.ts b/packages/core/src/modules/proofs/formats/indy/models/ProofAttributeInfo.ts deleted file mode 100644 index 4bf1f136b0..0000000000 --- a/packages/core/src/modules/proofs/formats/indy/models/ProofAttributeInfo.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { Expose, Type } from 'class-transformer' -import { ArrayNotEmpty, IsArray, IsInstance, IsOptional, IsString, ValidateIf, ValidateNested } from 'class-validator' - -import { IndyRevocationInterval } from '../../../../credentials' - -import { AttributeFilter } from './AttributeFilter' - -export class ProofAttributeInfo { - public constructor(options: ProofAttributeInfo) { - if (options) { - this.name = options.name - this.names = options.names - this.nonRevoked = options.nonRevoked - this.restrictions = options.restrictions - } - } - - @IsString() - @ValidateIf((o: ProofAttributeInfo) => o.names === undefined) - public name?: string - - @IsArray() - @IsString({ each: true }) - @ValidateIf((o: ProofAttributeInfo) => o.name === undefined) - @ArrayNotEmpty() - public names?: string[] - - @Expose({ name: 'non_revoked' }) - @ValidateNested() - @IsInstance(IndyRevocationInterval) - @Type(() => IndyRevocationInterval) - @IsOptional() - public nonRevoked?: IndyRevocationInterval - - @ValidateNested({ each: true }) - @Type(() => AttributeFilter) - @IsOptional() - @IsInstance(AttributeFilter, { each: true }) - public restrictions?: AttributeFilter[] -} diff --git a/packages/core/src/modules/proofs/formats/indy/models/ProofPredicateInfo.ts b/packages/core/src/modules/proofs/formats/indy/models/ProofPredicateInfo.ts deleted file mode 100644 index 8f246746bf..0000000000 --- a/packages/core/src/modules/proofs/formats/indy/models/ProofPredicateInfo.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { Expose, Type } from 'class-transformer' -import { IsArray, IsEnum, IsInstance, IsInt, IsOptional, IsString, ValidateNested } from 'class-validator' - -import { IndyRevocationInterval } from '../../../../credentials' - -import { AttributeFilter } from './AttributeFilter' -import { PredicateType } from './PredicateType' - -export class ProofPredicateInfo { - public constructor(options: ProofPredicateInfo) { - if (options) { - this.name = options.name - this.nonRevoked = options.nonRevoked - this.restrictions = options.restrictions - this.predicateType = options.predicateType - this.predicateValue = options.predicateValue - } - } - - @IsString() - public name!: string - - @Expose({ name: 'p_type' }) - @IsEnum(PredicateType) - public predicateType!: PredicateType - - @Expose({ name: 'p_value' }) - @IsInt() - public predicateValue!: number - - @Expose({ name: 'non_revoked' }) - @ValidateNested() - @Type(() => IndyRevocationInterval) - @IsOptional() - @IsInstance(IndyRevocationInterval) - public nonRevoked?: IndyRevocationInterval - - @ValidateNested({ each: true }) - @Type(() => AttributeFilter) - @IsOptional() - @IsInstance(AttributeFilter, { each: true }) - @IsArray() - public restrictions?: AttributeFilter[] -} diff --git a/packages/core/src/modules/proofs/formats/indy/models/ProofRequest.ts b/packages/core/src/modules/proofs/formats/indy/models/ProofRequest.ts deleted file mode 100644 index 224169c864..0000000000 --- a/packages/core/src/modules/proofs/formats/indy/models/ProofRequest.ts +++ /dev/null @@ -1,87 +0,0 @@ -import type { IndyProofRequest } from 'indy-sdk' - -import { Expose, Type } from 'class-transformer' -import { IsIn, IsInstance, IsOptional, IsString, ValidateNested } from 'class-validator' - -import { JsonTransformer } from '../../../../../utils/JsonTransformer' -import { IsMap } from '../../../../../utils/transformers' -import { IndyRevocationInterval } from '../../../../credentials' - -import { ProofAttributeInfo } from './ProofAttributeInfo' -import { ProofPredicateInfo } from './ProofPredicateInfo' - -export interface ProofRequestOptions { - name: string - version: string - nonce: string - nonRevoked?: IndyRevocationInterval - ver?: '1.0' | '2.0' - requestedAttributes?: Record | Map - requestedPredicates?: Record | Map -} - -/** - * Proof Request for Indy based proof format - * - * @see https://github.com/hyperledger/indy-sdk/blob/57dcdae74164d1c7aa06f2cccecaae121cefac25/libindy/src/api/anoncreds.rs#L1222-L1239 - */ -export class ProofRequest { - public constructor(options: ProofRequestOptions) { - if (options) { - this.name = options.name - this.version = options.version - this.nonce = options.nonce - this.requestedAttributes = options.requestedAttributes - ? options.requestedAttributes instanceof Map - ? options.requestedAttributes - : new Map(Object.entries(options.requestedAttributes)) - : new Map() - this.requestedPredicates = options.requestedPredicates - ? options.requestedPredicates instanceof Map - ? options.requestedPredicates - : new Map(Object.entries(options.requestedPredicates)) - : new Map() - this.nonRevoked = options.nonRevoked - this.ver = options.ver - } - } - - @IsString() - public name!: string - - @IsString() - public version!: string - - @IsString() - public nonce!: string - - @Expose({ name: 'requested_attributes' }) - @IsMap() - @ValidateNested({ each: true }) - @Type(() => ProofAttributeInfo) - @IsInstance(ProofAttributeInfo, { each: true }) - public requestedAttributes!: Map - - @Expose({ name: 'requested_predicates' }) - @IsMap() - @ValidateNested({ each: true }) - @Type(() => ProofPredicateInfo) - @IsInstance(ProofPredicateInfo, { each: true }) - public requestedPredicates!: Map - - @Expose({ name: 'non_revoked' }) - @ValidateNested() - @Type(() => IndyRevocationInterval) - @IsOptional() - @IsInstance(IndyRevocationInterval) - public nonRevoked?: IndyRevocationInterval - - @IsIn(['1.0', '2.0']) - @IsOptional() - public ver?: '1.0' | '2.0' - - public toJSON() { - // IndyProofRequest is indy-sdk json type - return JsonTransformer.toJSON(this) as unknown as IndyProofRequest - } -} diff --git a/packages/core/src/modules/proofs/formats/indy/models/RequestedAttribute.ts b/packages/core/src/modules/proofs/formats/indy/models/RequestedAttribute.ts deleted file mode 100644 index 048a89cf82..0000000000 --- a/packages/core/src/modules/proofs/formats/indy/models/RequestedAttribute.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { Exclude, Expose } from 'class-transformer' -import { IsBoolean, IsInt, IsOptional, IsString } from 'class-validator' - -import { IndyCredentialInfo } from '../../../../credentials/formats/indy/models/IndyCredentialInfo' - -/** - * Requested Attribute for Indy proof creation - */ -export class RequestedAttribute { - public constructor(options: RequestedAttribute) { - if (options) { - this.credentialId = options.credentialId - this.timestamp = options.timestamp - this.revealed = options.revealed - this.credentialInfo = options.credentialInfo - this.revoked = options.revoked - } - } - - @Expose({ name: 'cred_id' }) - @IsString() - public credentialId!: string - - @Expose({ name: 'timestamp' }) - @IsInt() - @IsOptional() - public timestamp?: number - - @IsBoolean() - public revealed!: boolean - - @Exclude({ toPlainOnly: true }) - public credentialInfo?: IndyCredentialInfo - - @Exclude({ toPlainOnly: true }) - public revoked?: boolean -} diff --git a/packages/core/src/modules/proofs/formats/indy/models/RequestedCredentials.ts b/packages/core/src/modules/proofs/formats/indy/models/RequestedCredentials.ts deleted file mode 100644 index b2824bf7bd..0000000000 --- a/packages/core/src/modules/proofs/formats/indy/models/RequestedCredentials.ts +++ /dev/null @@ -1,63 +0,0 @@ -import type { IndyRequestedCredentials } from 'indy-sdk' - -import { Expose } from 'class-transformer' -import { ValidateNested } from 'class-validator' - -import { JsonTransformer } from '../../../../../utils/JsonTransformer' -import { RecordTransformer } from '../../../../../utils/transformers' - -import { RequestedAttribute } from './RequestedAttribute' -import { RequestedPredicate } from './RequestedPredicate' - -export interface IndyRequestedCredentialsOptions { - requestedAttributes?: Record - requestedPredicates?: Record - selfAttestedAttributes?: Record -} - -/** - * Requested Credentials for Indy proof creation - * - * @see https://github.com/hyperledger/indy-sdk/blob/57dcdae74164d1c7aa06f2cccecaae121cefac25/libindy/src/api/anoncreds.rs#L1433-L1445 - */ -export class RequestedCredentials { - public constructor(options: IndyRequestedCredentialsOptions = {}) { - if (options) { - this.requestedAttributes = options.requestedAttributes ?? {} - this.requestedPredicates = options.requestedPredicates ?? {} - this.selfAttestedAttributes = options.selfAttestedAttributes ?? {} - } - } - - @Expose({ name: 'requested_attributes' }) - @ValidateNested({ each: true }) - @RecordTransformer(RequestedAttribute) - public requestedAttributes!: Record - - @Expose({ name: 'requested_predicates' }) - @ValidateNested({ each: true }) - @RecordTransformer(RequestedPredicate) - public requestedPredicates!: Record - - @Expose({ name: 'self_attested_attributes' }) - public selfAttestedAttributes!: Record - - public toJSON() { - // IndyRequestedCredentials is indy-sdk json type - return JsonTransformer.toJSON(this) as unknown as IndyRequestedCredentials - } - - public getCredentialIdentifiers(): string[] { - const credIds = new Set() - - Object.values(this.requestedAttributes).forEach((attr) => { - credIds.add(attr.credentialId) - }) - - Object.values(this.requestedPredicates).forEach((pred) => { - credIds.add(pred.credentialId) - }) - - return Array.from(credIds) - } -} diff --git a/packages/core/src/modules/proofs/formats/indy/models/RequestedPredicate.ts b/packages/core/src/modules/proofs/formats/indy/models/RequestedPredicate.ts deleted file mode 100644 index 9109b51a4d..0000000000 --- a/packages/core/src/modules/proofs/formats/indy/models/RequestedPredicate.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { Exclude, Expose } from 'class-transformer' -import { IsInt, IsOptional, IsString } from 'class-validator' - -import { IndyCredentialInfo } from '../../../../credentials' - -/** - * Requested Predicate for Indy proof creation - */ -export class RequestedPredicate { - public constructor(options: RequestedPredicate) { - if (options) { - this.credentialId = options.credentialId - this.timestamp = options.timestamp - this.credentialInfo = options.credentialInfo - this.revoked = options.revoked - } - } - - @Expose({ name: 'cred_id' }) - @IsString() - public credentialId!: string - - @Expose({ name: 'timestamp' }) - @IsInt() - @IsOptional() - public timestamp?: number - - @Exclude({ toPlainOnly: true }) - public credentialInfo?: IndyCredentialInfo - - @Exclude({ toPlainOnly: true }) - public revoked?: boolean -} diff --git a/packages/core/src/modules/proofs/formats/indy/models/RetrievedCredentials.ts b/packages/core/src/modules/proofs/formats/indy/models/RetrievedCredentials.ts deleted file mode 100644 index e529b24065..0000000000 --- a/packages/core/src/modules/proofs/formats/indy/models/RetrievedCredentials.ts +++ /dev/null @@ -1,20 +0,0 @@ -import type { RequestedAttribute } from './RequestedAttribute' -import type { RequestedPredicate } from './RequestedPredicate' - -export interface RetrievedCredentialsOptions { - requestedAttributes?: Record - requestedPredicates?: Record -} - -/** - * Lists of requested credentials for Indy proof creation - */ -export class RetrievedCredentials { - public requestedAttributes: Record - public requestedPredicates: Record - - public constructor(options: RetrievedCredentialsOptions = {}) { - this.requestedAttributes = options.requestedAttributes ?? {} - this.requestedPredicates = options.requestedPredicates ?? {} - } -} diff --git a/packages/core/src/modules/proofs/formats/indy/models/index.ts b/packages/core/src/modules/proofs/formats/indy/models/index.ts deleted file mode 100644 index 978b3ee89f..0000000000 --- a/packages/core/src/modules/proofs/formats/indy/models/index.ts +++ /dev/null @@ -1,9 +0,0 @@ -export * from './AttributeFilter' -export * from './PredicateType' -export * from './ProofAttributeInfo' -export * from './ProofPredicateInfo' -export * from './ProofRequest' -export * from './RequestedAttribute' -export * from './RequestedCredentials' -export * from './RequestedPredicate' -export * from './RetrievedCredentials' diff --git a/packages/core/src/modules/proofs/formats/models/ProofAttachmentFormat.ts b/packages/core/src/modules/proofs/formats/models/ProofAttachmentFormat.ts deleted file mode 100644 index d7fbb5f3c9..0000000000 --- a/packages/core/src/modules/proofs/formats/models/ProofAttachmentFormat.ts +++ /dev/null @@ -1,7 +0,0 @@ -import type { V1Attachment } from '../../../../decorators/attachment/V1Attachment' -import type { ProofFormatSpec } from '../../models/ProofFormatSpec' - -export interface ProofAttachmentFormat { - format: ProofFormatSpec - attachment: V1Attachment -} diff --git a/packages/core/src/modules/proofs/formats/models/ProofFormatServiceOptions.ts b/packages/core/src/modules/proofs/formats/models/ProofFormatServiceOptions.ts deleted file mode 100644 index f01e0a1b0d..0000000000 --- a/packages/core/src/modules/proofs/formats/models/ProofFormatServiceOptions.ts +++ /dev/null @@ -1,64 +0,0 @@ -import type { V1Attachment } from '../../../../decorators/attachment/V1Attachment' -import type { ProposeProofFormats } from '../../models/SharedOptions' -import type { ProofExchangeRecord } from '../../repository' -import type { ProofFormat, ProofFormatPayload } from '../ProofFormat' -import type { ProofRequestOptions } from '../indy/models/ProofRequest' -import type { ProofAttachmentFormat } from './ProofAttachmentFormat' - -export interface CreateRequestAttachmentOptions { - id?: string - proofRequestOptions: ProofRequestOptions -} - -export interface CreateProofAttachmentOptions { - id?: string - proofProposalOptions: ProofRequestOptions -} - -export interface FormatCreateProofProposalOptions { - id?: string - formats: ProposeProofFormats -} - -export interface ProcessProposalOptions { - proposal: ProofAttachmentFormat - record?: ProofExchangeRecord -} - -export interface CreateRequestOptions { - id?: string - formats: ProposeProofFormats -} - -export interface ProcessRequestOptions { - requestAttachment: ProofAttachmentFormat - record?: ProofExchangeRecord -} - -export interface FormatCreatePresentationOptions { - id?: string - attachment: V1Attachment - proofFormats: ProofFormatPayload<[PF], 'createPresentation'> -} - -export interface ProcessPresentationOptions { - record: ProofExchangeRecord - formatAttachments: { - request: ProofAttachmentFormat[] - presentation: ProofAttachmentFormat[] - } -} - -export interface VerifyProofOptions { - request: V1Attachment - proof: V1Attachment -} - -export interface CreateProblemReportOptions { - proofRecord: ProofExchangeRecord - description: string -} - -export interface CreatePresentationFormatsOptions { - presentationAttachment: V1Attachment -} diff --git a/packages/core/src/modules/proofs/formats/models/index.ts b/packages/core/src/modules/proofs/formats/models/index.ts deleted file mode 100644 index 968a6b53ee..0000000000 --- a/packages/core/src/modules/proofs/formats/models/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from './ProofAttachmentFormat' -export * from './ProofFormatServiceOptions' diff --git a/packages/core/src/modules/proofs/index.ts b/packages/core/src/modules/proofs/index.ts index 5d4f5f16c7..30eb44ba0f 100644 --- a/packages/core/src/modules/proofs/index.ts +++ b/packages/core/src/modules/proofs/index.ts @@ -1,13 +1,14 @@ export * from './errors' export * from './formats' -export * from './messages' export * from './models' export * from './protocol' export * from './repository' export * from './ProofEvents' -export * from './ProofResponseCoordinator' + +// Api export * from './ProofsApi' export * from './ProofsApiOptions' -export * from './ProofService' + +// Module export * from './ProofsModule' export * from './ProofsModuleConfig' diff --git a/packages/core/src/modules/proofs/messages/PresentationAckMessage.ts b/packages/core/src/modules/proofs/messages/PresentationAckMessage.ts deleted file mode 100644 index 64e60f56b2..0000000000 --- a/packages/core/src/modules/proofs/messages/PresentationAckMessage.ts +++ /dev/null @@ -1,10 +0,0 @@ -import type { ProtocolVersion } from '../../../types' -import type { AckMessageOptions } from '../../common' - -export type PresentationAckMessageOptions = AckMessageOptions - -type PresentationAckMessageType = `https://didcomm.org/present-proof/${ProtocolVersion}/ack` - -export interface PresentationAckMessage { - type: PresentationAckMessageType -} diff --git a/packages/core/src/modules/proofs/messages/index.ts b/packages/core/src/modules/proofs/messages/index.ts deleted file mode 100644 index 1f395b2d57..0000000000 --- a/packages/core/src/modules/proofs/messages/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './PresentationAckMessage' diff --git a/packages/core/src/modules/proofs/models/GetRequestedCredentialsConfig.ts b/packages/core/src/modules/proofs/models/GetRequestedCredentialsConfig.ts deleted file mode 100644 index 9041bbabe3..0000000000 --- a/packages/core/src/modules/proofs/models/GetRequestedCredentialsConfig.ts +++ /dev/null @@ -1,19 +0,0 @@ -export interface GetRequestedCredentialsConfig { - /** - * Whether to filter the retrieved credentials using the presentation preview. - * This configuration will only have effect if a presentation proposal message is available - * containing a presentation preview. - * - * @default false - */ - filterByPresentationPreview?: boolean - - /** - * Whether to filter the retrieved credentials using the non-revocation request in the proof request. - * This configuration will only have effect if the proof request requires proof on non-revocation of any kind. - * Default to true - * - * @default true - */ - filterByNonRevocationRequirements?: boolean -} diff --git a/packages/core/src/modules/proofs/models/ModuleOptions.ts b/packages/core/src/modules/proofs/models/ModuleOptions.ts deleted file mode 100644 index e471a243db..0000000000 --- a/packages/core/src/modules/proofs/models/ModuleOptions.ts +++ /dev/null @@ -1,22 +0,0 @@ -import type { GetRequestedCredentialsConfig } from './GetRequestedCredentialsConfig' -import type { AutoAcceptProof } from './ProofAutoAcceptType' -import type { ProposeProofFormats } from './SharedOptions' - -export interface ProofConfig { - name: string - version: string -} - -export interface NegotiateRequestOptions { - proofRecordId: string - proofFormats: ProposeProofFormats - comment?: string - autoAcceptProof?: AutoAcceptProof -} - -export interface AutoSelectCredentialsForProofRequestOptions { - proofRecordId: string - config?: GetRequestedCredentialsConfig -} - -export type GetRequestedCredentialsForProofRequest = AutoSelectCredentialsForProofRequestOptions diff --git a/packages/core/src/modules/proofs/models/ProofServiceOptions.ts b/packages/core/src/modules/proofs/models/ProofServiceOptions.ts deleted file mode 100644 index 3c7e7e47af..0000000000 --- a/packages/core/src/modules/proofs/models/ProofServiceOptions.ts +++ /dev/null @@ -1,85 +0,0 @@ -import type { ConnectionRecord } from '../../connections' -import type { ProofFormat, ProofFormatPayload } from '../formats/ProofFormat' -import type { ProofExchangeRecord } from '../repository' -import type { GetRequestedCredentialsConfig } from './GetRequestedCredentialsConfig' -import type { AutoAcceptProof } from './ProofAutoAcceptType' - -export type FormatDataMessagePayload< - CFs extends ProofFormat[] = ProofFormat[], - M extends keyof ProofFormat['formatData'] = keyof ProofFormat['formatData'] -> = { - [ProofFormat in CFs[number] as ProofFormat['formatKey']]?: ProofFormat['formatData'][M] -} - -interface BaseOptions { - willConfirm?: boolean - goalCode?: string - comment?: string - autoAcceptProof?: AutoAcceptProof -} - -export interface CreateProposalOptions extends BaseOptions { - connectionRecord: ConnectionRecord - proofFormats: ProofFormatPayload - parentThreadId?: string -} - -export interface CreateProposalAsResponseOptions extends BaseOptions { - proofRecord: ProofExchangeRecord - proofFormats: ProofFormatPayload -} - -export interface CreateRequestAsResponseOptions extends BaseOptions { - id?: string - proofRecord: ProofExchangeRecord - proofFormats: ProofFormatPayload -} - -export interface CreateRequestOptions extends BaseOptions { - connectionRecord?: ConnectionRecord - proofFormats: ProofFormatPayload - parentThreadId?: string -} - -export interface CreateProofRequestFromProposalOptions extends BaseOptions { - id?: string - proofRecord: ProofExchangeRecord -} - -export interface FormatRetrievedCredentialOptions { - proofFormats: ProofFormatPayload -} - -export interface FormatRequestedCredentialReturn { - proofFormats: ProofFormatPayload -} - -export interface CreatePresentationOptions extends BaseOptions { - proofRecord: ProofExchangeRecord - proofFormats: ProofFormatPayload // - lastPresentation?: boolean -} - -export interface CreateAckOptions { - proofRecord: ProofExchangeRecord -} - -export interface GetRequestedCredentialsForProofRequestOptions { - proofRecord: ProofExchangeRecord - config?: GetRequestedCredentialsConfig -} - -export interface ProofRequestFromProposalOptions { - proofRecord: ProofExchangeRecord - proofFormats: ProofFormatPayload -} - -export interface DeleteProofOptions { - deleteAssociatedDidCommMessages?: boolean -} - -export type GetFormatDataReturn = { - proposal?: FormatDataMessagePayload - request?: FormatDataMessagePayload - presentation?: FormatDataMessagePayload -} diff --git a/packages/core/src/modules/proofs/models/SharedOptions.ts b/packages/core/src/modules/proofs/models/SharedOptions.ts deleted file mode 100644 index e479dea456..0000000000 --- a/packages/core/src/modules/proofs/models/SharedOptions.ts +++ /dev/null @@ -1,62 +0,0 @@ -import type { IndyProposeProofFormat } from '../formats/indy/IndyProofFormat' -import type { IndyRequestProofFormat, IndyVerifyProofFormat } from '../formats/indy/IndyProofFormatsServiceOptions' -import type { ProofRequest } from '../formats/indy/models/ProofRequest' -import type { IndyRequestedCredentialsOptions } from '../formats/indy/models/RequestedCredentials' -import type { RetrievedCredentials } from '../formats/indy/models/RetrievedCredentials' -import type { GetRequestedCredentialsConfig } from './GetRequestedCredentialsConfig' - -export interface ProposeProofFormats { - // If you want to propose an indy proof without attributes or - // any of the other properties you should pass an empty object - indy?: IndyProposeProofFormat - presentationExchange?: never -} - -export interface RequestProofFormats { - indy?: IndyRequestProofFormat - presentationExchange?: never -} - -export interface CreatePresentationFormats { - indy?: IndyRequestedCredentialsOptions - presentationExchange?: never -} - -export interface AcceptProposalFormats { - indy?: IndyAcceptProposalOptions - presentationExchange?: never -} - -export interface VerifyProofFormats { - indy?: IndyVerifyProofFormat - presentationExchange?: never -} - -export interface RequestedCredentialConfigOptions { - indy?: GetRequestedCredentialsConfig - presentationExchange?: never -} - -// export interface RetrievedCredentialOptions { -// indy?: RetrievedCredentials -// presentationExchange?: undefined -// } - -export interface ProofRequestFormats { - indy?: ProofRequest - presentationExchange?: undefined -} - -// export interface RequestedCredentialsFormats { -// indy?: RequestedCredentials -// presentationExchange?: undefined -// } - -interface IndyAcceptProposalOptions { - request: ProofRequest -} - -export interface AutoSelectCredentialOptions { - indy?: RetrievedCredentials - presentationExchange?: undefined -} diff --git a/packages/core/src/modules/proofs/__tests__/ProofState.test.ts b/packages/core/src/modules/proofs/models/__tests__/ProofState.test.ts similarity index 92% rename from packages/core/src/modules/proofs/__tests__/ProofState.test.ts rename to packages/core/src/modules/proofs/models/__tests__/ProofState.test.ts index 4b67ed11d0..9cabafd183 100644 --- a/packages/core/src/modules/proofs/__tests__/ProofState.test.ts +++ b/packages/core/src/modules/proofs/models/__tests__/ProofState.test.ts @@ -1,4 +1,4 @@ -import { ProofState } from '../models/ProofState' +import { ProofState } from '../ProofState' describe('ProofState', () => { test('state matches Present Proof 1.0 (RFC 0037) state value', () => { diff --git a/packages/core/src/modules/proofs/models/index.ts b/packages/core/src/modules/proofs/models/index.ts index a092a0ae7e..9dec0e697a 100644 --- a/packages/core/src/modules/proofs/models/index.ts +++ b/packages/core/src/modules/proofs/models/index.ts @@ -1,3 +1,3 @@ -export * from './GetRequestedCredentialsConfig' export * from './ProofAutoAcceptType' export * from './ProofState' +export * from './ProofFormatSpec' diff --git a/packages/core/src/modules/proofs/protocol/BaseProofProtocol.ts b/packages/core/src/modules/proofs/protocol/BaseProofProtocol.ts new file mode 100644 index 0000000000..7a1ac4d372 --- /dev/null +++ b/packages/core/src/modules/proofs/protocol/BaseProofProtocol.ts @@ -0,0 +1,292 @@ +import type { ProofProtocol } from './ProofProtocol' +import type { + CreateProofProposalOptions, + CreateProofRequestOptions, + DeleteProofOptions, + GetProofFormatDataReturn, + CreateProofProblemReportOptions, + ProofProtocolMsgReturnType, + AcceptPresentationOptions, + AcceptProofProposalOptions, + AcceptProofRequestOptions, + GetCredentialsForRequestOptions, + GetCredentialsForRequestReturn, + NegotiateProofProposalOptions, + NegotiateProofRequestOptions, + SelectCredentialsForRequestOptions, + SelectCredentialsForRequestReturn, +} from './ProofProtocolOptions' +import type { FeatureRegistry } from '../../../agent/FeatureRegistry' +import type { AgentContext } from '../../../agent/context/AgentContext' +import type { InboundMessageContext } from '../../../agent/models/InboundMessageContext' +import type { DidCommV1Message } from '../../../didcomm' +import type { DependencyManager } from '../../../plugins' +import type { Query } from '../../../storage/StorageService' +import type { ProblemReportMessage } from '../../problem-reports' +import type { ProofStateChangedEvent } from '../ProofEvents' +import type { ExtractProofFormats, ProofFormatService } from '../formats' +import type { ProofExchangeRecord } from '../repository' + +import { EventEmitter } from '../../../agent/EventEmitter' +import { DidCommMessageRepository } from '../../../storage' +import { JsonTransformer } from '../../../utils/JsonTransformer' +import { ProofEventTypes } from '../ProofEvents' +import { ProofState } from '../models/ProofState' +import { ProofRepository } from '../repository' + +export abstract class BaseProofProtocol + implements ProofProtocol +{ + public abstract readonly version: string + + public abstract register(dependencyManager: DependencyManager, featureRegistry: FeatureRegistry): void + + // methods for proposal + public abstract createProposal( + agentContext: AgentContext, + options: CreateProofProposalOptions + ): Promise> + public abstract processProposal(messageContext: InboundMessageContext): Promise + public abstract acceptProposal( + agentContext: AgentContext, + options: AcceptProofProposalOptions + ): Promise> + public abstract negotiateProposal( + agentContext: AgentContext, + options: NegotiateProofProposalOptions + ): Promise> + + // methods for request + public abstract createRequest( + agentContext: AgentContext, + options: CreateProofRequestOptions + ): Promise> + public abstract processRequest(messageContext: InboundMessageContext): Promise + public abstract acceptRequest( + agentContext: AgentContext, + options: AcceptProofRequestOptions + ): Promise> + public abstract negotiateRequest( + agentContext: AgentContext, + options: NegotiateProofRequestOptions + ): Promise> + + // retrieving credentials for request + public abstract getCredentialsForRequest( + agentContext: AgentContext, + options: GetCredentialsForRequestOptions + ): Promise> + public abstract selectCredentialsForRequest( + agentContext: AgentContext, + options: SelectCredentialsForRequestOptions + ): Promise> + + // methods for presentation + public abstract processPresentation( + messageContext: InboundMessageContext + ): Promise + public abstract acceptPresentation( + agentContext: AgentContext, + options: AcceptPresentationOptions + ): Promise> + + // methods for ack + public abstract processAck(messageContext: InboundMessageContext): Promise + // method for problem report + public abstract createProblemReport( + agentContext: AgentContext, + options: CreateProofProblemReportOptions + ): Promise> + + public abstract findProposalMessage( + agentContext: AgentContext, + proofExchangeId: string + ): Promise + public abstract findRequestMessage( + agentContext: AgentContext, + proofExchangeId: string + ): Promise + public abstract findPresentationMessage( + agentContext: AgentContext, + proofExchangeId: string + ): Promise + public abstract getFormatData( + agentContext: AgentContext, + proofExchangeId: string + ): Promise>> + + public async processProblemReport( + messageContext: InboundMessageContext + ): Promise { + const { message: proofProblemReportMessage, agentContext, connection } = messageContext + + agentContext.config.logger.debug(`Processing problem report with message id ${proofProblemReportMessage.id}`) + + const proofRecord = await this.getByThreadAndConnectionId( + agentContext, + proofProblemReportMessage.threadId, + connection?.id + ) + + // Update record + proofRecord.errorMessage = `${proofProblemReportMessage.description.code}: ${proofProblemReportMessage.description.en}` + await this.updateState(agentContext, proofRecord, ProofState.Abandoned) + return proofRecord + } + + /** + * Update the record to a new state and emit an state changed event. Also updates the record + * in storage. + * + * @param proofRecord The proof record to update the state for + * @param newState The state to update to + * + */ + public async updateState(agentContext: AgentContext, proofRecord: ProofExchangeRecord, newState: ProofState) { + const proofRepository = agentContext.dependencyManager.resolve(ProofRepository) + + agentContext.config.logger.debug( + `Updating proof record ${proofRecord.id} to state ${newState} (previous=${proofRecord.state})` + ) + + const previousState = proofRecord.state + proofRecord.state = newState + await proofRepository.update(agentContext, proofRecord) + + this.emitStateChangedEvent(agentContext, proofRecord, previousState) + } + + protected emitStateChangedEvent( + agentContext: AgentContext, + proofRecord: ProofExchangeRecord, + previousState: ProofState | null + ) { + const eventEmitter = agentContext.dependencyManager.resolve(EventEmitter) + + const clonedProof = JsonTransformer.clone(proofRecord) + + eventEmitter.emit(agentContext, { + type: ProofEventTypes.ProofStateChanged, + payload: { + proofRecord: clonedProof, + previousState: previousState, + }, + }) + } + + /** + * Retrieve a proof record by id + * + * @param proofRecordId The proof record id + * @throws {RecordNotFoundError} If no record is found + * @return The proof record + * + */ + public getById(agentContext: AgentContext, proofRecordId: string): Promise { + const proofRepository = agentContext.dependencyManager.resolve(ProofRepository) + + return proofRepository.getById(agentContext, proofRecordId) + } + + /** + * Retrieve all proof records + * + * @returns List containing all proof records + */ + public getAll(agentContext: AgentContext): Promise { + const proofRepository = agentContext.dependencyManager.resolve(ProofRepository) + + return proofRepository.getAll(agentContext) + } + + public async findAllByQuery( + agentContext: AgentContext, + query: Query + ): Promise { + const proofRepository = agentContext.dependencyManager.resolve(ProofRepository) + + return proofRepository.findByQuery(agentContext, query) + } + + /** + * Find a proof record by id + * + * @param proofRecordId the proof record id + * @returns The proof record or null if not found + */ + public findById(agentContext: AgentContext, proofRecordId: string): Promise { + const proofRepository = agentContext.dependencyManager.resolve(ProofRepository) + + return proofRepository.findById(agentContext, proofRecordId) + } + + public async delete( + agentContext: AgentContext, + proofRecord: ProofExchangeRecord, + options?: DeleteProofOptions + ): Promise { + const proofRepository = agentContext.dependencyManager.resolve(ProofRepository) + const didCommMessageRepository = agentContext.dependencyManager.resolve(DidCommMessageRepository) + + await proofRepository.delete(agentContext, proofRecord) + + const deleteAssociatedDidCommMessages = options?.deleteAssociatedDidCommMessages ?? true + + if (deleteAssociatedDidCommMessages) { + const didCommMessages = await didCommMessageRepository.findByQuery(agentContext, { + associatedRecordId: proofRecord.id, + }) + for (const didCommMessage of didCommMessages) { + await didCommMessageRepository.delete(agentContext, didCommMessage) + } + } + } + + /** + * Retrieve a proof record by connection id and thread id + * + * @param connectionId The connection id + * @param threadId The thread id + * @throws {RecordNotFoundError} If no record is found + * @throws {RecordDuplicateError} If multiple records are found + * @returns The proof record + */ + public getByThreadAndConnectionId( + agentContext: AgentContext, + threadId: string, + connectionId?: string + ): Promise { + const proofRepository = agentContext.dependencyManager.resolve(ProofRepository) + + return proofRepository.getSingleByQuery(agentContext, { + connectionId, + threadId, + }) + } + + /** + * Find a proof record by connection id and thread id, returns null if not found + * + * @param connectionId The connection id + * @param threadId The thread id + * @returns The proof record + */ + public findByThreadAndConnectionId( + agentContext: AgentContext, + threadId: string, + connectionId?: string + ): Promise { + const proofRepository = agentContext.dependencyManager.resolve(ProofRepository) + + return proofRepository.findSingleByQuery(agentContext, { + connectionId, + threadId, + }) + } + + public async update(agentContext: AgentContext, proofRecord: ProofExchangeRecord) { + const proofRepository = agentContext.dependencyManager.resolve(ProofRepository) + + return await proofRepository.update(agentContext, proofRecord) + } +} diff --git a/packages/core/src/modules/proofs/protocol/ProofProtocol.ts b/packages/core/src/modules/proofs/protocol/ProofProtocol.ts new file mode 100644 index 0000000000..7436ef6493 --- /dev/null +++ b/packages/core/src/modules/proofs/protocol/ProofProtocol.ts @@ -0,0 +1,117 @@ +import type { + CreateProofProposalOptions, + CreateProofRequestOptions, + DeleteProofOptions, + GetProofFormatDataReturn, + CreateProofProblemReportOptions, + ProofProtocolMsgReturnType, + AcceptProofProposalOptions, + NegotiateProofProposalOptions, + AcceptProofRequestOptions, + NegotiateProofRequestOptions, + AcceptPresentationOptions, + GetCredentialsForRequestOptions, + GetCredentialsForRequestReturn, + SelectCredentialsForRequestOptions, + SelectCredentialsForRequestReturn, +} from './ProofProtocolOptions' +import type { FeatureRegistry } from '../../../agent/FeatureRegistry' +import type { AgentContext } from '../../../agent/context/AgentContext' +import type { InboundMessageContext } from '../../../agent/models/InboundMessageContext' +import type { DidCommV1Message } from '../../../didcomm' +import type { DependencyManager } from '../../../plugins' +import type { Query } from '../../../storage/StorageService' +import type { ProblemReportMessage } from '../../problem-reports' +import type { ExtractProofFormats, ProofFormatService } from '../formats' +import type { ProofState } from '../models/ProofState' +import type { ProofExchangeRecord } from '../repository' + +export interface ProofProtocol { + readonly version: string + + // methods for proposal + createProposal( + agentContext: AgentContext, + options: CreateProofProposalOptions + ): Promise> + processProposal(messageContext: InboundMessageContext): Promise + acceptProposal( + agentContext: AgentContext, + options: AcceptProofProposalOptions + ): Promise> + negotiateProposal( + agentContext: AgentContext, + options: NegotiateProofProposalOptions + ): Promise> + + // methods for request + createRequest( + agentContext: AgentContext, + options: CreateProofRequestOptions + ): Promise> + processRequest(messageContext: InboundMessageContext): Promise + acceptRequest( + agentContext: AgentContext, + options: AcceptProofRequestOptions + ): Promise> + negotiateRequest( + agentContext: AgentContext, + options: NegotiateProofRequestOptions + ): Promise> + + // retrieving credentials for request + getCredentialsForRequest( + agentContext: AgentContext, + options: GetCredentialsForRequestOptions + ): Promise> + selectCredentialsForRequest( + agentContext: AgentContext, + options: SelectCredentialsForRequestOptions + ): Promise> + + // methods for presentation + processPresentation(messageContext: InboundMessageContext): Promise + acceptPresentation( + agentContext: AgentContext, + options: AcceptPresentationOptions + ): Promise> + + // methods for ack + processAck(messageContext: InboundMessageContext): Promise + + // method for problem report + createProblemReport( + agentContext: AgentContext, + options: CreateProofProblemReportOptions + ): Promise> + processProblemReport(messageContext: InboundMessageContext): Promise + + findProposalMessage(agentContext: AgentContext, proofExchangeId: string): Promise + findRequestMessage(agentContext: AgentContext, proofExchangeId: string): Promise + findPresentationMessage(agentContext: AgentContext, proofExchangeId: string): Promise + getFormatData( + agentContext: AgentContext, + proofExchangeId: string + ): Promise>> + + // repository methods + updateState(agentContext: AgentContext, proofRecord: ProofExchangeRecord, newState: ProofState): Promise + getById(agentContext: AgentContext, proofExchangeId: string): Promise + getAll(agentContext: AgentContext): Promise + findAllByQuery(agentContext: AgentContext, query: Query): Promise + findById(agentContext: AgentContext, proofExchangeId: string): Promise + delete(agentContext: AgentContext, proofRecord: ProofExchangeRecord, options?: DeleteProofOptions): Promise + getByThreadAndConnectionId( + agentContext: AgentContext, + threadId: string, + connectionId?: string + ): Promise + findByThreadAndConnectionId( + agentContext: AgentContext, + threadId: string, + connectionId?: string + ): Promise + update(agentContext: AgentContext, proofRecord: ProofExchangeRecord): Promise + + register(dependencyManager: DependencyManager, featureRegistry: FeatureRegistry): void +} diff --git a/packages/core/src/modules/proofs/protocol/ProofProtocolOptions.ts b/packages/core/src/modules/proofs/protocol/ProofProtocolOptions.ts new file mode 100644 index 0000000000..e84881f32a --- /dev/null +++ b/packages/core/src/modules/proofs/protocol/ProofProtocolOptions.ts @@ -0,0 +1,165 @@ +import type { ProofProtocol } from './ProofProtocol' +import type { DidCommV1Message } from '../../../didcomm' +import type { ConnectionRecord } from '../../connections' +import type { + ExtractProofFormats, + ProofFormat, + ProofFormatCredentialForRequestPayload, + ProofFormatPayload, + ProofFormatService, +} from '../formats' +import type { AutoAcceptProof } from '../models' +import type { ProofExchangeRecord } from '../repository' + +/** + * Get the format data payload for a specific message from a list of ProofFormat interfaces and a message + * + * For an indy offer, this resolves to the proof request format as defined here: + * https://github.com/hyperledger/aries-rfcs/tree/b3a3942ef052039e73cd23d847f42947f8287da2/features/0592-indy-attachments#proof-request-format + * + * @example + * ``` + * + * type RequestFormatData = ProofFormatDataMessagePayload<[IndyProofFormat, PresentationExchangeProofFormat], 'createRequest'> + * + * // equal to + * type RequestFormatData = { + * indy: { + * // ... payload for indy proof request attachment as defined in RFC 0592 ... + * }, + * presentationExchange: { + * // ... payload for presentation exchange request attachment as defined in RFC 0510 ... + * } + * } + * ``` + */ +export type ProofFormatDataMessagePayload< + CFs extends ProofFormat[] = ProofFormat[], + M extends keyof ProofFormat['formatData'] = keyof ProofFormat['formatData'] +> = { + [ProofFormat in CFs[number] as ProofFormat['formatKey']]?: ProofFormat['formatData'][M] +} + +/** + * Infer the {@link ProofFormat} types based on an array of {@link ProofProtocol} types. + * + * It does this by extracting the `ProofFormatServices` generic from the `ProofProtocol`, and + * then extracting the `ProofFormat` generic from each of the `ProofFormatService` types. + * + * @example + * ``` + * // TheProofFormatServices is now equal to [IndyProofFormatService] + * type TheProofFormatServices = ProofFormatsFromProtocols<[V1ProofProtocol]> + * ``` + * + * Because the `V1ProofProtocol` is defined as follows: + * ``` + * class V1ProofProtocol implements ProofProtocol<[IndyProofFormatService]> { + * } + * ``` + */ +export type ProofFormatsFromProtocols = Type[number] extends ProofProtocol< + infer ProofFormatServices +> + ? ProofFormatServices extends ProofFormatService[] + ? ExtractProofFormats + : never + : never + +export type GetProofFormatDataReturn = { + proposal?: ProofFormatDataMessagePayload + request?: ProofFormatDataMessagePayload + presentation?: ProofFormatDataMessagePayload +} + +interface BaseOptions { + goalCode?: string + comment?: string + autoAcceptProof?: AutoAcceptProof +} + +export interface CreateProofProposalOptions extends BaseOptions { + connectionRecord: ConnectionRecord + proofFormats: ProofFormatPayload, 'createProposal'> + parentThreadId?: string +} + +export interface AcceptProofProposalOptions extends BaseOptions { + proofRecord: ProofExchangeRecord + proofFormats?: ProofFormatPayload, 'acceptProposal'> + + /** @default true */ + willConfirm?: boolean +} + +export interface NegotiateProofProposalOptions extends BaseOptions { + proofRecord: ProofExchangeRecord + proofFormats: ProofFormatPayload, 'createRequest'> + + /** @default true */ + willConfirm?: boolean +} + +export interface CreateProofRequestOptions extends BaseOptions { + // Create request can also be used for connection-less, so connection is optional + connectionRecord?: ConnectionRecord + proofFormats: ProofFormatPayload, 'createRequest'> + parentThreadId?: string + + /** @default true */ + willConfirm?: boolean +} + +export interface AcceptProofRequestOptions extends BaseOptions { + proofRecord: ProofExchangeRecord + proofFormats?: ProofFormatPayload, 'acceptRequest'> +} + +export interface NegotiateProofRequestOptions extends BaseOptions { + proofRecord: ProofExchangeRecord + proofFormats: ProofFormatPayload, 'createProposal'> +} + +export interface GetCredentialsForRequestOptions { + proofRecord: ProofExchangeRecord + proofFormats?: ProofFormatCredentialForRequestPayload, 'getCredentialsForRequest', 'input'> +} + +export interface GetCredentialsForRequestReturn { + proofFormats: ProofFormatCredentialForRequestPayload, 'getCredentialsForRequest', 'output'> +} + +export interface SelectCredentialsForRequestOptions { + proofRecord: ProofExchangeRecord + proofFormats?: ProofFormatCredentialForRequestPayload< + ExtractProofFormats, + 'selectCredentialsForRequest', + 'input' + > +} + +export interface SelectCredentialsForRequestReturn { + proofFormats: ProofFormatCredentialForRequestPayload< + ExtractProofFormats, + 'selectCredentialsForRequest', + 'output' + > +} + +export interface AcceptPresentationOptions { + proofRecord: ProofExchangeRecord +} + +export interface CreateProofProblemReportOptions { + proofRecord: ProofExchangeRecord + description: string +} + +export interface ProofProtocolMsgReturnType { + message: MessageType + proofRecord: ProofExchangeRecord +} + +export interface DeleteProofOptions { + deleteAssociatedDidCommMessages?: boolean +} diff --git a/packages/core/src/modules/proofs/protocol/index.ts b/packages/core/src/modules/proofs/protocol/index.ts index 4d9da63573..71799a5c45 100644 --- a/packages/core/src/modules/proofs/protocol/index.ts +++ b/packages/core/src/modules/proofs/protocol/index.ts @@ -1,2 +1,10 @@ -export * from './v1' export * from './v2' +import * as ProofProtocolOptions from './ProofProtocolOptions' + +export { ProofProtocol } from './ProofProtocol' +// NOTE: ideally we don't export the BaseProofProtocol, but as the V1ProofProtocol is defined in the +// anoncreds package, we need to export it. We should at some point look at creating a core package which can be used for +// sharing internal types, and when you want to build you own modules, and an agent package, which is the one you use when +// consuming the framework +export { BaseProofProtocol } from './BaseProofProtocol' +export { ProofProtocolOptions } diff --git a/packages/core/src/modules/proofs/protocol/v1/V1ProofService.ts b/packages/core/src/modules/proofs/protocol/v1/V1ProofService.ts deleted file mode 100644 index 7d66de7755..0000000000 --- a/packages/core/src/modules/proofs/protocol/v1/V1ProofService.ts +++ /dev/null @@ -1,1111 +0,0 @@ -import type { AgentContext } from '../../../../agent' -import type { Dispatcher } from '../../../../agent/Dispatcher' -import type { InboundMessageContext } from '../../../../agent/models/InboundMessageContext' -import type { V1Attachment } from '../../../../decorators/attachment/V1Attachment' -import type { DidCommV1Message } from '../../../../didcomm' -import type { MediationRecipientService } from '../../../routing/services/MediationRecipientService' -import type { RoutingService } from '../../../routing/services/RoutingService' -import type { ProofResponseCoordinator } from '../../ProofResponseCoordinator' -import type { ProofFormat } from '../../formats/ProofFormat' -import type { IndyProofFormat, IndyProposeProofFormat } from '../../formats/indy/IndyProofFormat' -import type { ProofAttributeInfo } from '../../formats/indy/models' -import type { - CreateProblemReportOptions, - FormatCreatePresentationOptions, -} from '../../formats/models/ProofFormatServiceOptions' -import type { - CreateAckOptions, - CreatePresentationOptions, - CreateProofRequestFromProposalOptions, - CreateProposalAsResponseOptions, - CreateProposalOptions, - CreateRequestAsResponseOptions, - CreateRequestOptions, - FormatRequestedCredentialReturn, - FormatRetrievedCredentialOptions, - GetFormatDataReturn, - GetRequestedCredentialsForProofRequestOptions, - ProofRequestFromProposalOptions, -} from '../../models/ProofServiceOptions' - -import { validateOrReject } from 'class-validator' -import { inject, Lifecycle, scoped } from 'tsyringe' - -import { AgentConfig } from '../../../../agent/AgentConfig' -import { EventEmitter } from '../../../../agent/EventEmitter' -import { InjectionSymbols } from '../../../../constants' -import { AriesFrameworkError } from '../../../../error/AriesFrameworkError' -import { DidCommMessageRole } from '../../../../storage' -import { DidCommMessageRepository } from '../../../../storage/didcomm/DidCommMessageRepository' -import { checkProofRequestForDuplicates } from '../../../../utils' -import { JsonTransformer } from '../../../../utils/JsonTransformer' -import { MessageValidator } from '../../../../utils/MessageValidator' -import { Wallet } from '../../../../wallet' -import { AckStatus } from '../../../common/messages/AckMessage' -import { ConnectionService } from '../../../connections' -import { CredentialRepository } from '../../../credentials' -import { IndyCredentialInfo } from '../../../credentials/formats/indy/models/IndyCredentialInfo' -import { IndyHolderService, IndyRevocationService } from '../../../indy' -import { IndyLedgerService } from '../../../ledger/services/IndyLedgerService' -import { ProofService } from '../../ProofService' -import { PresentationProblemReportReason } from '../../errors/PresentationProblemReportReason' -import { IndyProofFormatService } from '../../formats/indy/IndyProofFormatService' -import { ProofRequest } from '../../formats/indy/models/ProofRequest' -import { RequestedCredentials } from '../../formats/indy/models/RequestedCredentials' -import { ProofState } from '../../models/ProofState' -import { ProofExchangeRecord } from '../../repository/ProofExchangeRecord' -import { ProofRepository } from '../../repository/ProofRepository' - -import { V1PresentationProblemReportError } from './errors' -import { - V1PresentationAckHandler, - V1PresentationHandler, - V1PresentationProblemReportHandler, - V1ProposePresentationHandler, - V1RequestPresentationHandler, -} from './handlers' -import { - INDY_PROOF_ATTACHMENT_ID, - INDY_PROOF_REQUEST_ATTACHMENT_ID, - V1PresentationAckMessage, - V1PresentationMessage, - V1ProposePresentationMessage, - V1RequestPresentationMessage, -} from './messages' -import { V1PresentationProblemReportMessage } from './messages/V1PresentationProblemReportMessage' -import { PresentationPreview } from './models/V1PresentationPreview' - -/** - * @todo add method to check if request matches proposal. Useful to see if a request I received is the same as the proposal I sent. - * @todo add method to reject / revoke messages - * @todo validate attachments / messages - */ -@scoped(Lifecycle.ContainerScoped) -export class V1ProofService extends ProofService<[IndyProofFormat]> { - private credentialRepository: CredentialRepository - private ledgerService: IndyLedgerService - private indyHolderService: IndyHolderService - private indyRevocationService: IndyRevocationService - private indyProofFormatService: IndyProofFormatService - - public constructor( - proofRepository: ProofRepository, - didCommMessageRepository: DidCommMessageRepository, - ledgerService: IndyLedgerService, - @inject(InjectionSymbols.Wallet) wallet: Wallet, - agentConfig: AgentConfig, - connectionService: ConnectionService, - eventEmitter: EventEmitter, - credentialRepository: CredentialRepository, - formatService: IndyProofFormatService, - indyHolderService: IndyHolderService, - indyRevocationService: IndyRevocationService - ) { - super(agentConfig, proofRepository, connectionService, didCommMessageRepository, wallet, eventEmitter) - this.credentialRepository = credentialRepository - this.ledgerService = ledgerService - this.wallet = wallet - this.indyProofFormatService = formatService - this.indyHolderService = indyHolderService - this.indyRevocationService = indyRevocationService - } - - /** - * The version of the present proof protocol this service supports - */ - public readonly version = 'v1' as const - - public async createProposal( - agentContext: AgentContext, - options: CreateProposalOptions<[IndyProofFormat]> - ): Promise<{ proofRecord: ProofExchangeRecord; message: DidCommV1Message }> { - const { connectionRecord, proofFormats } = options - - // Assert - connectionRecord.assertReady() - - if (!proofFormats.indy || Object.keys(proofFormats).length !== 1) { - throw new AriesFrameworkError('Only indy proof format is supported for present proof protocol v1') - } - - const presentationProposal = new PresentationPreview({ - attributes: proofFormats.indy?.attributes, - predicates: proofFormats.indy?.predicates, - }) - - // Create message - const proposalMessage = new V1ProposePresentationMessage({ - comment: options?.comment, - presentationProposal, - parentThreadId: options.parentThreadId, - }) - - // Create record - const proofRecord = new ProofExchangeRecord({ - connectionId: connectionRecord.id, - threadId: proposalMessage.threadId, - parentThreadId: proposalMessage.thread?.parentThreadId, - state: ProofState.ProposalSent, - autoAcceptProof: options?.autoAcceptProof, - protocolVersion: 'v1', - }) - - await this.didCommMessageRepository.saveOrUpdateAgentMessage(agentContext, { - agentMessage: proposalMessage, - associatedRecordId: proofRecord.id, - role: DidCommMessageRole.Sender, - }) - - await this.proofRepository.save(agentContext, proofRecord) - this.emitStateChangedEvent(agentContext, proofRecord, null) - - return { proofRecord, message: proposalMessage } - } - - public async createProposalAsResponse( - agentContext: AgentContext, - options: CreateProposalAsResponseOptions<[IndyProofFormat]> - ): Promise<{ proofRecord: ProofExchangeRecord; message: DidCommV1Message }> { - const { proofRecord, proofFormats, comment } = options - - // Assert - proofRecord.assertState(ProofState.RequestReceived) - - if (!proofFormats.indy || Object.keys(proofFormats).length !== 1) { - throw new AriesFrameworkError('Only indy proof format is supported for present proof protocol v1') - } - - // Create message - const presentationPreview = new PresentationPreview({ - attributes: proofFormats.indy?.attributes, - predicates: proofFormats.indy?.predicates, - }) - - const proposalMessage: V1ProposePresentationMessage = new V1ProposePresentationMessage({ - comment, - presentationProposal: presentationPreview, - }) - - proposalMessage.setThread({ threadId: proofRecord.threadId }) - - await this.didCommMessageRepository.saveOrUpdateAgentMessage(agentContext, { - agentMessage: proposalMessage, - associatedRecordId: proofRecord.id, - role: DidCommMessageRole.Sender, - }) - - // Update record - await this.updateState(agentContext, proofRecord, ProofState.ProposalSent) - - return { proofRecord, message: proposalMessage } - } - - public async processProposal( - messageContext: InboundMessageContext - ): Promise { - let proofRecord: ProofExchangeRecord - const { message: proposalMessage, connection } = messageContext - - this.logger.debug(`Processing presentation proposal with id ${proposalMessage.id}`) - - try { - // Proof record already exists - proofRecord = await this.getByThreadAndConnectionId( - messageContext.agentContext, - proposalMessage.threadId, - connection?.id - ) - - const requestMessage = await this.didCommMessageRepository.findAgentMessage(messageContext.agentContext, { - associatedRecordId: proofRecord.id, - messageClass: V1RequestPresentationMessage, - }) - - // Assert - proofRecord.assertState(ProofState.RequestSent) - this.connectionService.assertConnectionOrServiceDecorator(messageContext, { - previousReceivedMessage: proposalMessage, - previousSentMessage: requestMessage ?? undefined, - }) - - await this.didCommMessageRepository.saveOrUpdateAgentMessage(messageContext.agentContext, { - agentMessage: proposalMessage, - associatedRecordId: proofRecord.id, - role: DidCommMessageRole.Receiver, - }) - - // Update record - await this.updateState(messageContext.agentContext, proofRecord, ProofState.ProposalReceived) - } catch { - // No proof record exists with thread id - proofRecord = new ProofExchangeRecord({ - connectionId: connection?.id, - threadId: proposalMessage.threadId, - parentThreadId: proposalMessage.thread?.parentThreadId, - state: ProofState.ProposalReceived, - protocolVersion: 'v1', - }) - - // Assert - this.connectionService.assertConnectionOrServiceDecorator(messageContext) - - // Save record - await this.didCommMessageRepository.saveOrUpdateAgentMessage(messageContext.agentContext, { - agentMessage: proposalMessage, - associatedRecordId: proofRecord.id, - role: DidCommMessageRole.Sender, - }) - - await this.proofRepository.save(messageContext.agentContext, proofRecord) - - this.emitStateChangedEvent(messageContext.agentContext, proofRecord, null) - } - - return proofRecord - } - - public async createRequestAsResponse( - agentContext: AgentContext, - options: CreateRequestAsResponseOptions<[IndyProofFormat]> - ): Promise<{ proofRecord: ProofExchangeRecord; message: DidCommV1Message }> { - const { proofRecord, comment, proofFormats } = options - if (!proofFormats.indy) { - throw new AriesFrameworkError('Only indy proof format is supported for present proof protocol v1') - } - - // Assert - proofRecord.assertState(ProofState.ProposalReceived) - - // Create message - const { attachment } = await this.indyProofFormatService.createRequest({ - id: INDY_PROOF_REQUEST_ATTACHMENT_ID, - formats: proofFormats, - }) - - const requestPresentationMessage = new V1RequestPresentationMessage({ - comment, - requestPresentationAttachments: [attachment], - }) - requestPresentationMessage.setThread({ - threadId: proofRecord.threadId, - }) - - await this.didCommMessageRepository.saveOrUpdateAgentMessage(agentContext, { - agentMessage: requestPresentationMessage, - associatedRecordId: proofRecord.id, - role: DidCommMessageRole.Sender, - }) - - // Update record - await this.updateState(agentContext, proofRecord, ProofState.RequestSent) - - return { message: requestPresentationMessage, proofRecord } - } - - public async createRequest( - agentContext: AgentContext, - options: CreateRequestOptions<[IndyProofFormat]> - ): Promise<{ proofRecord: ProofExchangeRecord; message: DidCommV1Message }> { - this.logger.debug(`Creating proof request`) - - // Assert - if (options.connectionRecord) { - options.connectionRecord.assertReady() - } - - if (!options.proofFormats.indy || Object.keys(options.proofFormats).length !== 1) { - throw new AriesFrameworkError('Only indy proof format is supported for present proof protocol v1') - } - - // Create message - const { attachment } = await this.indyProofFormatService.createRequest({ - id: INDY_PROOF_REQUEST_ATTACHMENT_ID, - formats: options.proofFormats, - }) - - const requestPresentationMessage = new V1RequestPresentationMessage({ - comment: options?.comment, - requestPresentationAttachments: [attachment], - parentThreadId: options.parentThreadId, - }) - - // Create record - const proofRecord = new ProofExchangeRecord({ - connectionId: options.connectionRecord?.id, - threadId: requestPresentationMessage.threadId, - parentThreadId: requestPresentationMessage.thread?.parentThreadId, - state: ProofState.RequestSent, - autoAcceptProof: options?.autoAcceptProof, - protocolVersion: 'v1', - }) - - await this.didCommMessageRepository.saveOrUpdateAgentMessage(agentContext, { - agentMessage: requestPresentationMessage, - associatedRecordId: proofRecord.id, - role: DidCommMessageRole.Sender, - }) - - await this.proofRepository.save(agentContext, proofRecord) - this.emitStateChangedEvent(agentContext, proofRecord, null) - - return { message: requestPresentationMessage, proofRecord } - } - - public async processRequest( - messageContext: InboundMessageContext - ): Promise { - let proofRecord: ProofExchangeRecord - const { message: proofRequestMessage, connection } = messageContext - - this.logger.debug(`Processing presentation request with id ${proofRequestMessage.id}`) - - const requestAttachments = proofRequestMessage.getAttachmentFormats() - - for (const attachmentFormat of requestAttachments) { - await this.indyProofFormatService.processRequest({ - requestAttachment: attachmentFormat, - }) - } - - const proofRequest = proofRequestMessage.indyProofRequest - - // Assert attachment - if (!proofRequest) { - throw new V1PresentationProblemReportError( - `Missing required base64 or json encoded attachment data for presentation request with thread id ${proofRequestMessage.threadId}`, - { problemCode: PresentationProblemReportReason.Abandoned } - ) - } - await validateOrReject(proofRequest) - - // Assert attribute and predicate (group) names do not match - checkProofRequestForDuplicates(proofRequest) - - this.logger.debug('received proof request', proofRequest) - - try { - // Proof record already exists - proofRecord = await this.getByThreadAndConnectionId( - messageContext.agentContext, - proofRequestMessage.threadId, - connection?.id - ) - - const requestMessage = await this.didCommMessageRepository.findAgentMessage(messageContext.agentContext, { - associatedRecordId: proofRecord.id, - messageClass: V1RequestPresentationMessage, - }) - - const proposalMessage = await this.didCommMessageRepository.findAgentMessage(messageContext.agentContext, { - associatedRecordId: proofRecord.id, - messageClass: V1ProposePresentationMessage, - }) - - // Assert - proofRecord.assertState(ProofState.ProposalSent) - this.connectionService.assertConnectionOrServiceDecorator(messageContext, { - previousReceivedMessage: requestMessage ?? undefined, - previousSentMessage: proposalMessage ?? undefined, - }) - - await this.didCommMessageRepository.saveOrUpdateAgentMessage(messageContext.agentContext, { - agentMessage: proofRequestMessage, - associatedRecordId: proofRecord.id, - role: DidCommMessageRole.Receiver, - }) - - // Update record - await this.updateState(messageContext.agentContext, proofRecord, ProofState.RequestReceived) - } catch { - // No proof record exists with thread id - proofRecord = new ProofExchangeRecord({ - connectionId: connection?.id, - threadId: proofRequestMessage.threadId, - parentThreadId: proofRequestMessage.thread?.parentThreadId, - state: ProofState.RequestReceived, - protocolVersion: 'v1', - }) - - await this.didCommMessageRepository.saveOrUpdateAgentMessage(messageContext.agentContext, { - agentMessage: proofRequestMessage, - associatedRecordId: proofRecord.id, - role: DidCommMessageRole.Receiver, - }) - - // Assert - this.connectionService.assertConnectionOrServiceDecorator(messageContext) - - // Save in repository - await this.proofRepository.save(messageContext.agentContext, proofRecord) - this.emitStateChangedEvent(messageContext.agentContext, proofRecord, null) - } - - return proofRecord - } - - public async createPresentation( - agentContext: AgentContext, - options: CreatePresentationOptions<[IndyProofFormat]> - ): Promise<{ proofRecord: ProofExchangeRecord; message: DidCommV1Message }> { - const { proofRecord, proofFormats } = options - - this.logger.debug(`Creating presentation for proof record with id ${proofRecord.id}`) - - if (!proofFormats.indy || Object.keys(proofFormats).length !== 1) { - throw new AriesFrameworkError('Only indy proof format is supported for present proof protocol v1') - } - - // Assert - proofRecord.assertState(ProofState.RequestReceived) - - const requestMessage = await this.didCommMessageRepository.findAgentMessage(agentContext, { - associatedRecordId: proofRecord.id, - messageClass: V1RequestPresentationMessage, - }) - - const requestAttachment = requestMessage?.indyAttachment - - if (!requestAttachment) { - throw new V1PresentationProblemReportError( - `Missing required base64 or json encoded attachment data for presentation with thread id ${proofRecord.threadId}`, - { problemCode: PresentationProblemReportReason.Abandoned } - ) - } - - const presentationOptions: FormatCreatePresentationOptions = { - id: INDY_PROOF_ATTACHMENT_ID, - attachment: requestAttachment, - proofFormats: proofFormats, - } - - const proof = await this.indyProofFormatService.createPresentation(agentContext, presentationOptions) - - // Extract proof request from attachment - const proofRequestJson = requestAttachment.getDataAsJson() ?? null - const proofRequest = JsonTransformer.fromJSON(proofRequestJson, ProofRequest) - - const requestedCredentials = new RequestedCredentials({ - requestedAttributes: proofFormats.indy?.requestedAttributes, - requestedPredicates: proofFormats.indy?.requestedPredicates, - selfAttestedAttributes: proofFormats.indy?.selfAttestedAttributes, - }) - - // Get the matching attachments to the requested credentials - const linkedAttachments = await this.getRequestedAttachmentsForRequestedCredentials( - agentContext, - proofRequest, - requestedCredentials - ) - - const presentationMessage = new V1PresentationMessage({ - comment: options?.comment, - presentationAttachments: [proof.attachment], - attachments: linkedAttachments, - }) - presentationMessage.setThread({ threadId: proofRecord.threadId }) - - await this.didCommMessageRepository.saveOrUpdateAgentMessage(agentContext, { - agentMessage: presentationMessage, - associatedRecordId: proofRecord.id, - role: DidCommMessageRole.Sender, - }) - - // Update record - await this.updateState(agentContext, proofRecord, ProofState.PresentationSent) - - return { message: presentationMessage, proofRecord } - } - - public async processPresentation( - messageContext: InboundMessageContext - ): Promise { - const { message: presentationMessage, connection } = messageContext - - this.logger.debug(`Processing presentation with id ${presentationMessage.id}`) - - const proofRecord = await this.getByThreadAndConnectionId( - messageContext.agentContext, - presentationMessage.threadId, - connection?.id - ) - - const proposalMessage = await this.didCommMessageRepository.findAgentMessage(messageContext.agentContext, { - associatedRecordId: proofRecord.id, - messageClass: V1ProposePresentationMessage, - }) - - const requestMessage = await this.didCommMessageRepository.getAgentMessage(messageContext.agentContext, { - associatedRecordId: proofRecord.id, - messageClass: V1RequestPresentationMessage, - }) - - // Assert - proofRecord.assertState(ProofState.RequestSent) - this.connectionService.assertConnectionOrServiceDecorator(messageContext, { - previousReceivedMessage: proposalMessage ?? undefined, - previousSentMessage: requestMessage ?? undefined, - }) - - try { - const isValid = await this.indyProofFormatService.processPresentation(messageContext.agentContext, { - record: proofRecord, - formatAttachments: { - presentation: presentationMessage.getAttachmentFormats(), - request: requestMessage.getAttachmentFormats(), - }, - }) - await this.didCommMessageRepository.saveOrUpdateAgentMessage(messageContext.agentContext, { - agentMessage: presentationMessage, - associatedRecordId: proofRecord.id, - role: DidCommMessageRole.Receiver, - }) - - // Update record - proofRecord.isVerified = isValid - await this.updateState(messageContext.agentContext, proofRecord, ProofState.PresentationReceived) - } catch (e) { - if (e instanceof AriesFrameworkError) { - throw new V1PresentationProblemReportError(e.message, { - problemCode: PresentationProblemReportReason.Abandoned, - }) - } - throw e - } - - return proofRecord - } - - public async processAck( - messageContext: InboundMessageContext - ): Promise { - const { message: presentationAckMessage, connection } = messageContext - - this.logger.debug(`Processing presentation ack with id ${presentationAckMessage.id}`) - - const proofRecord = await this.getByThreadAndConnectionId( - messageContext.agentContext, - presentationAckMessage.threadId, - connection?.id - ) - - const requestMessage = await this.didCommMessageRepository.findAgentMessage(messageContext.agentContext, { - associatedRecordId: proofRecord.id, - messageClass: V1RequestPresentationMessage, - }) - - const presentationMessage = await this.didCommMessageRepository.findAgentMessage(messageContext.agentContext, { - associatedRecordId: proofRecord.id, - messageClass: V1PresentationMessage, - }) - - // Assert - proofRecord.assertState(ProofState.PresentationSent) - this.connectionService.assertConnectionOrServiceDecorator(messageContext, { - previousReceivedMessage: requestMessage ?? undefined, - previousSentMessage: presentationMessage ?? undefined, - }) - - // Update record - await this.updateState(messageContext.agentContext, proofRecord, ProofState.Done) - - return proofRecord - } - - public async createProblemReport( - agentContext: AgentContext, - options: CreateProblemReportOptions - ): Promise<{ proofRecord: ProofExchangeRecord; message: DidCommV1Message }> { - const msg = new V1PresentationProblemReportMessage({ - description: { - code: PresentationProblemReportReason.Abandoned, - en: options.description, - }, - }) - - msg.setThread({ - threadId: options.proofRecord.threadId, - parentThreadId: options.proofRecord.parentThreadId, - }) - - return { - proofRecord: options.proofRecord, - message: msg, - } - } - - public async processProblemReport( - messageContext: InboundMessageContext - ): Promise { - const { message: presentationProblemReportMessage } = messageContext - - const connection = messageContext.assertReadyConnection() - - this.logger.debug(`Processing problem report with id ${presentationProblemReportMessage.id}`) - - const proofRecord = await this.getByThreadAndConnectionId( - messageContext.agentContext, - presentationProblemReportMessage.threadId, - connection?.id - ) - - proofRecord.errorMessage = `${presentationProblemReportMessage.description.code}: ${presentationProblemReportMessage.description.en}` - await this.updateState(messageContext.agentContext, proofRecord, ProofState.Abandoned) - return proofRecord - } - - public async createProofRequestFromProposal( - agentContext: AgentContext, - options: CreateProofRequestFromProposalOptions - ): Promise> { - const proofRecordId = options.proofRecord.id - const proposalMessage = await this.didCommMessageRepository.findAgentMessage(agentContext, { - associatedRecordId: proofRecordId, - messageClass: V1ProposePresentationMessage, - }) - - if (!proposalMessage) { - throw new AriesFrameworkError(`Proof record with id ${proofRecordId} is missing required presentation proposal`) - } - - const indyProposeProofFormat: IndyProposeProofFormat = { - name: 'Proof Request', - version: '1.0', - nonce: await this.wallet.generateNonce(), - } - - const proofRequest: ProofRequest = await this.indyProofFormatService.createReferentForProofRequest( - indyProposeProofFormat, - proposalMessage.presentationProposal - ) - - return { - proofRecord: options.proofRecord, - proofFormats: { - indy: proofRequest, - }, - } - } - - /** - * Retrieves the linked attachments for an {@link indyProofRequest} - * @param indyProofRequest The proof request for which the linked attachments have to be found - * @param requestedCredentials The requested credentials - * @returns a list of attachments that are linked to the requested credentials - */ - public async getRequestedAttachmentsForRequestedCredentials( - agentContext: AgentContext, - indyProofRequest: ProofRequest, - requestedCredentials: RequestedCredentials - ): Promise { - const attachments: V1Attachment[] = [] - const credentialIds = new Set() - const requestedAttributesNames: (string | undefined)[] = [] - - // Get the credentialIds if it contains a hashlink - for (const [referent, requestedAttribute] of Object.entries(requestedCredentials.requestedAttributes)) { - // Find the requested Attributes - const requestedAttributes = indyProofRequest.requestedAttributes.get(referent) as ProofAttributeInfo - - // List the requested attributes - requestedAttributesNames.push(...(requestedAttributes.names ?? [requestedAttributes.name])) - - //Get credentialInfo - if (!requestedAttribute.credentialInfo) { - const indyCredentialInfo = await this.indyHolderService.getCredential( - agentContext, - requestedAttribute.credentialId - ) - requestedAttribute.credentialInfo = JsonTransformer.fromJSON(indyCredentialInfo, IndyCredentialInfo) - } - - // Find the attributes that have a hashlink as a value - for (const attribute of Object.values(requestedAttribute.credentialInfo.attributes)) { - if (attribute.toLowerCase().startsWith('hl:')) { - credentialIds.add(requestedAttribute.credentialId) - } - } - } - - // Only continues if there is an attribute value that contains a hashlink - for (const credentialId of credentialIds) { - // Get the credentialRecord that matches the ID - - const credentialRecord = await this.credentialRepository.getSingleByQuery(agentContext, { - credentialIds: [credentialId], - }) - - if (credentialRecord.linkedAttachments) { - // Get the credentials that have a hashlink as value and are requested - const requestedCredentials = credentialRecord.credentialAttributes?.filter( - (credential) => - credential.value.toLowerCase().startsWith('hl:') && requestedAttributesNames.includes(credential.name) - ) - - // Get the linked attachments that match the requestedCredentials - const linkedAttachments = credentialRecord.linkedAttachments.filter((attachment) => - requestedCredentials?.map((credential) => credential.value.split(':')[1]).includes(attachment.id) - ) - - if (linkedAttachments) { - attachments.push(...linkedAttachments) - } - } - } - - return attachments.length ? attachments : undefined - } - - public async shouldAutoRespondToProposal( - agentContext: AgentContext, - proofRecord: ProofExchangeRecord - ): Promise { - const proposal = await this.didCommMessageRepository.findAgentMessage(agentContext, { - associatedRecordId: proofRecord.id, - messageClass: V1ProposePresentationMessage, - }) - - if (!proposal) return false - MessageValidator.validateSync(proposal) - - // check the proposal against a possible previous request - const request = await this.didCommMessageRepository.findAgentMessage(agentContext, { - associatedRecordId: proofRecord.id, - messageClass: V1RequestPresentationMessage, - }) - - if (!request) return false - - const proofRequest = request.indyProofRequest - - if (!proofRequest) { - throw new V1PresentationProblemReportError( - `Missing required base64 or json encoded attachment data for presentation request with thread id ${request.threadId}`, - { problemCode: PresentationProblemReportReason.Abandoned } - ) - } - await validateOrReject(proofRequest) - - // Assert attribute and predicate (group) names do not match - checkProofRequestForDuplicates(proofRequest) - - const proposalAttributes = proposal.presentationProposal.attributes - const requestedAttributes = proofRequest.requestedAttributes - - const proposedAttributeNames = proposalAttributes.map((x) => x.name) - let requestedAttributeNames: string[] = [] - - const requestedAttributeList = Array.from(requestedAttributes.values()) - - requestedAttributeList.forEach((x) => { - if (x.name) { - requestedAttributeNames.push(x.name) - } else if (x.names) { - requestedAttributeNames = requestedAttributeNames.concat(x.names) - } - }) - - if (requestedAttributeNames.length > proposedAttributeNames.length) { - // more attributes are requested than have been proposed - return false - } - - requestedAttributeNames.forEach((x) => { - if (!proposedAttributeNames.includes(x)) { - this.logger.debug(`Attribute ${x} was requested but wasn't proposed.`) - return false - } - }) - - // assert that all requested attributes are provided - const providedPredicateNames = proposal.presentationProposal.predicates.map((x) => x.name) - proofRequest.requestedPredicates.forEach((x) => { - if (!providedPredicateNames.includes(x.name)) { - return false - } - }) - return true - } - - public async shouldAutoRespondToRequest( - agentContext: AgentContext, - proofRecord: ProofExchangeRecord - ): Promise { - const proposal = await this.didCommMessageRepository.findAgentMessage(agentContext, { - associatedRecordId: proofRecord.id, - messageClass: V1ProposePresentationMessage, - }) - - if (!proposal) { - return false - } - - const request = await this.didCommMessageRepository.findAgentMessage(agentContext, { - associatedRecordId: proofRecord.id, - messageClass: V1RequestPresentationMessage, - }) - - if (!request) { - throw new AriesFrameworkError( - `Expected to find a request message for ProofExchangeRecord with id ${proofRecord.id}` - ) - } - - const proofRequest = request.indyProofRequest - - // Assert attachment - if (!proofRequest) { - throw new V1PresentationProblemReportError( - `Missing required base64 or json encoded attachment data for presentation request with thread id ${request.threadId}`, - { problemCode: PresentationProblemReportReason.Abandoned } - ) - } - await validateOrReject(proofRequest) - - // Assert attribute and predicate (group) names do not match - checkProofRequestForDuplicates(proofRequest) - - const proposalAttributes = proposal.presentationProposal.attributes - const requestedAttributes = proofRequest.requestedAttributes - - const proposedAttributeNames = proposalAttributes.map((x) => x.name) - let requestedAttributeNames: string[] = [] - - const requestedAttributeList = Array.from(requestedAttributes.values()) - - requestedAttributeList.forEach((x) => { - if (x.name) { - requestedAttributeNames.push(x.name) - } else if (x.names) { - requestedAttributeNames = requestedAttributeNames.concat(x.names) - } - }) - - if (requestedAttributeNames.length > proposedAttributeNames.length) { - // more attributes are requested than have been proposed - return false - } - - requestedAttributeNames.forEach((x) => { - if (!proposedAttributeNames.includes(x)) { - this.logger.debug(`Attribute ${x} was requested but wasn't proposed.`) - return false - } - }) - - // assert that all requested attributes are provided - const providedPredicateNames = proposal.presentationProposal.predicates.map((x) => x.name) - proofRequest.requestedPredicates.forEach((x) => { - if (!providedPredicateNames.includes(x.name)) { - return false - } - }) - - return true - } - - public async shouldAutoRespondToPresentation( - agentContext: AgentContext, - proofRecord: ProofExchangeRecord - ): Promise { - this.logger.debug(`Should auto respond to presentation for proof record id: ${proofRecord.id}`) - return true - } - - public async getRequestedCredentialsForProofRequest( - agentContext: AgentContext, - options: GetRequestedCredentialsForProofRequestOptions - ): Promise> { - const requestMessage = await this.didCommMessageRepository.findAgentMessage(agentContext, { - associatedRecordId: options.proofRecord.id, - messageClass: V1RequestPresentationMessage, - }) - - const proposalMessage = await this.didCommMessageRepository.findAgentMessage(agentContext, { - associatedRecordId: options.proofRecord.id, - messageClass: V1ProposePresentationMessage, - }) - - const indyProofRequest = requestMessage?.requestPresentationAttachments - - if (!indyProofRequest) { - throw new AriesFrameworkError('Could not find proof request') - } - - const requestedCredentials: FormatRetrievedCredentialOptions<[IndyProofFormat]> = - await this.indyProofFormatService.getRequestedCredentialsForProofRequest(agentContext, { - attachment: indyProofRequest[0], - presentationProposal: proposalMessage?.presentationProposal, - config: options.config ?? undefined, - }) - return requestedCredentials - } - - public async autoSelectCredentialsForProofRequest( - options: FormatRetrievedCredentialOptions - ): Promise> { - return await this.indyProofFormatService.autoSelectCredentialsForProofRequest(options) - } - - public registerMessageHandlers( - dispatcher: Dispatcher, - agentConfig: AgentConfig, - proofResponseCoordinator: ProofResponseCoordinator, - mediationRecipientService: MediationRecipientService, - routingService: RoutingService - ): void { - dispatcher.registerMessageHandler( - new V1ProposePresentationHandler(this, agentConfig, proofResponseCoordinator, this.didCommMessageRepository) - ) - - dispatcher.registerMessageHandler( - new V1RequestPresentationHandler( - this, - agentConfig, - proofResponseCoordinator, - mediationRecipientService, - this.didCommMessageRepository, - routingService - ) - ) - - dispatcher.registerMessageHandler( - new V1PresentationHandler(this, agentConfig, proofResponseCoordinator, this.didCommMessageRepository) - ) - dispatcher.registerMessageHandler(new V1PresentationAckHandler(this)) - dispatcher.registerMessageHandler(new V1PresentationProblemReportHandler(this)) - } - - public async findRequestMessage( - agentContext: AgentContext, - proofRecordId: string - ): Promise { - return await this.didCommMessageRepository.findAgentMessage(agentContext, { - associatedRecordId: proofRecordId, - messageClass: V1RequestPresentationMessage, - }) - } - public async findPresentationMessage( - agentContext: AgentContext, - proofRecordId: string - ): Promise { - return await this.didCommMessageRepository.findAgentMessage(agentContext, { - associatedRecordId: proofRecordId, - messageClass: V1PresentationMessage, - }) - } - - public async findProposalMessage( - agentContext: AgentContext, - proofRecordId: string - ): Promise { - return await this.didCommMessageRepository.findAgentMessage(agentContext, { - associatedRecordId: proofRecordId, - messageClass: V1ProposePresentationMessage, - }) - } - - public async getFormatData( - agentContext: AgentContext, - proofRecordId: string - ): Promise> { - const [proposalMessage, requestMessage, presentationMessage] = await Promise.all([ - this.findProposalMessage(agentContext, proofRecordId), - this.findRequestMessage(agentContext, proofRecordId), - this.findPresentationMessage(agentContext, proofRecordId), - ]) - - const indyProposeProof = proposalMessage - ? JsonTransformer.toJSON(await this.rfc0592ProposalFromV1ProposeMessage(proposalMessage)) - : undefined - const indyRequestProof = requestMessage?.indyProofRequestJson ?? undefined - const indyPresentProof = presentationMessage?.indyProof ?? undefined - - return { - proposal: proposalMessage - ? { - indy: indyProposeProof, - } - : undefined, - request: requestMessage - ? { - indy: indyRequestProof, - } - : undefined, - presentation: presentationMessage - ? { - indy: indyPresentProof, - } - : undefined, - } - } - - private async rfc0592ProposalFromV1ProposeMessage( - proposalMessage: V1ProposePresentationMessage - ): Promise { - const indyFormat: IndyProposeProofFormat = { - name: 'Proof Request', - version: '1.0', - nonce: await this.wallet.generateNonce(), - attributes: proposalMessage.presentationProposal.attributes, - predicates: proposalMessage.presentationProposal.predicates, - } - - if (!indyFormat) { - throw new AriesFrameworkError('No Indy format found.') - } - - const preview = new PresentationPreview({ - attributes: indyFormat.attributes, - predicates: indyFormat.predicates, - }) - - return this.indyProofFormatService.createReferentForProofRequest(indyFormat, preview) - } - /** - * Retrieve all proof records - * - * @returns List containing all proof records - */ - public async getAll(agentContext: AgentContext): Promise { - return this.proofRepository.getAll(agentContext) - } - - /** - * Retrieve a proof record by connection id and thread id - * - * @param connectionId The connection id - * @param threadId The thread id - * @throws {RecordNotFoundError} If no record is found - * @throws {RecordDuplicateError} If multiple records are found - * @returns The proof record - */ - public async getByThreadAndConnectionId( - agentContext: AgentContext, - threadId: string, - connectionId?: string - ): Promise { - return this.proofRepository.getSingleByQuery(agentContext, { threadId, connectionId }) - } - - public async createAck( - gentContext: AgentContext, - options: CreateAckOptions - ): Promise<{ proofRecord: ProofExchangeRecord; message: DidCommV1Message }> { - const { proofRecord } = options - this.logger.debug(`Creating presentation ack for proof record with id ${proofRecord.id}`) - - // Assert - proofRecord.assertState(ProofState.PresentationReceived) - - // Create message - const ackMessage = new V1PresentationAckMessage({ - status: AckStatus.OK, - threadId: proofRecord.threadId, - }) - - // Update record - await this.updateState(gentContext, proofRecord, ProofState.Done) - - return { message: ackMessage, proofRecord } - } -} diff --git a/packages/core/src/modules/proofs/protocol/v1/__tests__/indy-proof-negotiation.test.ts b/packages/core/src/modules/proofs/protocol/v1/__tests__/indy-proof-negotiation.test.ts deleted file mode 100644 index 19134854a8..0000000000 --- a/packages/core/src/modules/proofs/protocol/v1/__tests__/indy-proof-negotiation.test.ts +++ /dev/null @@ -1,355 +0,0 @@ -import type { Agent } from '../../../../../agent/Agent' -import type { ConnectionRecord } from '../../../../connections/repository/ConnectionRecord' -import type { AcceptProofProposalOptions, NegotiateProposalOptions } from '../../../ProofsApiOptions' -import type { ProofExchangeRecord } from '../../../repository/ProofExchangeRecord' -import type { PresentationPreview } from '../models/V1PresentationPreview' -import type { CredDefId } from 'indy-sdk' - -import { setupProofsTest, waitForProofExchangeRecord } from '../../../../../../tests/helpers' -import testLogger from '../../../../../../tests/logger' -import { DidCommMessageRepository } from '../../../../../storage/didcomm' -import { AttributeFilter } from '../../../formats/indy/models/AttributeFilter' -import { PredicateType } from '../../../formats/indy/models/PredicateType' -import { ProofAttributeInfo } from '../../../formats/indy/models/ProofAttributeInfo' -import { ProofPredicateInfo } from '../../../formats/indy/models/ProofPredicateInfo' -import { ProofState } from '../../../models/ProofState' -import { V1ProposePresentationMessage, V1RequestPresentationMessage } from '../messages' - -describe('Present Proof', () => { - let faberAgent: Agent - let aliceAgent: Agent - let credDefId: CredDefId - let aliceConnection: ConnectionRecord - let presentationPreview: PresentationPreview - let faberProofExchangeRecord: ProofExchangeRecord - let aliceProofExchangeRecord: ProofExchangeRecord - let didCommMessageRepository: DidCommMessageRepository - - beforeAll(async () => { - testLogger.test('Initializing the agents') - ;({ faberAgent, aliceAgent, credDefId, aliceConnection, presentationPreview } = await setupProofsTest( - 'Faber agent', - 'Alice agent' - )) - }) - - afterAll(async () => { - testLogger.test('Shutting down both agents') - await faberAgent.shutdown() - await faberAgent.wallet.delete() - await aliceAgent.shutdown() - await aliceAgent.wallet.delete() - }) - - test(`Proof negotiation between Alice and Faber`, async () => { - testLogger.test('Alice sends proof proposal to Faber') - - let faberProofExchangeRecordPromise = waitForProofExchangeRecord(faberAgent, { - state: ProofState.ProposalReceived, - }) - - aliceProofExchangeRecord = await aliceAgent.proofs.proposeProof({ - connectionId: aliceConnection.id, - protocolVersion: 'v1', - proofFormats: { - indy: { - name: 'proof-request', - nonce: '58d223e5-fc4d-4448-b74c-5eb11c6b558f', - version: '1.0', - attributes: presentationPreview.attributes.filter((attribute) => attribute.name !== 'name'), - predicates: presentationPreview.predicates, - }, - }, - comment: 'V1 propose proof test 1', - }) - - testLogger.test('Faber waits for presentation from Alice') - faberProofExchangeRecord = await faberProofExchangeRecordPromise - - didCommMessageRepository = faberAgent.injectionContainer.resolve(DidCommMessageRepository) - - let proposal = await didCommMessageRepository.findAgentMessage(faberAgent.context, { - associatedRecordId: faberProofExchangeRecord.id, - messageClass: V1ProposePresentationMessage, - }) - - expect(proposal).toMatchObject({ - type: 'https://didcomm.org/present-proof/1.0/propose-presentation', - id: expect.any(String), - comment: 'V1 propose proof test 1', - presentationProposal: { - type: 'https://didcomm.org/present-proof/1.0/presentation-preview', - attributes: [ - { - name: 'image_0', - credentialDefinitionId: presentationPreview.attributes[1].credentialDefinitionId, - }, - ], - predicates: [ - { - name: 'age', - credentialDefinitionId: presentationPreview.predicates[0].credentialDefinitionId, - predicate: '>=', - threshold: 50, - }, - ], - }, - }) - expect(faberProofExchangeRecord).toMatchObject({ - id: expect.anything(), - threadId: faberProofExchangeRecord.threadId, - state: ProofState.ProposalReceived, - protocolVersion: 'v1', - }) - - // Negotiate Proposal - const attributes = { - name: new ProofAttributeInfo({ - name: 'name', - restrictions: [ - new AttributeFilter({ - credentialDefinitionId: credDefId, - }), - ], - }), - image_0: new ProofAttributeInfo({ - name: 'image_0', - restrictions: [ - new AttributeFilter({ - credentialDefinitionId: credDefId, - }), - ], - }), - } - - const predicates = { - age: new ProofPredicateInfo({ - name: 'age', - predicateType: PredicateType.GreaterThanOrEqualTo, - predicateValue: 50, - restrictions: [ - new AttributeFilter({ - credentialDefinitionId: credDefId, - }), - ], - }), - } - - const requestProofAsResponseOptions: NegotiateProposalOptions = { - proofRecordId: faberProofExchangeRecord.id, - proofFormats: { - indy: { - name: 'proof-request', - nonce: '58d223e5-fc4d-4448-b74c-5eb11c6b558f', - version: '1.0', - requestedAttributes: attributes, - requestedPredicates: predicates, - }, - }, - } - - let aliceProofExchangeRecordPromise = waitForProofExchangeRecord(aliceAgent, { - threadId: faberProofExchangeRecord.threadId, - state: ProofState.RequestReceived, - }) - - testLogger.test('Faber sends new proof request to Alice') - faberProofExchangeRecord = await faberAgent.proofs.negotiateProposal(requestProofAsResponseOptions) - - testLogger.test('Alice waits for proof request from Faber') - aliceProofExchangeRecord = await aliceProofExchangeRecordPromise - - didCommMessageRepository = faberAgent.injectionContainer.resolve(DidCommMessageRepository) - - let request = await didCommMessageRepository.findAgentMessage(faberAgent.context, { - associatedRecordId: faberProofExchangeRecord.id, - messageClass: V1RequestPresentationMessage, - }) - - expect(request).toMatchObject({ - type: 'https://didcomm.org/present-proof/1.0/request-presentation', - id: expect.any(String), - requestPresentationAttachments: [ - { - id: 'libindy-request-presentation-0', - mimeType: 'application/json', - data: { - base64: expect.any(String), - }, - }, - ], - thread: { - threadId: faberProofExchangeRecord.threadId, - }, - }) - expect(aliceProofExchangeRecord).toMatchObject({ - id: expect.anything(), - threadId: faberProofExchangeRecord.threadId, - state: ProofState.RequestReceived, - protocolVersion: 'v1', - }) - - testLogger.test('Alice sends proof proposal to Faber') - - faberProofExchangeRecordPromise = waitForProofExchangeRecord(faberAgent, { - state: ProofState.ProposalReceived, - }) - - aliceProofExchangeRecord = await aliceAgent.proofs.negotiateRequest({ - proofRecordId: aliceProofExchangeRecord.id, - proofFormats: { - indy: { - name: 'proof-request', - nonce: '58d223e5-fc4d-4448-b74c-5eb11c6b558f', - version: '1.0', - attributes: presentationPreview.attributes.filter((attribute) => attribute.name === 'name'), - predicates: presentationPreview.predicates, - }, - }, - comment: 'V1 propose proof test 2', - }) - - testLogger.test('Faber waits for presentation from Alice') - faberProofExchangeRecord = await faberProofExchangeRecordPromise - - didCommMessageRepository = faberAgent.injectionContainer.resolve(DidCommMessageRepository) - - proposal = await didCommMessageRepository.findAgentMessage(faberAgent.context, { - associatedRecordId: faberProofExchangeRecord.id, - messageClass: V1ProposePresentationMessage, - }) - - expect(proposal).toMatchObject({ - type: 'https://didcomm.org/present-proof/1.0/propose-presentation', - id: expect.any(String), - comment: 'V1 propose proof test 2', - presentationProposal: { - type: 'https://didcomm.org/present-proof/1.0/presentation-preview', - attributes: [ - { - name: 'name', - credentialDefinitionId: presentationPreview.attributes[0].credentialDefinitionId, - value: 'John', - referent: '0', - }, - ], - predicates: [ - { - name: 'age', - credentialDefinitionId: presentationPreview.predicates[0].credentialDefinitionId, - predicate: '>=', - threshold: 50, - }, - ], - }, - }) - expect(faberProofExchangeRecord).toMatchObject({ - id: expect.anything(), - threadId: faberProofExchangeRecord.threadId, - state: ProofState.ProposalReceived, - protocolVersion: 'v1', - }) - - // Accept Proposal - const acceptProposalOptions: AcceptProofProposalOptions = { - proofRecordId: faberProofExchangeRecord.id, - } - - aliceProofExchangeRecordPromise = waitForProofExchangeRecord(aliceAgent, { - threadId: faberProofExchangeRecord.threadId, - state: ProofState.RequestReceived, - }) - - testLogger.test('Faber accepts presentation proposal from Alice') - faberProofExchangeRecord = await faberAgent.proofs.acceptProposal(acceptProposalOptions) - - testLogger.test('Alice waits for proof request from Faber') - aliceProofExchangeRecord = await aliceProofExchangeRecordPromise - - didCommMessageRepository = faberAgent.injectionContainer.resolve(DidCommMessageRepository) - - request = await didCommMessageRepository.findAgentMessage(faberAgent.context, { - associatedRecordId: faberProofExchangeRecord.id, - messageClass: V1RequestPresentationMessage, - }) - - expect(request).toMatchObject({ - type: 'https://didcomm.org/present-proof/1.0/request-presentation', - id: expect.any(String), - requestPresentationAttachments: [ - { - id: 'libindy-request-presentation-0', - mimeType: 'application/json', - data: { - base64: expect.any(String), - }, - }, - ], - thread: { - threadId: faberProofExchangeRecord.threadId, - }, - }) - expect(aliceProofExchangeRecord).toMatchObject({ - id: expect.anything(), - threadId: faberProofExchangeRecord.threadId, - state: ProofState.RequestReceived, - protocolVersion: 'v1', - }) - - const presentationProposalMessage = await aliceAgent.proofs.findProposalMessage(aliceProofExchangeRecord.id) - - expect(presentationProposalMessage).toMatchObject({ - type: 'https://didcomm.org/present-proof/1.0/propose-presentation', - id: expect.any(String), - comment: 'V1 propose proof test 2', - presentationProposal: { - type: 'https://didcomm.org/present-proof/1.0/presentation-preview', - attributes: [ - { - name: 'name', - credentialDefinitionId: presentationPreview.attributes[0].credentialDefinitionId, - value: 'John', - referent: '0', - }, - ], - predicates: [ - { - name: 'age', - credentialDefinitionId: presentationPreview.predicates[0].credentialDefinitionId, - predicate: '>=', - threshold: 50, - }, - ], - }, - }) - - const proofRequestMessage = (await aliceAgent.proofs.findRequestMessage( - aliceProofExchangeRecord.id - )) as V1RequestPresentationMessage - - const predicateKey = proofRequestMessage.indyProofRequest?.requestedPredicates?.keys().next().value - const predicate = Object.values(predicates)[0] - - expect(proofRequestMessage.indyProofRequest).toMatchObject({ - name: 'Proof Request', - version: '1.0', - requestedAttributes: new Map( - Object.entries({ - '0': new ProofAttributeInfo({ - name: 'name', - restrictions: [ - new AttributeFilter({ - credentialDefinitionId: credDefId, - }), - ], - }), - }) - ), - requestedPredicates: new Map( - Object.entries({ - [predicateKey]: predicate, - }) - ), - }) - }) -}) diff --git a/packages/core/src/modules/proofs/protocol/v1/__tests__/indy-proof-proposal.test.ts b/packages/core/src/modules/proofs/protocol/v1/__tests__/indy-proof-proposal.test.ts deleted file mode 100644 index e2b2df04ff..0000000000 --- a/packages/core/src/modules/proofs/protocol/v1/__tests__/indy-proof-proposal.test.ts +++ /dev/null @@ -1,104 +0,0 @@ -import type { Agent } from '../../../../../agent/Agent' -import type { ConnectionRecord } from '../../../../connections/repository/ConnectionRecord' -import type { ProofExchangeRecord } from '../../../repository/ProofExchangeRecord' -import type { PresentationPreview } from '../models/V1PresentationPreview' - -import { setupProofsTest, waitForProofExchangeRecord } from '../../../../../../tests/helpers' -import testLogger from '../../../../../../tests/logger' -import { DidCommMessageRepository } from '../../../../../storage' -import { ProofState } from '../../../models/ProofState' -import { V1ProposePresentationMessage } from '../messages' - -describe('Present Proof', () => { - let faberAgent: Agent - let aliceAgent: Agent - let aliceConnection: ConnectionRecord - let presentationPreview: PresentationPreview - let faberProofExchangeRecord: ProofExchangeRecord - let didCommMessageRepository: DidCommMessageRepository - - beforeAll(async () => { - testLogger.test('Initializing the agents') - ;({ faberAgent, aliceAgent, aliceConnection, presentationPreview } = await setupProofsTest( - 'Faber agent', - 'Alice agent' - )) - }) - - afterAll(async () => { - testLogger.test('Shutting down both agents') - await faberAgent.shutdown() - await faberAgent.wallet.delete() - await aliceAgent.shutdown() - await aliceAgent.wallet.delete() - }) - - test(`Alice Creates and sends Proof Proposal to Faber`, async () => { - testLogger.test('Alice sends proof proposal to Faber') - - const faberProofExchangeRecordPromise = waitForProofExchangeRecord(faberAgent, { - state: ProofState.ProposalReceived, - }) - - await aliceAgent.proofs.proposeProof({ - connectionId: aliceConnection.id, - protocolVersion: 'v1', - proofFormats: { - indy: { - name: 'ProofRequest', - nonce: '58d223e5-fc4d-4448-b74c-5eb11c6b558f', - version: '1.0', - attributes: presentationPreview.attributes, - predicates: presentationPreview.predicates, - }, - }, - comment: 'V1 propose proof test', - }) - - testLogger.test('Faber waits for presentation from Alice') - faberProofExchangeRecord = await faberProofExchangeRecordPromise - - didCommMessageRepository = faberAgent.injectionContainer.resolve(DidCommMessageRepository) - - const proposal = await didCommMessageRepository.findAgentMessage(faberAgent.context, { - associatedRecordId: faberProofExchangeRecord.id, - messageClass: V1ProposePresentationMessage, - }) - - expect(proposal).toMatchObject({ - type: 'https://didcomm.org/present-proof/1.0/propose-presentation', - id: expect.any(String), - comment: 'V1 propose proof test', - presentationProposal: { - type: 'https://didcomm.org/present-proof/1.0/presentation-preview', - attributes: [ - { - name: 'name', - credentialDefinitionId: presentationPreview.attributes[0].credentialDefinitionId, - value: 'John', - referent: '0', - }, - { - name: 'image_0', - credentialDefinitionId: presentationPreview.attributes[1].credentialDefinitionId, - }, - ], - predicates: [ - { - name: 'age', - credentialDefinitionId: presentationPreview.predicates[0].credentialDefinitionId, - predicate: '>=', - threshold: 50, - }, - ], - }, - }) - - expect(faberProofExchangeRecord).toMatchObject({ - id: expect.anything(), - threadId: faberProofExchangeRecord.threadId, - state: ProofState.ProposalReceived, - protocolVersion: 'v1', - }) - }) -}) diff --git a/packages/core/src/modules/proofs/protocol/v1/__tests__/indy-proof-request.test.ts b/packages/core/src/modules/proofs/protocol/v1/__tests__/indy-proof-request.test.ts deleted file mode 100644 index 130f5cf04a..0000000000 --- a/packages/core/src/modules/proofs/protocol/v1/__tests__/indy-proof-request.test.ts +++ /dev/null @@ -1,153 +0,0 @@ -import type { Agent } from '../../../../../agent/Agent' -import type { ConnectionRecord } from '../../../../connections/repository/ConnectionRecord' -import type { AcceptProofProposalOptions } from '../../../ProofsApiOptions' -import type { ProofExchangeRecord } from '../../../repository/ProofExchangeRecord' -import type { PresentationPreview } from '../models/V1PresentationPreview' - -import { setupProofsTest, waitForProofExchangeRecord } from '../../../../../../tests/helpers' -import testLogger from '../../../../../../tests/logger' -import { DidCommMessageRepository } from '../../../../../storage/didcomm' -import { ProofState } from '../../../models/ProofState' -import { V1ProposePresentationMessage, V1RequestPresentationMessage } from '../messages' - -describe('Present Proof', () => { - let faberAgent: Agent - let aliceAgent: Agent - let aliceConnection: ConnectionRecord - let presentationPreview: PresentationPreview - let faberProofExchangeRecord: ProofExchangeRecord - let aliceProofExchangeRecord: ProofExchangeRecord - let didCommMessageRepository: DidCommMessageRepository - - beforeAll(async () => { - testLogger.test('Initializing the agents') - ;({ faberAgent, aliceAgent, aliceConnection, presentationPreview } = await setupProofsTest( - 'Faber agent', - 'Alice agent' - )) - }) - - afterAll(async () => { - testLogger.test('Shutting down both agents') - await faberAgent.shutdown() - await faberAgent.wallet.delete() - await aliceAgent.shutdown() - await aliceAgent.wallet.delete() - }) - - test(`Alice Creates and sends Proof Proposal to Faber`, async () => { - testLogger.test('Alice sends proof proposal to Faber') - - const faberProofExchangeRecordPromise = waitForProofExchangeRecord(faberAgent, { - state: ProofState.ProposalReceived, - }) - - aliceProofExchangeRecord = await aliceAgent.proofs.proposeProof({ - connectionId: aliceConnection.id, - protocolVersion: 'v1', - proofFormats: { - indy: { - name: 'ProofRequest', - nonce: '58d223e5-fc4d-4448-b74c-5eb11c6b558f', - version: '1.0', - attributes: presentationPreview.attributes, - predicates: presentationPreview.predicates, - }, - }, - comment: 'V1 propose proof test', - }) - - testLogger.test('Faber waits for presentation from Alice') - faberProofExchangeRecord = await faberProofExchangeRecordPromise - - didCommMessageRepository = faberAgent.injectionContainer.resolve(DidCommMessageRepository) - - const proposal = await didCommMessageRepository.findAgentMessage(faberAgent.context, { - associatedRecordId: faberProofExchangeRecord.id, - messageClass: V1ProposePresentationMessage, - }) - - expect(proposal).toMatchObject({ - type: 'https://didcomm.org/present-proof/1.0/propose-presentation', - id: expect.any(String), - comment: 'V1 propose proof test', - presentationProposal: { - type: 'https://didcomm.org/present-proof/1.0/presentation-preview', - attributes: [ - { - name: 'name', - credentialDefinitionId: presentationPreview.attributes[0].credentialDefinitionId, - value: 'John', - referent: '0', - }, - { - name: 'image_0', - credentialDefinitionId: presentationPreview.attributes[1].credentialDefinitionId, - }, - ], - predicates: [ - { - name: 'age', - credentialDefinitionId: presentationPreview.predicates[0].credentialDefinitionId, - predicate: '>=', - threshold: 50, - }, - ], - }, - }) - expect(faberProofExchangeRecord).toMatchObject({ - id: expect.anything(), - threadId: faberProofExchangeRecord.threadId, - state: ProofState.ProposalReceived, - protocolVersion: 'v1', - }) - }) - - test(`Faber accepts the Proposal send by Alice and Creates Proof Request`, async () => { - // Accept Proposal - const acceptProposalOptions: AcceptProofProposalOptions = { - proofRecordId: faberProofExchangeRecord.id, - } - - const aliceProofExchangeRecordPromise = waitForProofExchangeRecord(aliceAgent, { - threadId: faberProofExchangeRecord.threadId, - state: ProofState.RequestReceived, - }) - - testLogger.test('Faber accepts presentation proposal from Alice') - faberProofExchangeRecord = await faberAgent.proofs.acceptProposal(acceptProposalOptions) - - testLogger.test('Alice waits for proof request from Faber') - aliceProofExchangeRecord = await aliceProofExchangeRecordPromise - - didCommMessageRepository = faberAgent.injectionContainer.resolve(DidCommMessageRepository) - - const request = await didCommMessageRepository.findAgentMessage(faberAgent.context, { - associatedRecordId: faberProofExchangeRecord.id, - messageClass: V1RequestPresentationMessage, - }) - - expect(request).toMatchObject({ - type: 'https://didcomm.org/present-proof/1.0/request-presentation', - id: expect.any(String), - requestPresentationAttachments: [ - { - id: 'libindy-request-presentation-0', - mimeType: 'application/json', - data: { - base64: expect.any(String), - }, - }, - ], - thread: { - threadId: faberProofExchangeRecord.threadId, - }, - }) - expect(aliceProofExchangeRecord).toMatchObject({ - id: expect.anything(), - threadId: faberProofExchangeRecord.threadId, - state: ProofState.RequestReceived, - protocolVersion: 'v1', - }) - }) -}) diff --git a/packages/core/src/modules/proofs/protocol/v1/handlers/V1PresentationHandler.ts b/packages/core/src/modules/proofs/protocol/v1/handlers/V1PresentationHandler.ts deleted file mode 100644 index 6918979829..0000000000 --- a/packages/core/src/modules/proofs/protocol/v1/handlers/V1PresentationHandler.ts +++ /dev/null @@ -1,86 +0,0 @@ -import type { AgentConfig } from '../../../../../agent/AgentConfig' -import type { MessageHandler, MessageHandlerInboundMessage } from '../../../../../agent/MessageHandler' -import type { DidCommMessageRepository } from '../../../../../storage' -import type { ProofResponseCoordinator } from '../../../ProofResponseCoordinator' -import type { ProofExchangeRecord } from '../../../repository' -import type { V1ProofService } from '../V1ProofService' - -import { OutboundMessageContext } from '../../../../../agent/models' -import { V1PresentationMessage, V1RequestPresentationMessage } from '../messages' - -export class V1PresentationHandler implements MessageHandler { - private proofService: V1ProofService - private agentConfig: AgentConfig - private proofResponseCoordinator: ProofResponseCoordinator - private didCommMessageRepository: DidCommMessageRepository - public supportedMessages = [V1PresentationMessage] - - public constructor( - proofService: V1ProofService, - agentConfig: AgentConfig, - proofResponseCoordinator: ProofResponseCoordinator, - didCommMessageRepository: DidCommMessageRepository - ) { - this.proofService = proofService - this.agentConfig = agentConfig - this.proofResponseCoordinator = proofResponseCoordinator - this.didCommMessageRepository = didCommMessageRepository - } - - public async handle(messageContext: MessageHandlerInboundMessage) { - const proofRecord = await this.proofService.processPresentation(messageContext) - - const shouldAutoRespond = await this.proofResponseCoordinator.shouldAutoRespondToPresentation( - messageContext.agentContext, - proofRecord - ) - - if (shouldAutoRespond) { - return await this.createAck(proofRecord, messageContext) - } - } - - private async createAck( - record: ProofExchangeRecord, - messageContext: MessageHandlerInboundMessage - ) { - this.agentConfig.logger.info( - `Automatically sending acknowledgement with autoAccept on ${this.agentConfig.autoAcceptProofs}` - ) - - const { message, proofRecord } = await this.proofService.createAck(messageContext.agentContext, { - proofRecord: record, - }) - - const requestMessage = await this.didCommMessageRepository.findAgentMessage(messageContext.agentContext, { - associatedRecordId: proofRecord.id, - messageClass: V1RequestPresentationMessage, - }) - - const presentationMessage = await this.didCommMessageRepository.findAgentMessage(messageContext.agentContext, { - associatedRecordId: proofRecord.id, - messageClass: V1PresentationMessage, - }) - - if (messageContext.connection) { - return new OutboundMessageContext(message, { - agentContext: messageContext.agentContext, - connection: messageContext.connection, - associatedRecord: proofRecord, - }) - } else if (requestMessage?.service && presentationMessage?.service) { - const recipientService = presentationMessage?.service - const ourService = requestMessage?.service - - return new OutboundMessageContext(message, { - agentContext: messageContext.agentContext, - serviceParams: { - service: recipientService.resolvedDidCommService, - senderKey: ourService.resolvedDidCommService.recipientKeys[0], - }, - }) - } - - this.agentConfig.logger.error(`Could not automatically create presentation ack`) - } -} diff --git a/packages/core/src/modules/proofs/protocol/v1/handlers/V1ProposePresentationHandler.ts b/packages/core/src/modules/proofs/protocol/v1/handlers/V1ProposePresentationHandler.ts deleted file mode 100644 index e69764d3d5..0000000000 --- a/packages/core/src/modules/proofs/protocol/v1/handlers/V1ProposePresentationHandler.ts +++ /dev/null @@ -1,113 +0,0 @@ -import type { AgentConfig } from '../../../../../agent/AgentConfig' -import type { MessageHandler, MessageHandlerInboundMessage } from '../../../../../agent/MessageHandler' -import type { DidCommMessageRepository } from '../../../../../storage/didcomm/DidCommMessageRepository' -import type { ProofResponseCoordinator } from '../../../ProofResponseCoordinator' -import type { IndyProofFormat } from '../../../formats/indy/IndyProofFormat' -import type { IndyProofRequestFromProposalOptions } from '../../../formats/indy/IndyProofFormatsServiceOptions' -import type { ProofRequestFromProposalOptions } from '../../../models/ProofServiceOptions' -import type { ProofExchangeRecord } from '../../../repository/ProofExchangeRecord' -import type { V1ProofService } from '../V1ProofService' - -import { OutboundMessageContext } from '../../../../../agent/models' -import { AriesFrameworkError } from '../../../../../error' -import { V1ProposePresentationMessage } from '../messages' - -export class V1ProposePresentationHandler implements MessageHandler { - private proofService: V1ProofService - private agentConfig: AgentConfig - private didCommMessageRepository: DidCommMessageRepository - private proofResponseCoordinator: ProofResponseCoordinator - public supportedMessages = [V1ProposePresentationMessage] - - public constructor( - proofService: V1ProofService, - agentConfig: AgentConfig, - proofResponseCoordinator: ProofResponseCoordinator, - didCommMessageRepository: DidCommMessageRepository - ) { - this.proofService = proofService - this.agentConfig = agentConfig - this.proofResponseCoordinator = proofResponseCoordinator - this.didCommMessageRepository = didCommMessageRepository - } - - public async handle(messageContext: MessageHandlerInboundMessage) { - const proofRecord = await this.proofService.processProposal(messageContext) - - const shouldAutoRespond = await this.proofResponseCoordinator.shouldAutoRespondToProposal( - messageContext.agentContext, - proofRecord - ) - - if (shouldAutoRespond) { - return await this.createRequest(proofRecord, messageContext) - } - } - - private async createRequest( - proofRecord: ProofExchangeRecord, - messageContext: MessageHandlerInboundMessage - ) { - this.agentConfig.logger.info( - `Automatically sending request with autoAccept on ${this.agentConfig.autoAcceptProofs}` - ) - - if (!messageContext.connection) { - this.agentConfig.logger.error('No connection on the messageContext') - throw new AriesFrameworkError('No connection on the messageContext') - } - - const proposalMessage = await this.didCommMessageRepository.getAgentMessage(messageContext.agentContext, { - associatedRecordId: proofRecord.id, - messageClass: V1ProposePresentationMessage, - }) - - if (!proposalMessage) { - this.agentConfig.logger.error(`Proof record with id ${proofRecord.id} is missing required credential proposal`) - throw new AriesFrameworkError(`Proof record with id ${proofRecord.id} is missing required credential proposal`) - } - - const proofRequestFromProposalOptions: IndyProofRequestFromProposalOptions = { - name: 'proof-request', - version: '1.0', - nonce: await messageContext.agentContext.wallet.generateNonce(), - proofRecord, - } - - const proofRequest: ProofRequestFromProposalOptions<[IndyProofFormat]> = - await this.proofService.createProofRequestFromProposal( - messageContext.agentContext, - proofRequestFromProposalOptions - ) - - const indyProofRequest = proofRequest.proofFormats - - if (!indyProofRequest || !indyProofRequest.indy) { - this.agentConfig.logger.error(`No Indy proof request was found`) - throw new AriesFrameworkError('No Indy proof request was found') - } - - const { message } = await this.proofService.createRequestAsResponse(messageContext.agentContext, { - proofFormats: { - indy: { - name: indyProofRequest.indy?.name, - version: indyProofRequest.indy?.version, - nonRevoked: indyProofRequest.indy?.nonRevoked, - requestedAttributes: indyProofRequest.indy?.requestedAttributes, - requestedPredicates: indyProofRequest.indy?.requestedPredicates, - ver: indyProofRequest.indy?.ver, - nonce: indyProofRequest.indy?.nonce, - }, - }, - proofRecord: proofRecord, - autoAcceptProof: proofRecord.autoAcceptProof, - willConfirm: true, - }) - - return new OutboundMessageContext(message, { - agentContext: messageContext.agentContext, - connection: messageContext.connection, - associatedRecord: proofRecord, - }) - } -} diff --git a/packages/core/src/modules/proofs/protocol/v1/handlers/V1RequestPresentationHandler.ts b/packages/core/src/modules/proofs/protocol/v1/handlers/V1RequestPresentationHandler.ts deleted file mode 100644 index d460bbe122..0000000000 --- a/packages/core/src/modules/proofs/protocol/v1/handlers/V1RequestPresentationHandler.ts +++ /dev/null @@ -1,135 +0,0 @@ -import type { AgentConfig } from '../../../../../agent/AgentConfig' -import type { MessageHandler, MessageHandlerInboundMessage } from '../../../../../agent/MessageHandler' -import type { DidCommMessageRepository } from '../../../../../storage/didcomm/DidCommMessageRepository' -import type { MediationRecipientService, RoutingService } from '../../../../routing' -import type { ProofResponseCoordinator } from '../../../ProofResponseCoordinator' -import type { IndyProofFormat } from '../../../formats/indy/IndyProofFormat' -import type { - FormatRequestedCredentialReturn, - FormatRetrievedCredentialOptions, -} from '../../../models/ProofServiceOptions' -import type { ProofExchangeRecord } from '../../../repository/ProofExchangeRecord' -import type { V1ProofService } from '../V1ProofService' - -import { OutboundMessageContext } from '../../../../../agent/models' -import { ServiceDecorator } from '../../../../../decorators/service/ServiceDecorator' -import { AriesFrameworkError } from '../../../../../error' -import { DidCommMessageRole } from '../../../../../storage' -import { V1RequestPresentationMessage } from '../messages' - -export class V1RequestPresentationHandler implements MessageHandler { - private proofService: V1ProofService - private agentConfig: AgentConfig - private proofResponseCoordinator: ProofResponseCoordinator - private mediationRecipientService: MediationRecipientService - private didCommMessageRepository: DidCommMessageRepository - private routingService: RoutingService - public supportedMessages = [V1RequestPresentationMessage] - - public constructor( - proofService: V1ProofService, - agentConfig: AgentConfig, - proofResponseCoordinator: ProofResponseCoordinator, - mediationRecipientService: MediationRecipientService, - didCommMessageRepository: DidCommMessageRepository, - routingService: RoutingService - ) { - this.proofService = proofService - this.agentConfig = agentConfig - this.proofResponseCoordinator = proofResponseCoordinator - this.mediationRecipientService = mediationRecipientService - this.didCommMessageRepository = didCommMessageRepository - this.routingService = routingService - } - - public async handle(messageContext: MessageHandlerInboundMessage) { - const proofRecord = await this.proofService.processRequest(messageContext) - - const shouldAutoRespond = await this.proofResponseCoordinator.shouldAutoRespondToRequest( - messageContext.agentContext, - proofRecord - ) - - if (shouldAutoRespond) { - return await this.createPresentation(proofRecord, messageContext) - } - } - - private async createPresentation( - record: ProofExchangeRecord, - messageContext: MessageHandlerInboundMessage - ) { - const requestMessage = await this.didCommMessageRepository.getAgentMessage(messageContext.agentContext, { - associatedRecordId: record.id, - messageClass: V1RequestPresentationMessage, - }) - - const indyProofRequest = requestMessage.indyProofRequest - - this.agentConfig.logger.info( - `Automatically sending presentation with autoAccept on ${this.agentConfig.autoAcceptProofs}` - ) - - if (!indyProofRequest) { - this.agentConfig.logger.error('Proof request is undefined.') - throw new AriesFrameworkError('No proof request found.') - } - - const retrievedCredentials: FormatRetrievedCredentialOptions<[IndyProofFormat]> = - await this.proofService.getRequestedCredentialsForProofRequest(messageContext.agentContext, { - proofRecord: record, - config: { - filterByPresentationPreview: true, - }, - }) - if (!retrievedCredentials.proofFormats.indy) { - this.agentConfig.logger.error('No matching Indy credentials could be retrieved.') - throw new AriesFrameworkError('No matching Indy credentials could be retrieved.') - } - - const options: FormatRetrievedCredentialOptions<[IndyProofFormat]> = { - proofFormats: retrievedCredentials.proofFormats, - } - const requestedCredentials: FormatRequestedCredentialReturn<[IndyProofFormat]> = - await this.proofService.autoSelectCredentialsForProofRequest(options) - - const { message, proofRecord } = await this.proofService.createPresentation(messageContext.agentContext, { - proofRecord: record, - proofFormats: { - indy: requestedCredentials.proofFormats.indy, - }, - willConfirm: true, - }) - - if (messageContext.connection) { - return new OutboundMessageContext(message, { - agentContext: messageContext.agentContext, - connection: messageContext.connection, - associatedRecord: proofRecord, - }) - } else if (requestMessage.service) { - const routing = await this.routingService.getRouting(messageContext.agentContext) - message.service = new ServiceDecorator({ - serviceEndpoint: routing.endpoints[0], - recipientKeys: [routing.recipientKey.publicKeyBase58], - routingKeys: routing.routingKeys.map((key) => key.publicKeyBase58), - }) - const recipientService = requestMessage.service - - await this.didCommMessageRepository.saveOrUpdateAgentMessage(messageContext.agentContext, { - agentMessage: message, - associatedRecordId: proofRecord.id, - role: DidCommMessageRole.Sender, - }) - return new OutboundMessageContext(message, { - agentContext: messageContext.agentContext, - serviceParams: { - service: recipientService.resolvedDidCommService, - senderKey: message.service.resolvedDidCommService.recipientKeys[0], - }, - }) - } - - this.agentConfig.logger.error(`Could not automatically create presentation`) - } -} diff --git a/packages/core/src/modules/proofs/protocol/v1/messages/V1RequestPresentationMessage.ts b/packages/core/src/modules/proofs/protocol/v1/messages/V1RequestPresentationMessage.ts deleted file mode 100644 index 706fbd571b..0000000000 --- a/packages/core/src/modules/proofs/protocol/v1/messages/V1RequestPresentationMessage.ts +++ /dev/null @@ -1,106 +0,0 @@ -import type { ProofAttachmentFormat } from '../../../formats/models/ProofAttachmentFormat' -import type { IndyProofRequest } from 'indy-sdk' - -import { Expose, Type } from 'class-transformer' -import { IsArray, IsString, ValidateNested, IsOptional, IsInstance } from 'class-validator' - -import { V1Attachment } from '../../../../../decorators/attachment/V1Attachment' -import { DidCommV1Message } from '../../../../../didcomm' -import { AriesFrameworkError } from '../../../../../error/AriesFrameworkError' -import { JsonTransformer } from '../../../../../utils/JsonTransformer' -import { IsValidMessageType, parseMessageType } from '../../../../../utils/messageType' -import { V2_INDY_PRESENTATION_REQUEST } from '../../../formats/ProofFormatConstants' -import { ProofRequest } from '../../../formats/indy/models/ProofRequest' -import { ProofFormatSpec } from '../../../models/ProofFormatSpec' - -export interface RequestPresentationOptions { - id?: string - comment?: string - parentThreadId?: string - requestPresentationAttachments: V1Attachment[] -} - -export const INDY_PROOF_REQUEST_ATTACHMENT_ID = 'libindy-request-presentation-0' - -/** - * Request Presentation Message part of Present Proof Protocol used to initiate request from verifier to prover. - * - * @see https://github.com/hyperledger/aries-rfcs/blob/master/features/0037-present-proof/README.md#request-presentation - */ -export class V1RequestPresentationMessage extends DidCommV1Message { - public constructor(options: RequestPresentationOptions) { - super() - - if (options) { - this.id = options.id ?? this.generateId() - this.comment = options.comment - this.requestPresentationAttachments = options.requestPresentationAttachments - if (options.parentThreadId) { - this.setThread({ - parentThreadId: options.parentThreadId, - }) - } - } - } - - @IsValidMessageType(V1RequestPresentationMessage.type) - public readonly type = V1RequestPresentationMessage.type.messageTypeUri - public static readonly type = parseMessageType('https://didcomm.org/present-proof/1.0/request-presentation') - - /** - * Provides some human readable information about this request for a presentation. - */ - @IsOptional() - @IsString() - public comment?: string - - /** - * An array of attachments defining the acceptable formats for the presentation. - */ - @Expose({ name: 'request_presentations~attach' }) - @Type(() => V1Attachment) - @IsArray() - @ValidateNested({ - each: true, - }) - @IsInstance(V1Attachment, { each: true }) - public requestPresentationAttachments!: V1Attachment[] - - public get indyProofRequest(): ProofRequest | null { - // Extract proof request from attachment - const proofRequestJson = this.indyProofRequestJson - const proofRequest = JsonTransformer.fromJSON(proofRequestJson, ProofRequest) - - return proofRequest - } - - public get indyProofRequestJson(): IndyProofRequest | null { - const attachment = this.requestPresentationAttachments.find( - (attachment) => attachment.id === INDY_PROOF_REQUEST_ATTACHMENT_ID - ) - // Extract proof request from attachment - return attachment?.getDataAsJson() ?? null - } - - public getAttachmentFormats(): ProofAttachmentFormat[] { - const attachment = this.indyAttachment - - if (!attachment) { - throw new AriesFrameworkError(`Could not find a request presentation attachment`) - } - - return [ - { - format: new ProofFormatSpec({ format: V2_INDY_PRESENTATION_REQUEST }), - attachment: attachment, - }, - ] - } - - public get indyAttachment(): V1Attachment | null { - return ( - this.requestPresentationAttachments.find((attachment) => attachment.id === INDY_PROOF_REQUEST_ATTACHMENT_ID) ?? - null - ) - } -} diff --git a/packages/core/src/modules/proofs/protocol/v1/models/PartialProof.ts b/packages/core/src/modules/proofs/protocol/v1/models/PartialProof.ts deleted file mode 100644 index c33627c99a..0000000000 --- a/packages/core/src/modules/proofs/protocol/v1/models/PartialProof.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { Expose, Type } from 'class-transformer' -import { IsInstance, ValidateNested } from 'class-validator' - -import { ProofIdentifier } from './ProofIdentifier' -import { RequestedProof } from './RequestedProof' - -export class PartialProof { - public constructor(options: PartialProof) { - if (options) { - this.identifiers = options.identifiers - } - } - - @Type(() => ProofIdentifier) - @ValidateNested({ each: true }) - @IsInstance(ProofIdentifier, { each: true }) - public identifiers!: ProofIdentifier[] - - @Expose({ name: 'requested_proof' }) - @Type(() => RequestedProof) - @ValidateNested() - @IsInstance(RequestedProof) - public requestedProof!: RequestedProof -} diff --git a/packages/core/src/modules/proofs/protocol/v1/models/ProofAttribute.ts b/packages/core/src/modules/proofs/protocol/v1/models/ProofAttribute.ts deleted file mode 100644 index f307f92da6..0000000000 --- a/packages/core/src/modules/proofs/protocol/v1/models/ProofAttribute.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { Expose } from 'class-transformer' -import { IsInt, IsString } from 'class-validator' - -export class ProofAttribute { - public constructor(options: ProofAttribute) { - if (options) { - this.subProofIndex = options.subProofIndex - this.raw = options.raw - this.encoded = options.encoded - } - } - - @Expose({ name: 'sub_proof_index' }) - @IsInt() - public subProofIndex!: number - - @IsString() - public raw!: string - - @IsString() - public encoded!: string -} diff --git a/packages/core/src/modules/proofs/protocol/v1/models/ProofIdentifier.ts b/packages/core/src/modules/proofs/protocol/v1/models/ProofIdentifier.ts deleted file mode 100644 index 241ac74aaa..0000000000 --- a/packages/core/src/modules/proofs/protocol/v1/models/ProofIdentifier.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { Expose } from 'class-transformer' -import { IsNumber, IsOptional, IsString, Matches } from 'class-validator' - -import { credDefIdRegex } from '../../../../../utils/regex' - -export class ProofIdentifier { - public constructor(options: ProofIdentifier) { - if (options) { - this.schemaId = options.schemaId - this.credentialDefinitionId = options.credentialDefinitionId - this.revocationRegistryId = options.revocationRegistryId - this.timestamp = options.timestamp - } - } - - @Expose({ name: 'schema_id' }) - @IsString() - public schemaId!: string - - @Expose({ name: 'cred_def_id' }) - @IsString() - @Matches(credDefIdRegex) - public credentialDefinitionId!: string - - @Expose({ name: 'rev_reg_id' }) - @IsOptional() - @IsString() - public revocationRegistryId?: string - - @IsOptional() - @IsNumber() - public timestamp?: number -} diff --git a/packages/core/src/modules/proofs/protocol/v1/models/RequestedProof.ts b/packages/core/src/modules/proofs/protocol/v1/models/RequestedProof.ts deleted file mode 100644 index a2f2a5cf85..0000000000 --- a/packages/core/src/modules/proofs/protocol/v1/models/RequestedProof.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { Expose, Type } from 'class-transformer' -import { IsInstance, IsOptional, ValidateNested } from 'class-validator' - -import { ProofAttribute } from './ProofAttribute' - -export class RequestedProof { - public constructor(options: RequestedProof) { - if (options) { - this.revealedAttributes = options.revealedAttributes - this.selfAttestedAttributes = options.selfAttestedAttributes - } - } - - @Expose({ name: 'revealed_attrs' }) - @ValidateNested({ each: true }) - @Type(() => ProofAttribute) - @IsInstance(ProofAttribute, { each: true }) - public revealedAttributes!: Map - - @Expose({ name: 'self_attested_attrs' }) - @IsOptional() - // Validation is relaxed/skipped because empty Map validation will fail on JSON transform validation - public selfAttestedAttributes: Map = new Map() -} diff --git a/packages/core/src/modules/proofs/protocol/v1/models/index.ts b/packages/core/src/modules/proofs/protocol/v1/models/index.ts deleted file mode 100644 index 35ec9d0545..0000000000 --- a/packages/core/src/modules/proofs/protocol/v1/models/index.ts +++ /dev/null @@ -1,5 +0,0 @@ -export * from './PartialProof' -export * from './ProofAttribute' -export * from './ProofIdentifier' -export * from './RequestedProof' -export * from './V1PresentationPreview' diff --git a/packages/core/src/modules/proofs/protocol/v2/ProofFormatCoordinator.ts b/packages/core/src/modules/proofs/protocol/v2/ProofFormatCoordinator.ts new file mode 100644 index 0000000000..0f60cbbee4 --- /dev/null +++ b/packages/core/src/modules/proofs/protocol/v2/ProofFormatCoordinator.ts @@ -0,0 +1,519 @@ +import type { AgentContext } from '../../../../agent' +import type { V1Attachment } from '../../../../decorators/attachment/V1Attachment' +import type { + ExtractProofFormats, + ProofFormatCredentialForRequestPayload, + ProofFormatPayload, + ProofFormatService, +} from '../../formats' +import type { ProofFormatSpec } from '../../models/ProofFormatSpec' +import type { ProofExchangeRecord } from '../../repository' + +import { AriesFrameworkError } from '../../../../error' +import { DidCommMessageRepository, DidCommMessageRole } from '../../../../storage' + +import { V2PresentationMessage, V2ProposePresentationMessage, V2RequestPresentationMessage } from './messages' + +export class ProofFormatCoordinator { + /** + * Create a {@link V2ProposePresentationMessage}. + * + * @param options + * @returns The created {@link V2ProposePresentationMessage} + * + */ + public async createProposal( + agentContext: AgentContext, + { + proofFormats, + formatServices, + proofRecord, + comment, + goalCode, + }: { + formatServices: ProofFormatService[] + proofFormats: ProofFormatPayload, 'createProposal'> + proofRecord: ProofExchangeRecord + comment?: string + goalCode?: string + } + ): Promise { + const didCommMessageRepository = agentContext.dependencyManager.resolve(DidCommMessageRepository) + + // create message. there are two arrays in each message, one for formats the other for attachments + const formats: ProofFormatSpec[] = [] + const proposalAttachments: V1Attachment[] = [] + + for (const formatService of formatServices) { + const { format, attachment } = await formatService.createProposal(agentContext, { + proofFormats, + proofRecord, + }) + + proposalAttachments.push(attachment) + formats.push(format) + } + + const message = new V2ProposePresentationMessage({ + id: proofRecord.threadId, + formats, + proposalAttachments, + comment: comment, + goalCode, + }) + + message.setThread({ threadId: proofRecord.threadId, parentThreadId: proofRecord.parentThreadId }) + + await didCommMessageRepository.saveOrUpdateAgentMessage(agentContext, { + agentMessage: message, + role: DidCommMessageRole.Sender, + associatedRecordId: proofRecord.id, + }) + + return message + } + + public async processProposal( + agentContext: AgentContext, + { + proofRecord, + message, + formatServices, + }: { + proofRecord: ProofExchangeRecord + message: V2ProposePresentationMessage + formatServices: ProofFormatService[] + } + ) { + const didCommMessageRepository = agentContext.dependencyManager.resolve(DidCommMessageRepository) + + for (const formatService of formatServices) { + const attachment = this.getAttachmentForService(formatService, message.formats, message.proposalAttachments) + + await formatService.processProposal(agentContext, { + attachment, + proofRecord, + }) + } + + await didCommMessageRepository.saveOrUpdateAgentMessage(agentContext, { + agentMessage: message, + role: DidCommMessageRole.Receiver, + associatedRecordId: proofRecord.id, + }) + } + + public async acceptProposal( + agentContext: AgentContext, + { + proofRecord, + proofFormats, + formatServices, + comment, + goalCode, + presentMultiple, + willConfirm, + }: { + proofRecord: ProofExchangeRecord + proofFormats?: ProofFormatPayload, 'acceptProposal'> + formatServices: ProofFormatService[] + comment?: string + goalCode?: string + presentMultiple?: boolean + willConfirm?: boolean + } + ) { + const didCommMessageRepository = agentContext.dependencyManager.resolve(DidCommMessageRepository) + + // create message. there are two arrays in each message, one for formats the other for attachments + const formats: ProofFormatSpec[] = [] + const requestAttachments: V1Attachment[] = [] + + const proposalMessage = await didCommMessageRepository.getAgentMessage(agentContext, { + associatedRecordId: proofRecord.id, + messageClass: V2ProposePresentationMessage, + }) + + for (const formatService of formatServices) { + const proposalAttachment = this.getAttachmentForService( + formatService, + proposalMessage.formats, + proposalMessage.proposalAttachments + ) + + const { attachment, format } = await formatService.acceptProposal(agentContext, { + proofRecord, + proofFormats, + proposalAttachment, + }) + + requestAttachments.push(attachment) + formats.push(format) + } + + const message = new V2RequestPresentationMessage({ + formats, + requestAttachments, + comment, + goalCode, + presentMultiple, + willConfirm, + }) + + message.setThread({ threadId: proofRecord.threadId, parentThreadId: proofRecord.parentThreadId }) + + await didCommMessageRepository.saveOrUpdateAgentMessage(agentContext, { + agentMessage: message, + associatedRecordId: proofRecord.id, + role: DidCommMessageRole.Sender, + }) + + return message + } + + /** + * Create a {@link V2RequestPresentationMessage}. + * + * @param options + * @returns The created {@link V2RequestPresentationMessage} + * + */ + public async createRequest( + agentContext: AgentContext, + { + proofFormats, + formatServices, + proofRecord, + comment, + goalCode, + presentMultiple, + willConfirm, + }: { + formatServices: ProofFormatService[] + proofFormats: ProofFormatPayload, 'createRequest'> + proofRecord: ProofExchangeRecord + comment?: string + goalCode?: string + presentMultiple?: boolean + willConfirm?: boolean + } + ): Promise { + const didCommMessageRepository = agentContext.dependencyManager.resolve(DidCommMessageRepository) + + // create message. there are two arrays in each message, one for formats the other for attachments + const formats: ProofFormatSpec[] = [] + const requestAttachments: V1Attachment[] = [] + + for (const formatService of formatServices) { + const { format, attachment } = await formatService.createRequest(agentContext, { + proofFormats, + proofRecord, + }) + + requestAttachments.push(attachment) + formats.push(format) + } + + const message = new V2RequestPresentationMessage({ + formats, + comment, + requestAttachments, + goalCode, + presentMultiple, + willConfirm, + }) + + message.setThread({ threadId: proofRecord.threadId, parentThreadId: proofRecord.parentThreadId }) + + await didCommMessageRepository.saveOrUpdateAgentMessage(agentContext, { + agentMessage: message, + role: DidCommMessageRole.Sender, + associatedRecordId: proofRecord.id, + }) + + return message + } + + public async processRequest( + agentContext: AgentContext, + { + proofRecord, + message, + formatServices, + }: { + proofRecord: ProofExchangeRecord + message: V2RequestPresentationMessage + formatServices: ProofFormatService[] + } + ) { + const didCommMessageRepository = agentContext.dependencyManager.resolve(DidCommMessageRepository) + + for (const formatService of formatServices) { + const attachment = this.getAttachmentForService(formatService, message.formats, message.requestAttachments) + + await formatService.processRequest(agentContext, { + attachment, + proofRecord, + }) + } + + await didCommMessageRepository.saveOrUpdateAgentMessage(agentContext, { + agentMessage: message, + role: DidCommMessageRole.Receiver, + associatedRecordId: proofRecord.id, + }) + } + + public async acceptRequest( + agentContext: AgentContext, + { + proofRecord, + proofFormats, + formatServices, + comment, + lastPresentation, + goalCode, + }: { + proofRecord: ProofExchangeRecord + proofFormats?: ProofFormatPayload, 'acceptRequest'> + formatServices: ProofFormatService[] + comment?: string + lastPresentation?: boolean + goalCode?: string + } + ) { + const didCommMessageRepository = agentContext.dependencyManager.resolve(DidCommMessageRepository) + + const requestMessage = await didCommMessageRepository.getAgentMessage(agentContext, { + associatedRecordId: proofRecord.id, + messageClass: V2RequestPresentationMessage, + }) + + const proposalMessage = await didCommMessageRepository.findAgentMessage(agentContext, { + associatedRecordId: proofRecord.id, + messageClass: V2ProposePresentationMessage, + }) + + // create message. there are two arrays in each message, one for formats the other for attachments + const formats: ProofFormatSpec[] = [] + const presentationAttachments: V1Attachment[] = [] + + for (const formatService of formatServices) { + const requestAttachment = this.getAttachmentForService( + formatService, + requestMessage.formats, + requestMessage.requestAttachments + ) + + const proposalAttachment = proposalMessage + ? this.getAttachmentForService(formatService, proposalMessage.formats, proposalMessage.proposalAttachments) + : undefined + + const { attachment, format } = await formatService.acceptRequest(agentContext, { + requestAttachment, + proposalAttachment, + proofRecord, + proofFormats, + }) + + presentationAttachments.push(attachment) + formats.push(format) + } + + const message = new V2PresentationMessage({ + formats, + presentationAttachments, + comment, + lastPresentation, + goalCode, + }) + + message.setThread({ threadId: proofRecord.threadId }) + message.setPleaseAck() + + await didCommMessageRepository.saveOrUpdateAgentMessage(agentContext, { + agentMessage: message, + associatedRecordId: proofRecord.id, + role: DidCommMessageRole.Sender, + }) + + return message + } + + public async getCredentialsForRequest( + agentContext: AgentContext, + { + proofRecord, + proofFormats, + formatServices, + }: { + proofRecord: ProofExchangeRecord + proofFormats?: ProofFormatCredentialForRequestPayload< + ExtractProofFormats, + 'getCredentialsForRequest', + 'input' + > + formatServices: ProofFormatService[] + } + ): Promise, 'getCredentialsForRequest', 'output'>> { + const didCommMessageRepository = agentContext.dependencyManager.resolve(DidCommMessageRepository) + + const requestMessage = await didCommMessageRepository.getAgentMessage(agentContext, { + associatedRecordId: proofRecord.id, + messageClass: V2RequestPresentationMessage, + }) + + const proposalMessage = await didCommMessageRepository.findAgentMessage(agentContext, { + associatedRecordId: proofRecord.id, + messageClass: V2ProposePresentationMessage, + }) + + const credentialsForRequest: Record = {} + + for (const formatService of formatServices) { + const requestAttachment = this.getAttachmentForService( + formatService, + requestMessage.formats, + requestMessage.requestAttachments + ) + + const proposalAttachment = proposalMessage + ? this.getAttachmentForService(formatService, proposalMessage.formats, proposalMessage.proposalAttachments) + : undefined + + const credentialsForFormat = await formatService.getCredentialsForRequest(agentContext, { + requestAttachment, + proposalAttachment, + proofRecord, + proofFormats, + }) + + credentialsForRequest[formatService.formatKey] = credentialsForFormat + } + + return credentialsForRequest + } + + public async selectCredentialsForRequest( + agentContext: AgentContext, + { + proofRecord, + proofFormats, + formatServices, + }: { + proofRecord: ProofExchangeRecord + proofFormats?: ProofFormatCredentialForRequestPayload< + ExtractProofFormats, + 'selectCredentialsForRequest', + 'input' + > + formatServices: ProofFormatService[] + } + ): Promise< + ProofFormatCredentialForRequestPayload, 'selectCredentialsForRequest', 'output'> + > { + const didCommMessageRepository = agentContext.dependencyManager.resolve(DidCommMessageRepository) + + const requestMessage = await didCommMessageRepository.getAgentMessage(agentContext, { + associatedRecordId: proofRecord.id, + messageClass: V2RequestPresentationMessage, + }) + + const proposalMessage = await didCommMessageRepository.findAgentMessage(agentContext, { + associatedRecordId: proofRecord.id, + messageClass: V2ProposePresentationMessage, + }) + + const credentialsForRequest: Record = {} + + for (const formatService of formatServices) { + const requestAttachment = this.getAttachmentForService( + formatService, + requestMessage.formats, + requestMessage.requestAttachments + ) + + const proposalAttachment = proposalMessage + ? this.getAttachmentForService(formatService, proposalMessage.formats, proposalMessage.proposalAttachments) + : undefined + + const credentialsForFormat = await formatService.selectCredentialsForRequest(agentContext, { + requestAttachment, + proposalAttachment, + proofRecord, + proofFormats, + }) + + credentialsForRequest[formatService.formatKey] = credentialsForFormat + } + + return credentialsForRequest + } + + public async processPresentation( + agentContext: AgentContext, + { + proofRecord, + message, + requestMessage, + formatServices, + }: { + proofRecord: ProofExchangeRecord + message: V2PresentationMessage + requestMessage: V2RequestPresentationMessage + formatServices: ProofFormatService[] + } + ) { + const didCommMessageRepository = agentContext.dependencyManager.resolve(DidCommMessageRepository) + + const formatVerificationResults: boolean[] = [] + + for (const formatService of formatServices) { + const attachment = this.getAttachmentForService(formatService, message.formats, message.presentationAttachments) + const requestAttachment = this.getAttachmentForService( + formatService, + requestMessage.formats, + requestMessage.requestAttachments + ) + + const isValid = await formatService.processPresentation(agentContext, { + attachment, + requestAttachment, + proofRecord, + }) + + formatVerificationResults.push(isValid) + } + + await didCommMessageRepository.saveOrUpdateAgentMessage(agentContext, { + agentMessage: message, + role: DidCommMessageRole.Receiver, + associatedRecordId: proofRecord.id, + }) + + return formatVerificationResults.every((isValid) => isValid === true) + } + + public getAttachmentForService( + credentialFormatService: ProofFormatService, + formats: ProofFormatSpec[], + attachments: V1Attachment[] + ) { + const attachmentId = this.getAttachmentIdForService(credentialFormatService, formats) + const attachment = attachments.find((attachment) => attachment.id === attachmentId) + + if (!attachment) { + throw new AriesFrameworkError(`Attachment with id ${attachmentId} not found in attachments.`) + } + + return attachment + } + + private getAttachmentIdForService(credentialFormatService: ProofFormatService, formats: ProofFormatSpec[]) { + const format = formats.find((format) => credentialFormatService.supportsFormat(format.format)) + + if (!format) throw new AriesFrameworkError(`No attachment found for service ${credentialFormatService.formatKey}`) + + return format.attachmentId + } +} diff --git a/packages/core/src/modules/proofs/protocol/v2/V2ProofProtocol.ts b/packages/core/src/modules/proofs/protocol/v2/V2ProofProtocol.ts new file mode 100644 index 0000000000..f873ff9b47 --- /dev/null +++ b/packages/core/src/modules/proofs/protocol/v2/V2ProofProtocol.ts @@ -0,0 +1,1069 @@ +import type { AgentContext } from '../../../../agent' +import type { FeatureRegistry } from '../../../../agent/FeatureRegistry' +import type { InboundMessageContext } from '../../../../agent/models/InboundMessageContext' +import type { DidCommV1Message } from '../../../../didcomm' +import type { DependencyManager } from '../../../../plugins' +import type { ProblemReportMessage } from '../../../problem-reports' +import type { + ExtractProofFormats, + ProofFormat, + ProofFormatCredentialForRequestPayload, + ProofFormatPayload, +} from '../../formats' +import type { ProofFormatService } from '../../formats/ProofFormatService' +import type { ProofFormatSpec } from '../../models/ProofFormatSpec' +import type { ProofProtocol } from '../ProofProtocol' +import type { + AcceptPresentationOptions, + AcceptProofProposalOptions, + AcceptProofRequestOptions, + CreateProofProblemReportOptions, + CreateProofProposalOptions, + CreateProofRequestOptions, + ProofFormatDataMessagePayload, + GetCredentialsForRequestOptions, + GetCredentialsForRequestReturn, + GetProofFormatDataReturn, + NegotiateProofProposalOptions, + NegotiateProofRequestOptions, + ProofProtocolMsgReturnType, + SelectCredentialsForRequestOptions, + SelectCredentialsForRequestReturn, +} from '../ProofProtocolOptions' + +import { Protocol } from '../../../../agent/models' +import { AriesFrameworkError } from '../../../../error' +import { DidCommMessageRepository } from '../../../../storage' +import { uuid } from '../../../../utils/uuid' +import { AckStatus } from '../../../common' +import { ConnectionService } from '../../../connections' +import { V2ProposeCredentialMessage } from '../../../credentials' +import { ProofsModuleConfig } from '../../ProofsModuleConfig' +import { PresentationProblemReportReason } from '../../errors/PresentationProblemReportReason' +import { AutoAcceptProof, ProofState } from '../../models' +import { ProofExchangeRecord, ProofRepository } from '../../repository' +import { composeAutoAccept } from '../../utils/composeAutoAccept' +import { BaseProofProtocol } from '../BaseProofProtocol' + +import { ProofFormatCoordinator } from './ProofFormatCoordinator' +import { V2PresentationAckHandler } from './handlers/V2PresentationAckHandler' +import { V2PresentationHandler } from './handlers/V2PresentationHandler' +import { V2PresentationProblemReportHandler } from './handlers/V2PresentationProblemReportHandler' +import { V2ProposePresentationHandler } from './handlers/V2ProposePresentationHandler' +import { V2RequestPresentationHandler } from './handlers/V2RequestPresentationHandler' +import { V2PresentationAckMessage, V2RequestPresentationMessage } from './messages' +import { V2PresentationMessage } from './messages/V2PresentationMessage' +import { V2PresentationProblemReportMessage } from './messages/V2PresentationProblemReportMessage' +import { V2ProposePresentationMessage } from './messages/V2ProposePresentationMessage' + +export interface V2ProofProtocolConfig { + proofFormats: ProofFormatServices +} + +export class V2ProofProtocol + extends BaseProofProtocol + implements ProofProtocol +{ + private proofFormatCoordinator = new ProofFormatCoordinator() + private proofFormats: PFs + + public constructor({ proofFormats }: V2ProofProtocolConfig) { + super() + + this.proofFormats = proofFormats + } + + /** + * The version of the present proof protocol this service supports + */ + public readonly version = 'v2' as const + + public register(dependencyManager: DependencyManager, featureRegistry: FeatureRegistry) { + // Register message handlers for the Present Proof V2 Protocol + dependencyManager.registerMessageHandlers([ + new V2ProposePresentationHandler(this), + new V2RequestPresentationHandler(this), + new V2PresentationHandler(this), + new V2PresentationAckHandler(this), + new V2PresentationProblemReportHandler(this), + ]) + + // Register Present Proof V2 in feature registry, with supported roles + featureRegistry.register( + new Protocol({ + id: 'https://didcomm.org/present-proof/2.0', + roles: ['prover', 'verifier'], + }) + ) + } + + public async createProposal( + agentContext: AgentContext, + { + connectionRecord, + proofFormats, + comment, + autoAcceptProof, + goalCode, + parentThreadId, + }: CreateProofProposalOptions + ): Promise<{ proofRecord: ProofExchangeRecord; message: DidCommV1Message }> { + const proofRepository = agentContext.dependencyManager.resolve(ProofRepository) + + const formatServices = this.getFormatServices(proofFormats) + if (formatServices.length === 0) { + throw new AriesFrameworkError(`Unable to create proposal. No supported formats`) + } + + const proofRecord = new ProofExchangeRecord({ + connectionId: connectionRecord.id, + threadId: uuid(), + parentThreadId, + state: ProofState.ProposalSent, + protocolVersion: 'v2', + autoAcceptProof, + }) + + const proposalMessage = await this.proofFormatCoordinator.createProposal(agentContext, { + proofFormats, + proofRecord, + formatServices, + comment, + goalCode, + }) + + agentContext.config.logger.debug('Save record and emit state change event') + await proofRepository.save(agentContext, proofRecord) + this.emitStateChangedEvent(agentContext, proofRecord, null) + + return { + proofRecord, + message: proposalMessage, + } + } + + /** + * Method called by {@link V2ProposeCredentialHandler} on reception of a propose presentation message + * We do the necessary processing here to accept the proposal and do the state change, emit event etc. + * @param messageContext the inbound propose presentation message + * @returns proof record appropriate for this incoming message (once accepted) + */ + public async processProposal( + messageContext: InboundMessageContext + ): Promise { + const { message: proposalMessage, connection, agentContext } = messageContext + + agentContext.config.logger.debug(`Processing presentation proposal with id ${proposalMessage.id}`) + + const proofRepository = agentContext.dependencyManager.resolve(ProofRepository) + const didCommMessageRepository = agentContext.dependencyManager.resolve(DidCommMessageRepository) + const connectionService = agentContext.dependencyManager.resolve(ConnectionService) + + let proofRecord = await this.findByThreadAndConnectionId( + messageContext.agentContext, + proposalMessage.threadId, + connection?.id + ) + + const formatServices = this.getFormatServicesFromMessage(proposalMessage.formats) + if (formatServices.length === 0) { + throw new AriesFrameworkError(`Unable to process proposal. No supported formats`) + } + + // credential record already exists + if (proofRecord) { + const previousReceivedMessage = await didCommMessageRepository.findAgentMessage(messageContext.agentContext, { + associatedRecordId: proofRecord.id, + messageClass: V2ProposePresentationMessage, + }) + const previousSentMessage = await didCommMessageRepository.findAgentMessage(messageContext.agentContext, { + associatedRecordId: proofRecord.id, + messageClass: V2RequestPresentationMessage, + }) + + // Assert + proofRecord.assertProtocolVersion('v2') + proofRecord.assertState(ProofState.RequestSent) + connectionService.assertConnectionOrServiceDecorator(messageContext, { + previousReceivedMessage, + previousSentMessage, + }) + + await this.proofFormatCoordinator.processProposal(messageContext.agentContext, { + proofRecord, + formatServices, + message: proposalMessage, + }) + + await this.updateState(messageContext.agentContext, proofRecord, ProofState.ProposalReceived) + + return proofRecord + } else { + // Assert + connectionService.assertConnectionOrServiceDecorator(messageContext) + + // No proof record exists with thread id + proofRecord = new ProofExchangeRecord({ + connectionId: connection?.id, + threadId: proposalMessage.threadId, + state: ProofState.ProposalReceived, + protocolVersion: 'v2', + parentThreadId: proposalMessage.thread?.parentThreadId, + }) + + await this.proofFormatCoordinator.processProposal(messageContext.agentContext, { + proofRecord, + formatServices, + message: proposalMessage, + }) + + // Save record and emit event + await proofRepository.save(messageContext.agentContext, proofRecord) + this.emitStateChangedEvent(messageContext.agentContext, proofRecord, null) + + return proofRecord + } + } + + public async acceptProposal( + agentContext: AgentContext, + { proofRecord, proofFormats, autoAcceptProof, comment, goalCode, willConfirm }: AcceptProofProposalOptions + ): Promise> { + // Assert + proofRecord.assertProtocolVersion('v2') + proofRecord.assertState(ProofState.ProposalReceived) + + const didCommMessageRepository = agentContext.dependencyManager.resolve(DidCommMessageRepository) + + // Use empty proofFormats if not provided to denote all formats should be accepted + let formatServices = this.getFormatServices(proofFormats ?? {}) + + // if no format services could be extracted from the proofFormats + // take all available format services from the proposal message + if (formatServices.length === 0) { + const proposalMessage = await didCommMessageRepository.getAgentMessage(agentContext, { + associatedRecordId: proofRecord.id, + messageClass: V2ProposePresentationMessage, + }) + + formatServices = this.getFormatServicesFromMessage(proposalMessage.formats) + } + + // If the format services list is still empty, throw an error as we don't support any + // of the formats + if (formatServices.length === 0) { + throw new AriesFrameworkError( + `Unable to accept proposal. No supported formats provided as input or in proposal message` + ) + } + + const requestMessage = await this.proofFormatCoordinator.acceptProposal(agentContext, { + proofRecord, + formatServices, + comment, + proofFormats, + goalCode, + willConfirm, + // Not supported at the moment + presentMultiple: false, + }) + + proofRecord.autoAcceptProof = autoAcceptProof ?? proofRecord.autoAcceptProof + await this.updateState(agentContext, proofRecord, ProofState.RequestSent) + + return { proofRecord, message: requestMessage } + } + + /** + * Negotiate a proof proposal as verifier (by sending a proof request message) to the connection + * associated with the proof record. + * + * @param options configuration for the request see {@link NegotiateProofProposalOptions} + * @returns Proof exchange record associated with the proof request + * + */ + public async negotiateProposal( + agentContext: AgentContext, + { proofRecord, proofFormats, autoAcceptProof, comment, goalCode, willConfirm }: NegotiateProofProposalOptions + ): Promise> { + // Assert + proofRecord.assertProtocolVersion('v2') + proofRecord.assertState(ProofState.ProposalReceived) + + if (!proofRecord.connectionId) { + throw new AriesFrameworkError( + `No connectionId found for proof record '${proofRecord.id}'. Connection-less verification does not support negotiation.` + ) + } + + const formatServices = this.getFormatServices(proofFormats) + if (formatServices.length === 0) { + throw new AriesFrameworkError(`Unable to create request. No supported formats`) + } + + const requestMessage = await this.proofFormatCoordinator.createRequest(agentContext, { + formatServices, + proofFormats, + proofRecord, + comment, + goalCode, + willConfirm, + // Not supported at the moment + presentMultiple: false, + }) + + proofRecord.autoAcceptProof = autoAcceptProof ?? proofRecord.autoAcceptProof + await this.updateState(agentContext, proofRecord, ProofState.RequestSent) + + return { proofRecord, message: requestMessage } + } + + /** + * Create a {@link V2RequestPresentationMessage} as beginning of protocol process. + * @returns Object containing request message and associated credential record + * + */ + public async createRequest( + agentContext: AgentContext, + { + proofFormats, + autoAcceptProof, + comment, + connectionRecord, + parentThreadId, + goalCode, + willConfirm, + }: CreateProofRequestOptions + ): Promise> { + const proofRepository = agentContext.dependencyManager.resolve(ProofRepository) + + const formatServices = this.getFormatServices(proofFormats) + if (formatServices.length === 0) { + throw new AriesFrameworkError(`Unable to create request. No supported formats`) + } + + const proofRecord = new ProofExchangeRecord({ + connectionId: connectionRecord?.id, + threadId: uuid(), + state: ProofState.RequestSent, + autoAcceptProof, + protocolVersion: 'v2', + parentThreadId, + }) + + const requestMessage = await this.proofFormatCoordinator.createRequest(agentContext, { + formatServices, + proofFormats, + proofRecord, + comment, + goalCode, + willConfirm, + }) + + agentContext.config.logger.debug( + `Saving record and emitting state changed for proof exchange record ${proofRecord.id}` + ) + await proofRepository.save(agentContext, proofRecord) + this.emitStateChangedEvent(agentContext, proofRecord, null) + + return { proofRecord, message: requestMessage } + } + + /** + * Process a received {@link V2RequestPresentationMessage}. This will not accept the proof request + * or send a proof. It will only update the existing proof record with + * the information from the proof request message. Use {@link createCredential} + * after calling this method to create a proof. + *z + * @param messageContext The message context containing a v2 proof request message + * @returns proof record associated with the proof request message + * + */ + public async processRequest( + messageContext: InboundMessageContext + ): Promise { + const { message: requestMessage, connection, agentContext } = messageContext + + const proofRepository = agentContext.dependencyManager.resolve(ProofRepository) + const didCommMessageRepository = agentContext.dependencyManager.resolve(DidCommMessageRepository) + const connectionService = agentContext.dependencyManager.resolve(ConnectionService) + + agentContext.config.logger.debug(`Processing proof request with id ${requestMessage.id}`) + + let proofRecord = await this.findByThreadAndConnectionId( + messageContext.agentContext, + requestMessage.threadId, + connection?.id + ) + + const formatServices = this.getFormatServicesFromMessage(requestMessage.formats) + if (formatServices.length === 0) { + throw new AriesFrameworkError(`Unable to process request. No supported formats`) + } + + // proof record already exists + if (proofRecord) { + const previousSentMessage = await didCommMessageRepository.findAgentMessage(messageContext.agentContext, { + associatedRecordId: proofRecord.id, + messageClass: V2ProposeCredentialMessage, + }) + + const previousReceivedMessage = await didCommMessageRepository.findAgentMessage(messageContext.agentContext, { + associatedRecordId: proofRecord.id, + messageClass: V2RequestPresentationMessage, + }) + + // Assert + proofRecord.assertProtocolVersion('v2') + proofRecord.assertState(ProofState.ProposalSent) + connectionService.assertConnectionOrServiceDecorator(messageContext, { + previousReceivedMessage, + previousSentMessage, + }) + + await this.proofFormatCoordinator.processRequest(messageContext.agentContext, { + proofRecord, + formatServices, + message: requestMessage, + }) + + await this.updateState(messageContext.agentContext, proofRecord, ProofState.RequestReceived) + return proofRecord + } else { + // Assert + connectionService.assertConnectionOrServiceDecorator(messageContext) + + // No proof record exists with thread id + agentContext.config.logger.debug('No proof record found for request, creating a new one') + proofRecord = new ProofExchangeRecord({ + connectionId: connection?.id, + threadId: requestMessage.threadId, + state: ProofState.RequestReceived, + protocolVersion: 'v2', + parentThreadId: requestMessage.thread?.parentThreadId, + }) + + await this.proofFormatCoordinator.processRequest(messageContext.agentContext, { + proofRecord, + formatServices, + message: requestMessage, + }) + + // Save in repository + agentContext.config.logger.debug('Saving proof record and emit request-received event') + await proofRepository.save(messageContext.agentContext, proofRecord) + + this.emitStateChangedEvent(messageContext.agentContext, proofRecord, null) + return proofRecord + } + } + + public async acceptRequest( + agentContext: AgentContext, + { proofRecord, autoAcceptProof, comment, proofFormats, goalCode }: AcceptProofRequestOptions + ) { + const didCommMessageRepository = agentContext.dependencyManager.resolve(DidCommMessageRepository) + + // Assert + proofRecord.assertProtocolVersion('v2') + proofRecord.assertState(ProofState.RequestReceived) + + // Use empty proofFormats if not provided to denote all formats should be accepted + let formatServices = this.getFormatServices(proofFormats ?? {}) + + // if no format services could be extracted from the proofFormats + // take all available format services from the request message + if (formatServices.length === 0) { + const requestMessage = await didCommMessageRepository.getAgentMessage(agentContext, { + associatedRecordId: proofRecord.id, + messageClass: V2RequestPresentationMessage, + }) + + formatServices = this.getFormatServicesFromMessage(requestMessage.formats) + } + + // If the format services list is still empty, throw an error as we don't support any + // of the formats + if (formatServices.length === 0) { + throw new AriesFrameworkError( + `Unable to accept request. No supported formats provided as input or in request message` + ) + } + const message = await this.proofFormatCoordinator.acceptRequest(agentContext, { + proofRecord, + formatServices, + comment, + proofFormats, + goalCode, + // Sending multiple presentation messages not supported at the moment + lastPresentation: true, + }) + + proofRecord.autoAcceptProof = autoAcceptProof ?? proofRecord.autoAcceptProof + await this.updateState(agentContext, proofRecord, ProofState.PresentationSent) + + return { proofRecord, message } + } + + /** + * Create a {@link V2ProposePresentationMessage} as response to a received credential request. + * To create a proposal not bound to an existing proof exchange, use {@link createProposal}. + * + * @param options configuration to use for the proposal + * @returns Object containing proposal message and associated proof record + * + */ + public async negotiateRequest( + agentContext: AgentContext, + { proofRecord, proofFormats, autoAcceptProof, comment, goalCode }: NegotiateProofRequestOptions + ): Promise> { + // Assert + proofRecord.assertProtocolVersion('v2') + proofRecord.assertState(ProofState.RequestReceived) + + if (!proofRecord.connectionId) { + throw new AriesFrameworkError( + `No connectionId found for proof record '${proofRecord.id}'. Connection-less verification does not support negotiation.` + ) + } + + const formatServices = this.getFormatServices(proofFormats) + if (formatServices.length === 0) { + throw new AriesFrameworkError(`Unable to create proposal. No supported formats`) + } + + const proposalMessage = await this.proofFormatCoordinator.createProposal(agentContext, { + formatServices, + proofFormats, + proofRecord, + comment, + goalCode, + }) + + proofRecord.autoAcceptProof = autoAcceptProof ?? proofRecord.autoAcceptProof + await this.updateState(agentContext, proofRecord, ProofState.ProposalSent) + + return { proofRecord, message: proposalMessage } + } + + public async getCredentialsForRequest( + agentContext: AgentContext, + { proofRecord, proofFormats }: GetCredentialsForRequestOptions + ): Promise> { + const didCommMessageRepository = agentContext.dependencyManager.resolve(DidCommMessageRepository) + + // Assert + proofRecord.assertProtocolVersion('v2') + proofRecord.assertState(ProofState.RequestReceived) + + // Use empty proofFormats if not provided to denote all formats should be accepted + let formatServices = this.getFormatServices(proofFormats ?? {}) + + // if no format services could be extracted from the proofFormats + // take all available format services from the request message + if (formatServices.length === 0) { + const requestMessage = await didCommMessageRepository.getAgentMessage(agentContext, { + associatedRecordId: proofRecord.id, + messageClass: V2RequestPresentationMessage, + }) + + formatServices = this.getFormatServicesFromMessage(requestMessage.formats) + } + + // If the format services list is still empty, throw an error as we don't support any + // of the formats + if (formatServices.length === 0) { + throw new AriesFrameworkError( + `Unable to get credentials for request. No supported formats provided as input or in request message` + ) + } + + const result = await this.proofFormatCoordinator.getCredentialsForRequest(agentContext, { + formatServices, + proofFormats, + proofRecord, + }) + + return { + proofFormats: result, + } + } + + public async selectCredentialsForRequest( + agentContext: AgentContext, + { proofRecord, proofFormats }: SelectCredentialsForRequestOptions + ): Promise> { + const didCommMessageRepository = agentContext.dependencyManager.resolve(DidCommMessageRepository) + + // Assert + proofRecord.assertProtocolVersion('v2') + proofRecord.assertState(ProofState.RequestReceived) + + // Use empty proofFormats if not provided to denote all formats should be accepted + let formatServices = this.getFormatServices(proofFormats ?? {}) + + // if no format services could be extracted from the proofFormats + // take all available format services from the request message + if (formatServices.length === 0) { + const requestMessage = await didCommMessageRepository.getAgentMessage(agentContext, { + associatedRecordId: proofRecord.id, + messageClass: V2RequestPresentationMessage, + }) + + formatServices = this.getFormatServicesFromMessage(requestMessage.formats) + } + + // If the format services list is still empty, throw an error as we don't support any + // of the formats + if (formatServices.length === 0) { + throw new AriesFrameworkError( + `Unable to get credentials for request. No supported formats provided as input or in request message` + ) + } + + const result = await this.proofFormatCoordinator.selectCredentialsForRequest(agentContext, { + formatServices, + proofFormats, + proofRecord, + }) + + return { + proofFormats: result, + } + } + + public async processPresentation( + messageContext: InboundMessageContext + ): Promise { + const { message: presentationMessage, connection, agentContext } = messageContext + + const didCommMessageRepository = agentContext.dependencyManager.resolve(DidCommMessageRepository) + const connectionService = agentContext.dependencyManager.resolve(ConnectionService) + + agentContext.config.logger.debug(`Processing presentation with id ${presentationMessage.id}`) + + const proofRecord = await this.getByThreadAndConnectionId( + messageContext.agentContext, + presentationMessage.threadId, + connection?.id + ) + + const previousSentMessage = await didCommMessageRepository.getAgentMessage(messageContext.agentContext, { + associatedRecordId: proofRecord.id, + messageClass: V2RequestPresentationMessage, + }) + + const previousReceivedMessage = await didCommMessageRepository.findAgentMessage(messageContext.agentContext, { + associatedRecordId: proofRecord.id, + messageClass: V2ProposePresentationMessage, + }) + + // Assert + proofRecord.assertProtocolVersion('v2') + proofRecord.assertState(ProofState.RequestSent) + connectionService.assertConnectionOrServiceDecorator(messageContext, { + previousReceivedMessage, + previousSentMessage, + }) + + const formatServices = this.getFormatServicesFromMessage(presentationMessage.formats) + if (formatServices.length === 0) { + throw new AriesFrameworkError(`Unable to process presentation. No supported formats`) + } + + const isValid = await this.proofFormatCoordinator.processPresentation(messageContext.agentContext, { + proofRecord, + formatServices, + requestMessage: previousSentMessage, + message: presentationMessage, + }) + + proofRecord.isVerified = isValid + await this.updateState(messageContext.agentContext, proofRecord, ProofState.PresentationReceived) + + return proofRecord + } + + public async acceptPresentation( + agentContext: AgentContext, + { proofRecord }: AcceptPresentationOptions + ): Promise> { + proofRecord.assertProtocolVersion('v2') + proofRecord.assertState(ProofState.PresentationReceived) + + const didCommMessageRepository = agentContext.dependencyManager.resolve(DidCommMessageRepository) + + // assert we've received the final presentation + const presentation = await didCommMessageRepository.getAgentMessage(agentContext, { + associatedRecordId: proofRecord.id, + messageClass: V2PresentationMessage, + }) + + if (!presentation.lastPresentation) { + throw new AriesFrameworkError( + `Trying to send an ack message while presentation with id ${presentation.id} indicates this is not the last presentation (presentation.last_presentation is set to false)` + ) + } + + const message = new V2PresentationAckMessage({ + threadId: proofRecord.threadId, + status: AckStatus.OK, + }) + + await this.updateState(agentContext, proofRecord, ProofState.Done) + + return { + message, + proofRecord, + } + } + + public async processAck( + messageContext: InboundMessageContext + ): Promise { + const { message: ackMessage, connection, agentContext } = messageContext + + agentContext.config.logger.debug(`Processing proof ack with id ${ackMessage.id}`) + + const didCommMessageRepository = agentContext.dependencyManager.resolve(DidCommMessageRepository) + const connectionService = agentContext.dependencyManager.resolve(ConnectionService) + + const proofRecord = await this.getByThreadAndConnectionId( + messageContext.agentContext, + ackMessage.threadId, + connection?.id + ) + proofRecord.connectionId = connection?.id + + const previousReceivedMessage = await didCommMessageRepository.getAgentMessage(messageContext.agentContext, { + associatedRecordId: proofRecord.id, + messageClass: V2RequestPresentationMessage, + }) + + const previousSentMessage = await didCommMessageRepository.getAgentMessage(messageContext.agentContext, { + associatedRecordId: proofRecord.id, + messageClass: V2PresentationMessage, + }) + + // Assert + proofRecord.assertProtocolVersion('v2') + proofRecord.assertState(ProofState.PresentationSent) + connectionService.assertConnectionOrServiceDecorator(messageContext, { + previousReceivedMessage, + previousSentMessage, + }) + + // Update record + await this.updateState(messageContext.agentContext, proofRecord, ProofState.Done) + + return proofRecord + } + + public async createProblemReport( + agentContext: AgentContext, + { description, proofRecord }: CreateProofProblemReportOptions + ): Promise> { + const message = new V2PresentationProblemReportMessage({ + description: { + en: description, + code: PresentationProblemReportReason.Abandoned, + }, + }) + + message.setThread({ + threadId: proofRecord.threadId, + parentThreadId: proofRecord.parentThreadId, + }) + + return { + proofRecord, + message, + } + } + + public async shouldAutoRespondToProposal( + agentContext: AgentContext, + options: { + proofRecord: ProofExchangeRecord + proposalMessage: V2ProposePresentationMessage + } + ): Promise { + const { proofRecord, proposalMessage } = options + const proofsModuleConfig = agentContext.dependencyManager.resolve(ProofsModuleConfig) + + const autoAccept = composeAutoAccept(proofRecord.autoAcceptProof, proofsModuleConfig.autoAcceptProofs) + + // Handle always / never cases + if (autoAccept === AutoAcceptProof.Always) return true + if (autoAccept === AutoAcceptProof.Never) return false + + const requestMessage = await this.findRequestMessage(agentContext, proofRecord.id) + if (!requestMessage) return false + + // NOTE: we take the formats from the requestMessage so we always check all services that we last sent + // Otherwise we'll only check the formats from the proposal, which could be different from the formats + // we use. + const formatServices = this.getFormatServicesFromMessage(requestMessage.formats) + + for (const formatService of formatServices) { + const requestAttachment = this.proofFormatCoordinator.getAttachmentForService( + formatService, + requestMessage.formats, + requestMessage.requestAttachments + ) + + const proposalAttachment = this.proofFormatCoordinator.getAttachmentForService( + formatService, + proposalMessage.formats, + proposalMessage.proposalAttachments + ) + + const shouldAutoRespondToFormat = await formatService.shouldAutoRespondToProposal(agentContext, { + proofRecord, + requestAttachment, + proposalAttachment, + }) + // If any of the formats return false, we should not auto accept + if (!shouldAutoRespondToFormat) return false + } + + return true + } + + public async shouldAutoRespondToRequest( + agentContext: AgentContext, + options: { + proofRecord: ProofExchangeRecord + requestMessage: V2RequestPresentationMessage + } + ): Promise { + const { proofRecord, requestMessage } = options + const proofsModuleConfig = agentContext.dependencyManager.resolve(ProofsModuleConfig) + + const autoAccept = composeAutoAccept(proofRecord.autoAcceptProof, proofsModuleConfig.autoAcceptProofs) + + // Handle always / never cases + if (autoAccept === AutoAcceptProof.Always) return true + if (autoAccept === AutoAcceptProof.Never) return false + + const proposalMessage = await this.findProposalMessage(agentContext, proofRecord.id) + if (!proposalMessage) return false + + // NOTE: we take the formats from the proposalMessage so we always check all services that we last sent + // Otherwise we'll only check the formats from the request, which could be different from the formats + // we use. + const formatServices = this.getFormatServicesFromMessage(proposalMessage.formats) + + for (const formatService of formatServices) { + const proposalAttachment = this.proofFormatCoordinator.getAttachmentForService( + formatService, + proposalMessage.formats, + proposalMessage.proposalAttachments + ) + + const requestAttachment = this.proofFormatCoordinator.getAttachmentForService( + formatService, + requestMessage.formats, + requestMessage.requestAttachments + ) + + const shouldAutoRespondToFormat = await formatService.shouldAutoRespondToRequest(agentContext, { + proofRecord, + requestAttachment, + proposalAttachment, + }) + + // If any of the formats return false, we should not auto accept + if (!shouldAutoRespondToFormat) return false + } + + return true + } + + public async shouldAutoRespondToPresentation( + agentContext: AgentContext, + options: { proofRecord: ProofExchangeRecord; presentationMessage: V2PresentationMessage } + ): Promise { + const { proofRecord, presentationMessage } = options + const proofsModuleConfig = agentContext.dependencyManager.resolve(ProofsModuleConfig) + + // If this isn't the last presentation yet, we should not auto accept + if (!presentationMessage.lastPresentation) return false + + const autoAccept = composeAutoAccept(proofRecord.autoAcceptProof, proofsModuleConfig.autoAcceptProofs) + + // Handle always / never cases + if (autoAccept === AutoAcceptProof.Always) return true + if (autoAccept === AutoAcceptProof.Never) return false + + const proposalMessage = await this.findProposalMessage(agentContext, proofRecord.id) + + const requestMessage = await this.findRequestMessage(agentContext, proofRecord.id) + if (!requestMessage) return false + if (!requestMessage.willConfirm) return false + + // NOTE: we take the formats from the requestMessage so we always check all services that we last sent + // Otherwise we'll only check the formats from the credential, which could be different from the formats + // we use. + const formatServices = this.getFormatServicesFromMessage(requestMessage.formats) + + for (const formatService of formatServices) { + const proposalAttachment = proposalMessage + ? this.proofFormatCoordinator.getAttachmentForService( + formatService, + proposalMessage.formats, + proposalMessage.proposalAttachments + ) + : undefined + + const requestAttachment = this.proofFormatCoordinator.getAttachmentForService( + formatService, + requestMessage.formats, + requestMessage.requestAttachments + ) + + const presentationAttachment = this.proofFormatCoordinator.getAttachmentForService( + formatService, + presentationMessage.formats, + presentationMessage.presentationAttachments + ) + + const shouldAutoRespondToFormat = await formatService.shouldAutoRespondToPresentation(agentContext, { + proofRecord, + presentationAttachment, + requestAttachment, + proposalAttachment, + }) + + // If any of the formats return false, we should not auto accept + if (!shouldAutoRespondToFormat) return false + } + return true + } + + public async findRequestMessage( + agentContext: AgentContext, + proofRecordId: string + ): Promise { + const didCommMessageRepository = agentContext.dependencyManager.resolve(DidCommMessageRepository) + + return await didCommMessageRepository.findAgentMessage(agentContext, { + associatedRecordId: proofRecordId, + messageClass: V2RequestPresentationMessage, + }) + } + + public async findPresentationMessage( + agentContext: AgentContext, + proofRecordId: string + ): Promise { + const didCommMessageRepository = agentContext.dependencyManager.resolve(DidCommMessageRepository) + + return await didCommMessageRepository.findAgentMessage(agentContext, { + associatedRecordId: proofRecordId, + messageClass: V2PresentationMessage, + }) + } + + public async findProposalMessage( + agentContext: AgentContext, + proofRecordId: string + ): Promise { + const didCommMessageRepository = agentContext.dependencyManager.resolve(DidCommMessageRepository) + + return await didCommMessageRepository.findAgentMessage(agentContext, { + associatedRecordId: proofRecordId, + messageClass: V2ProposePresentationMessage, + }) + } + + public async getFormatData(agentContext: AgentContext, proofRecordId: string): Promise { + // TODO: we could looking at fetching all record using a single query and then filtering based on the type of the message. + const [proposalMessage, requestMessage, presentationMessage] = await Promise.all([ + this.findProposalMessage(agentContext, proofRecordId), + this.findRequestMessage(agentContext, proofRecordId), + this.findPresentationMessage(agentContext, proofRecordId), + ]) + + // Create object with the keys and the message formats/attachments. We can then loop over this in a generic + // way so we don't have to add the same operation code four times + const messages = { + proposal: [proposalMessage?.formats, proposalMessage?.proposalAttachments], + request: [requestMessage?.formats, requestMessage?.requestAttachments], + presentation: [presentationMessage?.formats, presentationMessage?.presentationAttachments], + } as const + + const formatData: GetProofFormatDataReturn = {} + + // We loop through all of the message keys as defined above + for (const [messageKey, [formats, attachments]] of Object.entries(messages)) { + // Message can be undefined, so we continue if it is not defined + if (!formats || !attachments) continue + + // Find all format services associated with the message + const formatServices = this.getFormatServicesFromMessage(formats) + + const messageFormatData: ProofFormatDataMessagePayload = {} + + // Loop through all of the format services, for each we will extract the attachment data and assign this to the object + // using the unique format key (e.g. indy) + for (const formatService of formatServices) { + const attachment = this.proofFormatCoordinator.getAttachmentForService(formatService, formats, attachments) + messageFormatData[formatService.formatKey] = attachment.getDataAsJson() + } + + formatData[messageKey as keyof GetProofFormatDataReturn] = messageFormatData + } + + return formatData + } + + /** + * Get all the format service objects for a given proof format from an incoming message + * @param messageFormats the format objects containing the format name (eg indy) + * @return the proof format service objects in an array - derived from format object keys + */ + private getFormatServicesFromMessage(messageFormats: ProofFormatSpec[]): ProofFormatService[] { + const formatServices = new Set() + + for (const msg of messageFormats) { + const service = this.getFormatServiceForFormat(msg.format) + if (service) formatServices.add(service) + } + + return Array.from(formatServices) + } + + /** + * Get all the format service objects for a given proof format + * @param proofFormats the format object containing various optional parameters + * @return the proof format service objects in an array - derived from format object keys + */ + private getFormatServices( + proofFormats: M extends 'selectCredentialsForRequest' | 'getCredentialsForRequest' + ? ProofFormatCredentialForRequestPayload, M, 'input'> + : ProofFormatPayload, M> + ): ProofFormatService[] { + const formats = new Set() + + for (const formatKey of Object.keys(proofFormats)) { + const formatService = this.getFormatServiceForFormatKey(formatKey) + + if (formatService) formats.add(formatService) + } + + return Array.from(formats) + } + + private getFormatServiceForFormatKey(formatKey: string): ProofFormatService | null { + const formatService = this.proofFormats.find((proofFormats) => proofFormats.formatKey === formatKey) + + return formatService ?? null + } + + private getFormatServiceForFormat(format: string): ProofFormatService | null { + const formatService = this.proofFormats.find((proofFormats) => proofFormats.supportsFormat(format)) + + return formatService ?? null + } +} diff --git a/packages/core/src/modules/proofs/protocol/v2/V2ProofService.ts b/packages/core/src/modules/proofs/protocol/v2/V2ProofService.ts deleted file mode 100644 index 0d592c33f4..0000000000 --- a/packages/core/src/modules/proofs/protocol/v2/V2ProofService.ts +++ /dev/null @@ -1,953 +0,0 @@ -import type { AgentContext } from '../../../../agent' -import type { Dispatcher } from '../../../../agent/Dispatcher' -import type { InboundMessageContext } from '../../../../agent/models/InboundMessageContext' -import type { V1Attachment } from '../../../../decorators/attachment/V1Attachment' -import type { DidCommV1Message } from '../../../../didcomm' -import type { MediationRecipientService } from '../../../routing/services/MediationRecipientService' -import type { RoutingService } from '../../../routing/services/RoutingService' -import type { ProofResponseCoordinator } from '../../ProofResponseCoordinator' -import type { ProofFormatServiceMap } from '../../formats' -import type { ProofFormat } from '../../formats/ProofFormat' -import type { ProofFormatService } from '../../formats/ProofFormatService' -import type { CreateProblemReportOptions } from '../../formats/models/ProofFormatServiceOptions' -import type { ProofFormatSpec } from '../../models/ProofFormatSpec' -import type { - CreateAckOptions, - CreatePresentationOptions, - CreateProofRequestFromProposalOptions, - CreateProposalAsResponseOptions, - CreateProposalOptions, - CreateRequestAsResponseOptions, - CreateRequestOptions, - FormatDataMessagePayload, - FormatRequestedCredentialReturn, - FormatRetrievedCredentialOptions, - GetFormatDataReturn, - GetRequestedCredentialsForProofRequestOptions, - ProofRequestFromProposalOptions, -} from '../../models/ProofServiceOptions' - -import { inject, Lifecycle, scoped } from 'tsyringe' - -import { AgentConfig } from '../../../../agent/AgentConfig' -import { EventEmitter } from '../../../../agent/EventEmitter' -import { InjectionSymbols } from '../../../../constants' -import { AriesFrameworkError } from '../../../../error' -import { DidCommMessageRepository, DidCommMessageRole } from '../../../../storage' -import { MessageValidator } from '../../../../utils/MessageValidator' -import { Wallet } from '../../../../wallet/Wallet' -import { AckStatus } from '../../../common' -import { ConnectionService } from '../../../connections' -import { ProofService } from '../../ProofService' -import { PresentationProblemReportReason } from '../../errors/PresentationProblemReportReason' -import { V2_INDY_PRESENTATION_REQUEST } from '../../formats/ProofFormatConstants' -import { IndyProofFormatService } from '../../formats/indy/IndyProofFormatService' -import { ProofState } from '../../models/ProofState' -import { ProofExchangeRecord, ProofRepository } from '../../repository' - -import { V2PresentationProblemReportError } from './errors' -import { V2PresentationAckHandler } from './handlers/V2PresentationAckHandler' -import { V2PresentationHandler } from './handlers/V2PresentationHandler' -import { V2PresentationProblemReportHandler } from './handlers/V2PresentationProblemReportHandler' -import { V2ProposePresentationHandler } from './handlers/V2ProposePresentationHandler' -import { V2RequestPresentationHandler } from './handlers/V2RequestPresentationHandler' -import { V2PresentationAckMessage } from './messages' -import { V2PresentationMessage } from './messages/V2PresentationMessage' -import { V2PresentationProblemReportMessage } from './messages/V2PresentationProblemReportMessage' -import { V2ProposalPresentationMessage } from './messages/V2ProposalPresentationMessage' -import { V2RequestPresentationMessage } from './messages/V2RequestPresentationMessage' - -@scoped(Lifecycle.ContainerScoped) -export class V2ProofService extends ProofService { - private formatServiceMap: { [key: string]: ProofFormatService } - - public constructor( - agentConfig: AgentConfig, - connectionService: ConnectionService, - proofRepository: ProofRepository, - didCommMessageRepository: DidCommMessageRepository, - eventEmitter: EventEmitter, - indyProofFormatService: IndyProofFormatService, - @inject(InjectionSymbols.Wallet) wallet: Wallet - ) { - super(agentConfig, proofRepository, connectionService, didCommMessageRepository, wallet, eventEmitter) - this.wallet = wallet - // Dynamically build format service map. This will be extracted once services are registered dynamically - this.formatServiceMap = [indyProofFormatService].reduce( - (formatServiceMap, formatService) => ({ - ...formatServiceMap, - [formatService.formatKey]: formatService, - }), - {} - ) as ProofFormatServiceMap - } - - /** - * The version of the present proof protocol this service supports - */ - public readonly version = 'v2' as const - - public async createProposal( - agentContext: AgentContext, - options: CreateProposalOptions - ): Promise<{ proofRecord: ProofExchangeRecord; message: DidCommV1Message }> { - const formats = [] - for (const key of Object.keys(options.proofFormats)) { - const service = this.formatServiceMap[key] - formats.push(await service.createProposal({ formats: options.proofFormats })) - } - - const proposalMessage = new V2ProposalPresentationMessage({ - attachmentInfo: formats, - comment: options.comment, - willConfirm: options.willConfirm, - goalCode: options.goalCode, - parentThreadId: options.parentThreadId, - }) - - const proofRecord = new ProofExchangeRecord({ - connectionId: options.connectionRecord.id, - threadId: proposalMessage.threadId, - parentThreadId: proposalMessage.thread?.parentThreadId, - state: ProofState.ProposalSent, - protocolVersion: 'v2', - }) - - await this.proofRepository.save(agentContext, proofRecord) - - await this.didCommMessageRepository.saveOrUpdateAgentMessage(agentContext, { - agentMessage: proposalMessage, - role: DidCommMessageRole.Sender, - associatedRecordId: proofRecord.id, - }) - - this.emitStateChangedEvent(agentContext, proofRecord, null) - - return { - proofRecord: proofRecord, - message: proposalMessage, - } - } - - public async createProposalAsResponse( - agentContext: AgentContext, - options: CreateProposalAsResponseOptions - ): Promise<{ proofRecord: ProofExchangeRecord; message: DidCommV1Message }> { - options.proofRecord.assertState(ProofState.RequestReceived) - - const formats = [] - for (const key of Object.keys(options.proofFormats)) { - const service = this.formatServiceMap[key] - formats.push( - await service.createProposal({ - formats: options.proofFormats, - }) - ) - } - - const proposalMessage = new V2ProposalPresentationMessage({ - attachmentInfo: formats, - comment: options.comment, - goalCode: options.goalCode, - willConfirm: options.willConfirm, - }) - - proposalMessage.setThread({ threadId: options.proofRecord.threadId }) - - await this.didCommMessageRepository.saveOrUpdateAgentMessage(agentContext, { - agentMessage: proposalMessage, - role: DidCommMessageRole.Sender, - associatedRecordId: options.proofRecord.id, - }) - - await this.updateState(agentContext, options.proofRecord, ProofState.ProposalSent) - - return { message: proposalMessage, proofRecord: options.proofRecord } - } - - public async processProposal( - messageContext: InboundMessageContext - ): Promise { - const { message: proposalMessage, connection: connectionRecord } = messageContext - let proofRecord: ProofExchangeRecord - - const proposalAttachments = proposalMessage.getAttachmentFormats() - - for (const attachmentFormat of proposalAttachments) { - const service = this.getFormatServiceForFormat(attachmentFormat.format) - await service?.processProposal({ - proposal: attachmentFormat, - }) - } - - try { - proofRecord = await this.proofRepository.getSingleByQuery(messageContext.agentContext, { - threadId: proposalMessage.threadId, - connectionId: connectionRecord?.id, - }) - - const requestMessage = await this.didCommMessageRepository.findAgentMessage(messageContext.agentContext, { - associatedRecordId: proofRecord.id, - messageClass: V2RequestPresentationMessage, - }) - - // Assert - proofRecord.assertState(ProofState.RequestSent) - this.connectionService.assertConnectionOrServiceDecorator(messageContext, { - previousReceivedMessage: proposalMessage, - previousSentMessage: requestMessage ?? undefined, - }) - - await this.didCommMessageRepository.saveOrUpdateAgentMessage(messageContext.agentContext, { - agentMessage: proposalMessage, - associatedRecordId: proofRecord.id, - role: DidCommMessageRole.Receiver, - }) - - await this.updateState(messageContext.agentContext, proofRecord, ProofState.ProposalReceived) - } catch { - // No proof record exists with thread id - proofRecord = new ProofExchangeRecord({ - connectionId: connectionRecord?.id, - threadId: proposalMessage.threadId, - parentThreadId: proposalMessage.thread?.parentThreadId, - state: ProofState.ProposalReceived, - protocolVersion: 'v2', - }) - - // Assert - this.connectionService.assertConnectionOrServiceDecorator(messageContext) - - // Save record - await this.didCommMessageRepository.saveOrUpdateAgentMessage(messageContext.agentContext, { - agentMessage: proposalMessage, - associatedRecordId: proofRecord.id, - role: DidCommMessageRole.Receiver, - }) - - await this.proofRepository.save(messageContext.agentContext, proofRecord) - this.emitStateChangedEvent(messageContext.agentContext, proofRecord, null) - } - - return proofRecord - } - - public async createRequest( - agentContext: AgentContext, - options: CreateRequestOptions - ): Promise<{ proofRecord: ProofExchangeRecord; message: DidCommV1Message }> { - // create attachment formats - const formats = [] - for (const key of Object.keys(options.proofFormats)) { - const service = this.formatServiceMap[key] - formats.push( - await service.createRequest({ - formats: options.proofFormats, - }) - ) - } - - // create request message - const requestMessage = new V2RequestPresentationMessage({ - attachmentInfo: formats, - comment: options.comment, - willConfirm: options.willConfirm, - goalCode: options.goalCode, - parentThreadId: options.parentThreadId, - }) - - // create & store proof record - const proofRecord = new ProofExchangeRecord({ - connectionId: options.connectionRecord?.id, - threadId: requestMessage.threadId, - parentThreadId: requestMessage.thread?.parentThreadId, - state: ProofState.RequestSent, - protocolVersion: 'v2', - }) - - await this.proofRepository.save(agentContext, proofRecord) - - // create DIDComm message - await this.didCommMessageRepository.saveOrUpdateAgentMessage(agentContext, { - agentMessage: requestMessage, - role: DidCommMessageRole.Sender, - associatedRecordId: proofRecord.id, - }) - - this.emitStateChangedEvent(agentContext, proofRecord, null) - - return { - proofRecord: proofRecord, - message: requestMessage, - } - } - - public async createRequestAsResponse( - agentContext: AgentContext, - options: CreateRequestAsResponseOptions - ): Promise<{ proofRecord: ProofExchangeRecord; message: DidCommV1Message }> { - options.proofRecord.assertState(ProofState.ProposalReceived) - - const proposal = await this.didCommMessageRepository.getAgentMessage(agentContext, { - associatedRecordId: options.proofRecord.id, - messageClass: V2ProposalPresentationMessage, - }) - - if (!proposal) { - throw new AriesFrameworkError( - `Proof record with id ${options.proofRecord.id} is missing required presentation proposal` - ) - } - - // create attachment formats - const formats = [] - - for (const key of Object.keys(options.proofFormats)) { - const service = this.formatServiceMap[key] - const requestOptions: CreateRequestAsResponseOptions = { - proofFormats: options.proofFormats, - proofRecord: options.proofRecord, - } - formats.push(await service.createRequestAsResponse(requestOptions)) - } - - // create request message - const requestMessage = new V2RequestPresentationMessage({ - attachmentInfo: formats, - comment: options.comment, - willConfirm: options.willConfirm, - goalCode: options.goalCode, - }) - requestMessage.setThread({ threadId: options.proofRecord.threadId }) - - await this.didCommMessageRepository.saveOrUpdateAgentMessage(agentContext, { - agentMessage: requestMessage, - role: DidCommMessageRole.Sender, - associatedRecordId: options.proofRecord.id, - }) - - await this.updateState(agentContext, options.proofRecord, ProofState.RequestSent) - - return { message: requestMessage, proofRecord: options.proofRecord } - } - - public async processRequest( - messageContext: InboundMessageContext - ): Promise { - const { message: proofRequestMessage, connection: connectionRecord } = messageContext - - const requestAttachments = proofRequestMessage.getAttachmentFormats() - - for (const attachmentFormat of requestAttachments) { - const service = this.getFormatServiceForFormat(attachmentFormat.format) - service?.processRequest({ - requestAttachment: attachmentFormat, - }) - } - - // assert - if (proofRequestMessage.requestPresentationsAttach.length === 0) { - throw new V2PresentationProblemReportError( - `Missing required base64 or json encoded attachment data for presentation request with thread id ${proofRequestMessage.threadId}`, - { problemCode: PresentationProblemReportReason.Abandoned } - ) - } - - this.logger.debug(`Received proof request`, proofRequestMessage) - - let proofRecord: ProofExchangeRecord - - try { - proofRecord = await this.proofRepository.getSingleByQuery(messageContext.agentContext, { - threadId: proofRequestMessage.threadId, - connectionId: connectionRecord?.id, - }) - - const requestMessage = await this.didCommMessageRepository.findAgentMessage(messageContext.agentContext, { - associatedRecordId: proofRecord.id, - messageClass: V2RequestPresentationMessage, - }) - - const proposalMessage = await this.didCommMessageRepository.findAgentMessage(messageContext.agentContext, { - associatedRecordId: proofRecord.id, - messageClass: V2ProposalPresentationMessage, - }) - - // Assert - proofRecord.assertState(ProofState.ProposalSent) - this.connectionService.assertConnectionOrServiceDecorator(messageContext, { - previousReceivedMessage: requestMessage ?? undefined, - previousSentMessage: proposalMessage ?? undefined, - }) - - await this.didCommMessageRepository.saveOrUpdateAgentMessage(messageContext.agentContext, { - agentMessage: proofRequestMessage, - associatedRecordId: proofRecord.id, - role: DidCommMessageRole.Receiver, - }) - - // Update record - await this.updateState(messageContext.agentContext, proofRecord, ProofState.RequestReceived) - } catch { - // No proof record exists with thread id - proofRecord = new ProofExchangeRecord({ - connectionId: connectionRecord?.id, - threadId: proofRequestMessage.threadId, - parentThreadId: proofRequestMessage.thread?.parentThreadId, - state: ProofState.RequestReceived, - protocolVersion: 'v2', - }) - - await this.didCommMessageRepository.saveOrUpdateAgentMessage(messageContext.agentContext, { - agentMessage: proofRequestMessage, - associatedRecordId: proofRecord.id, - role: DidCommMessageRole.Receiver, - }) - - // Assert - this.connectionService.assertConnectionOrServiceDecorator(messageContext) - - // Save in repository - await this.proofRepository.save(messageContext.agentContext, proofRecord) - this.emitStateChangedEvent(messageContext.agentContext, proofRecord, null) - } - - return proofRecord - } - - public async createPresentation( - agentContext: AgentContext, - options: CreatePresentationOptions - ): Promise<{ proofRecord: ProofExchangeRecord; message: DidCommV1Message }> { - // assert state - options.proofRecord.assertState(ProofState.RequestReceived) - - const proofRequest = await this.didCommMessageRepository.getAgentMessage(agentContext, { - associatedRecordId: options.proofRecord.id, - messageClass: V2RequestPresentationMessage, - }) - - const formats = [] - for (const key of Object.keys(options.proofFormats)) { - const service = this.formatServiceMap[key] - formats.push( - await service.createPresentation(agentContext, { - attachment: proofRequest.getAttachmentByFormatIdentifier(V2_INDY_PRESENTATION_REQUEST), - proofFormats: options.proofFormats, - }) - ) - } - - const presentationMessage = new V2PresentationMessage({ - comment: options.comment, - attachmentInfo: formats, - goalCode: options.goalCode, - lastPresentation: options.lastPresentation, - }) - presentationMessage.setThread({ threadId: options.proofRecord.threadId }) - - await this.didCommMessageRepository.saveOrUpdateAgentMessage(agentContext, { - agentMessage: presentationMessage, - associatedRecordId: options.proofRecord.id, - role: DidCommMessageRole.Sender, - }) - - await this.updateState(agentContext, options.proofRecord, ProofState.PresentationSent) - - return { message: presentationMessage, proofRecord: options.proofRecord } - } - - public async processPresentation( - messageContext: InboundMessageContext - ): Promise { - const { message: presentationMessage, connection: connectionRecord } = messageContext - - this.logger.debug(`Processing presentation with id ${presentationMessage.id}`) - - const proofRecord = await this.proofRepository.getSingleByQuery(messageContext.agentContext, { - threadId: presentationMessage.threadId, - connectionId: connectionRecord?.id, - }) - - const proposalMessage = await this.didCommMessageRepository.findAgentMessage(messageContext.agentContext, { - associatedRecordId: proofRecord.id, - messageClass: V2ProposalPresentationMessage, - }) - - const requestMessage = await this.didCommMessageRepository.getAgentMessage(messageContext.agentContext, { - associatedRecordId: proofRecord.id, - messageClass: V2RequestPresentationMessage, - }) - - // Assert - proofRecord.assertState(ProofState.RequestSent) - this.connectionService.assertConnectionOrServiceDecorator(messageContext, { - previousReceivedMessage: proposalMessage ?? undefined, - previousSentMessage: requestMessage ?? undefined, - }) - - const formatVerificationResults = [] - for (const attachmentFormat of presentationMessage.getAttachmentFormats()) { - const service = this.getFormatServiceForFormat(attachmentFormat.format) - if (service) { - try { - formatVerificationResults.push( - await service.processPresentation(messageContext.agentContext, { - record: proofRecord, - formatAttachments: { - request: requestMessage?.getAttachmentFormats(), - presentation: presentationMessage.getAttachmentFormats(), - }, - }) - ) - } catch (e) { - if (e instanceof AriesFrameworkError) { - throw new V2PresentationProblemReportError(e.message, { - problemCode: PresentationProblemReportReason.Abandoned, - }) - } - throw e - } - } - } - if (formatVerificationResults.length === 0) { - throw new V2PresentationProblemReportError('None of the received formats are supported.', { - problemCode: PresentationProblemReportReason.Abandoned, - }) - } - - const isValid = formatVerificationResults.every((x) => x === true) - - await this.didCommMessageRepository.saveOrUpdateAgentMessage(messageContext.agentContext, { - agentMessage: presentationMessage, - associatedRecordId: proofRecord.id, - role: DidCommMessageRole.Receiver, - }) - - // Update record - proofRecord.isVerified = isValid - await this.updateState(messageContext.agentContext, proofRecord, ProofState.PresentationReceived) - - return proofRecord - } - - public async createAck( - agentContext: AgentContext, - options: CreateAckOptions - ): Promise<{ proofRecord: ProofExchangeRecord; message: DidCommV1Message }> { - // assert we've received the final presentation - const presentation = await this.didCommMessageRepository.getAgentMessage(agentContext, { - associatedRecordId: options.proofRecord.id, - messageClass: V2PresentationMessage, - }) - - if (!presentation.lastPresentation) { - throw new AriesFrameworkError( - `Trying to send an ack message while presentation with id ${presentation.id} indicates this is not the last presentation (presentation.lastPresentation is set to false)` - ) - } - - const message = new V2PresentationAckMessage({ - threadId: options.proofRecord.threadId, - status: AckStatus.OK, - }) - - await this.updateState(agentContext, options.proofRecord, ProofState.Done) - - return { - message, - proofRecord: options.proofRecord, - } - } - - public async processAck( - messageContext: InboundMessageContext - ): Promise { - const { message: ackMessage, connection: connectionRecord } = messageContext - - const proofRecord = await this.proofRepository.getSingleByQuery(messageContext.agentContext, { - threadId: ackMessage.threadId, - connectionId: connectionRecord?.id, - }) - - const requestMessage = await this.didCommMessageRepository.findAgentMessage(messageContext.agentContext, { - associatedRecordId: proofRecord.id, - messageClass: V2RequestPresentationMessage, - }) - - const presentationMessage = await this.didCommMessageRepository.findAgentMessage(messageContext.agentContext, { - associatedRecordId: proofRecord.id, - messageClass: V2PresentationMessage, - }) - - // Assert - proofRecord.assertState(ProofState.PresentationSent) - this.connectionService.assertConnectionOrServiceDecorator(messageContext, { - previousReceivedMessage: requestMessage ?? undefined, - previousSentMessage: presentationMessage ?? undefined, - }) - - // Update record - await this.updateState(messageContext.agentContext, proofRecord, ProofState.Done) - - return proofRecord - } - - public async createProblemReport( - agentContext: AgentContext, - options: CreateProblemReportOptions - ): Promise<{ proofRecord: ProofExchangeRecord; message: DidCommV1Message }> { - const msg = new V2PresentationProblemReportMessage({ - description: { - code: PresentationProblemReportReason.Abandoned, - en: options.description, - }, - }) - - msg.setThread({ - threadId: options.proofRecord.threadId, - parentThreadId: options.proofRecord.threadId, - }) - - return { - proofRecord: options.proofRecord, - message: msg, - } - } - - public async processProblemReport( - messageContext: InboundMessageContext - ): Promise { - const { message: presentationProblemReportMessage } = messageContext - - const connectionRecord = messageContext.assertReadyConnection() - - this.logger.debug(`Processing problem report with id ${presentationProblemReportMessage.id}`) - - const proofRecord = await this.proofRepository.getSingleByQuery(messageContext.agentContext, { - threadId: presentationProblemReportMessage.threadId, - connectionId: connectionRecord?.id, - }) - - proofRecord.errorMessage = `${presentationProblemReportMessage.description.code}: ${presentationProblemReportMessage.description.en}` - await this.updateState(messageContext.agentContext, proofRecord, ProofState.Abandoned) - return proofRecord - } - - public async createProofRequestFromProposal( - agentContext: AgentContext, - options: CreateProofRequestFromProposalOptions - ): Promise> { - const proofRecordId = options.proofRecord.id - const proposalMessage = await this.didCommMessageRepository.findAgentMessage(agentContext, { - associatedRecordId: proofRecordId, - messageClass: V2ProposalPresentationMessage, - }) - - if (!proposalMessage) { - throw new AriesFrameworkError(`Proof record with id ${proofRecordId} is missing required presentation proposal`) - } - - const proposalAttachments = proposalMessage.getAttachmentFormats() - - let result = {} - - for (const attachmentFormat of proposalAttachments) { - const service = this.getFormatServiceForFormat(attachmentFormat.format) - - if (!service) { - throw new AriesFrameworkError('No format service found for getting requested.') - } - - result = { - ...result, - ...(await service.createProofRequestFromProposal({ - presentationAttachment: attachmentFormat.attachment, - })), - } - } - - const retVal: ProofRequestFromProposalOptions = { - proofRecord: options.proofRecord, - proofFormats: result, - } - return retVal - } - - public async shouldAutoRespondToProposal( - agentContext: AgentContext, - proofRecord: ProofExchangeRecord - ): Promise { - const proposal = await this.didCommMessageRepository.findAgentMessage(agentContext, { - associatedRecordId: proofRecord.id, - messageClass: V2ProposalPresentationMessage, - }) - - if (!proposal) return false - - const request = await this.didCommMessageRepository.findAgentMessage(agentContext, { - associatedRecordId: proofRecord.id, - messageClass: V2RequestPresentationMessage, - }) - - if (!request) return false - - MessageValidator.validateSync(proposal) - - const proposalAttachments = proposal.getAttachmentFormats() - const requestAttachments = request.getAttachmentFormats() - - const equalityResults = [] - for (const attachmentFormat of proposalAttachments) { - const service = this.getFormatServiceForFormat(attachmentFormat.format) - equalityResults.push(service?.proposalAndRequestAreEqual(proposalAttachments, requestAttachments)) - } - return true - } - - public async shouldAutoRespondToRequest( - agentContext: AgentContext, - proofRecord: ProofExchangeRecord - ): Promise { - const proposal = await this.didCommMessageRepository.findAgentMessage(agentContext, { - associatedRecordId: proofRecord.id, - messageClass: V2ProposalPresentationMessage, - }) - - if (!proposal) { - return false - } - - const request = await this.didCommMessageRepository.findAgentMessage(agentContext, { - associatedRecordId: proofRecord.id, - messageClass: V2RequestPresentationMessage, - }) - - if (!request) { - throw new AriesFrameworkError( - `Expected to find a request message for ProofExchangeRecord with id ${proofRecord.id}` - ) - } - - const proposalAttachments = proposal.getAttachmentFormats() - const requestAttachments = request.getAttachmentFormats() - - const equalityResults = [] - for (const attachmentFormat of proposalAttachments) { - const service = this.getFormatServiceForFormat(attachmentFormat.format) - equalityResults.push(service?.proposalAndRequestAreEqual(proposalAttachments, requestAttachments)) - } - - return equalityResults.every((x) => x === true) - } - - public async shouldAutoRespondToPresentation( - agentContext: AgentContext, - proofRecord: ProofExchangeRecord - ): Promise { - const request = await this.didCommMessageRepository.getAgentMessage(agentContext, { - associatedRecordId: proofRecord.id, - messageClass: V2RequestPresentationMessage, - }) - - return request.willConfirm - } - - public async findRequestMessage( - agentContext: AgentContext, - proofRecordId: string - ): Promise { - return await this.didCommMessageRepository.findAgentMessage(agentContext, { - associatedRecordId: proofRecordId, - messageClass: V2RequestPresentationMessage, - }) - } - - public async findPresentationMessage( - agentContext: AgentContext, - proofRecordId: string - ): Promise { - return await this.didCommMessageRepository.findAgentMessage(agentContext, { - associatedRecordId: proofRecordId, - messageClass: V2PresentationMessage, - }) - } - - public async findProposalMessage( - agentContext: AgentContext, - proofRecordId: string - ): Promise { - return await this.didCommMessageRepository.findAgentMessage(agentContext, { - associatedRecordId: proofRecordId, - messageClass: V2ProposalPresentationMessage, - }) - } - - public async getFormatData(agentContext: AgentContext, proofRecordId: string): Promise { - // TODO: we could looking at fetching all record using a single query and then filtering based on the type of the message. - const [proposalMessage, requestMessage, presentationMessage] = await Promise.all([ - this.findProposalMessage(agentContext, proofRecordId), - this.findRequestMessage(agentContext, proofRecordId), - this.findPresentationMessage(agentContext, proofRecordId), - ]) - - // Create object with the keys and the message formats/attachments. We can then loop over this in a generic - // way so we don't have to add the same operation code four times - const messages = { - proposal: [proposalMessage?.formats, proposalMessage?.proposalsAttach], - request: [requestMessage?.formats, requestMessage?.requestPresentationsAttach], - presentation: [presentationMessage?.formats, presentationMessage?.presentationsAttach], - } as const - - const formatData: GetFormatDataReturn = {} - - // We loop through all of the message keys as defined above - for (const [messageKey, [formats, attachments]] of Object.entries(messages)) { - // Message can be undefined, so we continue if it is not defined - if (!formats || !attachments) continue - - // Find all format services associated with the message - const formatServices = this.getFormatServicesFromMessage(formats) - - const messageFormatData: FormatDataMessagePayload = {} - - // Loop through all of the format services, for each we will extract the attachment data and assign this to the object - // using the unique format key (e.g. indy) - for (const formatService of formatServices) { - const attachment = this.getAttachmentForService(formatService, formats, attachments) - messageFormatData[formatService.formatKey] = attachment.getDataAsJson() - } - - formatData[messageKey as Exclude] = - messageFormatData - } - - return formatData - } - - private getFormatServicesFromMessage(messageFormats: ProofFormatSpec[]): ProofFormatService[] { - const formatServices = new Set() - - for (const msg of messageFormats) { - const service = this.getFormatServiceForFormat(msg) - if (service) formatServices.add(service) - } - - return Array.from(formatServices) - } - - private getAttachmentForService( - proofFormatService: ProofFormatService, - formats: ProofFormatSpec[], - attachments: V1Attachment[] - ) { - const attachmentId = this.getAttachmentIdForService(proofFormatService, formats) - const attachment = attachments.find((attachment) => attachment.id === attachmentId) - - if (!attachment) { - throw new AriesFrameworkError(`Attachment with id ${attachmentId} not found in attachments.`) - } - - return attachment - } - - private getAttachmentIdForService(proofFormatService: ProofFormatService, formats: ProofFormatSpec[]) { - const format = formats.find((format) => proofFormatService.supportsFormat(format.format)) - - if (!format) throw new AriesFrameworkError(`No attachment found for service ${proofFormatService.formatKey}`) - - return format.attachmentId - } - - public async getRequestedCredentialsForProofRequest( - agentContext: AgentContext, - options: GetRequestedCredentialsForProofRequestOptions - ): Promise> { - const requestMessage = await this.didCommMessageRepository.findAgentMessage(agentContext, { - associatedRecordId: options.proofRecord.id, - messageClass: V2RequestPresentationMessage, - }) - - if (!requestMessage) { - throw new AriesFrameworkError('No proof request found.') - } - - const requestAttachments = requestMessage.getAttachmentFormats() - - let result = { - proofFormats: {}, - } - for (const attachmentFormat of requestAttachments) { - const service = this.getFormatServiceForFormat(attachmentFormat.format) - - if (!service) { - throw new AriesFrameworkError('No format service found for getting requested.') - } - - result = { - ...result, - ...(await service.getRequestedCredentialsForProofRequest(agentContext, { - attachment: attachmentFormat.attachment, - presentationProposal: undefined, - config: options.config, - })), - } - } - - return result - } - - public async autoSelectCredentialsForProofRequest( - options: FormatRetrievedCredentialOptions - ): Promise> { - let returnValue = { - proofFormats: {}, - } - - for (const [id] of Object.entries(options.proofFormats)) { - const service = this.formatServiceMap[id] - const credentials = await service.autoSelectCredentialsForProofRequest(options) - returnValue = { ...returnValue, ...credentials } - } - - return returnValue - } - - public registerMessageHandlers( - dispatcher: Dispatcher, - agentConfig: AgentConfig, - proofResponseCoordinator: ProofResponseCoordinator, - mediationRecipientService: MediationRecipientService, - routingService: RoutingService - ): void { - dispatcher.registerMessageHandler( - new V2ProposePresentationHandler(this, agentConfig, this.didCommMessageRepository, proofResponseCoordinator) - ) - - dispatcher.registerMessageHandler( - new V2RequestPresentationHandler( - this, - agentConfig, - proofResponseCoordinator, - mediationRecipientService, - this.didCommMessageRepository, - routingService - ) - ) - - dispatcher.registerMessageHandler( - new V2PresentationHandler(this, agentConfig, proofResponseCoordinator, this.didCommMessageRepository) - ) - dispatcher.registerMessageHandler(new V2PresentationAckHandler(this)) - dispatcher.registerMessageHandler(new V2PresentationProblemReportHandler(this)) - } - - private getFormatServiceForFormat(format: ProofFormatSpec) { - for (const service of Object.values(this.formatServiceMap)) { - if (service.supportsFormat(format.format)) { - return service - } - } - return null - } -} diff --git a/packages/core/src/modules/proofs/__tests__/V2ProofService.test.ts b/packages/core/src/modules/proofs/protocol/v2/__tests__/V2ProofProtocol.test.ts similarity index 61% rename from packages/core/src/modules/proofs/__tests__/V2ProofService.test.ts rename to packages/core/src/modules/proofs/protocol/v2/__tests__/V2ProofProtocol.test.ts index ca09d6d432..d0a44f69ea 100644 --- a/packages/core/src/modules/proofs/__tests__/V2ProofService.test.ts +++ b/packages/core/src/modules/proofs/protocol/v2/__tests__/V2ProofProtocol.test.ts @@ -1,44 +1,57 @@ -import type { AgentContext } from '../../../agent' -import type { Wallet } from '../../../wallet/Wallet' -import type { ProofStateChangedEvent } from '../ProofEvents' -import type { CustomProofTags } from '../repository/ProofExchangeRecord' +import type { ProofStateChangedEvent } from '../../../ProofEvents' +import type { ProofFormatService } from '../../../formats' +import type { CustomProofTags } from '../../../repository/ProofExchangeRecord' import { Subject } from 'rxjs' -import { getAgentConfig, getAgentContext, getMockConnection, mockFunction } from '../../../../tests/helpers' -import { EventEmitter } from '../../../agent/EventEmitter' -import { InboundMessageContext } from '../../../agent/models/InboundMessageContext' -import { V1Attachment, V1AttachmentData } from '../../../decorators/attachment/V1Attachment' -import { DidCommMessageRepository } from '../../../storage' -import { ConnectionService, DidExchangeState } from '../../connections' -import { IndyLedgerService } from '../../ledger/services/IndyLedgerService' -import { ProofEventTypes } from '../ProofEvents' -import { PresentationProblemReportReason } from '../errors/PresentationProblemReportReason' -import { V2_INDY_PRESENTATION, V2_INDY_PRESENTATION_REQUEST } from '../formats/ProofFormatConstants' -import { IndyProofFormatService } from '../formats/indy/IndyProofFormatService' -import { ProofState } from '../models/ProofState' -import { V2ProofService } from '../protocol/v2/V2ProofService' -import { V2PresentationProblemReportMessage, V2RequestPresentationMessage } from '../protocol/v2/messages' -import { ProofExchangeRecord } from '../repository/ProofExchangeRecord' -import { ProofRepository } from '../repository/ProofRepository' - -import { credDef } from './fixtures' +import { getAgentConfig, getAgentContext, getMockConnection, mockFunction } from '../../../../../../tests/helpers' +import { EventEmitter } from '../../../../../agent/EventEmitter' +import { InboundMessageContext } from '../../../../../agent/models/InboundMessageContext' +import { V1Attachment, V1AttachmentData } from '../../../../../decorators/attachment/V1Attachment' +import { DidCommMessageRepository } from '../../../../../storage' +import { uuid } from '../../../../../utils/uuid' +import { ConnectionService, DidExchangeState } from '../../../../connections' +import { ProofEventTypes } from '../../../ProofEvents' +import { PresentationProblemReportReason } from '../../../errors/PresentationProblemReportReason' +import { ProofFormatSpec } from '../../../models/ProofFormatSpec' +import { ProofState } from '../../../models/ProofState' +import { ProofExchangeRecord } from '../../../repository/ProofExchangeRecord' +import { ProofRepository } from '../../../repository/ProofRepository' +import { V2ProofProtocol } from '../V2ProofProtocol' +import { V2PresentationProblemReportMessage, V2RequestPresentationMessage } from '../messages' // Mock classes -jest.mock('../repository/ProofRepository') -jest.mock('../../../modules/ledger/services/IndyLedgerService') -jest.mock('../../indy/services/IndyHolderService') -jest.mock('../../indy/services/IndyIssuerService') -jest.mock('../../indy/services/IndyVerifierService') -jest.mock('../../connections/services/ConnectionService') -jest.mock('../../../storage/Repository') +jest.mock('../../../repository/ProofRepository') +jest.mock('../../../../connections/services/ConnectionService') +jest.mock('../../../../../storage/Repository') // Mock typed object const ProofRepositoryMock = ProofRepository as jest.Mock -const IndyLedgerServiceMock = IndyLedgerService as jest.Mock const connectionServiceMock = ConnectionService as jest.Mock const didCommMessageRepositoryMock = DidCommMessageRepository as jest.Mock -const indyProofFormatServiceMock = IndyProofFormatService as jest.Mock + +const proofRepository = new ProofRepositoryMock() +const connectionService = new connectionServiceMock() +const didCommMessageRepository = new didCommMessageRepositoryMock() +const proofFormatService = { + supportsFormat: () => true, + processRequest: jest.fn(), +} as unknown as ProofFormatService + +const agentConfig = getAgentConfig('V2ProofProtocolTest') +const eventEmitter = new EventEmitter(agentConfig.agentDependencies, new Subject()) + +const agentContext = getAgentContext({ + registerInstances: [ + [ProofRepository, proofRepository], + [DidCommMessageRepository, didCommMessageRepository], + [ConnectionService, connectionService], + [EventEmitter, eventEmitter], + ], + agentConfig, +}) + +const proofProtocol = new V2ProofProtocol({ proofFormats: [proofFormatService] }) const connection = getMockConnection({ id: '123', @@ -64,30 +77,16 @@ const mockProofExchangeRecord = ({ id, }: { state?: ProofState - requestMessage?: V2RequestPresentationMessage tags?: CustomProofTags threadId?: string connectionId?: string id?: string } = {}) => { - const requestPresentationMessage = new V2RequestPresentationMessage({ - attachmentInfo: [ - { - format: { - attachmentId: 'abdc8b63-29c6-49ad-9e10-98f9d85db9a2', - format: V2_INDY_PRESENTATION, - }, - attachment: requestAttachment, - }, - ], - comment: 'some comment', - }) - const proofRecord = new ProofExchangeRecord({ protocolVersion: 'v2', id, state: state || ProofState.RequestSent, - threadId: threadId ?? requestPresentationMessage.id, + threadId: threadId ?? uuid(), connectionId: connectionId ?? '123', tags, }) @@ -95,57 +94,23 @@ const mockProofExchangeRecord = ({ return proofRecord } -describe('V2ProofService', () => { - let proofRepository: ProofRepository - let proofService: V2ProofService - let ledgerService: IndyLedgerService - let wallet: Wallet - let eventEmitter: EventEmitter - let connectionService: ConnectionService - let didCommMessageRepository: DidCommMessageRepository - let indyProofFormatService: IndyProofFormatService - let agentContext: AgentContext - - beforeEach(() => { - agentContext = getAgentContext() - const agentConfig = getAgentConfig('V2ProofServiceTest') - proofRepository = new ProofRepositoryMock() - ledgerService = new IndyLedgerServiceMock() - eventEmitter = new EventEmitter(agentConfig.agentDependencies, new Subject()) - connectionService = new connectionServiceMock() - didCommMessageRepository = new didCommMessageRepositoryMock() - indyProofFormatService = new indyProofFormatServiceMock() - - proofService = new V2ProofService( - agentConfig, - connectionService, - proofRepository, - didCommMessageRepository, - eventEmitter, - indyProofFormatService, - wallet - ) - - mockFunction(ledgerService.getCredentialDefinition).mockReturnValue(Promise.resolve(credDef)) - }) - +describe('V2ProofProtocol', () => { describe('processProofRequest', () => { let presentationRequest: V2RequestPresentationMessage let messageContext: InboundMessageContext beforeEach(() => { presentationRequest = new V2RequestPresentationMessage({ - attachmentInfo: [ - { - format: { - attachmentId: 'abdc8b63-29c6-49ad-9e10-98f9d85db9a2', - format: V2_INDY_PRESENTATION_REQUEST, - }, - attachment: requestAttachment, - }, + formats: [ + new ProofFormatSpec({ + attachmentId: 'abdc8b63-29c6-49ad-9e10-98f9d85db9a2', + format: 'hlindy/proof-req@v2.0', + }), ], + requestAttachments: [requestAttachment], comment: 'Proof Request', }) + messageContext = new InboundMessageContext(presentationRequest, { agentContext, connection }) }) @@ -153,7 +118,7 @@ describe('V2ProofService', () => { const repositorySaveSpy = jest.spyOn(proofRepository, 'save') // when - const returnedProofExchangeRecord = await proofService.processRequest(messageContext) + const returnedProofExchangeRecord = await proofProtocol.processRequest(messageContext) // then const expectedProofExchangeRecord = { @@ -175,7 +140,7 @@ describe('V2ProofService', () => { eventEmitter.on(ProofEventTypes.ProofStateChanged, eventListenerMock) // when - await proofService.processRequest(messageContext) + await proofProtocol.processRequest(messageContext) // then expect(eventListenerMock).toHaveBeenCalledWith({ @@ -254,7 +219,7 @@ describe('V2ProofService', () => { mockFunction(proofRepository.getSingleByQuery).mockReturnValue(Promise.resolve(proof)) // when - const returnedCredentialRecord = await proofService.processProblemReport(messageContext) + const returnedCredentialRecord = await proofProtocol.processProblemReport(messageContext) // then const expectedCredentialRecord = { diff --git a/packages/core/src/modules/proofs/protocol/v2/__tests__/indy-proof-negotiation.test.ts b/packages/core/src/modules/proofs/protocol/v2/__tests__/indy-proof-negotiation.test.ts deleted file mode 100644 index 7ebe83cb32..0000000000 --- a/packages/core/src/modules/proofs/protocol/v2/__tests__/indy-proof-negotiation.test.ts +++ /dev/null @@ -1,444 +0,0 @@ -import type { Agent } from '../../../../../agent/Agent' -import type { ConnectionRecord } from '../../../../connections/repository/ConnectionRecord' -import type { AcceptProofProposalOptions, NegotiateProposalOptions } from '../../../ProofsApiOptions' -import type { ProofExchangeRecord } from '../../../repository/ProofExchangeRecord' -import type { PresentationPreview } from '../../v1/models/V1PresentationPreview' -import type { CredDefId } from 'indy-sdk' - -import { setupProofsTest, waitForProofExchangeRecord } from '../../../../../../tests/helpers' -import testLogger from '../../../../../../tests/logger' -import { DidCommMessageRepository } from '../../../../../storage/didcomm' -import { JsonTransformer } from '../../../../../utils/JsonTransformer' -import { V2_INDY_PRESENTATION_PROPOSAL, V2_INDY_PRESENTATION_REQUEST } from '../../../formats/ProofFormatConstants' -import { AttributeFilter } from '../../../formats/indy/models/AttributeFilter' -import { PredicateType } from '../../../formats/indy/models/PredicateType' -import { ProofAttributeInfo } from '../../../formats/indy/models/ProofAttributeInfo' -import { ProofPredicateInfo } from '../../../formats/indy/models/ProofPredicateInfo' -import { ProofRequest } from '../../../formats/indy/models/ProofRequest' -import { ProofState } from '../../../models/ProofState' -import { V2ProposalPresentationMessage, V2RequestPresentationMessage } from '../messages' - -describe('Present Proof', () => { - let faberAgent: Agent - let aliceAgent: Agent - let credDefId: CredDefId - let aliceConnection: ConnectionRecord - let presentationPreview: PresentationPreview - let faberProofExchangeRecord: ProofExchangeRecord - let aliceProofExchangeRecord: ProofExchangeRecord - let didCommMessageRepository: DidCommMessageRepository - - beforeAll(async () => { - testLogger.test('Initializing the agents') - ;({ faberAgent, aliceAgent, credDefId, aliceConnection, presentationPreview } = await setupProofsTest( - 'Faber agent', - 'Alice agent' - )) - }) - - afterAll(async () => { - testLogger.test('Shutting down both agents') - await faberAgent.shutdown() - await faberAgent.wallet.delete() - await aliceAgent.shutdown() - await aliceAgent.wallet.delete() - }) - - test(`Proof negotiation between Alice and Faber`, async () => { - testLogger.test('Alice sends proof proposal to Faber') - - let faberProofExchangeRecordPromise = waitForProofExchangeRecord(faberAgent, { - state: ProofState.ProposalReceived, - }) - - aliceProofExchangeRecord = await aliceAgent.proofs.proposeProof({ - connectionId: aliceConnection.id, - protocolVersion: 'v2', - proofFormats: { - indy: { - name: 'proof-request', - nonce: '58d223e5-fc4d-4448-b74c-5eb11c6b558f', - version: '1.0', - attributes: presentationPreview.attributes.filter((attribute) => attribute.name !== 'name'), - predicates: presentationPreview.predicates, - }, - }, - comment: 'V2 propose proof test 1', - }) - - testLogger.test('Faber waits for presentation from Alice') - faberProofExchangeRecord = await faberProofExchangeRecordPromise - - didCommMessageRepository = faberAgent.injectionContainer.resolve(DidCommMessageRepository) - - let proposal = await didCommMessageRepository.findAgentMessage(faberAgent.context, { - associatedRecordId: faberProofExchangeRecord.id, - messageClass: V2ProposalPresentationMessage, - }) - - expect(proposal).toMatchObject({ - type: 'https://didcomm.org/present-proof/2.0/propose-presentation', - formats: [ - { - attachmentId: expect.any(String), - format: V2_INDY_PRESENTATION_PROPOSAL, - }, - ], - proposalsAttach: [ - { - id: expect.any(String), - mimeType: 'application/json', - data: { - base64: expect.any(String), - }, - }, - ], - id: expect.any(String), - comment: 'V2 propose proof test 1', - }) - // eslint-disable-next-line @typescript-eslint/no-explicit-any - let proposalAttach = proposal?.proposalsAttach[0].getDataAsJson() as any - let attributesGroup = Object.keys(proposalAttach.requested_attributes ?? {})[0] - let predicatesGroup = Object.keys(proposalAttach.requested_predicates ?? {})[0] - expect(proposalAttach).toMatchObject({ - requested_attributes: { - [attributesGroup]: { - name: 'image_0', - restrictions: [ - { - cred_def_id: presentationPreview.attributes[1].credentialDefinitionId, - }, - ], - }, - }, - requested_predicates: { - [predicatesGroup]: { - name: 'age', - p_type: '>=', - p_value: 50, - restrictions: [ - { - cred_def_id: presentationPreview.predicates[0].credentialDefinitionId, - }, - ], - }, - }, - }) - expect(faberProofExchangeRecord).toMatchObject({ - id: expect.anything(), - threadId: faberProofExchangeRecord.threadId, - state: ProofState.ProposalReceived, - protocolVersion: 'v2', - }) - - // Negotiate Proposal - const attributes = { - name: new ProofAttributeInfo({ - name: 'name', - restrictions: [ - new AttributeFilter({ - credentialDefinitionId: credDefId, - }), - ], - }), - image_0: new ProofAttributeInfo({ - name: 'image_0', - restrictions: [ - new AttributeFilter({ - credentialDefinitionId: credDefId, - }), - ], - }), - } - - const predicates = { - age: new ProofPredicateInfo({ - name: 'age', - predicateType: PredicateType.GreaterThanOrEqualTo, - predicateValue: 50, - restrictions: [ - new AttributeFilter({ - credentialDefinitionId: credDefId, - }), - ], - }), - } - - const requestProofAsResponseOptions: NegotiateProposalOptions = { - proofRecordId: faberProofExchangeRecord.id, - proofFormats: { - indy: { - name: 'proof-request', - nonce: '58d223e5-fc4d-4448-b74c-5eb11c6b558f', - version: '1.0', - requestedAttributes: attributes, - requestedPredicates: predicates, - }, - }, - } - - let aliceProofExchangeRecordPromise = waitForProofExchangeRecord(aliceAgent, { - threadId: faberProofExchangeRecord.threadId, - state: ProofState.RequestReceived, - }) - - testLogger.test('Faber sends new proof request to Alice') - faberProofExchangeRecord = await faberAgent.proofs.negotiateProposal(requestProofAsResponseOptions) - - testLogger.test('Alice waits for proof request from Faber') - aliceProofExchangeRecord = await aliceProofExchangeRecordPromise - - didCommMessageRepository = faberAgent.injectionContainer.resolve(DidCommMessageRepository) - - let request = await didCommMessageRepository.findAgentMessage(faberAgent.context, { - associatedRecordId: faberProofExchangeRecord.id, - messageClass: V2RequestPresentationMessage, - }) - - expect(request).toMatchObject({ - type: 'https://didcomm.org/present-proof/2.0/request-presentation', - id: expect.any(String), - requestPresentationsAttach: [ - { - id: expect.any(String), - mimeType: 'application/json', - data: { - base64: expect.any(String), - }, - }, - ], - thread: { - threadId: faberProofExchangeRecord.threadId, - }, - }) - expect(aliceProofExchangeRecord).toMatchObject({ - id: expect.anything(), - threadId: faberProofExchangeRecord.threadId, - state: ProofState.RequestReceived, - protocolVersion: 'v2', - }) - - testLogger.test('Alice sends proof proposal to Faber') - - faberProofExchangeRecordPromise = waitForProofExchangeRecord(faberAgent, { - state: ProofState.ProposalReceived, - }) - - aliceProofExchangeRecord = await aliceAgent.proofs.negotiateRequest({ - proofRecordId: aliceProofExchangeRecord.id, - proofFormats: { - indy: { - name: 'proof-request', - nonce: '58d223e5-fc4d-4448-b74c-5eb11c6b558f', - version: '1.0', - attributes: presentationPreview.attributes.filter((attribute) => attribute.name === 'name'), - predicates: presentationPreview.predicates, - }, - }, - comment: 'V2 propose proof test 2', - }) - - testLogger.test('Faber waits for presentation from Alice') - faberProofExchangeRecord = await faberProofExchangeRecordPromise - - didCommMessageRepository = faberAgent.injectionContainer.resolve(DidCommMessageRepository) - - proposal = await didCommMessageRepository.findAgentMessage(faberAgent.context, { - associatedRecordId: faberProofExchangeRecord.id, - messageClass: V2ProposalPresentationMessage, - }) - - expect(proposal).toMatchObject({ - type: 'https://didcomm.org/present-proof/2.0/propose-presentation', - formats: [ - { - attachmentId: expect.any(String), - format: V2_INDY_PRESENTATION_PROPOSAL, - }, - ], - proposalsAttach: [ - { - id: expect.any(String), - mimeType: 'application/json', - data: { - base64: expect.any(String), - }, - }, - ], - id: expect.any(String), - comment: 'V2 propose proof test 2', - }) - // eslint-disable-next-line @typescript-eslint/no-explicit-any - proposalAttach = proposal?.proposalsAttach[0].getDataAsJson() as any - attributesGroup = Object.keys(proposalAttach.requested_attributes ?? {})[0] - predicatesGroup = Object.keys(proposalAttach.requested_predicates ?? {})[0] - expect(proposalAttach).toMatchObject({ - requested_attributes: { - [attributesGroup]: { - name: 'name', - restrictions: [ - { - cred_def_id: presentationPreview.attributes[1].credentialDefinitionId, - }, - ], - }, - }, - requested_predicates: { - [predicatesGroup]: { - name: 'age', - p_type: '>=', - p_value: 50, - restrictions: [ - { - cred_def_id: presentationPreview.predicates[0].credentialDefinitionId, - }, - ], - }, - }, - }) - expect(faberProofExchangeRecord).toMatchObject({ - id: expect.anything(), - threadId: faberProofExchangeRecord.threadId, - state: ProofState.ProposalReceived, - protocolVersion: 'v2', - }) - - // Accept Proposal - const acceptProposalOptions: AcceptProofProposalOptions = { - proofRecordId: faberProofExchangeRecord.id, - } - - aliceProofExchangeRecordPromise = waitForProofExchangeRecord(aliceAgent, { - threadId: faberProofExchangeRecord.threadId, - state: ProofState.RequestReceived, - }) - - testLogger.test('Faber accepts presentation proposal from Alice') - faberProofExchangeRecord = await faberAgent.proofs.acceptProposal(acceptProposalOptions) - - testLogger.test('Alice waits for proof request from Faber') - aliceProofExchangeRecord = await aliceProofExchangeRecordPromise - - didCommMessageRepository = faberAgent.injectionContainer.resolve(DidCommMessageRepository) - - request = await didCommMessageRepository.findAgentMessage(faberAgent.context, { - associatedRecordId: faberProofExchangeRecord.id, - messageClass: V2RequestPresentationMessage, - }) - - expect(request).toMatchObject({ - type: 'https://didcomm.org/present-proof/2.0/request-presentation', - formats: [ - { - attachmentId: expect.any(String), - format: V2_INDY_PRESENTATION_REQUEST, - }, - ], - requestPresentationsAttach: [ - { - id: expect.any(String), - mimeType: 'application/json', - data: { - base64: expect.any(String), - }, - }, - ], - id: expect.any(String), - thread: { - threadId: faberProofExchangeRecord.threadId, - }, - }) - expect(aliceProofExchangeRecord).toMatchObject({ - id: expect.anything(), - threadId: faberProofExchangeRecord.threadId, - state: ProofState.RequestReceived, - protocolVersion: 'v2', - }) - - const presentationProposalMessage = await aliceAgent.proofs.findProposalMessage(aliceProofExchangeRecord.id) - - expect(presentationProposalMessage).toMatchObject({ - type: 'https://didcomm.org/present-proof/2.0/propose-presentation', - formats: [ - { - attachmentId: expect.any(String), - format: V2_INDY_PRESENTATION_PROPOSAL, - }, - ], - proposalsAttach: [ - { - id: expect.any(String), - mimeType: 'application/json', - data: { - base64: expect.any(String), - }, - }, - ], - id: expect.any(String), - comment: 'V2 propose proof test 2', - }) - - // eslint-disable-next-line @typescript-eslint/no-explicit-any - proposalAttach = proposal?.proposalsAttach[0].getDataAsJson() as any - attributesGroup = Object.keys(proposalAttach.requested_attributes ?? {})[0] - predicatesGroup = Object.keys(proposalAttach.requested_predicates ?? {})[0] - expect(proposalAttach).toMatchObject({ - requested_attributes: { - [attributesGroup]: { - name: 'name', - restrictions: [ - { - cred_def_id: presentationPreview.attributes[1].credentialDefinitionId, - }, - ], - }, - }, - requested_predicates: { - [predicatesGroup]: { - name: 'age', - p_type: '>=', - p_value: 50, - restrictions: [ - { - cred_def_id: presentationPreview.predicates[0].credentialDefinitionId, - }, - ], - }, - }, - }) - - const proofRequestMessage = (await aliceAgent.proofs.findRequestMessage( - aliceProofExchangeRecord.id - )) as V2RequestPresentationMessage - - const proofRequest = JsonTransformer.fromJSON( - proofRequestMessage.requestPresentationsAttach[0].getDataAsJson(), - ProofRequest - ) - const predicateKey = proofRequest.requestedPredicates?.keys().next().value - const predicate = Object.values(predicates)[0] - - expect(proofRequest).toMatchObject({ - name: 'proof-request', - nonce: '58d223e5-fc4d-4448-b74c-5eb11c6b558f', - version: '1.0', - requestedAttributes: new Map( - Object.entries({ - '0': new ProofAttributeInfo({ - name: 'name', - restrictions: [ - new AttributeFilter({ - credentialDefinitionId: credDefId, - }), - ], - }), - }) - ), - requestedPredicates: new Map( - Object.entries({ - [predicateKey]: predicate, - }) - ), - }) - }) -}) diff --git a/packages/core/src/modules/proofs/protocol/v2/__tests__/indy-proof-proposal.test.ts b/packages/core/src/modules/proofs/protocol/v2/__tests__/indy-proof-proposal.test.ts deleted file mode 100644 index 0aed8af01c..0000000000 --- a/packages/core/src/modules/proofs/protocol/v2/__tests__/indy-proof-proposal.test.ts +++ /dev/null @@ -1,96 +0,0 @@ -import type { Agent } from '../../../../../agent/Agent' -import type { ConnectionRecord } from '../../../../connections/repository/ConnectionRecord' -import type { ProofExchangeRecord } from '../../../repository' -import type { PresentationPreview } from '../../v1/models/V1PresentationPreview' - -import { setupProofsTest, waitForProofExchangeRecord } from '../../../../../../tests/helpers' -import testLogger from '../../../../../../tests/logger' -import { DidCommMessageRepository } from '../../../../../storage' -import { V2_INDY_PRESENTATION_PROPOSAL } from '../../../formats/ProofFormatConstants' -import { ProofState } from '../../../models/ProofState' -import { V2ProposalPresentationMessage } from '../messages/V2ProposalPresentationMessage' - -describe('Present Proof', () => { - let faberAgent: Agent - let aliceAgent: Agent - let aliceConnection: ConnectionRecord - let presentationPreview: PresentationPreview - let faberPresentationRecord: ProofExchangeRecord - let didCommMessageRepository: DidCommMessageRepository - - beforeAll(async () => { - testLogger.test('Initializing the agents') - ;({ faberAgent, aliceAgent, aliceConnection, presentationPreview } = await setupProofsTest( - 'Faber agent', - 'Alice agent' - )) - }) - - afterAll(async () => { - testLogger.test('Shutting down both agents') - await faberAgent.shutdown() - await faberAgent.wallet.delete() - await aliceAgent.shutdown() - await aliceAgent.wallet.delete() - }) - - test(`Alice Creates and sends Proof Proposal to Faber`, async () => { - testLogger.test('Alice sends proof proposal to Faber') - - const faberPresentationRecordPromise = waitForProofExchangeRecord(faberAgent, { - state: ProofState.ProposalReceived, - }) - - await aliceAgent.proofs.proposeProof({ - connectionId: aliceConnection.id, - protocolVersion: 'v2', - proofFormats: { - indy: { - name: 'ProofRequest', - nonce: '58d223e5-fc4d-4448-b74c-5eb11c6b558f', - version: '1.0', - attributes: presentationPreview.attributes, - predicates: presentationPreview.predicates, - }, - }, - comment: 'V2 propose proof test', - }) - - testLogger.test('Faber waits for presentation from Alice') - faberPresentationRecord = await faberPresentationRecordPromise - - didCommMessageRepository = faberAgent.injectionContainer.resolve(DidCommMessageRepository) - - const proposal = await didCommMessageRepository.findAgentMessage(faberAgent.context, { - associatedRecordId: faberPresentationRecord.id, - messageClass: V2ProposalPresentationMessage, - }) - - expect(proposal).toMatchObject({ - type: 'https://didcomm.org/present-proof/2.0/propose-presentation', - formats: [ - { - attachmentId: expect.any(String), - format: V2_INDY_PRESENTATION_PROPOSAL, - }, - ], - proposalsAttach: [ - { - id: expect.any(String), - mimeType: 'application/json', - data: { - base64: expect.any(String), - }, - }, - ], - id: expect.any(String), - comment: 'V2 propose proof test', - }) - expect(faberPresentationRecord).toMatchObject({ - id: expect.anything(), - threadId: faberPresentationRecord.threadId, - state: ProofState.ProposalReceived, - protocolVersion: 'v2', - }) - }) -}) diff --git a/packages/core/src/modules/proofs/protocol/v2/__tests__/indy-proof-request.test.ts b/packages/core/src/modules/proofs/protocol/v2/__tests__/indy-proof-request.test.ts deleted file mode 100644 index d30a0c02b9..0000000000 --- a/packages/core/src/modules/proofs/protocol/v2/__tests__/indy-proof-request.test.ts +++ /dev/null @@ -1,153 +0,0 @@ -import type { Agent } from '../../../../../agent/Agent' -import type { ConnectionRecord } from '../../../../connections/repository/ConnectionRecord' -import type { AcceptProofProposalOptions } from '../../../ProofsApiOptions' -import type { ProofExchangeRecord } from '../../../repository/ProofExchangeRecord' -import type { PresentationPreview } from '../../v1/models/V1PresentationPreview' - -import { setupProofsTest, waitForProofExchangeRecord } from '../../../../../../tests/helpers' -import testLogger from '../../../../../../tests/logger' -import { DidCommMessageRepository } from '../../../../../storage' -import { V2_INDY_PRESENTATION_PROPOSAL, V2_INDY_PRESENTATION_REQUEST } from '../../../formats/ProofFormatConstants' -import { ProofState } from '../../../models/ProofState' -import { V2RequestPresentationMessage } from '../messages' -import { V2ProposalPresentationMessage } from '../messages/V2ProposalPresentationMessage' - -describe('Present Proof', () => { - let faberAgent: Agent - let aliceAgent: Agent - let aliceConnection: ConnectionRecord - let presentationPreview: PresentationPreview - let faberProofExchangeRecord: ProofExchangeRecord - let aliceProofExchangeRecord: ProofExchangeRecord - let didCommMessageRepository: DidCommMessageRepository - - beforeAll(async () => { - testLogger.test('Initializing the agents') - ;({ faberAgent, aliceAgent, aliceConnection, presentationPreview } = await setupProofsTest( - 'Faber agent', - 'Alice agent' - )) - }) - - afterAll(async () => { - testLogger.test('Shutting down both agents') - await faberAgent.shutdown() - await faberAgent.wallet.delete() - await aliceAgent.shutdown() - await aliceAgent.wallet.delete() - }) - - test(`Alice Creates and sends Proof Proposal to Faber`, async () => { - testLogger.test('Alice sends proof proposal to Faber') - - const faberPresentationRecordPromise = waitForProofExchangeRecord(faberAgent, { - state: ProofState.ProposalReceived, - }) - - aliceProofExchangeRecord = await aliceAgent.proofs.proposeProof({ - connectionId: aliceConnection.id, - protocolVersion: 'v2', - proofFormats: { - indy: { - name: 'ProofRequest', - nonce: '58d223e5-fc4d-4448-b74c-5eb11c6b558f', - version: '1.0', - attributes: presentationPreview.attributes, - predicates: presentationPreview.predicates, - }, - }, - comment: 'V2 propose proof test', - }) - - testLogger.test('Faber waits for presentation from Alice') - faberProofExchangeRecord = await faberPresentationRecordPromise - - didCommMessageRepository = faberAgent.injectionContainer.resolve(DidCommMessageRepository) - - const proposal = await didCommMessageRepository.findAgentMessage(faberAgent.context, { - associatedRecordId: faberProofExchangeRecord.id, - messageClass: V2ProposalPresentationMessage, - }) - - expect(proposal).toMatchObject({ - type: 'https://didcomm.org/present-proof/2.0/propose-presentation', - formats: [ - { - attachmentId: expect.any(String), - format: V2_INDY_PRESENTATION_PROPOSAL, - }, - ], - proposalsAttach: [ - { - id: expect.any(String), - mimeType: 'application/json', - data: { - base64: expect.any(String), - }, - }, - ], - id: expect.any(String), - comment: 'V2 propose proof test', - }) - expect(faberProofExchangeRecord).toMatchObject({ - id: expect.anything(), - threadId: faberProofExchangeRecord.threadId, - state: ProofState.ProposalReceived, - protocolVersion: 'v2', - }) - }) - - test(`Faber accepts the Proposal send by Alice`, async () => { - // Accept Proposal - const acceptProposalOptions: AcceptProofProposalOptions = { - proofRecordId: faberProofExchangeRecord.id, - } - - const alicePresentationRecordPromise = waitForProofExchangeRecord(aliceAgent, { - threadId: faberProofExchangeRecord.threadId, - state: ProofState.RequestReceived, - }) - - testLogger.test('Faber accepts presentation proposal from Alice') - faberProofExchangeRecord = await faberAgent.proofs.acceptProposal(acceptProposalOptions) - - testLogger.test('Alice waits for proof request from Faber') - aliceProofExchangeRecord = await alicePresentationRecordPromise - - didCommMessageRepository = faberAgent.injectionContainer.resolve(DidCommMessageRepository) - - const request = await didCommMessageRepository.findAgentMessage(faberAgent.context, { - associatedRecordId: faberProofExchangeRecord.id, - messageClass: V2RequestPresentationMessage, - }) - - expect(request).toMatchObject({ - type: 'https://didcomm.org/present-proof/2.0/request-presentation', - formats: [ - { - attachmentId: expect.any(String), - format: V2_INDY_PRESENTATION_REQUEST, - }, - ], - requestPresentationsAttach: [ - { - id: expect.any(String), - mimeType: 'application/json', - data: { - base64: expect.any(String), - }, - }, - ], - id: expect.any(String), - thread: { - threadId: faberProofExchangeRecord.threadId, - }, - }) - expect(aliceProofExchangeRecord).toMatchObject({ - id: expect.anything(), - threadId: faberProofExchangeRecord.threadId, - state: ProofState.RequestReceived, - protocolVersion: 'v2', - }) - }) -}) diff --git a/packages/core/src/modules/proofs/protocol/v2/__tests__/v2-indy-connectionless-proofs.e2e.test.ts b/packages/core/src/modules/proofs/protocol/v2/__tests__/v2-indy-connectionless-proofs.e2e.test.ts new file mode 100644 index 0000000000..78e28c3aff --- /dev/null +++ b/packages/core/src/modules/proofs/protocol/v2/__tests__/v2-indy-connectionless-proofs.e2e.test.ts @@ -0,0 +1,641 @@ +import type { SubjectMessage } from '../../../../../../../../tests/transport/SubjectInboundTransport' +import type { AnonCredsTestsAgent } from '../../../../../../../anoncreds/tests/legacyAnonCredsSetup' + +import { Subject } from 'rxjs' + +import { SubjectInboundTransport } from '../../../../../../../../tests/transport/SubjectInboundTransport' +import { SubjectOutboundTransport } from '../../../../../../../../tests/transport/SubjectOutboundTransport' +import { V1CredentialPreview } from '../../../../../../../anoncreds/src' +import { + getLegacyAnonCredsModules, + issueLegacyAnonCredsCredential, + prepareForAnonCredsIssuance, + setupAnonCredsTests, +} from '../../../../../../../anoncreds/tests/legacyAnonCredsSetup' +import { + waitForProofExchangeRecordSubject, + getAgentOptions, + makeConnection, + testLogger, + setupEventReplaySubjects, + waitForProofExchangeRecord, +} from '../../../../../../tests' +import { Agent } from '../../../../../agent/Agent' +import { V1Attachment, V1AttachmentData } from '../../../../../decorators/attachment/V1Attachment' +import { LinkedAttachment } from '../../../../../utils/LinkedAttachment' +import { uuid } from '../../../../../utils/uuid' +import { HandshakeProtocol } from '../../../../connections' +import { CredentialEventTypes } from '../../../../credentials' +import { MediatorModule, MediatorPickupStrategy, MediationRecipientModule } from '../../../../routing' +import { ProofEventTypes } from '../../../ProofEvents' +import { AutoAcceptProof, ProofState } from '../../../models' + +describe('V2 Connectionless Proofs - Indy', () => { + let agents: Agent[] + + afterEach(async () => { + for (const agent of agents) { + await agent.shutdown() + await agent.wallet.delete() + } + }) + + const connectionlessTest = async (returnRoute?: boolean) => { + const { + issuerAgent: faberAgent, + issuerReplay: faberReplay, + holderAgent: aliceAgent, + holderReplay: aliceReplay, + credentialDefinitionId, + issuerHolderConnectionId: faberConnectionId, + } = await setupAnonCredsTests({ + issuerName: 'Faber connection-less Proofs v2', + holderName: 'Alice connection-less Proofs v2', + autoAcceptProofs: AutoAcceptProof.Never, + attributeNames: ['name', 'age'], + }) + + await issueLegacyAnonCredsCredential({ + issuerAgent: faberAgent, + issuerReplay: faberReplay, + holderAgent: aliceAgent, + holderReplay: aliceReplay, + issuerHolderConnectionId: faberConnectionId, + offer: { + credentialDefinitionId, + attributes: [ + { + name: 'name', + value: 'Alice', + }, + { + name: 'age', + value: '99', + }, + ], + }, + }) + + agents = [aliceAgent, faberAgent] + testLogger.test('Faber sends presentation request to Alice') + + // eslint-disable-next-line prefer-const + let { proofRecord: faberProofExchangeRecord, message } = await faberAgent.proofs.createRequest({ + protocolVersion: 'v2', + proofFormats: { + indy: { + name: 'test-proof-request', + version: '1.0', + requested_attributes: { + name: { + name: 'name', + restrictions: [ + { + cred_def_id: credentialDefinitionId, + }, + ], + }, + }, + requested_predicates: { + age: { + name: 'age', + p_type: '>=', + p_value: 50, + restrictions: [ + { + cred_def_id: credentialDefinitionId, + }, + ], + }, + }, + }, + }, + }) + + const { message: requestMessage } = await faberAgent.oob.createLegacyConnectionlessInvitation({ + recordId: faberProofExchangeRecord.id, + message, + domain: 'https://a-domain.com', + }) + + await aliceAgent.receiveMessage(requestMessage.toJSON()) + + testLogger.test('Alice waits for presentation request from Faber') + let aliceProofExchangeRecord = await waitForProofExchangeRecordSubject(aliceReplay, { + state: ProofState.RequestReceived, + }) + + testLogger.test('Alice accepts presentation request from Faber') + const requestedCredentials = await aliceAgent.proofs.selectCredentialsForRequest({ + proofRecordId: aliceProofExchangeRecord.id, + }) + + aliceProofExchangeRecord = await aliceAgent.proofs.acceptRequest({ + proofRecordId: aliceProofExchangeRecord.id, + useReturnRoute: returnRoute, + proofFormats: { indy: requestedCredentials.proofFormats.indy }, + }) + + testLogger.test('Faber waits for presentation from Alice') + faberProofExchangeRecord = await waitForProofExchangeRecordSubject(faberReplay, { + threadId: aliceProofExchangeRecord.threadId, + state: ProofState.PresentationReceived, + }) + + const sentPresentationMessage = aliceAgent.proofs.findPresentationMessage(aliceProofExchangeRecord.id) + + // assert presentation is valid + expect(faberProofExchangeRecord.isVerified).toBe(true) + + // Faber accepts presentation + await faberAgent.proofs.acceptPresentation({ proofRecordId: faberProofExchangeRecord.id }) + + // Alice waits until it receives presentation ack + aliceProofExchangeRecord = await waitForProofExchangeRecordSubject(aliceReplay, { + threadId: aliceProofExchangeRecord.threadId, + state: ProofState.Done, + }) + return sentPresentationMessage + } + + test('Faber starts with connection-less proof requests to Alice', async () => { + await connectionlessTest() + }) + + test('Faber starts with connection-less proof requests to Alice with auto-accept enabled', async () => { + testLogger.test('Faber sends presentation request to Alice') + + const { + issuerAgent: faberAgent, + issuerReplay: faberReplay, + holderAgent: aliceAgent, + holderReplay: aliceReplay, + credentialDefinitionId, + issuerHolderConnectionId: faberConnectionId, + } = await setupAnonCredsTests({ + issuerName: 'Faber connection-less Proofs v2 - Auto Accept', + holderName: 'Alice connection-less Proofs v2 - Auto Accept', + autoAcceptProofs: AutoAcceptProof.Always, + attributeNames: ['name', 'age'], + }) + + await issueLegacyAnonCredsCredential({ + issuerAgent: faberAgent, + issuerReplay: faberReplay, + holderAgent: aliceAgent, + holderReplay: aliceReplay, + issuerHolderConnectionId: faberConnectionId, + offer: { + credentialDefinitionId, + attributes: [ + { + name: 'name', + value: 'Alice', + }, + { + name: 'age', + value: '99', + }, + ], + }, + }) + + agents = [aliceAgent, faberAgent] + + // eslint-disable-next-line prefer-const + let { message, proofRecord: faberProofExchangeRecord } = await faberAgent.proofs.createRequest({ + protocolVersion: 'v2', + proofFormats: { + indy: { + name: 'test-proof-request', + version: '1.0', + requested_attributes: { + name: { + name: 'name', + restrictions: [ + { + cred_def_id: credentialDefinitionId, + }, + ], + }, + }, + requested_predicates: { + age: { + name: 'age', + p_type: '>=', + p_value: 50, + restrictions: [ + { + cred_def_id: credentialDefinitionId, + }, + ], + }, + }, + }, + }, + autoAcceptProof: AutoAcceptProof.ContentApproved, + }) + + const { message: requestMessage } = await faberAgent.oob.createLegacyConnectionlessInvitation({ + recordId: faberProofExchangeRecord.id, + message, + domain: 'https://a-domain.com', + }) + + await aliceAgent.receiveMessage(requestMessage.toJSON()) + + await waitForProofExchangeRecordSubject(aliceReplay, { + state: ProofState.Done, + threadId: requestMessage.threadId, + }) + + await waitForProofExchangeRecordSubject(faberReplay, { + state: ProofState.Done, + threadId: requestMessage.threadId, + }) + }) + + test('Faber starts with connection-less proof requests to Alice with auto-accept enabled and both agents having a mediator', async () => { + testLogger.test('Faber sends presentation request to Alice') + + const credentialPreview = V1CredentialPreview.fromRecord({ + name: 'John', + age: '99', + }) + + const unique = uuid().substring(0, 4) + + const mediatorOptions = getAgentOptions( + `Connectionless proofs with mediator Mediator-${unique}`, + { + endpoints: ['rxjs:mediator'], + }, + { + ...getLegacyAnonCredsModules({ + autoAcceptProofs: AutoAcceptProof.Always, + }), + mediator: new MediatorModule({ + autoAcceptMediationRequests: true, + }), + } + ) + + const mediatorMessages = new Subject() + const subjectMap = { 'rxjs:mediator': mediatorMessages } + + // Initialize mediator + const mediatorAgent = new Agent(mediatorOptions) + mediatorAgent.registerOutboundTransport(new SubjectOutboundTransport(subjectMap)) + mediatorAgent.registerInboundTransport(new SubjectInboundTransport(mediatorMessages)) + await mediatorAgent.initialize() + + const faberMediationOutOfBandRecord = await mediatorAgent.oob.createInvitation({ + label: 'faber invitation', + handshakeProtocols: [HandshakeProtocol.Connections], + }) + + const aliceMediationOutOfBandRecord = await mediatorAgent.oob.createInvitation({ + label: 'alice invitation', + handshakeProtocols: [HandshakeProtocol.Connections], + }) + + const faberOptions = getAgentOptions( + `Connectionless proofs with mediator Faber-${unique}`, + {}, + { + ...getLegacyAnonCredsModules({ + autoAcceptProofs: AutoAcceptProof.Always, + }), + mediationRecipient: new MediationRecipientModule({ + mediatorInvitationUrl: faberMediationOutOfBandRecord.getOutOfBandInvitation().toUrl({ + domain: 'https://example.com', + }), + mediatorPickupStrategy: MediatorPickupStrategy.PickUpV1, + }), + } + ) + + const aliceOptions = getAgentOptions( + `Connectionless proofs with mediator Alice-${unique}`, + {}, + { + ...getLegacyAnonCredsModules({ + autoAcceptProofs: AutoAcceptProof.Always, + }), + mediationRecipient: new MediationRecipientModule({ + mediatorInvitationUrl: aliceMediationOutOfBandRecord.getOutOfBandInvitation().toUrl({ + domain: 'https://example.com', + }), + mediatorPickupStrategy: MediatorPickupStrategy.PickUpV1, + }), + } + ) + + const faberAgent = new Agent(faberOptions) + faberAgent.registerOutboundTransport(new SubjectOutboundTransport(subjectMap)) + await faberAgent.initialize() + + const aliceAgent = new Agent(aliceOptions) + aliceAgent.registerOutboundTransport(new SubjectOutboundTransport(subjectMap)) + await aliceAgent.initialize() + + const [faberReplay, aliceReplay] = setupEventReplaySubjects( + [faberAgent, aliceAgent], + [CredentialEventTypes.CredentialStateChanged, ProofEventTypes.ProofStateChanged] + ) + agents = [aliceAgent, faberAgent, mediatorAgent] + + await aliceAgent.modules.anoncreds.createLinkSecret({ + linkSecretId: 'default', + setAsDefault: true, + }) + + const { credentialDefinition } = await prepareForAnonCredsIssuance(faberAgent, { + attributeNames: ['name', 'age', 'image_0', 'image_1'], + }) + + const [faberConnection] = await makeConnection(faberAgent, aliceAgent) + + // issue credential with two linked attachments + await issueLegacyAnonCredsCredential({ + issuerAgent: faberAgent as AnonCredsTestsAgent, + issuerReplay: faberReplay, + issuerHolderConnectionId: faberConnection.id, + holderAgent: aliceAgent as AnonCredsTestsAgent, + holderReplay: aliceReplay, + offer: { + credentialDefinitionId: credentialDefinition.credentialDefinitionId, + attributes: credentialPreview.attributes, + linkedAttachments: [ + new LinkedAttachment({ + name: 'image_0', + attachment: new V1Attachment({ + filename: 'picture-of-a-cat.png', + data: new V1AttachmentData({ base64: 'cGljdHVyZSBvZiBhIGNhdA==' }), + }), + }), + new LinkedAttachment({ + name: 'image_1', + attachment: new V1Attachment({ + filename: 'picture-of-a-dog.png', + data: new V1AttachmentData({ base64: 'UGljdHVyZSBvZiBhIGRvZw==' }), + }), + }), + ], + }, + }) + + // eslint-disable-next-line prefer-const + let { message, proofRecord: faberProofExchangeRecord } = await faberAgent.proofs.createRequest({ + protocolVersion: 'v2', + proofFormats: { + indy: { + name: 'test-proof-request', + version: '1.0', + requested_attributes: { + name: { + name: 'name', + restrictions: [ + { + cred_def_id: credentialDefinition.credentialDefinitionId, + }, + ], + }, + }, + requested_predicates: { + age: { + name: 'age', + p_type: '>=', + p_value: 50, + restrictions: [ + { + cred_def_id: credentialDefinition.credentialDefinitionId, + }, + ], + }, + }, + }, + }, + autoAcceptProof: AutoAcceptProof.ContentApproved, + }) + + const { message: requestMessage } = await faberAgent.oob.createLegacyConnectionlessInvitation({ + recordId: faberProofExchangeRecord.id, + message, + domain: 'https://a-domain.com', + }) + + const mediationRecord = await faberAgent.mediationRecipient.findDefaultMediator() + if (!mediationRecord) throw new Error('Faber agent has no default mediator') + + expect(requestMessage).toMatchObject({ + service: { + recipientKeys: [expect.any(String)], + routingKeys: mediationRecord.routingKeys, + serviceEndpoint: mediationRecord.endpoint, + }, + }) + + await aliceAgent.receiveMessage(requestMessage.toJSON()) + + await waitForProofExchangeRecordSubject(aliceReplay, { + state: ProofState.Done, + threadId: requestMessage.threadId, + }) + + await waitForProofExchangeRecordSubject(faberReplay, { + state: ProofState.Done, + threadId: requestMessage.threadId, + }) + }) + + test('Faber starts with connection-less proof requests to Alice with auto-accept enabled and without an outbound transport', async () => { + testLogger.test('Faber sends presentation request to Alice') + + const { + issuerAgent: faberAgent, + issuerReplay: faberReplay, + holderAgent: aliceAgent, + holderReplay: aliceReplay, + credentialDefinitionId, + issuerHolderConnectionId: faberConnectionId, + } = await setupAnonCredsTests({ + issuerName: 'Faber connection-less Proofs v2 - Auto Accept', + holderName: 'Alice connection-less Proofs v2 - Auto Accept', + autoAcceptProofs: AutoAcceptProof.Always, + attributeNames: ['name', 'age'], + }) + + await issueLegacyAnonCredsCredential({ + issuerAgent: faberAgent, + issuerReplay: faberReplay, + holderAgent: aliceAgent, + holderReplay: aliceReplay, + issuerHolderConnectionId: faberConnectionId, + offer: { + credentialDefinitionId, + attributes: [ + { + name: 'name', + value: 'Alice', + }, + { + name: 'age', + value: '99', + }, + ], + }, + }) + + agents = [aliceAgent, faberAgent] + + // eslint-disable-next-line prefer-const + let { message, proofRecord: faberProofExchangeRecord } = await faberAgent.proofs.createRequest({ + protocolVersion: 'v2', + proofFormats: { + indy: { + name: 'test-proof-request', + version: '1.0', + requested_attributes: { + name: { + name: 'name', + restrictions: [ + { + cred_def_id: credentialDefinitionId, + }, + ], + }, + }, + requested_predicates: { + age: { + name: 'age', + p_type: '>=', + p_value: 50, + restrictions: [ + { + cred_def_id: credentialDefinitionId, + }, + ], + }, + }, + }, + }, + autoAcceptProof: AutoAcceptProof.ContentApproved, + }) + + const { message: requestMessage } = await faberAgent.oob.createLegacyConnectionlessInvitation({ + recordId: faberProofExchangeRecord.id, + message, + domain: 'rxjs:faber', + }) + + for (const transport of faberAgent.outboundTransports) { + await faberAgent.unregisterOutboundTransport(transport) + } + + await aliceAgent.receiveMessage(requestMessage.toJSON()) + await waitForProofExchangeRecordSubject(aliceReplay, { + state: ProofState.Done, + threadId: requestMessage.threadId, + }) + + await waitForProofExchangeRecordSubject(faberReplay, { + state: ProofState.Done, + threadId: requestMessage.threadId, + }) + }) + + test('Faber starts with connection-less proof requests to Alice but gets Problem Reported', async () => { + testLogger.test('Faber sends presentation request to Alice') + + const { + issuerAgent: faberAgent, + issuerReplay: faberReplay, + holderAgent: aliceAgent, + holderReplay: aliceReplay, + credentialDefinitionId, + issuerHolderConnectionId: faberConnectionId, + } = await setupAnonCredsTests({ + issuerName: 'Faber connection-less Proofs v2 - Reject Request', + holderName: 'Alice connection-less Proofs v2 - Reject Request', + autoAcceptProofs: AutoAcceptProof.Never, + attributeNames: ['name', 'age'], + }) + + await issueLegacyAnonCredsCredential({ + issuerAgent: faberAgent, + issuerReplay: faberReplay, + holderAgent: aliceAgent, + holderReplay: aliceReplay, + issuerHolderConnectionId: faberConnectionId, + offer: { + credentialDefinitionId, + attributes: [ + { + name: 'name', + value: 'Alice', + }, + { + name: 'age', + value: '99', + }, + ], + }, + }) + + agents = [aliceAgent, faberAgent] + + // eslint-disable-next-line prefer-const + // eslint-disable-next-line prefer-const + let { message, proofRecord: faberProofExchangeRecord } = await faberAgent.proofs.createRequest({ + protocolVersion: 'v2', + proofFormats: { + indy: { + name: 'test-proof-request', + version: '1.0', + requested_attributes: { + name: { + name: 'name', + restrictions: [ + { + cred_def_id: credentialDefinitionId, + }, + ], + }, + }, + requested_predicates: {}, + }, + }, + autoAcceptProof: AutoAcceptProof.ContentApproved, + }) + + const { message: requestMessage } = await faberAgent.oob.createLegacyConnectionlessInvitation({ + recordId: faberProofExchangeRecord.id, + message, + domain: 'rxjs:faber', + }) + + for (const transport of faberAgent.outboundTransports) { + await faberAgent.unregisterOutboundTransport(transport) + } + + const aliceProofExchangeRecordPromise = waitForProofExchangeRecord(aliceAgent, { + state: ProofState.RequestReceived, + }) + + await aliceAgent.receiveMessage(requestMessage.toJSON()) + const aliceProofExchangeRecord = await aliceProofExchangeRecordPromise + + await aliceAgent.proofs.declineRequest({ proofRecordId: aliceProofExchangeRecord.id, sendProblemReport: true }) + + await waitForProofExchangeRecordSubject(aliceReplay, { + state: ProofState.Declined, + threadId: requestMessage.threadId, + }) + + await waitForProofExchangeRecordSubject(faberReplay, { + state: ProofState.Abandoned, + threadId: requestMessage.threadId, + }) + }) +}) diff --git a/packages/core/src/modules/proofs/protocol/v2/__tests__/v2-indy-proof-negotiation.test.ts b/packages/core/src/modules/proofs/protocol/v2/__tests__/v2-indy-proof-negotiation.test.ts new file mode 100644 index 0000000000..6bed06c5ab --- /dev/null +++ b/packages/core/src/modules/proofs/protocol/v2/__tests__/v2-indy-proof-negotiation.test.ts @@ -0,0 +1,404 @@ +import type { AnonCredsProofRequest } from '../../../../../../../anoncreds/src/models/exchange' +import type { AnonCredsTestsAgent } from '../../../../../../../anoncreds/tests/legacyAnonCredsSetup' +import type { EventReplaySubject } from '../../../../../../tests' +import type { V2ProposePresentationMessage, V2RequestPresentationMessage } from '../messages' + +import { AnonCredsProofRequest as AnonCredsProofRequestClass } from '../../../../../../../anoncreds/src/models/AnonCredsProofRequest' +import { + issueLegacyAnonCredsCredential, + setupAnonCredsTests, +} from '../../../../../../../anoncreds/tests/legacyAnonCredsSetup' +import { waitForProofExchangeRecordSubject, testLogger } from '../../../../../../tests' +import { JsonTransformer } from '../../../../../utils/JsonTransformer' +import { ProofState } from '../../../models/ProofState' + +describe('V2 Proofs Negotiation - Indy', () => { + let faberAgent: AnonCredsTestsAgent + let faberReplay: EventReplaySubject + let aliceAgent: AnonCredsTestsAgent + let aliceReplay: EventReplaySubject + let faberConnectionId: string + let aliceConnectionId: string + let credentialDefinitionId: string + + beforeAll(async () => { + testLogger.test('Initializing the agents') + ;({ + issuerAgent: faberAgent, + issuerReplay: faberReplay, + holderAgent: aliceAgent, + holderReplay: aliceReplay, + issuerHolderConnectionId: faberConnectionId, + holderIssuerConnectionId: aliceConnectionId, + + credentialDefinitionId, + } = await setupAnonCredsTests({ + issuerName: 'Faber agent v2', + holderName: 'Alice agent v2', + attributeNames: ['name', 'age'], + })) + + await issueLegacyAnonCredsCredential({ + issuerAgent: faberAgent, + issuerReplay: faberReplay, + holderAgent: aliceAgent, + holderReplay: aliceReplay, + issuerHolderConnectionId: faberConnectionId, + offer: { + credentialDefinitionId, + attributes: [ + { + name: 'name', + value: 'Alice', + }, + { + name: 'age', + value: '99', + }, + ], + }, + }) + }) + + afterAll(async () => { + testLogger.test('Shutting down both agents') + await faberAgent.shutdown() + await faberAgent.wallet.delete() + await aliceAgent.shutdown() + await aliceAgent.wallet.delete() + }) + + test(`Proof negotiation between Alice and Faber`, async () => { + testLogger.test('Alice sends proof proposal to Faber') + + let aliceProofExchangeRecord = await aliceAgent.proofs.proposeProof({ + connectionId: aliceConnectionId, + protocolVersion: 'v2', + proofFormats: { + indy: { + name: 'proof-request', + version: '1.0', + attributes: [], + predicates: [ + { + credentialDefinitionId, + name: 'age', + predicate: '>=', + threshold: 50, + }, + ], + }, + }, + comment: 'V2 propose proof test 1', + }) + + testLogger.test('Faber waits for presentation from Alice') + let faberProofExchangeRecord = await waitForProofExchangeRecordSubject(faberReplay, { + state: ProofState.ProposalReceived, + threadId: aliceProofExchangeRecord.threadId, + }) + + const proposal = await faberAgent.proofs.findProposalMessage(faberProofExchangeRecord.id) + expect(proposal).toMatchObject({ + type: 'https://didcomm.org/present-proof/2.0/propose-presentation', + formats: [ + { + attachmentId: expect.any(String), + format: 'hlindy/proof-req@v2.0', + }, + ], + proposalAttachments: [ + { + id: expect.any(String), + mimeType: 'application/json', + data: { + base64: expect.any(String), + }, + }, + ], + id: expect.any(String), + comment: 'V2 propose proof test 1', + }) + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const proposalAttach = ( + proposal as V2ProposePresentationMessage + )?.proposalAttachments?.[0].getDataAsJson() + + expect(proposalAttach).toMatchObject({ + requested_attributes: {}, + requested_predicates: { + [Object.keys(proposalAttach.requested_predicates)[0]]: { + name: 'age', + p_type: '>=', + p_value: 50, + restrictions: [ + { + cred_def_id: credentialDefinitionId, + }, + ], + }, + }, + }) + expect(faberProofExchangeRecord).toMatchObject({ + id: expect.anything(), + threadId: faberProofExchangeRecord.threadId, + state: ProofState.ProposalReceived, + protocolVersion: 'v2', + }) + + testLogger.test('Faber sends new proof request to Alice') + faberProofExchangeRecord = await faberAgent.proofs.negotiateProposal({ + proofRecordId: faberProofExchangeRecord.id, + proofFormats: { + indy: { + name: 'proof-request', + version: '1.0', + requested_attributes: { + name: { + name: 'name', + restrictions: [ + { + cred_def_id: credentialDefinitionId, + }, + ], + }, + }, + requested_predicates: { + age: { + name: 'age', + p_type: '>=', + p_value: 50, + restrictions: [ + { + cred_def_id: credentialDefinitionId, + }, + ], + }, + }, + }, + }, + }) + + testLogger.test('Alice waits for proof request from Faber') + aliceProofExchangeRecord = await waitForProofExchangeRecordSubject(aliceReplay, { + threadId: faberProofExchangeRecord.threadId, + state: ProofState.RequestReceived, + }) + + const request = await faberAgent.proofs.findRequestMessage(faberProofExchangeRecord.id) + expect(request).toMatchObject({ + type: 'https://didcomm.org/present-proof/2.0/request-presentation', + id: expect.any(String), + requestAttachments: [ + { + id: expect.any(String), + mimeType: 'application/json', + data: { + base64: expect.any(String), + }, + }, + ], + thread: { + threadId: faberProofExchangeRecord.threadId, + }, + }) + expect(aliceProofExchangeRecord).toMatchObject({ + id: expect.anything(), + threadId: faberProofExchangeRecord.threadId, + state: ProofState.RequestReceived, + protocolVersion: 'v2', + }) + + testLogger.test('Alice sends proof proposal to Faber') + + aliceProofExchangeRecord = await aliceAgent.proofs.negotiateRequest({ + proofRecordId: aliceProofExchangeRecord.id, + proofFormats: { + indy: { + name: 'proof-request', + version: '1.0', + attributes: [], + predicates: [ + { + credentialDefinitionId, + name: 'age', + predicate: '>=', + threshold: 50, + }, + ], + }, + }, + comment: 'V2 propose proof test 2', + }) + + testLogger.test('Faber waits for presentation from Alice') + faberProofExchangeRecord = await waitForProofExchangeRecordSubject(faberReplay, { + state: ProofState.ProposalReceived, + threadId: aliceProofExchangeRecord.threadId, + // Negotiation so this will be the second proposal + count: 2, + }) + + const proposal2 = await faberAgent.proofs.findProposalMessage(faberProofExchangeRecord.id) + expect(proposal2).toMatchObject({ + type: 'https://didcomm.org/present-proof/2.0/propose-presentation', + formats: [ + { + attachmentId: expect.any(String), + format: 'hlindy/proof-req@v2.0', + }, + ], + proposalAttachments: [ + { + id: expect.any(String), + mimeType: 'application/json', + data: { + base64: expect.any(String), + }, + }, + ], + id: expect.any(String), + comment: 'V2 propose proof test 2', + }) + + const proposalAttach2 = ( + proposal as V2ProposePresentationMessage + )?.proposalAttachments[0].getDataAsJson() + expect(proposalAttach2).toMatchObject({ + requested_attributes: {}, + requested_predicates: { + [Object.keys(proposalAttach2.requested_predicates)[0]]: { + name: 'age', + p_type: '>=', + p_value: 50, + restrictions: [ + { + cred_def_id: credentialDefinitionId, + }, + ], + }, + }, + }) + expect(faberProofExchangeRecord).toMatchObject({ + id: expect.anything(), + threadId: faberProofExchangeRecord.threadId, + state: ProofState.ProposalReceived, + protocolVersion: 'v2', + }) + + // Accept Proposal + testLogger.test('Faber accepts presentation proposal from Alice') + faberProofExchangeRecord = await faberAgent.proofs.acceptProposal({ + proofRecordId: faberProofExchangeRecord.id, + }) + + testLogger.test('Alice waits for proof request from Faber') + aliceProofExchangeRecord = await waitForProofExchangeRecordSubject(aliceReplay, { + threadId: faberProofExchangeRecord.threadId, + state: ProofState.RequestReceived, + // Negotiation so this will be the second request + count: 2, + }) + + const request2 = await faberAgent.proofs.findRequestMessage(faberProofExchangeRecord.id) + expect(request2).toMatchObject({ + type: 'https://didcomm.org/present-proof/2.0/request-presentation', + formats: [ + { + attachmentId: expect.any(String), + format: 'hlindy/proof-req@v2.0', + }, + ], + requestAttachments: [ + { + id: expect.any(String), + mimeType: 'application/json', + data: { + base64: expect.any(String), + }, + }, + ], + id: expect.any(String), + thread: { + threadId: faberProofExchangeRecord.threadId, + }, + }) + expect(aliceProofExchangeRecord).toMatchObject({ + id: expect.anything(), + threadId: faberProofExchangeRecord.threadId, + state: ProofState.RequestReceived, + protocolVersion: 'v2', + }) + + const proposalMessage = await aliceAgent.proofs.findProposalMessage(aliceProofExchangeRecord.id) + expect(proposalMessage).toMatchObject({ + type: 'https://didcomm.org/present-proof/2.0/propose-presentation', + formats: [ + { + attachmentId: expect.any(String), + format: 'hlindy/proof-req@v2.0', + }, + ], + proposalAttachments: [ + { + id: expect.any(String), + mimeType: 'application/json', + data: { + base64: expect.any(String), + }, + }, + ], + id: expect.any(String), + comment: 'V2 propose proof test 2', + }) + + const proposalAttach3 = ( + proposal as V2ProposePresentationMessage + )?.proposalAttachments[0].getDataAsJson() + expect(proposalAttach3).toMatchObject({ + requested_attributes: {}, + requested_predicates: { + [Object.keys(proposalAttach3.requested_predicates ?? {})[0]]: { + name: 'age', + p_type: '>=', + p_value: 50, + restrictions: [ + { + cred_def_id: credentialDefinitionId, + }, + ], + }, + }, + }) + + const proofRequestMessage = (await aliceAgent.proofs.findRequestMessage( + aliceProofExchangeRecord.id + )) as V2RequestPresentationMessage + + const proofRequest = JsonTransformer.fromJSON( + proofRequestMessage.requestAttachments[0].getDataAsJson(), + AnonCredsProofRequestClass + ) + const predicateKey = proofRequest.requestedPredicates?.keys().next().value + + expect(JsonTransformer.toJSON(proofRequest)).toMatchObject({ + name: 'proof-request', + nonce: expect.any(String), + version: '1.0', + requested_attributes: {}, + requested_predicates: { + [predicateKey]: { + name: 'age', + p_type: '>=', + p_value: 50, + restrictions: [ + { + cred_def_id: credentialDefinitionId, + }, + ], + }, + }, + }) + }) +}) diff --git a/packages/core/src/modules/proofs/protocol/v2/__tests__/indy-proof-presentation.test.ts b/packages/core/src/modules/proofs/protocol/v2/__tests__/v2-indy-proof-presentation.e2e.test.ts similarity index 58% rename from packages/core/src/modules/proofs/protocol/v2/__tests__/indy-proof-presentation.test.ts rename to packages/core/src/modules/proofs/protocol/v2/__tests__/v2-indy-proof-presentation.e2e.test.ts index 08d5978d80..ee4d3cd44b 100644 --- a/packages/core/src/modules/proofs/protocol/v2/__tests__/indy-proof-presentation.test.ts +++ b/packages/core/src/modules/proofs/protocol/v2/__tests__/v2-indy-proof-presentation.e2e.test.ts @@ -1,36 +1,60 @@ -import type { Agent } from '../../../../../agent/Agent' -import type { ConnectionRecord } from '../../../../connections/repository/ConnectionRecord' -import type { AcceptProofProposalOptions } from '../../../ProofsApiOptions' -import type { PresentationPreview } from '../../v1/models/V1PresentationPreview' - -import { setupProofsTest, waitForProofExchangeRecord } from '../../../../../../tests/helpers' -import testLogger from '../../../../../../tests/logger' -import { DidCommMessageRepository } from '../../../../../storage' +import type { AnonCredsTestsAgent } from '../../../../../../../anoncreds/tests/legacyAnonCredsSetup' +import type { EventReplaySubject } from '../../../../../../tests' + import { - V2_INDY_PRESENTATION_PROPOSAL, - V2_INDY_PRESENTATION_REQUEST, - V2_INDY_PRESENTATION, -} from '../../../formats/ProofFormatConstants' + setupAnonCredsTests, + issueLegacyAnonCredsCredential, +} from '../../../../../../../anoncreds/tests/legacyAnonCredsSetup' +import { waitForProofExchangeRecordSubject, testLogger } from '../../../../../../tests' import { ProofState } from '../../../models/ProofState' import { ProofExchangeRecord } from '../../../repository/ProofExchangeRecord' -import { V2PresentationMessage, V2RequestPresentationMessage } from '../messages' -import { V2ProposalPresentationMessage } from '../messages/V2ProposalPresentationMessage' - -describe('Present Proof', () => { - let faberAgent: Agent - let aliceAgent: Agent - let aliceConnection: ConnectionRecord - let presentationPreview: PresentationPreview - let faberProofExchangeRecord: ProofExchangeRecord - let aliceProofExchangeRecord: ProofExchangeRecord - let didCommMessageRepository: DidCommMessageRepository + +describe('V2 Proofs - Indy', () => { + let faberAgent: AnonCredsTestsAgent + let faberReplay: EventReplaySubject + let aliceAgent: AnonCredsTestsAgent + let aliceReplay: EventReplaySubject + let faberConnectionId: string + let aliceConnectionId: string + let credentialDefinitionId: string beforeAll(async () => { testLogger.test('Initializing the agents') - ;({ faberAgent, aliceAgent, aliceConnection, presentationPreview } = await setupProofsTest( - 'Faber agent', - 'Alice agent' - )) + ;({ + issuerAgent: faberAgent, + issuerReplay: faberReplay, + holderAgent: aliceAgent, + holderReplay: aliceReplay, + issuerHolderConnectionId: faberConnectionId, + holderIssuerConnectionId: aliceConnectionId, + + credentialDefinitionId, + } = await setupAnonCredsTests({ + issuerName: 'Faber agent v2', + holderName: 'Alice agent v2', + attributeNames: ['name', 'age'], + })) + + await issueLegacyAnonCredsCredential({ + issuerAgent: faberAgent, + issuerReplay: faberReplay, + holderAgent: aliceAgent, + holderReplay: aliceReplay, + issuerHolderConnectionId: faberConnectionId, + offer: { + credentialDefinitionId, + attributes: [ + { + name: 'name', + value: 'Alice', + }, + { + name: 'age', + value: '99', + }, + ], + }, + }) }) afterAll(async () => { @@ -44,44 +68,48 @@ describe('Present Proof', () => { test(`Alice Creates and sends Proof Proposal to Faber`, async () => { testLogger.test('Alice sends proof proposal to Faber') - const faberPresentationRecordPromise = waitForProofExchangeRecord(faberAgent, { - state: ProofState.ProposalReceived, - }) - - aliceProofExchangeRecord = await aliceAgent.proofs.proposeProof({ - connectionId: aliceConnection.id, + let aliceProofExchangeRecord = await aliceAgent.proofs.proposeProof({ + connectionId: aliceConnectionId, protocolVersion: 'v2', proofFormats: { indy: { name: 'ProofRequest', - nonce: '947121108704767252195126', version: '1.0', - attributes: presentationPreview.attributes, - predicates: presentationPreview.predicates, + attributes: [ + { + name: 'name', + value: 'Alice', + credentialDefinitionId, + }, + ], + predicates: [ + { + credentialDefinitionId, + name: 'age', + predicate: '>=', + threshold: 18, + }, + ], }, }, comment: 'V2 propose proof test', }) testLogger.test('Faber waits for presentation from Alice') - faberProofExchangeRecord = await faberPresentationRecordPromise - - didCommMessageRepository = faberAgent.injectionContainer.resolve(DidCommMessageRepository) - - const proposal = await didCommMessageRepository.findAgentMessage(faberAgent.context, { - associatedRecordId: faberProofExchangeRecord.id, - messageClass: V2ProposalPresentationMessage, + let faberProofExchangeRecord = await waitForProofExchangeRecordSubject(faberReplay, { + state: ProofState.ProposalReceived, }) + const proposal = await faberAgent.proofs.findProposalMessage(faberProofExchangeRecord.id) expect(proposal).toMatchObject({ type: 'https://didcomm.org/present-proof/2.0/propose-presentation', formats: [ { attachmentId: expect.any(String), - format: V2_INDY_PRESENTATION_PROPOSAL, + format: 'hlindy/proof-req@v2.0', }, ], - proposalsAttach: [ + proposalAttachments: [ { id: expect.any(String), mimeType: 'application/json', @@ -99,41 +127,29 @@ describe('Present Proof', () => { state: ProofState.ProposalReceived, protocolVersion: 'v2', }) - }) - test(`Faber accepts the Proposal send by Alice`, async () => { // Accept Proposal - const acceptProposalOptions: AcceptProofProposalOptions = { + testLogger.test('Faber accepts presentation proposal from Alice') + faberProofExchangeRecord = await faberAgent.proofs.acceptProposal({ proofRecordId: faberProofExchangeRecord.id, - } - - const alicePresentationRecordPromise = waitForProofExchangeRecord(aliceAgent, { - threadId: faberProofExchangeRecord.threadId, - state: ProofState.RequestReceived, }) - testLogger.test('Faber accepts presentation proposal from Alice') - faberProofExchangeRecord = await faberAgent.proofs.acceptProposal(acceptProposalOptions) - testLogger.test('Alice waits for proof request from Faber') - aliceProofExchangeRecord = await alicePresentationRecordPromise - - didCommMessageRepository = faberAgent.injectionContainer.resolve(DidCommMessageRepository) - - const request = await didCommMessageRepository.findAgentMessage(faberAgent.context, { - associatedRecordId: faberProofExchangeRecord.id, - messageClass: V2RequestPresentationMessage, + aliceProofExchangeRecord = await waitForProofExchangeRecordSubject(aliceReplay, { + threadId: faberProofExchangeRecord.threadId, + state: ProofState.RequestReceived, }) + const request = await faberAgent.proofs.findRequestMessage(faberProofExchangeRecord.id) expect(request).toMatchObject({ type: 'https://didcomm.org/present-proof/2.0/request-presentation', formats: [ { attachmentId: expect.any(String), - format: V2_INDY_PRESENTATION_REQUEST, + format: 'hlindy/proof-req@v2.0', }, ], - requestPresentationsAttach: [ + requestAttachments: [ { id: expect.any(String), mimeType: 'application/json', @@ -153,22 +169,12 @@ describe('Present Proof', () => { state: ProofState.RequestReceived, protocolVersion: 'v2', }) - }) - test(`Alice accepts presentation request from Faber`, async () => { // Alice retrieves the requested credentials and accepts the presentation request testLogger.test('Alice accepts presentation request from Faber') - const requestedCredentials = await aliceAgent.proofs.autoSelectCredentialsForProofRequest({ + const requestedCredentials = await aliceAgent.proofs.selectCredentialsForRequest({ proofRecordId: aliceProofExchangeRecord.id, - config: { - filterByPresentationPreview: true, - }, - }) - - const faberPresentationRecordPromise = waitForProofExchangeRecord(faberAgent, { - threadId: aliceProofExchangeRecord.threadId, - state: ProofState.PresentationReceived, }) await aliceAgent.proofs.acceptRequest({ @@ -176,24 +182,24 @@ describe('Present Proof', () => { proofFormats: { indy: requestedCredentials.proofFormats.indy }, }) + faberProofExchangeRecord = await waitForProofExchangeRecordSubject(faberReplay, { + threadId: aliceProofExchangeRecord.threadId, + state: ProofState.PresentationReceived, + }) + // Faber waits for the presentation from Alice testLogger.test('Faber waits for presentation from Alice') - faberProofExchangeRecord = await faberPresentationRecordPromise - - const presentation = await didCommMessageRepository.findAgentMessage(faberAgent.context, { - associatedRecordId: faberProofExchangeRecord.id, - messageClass: V2PresentationMessage, - }) + const presentation = await faberAgent.proofs.findPresentationMessage(faberProofExchangeRecord.id) expect(presentation).toMatchObject({ type: 'https://didcomm.org/present-proof/2.0/presentation', formats: [ { attachmentId: expect.any(String), - format: V2_INDY_PRESENTATION, + format: 'hlindy/proof@v2.0', }, ], - presentationsAttach: [ + presentationAttachments: [ { id: expect.any(String), mimeType: 'application/json', @@ -213,17 +219,15 @@ describe('Present Proof', () => { state: ProofState.PresentationReceived, protocolVersion: 'v2', }) - }) - test(`Faber accepts the presentation provided by Alice`, async () => { - const aliceProofExchangeRecordPromise = waitForProofExchangeRecord(aliceAgent, { + const aliceProofExchangeRecordPromise = waitForProofExchangeRecordSubject(aliceReplay, { threadId: aliceProofExchangeRecord.threadId, state: ProofState.Done, }) // Faber accepts the presentation provided by Alice testLogger.test('Faber accepts the presentation provided by Alice') - await faberAgent.proofs.acceptPresentation(faberProofExchangeRecord.id) + await faberAgent.proofs.acceptPresentation({ proofRecordId: faberProofExchangeRecord.id }) // Alice waits until she received a presentation acknowledgement testLogger.test('Alice waits until she receives a presentation acknowledgement') diff --git a/packages/core/src/modules/proofs/protocol/v2/__tests__/v2-indy-proof-request.e2e.test.ts b/packages/core/src/modules/proofs/protocol/v2/__tests__/v2-indy-proof-request.e2e.test.ts new file mode 100644 index 0000000000..afe41e9417 --- /dev/null +++ b/packages/core/src/modules/proofs/protocol/v2/__tests__/v2-indy-proof-request.e2e.test.ts @@ -0,0 +1,173 @@ +import type { AnonCredsTestsAgent } from '../../../../../../../anoncreds/tests/legacyAnonCredsSetup' +import type { EventReplaySubject } from '../../../../../../tests' + +import { + issueLegacyAnonCredsCredential, + setupAnonCredsTests, +} from '../../../../../../../anoncreds/tests/legacyAnonCredsSetup' +import { waitForProofExchangeRecordSubject } from '../../../../../../tests' +import testLogger from '../../../../../../tests/logger' +import { ProofState } from '../../../models/ProofState' + +describe('V2 Proofs - Indy', () => { + let faberAgent: AnonCredsTestsAgent + let faberReplay: EventReplaySubject + let aliceAgent: AnonCredsTestsAgent + let aliceReplay: EventReplaySubject + let faberConnectionId: string + let aliceConnectionId: string + let credentialDefinitionId: string + + beforeAll(async () => { + testLogger.test('Initializing the agents') + ;({ + issuerAgent: faberAgent, + issuerReplay: faberReplay, + holderAgent: aliceAgent, + holderReplay: aliceReplay, + issuerHolderConnectionId: faberConnectionId, + holderIssuerConnectionId: aliceConnectionId, + + credentialDefinitionId, + } = await setupAnonCredsTests({ + issuerName: 'Faber agent v2', + holderName: 'Alice agent v2', + attributeNames: ['name', 'age'], + })) + + await issueLegacyAnonCredsCredential({ + issuerAgent: faberAgent, + issuerReplay: faberReplay, + holderAgent: aliceAgent, + holderReplay: aliceReplay, + issuerHolderConnectionId: faberConnectionId, + offer: { + credentialDefinitionId, + attributes: [ + { + name: 'name', + value: 'Alice', + }, + { + name: 'age', + value: '99', + }, + ], + }, + }) + }) + + afterAll(async () => { + testLogger.test('Shutting down both agents') + await faberAgent.shutdown() + await faberAgent.wallet.delete() + await aliceAgent.shutdown() + await aliceAgent.wallet.delete() + }) + + test(`Alice Creates and sends Proof Proposal to Faber`, async () => { + testLogger.test('Alice sends proof proposal to Faber') + + let aliceProofExchangeRecord = await aliceAgent.proofs.proposeProof({ + connectionId: aliceConnectionId, + protocolVersion: 'v2', + proofFormats: { + indy: { + name: 'ProofRequest', + version: '1.0', + attributes: [ + { + name: 'name', + value: 'Alice', + credentialDefinitionId, + }, + ], + predicates: [ + { + credentialDefinitionId, + name: 'age', + predicate: '>=', + threshold: 18, + }, + ], + }, + }, + comment: 'V2 propose proof test', + }) + + testLogger.test('Faber waits for presentation from Alice') + let faberProofExchangeRecord = await waitForProofExchangeRecordSubject(faberReplay, { + state: ProofState.ProposalReceived, + }) + + const proposal = await faberAgent.proofs.findProposalMessage(faberProofExchangeRecord.id) + expect(proposal).toMatchObject({ + type: 'https://didcomm.org/present-proof/2.0/propose-presentation', + formats: [ + { + attachmentId: expect.any(String), + format: 'hlindy/proof-req@v2.0', + }, + ], + proposalAttachments: [ + { + id: expect.any(String), + mimeType: 'application/json', + data: { + base64: expect.any(String), + }, + }, + ], + id: expect.any(String), + comment: 'V2 propose proof test', + }) + expect(faberProofExchangeRecord).toMatchObject({ + id: expect.anything(), + threadId: faberProofExchangeRecord.threadId, + state: ProofState.ProposalReceived, + protocolVersion: 'v2', + }) + + // Accept Proposal + testLogger.test('Faber accepts presentation proposal from Alice') + faberProofExchangeRecord = await faberAgent.proofs.acceptProposal({ + proofRecordId: faberProofExchangeRecord.id, + }) + + testLogger.test('Alice waits for proof request from Faber') + aliceProofExchangeRecord = await waitForProofExchangeRecordSubject(aliceReplay, { + threadId: faberProofExchangeRecord.threadId, + state: ProofState.RequestReceived, + }) + + const request = await faberAgent.proofs.findRequestMessage(faberProofExchangeRecord.id) + expect(request).toMatchObject({ + type: 'https://didcomm.org/present-proof/2.0/request-presentation', + formats: [ + { + attachmentId: expect.any(String), + format: 'hlindy/proof-req@v2.0', + }, + ], + requestAttachments: [ + { + id: expect.any(String), + mimeType: 'application/json', + data: { + base64: expect.any(String), + }, + }, + ], + id: expect.any(String), + thread: { + threadId: faberProofExchangeRecord.threadId, + }, + }) + expect(aliceProofExchangeRecord).toMatchObject({ + id: expect.anything(), + threadId: faberProofExchangeRecord.threadId, + state: ProofState.RequestReceived, + protocolVersion: 'v2', + }) + }) +}) diff --git a/packages/core/src/modules/proofs/protocol/v2/__tests__/v2-indy-proofs-auto-accept.e2e.test.ts b/packages/core/src/modules/proofs/protocol/v2/__tests__/v2-indy-proofs-auto-accept.e2e.test.ts new file mode 100644 index 0000000000..06ddf5cc34 --- /dev/null +++ b/packages/core/src/modules/proofs/protocol/v2/__tests__/v2-indy-proofs-auto-accept.e2e.test.ts @@ -0,0 +1,291 @@ +import type { AnonCredsTestsAgent } from '../../../../../../../anoncreds/tests/legacyAnonCredsSetup' +import type { EventReplaySubject } from '../../../../../../tests' + +import { + issueLegacyAnonCredsCredential, + setupAnonCredsTests, +} from '../../../../../../../anoncreds/tests/legacyAnonCredsSetup' +import { waitForProofExchangeRecord, testLogger } from '../../../../../../tests' +import { AutoAcceptProof, ProofState } from '../../../models' + +describe('Auto accept present proof', () => { + let faberAgent: AnonCredsTestsAgent + let faberReplay: EventReplaySubject + let aliceAgent: AnonCredsTestsAgent + let aliceReplay: EventReplaySubject + let credentialDefinitionId: string + let faberConnectionId: string + let aliceConnectionId: string + + describe("Auto accept on 'always'", () => { + beforeAll(async () => { + ;({ + issuerAgent: faberAgent, + issuerReplay: faberReplay, + holderAgent: aliceAgent, + holderReplay: aliceReplay, + credentialDefinitionId, + issuerHolderConnectionId: faberConnectionId, + holderIssuerConnectionId: aliceConnectionId, + } = await setupAnonCredsTests({ + issuerName: 'Faber Auto Accept Always Proofs', + holderName: 'Alice Auto Accept Always Proofs', + attributeNames: ['name', 'age'], + autoAcceptProofs: AutoAcceptProof.Always, + })) + + await issueLegacyAnonCredsCredential({ + issuerAgent: faberAgent, + issuerReplay: faberReplay, + holderAgent: aliceAgent, + holderReplay: aliceReplay, + issuerHolderConnectionId: faberConnectionId, + offer: { + credentialDefinitionId, + attributes: [ + { + name: 'name', + value: 'Alice', + }, + { + name: 'age', + value: '99', + }, + ], + }, + }) + }) + + afterAll(async () => { + await faberAgent.shutdown() + await faberAgent.wallet.delete() + await aliceAgent.shutdown() + await aliceAgent.wallet.delete() + }) + + test("Alice starts with proof proposal to Faber, both with autoAcceptProof on 'always'", async () => { + testLogger.test('Alice sends presentation proposal to Faber') + + await aliceAgent.proofs.proposeProof({ + connectionId: aliceConnectionId, + protocolVersion: 'v2', + proofFormats: { + indy: { + name: 'abc', + version: '1.0', + attributes: [ + { + credentialDefinitionId, + name: 'name', + value: 'Alice', + }, + ], + predicates: [ + { + credentialDefinitionId, + name: 'age', + predicate: '>=', + threshold: 50, + }, + ], + }, + }, + }) + + testLogger.test('Faber waits for presentation from Alice') + testLogger.test('Alice waits till it receives presentation ack') + await Promise.all([ + waitForProofExchangeRecord(faberAgent, { state: ProofState.Done }), + waitForProofExchangeRecord(aliceAgent, { state: ProofState.Done }), + ]) + }) + + test("Faber starts with proof requests to Alice, both with autoAcceptProof on 'always'", async () => { + testLogger.test('Faber sends presentation request to Alice') + + await faberAgent.proofs.requestProof({ + protocolVersion: 'v2', + connectionId: faberConnectionId, + proofFormats: { + indy: { + name: 'proof-request', + version: '1.0', + requested_attributes: { + name: { + name: 'name', + restrictions: [ + { + cred_def_id: credentialDefinitionId, + }, + ], + }, + }, + requested_predicates: { + age: { + name: 'age', + p_type: '>=', + p_value: 50, + restrictions: [ + { + cred_def_id: credentialDefinitionId, + }, + ], + }, + }, + }, + }, + }) + + testLogger.test('Alice waits for presentation from Faber') + testLogger.test('Faber waits till it receives presentation ack') + await Promise.all([ + waitForProofExchangeRecord(faberAgent, { state: ProofState.Done }), + waitForProofExchangeRecord(aliceAgent, { state: ProofState.Done }), + ]) + }) + }) + + describe("Auto accept on 'contentApproved'", () => { + beforeAll(async () => { + testLogger.test('Initializing the agents') + ;({ + issuerAgent: faberAgent, + issuerReplay: faberReplay, + holderAgent: aliceAgent, + holderReplay: aliceReplay, + credentialDefinitionId, + issuerHolderConnectionId: faberConnectionId, + holderIssuerConnectionId: aliceConnectionId, + } = await setupAnonCredsTests({ + issuerName: 'Faber Auto Accept ContentApproved Proofs', + holderName: 'Alice Auto Accept ContentApproved Proofs', + attributeNames: ['name', 'age'], + autoAcceptProofs: AutoAcceptProof.ContentApproved, + })) + + await issueLegacyAnonCredsCredential({ + issuerAgent: faberAgent, + issuerReplay: faberReplay, + holderAgent: aliceAgent, + holderReplay: aliceReplay, + issuerHolderConnectionId: faberConnectionId, + offer: { + credentialDefinitionId, + attributes: [ + { + name: 'name', + value: 'Alice', + }, + { + name: 'age', + value: '99', + }, + ], + }, + }) + }) + + afterAll(async () => { + testLogger.test('Shutting down both agents') + await faberAgent.shutdown() + await faberAgent.wallet.delete() + await aliceAgent.shutdown() + await aliceAgent.wallet.delete() + }) + + test("Alice starts with proof proposal to Faber, both with autoAcceptProof on 'contentApproved'", async () => { + testLogger.test('Alice sends presentation proposal to Faber') + + const faberProofExchangeRecordPromise = waitForProofExchangeRecord(faberAgent, { + state: ProofState.ProposalReceived, + }) + + await aliceAgent.proofs.proposeProof({ + connectionId: aliceConnectionId, + protocolVersion: 'v2', + proofFormats: { + indy: { + name: 'abc', + version: '1.0', + attributes: [ + { + credentialDefinitionId, + name: 'name', + value: 'Alice', + }, + ], + predicates: [ + { + credentialDefinitionId, + name: 'age', + predicate: '>=', + threshold: 50, + }, + ], + }, + }, + }) + + const faberProofExchangeRecord = await faberProofExchangeRecordPromise + await faberAgent.proofs.acceptProposal({ + proofRecordId: faberProofExchangeRecord.id, + }) + + await Promise.all([ + waitForProofExchangeRecord(aliceAgent, { state: ProofState.Done }), + waitForProofExchangeRecord(faberAgent, { state: ProofState.Done }), + ]) + }) + + test("Faber starts with proof requests to Alice, both with autoAcceptProof on 'contentApproved'", async () => { + testLogger.test('Faber sends presentation request to Alice') + + const aliceProofExchangeRecordPromise = waitForProofExchangeRecord(aliceAgent, { + state: ProofState.RequestReceived, + }) + + await faberAgent.proofs.requestProof({ + protocolVersion: 'v2', + connectionId: faberConnectionId, + proofFormats: { + indy: { + name: 'proof-request', + version: '1.0', + requested_attributes: { + name: { + name: 'name', + restrictions: [ + { + cred_def_id: credentialDefinitionId, + }, + ], + }, + }, + requested_predicates: { + age: { + name: 'age', + p_type: '>=', + p_value: 50, + restrictions: [ + { + cred_def_id: credentialDefinitionId, + }, + ], + }, + }, + }, + }, + }) + + const aliceProofExchangeRecord = await aliceProofExchangeRecordPromise + await aliceAgent.proofs.acceptRequest({ + proofRecordId: aliceProofExchangeRecord.id, + }) + + await Promise.all([ + waitForProofExchangeRecord(faberAgent, { state: ProofState.Done }), + waitForProofExchangeRecord(aliceAgent, { state: ProofState.Done }), + ]) + }) + }) +}) diff --git a/packages/core/tests/v2-indy-proofs.test.ts b/packages/core/src/modules/proofs/protocol/v2/__tests__/v2-indy-proofs.e2e.test.ts similarity index 58% rename from packages/core/tests/v2-indy-proofs.test.ts rename to packages/core/src/modules/proofs/protocol/v2/__tests__/v2-indy-proofs.e2e.test.ts index 9be131c559..058e64a54a 100644 --- a/packages/core/tests/v2-indy-proofs.test.ts +++ b/packages/core/src/modules/proofs/protocol/v2/__tests__/v2-indy-proofs.e2e.test.ts @@ -1,47 +1,82 @@ -import type { Agent, ConnectionRecord } from '../src' -import type { AcceptProofProposalOptions } from '../src/modules/proofs/ProofsApiOptions' -import type { PresentationPreview } from '../src/modules/proofs/protocol/v1/models/V1PresentationPreview' -import type { CredDefId } from 'indy-sdk' +import type { AnonCredsTestsAgent } from '../../../../../../../anoncreds/tests/legacyAnonCredsSetup' +import type { EventReplaySubject } from '../../../../../../tests' import { - ProofExchangeRecord, - AttributeFilter, - PredicateType, - ProofAttributeInfo, - ProofPredicateInfo, - ProofState, -} from '../src' -import { getGroupKeysFromIndyProofFormatData } from '../src/modules/proofs/__tests__/groupKeys' -import { - V2_INDY_PRESENTATION_PROPOSAL, - V2_INDY_PRESENTATION_REQUEST, - V2_INDY_PRESENTATION, -} from '../src/modules/proofs/formats/ProofFormatConstants' -import { - V2PresentationMessage, - V2ProposalPresentationMessage, - V2RequestPresentationMessage, -} from '../src/modules/proofs/protocol/v2/messages' -import { DidCommMessageRepository } from '../src/storage/didcomm' - -import { setupProofsTest, waitForProofExchangeRecord } from './helpers' -import testLogger from './logger' + issueLegacyAnonCredsCredential, + setupAnonCredsTests, +} from '../../../../../../../anoncreds/tests/legacyAnonCredsSetup' +import { waitForProofExchangeRecord } from '../../../../../../tests' +import testLogger from '../../../../../../tests/logger' +import { V1Attachment, V1AttachmentData } from '../../../../../decorators/attachment/V1Attachment' +import { LinkedAttachment } from '../../../../../utils/LinkedAttachment' +import { ProofState } from '../../../models' +import { ProofExchangeRecord } from '../../../repository' +import { V2ProposePresentationMessage, V2RequestPresentationMessage, V2PresentationMessage } from '../messages' describe('Present Proof', () => { - let faberAgent: Agent - let aliceAgent: Agent - let credDefId: CredDefId - let aliceConnection: ConnectionRecord - let faberConnection: ConnectionRecord + let faberAgent: AnonCredsTestsAgent + let faberReplay: EventReplaySubject + let aliceAgent: AnonCredsTestsAgent + let aliceReplay: EventReplaySubject + let credentialDefinitionId: string + let aliceConnectionId: string + let faberConnectionId: string let faberProofExchangeRecord: ProofExchangeRecord let aliceProofExchangeRecord: ProofExchangeRecord - let presentationPreview: PresentationPreview - let didCommMessageRepository: DidCommMessageRepository beforeAll(async () => { testLogger.test('Initializing the agents') - ;({ faberAgent, aliceAgent, credDefId, faberConnection, aliceConnection, presentationPreview } = - await setupProofsTest('Faber agent', 'Alice agent')) + ;({ + issuerAgent: faberAgent, + issuerReplay: faberReplay, + + holderAgent: aliceAgent, + holderReplay: aliceReplay, + credentialDefinitionId, + issuerHolderConnectionId: faberConnectionId, + holderIssuerConnectionId: aliceConnectionId, + } = await setupAnonCredsTests({ + issuerName: 'Faber agent indy proofs', + holderName: 'Alice agent indy proofs', + attributeNames: ['name', 'age', 'image_0', 'image_1'], + })) + + await issueLegacyAnonCredsCredential({ + issuerAgent: faberAgent, + holderAgent: aliceAgent, + holderReplay: aliceReplay, + issuerReplay: faberReplay, + issuerHolderConnectionId: faberConnectionId, + offer: { + credentialDefinitionId, + attributes: [ + { + name: 'name', + value: 'John', + }, + { + name: 'age', + value: '99', + }, + ], + linkedAttachments: [ + new LinkedAttachment({ + name: 'image_0', + attachment: new V1Attachment({ + filename: 'picture-of-a-cat.png', + data: new V1AttachmentData({ base64: 'cGljdHVyZSBvZiBhIGNhdA==' }), + }), + }), + new LinkedAttachment({ + name: 'image_1', + attachment: new V1Attachment({ + filename: 'picture-of-a-dog.png', + data: new V1AttachmentData({ base64: 'UGljdHVyZSBvZiBhIGRvZw==' }), + }), + }), + ], + }, + }) }) afterAll(async () => { @@ -61,15 +96,27 @@ describe('Present Proof', () => { }) aliceProofExchangeRecord = await aliceAgent.proofs.proposeProof({ - connectionId: aliceConnection.id, + connectionId: aliceConnectionId, protocolVersion: 'v2', proofFormats: { indy: { name: 'abc', version: '1.0', - nonce: '947121108704767252195126', - attributes: presentationPreview.attributes, - predicates: presentationPreview.predicates, + attributes: [ + { + name: 'name', + value: 'Alice', + credentialDefinitionId, + }, + ], + predicates: [ + { + name: 'age', + predicate: '>=', + threshold: 50, + credentialDefinitionId, + }, + ], }, }, }) @@ -78,22 +125,16 @@ describe('Present Proof', () => { testLogger.test('Faber waits for a presentation proposal from Alice') faberProofExchangeRecord = await faberProofExchangeRecordPromise - didCommMessageRepository = faberAgent.injectionContainer.resolve(DidCommMessageRepository) - - const proposal = await didCommMessageRepository.findAgentMessage(faberAgent.context, { - associatedRecordId: faberProofExchangeRecord.id, - messageClass: V2ProposalPresentationMessage, - }) - + const proposal = await faberAgent.proofs.findProposalMessage(faberProofExchangeRecord.id) expect(proposal).toMatchObject({ type: 'https://didcomm.org/present-proof/2.0/propose-presentation', formats: [ { attachmentId: expect.any(String), - format: V2_INDY_PRESENTATION_PROPOSAL, + format: 'hlindy/proof-req@v2.0', }, ], - proposalsAttach: [ + proposalAttachments: [ { id: expect.any(String), mimeType: 'application/json', @@ -111,36 +152,30 @@ describe('Present Proof', () => { protocolVersion: 'v2', }) - const acceptProposalOptions: AcceptProofProposalOptions = { - proofRecordId: faberProofExchangeRecord.id, - } - let aliceProofExchangeRecordPromise = waitForProofExchangeRecord(aliceAgent, { state: ProofState.RequestReceived, }) // Faber accepts the presentation proposal from Alice testLogger.test('Faber accepts presentation proposal from Alice') - faberProofExchangeRecord = await faberAgent.proofs.acceptProposal(acceptProposalOptions) + faberProofExchangeRecord = await faberAgent.proofs.acceptProposal({ + proofRecordId: faberProofExchangeRecord.id, + }) // Alice waits for presentation request from Faber testLogger.test('Alice waits for presentation request from Faber') aliceProofExchangeRecord = await aliceProofExchangeRecordPromise - const request = await didCommMessageRepository.findAgentMessage(faberAgent.context, { - associatedRecordId: faberProofExchangeRecord.id, - messageClass: V2RequestPresentationMessage, - }) - + const request = await faberAgent.proofs.findRequestMessage(faberProofExchangeRecord.id) expect(request).toMatchObject({ type: 'https://didcomm.org/present-proof/2.0/request-presentation', formats: [ { attachmentId: expect.any(String), - format: V2_INDY_PRESENTATION_REQUEST, + format: 'hlindy/proof-req@v2.0', }, ], - requestPresentationsAttach: [ + requestAttachments: [ { id: expect.any(String), mimeType: 'application/json', @@ -158,11 +193,8 @@ describe('Present Proof', () => { // Alice retrieves the requested credentials and accepts the presentation request testLogger.test('Alice accepts presentation request from Faber') - const requestedCredentials = await aliceAgent.proofs.autoSelectCredentialsForProofRequest({ + const requestedCredentials = await aliceAgent.proofs.selectCredentialsForRequest({ proofRecordId: aliceProofExchangeRecord.id, - config: { - filterByPresentationPreview: true, - }, }) faberProofExchangeRecordPromise = waitForProofExchangeRecord(faberAgent, { @@ -179,20 +211,16 @@ describe('Present Proof', () => { testLogger.test('Faber waits for presentation from Alice') faberProofExchangeRecord = await faberProofExchangeRecordPromise - const presentation = await didCommMessageRepository.findAgentMessage(faberAgent.context, { - associatedRecordId: faberProofExchangeRecord.id, - messageClass: V2PresentationMessage, - }) - + const presentation = await faberAgent.proofs.findPresentationMessage(faberProofExchangeRecord.id) expect(presentation).toMatchObject({ type: 'https://didcomm.org/present-proof/2.0/presentation', formats: [ { attachmentId: expect.any(String), - format: V2_INDY_PRESENTATION, + format: 'hlindy/proof@v2.0', }, ], - presentationsAttach: [ + presentationAttachments: [ { id: expect.any(String), mimeType: 'application/json', @@ -220,7 +248,7 @@ describe('Present Proof', () => { // Faber accepts the presentation provided by Alice testLogger.test('Faber accepts the presentation provided by Alice') - await faberAgent.proofs.acceptPresentation(faberProofExchangeRecord.id) + await faberAgent.proofs.acceptPresentation({ proofRecordId: faberProofExchangeRecord.id }) // Alice waits until she received a presentation acknowledgement testLogger.test('Alice waits until she receives a presentation acknowledgement') @@ -249,42 +277,36 @@ describe('Present Proof', () => { const requestMessage = await aliceAgent.proofs.findRequestMessage(aliceProofExchangeRecord.id) const presentationMessage = await aliceAgent.proofs.findPresentationMessage(aliceProofExchangeRecord.id) - expect(proposalMessage).toBeInstanceOf(V2ProposalPresentationMessage) + expect(proposalMessage).toBeInstanceOf(V2ProposePresentationMessage) expect(requestMessage).toBeInstanceOf(V2RequestPresentationMessage) expect(presentationMessage).toBeInstanceOf(V2PresentationMessage) const formatData = await aliceAgent.proofs.getFormatData(aliceProofExchangeRecord.id) - // eslint-disable-next-line prefer-const - let { proposeKey1, proposeKey2, requestKey1, requestKey2 } = getGroupKeysFromIndyProofFormatData(formatData) - expect(formatData).toMatchObject({ proposal: { indy: { name: 'abc', version: '1.0', - nonce: '947121108704767252195126', + nonce: expect.any(String), requested_attributes: { - 0: { + [Object.keys(formatData.proposal?.indy?.requested_attributes ?? {})[0]]: { name: 'name', - }, - [proposeKey1]: { - name: 'image_0', restrictions: [ { - cred_def_id: credDefId, + cred_def_id: credentialDefinitionId, }, ], }, }, requested_predicates: { - [proposeKey2]: { + [Object.keys(formatData.proposal?.indy?.requested_predicates ?? {})[0]]: { name: 'age', p_type: '>=', p_value: 50, restrictions: [ { - cred_def_id: credDefId, + cred_def_id: credentialDefinitionId, }, ], }, @@ -295,28 +317,25 @@ describe('Present Proof', () => { indy: { name: 'abc', version: '1.0', - nonce: '947121108704767252195126', + nonce: expect.any(String), requested_attributes: { - 0: { + [Object.keys(formatData.request?.indy?.requested_attributes ?? {})[0]]: { name: 'name', - }, - [requestKey1]: { - name: 'image_0', restrictions: [ { - cred_def_id: credDefId, + cred_def_id: credentialDefinitionId, }, ], }, }, requested_predicates: { - [requestKey2]: { + [Object.keys(formatData.request?.indy?.requested_predicates ?? {})[0]]: { name: 'age', p_type: '>=', p_value: 50, restrictions: [ { - cred_def_id: credDefId, + cred_def_id: credentialDefinitionId, }, ], }, @@ -345,39 +364,6 @@ describe('Present Proof', () => { }) test('Faber starts with proof request to Alice', async () => { - const attributes = { - name: new ProofAttributeInfo({ - name: 'name', - restrictions: [ - new AttributeFilter({ - credentialDefinitionId: credDefId, - }), - ], - }), - image_0: new ProofAttributeInfo({ - name: 'image_0', - restrictions: [ - new AttributeFilter({ - credentialDefinitionId: credDefId, - }), - ], - }), - } - - // Sample predicates - const predicates = { - age: new ProofPredicateInfo({ - name: 'age', - predicateType: PredicateType.GreaterThanOrEqualTo, - predicateValue: 50, - restrictions: [ - new AttributeFilter({ - credentialDefinitionId: credDefId, - }), - ], - }), - } - let aliceProofExchangeRecordPromise = waitForProofExchangeRecord(aliceAgent, { state: ProofState.RequestReceived, }) @@ -386,11 +372,41 @@ describe('Present Proof', () => { testLogger.test('Faber sends a presentation request to Alice') faberProofExchangeRecord = await faberAgent.proofs.requestProof({ protocolVersion: 'v2', - connectionId: faberConnection.id, + connectionId: faberConnectionId, proofFormats: { indy: { - requestedAttributes: attributes, - requestedPredicates: predicates, + name: 'Proof Request', + version: '1.0.0', + requested_attributes: { + name: { + name: 'name', + restrictions: [ + { + cred_def_id: credentialDefinitionId, + }, + ], + }, + image_0: { + name: 'image_0', + restrictions: [ + { + cred_def_id: credentialDefinitionId, + }, + ], + }, + }, + requested_predicates: { + age: { + name: 'age', + p_type: '>=', + p_value: 50, + restrictions: [ + { + cred_def_id: credentialDefinitionId, + }, + ], + }, + }, }, }, }) @@ -399,20 +415,16 @@ describe('Present Proof', () => { testLogger.test('Alice waits for presentation request from Faber') aliceProofExchangeRecord = await aliceProofExchangeRecordPromise - const request = await didCommMessageRepository.findAgentMessage(faberAgent.context, { - associatedRecordId: faberProofExchangeRecord.id, - messageClass: V2RequestPresentationMessage, - }) - + const request = await faberAgent.proofs.findRequestMessage(faberProofExchangeRecord.id) expect(request).toMatchObject({ type: 'https://didcomm.org/present-proof/2.0/request-presentation', formats: [ { attachmentId: expect.any(String), - format: V2_INDY_PRESENTATION_REQUEST, + format: 'hlindy/proof-req@v2.0', }, ], - requestPresentationsAttach: [ + requestAttachments: [ { id: expect.any(String), mimeType: 'application/json', @@ -434,11 +446,8 @@ describe('Present Proof', () => { // Alice retrieves the requested credentials and accepts the presentation request testLogger.test('Alice accepts presentation request from Faber') - const requestedCredentials = await aliceAgent.proofs.autoSelectCredentialsForProofRequest({ + const requestedCredentials = await aliceAgent.proofs.selectCredentialsForRequest({ proofRecordId: aliceProofExchangeRecord.id, - config: { - filterByPresentationPreview: true, - }, }) const faberProofExchangeRecordPromise = waitForProofExchangeRecord(faberAgent, { @@ -455,20 +464,16 @@ describe('Present Proof', () => { testLogger.test('Faber waits for presentation from Alice') faberProofExchangeRecord = await faberProofExchangeRecordPromise - const presentation = await didCommMessageRepository.findAgentMessage(faberAgent.context, { - associatedRecordId: faberProofExchangeRecord.id, - messageClass: V2PresentationMessage, - }) - + const presentation = await faberAgent.proofs.findPresentationMessage(faberProofExchangeRecord.id) expect(presentation).toMatchObject({ type: 'https://didcomm.org/present-proof/2.0/presentation', formats: [ { attachmentId: expect.any(String), - format: V2_INDY_PRESENTATION, + format: 'hlindy/proof@v2.0', }, ], - presentationsAttach: [ + presentationAttachments: [ { id: expect.any(String), mimeType: 'application/json', @@ -496,7 +501,7 @@ describe('Present Proof', () => { // Faber accepts the presentation testLogger.test('Faber accept the presentation from Alice') - await faberAgent.proofs.acceptPresentation(faberProofExchangeRecord.id) + await faberAgent.proofs.acceptPresentation({ proofRecordId: faberProofExchangeRecord.id }) // Alice waits until she receives a presentation acknowledgement testLogger.test('Alice waits for acceptance by Faber') @@ -523,39 +528,6 @@ describe('Present Proof', () => { }) test('Alice provides credentials via call to getRequestedCredentials', async () => { - const attributes = { - name: new ProofAttributeInfo({ - name: 'name', - restrictions: [ - new AttributeFilter({ - credentialDefinitionId: credDefId, - }), - ], - }), - image_0: new ProofAttributeInfo({ - name: 'image_0', - restrictions: [ - new AttributeFilter({ - credentialDefinitionId: credDefId, - }), - ], - }), - } - - // Sample predicates - const predicates = { - age: new ProofPredicateInfo({ - name: 'age', - predicateType: PredicateType.GreaterThanOrEqualTo, - predicateValue: 50, - restrictions: [ - new AttributeFilter({ - credentialDefinitionId: credDefId, - }), - ], - }), - } - const aliceProofExchangeRecordPromise = waitForProofExchangeRecord(aliceAgent, { state: ProofState.RequestReceived, }) @@ -564,14 +536,41 @@ describe('Present Proof', () => { testLogger.test('Faber sends a presentation request to Alice') faberProofExchangeRecord = await faberAgent.proofs.requestProof({ protocolVersion: 'v2', - connectionId: faberConnection.id, + connectionId: faberConnectionId, proofFormats: { indy: { - name: 'proof-request', - version: '1.0', - nonce: '1298236324864', - requestedAttributes: attributes, - requestedPredicates: predicates, + name: 'Proof Request', + version: '1.0.0', + requested_attributes: { + name: { + name: 'name', + restrictions: [ + { + cred_def_id: credentialDefinitionId, + }, + ], + }, + image_0: { + name: 'image_0', + restrictions: [ + { + cred_def_id: credentialDefinitionId, + }, + ], + }, + }, + requested_predicates: { + age: { + name: 'age', + p_type: '>=', + p_value: 50, + restrictions: [ + { + cred_def_id: credentialDefinitionId, + }, + ], + }, + }, }, }, }) @@ -580,54 +579,79 @@ describe('Present Proof', () => { testLogger.test('Alice waits for presentation request from Faber') aliceProofExchangeRecord = await aliceProofExchangeRecordPromise - const retrievedCredentials = await faberAgent.proofs.getRequestedCredentialsForProofRequest({ - proofRecordId: faberProofExchangeRecord.id, - config: {}, + const retrievedCredentials = await aliceAgent.proofs.getCredentialsForRequest({ + proofRecordId: aliceProofExchangeRecord.id, }) - if (retrievedCredentials.proofFormats.indy) { - const keys = Object.keys(retrievedCredentials.proofFormats.indy?.requestedAttributes) - expect(keys).toContain('name') - expect(keys).toContain('image_0') - } else { - fail() - } + expect(retrievedCredentials).toMatchObject({ + proofFormats: { + indy: { + attributes: { + name: [ + { + credentialId: expect.any(String), + revealed: true, + credentialInfo: { + credentialId: expect.any(String), + attributes: { + image_0: 'hl:zQmfDXo7T3J43j3CTkEZaz7qdHuABhWktksZ7JEBueZ5zUS', + image_1: 'hl:zQmRHBT9rDs5QhsnYuPY3mNpXxgLcnNXkhjWJvTSAPMmcVd', + name: 'John', + age: '99', + }, + schemaId: expect.any(String), + credentialDefinitionId: expect.any(String), + revocationRegistryId: null, + credentialRevocationId: null, + }, + }, + ], + image_0: [ + { + credentialId: expect.any(String), + revealed: true, + credentialInfo: { + credentialId: expect.any(String), + attributes: { + age: '99', + image_0: 'hl:zQmfDXo7T3J43j3CTkEZaz7qdHuABhWktksZ7JEBueZ5zUS', + image_1: 'hl:zQmRHBT9rDs5QhsnYuPY3mNpXxgLcnNXkhjWJvTSAPMmcVd', + name: 'John', + }, + schemaId: expect.any(String), + credentialDefinitionId: expect.any(String), + revocationRegistryId: null, + credentialRevocationId: null, + }, + }, + ], + }, + predicates: { + age: [ + { + credentialId: expect.any(String), + credentialInfo: { + credentialId: expect.any(String), + attributes: { + image_1: 'hl:zQmRHBT9rDs5QhsnYuPY3mNpXxgLcnNXkhjWJvTSAPMmcVd', + image_0: 'hl:zQmfDXo7T3J43j3CTkEZaz7qdHuABhWktksZ7JEBueZ5zUS', + name: 'John', + age: '99', + }, + schemaId: expect.any(String), + credentialDefinitionId: expect.any(String), + revocationRegistryId: null, + credentialRevocationId: null, + }, + }, + ], + }, + }, + }, + }) }) test('Faber starts with proof request to Alice but gets Problem Reported', async () => { - const attributes = { - name: new ProofAttributeInfo({ - name: 'name', - restrictions: [ - new AttributeFilter({ - credentialDefinitionId: credDefId, - }), - ], - }), - image_0: new ProofAttributeInfo({ - name: 'image_0', - restrictions: [ - new AttributeFilter({ - credentialDefinitionId: credDefId, - }), - ], - }), - } - - // Sample predicates - const predicates = { - age: new ProofPredicateInfo({ - name: 'age', - predicateType: PredicateType.GreaterThanOrEqualTo, - predicateValue: 50, - restrictions: [ - new AttributeFilter({ - credentialDefinitionId: credDefId, - }), - ], - }), - } - const aliceProofExchangeRecordPromise = waitForProofExchangeRecord(aliceAgent, { state: ProofState.RequestReceived, }) @@ -636,14 +660,41 @@ describe('Present Proof', () => { testLogger.test('Faber sends a presentation request to Alice') faberProofExchangeRecord = await faberAgent.proofs.requestProof({ protocolVersion: 'v2', - connectionId: faberConnection.id, + connectionId: faberConnectionId, proofFormats: { indy: { name: 'proof-request', version: '1.0', - nonce: '1298236324864', - requestedAttributes: attributes, - requestedPredicates: predicates, + requested_attributes: { + name: { + name: 'name', + restrictions: [ + { + cred_def_id: credentialDefinitionId, + }, + ], + }, + image_0: { + name: 'image_0', + restrictions: [ + { + cred_def_id: credentialDefinitionId, + }, + ], + }, + }, + requested_predicates: { + age: { + name: 'age', + p_type: '>=', + p_value: 50, + restrictions: [ + { + cred_def_id: credentialDefinitionId, + }, + ], + }, + }, }, }, }) @@ -652,20 +703,17 @@ describe('Present Proof', () => { testLogger.test('Alice waits for presentation request from Faber') aliceProofExchangeRecord = await aliceProofExchangeRecordPromise - const request = await didCommMessageRepository.findAgentMessage(faberAgent.context, { - associatedRecordId: faberProofExchangeRecord.id, - messageClass: V2RequestPresentationMessage, - }) + const request = await faberAgent.proofs.findRequestMessage(faberProofExchangeRecord.id) expect(request).toMatchObject({ type: 'https://didcomm.org/present-proof/2.0/request-presentation', formats: [ { attachmentId: expect.any(String), - format: V2_INDY_PRESENTATION_REQUEST, + format: 'hlindy/proof-req@v2.0', }, ], - requestPresentationsAttach: [ + requestAttachments: [ { id: expect.any(String), mimeType: 'application/json', @@ -689,10 +737,10 @@ describe('Present Proof', () => { state: ProofState.Abandoned, }) - aliceProofExchangeRecord = await aliceAgent.proofs.sendProblemReport( - aliceProofExchangeRecord.id, - 'Problem inside proof request' - ) + aliceProofExchangeRecord = await aliceAgent.proofs.sendProblemReport({ + description: 'Problem inside proof request', + proofRecordId: aliceProofExchangeRecord.id, + }) faberProofExchangeRecord = await faberProofExchangeRecordPromise diff --git a/packages/core/src/modules/proofs/protocol/v2/handlers/V2PresentationAckHandler.ts b/packages/core/src/modules/proofs/protocol/v2/handlers/V2PresentationAckHandler.ts index 3d28970a6e..43b9e15a69 100644 --- a/packages/core/src/modules/proofs/protocol/v2/handlers/V2PresentationAckHandler.ts +++ b/packages/core/src/modules/proofs/protocol/v2/handlers/V2PresentationAckHandler.ts @@ -1,17 +1,17 @@ import type { MessageHandler, MessageHandlerInboundMessage } from '../../../../../agent/MessageHandler' -import type { ProofService } from '../../../ProofService' +import type { ProofProtocol } from '../../ProofProtocol' import { V2PresentationAckMessage } from '../messages' export class V2PresentationAckHandler implements MessageHandler { - private proofService: ProofService + private proofProtocol: ProofProtocol public supportedMessages = [V2PresentationAckMessage] - public constructor(proofService: ProofService) { - this.proofService = proofService + public constructor(proofProtocol: ProofProtocol) { + this.proofProtocol = proofProtocol } public async handle(messageContext: MessageHandlerInboundMessage) { - await this.proofService.processAck(messageContext) + await this.proofProtocol.processAck(messageContext) } } diff --git a/packages/core/src/modules/proofs/protocol/v2/handlers/V2PresentationHandler.ts b/packages/core/src/modules/proofs/protocol/v2/handlers/V2PresentationHandler.ts index 9f2d074d67..73216d17c1 100644 --- a/packages/core/src/modules/proofs/protocol/v2/handlers/V2PresentationHandler.ts +++ b/packages/core/src/modules/proofs/protocol/v2/handlers/V2PresentationHandler.ts @@ -1,75 +1,56 @@ -import type { AgentConfig } from '../../../../../agent/AgentConfig' import type { MessageHandler, MessageHandlerInboundMessage } from '../../../../../agent/MessageHandler' -import type { DidCommMessageRepository } from '../../../../../storage' -import type { ProofResponseCoordinator } from '../../../ProofResponseCoordinator' import type { ProofExchangeRecord } from '../../../repository' -import type { V2ProofService } from '../V2ProofService' +import type { V2ProofProtocol } from '../V2ProofProtocol' import { OutboundMessageContext } from '../../../../../agent/models' +import { DidCommMessageRepository } from '../../../../../storage' import { V2PresentationMessage, V2RequestPresentationMessage } from '../messages' export class V2PresentationHandler implements MessageHandler { - private proofService: V2ProofService - private agentConfig: AgentConfig - private proofResponseCoordinator: ProofResponseCoordinator - private didCommMessageRepository: DidCommMessageRepository + private proofProtocol: V2ProofProtocol public supportedMessages = [V2PresentationMessage] - public constructor( - proofService: V2ProofService, - agentConfig: AgentConfig, - proofResponseCoordinator: ProofResponseCoordinator, - didCommMessageRepository: DidCommMessageRepository - ) { - this.proofService = proofService - this.agentConfig = agentConfig - this.proofResponseCoordinator = proofResponseCoordinator - this.didCommMessageRepository = didCommMessageRepository + public constructor(proofProtocol: V2ProofProtocol) { + this.proofProtocol = proofProtocol } public async handle(messageContext: MessageHandlerInboundMessage) { - const proofRecord = await this.proofService.processPresentation(messageContext) + const proofRecord = await this.proofProtocol.processPresentation(messageContext) - const shouldAutoRespond = await this.proofResponseCoordinator.shouldAutoRespondToPresentation( - messageContext.agentContext, - proofRecord - ) + const shouldAutoRespond = await this.proofProtocol.shouldAutoRespondToPresentation(messageContext.agentContext, { + proofRecord, + presentationMessage: messageContext.message, + }) if (shouldAutoRespond) { - return await this.createAck(proofRecord, messageContext) + return await this.acceptPresentation(proofRecord, messageContext) } } - private async createAck( - record: ProofExchangeRecord, + private async acceptPresentation( + proofRecord: ProofExchangeRecord, messageContext: MessageHandlerInboundMessage ) { - this.agentConfig.logger.info( - `Automatically sending acknowledgement with autoAccept on ${this.agentConfig.autoAcceptProofs}` - ) + messageContext.agentContext.config.logger.info(`Automatically sending acknowledgement with autoAccept`) - const { message, proofRecord } = await this.proofService.createAck(messageContext.agentContext, { - proofRecord: record, + const { message } = await this.proofProtocol.acceptPresentation(messageContext.agentContext, { + proofRecord, }) - const requestMessage = await this.didCommMessageRepository.findAgentMessage(messageContext.agentContext, { + const didCommMessageRepository = messageContext.agentContext.dependencyManager.resolve(DidCommMessageRepository) + const requestMessage = await didCommMessageRepository.findAgentMessage(messageContext.agentContext, { associatedRecordId: proofRecord.id, messageClass: V2RequestPresentationMessage, }) - const presentationMessage = await this.didCommMessageRepository.findAgentMessage(messageContext.agentContext, { - associatedRecordId: proofRecord.id, - messageClass: V2PresentationMessage, - }) - if (messageContext.connection) { return new OutboundMessageContext(message, { agentContext: messageContext.agentContext, connection: messageContext.connection, associatedRecord: proofRecord, }) - } else if (requestMessage?.service && presentationMessage?.service) { - const recipientService = presentationMessage?.service + } else if (requestMessage?.service && messageContext.message?.service) { + const recipientService = messageContext.message?.service const ourService = requestMessage?.service return new OutboundMessageContext(message, { @@ -77,10 +58,11 @@ export class V2PresentationHandler implements MessageHandler { serviceParams: { service: recipientService.resolvedDidCommService, senderKey: ourService.resolvedDidCommService.recipientKeys[0], + returnRoute: true, }, }) } - this.agentConfig.logger.error(`Could not automatically create presentation ack`) + messageContext.agentContext.config.logger.error(`Could not automatically create presentation ack`) } } diff --git a/packages/core/src/modules/proofs/protocol/v2/handlers/V2PresentationProblemReportHandler.ts b/packages/core/src/modules/proofs/protocol/v2/handlers/V2PresentationProblemReportHandler.ts index 947a8c6c44..5d9512d824 100644 --- a/packages/core/src/modules/proofs/protocol/v2/handlers/V2PresentationProblemReportHandler.ts +++ b/packages/core/src/modules/proofs/protocol/v2/handlers/V2PresentationProblemReportHandler.ts @@ -1,13 +1,13 @@ import type { MessageHandler, MessageHandlerInboundMessage } from '../../../../../agent/MessageHandler' -import type { V2ProofService } from '../V2ProofService' +import type { V2ProofProtocol } from '../V2ProofProtocol' import { V2PresentationProblemReportMessage } from '../messages' export class V2PresentationProblemReportHandler implements MessageHandler { - private proofService: V2ProofService + private proofService: V2ProofProtocol public supportedMessages = [V2PresentationProblemReportMessage] - public constructor(proofService: V2ProofService) { + public constructor(proofService: V2ProofProtocol) { this.proofService = proofService } diff --git a/packages/core/src/modules/proofs/protocol/v2/handlers/V2ProposePresentationHandler.ts b/packages/core/src/modules/proofs/protocol/v2/handlers/V2ProposePresentationHandler.ts index 9432a3ca56..589ff6db3e 100644 --- a/packages/core/src/modules/proofs/protocol/v2/handlers/V2ProposePresentationHandler.ts +++ b/packages/core/src/modules/proofs/protocol/v2/handlers/V2ProposePresentationHandler.ts @@ -1,99 +1,42 @@ -import type { AgentConfig } from '../../../../../agent/AgentConfig' import type { MessageHandler, MessageHandlerInboundMessage } from '../../../../../agent/MessageHandler' -import type { DidCommMessageRepository } from '../../../../../storage' -import type { ProofResponseCoordinator } from '../../../ProofResponseCoordinator' -import type { ProofFormat } from '../../../formats/ProofFormat' -import type { - CreateProofRequestFromProposalOptions, - CreateRequestAsResponseOptions, - ProofRequestFromProposalOptions, -} from '../../../models/ProofServiceOptions' import type { ProofExchangeRecord } from '../../../repository/ProofExchangeRecord' -import type { V2ProofService } from '../V2ProofService' +import type { V2ProofProtocol } from '../V2ProofProtocol' import { OutboundMessageContext } from '../../../../../agent/models' -import { AriesFrameworkError } from '../../../../../error/AriesFrameworkError' -import { V2ProposalPresentationMessage } from '../messages/V2ProposalPresentationMessage' +import { V2ProposePresentationMessage } from '../messages/V2ProposePresentationMessage' -export class V2ProposePresentationHandler implements MessageHandler { - private proofService: V2ProofService - private agentConfig: AgentConfig - private didCommMessageRepository: DidCommMessageRepository - private proofResponseCoordinator: ProofResponseCoordinator - public supportedMessages = [V2ProposalPresentationMessage] +export class V2ProposePresentationHandler implements MessageHandler { + private proofProtocol: V2ProofProtocol + public supportedMessages = [V2ProposePresentationMessage] - public constructor( - proofService: V2ProofService, - agentConfig: AgentConfig, - didCommMessageRepository: DidCommMessageRepository, - proofResponseCoordinator: ProofResponseCoordinator - ) { - this.proofService = proofService - this.agentConfig = agentConfig - this.didCommMessageRepository = didCommMessageRepository - this.proofResponseCoordinator = proofResponseCoordinator + public constructor(proofProtocol: V2ProofProtocol) { + this.proofProtocol = proofProtocol } public async handle(messageContext: MessageHandlerInboundMessage) { - const proofRecord = await this.proofService.processProposal(messageContext) + const proofRecord = await this.proofProtocol.processProposal(messageContext) - const shouldAutoRespond = await this.proofResponseCoordinator.shouldAutoRespondToProposal( - messageContext.agentContext, - proofRecord - ) + const shouldAutoRespond = await this.proofProtocol.shouldAutoRespondToProposal(messageContext.agentContext, { + proofRecord, + proposalMessage: messageContext.message, + }) if (shouldAutoRespond) { - return this.createRequest(proofRecord, messageContext) + return this.acceptProposal(proofRecord, messageContext) } } - - private async createRequest( + private async acceptProposal( proofRecord: ProofExchangeRecord, messageContext: MessageHandlerInboundMessage ) { - this.agentConfig.logger.info( - `Automatically sending request with autoAccept on ${this.agentConfig.autoAcceptProofs}` - ) + messageContext.agentContext.config.logger.info(`Automatically sending request with autoAccept`) if (!messageContext.connection) { - this.agentConfig.logger.error('No connection on the messageContext') - throw new AriesFrameworkError('No connection on the messageContext') - } - - const proposalMessage = await this.didCommMessageRepository.findAgentMessage(messageContext.agentContext, { - associatedRecordId: proofRecord.id, - messageClass: V2ProposalPresentationMessage, - }) - - if (!proposalMessage) { - this.agentConfig.logger.error(`Proof record with id ${proofRecord.id} is missing required credential proposal`) - throw new AriesFrameworkError(`Proof record with id ${proofRecord.id} is missing required credential proposal`) - } - - const proofRequestFromProposalOptions: CreateProofRequestFromProposalOptions = { - proofRecord, - } - - const proofRequest: ProofRequestFromProposalOptions = await this.proofService.createProofRequestFromProposal( - messageContext.agentContext, - proofRequestFromProposalOptions - ) - - const indyProofRequest = proofRequest.proofFormats - - if (!indyProofRequest) { - this.agentConfig.logger.error('Failed to create proof request') - throw new AriesFrameworkError('Failed to create proof request.') - } - - const options: CreateRequestAsResponseOptions = { - proofRecord: proofRecord, - autoAcceptProof: proofRecord.autoAcceptProof, - proofFormats: indyProofRequest, - willConfirm: true, + messageContext.agentContext.config.logger.error('No connection on the messageContext, aborting auto accept') + return } - const { message } = await this.proofService.createRequestAsResponse(messageContext.agentContext, options) + const { message } = await this.proofProtocol.acceptProposal(messageContext.agentContext, { proofRecord }) return new OutboundMessageContext(message, { agentContext: messageContext.agentContext, diff --git a/packages/core/src/modules/proofs/protocol/v2/handlers/V2RequestPresentationHandler.ts b/packages/core/src/modules/proofs/protocol/v2/handlers/V2RequestPresentationHandler.ts index 65edcd85c5..0eaad0f2d0 100644 --- a/packages/core/src/modules/proofs/protocol/v2/handlers/V2RequestPresentationHandler.ts +++ b/packages/core/src/modules/proofs/protocol/v2/handlers/V2RequestPresentationHandler.ts @@ -1,86 +1,45 @@ -import type { AgentConfig } from '../../../../../agent/AgentConfig' import type { MessageHandler, MessageHandlerInboundMessage } from '../../../../../agent/MessageHandler' -import type { DidCommMessageRepository } from '../../../../../storage/didcomm/DidCommMessageRepository' -import type { MediationRecipientService, RoutingService } from '../../../../routing' -import type { ProofResponseCoordinator } from '../../../ProofResponseCoordinator' -import type { ProofFormat } from '../../../formats/ProofFormat' -import type { - FormatRequestedCredentialReturn, - FormatRetrievedCredentialOptions, -} from '../../../models/ProofServiceOptions' import type { ProofExchangeRecord } from '../../../repository/ProofExchangeRecord' -import type { V2ProofService } from '../V2ProofService' +import type { V2ProofProtocol } from '../V2ProofProtocol' import { OutboundMessageContext } from '../../../../../agent/models' import { ServiceDecorator } from '../../../../../decorators/service/ServiceDecorator' import { DidCommMessageRole } from '../../../../../storage' +import { DidCommMessageRepository } from '../../../../../storage/didcomm/DidCommMessageRepository' +import { RoutingService } from '../../../../routing' import { V2RequestPresentationMessage } from '../messages/V2RequestPresentationMessage' -export class V2RequestPresentationHandler implements MessageHandler { - private proofService: V2ProofService - private agentConfig: AgentConfig - private proofResponseCoordinator: ProofResponseCoordinator - private mediationRecipientService: MediationRecipientService - private didCommMessageRepository: DidCommMessageRepository - private routingService: RoutingService +export class V2RequestPresentationHandler implements MessageHandler { + private proofProtocol: V2ProofProtocol public supportedMessages = [V2RequestPresentationMessage] - public constructor( - proofService: V2ProofService, - agentConfig: AgentConfig, - proofResponseCoordinator: ProofResponseCoordinator, - mediationRecipientService: MediationRecipientService, - didCommMessageRepository: DidCommMessageRepository, - routingService: RoutingService - ) { - this.proofService = proofService - this.agentConfig = agentConfig - this.proofResponseCoordinator = proofResponseCoordinator - this.mediationRecipientService = mediationRecipientService - this.didCommMessageRepository = didCommMessageRepository - this.routingService = routingService + public constructor(proofProtocol: V2ProofProtocol) { + this.proofProtocol = proofProtocol } public async handle(messageContext: MessageHandlerInboundMessage) { - const proofRecord = await this.proofService.processRequest(messageContext) + const proofRecord = await this.proofProtocol.processRequest(messageContext) + + const shouldAutoRespond = await this.proofProtocol.shouldAutoRespondToRequest(messageContext.agentContext, { + proofRecord, + requestMessage: messageContext.message, + }) - const shouldAutoRespond = await this.proofResponseCoordinator.shouldAutoRespondToRequest( - messageContext.agentContext, - proofRecord - ) + messageContext.agentContext.config.logger.debug(`Should auto respond to request: ${shouldAutoRespond}`) if (shouldAutoRespond) { - return await this.createPresentation(proofRecord, messageContext) + return await this.acceptRequest(proofRecord, messageContext) } } - private async createPresentation( - record: ProofExchangeRecord, + private async acceptRequest( + proofRecord: ProofExchangeRecord, messageContext: MessageHandlerInboundMessage ) { - const requestMessage = await this.didCommMessageRepository.getAgentMessage(messageContext.agentContext, { - associatedRecordId: record.id, - messageClass: V2RequestPresentationMessage, - }) - - this.agentConfig.logger.info( - `Automatically sending presentation with autoAccept on ${this.agentConfig.autoAcceptProofs}` - ) + messageContext.agentContext.config.logger.info(`Automatically sending presentation with autoAccept`) - const retrievedCredentials: FormatRetrievedCredentialOptions = - await this.proofService.getRequestedCredentialsForProofRequest(messageContext.agentContext, { - proofRecord: record, - config: { - filterByPresentationPreview: false, - }, - }) - - const requestedCredentials: FormatRequestedCredentialReturn = - await this.proofService.autoSelectCredentialsForProofRequest(retrievedCredentials) - - const { message, proofRecord } = await this.proofService.createPresentation(messageContext.agentContext, { - proofRecord: record, - proofFormats: requestedCredentials.proofFormats, + const { message } = await this.proofProtocol.acceptRequest(messageContext.agentContext, { + proofRecord, }) if (messageContext.connection) { @@ -89,16 +48,19 @@ export class V2RequestPresentationHandler(RoutingService) + const didCommMessageRepository = messageContext.agentContext.dependencyManager.resolve(DidCommMessageRepository) + + const routing = await routingService.getRouting(messageContext.agentContext) message.service = new ServiceDecorator({ serviceEndpoint: routing.endpoints[0], recipientKeys: [routing.recipientKey.publicKeyBase58], routingKeys: routing.routingKeys.map((key) => key.publicKeyBase58), }) - const recipientService = requestMessage.service + const recipientService = messageContext.message.service - await this.didCommMessageRepository.saveOrUpdateAgentMessage(messageContext.agentContext, { + await didCommMessageRepository.saveOrUpdateAgentMessage(messageContext.agentContext, { agentMessage: message, associatedRecordId: proofRecord.id, role: DidCommMessageRole.Sender, @@ -109,10 +71,11 @@ export class V2RequestPresentationHandler { - const attachment = this.presentationsAttach.find((attachment) => attachment.id === format.attachmentId) - - if (!attachment) { - throw new AriesFrameworkError(`Could not find a matching attachment with attachmentId: ${format.attachmentId}`) - } - - attachmentFormats.push({ format, attachment }) - }) - return attachmentFormats - } - @IsValidMessageType(V2PresentationMessage.type) public readonly type = V2PresentationMessage.type.messageTypeUri public static readonly type = parseMessageType('https://didcomm.org/present-proof/2.0/presentation') @@ -89,5 +62,9 @@ export class V2PresentationMessage extends DidCommV1Message { @IsArray() @ValidateNested({ each: true }) @IsInstance(V1Attachment, { each: true }) - public presentationsAttach!: V1Attachment[] + public presentationAttachments!: V1Attachment[] + + public getPresentationAttachmentById(id: string): V1Attachment | undefined { + return this.presentationAttachments.find((attachment) => attachment.id === id) + } } diff --git a/packages/core/src/modules/proofs/protocol/v2/messages/V2PresentationProblemReportMessage.ts b/packages/core/src/modules/proofs/protocol/v2/messages/V2PresentationProblemReportMessage.ts index f5592dc322..f5b1476d4c 100644 --- a/packages/core/src/modules/proofs/protocol/v2/messages/V2PresentationProblemReportMessage.ts +++ b/packages/core/src/modules/proofs/protocol/v2/messages/V2PresentationProblemReportMessage.ts @@ -1,22 +1,10 @@ -import type { ProblemReportMessageOptions } from '../../../../problem-reports/versions/v1/messages/ProblemReportMessage' - import { IsValidMessageType, parseMessageType } from '../../../../../utils/messageType' import { ProblemReportMessage } from '../../../../problem-reports/versions/v1/messages/ProblemReportMessage' -export type V2PresentationProblemReportMessageOptions = ProblemReportMessageOptions - /** * @see https://github.com/hyperledger/aries-rfcs/blob/main/features/0035-report-problem/README.md */ export class V2PresentationProblemReportMessage extends ProblemReportMessage { - /** - * Create new PresentationProblemReportMessage instance. - * @param options - */ - public constructor(options: V2PresentationProblemReportMessageOptions) { - super(options) - } - @IsValidMessageType(V2PresentationProblemReportMessage.type) public readonly type = V2PresentationProblemReportMessage.type.messageTypeUri public static readonly type = parseMessageType('https://didcomm.org/present-proof/2.0/problem-report') diff --git a/packages/core/src/modules/proofs/protocol/v2/messages/V2ProposalPresentationMessage.ts b/packages/core/src/modules/proofs/protocol/v2/messages/V2ProposalPresentationMessage.ts deleted file mode 100644 index c82bcd2844..0000000000 --- a/packages/core/src/modules/proofs/protocol/v2/messages/V2ProposalPresentationMessage.ts +++ /dev/null @@ -1,100 +0,0 @@ -import type { ProofAttachmentFormat } from '../../../formats/models/ProofAttachmentFormat' - -import { Expose, Type } from 'class-transformer' -import { IsArray, IsBoolean, IsInstance, IsOptional, IsString, ValidateNested } from 'class-validator' - -import { V1Attachment } from '../../../../../decorators/attachment/V1Attachment' -import { DidCommV1Message } from '../../../../../didcomm' -import { AriesFrameworkError } from '../../../../../error/AriesFrameworkError' -import { IsValidMessageType, parseMessageType } from '../../../../../utils/messageType' -import { uuid } from '../../../../../utils/uuid' -import { ProofFormatSpec } from '../../../models/ProofFormatSpec' - -export interface V2ProposePresentationMessageOptions { - id?: string - comment?: string - goalCode?: string - willConfirm?: boolean - parentThreadId?: string - attachmentInfo: ProofAttachmentFormat[] -} - -export class V2ProposalPresentationMessage extends DidCommV1Message { - public constructor(options: V2ProposePresentationMessageOptions) { - super() - - if (options) { - this.formats = [] - this.proposalsAttach = [] - this.id = options.id ?? uuid() - this.comment = options.comment - this.goalCode = options.goalCode - this.willConfirm = options.willConfirm ?? false - - if (options.parentThreadId) { - this.setThread({ - parentThreadId: options.parentThreadId, - }) - } - - for (const entry of options.attachmentInfo) { - this.addProposalsAttachment(entry) - } - } - } - - public addProposalsAttachment(attachment: ProofAttachmentFormat) { - this.formats.push(attachment.format) - this.proposalsAttach.push(attachment.attachment) - } - - /** - * Every attachment has a corresponding entry in the formats array. - * This method pairs those together in a {@link ProofAttachmentFormat} object. - */ - public getAttachmentFormats(): ProofAttachmentFormat[] { - const attachmentFormats: ProofAttachmentFormat[] = [] - - this.formats.forEach((format) => { - const attachment = this.proposalsAttach.find((attachment) => attachment.id === format.attachmentId) - - if (!attachment) { - throw new AriesFrameworkError(`Could not find a matching attachment with attachmentId: ${format.attachmentId}`) - } - - attachmentFormats.push({ format, attachment }) - }) - return attachmentFormats - } - - @IsValidMessageType(V2ProposalPresentationMessage.type) - public readonly type = V2ProposalPresentationMessage.type.messageTypeUri - public static readonly type = parseMessageType(`https://didcomm.org/present-proof/2.0/propose-presentation`) - - @IsString() - @IsOptional() - public comment?: string - - @Expose({ name: 'goal_code' }) - @IsString() - @IsOptional() - public goalCode?: string - - @Expose({ name: 'will_confirm' }) - @IsBoolean() - public willConfirm = false - - @Expose({ name: 'formats' }) - @Type(() => ProofFormatSpec) - @IsArray() - @ValidateNested({ each: true }) - @IsInstance(ProofFormatSpec, { each: true }) - public formats!: ProofFormatSpec[] - - @Expose({ name: 'proposals~attach' }) - @Type(() => V1Attachment) - @IsArray() - @ValidateNested({ each: true }) - @IsInstance(V1Attachment, { each: true }) - public proposalsAttach!: V1Attachment[] -} diff --git a/packages/core/src/modules/proofs/protocol/v2/messages/V2ProposePresentationMessage.ts b/packages/core/src/modules/proofs/protocol/v2/messages/V2ProposePresentationMessage.ts new file mode 100644 index 0000000000..7990bb254c --- /dev/null +++ b/packages/core/src/modules/proofs/protocol/v2/messages/V2ProposePresentationMessage.ts @@ -0,0 +1,62 @@ +import { Expose, Type } from 'class-transformer' +import { IsArray, IsInstance, IsOptional, IsString, ValidateNested } from 'class-validator' + +import { V1Attachment } from '../../../../../decorators/attachment/V1Attachment' +import { DidCommV1Message } from '../../../../../didcomm' +import { IsValidMessageType, parseMessageType } from '../../../../../utils/messageType' +import { uuid } from '../../../../../utils/uuid' +import { ProofFormatSpec } from '../../../models/ProofFormatSpec' + +export interface V2ProposePresentationMessageOptions { + id?: string + comment?: string + goalCode?: string + proposalAttachments: V1Attachment[] + formats: ProofFormatSpec[] +} + +export class V2ProposePresentationMessage extends DidCommV1Message { + public constructor(options: V2ProposePresentationMessageOptions) { + super() + + if (options) { + this.formats = [] + this.proposalAttachments = [] + this.id = options.id ?? uuid() + this.comment = options.comment + this.goalCode = options.goalCode + this.formats = options.formats + this.proposalAttachments = options.proposalAttachments + } + } + + @IsValidMessageType(V2ProposePresentationMessage.type) + public readonly type = V2ProposePresentationMessage.type.messageTypeUri + public static readonly type = parseMessageType('https://didcomm.org/present-proof/2.0/propose-presentation') + + @IsString() + @IsOptional() + public comment?: string + + @Expose({ name: 'goal_code' }) + @IsString() + @IsOptional() + public goalCode?: string + + @Type(() => ProofFormatSpec) + @IsArray() + @ValidateNested({ each: true }) + @IsInstance(ProofFormatSpec, { each: true }) + public formats!: ProofFormatSpec[] + + @Expose({ name: 'proposals~attach' }) + @Type(() => V1Attachment) + @IsArray() + @ValidateNested({ each: true }) + @IsInstance(V1Attachment, { each: true }) + public proposalAttachments!: V1Attachment[] + + public getProposalAttachmentById(id: string): V1Attachment | undefined { + return this.proposalAttachments.find((attachment) => attachment.id === id) + } +} diff --git a/packages/core/src/modules/proofs/protocol/v2/messages/V2RequestPresentationMessage.ts b/packages/core/src/modules/proofs/protocol/v2/messages/V2RequestPresentationMessage.ts index 61fbd23e66..5a890a3f57 100644 --- a/packages/core/src/modules/proofs/protocol/v2/messages/V2RequestPresentationMessage.ts +++ b/packages/core/src/modules/proofs/protocol/v2/messages/V2RequestPresentationMessage.ts @@ -1,11 +1,8 @@ -import type { ProofAttachmentFormat } from '../../../formats/models/ProofAttachmentFormat' - import { Expose, Type } from 'class-transformer' import { IsArray, IsBoolean, IsInstance, IsOptional, IsString, ValidateNested } from 'class-validator' import { V1Attachment } from '../../../../../decorators/attachment/V1Attachment' import { DidCommV1Message } from '../../../../../didcomm' -import { AriesFrameworkError } from '../../../../../error' import { IsValidMessageType, parseMessageType } from '../../../../../utils/messageType' import { uuid } from '../../../../../utils/uuid' import { ProofFormatSpec } from '../../../models/ProofFormatSpec' @@ -16,8 +13,8 @@ export interface V2RequestPresentationMessageOptions { goalCode?: string presentMultiple?: boolean willConfirm?: boolean - parentThreadId?: string - attachmentInfo: ProofAttachmentFormat[] + formats: ProofFormatSpec[] + requestAttachments: V1Attachment[] } export class V2RequestPresentationMessage extends DidCommV1Message { @@ -26,68 +23,17 @@ export class V2RequestPresentationMessage extends DidCommV1Message { if (options) { this.formats = [] - this.requestPresentationsAttach = [] + this.requestAttachments = [] this.id = options.id ?? uuid() this.comment = options.comment this.goalCode = options.goalCode this.willConfirm = options.willConfirm ?? true this.presentMultiple = options.presentMultiple ?? false - - if (options.parentThreadId) { - this.setThread({ - parentThreadId: options.parentThreadId, - }) - } - - for (const entry of options.attachmentInfo) { - this.addRequestPresentationsAttachment(entry) - } + this.requestAttachments = options.requestAttachments + this.formats = options.formats } } - public addRequestPresentationsAttachment(attachment: ProofAttachmentFormat) { - this.formats.push(attachment.format) - this.requestPresentationsAttach.push(attachment.attachment) - } - - public getAttachmentByFormatIdentifier(formatIdentifier: string) { - const format = this.formats.find((x) => x.format === formatIdentifier) - if (!format) { - throw new AriesFrameworkError( - `Expected to find a format entry of type: ${formatIdentifier}, but none could be found.` - ) - } - - const attachment = this.requestPresentationsAttach.find((x) => x.id === format.attachmentId) - - if (!attachment) { - throw new AriesFrameworkError( - `Expected to find an attachment entry with id: ${format.attachmentId}, but none could be found.` - ) - } - - return attachment - } - - /** - * Every attachment has a corresponding entry in the formats array. - * This method pairs those together in a {@link ProofAttachmentFormat} object. - */ - public getAttachmentFormats(): ProofAttachmentFormat[] { - const attachmentFormats: ProofAttachmentFormat[] = [] - - this.formats.forEach((format) => { - const attachment = this.requestPresentationsAttach.find((attachment) => attachment.id === format.attachmentId) - - if (!attachment) { - throw new AriesFrameworkError(`Could not find a matching attachment with attachmentId: ${format.attachmentId}`) - } - - attachmentFormats.push({ format, attachment }) - }) - return attachmentFormats - } - @IsValidMessageType(V2RequestPresentationMessage.type) public readonly type = V2RequestPresentationMessage.type.messageTypeUri public static readonly type = parseMessageType('https://didcomm.org/present-proof/2.0/request-presentation') @@ -121,5 +67,9 @@ export class V2RequestPresentationMessage extends DidCommV1Message { @IsArray() @ValidateNested({ each: true }) @IsInstance(V1Attachment, { each: true }) - public requestPresentationsAttach!: V1Attachment[] + public requestAttachments!: V1Attachment[] + + public getRequestAttachmentById(id: string): V1Attachment | undefined { + return this.requestAttachments.find((attachment) => attachment.id === id) + } } diff --git a/packages/core/src/modules/proofs/protocol/v2/messages/index.ts b/packages/core/src/modules/proofs/protocol/v2/messages/index.ts index 8b0c4a005d..515b0afb9c 100644 --- a/packages/core/src/modules/proofs/protocol/v2/messages/index.ts +++ b/packages/core/src/modules/proofs/protocol/v2/messages/index.ts @@ -1,5 +1,5 @@ export * from './V2PresentationAckMessage' export * from './V2PresentationMessage' export * from './V2PresentationProblemReportMessage' -export * from './V2ProposalPresentationMessage' +export * from './V2ProposePresentationMessage' export * from './V2RequestPresentationMessage' diff --git a/packages/core/src/modules/proofs/repository/ProofExchangeRecord.ts b/packages/core/src/modules/proofs/repository/ProofExchangeRecord.ts index f145703dff..30d236a1ac 100644 --- a/packages/core/src/modules/proofs/repository/ProofExchangeRecord.ts +++ b/packages/core/src/modules/proofs/repository/ProofExchangeRecord.ts @@ -82,6 +82,14 @@ export class ProofExchangeRecord extends BaseRecord() public constructor( - dispatcher: Dispatcher, + messageHandlerRegistry: MessageHandlerRegistry, mediationRecipientService: MediationRecipientService, connectionService: ConnectionService, dids: DidsApi, messageSender: MessageSender, eventEmitter: EventEmitter, discoverFeaturesApi: DiscoverFeaturesApi, + messagePickupApi: MessagePickupApi, mediationRepository: MediationRepository, routingService: RoutingService, @inject(InjectionSymbols.Logger) logger: Logger, agentContext: AgentContext, @inject(InjectionSymbols.Stop$) stop$: Subject, - recipientModuleConfig: RecipientModuleConfig + mediationRecipientModuleConfig: MediationRecipientModuleConfig ) { this.connectionService = connectionService this.dids = dids @@ -79,27 +82,16 @@ export class RecipientApi { this.eventEmitter = eventEmitter this.logger = logger this.discoverFeaturesApi = discoverFeaturesApi + this.messagePickupApi = messagePickupApi this.mediationRepository = mediationRepository this.routingService = routingService this.agentContext = agentContext this.stop$ = stop$ - this.config = recipientModuleConfig - this.registerMessageHandlers(dispatcher) + this.config = mediationRecipientModuleConfig + this.registerMessageHandlers(messageHandlerRegistry) } public async initialize() { - const { defaultMediatorId, clearDefaultMediator } = this.agentContext.config - - // Set default mediator by id - if (defaultMediatorId) { - const mediatorRecord = await this.mediationRecipientService.getById(this.agentContext, defaultMediatorId) - await this.mediationRecipientService.setDefaultMediator(this.agentContext, mediatorRecord) - } - // Clear the stored default mediator - else if (clearDefaultMediator) { - await this.mediationRecipientService.clearDefaultMediator(this.agentContext) - } - // Poll for messages from mediator const defaultMediator = await this.findDefaultMediator() if (defaultMediator) { @@ -207,7 +199,11 @@ export class RecipientApi { try { if (pickupStrategy === MediatorPickupStrategy.PickUpV2) { // Start Pickup v2 protocol to receive messages received while websocket offline - await this.sendStatusRequest({ mediatorId: mediator.id }) + await this.messagePickupApi.pickupMessages({ + connectionId: mediator.connectionId, + batchSize: this.config.maximumMessagePickup, + protocolVersion: 'v2', + }) } else { await this.openMediationWebSocket(mediator) } @@ -249,7 +245,11 @@ export class RecipientApi { case MediatorPickupStrategy.PickUpV2: this.logger.info(`Starting pickup of messages from mediator '${mediatorRecord.id}'`) await this.openWebSocketAndPickUp(mediatorRecord, mediatorPickupStrategy) - await this.sendStatusRequest({ mediatorId: mediatorRecord.id }) + await this.messagePickupApi.pickupMessages({ + connectionId: mediatorConnection.id, + batchSize: this.config.maximumMessagePickup, + protocolVersion: 'v2', + }) break case MediatorPickupStrategy.PickUpV1: { const stopConditions$ = merge(this.stop$, this.stopMessagePickup$).pipe() @@ -259,7 +259,11 @@ export class RecipientApi { .pipe(takeUntil(stopConditions$)) .subscribe({ next: async () => { - await this.pickupMessages(mediatorConnection) + await this.messagePickupApi.pickupMessages({ + connectionId: mediatorConnection.id, + batchSize: this.config.maximumMessagePickup, + protocolVersion: 'v1', + }) }, complete: () => this.logger.info(`Stopping pickup of messages from mediator '${mediatorRecord.id}'`), }) @@ -283,22 +287,6 @@ export class RecipientApi { this.stopMessagePickup$.next(true) } - private async sendStatusRequest(config: { mediatorId: string; recipientKey?: string }) { - const mediationRecord = await this.mediationRecipientService.getById(this.agentContext, config.mediatorId) - - const statusRequestMessage = await this.mediationRecipientService.createStatusRequest(mediationRecord, { - recipientKey: config.recipientKey, - }) - - const mediatorConnection = await this.connectionService.getById(this.agentContext, mediationRecord.connectionId) - return this.messageSender.sendMessage( - new OutboundMessageContext(statusRequestMessage, { - agentContext: this.agentContext, - connection: mediatorConnection, - }) - ) - } - private async getPickupStrategyForMediator(mediator: MediationRecord) { let mediatorPickupStrategy = mediator.pickupStrategy ?? this.config.mediatorPickupStrategy @@ -308,22 +296,22 @@ export class RecipientApi { const discloseForPickupV2 = await this.discoverFeaturesApi.queryFeatures({ connectionId: mediator.connectionId, protocolVersion: 'v1', - queries: [{ featureType: 'protocol', match: StatusMessage.type.protocolUri }], + queries: [{ featureType: 'protocol', match: V2StatusMessage.type.protocolUri }], awaitDisclosures: true, }) - if (discloseForPickupV2.features?.find((item) => item.id === StatusMessage.type.protocolUri)) { + if (discloseForPickupV2.features?.find((item) => item.id === V2StatusMessage.type.protocolUri)) { mediatorPickupStrategy = MediatorPickupStrategy.PickUpV2 } else { const discloseForPickupV1 = await this.discoverFeaturesApi.queryFeatures({ connectionId: mediator.connectionId, protocolVersion: 'v1', - queries: [{ featureType: 'protocol', match: BatchPickupMessage.type.protocolUri }], + queries: [{ featureType: 'protocol', match: V1BatchPickupMessage.type.protocolUri }], awaitDisclosures: true, }) // Use explicit pickup strategy mediatorPickupStrategy = discloseForPickupV1.features?.find( - (item) => item.id === BatchPickupMessage.type.protocolUri + (item) => item.id === V1BatchPickupMessage.type.protocolUri ) ? MediatorPickupStrategy.PickUpV1 : MediatorPickupStrategy.Implicit @@ -341,18 +329,18 @@ export class RecipientApi { return this.mediationRecipientService.discoverMediation(this.agentContext) } + /** + * @deprecated Use `MessagePickupApi.pickupMessages` instead. + * */ public async pickupMessages(mediatorConnection: ConnectionRecord, pickupStrategy?: MediatorPickupStrategy) { mediatorConnection.assertReady() - const pickupMessage = - pickupStrategy === MediatorPickupStrategy.PickUpV2 - ? new StatusRequestMessage({}) - : new BatchPickupMessage({ batchSize: 10 }) - const outboundMessageContext = new OutboundMessageContext(pickupMessage, { - agentContext: this.agentContext, - connection: mediatorConnection, + const messagePickupApi = this.agentContext.dependencyManager.resolve(MessagePickupApi) + + await messagePickupApi.pickupMessages({ + connectionId: mediatorConnection.id, + protocolVersion: pickupStrategy === MediatorPickupStrategy.PickUpV2 ? 'v2' : 'v1', }) - await this.sendMessage(outboundMessageContext, pickupStrategy) } public async setDefaultMediator(mediatorRecord: MediationRecord) { @@ -484,12 +472,10 @@ export class RecipientApi { } // Register handlers for the several messages for the mediator. - private registerMessageHandlers(dispatcher: Dispatcher) { - dispatcher.registerMessageHandler(new KeylistUpdateResponseHandler(this.mediationRecipientService)) - dispatcher.registerMessageHandler(new MediationGrantHandler(this.mediationRecipientService)) - dispatcher.registerMessageHandler(new MediationDenyHandler(this.mediationRecipientService)) - dispatcher.registerMessageHandler(new StatusHandler(this.mediationRecipientService)) - dispatcher.registerMessageHandler(new MessageDeliveryHandler(this.mediationRecipientService)) - //dispatcher.registerMessageHandler(new KeylistListHandler(this.mediationRecipientService)) // TODO: write this + private registerMessageHandlers(messageHandlerRegistry: MessageHandlerRegistry) { + messageHandlerRegistry.registerMessageHandler(new KeylistUpdateResponseHandler(this.mediationRecipientService)) + messageHandlerRegistry.registerMessageHandler(new MediationGrantHandler(this.mediationRecipientService)) + messageHandlerRegistry.registerMessageHandler(new MediationDenyHandler(this.mediationRecipientService)) + //messageHandlerRegistry.registerMessageHandler(new KeylistListHandler(this.mediationRecipientService)) // TODO: write this } } diff --git a/packages/core/src/modules/routing/RecipientModule.ts b/packages/core/src/modules/routing/MediationRecipientModule.ts similarity index 58% rename from packages/core/src/modules/routing/RecipientModule.ts rename to packages/core/src/modules/routing/MediationRecipientModule.ts index 7f160cdb4a..f27da353c9 100644 --- a/packages/core/src/modules/routing/RecipientModule.ts +++ b/packages/core/src/modules/routing/MediationRecipientModule.ts @@ -1,21 +1,21 @@ +import type { MediationRecipientModuleConfigOptions } from './MediationRecipientModuleConfig' import type { FeatureRegistry } from '../../agent/FeatureRegistry' import type { DependencyManager, Module } from '../../plugins' -import type { RecipientModuleConfigOptions } from './RecipientModuleConfig' import { Protocol } from '../../agent/models' -import { RecipientApi } from './RecipientApi' -import { RecipientModuleConfig } from './RecipientModuleConfig' +import { MediationRecipientApi } from './MediationRecipientApi' +import { MediationRecipientModuleConfig } from './MediationRecipientModuleConfig' import { MediationRole } from './models' import { MediationRepository } from './repository' import { MediationRecipientService, RoutingService } from './services' -export class RecipientModule implements Module { - public readonly config: RecipientModuleConfig - public readonly api = RecipientApi +export class MediationRecipientModule implements Module { + public readonly config: MediationRecipientModuleConfig + public readonly api = MediationRecipientApi - public constructor(config?: RecipientModuleConfigOptions) { - this.config = new RecipientModuleConfig(config) + public constructor(config?: MediationRecipientModuleConfigOptions) { + this.config = new MediationRecipientModuleConfig(config) } /** @@ -23,10 +23,10 @@ export class RecipientModule implements Module { */ public register(dependencyManager: DependencyManager, featureRegistry: FeatureRegistry) { // Api - dependencyManager.registerContextScoped(RecipientApi) + dependencyManager.registerContextScoped(MediationRecipientApi) // Config - dependencyManager.registerInstance(RecipientModuleConfig, this.config) + dependencyManager.registerInstance(MediationRecipientModuleConfig, this.config) // Services dependencyManager.registerSingleton(MediationRecipientService) diff --git a/packages/core/src/modules/routing/RecipientModuleConfig.ts b/packages/core/src/modules/routing/MediationRecipientModuleConfig.ts similarity index 81% rename from packages/core/src/modules/routing/RecipientModuleConfig.ts rename to packages/core/src/modules/routing/MediationRecipientModuleConfig.ts index 4463289936..6f94234fc5 100644 --- a/packages/core/src/modules/routing/RecipientModuleConfig.ts +++ b/packages/core/src/modules/routing/MediationRecipientModuleConfig.ts @@ -1,10 +1,10 @@ import type { MediatorPickupStrategy } from './MediatorPickupStrategy' /** - * RecipientModuleConfigOptions defines the interface for the options of the RecipientModuleConfig class. + * MediationRecipientModuleConfigOptions defines the interface for the options of the MediationRecipientModuleConfig class. * This can contain optional parameters that have default values in the config class itself. */ -export interface RecipientModuleConfigOptions { +export interface MediationRecipientModuleConfigOptions { /** * Strategy to use for picking up messages from the mediator. If no strategy is provided, the agent will use the discover * features protocol to determine the best strategy. @@ -68,39 +68,39 @@ export interface RecipientModuleConfigOptions { mediatorInvitationUrl?: string } -export class RecipientModuleConfig { - private options: RecipientModuleConfigOptions +export class MediationRecipientModuleConfig { + private options: MediationRecipientModuleConfigOptions - public constructor(options?: RecipientModuleConfigOptions) { + public constructor(options?: MediationRecipientModuleConfigOptions) { this.options = options ?? {} } - /** See {@link RecipientModuleConfigOptions.mediatorPollingInterval} */ + /** See {@link MediationRecipientModuleConfigOptions.mediatorPollingInterval} */ public get mediatorPollingInterval() { return this.options.mediatorPollingInterval ?? 5000 } - /** See {@link RecipientModuleConfigOptions.mediatorPickupStrategy} */ + /** See {@link MediationRecipientModuleConfigOptions.mediatorPickupStrategy} */ public get mediatorPickupStrategy() { return this.options.mediatorPickupStrategy } - /** See {@link RecipientModuleConfigOptions.maximumMessagePickup} */ + /** See {@link MediationRecipientModuleConfigOptions.maximumMessagePickup} */ public get maximumMessagePickup() { return this.options.maximumMessagePickup ?? 10 } - /** See {@link RecipientModuleConfigOptions.baseMediatorReconnectionIntervalMs} */ + /** See {@link MediationRecipientModuleConfigOptions.baseMediatorReconnectionIntervalMs} */ public get baseMediatorReconnectionIntervalMs() { return this.options.baseMediatorReconnectionIntervalMs ?? 100 } - /** See {@link RecipientModuleConfigOptions.maximumMediatorReconnectionIntervalMs} */ + /** See {@link MediationRecipientModuleConfigOptions.maximumMediatorReconnectionIntervalMs} */ public get maximumMediatorReconnectionIntervalMs() { return this.options.maximumMediatorReconnectionIntervalMs ?? Number.POSITIVE_INFINITY } - /** See {@link RecipientModuleConfigOptions.mediatorInvitationUrl} */ + /** See {@link MediationRecipientModuleConfigOptions.mediatorInvitationUrl} */ public get mediatorInvitationUrl() { return this.options.mediatorInvitationUrl } diff --git a/packages/core/src/modules/routing/MediatorApi.ts b/packages/core/src/modules/routing/MediatorApi.ts index 77b7af2a4f..24b788ed4f 100644 --- a/packages/core/src/modules/routing/MediatorApi.ts +++ b/packages/core/src/modules/routing/MediatorApi.ts @@ -1,19 +1,17 @@ -import type { EncryptedMessage } from '../../didcomm/types' import type { MediationRecord } from './repository' +import type { EncryptedMessage } from '../../didcomm/types' import { AgentContext } from '../../agent' -import { Dispatcher } from '../../agent/Dispatcher' -import { EventEmitter } from '../../agent/EventEmitter' +import { MessageHandlerRegistry } from '../../agent/MessageHandlerRegistry' import { MessageSender } from '../../agent/MessageSender' import { OutboundMessageContext } from '../../agent/models' import { injectable } from '../../plugins' import { ConnectionService } from '../connections/services' +import { MessagePickupApi } from '../message-pìckup' import { MediatorModuleConfig } from './MediatorModuleConfig' import { ForwardHandler, KeylistUpdateHandler } from './handlers' import { MediationRequestHandler } from './handlers/MediationRequestHandler' -import { MessagePickupService, V2MessagePickupService } from './protocol' -import { BatchHandler, BatchPickupHandler } from './protocol/pickup/v1/handlers' import { MediatorService } from './services/MediatorService' @injectable() @@ -21,32 +19,24 @@ export class MediatorApi { public config: MediatorModuleConfig private mediatorService: MediatorService - private messagePickupService: MessagePickupService private messageSender: MessageSender - private eventEmitter: EventEmitter private agentContext: AgentContext private connectionService: ConnectionService public constructor( - dispatcher: Dispatcher, + messageHandlerRegistry: MessageHandlerRegistry, mediationService: MediatorService, - messagePickupService: MessagePickupService, - // Only imported so it is injected and handlers are registered - v2MessagePickupService: V2MessagePickupService, messageSender: MessageSender, - eventEmitter: EventEmitter, agentContext: AgentContext, connectionService: ConnectionService, config: MediatorModuleConfig ) { this.mediatorService = mediationService - this.messagePickupService = messagePickupService this.messageSender = messageSender - this.eventEmitter = eventEmitter this.connectionService = connectionService this.agentContext = agentContext this.config = config - this.registerMessageHandlers(dispatcher) + this.registerMessageHandlers(messageHandlerRegistry) } public async initialize() { @@ -81,17 +71,19 @@ export class MediatorApi { return mediationRecord } + /** + * @deprecated Use `MessagePickupApi.queueMessage` instead. + * */ public queueMessage(connectionId: string, message: EncryptedMessage) { - return this.messagePickupService.queueMessage(connectionId, message) + const messagePickupApi = this.agentContext.dependencyManager.resolve(MessagePickupApi) + return messagePickupApi.queueMessage({ connectionId, message }) } - private registerMessageHandlers(dispatcher: Dispatcher) { - dispatcher.registerMessageHandler(new KeylistUpdateHandler(this.mediatorService)) - dispatcher.registerMessageHandler( + private registerMessageHandlers(messageHandlerRegistry: MessageHandlerRegistry) { + messageHandlerRegistry.registerMessageHandler(new KeylistUpdateHandler(this.mediatorService)) + messageHandlerRegistry.registerMessageHandler( new ForwardHandler(this.mediatorService, this.connectionService, this.messageSender) ) - dispatcher.registerMessageHandler(new BatchPickupHandler(this.messagePickupService)) - dispatcher.registerMessageHandler(new BatchHandler(this.eventEmitter)) - dispatcher.registerMessageHandler(new MediationRequestHandler(this.mediatorService, this.config)) + messageHandlerRegistry.registerMessageHandler(new MediationRequestHandler(this.mediatorService, this.config)) } } diff --git a/packages/core/src/modules/routing/MediatorModule.ts b/packages/core/src/modules/routing/MediatorModule.ts index db81da0f1a..0ddc220263 100644 --- a/packages/core/src/modules/routing/MediatorModule.ts +++ b/packages/core/src/modules/routing/MediatorModule.ts @@ -1,13 +1,12 @@ +import type { MediatorModuleConfigOptions } from './MediatorModuleConfig' import type { FeatureRegistry } from '../../agent/FeatureRegistry' import type { DependencyManager, Module } from '../../plugins' -import type { MediatorModuleConfigOptions } from './MediatorModuleConfig' import { Protocol } from '../../agent/models' import { MediatorApi } from './MediatorApi' import { MediatorModuleConfig } from './MediatorModuleConfig' import { MediationRole } from './models' -import { MessagePickupService, V2MessagePickupService } from './protocol' import { MediationRepository, MediatorRoutingRepository } from './repository' import { MediatorService } from './services' @@ -31,8 +30,6 @@ export class MediatorModule implements Module { // Services dependencyManager.registerSingleton(MediatorService) - dependencyManager.registerSingleton(MessagePickupService) - dependencyManager.registerSingleton(V2MessagePickupService) // Repositories dependencyManager.registerSingleton(MediationRepository) @@ -43,14 +40,6 @@ export class MediatorModule implements Module { new Protocol({ id: 'https://didcomm.org/coordinate-mediation/1.0', roles: [MediationRole.Mediator], - }), - new Protocol({ - id: 'https://didcomm.org/messagepickup/1.0', - roles: ['message_holder', 'recipient', 'batch_sender', 'batch_recipient'], - }), - new Protocol({ - id: 'https://didcomm.org/messagepickup/2.0', - roles: ['mediator', 'recipient'], }) ) } diff --git a/packages/core/src/modules/routing/MediatorModuleConfig.ts b/packages/core/src/modules/routing/MediatorModuleConfig.ts index 2e781fbc85..8b70d9591a 100644 --- a/packages/core/src/modules/routing/MediatorModuleConfig.ts +++ b/packages/core/src/modules/routing/MediatorModuleConfig.ts @@ -1,5 +1,5 @@ /** - * MediatorModuleConfigOptions defines the interface for the options of the RecipientModuleConfig class. + * MediatorModuleConfigOptions defines the interface for the options of the MediatorModuleConfig class. * This can contain optional parameters that have default values in the config class itself. */ export interface MediatorModuleConfigOptions { @@ -18,7 +18,7 @@ export class MediatorModuleConfig { this.options = options ?? {} } - /** See {@link RecipientModuleConfigOptions.autoAcceptMediationRequests} */ + /** See {@link MediatorModuleConfigOptions.autoAcceptMediationRequests} */ public get autoAcceptMediationRequests() { return this.options.autoAcceptMediationRequests ?? false } diff --git a/packages/core/src/modules/routing/RoutingEvents.ts b/packages/core/src/modules/routing/RoutingEvents.ts index f7aa892ebf..86a151abff 100644 --- a/packages/core/src/modules/routing/RoutingEvents.ts +++ b/packages/core/src/modules/routing/RoutingEvents.ts @@ -1,8 +1,8 @@ -import type { BaseEvent } from '../../agent/Events' -import type { Routing } from '../connections' import type { KeylistUpdate } from './messages/KeylistUpdateMessage' import type { MediationState } from './models/MediationState' import type { MediationRecord } from './repository/MediationRecord' +import type { BaseEvent } from '../../agent/Events' +import type { Routing } from '../connections' export enum RoutingEventTypes { MediationStateChanged = 'MediationStateChanged', diff --git a/packages/core/src/modules/routing/__tests__/RecipientModule.test.ts b/packages/core/src/modules/routing/__tests__/MediationRecipientModule.test.ts similarity index 80% rename from packages/core/src/modules/routing/__tests__/RecipientModule.test.ts rename to packages/core/src/modules/routing/__tests__/MediationRecipientModule.test.ts index 0008d36f8d..dad1f499be 100644 --- a/packages/core/src/modules/routing/__tests__/RecipientModule.test.ts +++ b/packages/core/src/modules/routing/__tests__/MediationRecipientModule.test.ts @@ -1,7 +1,7 @@ import { FeatureRegistry } from '../../../agent/FeatureRegistry' import { DependencyManager } from '../../../plugins/DependencyManager' -import { RecipientApi } from '../RecipientApi' -import { RecipientModule } from '../RecipientModule' +import { MediationRecipientApi } from '../MediationRecipientApi' +import { MediationRecipientModule } from '../MediationRecipientModule' import { MediationRepository } from '../repository' import { MediationRecipientService, RoutingService } from '../services' @@ -15,12 +15,12 @@ const FeatureRegistryMock = FeatureRegistry as jest.Mock const featureRegistry = new FeatureRegistryMock() -describe('RecipientModule', () => { +describe('MediationRecipientModule', () => { test('registers dependencies on the dependency manager', () => { - new RecipientModule().register(dependencyManager, featureRegistry) + new MediationRecipientModule().register(dependencyManager, featureRegistry) expect(dependencyManager.registerContextScoped).toHaveBeenCalledTimes(1) - expect(dependencyManager.registerContextScoped).toHaveBeenCalledWith(RecipientApi) + expect(dependencyManager.registerContextScoped).toHaveBeenCalledWith(MediationRecipientApi) expect(dependencyManager.registerSingleton).toHaveBeenCalledTimes(3) expect(dependencyManager.registerSingleton).toHaveBeenCalledWith(MediationRecipientService) diff --git a/packages/core/src/modules/routing/__tests__/MediatorModule.test.ts b/packages/core/src/modules/routing/__tests__/MediatorModule.test.ts index 5835103180..81ba044281 100644 --- a/packages/core/src/modules/routing/__tests__/MediatorModule.test.ts +++ b/packages/core/src/modules/routing/__tests__/MediatorModule.test.ts @@ -2,7 +2,6 @@ import { FeatureRegistry } from '../../../agent/FeatureRegistry' import { DependencyManager } from '../../../plugins/DependencyManager' import { MediatorApi } from '../MediatorApi' import { MediatorModule } from '../MediatorModule' -import { MessagePickupService, V2MessagePickupService } from '../protocol' import { MediationRepository, MediatorRoutingRepository } from '../repository' import { MediatorService } from '../services' @@ -22,10 +21,8 @@ describe('MediatorModule', () => { expect(dependencyManager.registerContextScoped).toHaveBeenCalledTimes(1) expect(dependencyManager.registerContextScoped).toHaveBeenCalledWith(MediatorApi) - expect(dependencyManager.registerSingleton).toHaveBeenCalledTimes(5) + expect(dependencyManager.registerSingleton).toHaveBeenCalledTimes(3) expect(dependencyManager.registerSingleton).toHaveBeenCalledWith(MediatorService) - expect(dependencyManager.registerSingleton).toHaveBeenCalledWith(MessagePickupService) - expect(dependencyManager.registerSingleton).toHaveBeenCalledWith(V2MessagePickupService) expect(dependencyManager.registerSingleton).toHaveBeenCalledWith(MediationRepository) expect(dependencyManager.registerSingleton).toHaveBeenCalledWith(MediatorRoutingRepository) }) diff --git a/packages/core/src/modules/routing/__tests__/mediation.test.ts b/packages/core/src/modules/routing/__tests__/mediation.test.ts index 8cbb712fb9..459e538909 100644 --- a/packages/core/src/modules/routing/__tests__/mediation.test.ts +++ b/packages/core/src/modules/routing/__tests__/mediation.test.ts @@ -1,31 +1,44 @@ /* eslint-disable @typescript-eslint/no-non-null-assertion */ import type { SubjectMessage } from '../../../../../../tests/transport/SubjectInboundTransport' import type { AgentDependencies } from '../../../agent/AgentDependencies' +import type { AgentModulesInput } from '../../../agent/AgentModules' import type { InitConfig } from '../../../types' import { Subject } from 'rxjs' import { SubjectInboundTransport } from '../../../../../../tests/transport/SubjectInboundTransport' import { SubjectOutboundTransport } from '../../../../../../tests/transport/SubjectOutboundTransport' +import { getIndySdkModules } from '../../../../../indy-sdk/tests/setupIndySdkModule' import { getAgentOptions, waitForBasicMessage } from '../../../../tests/helpers' import { Agent } from '../../../agent/Agent' +import { sleep } from '../../../utils/sleep' import { ConnectionRecord, HandshakeProtocol } from '../../connections' +import { MediationRecipientModule } from '../MediationRecipientModule' +import { MediatorModule } from '../MediatorModule' import { MediatorPickupStrategy } from '../MediatorPickupStrategy' import { MediationState } from '../models/MediationState' -const recipientAgentOptions = getAgentOptions('Mediation: Recipient', { - indyLedgers: [], -}) -const mediatorAgentOptions = getAgentOptions('Mediation: Mediator', { - autoAcceptMediationRequests: true, - endpoints: ['rxjs:mediator'], - indyLedgers: [], -}) +const recipientAgentOptions = getAgentOptions('Mediation: Recipient', {}, getIndySdkModules()) +const mediatorAgentOptions = getAgentOptions( + 'Mediation: Mediator', + { + endpoints: ['rxjs:mediator'], + }, + { + ...getIndySdkModules(), + mediator: new MediatorModule({ + autoAcceptMediationRequests: true, + }), + } +) -const senderAgentOptions = getAgentOptions('Mediation: Sender', { - endpoints: ['rxjs:sender'], - indyLedgers: [], -}) +const senderAgentOptions = getAgentOptions( + 'Mediation: Sender', + { + endpoints: ['rxjs:sender'], + }, + getIndySdkModules() +) describe('mediator establishment', () => { let recipientAgent: Agent @@ -45,10 +58,12 @@ describe('mediator establishment', () => { mediatorAgentOptions: { readonly config: InitConfig readonly dependencies: AgentDependencies + modules: AgentModulesInput }, recipientAgentOptions: { config: InitConfig dependencies: AgentDependencies + modules: AgentModulesInput } ) => { const mediatorMessages = new Subject() @@ -76,10 +91,13 @@ describe('mediator establishment', () => { // Initialize recipient with mediation connections invitation recipientAgent = new Agent({ ...recipientAgentOptions, - config: { - ...recipientAgentOptions.config, - mediatorConnectionsInvite: mediatorOutOfBandRecord.getOutOfBandInvitation().toUrl({ - domain: 'https://example.com/ssi', + modules: { + ...recipientAgentOptions.modules, + mediationRecipient: new MediationRecipientModule({ + mediatorPickupStrategy: MediatorPickupStrategy.PickUpV1, + mediatorInvitationUrl: mediatorOutOfBandRecord.getOutOfBandInvitation().toUrl({ + domain: 'https://example.com/ssi', + }), }), }, }) @@ -97,10 +115,10 @@ describe('mediator establishment', () => { const [mediatorRecipientConnection] = await mediatorAgent.connections.findAllByOutOfBandId( mediatorOutOfBandRecord.id ) - expect(mediatorRecipientConnection.isReady).toBe(true) + expect(mediatorRecipientConnection!.isReady).toBe(true) expect(mediatorRecipientConnection).toBeConnectedWith(recipientMediatorConnection) - expect(recipientMediatorConnection).toBeConnectedWith(mediatorRecipientConnection) + expect(recipientMediatorConnection).toBeConnectedWith(mediatorRecipientConnection!) expect(recipientMediator?.state).toBe(MediationState.Granted) @@ -123,15 +141,13 @@ describe('mediator establishment', () => { senderRecipientConnection = await senderAgent.connections.returnWhenIsConnected(senderRecipientConnection!.id) - let [recipientSenderConnection] = await recipientAgent.connections.findAllByOutOfBandId( - recipientOutOfBandRecord!.id - ) + let [recipientSenderConnection] = await recipientAgent.connections.findAllByOutOfBandId(recipientOutOfBandRecord.id) expect(recipientSenderConnection).toBeConnectedWith(senderRecipientConnection) - expect(senderRecipientConnection).toBeConnectedWith(recipientSenderConnection) - expect(recipientSenderConnection.isReady).toBe(true) + expect(senderRecipientConnection).toBeConnectedWith(recipientSenderConnection!) + expect(recipientSenderConnection!.isReady).toBe(true) expect(senderRecipientConnection.isReady).toBe(true) - recipientSenderConnection = await recipientAgent.connections.returnWhenIsConnected(recipientSenderConnection.id) + recipientSenderConnection = await recipientAgent.connections.returnWhenIsConnected(recipientSenderConnection!.id) const message = 'hello, world' await senderAgent.basicMessages.sendMessage(senderRecipientConnection.id, message) @@ -140,6 +156,10 @@ describe('mediator establishment', () => { content: message, }) + // polling interval is 100ms, so 500ms should be enough to make sure no messages are sent + await recipientAgent.mediationRecipient.stopMessagePickup() + await sleep(500) + expect(basicMessage.content).toBe(message) } @@ -151,28 +171,21 @@ describe('mediator establishment', () => { 5. Assert endpoint in recipient invitation for sender is mediator endpoint 6. Send basic message from sender to recipient and assert it is received on the recipient side `, async () => { - await e2eMediationTest(mediatorAgentOptions, { - config: { - ...recipientAgentOptions.config, - mediatorPickupStrategy: MediatorPickupStrategy.PickUpV1, - }, - dependencies: recipientAgentOptions.dependencies, - }) + await e2eMediationTest(mediatorAgentOptions, recipientAgentOptions) }) test('Mediation end-to-end flow (not using did:key)', async () => { await e2eMediationTest( { + ...mediatorAgentOptions, config: { ...mediatorAgentOptions.config, useDidKeyInProtocols: false }, - dependencies: mediatorAgentOptions.dependencies, }, { + ...recipientAgentOptions, config: { ...recipientAgentOptions.config, - mediatorPickupStrategy: MediatorPickupStrategy.PickUpV1, useDidKeyInProtocols: false, }, - dependencies: recipientAgentOptions.dependencies, } ) }) @@ -203,12 +216,14 @@ describe('mediator establishment', () => { // Initialize recipient with mediation connections invitation recipientAgent = new Agent({ ...recipientAgentOptions, - config: { - ...recipientAgentOptions.config, - mediatorConnectionsInvite: mediatorOutOfBandRecord.getOutOfBandInvitation().toUrl({ - domain: 'https://example.com/ssi', + modules: { + ...recipientAgentOptions.modules, + mediationRecipient: new MediationRecipientModule({ + mediatorInvitationUrl: mediatorOutOfBandRecord.getOutOfBandInvitation().toUrl({ + domain: 'https://example.com/ssi', + }), + mediatorPickupStrategy: MediatorPickupStrategy.PickUpV1, }), - mediatorPickupStrategy: MediatorPickupStrategy.PickUpV1, }, }) recipientAgent.registerOutboundTransport(new SubjectOutboundTransport(subjectMap)) @@ -222,11 +237,11 @@ describe('mediator establishment', () => { const [mediatorRecipientConnection] = await mediatorAgent.connections.findAllByOutOfBandId( mediatorOutOfBandRecord.id ) - expect(mediatorRecipientConnection.isReady).toBe(true) + expect(mediatorRecipientConnection!.isReady).toBe(true) // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - expect(mediatorRecipientConnection).toBeConnectedWith(recipientMediatorConnection) - expect(recipientMediatorConnection).toBeConnectedWith(mediatorRecipientConnection) + expect(mediatorRecipientConnection).toBeConnectedWith(recipientMediatorConnection!) + expect(recipientMediatorConnection).toBeConnectedWith(mediatorRecipientConnection!) expect(recipientMediator?.state).toBe(MediationState.Granted) @@ -234,12 +249,14 @@ describe('mediator establishment', () => { await recipientAgent.shutdown() recipientAgent = new Agent({ ...recipientAgentOptions, - config: { - ...recipientAgentOptions.config, - mediatorConnectionsInvite: mediatorOutOfBandRecord.getOutOfBandInvitation().toUrl({ - domain: 'https://example.com/ssi', + modules: { + ...recipientAgentOptions.modules, + mediationRecipient: new MediationRecipientModule({ + mediatorInvitationUrl: mediatorOutOfBandRecord.getOutOfBandInvitation().toUrl({ + domain: 'https://example.com/ssi', + }), + mediatorPickupStrategy: MediatorPickupStrategy.PickUpV1, }), - mediatorPickupStrategy: MediatorPickupStrategy.PickUpV1, }, }) recipientAgent.registerOutboundTransport(new SubjectOutboundTransport(subjectMap)) @@ -268,9 +285,9 @@ describe('mediator establishment', () => { recipientOutOfBandRecord.id ) expect(recipientSenderConnection).toBeConnectedWith(senderRecipientConnection) - expect(senderRecipientConnection).toBeConnectedWith(recipientSenderConnection) + expect(senderRecipientConnection).toBeConnectedWith(recipientSenderConnection!) - expect(recipientSenderConnection.isReady).toBe(true) + expect(recipientSenderConnection!.isReady).toBe(true) expect(senderRecipientConnection.isReady).toBe(true) const message = 'hello, world' @@ -281,5 +298,7 @@ describe('mediator establishment', () => { }) expect(basicMessage.content).toBe(message) + + await recipientAgent.mediationRecipient.stopMessagePickup() }) }) diff --git a/packages/core/src/modules/routing/index.ts b/packages/core/src/modules/routing/index.ts index 6032d26ecd..981dbe5207 100644 --- a/packages/core/src/modules/routing/index.ts +++ b/packages/core/src/modules/routing/index.ts @@ -1,11 +1,10 @@ export * from './messages' export * from './services' -export * from './protocol' export * from './repository' export * from './models' export * from './RoutingEvents' export * from './MediatorApi' -export * from './RecipientApi' +export * from './MediationRecipientApi' export * from './MediatorPickupStrategy' export * from './MediatorModule' -export * from './RecipientModule' +export * from './MediationRecipientModule' diff --git a/packages/core/src/modules/routing/messages/ForwardMessage.ts b/packages/core/src/modules/routing/messages/ForwardMessage.ts index 87585bf208..f6a1124624 100644 --- a/packages/core/src/modules/routing/messages/ForwardMessage.ts +++ b/packages/core/src/modules/routing/messages/ForwardMessage.ts @@ -15,6 +15,8 @@ export interface ForwardMessageOptions { * @see https://github.com/hyperledger/aries-rfcs/blob/master/concepts/0094-cross-domain-messaging/README.md#corerouting10forward */ export class ForwardMessage extends DidCommV1Message { + public readonly allowDidSovPrefix = true + /** * Create new ForwardMessage instance. * diff --git a/packages/core/src/modules/routing/messages/V2ForwardMessage.ts b/packages/core/src/modules/routing/messages/V2ForwardMessage.ts new file mode 100644 index 0000000000..248f04625b --- /dev/null +++ b/packages/core/src/modules/routing/messages/V2ForwardMessage.ts @@ -0,0 +1,42 @@ +import type { DidCommV2MessageParams } from '../../../didcomm' + +import { Type } from 'class-transformer' +import { IsString, ValidateNested } from 'class-validator' + +import { DidCommV2Message } from '../../../didcomm' +import { IsValidMessageType, parseMessageType } from '../../../utils/messageType' + +export class V2ForwardMessageBody { + @IsString() + public next!: string +} + +export type V2ForwardMessageOptions = { + body: V2ForwardMessageBody +} & DidCommV2MessageParams + +/** + * DIDComm V2 version of message defined here https://identity.foundation/didcomm-messaging/spec/#messages + */ +export class V2ForwardMessage extends DidCommV2Message { + /** + * Create new ForwardMessage instance. + * + * @param options + */ + public constructor(options: V2ForwardMessageOptions) { + super(options) + + if (options) { + this.body = options.body + } + } + + @IsValidMessageType(V2ForwardMessage.type) + public readonly type = V2ForwardMessage.type.messageTypeUri + public static readonly type = parseMessageType('https://didcomm.org/routing/2.0/forward') + + @Type(() => V2ForwardMessageBody) + @ValidateNested() + public body!: V2ForwardMessageBody +} diff --git a/packages/core/src/modules/routing/messages/index.ts b/packages/core/src/modules/routing/messages/index.ts index 06af8aeb93..4cd094bd6d 100644 --- a/packages/core/src/modules/routing/messages/index.ts +++ b/packages/core/src/modules/routing/messages/index.ts @@ -4,3 +4,4 @@ export * from './KeylistUpdateResponseMessage' export * from './MediationGrantMessage' export * from './MediationDenyMessage' export * from './MediationRequestMessage' +export * from './V2ForwardMessage' diff --git a/packages/core/src/modules/routing/protocol/index.ts b/packages/core/src/modules/routing/protocol/index.ts deleted file mode 100644 index c18db7326d..0000000000 --- a/packages/core/src/modules/routing/protocol/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './pickup' diff --git a/packages/core/src/modules/routing/protocol/pickup/v1/MessagePickupService.ts b/packages/core/src/modules/routing/protocol/pickup/v1/MessagePickupService.ts deleted file mode 100644 index 29f3a4b564..0000000000 --- a/packages/core/src/modules/routing/protocol/pickup/v1/MessagePickupService.ts +++ /dev/null @@ -1,64 +0,0 @@ -import type { InboundMessageContext } from '../../../../../agent/models/InboundMessageContext' -import type { EncryptedMessage } from '../../../../../didcomm/types' -import type { BatchPickupMessage } from './messages' - -import { Dispatcher } from '../../../../../agent/Dispatcher' -import { EventEmitter } from '../../../../../agent/EventEmitter' -import { OutboundMessageContext } from '../../../../../agent/models' -import { InjectionSymbols } from '../../../../../constants' -import { inject, injectable } from '../../../../../plugins' -import { MessageRepository } from '../../../../../storage/MessageRepository' - -import { BatchHandler, BatchPickupHandler } from './handlers' -import { BatchMessage, BatchMessageMessage } from './messages' - -@injectable() -export class MessagePickupService { - private messageRepository: MessageRepository - private dispatcher: Dispatcher - private eventEmitter: EventEmitter - - public constructor( - @inject(InjectionSymbols.MessageRepository) messageRepository: MessageRepository, - dispatcher: Dispatcher, - eventEmitter: EventEmitter - ) { - this.messageRepository = messageRepository - this.dispatcher = dispatcher - this.eventEmitter = eventEmitter - - this.registerMessageHandlers() - } - - public async batch(messageContext: InboundMessageContext) { - // Assert ready connection - const connection = messageContext.assertReadyConnection() - - const { message } = messageContext - const messages = await this.messageRepository.takeFromQueue(connection.id, message.batchSize) - - // TODO: each message should be stored with an id. to be able to conform to the id property - // of batch message - const batchMessages = messages.map( - (msg) => - new BatchMessageMessage({ - message: msg, - }) - ) - - const batchMessage = new BatchMessage({ - messages: batchMessages, - }) - - return new OutboundMessageContext(batchMessage, { agentContext: messageContext.agentContext, connection }) - } - - public async queueMessage(connectionId: string, message: EncryptedMessage) { - await this.messageRepository.add(connectionId, message) - } - - protected registerMessageHandlers() { - this.dispatcher.registerMessageHandler(new BatchPickupHandler(this)) - this.dispatcher.registerMessageHandler(new BatchHandler(this.eventEmitter)) - } -} diff --git a/packages/core/src/modules/routing/protocol/pickup/v1/handlers/BatchHandler.ts b/packages/core/src/modules/routing/protocol/pickup/v1/handlers/BatchHandler.ts deleted file mode 100644 index 30d8e5263f..0000000000 --- a/packages/core/src/modules/routing/protocol/pickup/v1/handlers/BatchHandler.ts +++ /dev/null @@ -1,32 +0,0 @@ -import type { EventEmitter } from '../../../../../../agent/EventEmitter' -import type { AgentMessageReceivedEvent } from '../../../../../../agent/Events' -import type { MessageHandler, MessageHandlerInboundMessage } from '../../../../../../agent/MessageHandler' - -import { AgentEventTypes } from '../../../../../../agent/Events' -import { BatchMessage } from '../messages' - -export class BatchHandler implements MessageHandler { - private eventEmitter: EventEmitter - public supportedMessages = [BatchMessage] - - public constructor(eventEmitter: EventEmitter) { - this.eventEmitter = eventEmitter - } - - public async handle(messageContext: MessageHandlerInboundMessage) { - const { message } = messageContext - - messageContext.assertReadyConnection() - - const forwardedMessages = message.messages - forwardedMessages.forEach((message) => { - this.eventEmitter.emit(messageContext.agentContext, { - type: AgentEventTypes.AgentMessageReceived, - payload: { - message: message.message, - contextCorrelationId: messageContext.agentContext.contextCorrelationId, - }, - }) - }) - } -} diff --git a/packages/core/src/modules/routing/protocol/pickup/v1/handlers/BatchPickupHandler.ts b/packages/core/src/modules/routing/protocol/pickup/v1/handlers/BatchPickupHandler.ts deleted file mode 100644 index e47fb6c2f5..0000000000 --- a/packages/core/src/modules/routing/protocol/pickup/v1/handlers/BatchPickupHandler.ts +++ /dev/null @@ -1,19 +0,0 @@ -import type { MessageHandler, MessageHandlerInboundMessage } from '../../../../../../agent/MessageHandler' -import type { MessagePickupService } from '../MessagePickupService' - -import { BatchPickupMessage } from '../messages' - -export class BatchPickupHandler implements MessageHandler { - private messagePickupService: MessagePickupService - public supportedMessages = [BatchPickupMessage] - - public constructor(messagePickupService: MessagePickupService) { - this.messagePickupService = messagePickupService - } - - public async handle(messageContext: MessageHandlerInboundMessage) { - messageContext.assertReadyConnection() - - return this.messagePickupService.batch(messageContext) - } -} diff --git a/packages/core/src/modules/routing/protocol/pickup/v1/handlers/index.ts b/packages/core/src/modules/routing/protocol/pickup/v1/handlers/index.ts deleted file mode 100644 index d7a709a49d..0000000000 --- a/packages/core/src/modules/routing/protocol/pickup/v1/handlers/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from './BatchHandler' -export * from './BatchPickupHandler' diff --git a/packages/core/src/modules/routing/protocol/pickup/v1/index.ts b/packages/core/src/modules/routing/protocol/pickup/v1/index.ts deleted file mode 100644 index 9174e24a93..0000000000 --- a/packages/core/src/modules/routing/protocol/pickup/v1/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from './MessagePickupService' -export * from './messages' diff --git a/packages/core/src/modules/routing/protocol/pickup/v1/messages/index.ts b/packages/core/src/modules/routing/protocol/pickup/v1/messages/index.ts deleted file mode 100644 index 8e32f97f68..0000000000 --- a/packages/core/src/modules/routing/protocol/pickup/v1/messages/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from './BatchMessage' -export * from './BatchPickupMessage' diff --git a/packages/core/src/modules/routing/protocol/pickup/v2/V2MessagePickupService.ts b/packages/core/src/modules/routing/protocol/pickup/v2/V2MessagePickupService.ts deleted file mode 100644 index 807718eba3..0000000000 --- a/packages/core/src/modules/routing/protocol/pickup/v2/V2MessagePickupService.ts +++ /dev/null @@ -1,126 +0,0 @@ -import type { InboundMessageContext } from '../../../../../agent/models/InboundMessageContext' -import type { EncryptedMessage } from '../../../../../didcomm/types' -import type { DeliveryRequestMessage, MessagesReceivedMessage, StatusRequestMessage } from './messages' - -import { Dispatcher } from '../../../../../agent/Dispatcher' -import { OutboundMessageContext } from '../../../../../agent/models' -import { InjectionSymbols } from '../../../../../constants' -import { V1Attachment } from '../../../../../decorators/attachment/V1Attachment' -import { AriesFrameworkError } from '../../../../../error' -import { inject, injectable } from '../../../../../plugins' -import { MessageRepository } from '../../../../../storage/MessageRepository' -import { MediationRecipientService } from '../../../services' - -import { - DeliveryRequestHandler, - MessageDeliveryHandler, - MessagesReceivedHandler, - StatusHandler, - StatusRequestHandler, -} from './handlers' -import { MessageDeliveryMessage, StatusMessage } from './messages' - -@injectable() -export class V2MessagePickupService { - private messageRepository: MessageRepository - private dispatcher: Dispatcher - private mediationRecipientService: MediationRecipientService - - public constructor( - @inject(InjectionSymbols.MessageRepository) messageRepository: MessageRepository, - dispatcher: Dispatcher, - mediationRecipientService: MediationRecipientService - ) { - this.messageRepository = messageRepository - this.dispatcher = dispatcher - this.mediationRecipientService = mediationRecipientService - - this.registerMessageHandlers() - } - - public async processStatusRequest(messageContext: InboundMessageContext) { - // Assert ready connection - const connection = messageContext.assertReadyConnection() - - if (messageContext.message.recipientKey) { - throw new AriesFrameworkError('recipient_key parameter not supported') - } - - const statusMessage = new StatusMessage({ - threadId: messageContext.message.threadId, - messageCount: await this.messageRepository.getAvailableMessageCount(connection.id), - }) - - return new OutboundMessageContext(statusMessage, { agentContext: messageContext.agentContext, connection }) - } - - public async queueMessage(connectionId: string, message: EncryptedMessage) { - await this.messageRepository.add(connectionId, message) - } - - public async processDeliveryRequest(messageContext: InboundMessageContext) { - // Assert ready connection - const connection = messageContext.assertReadyConnection() - - if (messageContext.message.recipientKey) { - throw new AriesFrameworkError('recipient_key parameter not supported') - } - - const { message } = messageContext - - // Get available messages from queue, but don't delete them - const messages = await this.messageRepository.takeFromQueue(connection.id, message.limit, true) - - // TODO: each message should be stored with an id. to be able to conform to the id property - // of delivery message - const attachments = messages.map( - (msg) => - new V1Attachment({ - data: { - json: msg, - }, - }) - ) - - const outboundMessageContext = - messages.length > 0 - ? new MessageDeliveryMessage({ - threadId: messageContext.message.threadId, - attachments, - }) - : new StatusMessage({ - threadId: messageContext.message.threadId, - messageCount: 0, - }) - - return new OutboundMessageContext(outboundMessageContext, { agentContext: messageContext.agentContext, connection }) - } - - public async processMessagesReceived(messageContext: InboundMessageContext) { - // Assert ready connection - const connection = messageContext.assertReadyConnection() - - const { message } = messageContext - - // TODO: Add Queued Message ID - await this.messageRepository.takeFromQueue( - connection.id, - message.messageIdList ? message.messageIdList.length : undefined - ) - - const statusMessage = new StatusMessage({ - threadId: messageContext.message.threadId, - messageCount: await this.messageRepository.getAvailableMessageCount(connection.id), - }) - - return new OutboundMessageContext(statusMessage, { agentContext: messageContext.agentContext, connection }) - } - - protected registerMessageHandlers() { - this.dispatcher.registerMessageHandler(new StatusRequestHandler(this)) - this.dispatcher.registerMessageHandler(new DeliveryRequestHandler(this)) - this.dispatcher.registerMessageHandler(new MessagesReceivedHandler(this)) - this.dispatcher.registerMessageHandler(new StatusHandler(this.mediationRecipientService)) - this.dispatcher.registerMessageHandler(new MessageDeliveryHandler(this.mediationRecipientService)) - } -} diff --git a/packages/core/src/modules/routing/protocol/pickup/v2/handlers/DeliveryRequestHandler.ts b/packages/core/src/modules/routing/protocol/pickup/v2/handlers/DeliveryRequestHandler.ts deleted file mode 100644 index 78334b5448..0000000000 --- a/packages/core/src/modules/routing/protocol/pickup/v2/handlers/DeliveryRequestHandler.ts +++ /dev/null @@ -1,19 +0,0 @@ -import type { MessageHandler } from '../../../../../../agent/MessageHandler' -import type { InboundMessageContext } from '../../../../../../agent/models/InboundMessageContext' -import type { V2MessagePickupService } from '../V2MessagePickupService' - -import { DeliveryRequestMessage } from '../messages' - -export class DeliveryRequestHandler implements MessageHandler { - public supportedMessages = [DeliveryRequestMessage] - private messagePickupService: V2MessagePickupService - - public constructor(messagePickupService: V2MessagePickupService) { - this.messagePickupService = messagePickupService - } - - public async handle(messageContext: InboundMessageContext) { - messageContext.assertReadyConnection() - return this.messagePickupService.processDeliveryRequest(messageContext) - } -} diff --git a/packages/core/src/modules/routing/protocol/pickup/v2/handlers/MessageDeliveryHandler.ts b/packages/core/src/modules/routing/protocol/pickup/v2/handlers/MessageDeliveryHandler.ts deleted file mode 100644 index 606647edb9..0000000000 --- a/packages/core/src/modules/routing/protocol/pickup/v2/handlers/MessageDeliveryHandler.ts +++ /dev/null @@ -1,27 +0,0 @@ -import type { MessageHandler } from '../../../../../../agent/MessageHandler' -import type { InboundMessageContext } from '../../../../../../agent/models/InboundMessageContext' -import type { MediationRecipientService } from '../../../../services' - -import { OutboundMessageContext } from '../../../../../../agent/models' -import { MessageDeliveryMessage } from '../messages/MessageDeliveryMessage' - -export class MessageDeliveryHandler implements MessageHandler { - public supportedMessages = [MessageDeliveryMessage] - private mediationRecipientService: MediationRecipientService - - public constructor(mediationRecipientService: MediationRecipientService) { - this.mediationRecipientService = mediationRecipientService - } - - public async handle(messageContext: InboundMessageContext) { - const connection = messageContext.assertReadyConnection() - const deliveryReceivedMessage = await this.mediationRecipientService.processDelivery(messageContext) - - if (deliveryReceivedMessage) { - return new OutboundMessageContext(deliveryReceivedMessage, { - agentContext: messageContext.agentContext, - connection, - }) - } - } -} diff --git a/packages/core/src/modules/routing/protocol/pickup/v2/handlers/MessagesReceivedHandler.ts b/packages/core/src/modules/routing/protocol/pickup/v2/handlers/MessagesReceivedHandler.ts deleted file mode 100644 index 7ddf4b6d75..0000000000 --- a/packages/core/src/modules/routing/protocol/pickup/v2/handlers/MessagesReceivedHandler.ts +++ /dev/null @@ -1,19 +0,0 @@ -import type { MessageHandler } from '../../../../../../agent/MessageHandler' -import type { InboundMessageContext } from '../../../../../../agent/models/InboundMessageContext' -import type { V2MessagePickupService } from '../V2MessagePickupService' - -import { MessagesReceivedMessage } from '../messages' - -export class MessagesReceivedHandler implements MessageHandler { - public supportedMessages = [MessagesReceivedMessage] - private messagePickupService: V2MessagePickupService - - public constructor(messagePickupService: V2MessagePickupService) { - this.messagePickupService = messagePickupService - } - - public async handle(messageContext: InboundMessageContext) { - messageContext.assertReadyConnection() - return this.messagePickupService.processMessagesReceived(messageContext) - } -} diff --git a/packages/core/src/modules/routing/protocol/pickup/v2/handlers/StatusHandler.ts b/packages/core/src/modules/routing/protocol/pickup/v2/handlers/StatusHandler.ts deleted file mode 100644 index 7fedcc381f..0000000000 --- a/packages/core/src/modules/routing/protocol/pickup/v2/handlers/StatusHandler.ts +++ /dev/null @@ -1,27 +0,0 @@ -import type { MessageHandler } from '../../../../../../agent/MessageHandler' -import type { InboundMessageContext } from '../../../../../../agent/models/InboundMessageContext' -import type { MediationRecipientService } from '../../../../services' - -import { OutboundMessageContext } from '../../../../../../agent/models' -import { StatusMessage } from '../messages' - -export class StatusHandler implements MessageHandler { - public supportedMessages = [StatusMessage] - private mediatorRecipientService: MediationRecipientService - - public constructor(mediatorRecipientService: MediationRecipientService) { - this.mediatorRecipientService = mediatorRecipientService - } - - public async handle(messageContext: InboundMessageContext) { - const connection = messageContext.assertReadyConnection() - const deliveryRequestMessage = await this.mediatorRecipientService.processStatus(messageContext) - - if (deliveryRequestMessage) { - return new OutboundMessageContext(deliveryRequestMessage, { - agentContext: messageContext.agentContext, - connection, - }) - } - } -} diff --git a/packages/core/src/modules/routing/protocol/pickup/v2/handlers/StatusRequestHandler.ts b/packages/core/src/modules/routing/protocol/pickup/v2/handlers/StatusRequestHandler.ts deleted file mode 100644 index 1e7c67ae1d..0000000000 --- a/packages/core/src/modules/routing/protocol/pickup/v2/handlers/StatusRequestHandler.ts +++ /dev/null @@ -1,19 +0,0 @@ -import type { MessageHandler } from '../../../../../../agent/MessageHandler' -import type { InboundMessageContext } from '../../../../../../agent/models/InboundMessageContext' -import type { V2MessagePickupService } from '../V2MessagePickupService' - -import { StatusRequestMessage } from '../messages' - -export class StatusRequestHandler implements MessageHandler { - public supportedMessages = [StatusRequestMessage] - private messagePickupService: V2MessagePickupService - - public constructor(messagePickupService: V2MessagePickupService) { - this.messagePickupService = messagePickupService - } - - public async handle(messageContext: InboundMessageContext) { - messageContext.assertReadyConnection() - return this.messagePickupService.processStatusRequest(messageContext) - } -} diff --git a/packages/core/src/modules/routing/protocol/pickup/v2/handlers/index.ts b/packages/core/src/modules/routing/protocol/pickup/v2/handlers/index.ts deleted file mode 100644 index c8f4456634..0000000000 --- a/packages/core/src/modules/routing/protocol/pickup/v2/handlers/index.ts +++ /dev/null @@ -1,5 +0,0 @@ -export * from './DeliveryRequestHandler' -export * from './MessageDeliveryHandler' -export * from './MessagesReceivedHandler' -export * from './StatusHandler' -export * from './StatusRequestHandler' diff --git a/packages/core/src/modules/routing/protocol/pickup/v2/index.ts b/packages/core/src/modules/routing/protocol/pickup/v2/index.ts deleted file mode 100644 index b6a5eb72c5..0000000000 --- a/packages/core/src/modules/routing/protocol/pickup/v2/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from './V2MessagePickupService' -export * from './messages' diff --git a/packages/core/src/modules/routing/protocol/pickup/v2/messages/index.ts b/packages/core/src/modules/routing/protocol/pickup/v2/messages/index.ts deleted file mode 100644 index fa807e7249..0000000000 --- a/packages/core/src/modules/routing/protocol/pickup/v2/messages/index.ts +++ /dev/null @@ -1,5 +0,0 @@ -export * from './DeliveryRequestMessage' -export * from './MessageDeliveryMessage' -export * from './MessagesReceivedMessage' -export * from './StatusMessage' -export * from './StatusRequestMessage' diff --git a/packages/core/src/modules/routing/services/MediationRecipientService.ts b/packages/core/src/modules/routing/services/MediationRecipientService.ts index 3311591d3f..e776438007 100644 --- a/packages/core/src/modules/routing/services/MediationRecipientService.ts +++ b/packages/core/src/modules/routing/services/MediationRecipientService.ts @@ -1,21 +1,18 @@ +import type { GetRoutingOptions, RemoveRoutingOptions } from './RoutingService' import type { AgentContext } from '../../../agent' -import type { AgentMessageReceivedEvent } from '../../../agent/Events' import type { InboundMessageContext } from '../../../agent/models/InboundMessageContext' -import type { EncryptedMessage } from '../../../didcomm/types' import type { DidCommV1Message } from '../../../didcomm/versions/v1' import type { Query } from '../../../storage/StorageService' import type { ConnectionRecord } from '../../connections' import type { Routing } from '../../connections/services/ConnectionService' import type { MediationStateChangedEvent, KeylistUpdatedEvent } from '../RoutingEvents' import type { MediationDenyMessage } from '../messages' -import type { StatusMessage, MessageDeliveryMessage } from '../protocol' -import type { GetRoutingOptions, RemoveRoutingOptions } from './RoutingService' import { firstValueFrom, ReplaySubject } from 'rxjs' import { filter, first, timeout } from 'rxjs/operators' import { EventEmitter } from '../../../agent/EventEmitter' -import { filterContextCorrelationId, AgentEventTypes } from '../../../agent/Events' +import { filterContextCorrelationId } from '../../../agent/Events' import { MessageSender } from '../../../agent/MessageSender' import { OutboundMessageContext } from '../../../agent/models' import { Key, KeyType } from '../../../crypto' @@ -27,10 +24,7 @@ import { ConnectionMetadataKeys } from '../../connections/repository/ConnectionM import { ConnectionService } from '../../connections/services/ConnectionService' import { DidKey } from '../../dids' import { didKeyToVerkey, isDidKey } from '../../dids/helpers' -import { ProblemReportError } from '../../problem-reports' -import { RecipientModuleConfig } from '../RecipientModuleConfig' import { RoutingEventTypes } from '../RoutingEvents' -import { RoutingProblemReportReason } from '../error' import { KeylistUpdateAction, KeylistUpdateResponseMessage, @@ -39,7 +33,6 @@ import { } from '../messages' import { KeylistUpdate, KeylistUpdateMessage } from '../messages/KeylistUpdateMessage' import { MediationRole, MediationState } from '../models' -import { DeliveryRequestMessage, MessagesReceivedMessage, StatusRequestMessage } from '../protocol/pickup/v2/messages' import { MediationRecord } from '../repository/MediationRecord' import { MediationRepository } from '../repository/MediationRepository' @@ -49,37 +42,17 @@ export class MediationRecipientService { private eventEmitter: EventEmitter private connectionService: ConnectionService private messageSender: MessageSender - private recipientModuleConfig: RecipientModuleConfig public constructor( connectionService: ConnectionService, messageSender: MessageSender, mediatorRepository: MediationRepository, - eventEmitter: EventEmitter, - recipientModuleConfig: RecipientModuleConfig + eventEmitter: EventEmitter ) { this.mediationRepository = mediatorRepository this.eventEmitter = eventEmitter this.connectionService = connectionService this.messageSender = messageSender - this.recipientModuleConfig = recipientModuleConfig - } - - public async createStatusRequest( - mediationRecord: MediationRecord, - config: { - recipientKey?: string - } = {} - ) { - mediationRecord.assertRole(MediationRole.Recipient) - mediationRecord.assertReady() - - const { recipientKey } = config - const statusRequest = new StatusRequestMessage({ - recipientKey, - }) - - return statusRequest } public async createRequest( @@ -308,81 +281,6 @@ export class MediationRecipientService { return mediationRecord } - public async processStatus(messageContext: InboundMessageContext) { - const connection = messageContext.assertReadyConnection() - const { message: statusMessage } = messageContext - const { messageCount, recipientKey } = statusMessage - - //No messages to be sent - if (messageCount === 0) { - const { message, connectionRecord } = await this.connectionService.createTrustPing( - messageContext.agentContext, - connection, - { - responseRequested: false, - } - ) - const websocketSchemes = ['ws', 'wss'] - - await this.messageSender.sendMessage( - new OutboundMessageContext(message, { - agentContext: messageContext.agentContext, - connection: connectionRecord, - }), - { - transportPriority: { - schemes: websocketSchemes, - restrictive: true, - // TODO: add keepAlive: true to enforce through the public api - // we need to keep the socket alive. It already works this way, but would - // be good to make more explicit from the public facing API. - // This would also make it easier to change the internal API later on. - // keepAlive: true, - }, - } - ) - - return null - } - const { maximumMessagePickup } = this.recipientModuleConfig - const limit = messageCount < maximumMessagePickup ? messageCount : maximumMessagePickup - - const deliveryRequestMessage = new DeliveryRequestMessage({ - limit, - recipientKey, - }) - - return deliveryRequestMessage - } - - public async processDelivery(messageContext: InboundMessageContext) { - messageContext.assertReadyConnection() - - const { appendedAttachments } = messageContext.message - - if (!appendedAttachments) - throw new ProblemReportError('Error processing attachments', { - problemCode: RoutingProblemReportReason.ErrorProcessingAttachments, - }) - - const ids: string[] = [] - for (const attachment of appendedAttachments) { - ids.push(attachment.id) - - this.eventEmitter.emit(messageContext.agentContext, { - type: AgentEventTypes.AgentMessageReceived, - payload: { - message: attachment.getDataAsJson(), - contextCorrelationId: messageContext.agentContext.contextCorrelationId, - }, - }) - } - - return new MessagesReceivedMessage({ - messageIdList: ids, - }) - } - /** * Update the record to a new state and emit an state changed event. Also updates the record * in storage. diff --git a/packages/core/src/modules/routing/services/MediatorService.ts b/packages/core/src/modules/routing/services/MediatorService.ts index 29ec9be980..3cce8e302e 100644 --- a/packages/core/src/modules/routing/services/MediatorService.ts +++ b/packages/core/src/modules/routing/services/MediatorService.ts @@ -212,6 +212,17 @@ export class MediatorService { await this.mediatorRoutingRepository.save(agentContext, routingRecord) + this.eventEmitter.emit(agentContext, { + type: RoutingEventTypes.RoutingCreatedEvent, + payload: { + routing: { + endpoints: agentContext.config.endpoints, + routingKeys: [], + recipientKey: routingKey, + }, + }, + }) + return routingRecord } diff --git a/packages/core/src/modules/routing/services/__tests__/MediationRecipientService.test.ts b/packages/core/src/modules/routing/services/__tests__/MediationRecipientService.test.ts index d9034ccac5..a5ac4eff54 100644 --- a/packages/core/src/modules/routing/services/__tests__/MediationRecipientService.test.ts +++ b/packages/core/src/modules/routing/services/__tests__/MediationRecipientService.test.ts @@ -1,25 +1,18 @@ import type { AgentContext } from '../../../../agent' -import type { Wallet } from '../../../../wallet/Wallet' import type { Routing } from '../../../connections/services/ConnectionService' import { getAgentConfig, getAgentContext, getMockConnection, mockFunction } from '../../../../../tests/helpers' import { EventEmitter } from '../../../../agent/EventEmitter' -import { AgentEventTypes } from '../../../../agent/Events' import { MessageSender } from '../../../../agent/MessageSender' import { InboundMessageContext } from '../../../../agent/models/InboundMessageContext' import { Key } from '../../../../crypto' -import { KeyProviderRegistry } from '../../../../crypto/key-provider' -import { V1Attachment } from '../../../../decorators/attachment/V1Attachment' -import { AriesFrameworkError } from '../../../../error' import { uuid } from '../../../../utils/uuid' -import { IndyWallet } from '../../../../wallet/IndyWallet' import { DidExchangeState } from '../../../connections' import { ConnectionMetadataKeys } from '../../../connections/repository/ConnectionMetadataTypes' import { ConnectionRepository } from '../../../connections/repository/ConnectionRepository' import { ConnectionService } from '../../../connections/services/ConnectionService' import { DidRepository } from '../../../dids/repository/DidRepository' import { DidRegistrarService } from '../../../dids/services/DidRegistrarService' -import { RecipientModuleConfig } from '../../RecipientModuleConfig' import { RoutingEventTypes } from '../../RoutingEvents' import { KeylistUpdateAction, @@ -28,7 +21,6 @@ import { MediationGrantMessage, } from '../../messages' import { MediationRole, MediationState } from '../../models' -import { DeliveryRequestMessage, MessageDeliveryMessage, MessagesReceivedMessage, StatusMessage } from '../../protocol' import { MediationRecord } from '../../repository/MediationRecord' import { MediationRepository } from '../../repository/MediationRepository' import { MediationRecipientService } from '../MediationRecipientService' @@ -53,17 +45,12 @@ const DidRegistrarServiceMock = DidRegistrarService as jest.Mock { const config = getAgentConfig('MediationRecipientServiceTest', { endpoints: ['http://agent.com:8080'], connectionImageUrl, }) - let wallet: Wallet let mediationRepository: MediationRepository let didRepository: DidRepository let didRegistrarService: DidRegistrarService @@ -76,16 +63,9 @@ describe('MediationRecipientService', () => { let agentContext: AgentContext beforeAll(async () => { - wallet = new IndyWallet(config.agentDependencies, config.logger, new KeyProviderRegistry([])) agentContext = getAgentContext({ agentConfig: config, }) - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - await wallet.createAndOpen(config.walletConfig!) - }) - - afterAll(async () => { - await wallet.delete() }) beforeEach(async () => { @@ -116,8 +96,7 @@ describe('MediationRecipientService', () => { connectionService, messageSender, mediationRepository, - eventEmitter, - new RecipientModuleConfig() + eventEmitter ) }) @@ -167,33 +146,6 @@ describe('MediationRecipientService', () => { }) }) - describe('createStatusRequest', () => { - it('creates a status request message', async () => { - const statusRequestMessage = await mediationRecipientService.createStatusRequest(mediationRecord, { - recipientKey: 'a-key', - }) - - expect(statusRequestMessage).toMatchObject({ - id: expect.any(String), - recipientKey: 'a-key', - }) - }) - - it('it throws an error when the mediation record has incorrect role or state', async () => { - mediationRecord.role = MediationRole.Mediator - await expect(mediationRecipientService.createStatusRequest(mediationRecord)).rejects.toThrowError( - 'Mediation record has invalid role MEDIATOR. Expected role RECIPIENT.' - ) - - mediationRecord.role = MediationRole.Recipient - mediationRecord.state = MediationState.Requested - - await expect(mediationRecipientService.createStatusRequest(mediationRecord)).rejects.toThrowError( - 'Mediation record is not ready to be used. Expected granted, found invalid state requested' - ) - }) - }) - describe('processKeylistUpdateResults', () => { it('it stores did:key-encoded keys in base58 format', async () => { const spyAddRecipientKey = jest.spyOn(mediationRecord, 'addRecipientKey') @@ -237,119 +189,6 @@ describe('MediationRecipientService', () => { }) }) - describe('processStatus', () => { - it('if status request has a message count of zero returns nothing', async () => { - const status = new StatusMessage({ - threadId: uuid(), - messageCount: 0, - }) - - const messageContext = new InboundMessageContext(status, { connection: mockConnection, agentContext }) - const deliveryRequestMessage = await mediationRecipientService.processStatus(messageContext) - expect(deliveryRequestMessage).toBeNull() - }) - - it('if it has a message count greater than zero return a valid delivery request', async () => { - const status = new StatusMessage({ - threadId: uuid(), - messageCount: 1, - }) - const messageContext = new InboundMessageContext(status, { connection: mockConnection, agentContext }) - - const deliveryRequestMessage = await mediationRecipientService.processStatus(messageContext) - expect(deliveryRequestMessage) - expect(deliveryRequestMessage).toEqual(new DeliveryRequestMessage({ id: deliveryRequestMessage?.id, limit: 1 })) - }) - }) - - describe('processDelivery', () => { - it('if the delivery has no attachments expect an error', async () => { - const messageContext = new InboundMessageContext({} as MessageDeliveryMessage, { - connection: mockConnection, - agentContext, - }) - - await expect(mediationRecipientService.processDelivery(messageContext)).rejects.toThrowError( - new AriesFrameworkError('Error processing attachments') - ) - }) - - it('should return a message received with an message id list in it', async () => { - const messageDeliveryMessage = new MessageDeliveryMessage({ - threadId: uuid(), - attachments: [ - new V1Attachment({ - id: '1', - data: { - json: { - a: 'value', - }, - }, - }), - ], - }) - const messageContext = new InboundMessageContext(messageDeliveryMessage, { - connection: mockConnection, - agentContext, - }) - - const messagesReceivedMessage = await mediationRecipientService.processDelivery(messageContext) - - expect(messagesReceivedMessage).toEqual( - new MessagesReceivedMessage({ - id: messagesReceivedMessage.id, - messageIdList: ['1'], - }) - ) - }) - - it('calls the event emitter for each message', async () => { - const messageDeliveryMessage = new MessageDeliveryMessage({ - threadId: uuid(), - attachments: [ - new V1Attachment({ - id: '1', - data: { - json: { - first: 'value', - }, - }, - }), - new V1Attachment({ - id: '2', - data: { - json: { - second: 'value', - }, - }, - }), - ], - }) - const messageContext = new InboundMessageContext(messageDeliveryMessage, { - connection: mockConnection, - agentContext, - }) - - await mediationRecipientService.processDelivery(messageContext) - - expect(eventEmitter.emit).toHaveBeenCalledTimes(2) - expect(eventEmitter.emit).toHaveBeenNthCalledWith(1, agentContext, { - type: AgentEventTypes.AgentMessageReceived, - payload: { - message: { first: 'value' }, - contextCorrelationId: agentContext.contextCorrelationId, - }, - }) - expect(eventEmitter.emit).toHaveBeenNthCalledWith(2, agentContext, { - type: AgentEventTypes.AgentMessageReceived, - payload: { - message: { second: 'value' }, - contextCorrelationId: agentContext.contextCorrelationId, - }, - }) - }) - }) - describe('addMediationRouting', () => { const routingKey = Key.fromFingerprint('z6Mkk7yqnGF3YwTrLpqrW6PGsKci7dNqh1CjnvMbzrMerSeL') const recipientKey = Key.fromFingerprint('z6MkmjY8GnV5i9YTDtPETC2uUAW6ejw3nk5mXF5yci5ab7th') diff --git a/packages/core/src/modules/routing/services/__tests__/RoutingService.test.ts b/packages/core/src/modules/routing/services/__tests__/RoutingService.test.ts index de6763884f..c3377b070f 100644 --- a/packages/core/src/modules/routing/services/__tests__/RoutingService.test.ts +++ b/packages/core/src/modules/routing/services/__tests__/RoutingService.test.ts @@ -1,40 +1,39 @@ +import type { Wallet } from '../../../../wallet' + import { Subject } from 'rxjs' import { getAgentConfig, getAgentContext, mockFunction } from '../../../../../tests/helpers' import { EventEmitter } from '../../../../agent/EventEmitter' import { Key } from '../../../../crypto' -import { IndyWallet } from '../../../../wallet/IndyWallet' import { RoutingEventTypes } from '../../RoutingEvents' import { MediationRecipientService } from '../MediationRecipientService' import { RoutingService } from '../RoutingService' -jest.mock('../../../../wallet/IndyWallet') -const IndyWalletMock = IndyWallet as jest.Mock - jest.mock('../MediationRecipientService') const MediationRecipientServiceMock = MediationRecipientService as jest.Mock +const recipientKey = Key.fromFingerprint('z6Mkk7yqnGF3YwTrLpqrW6PGsKci7dNqh1CjnvMbzrMerSeL') const agentConfig = getAgentConfig('RoutingService', { endpoints: ['http://endpoint.com'], }) const eventEmitter = new EventEmitter(agentConfig.agentDependencies, new Subject()) -const wallet = new IndyWalletMock() +const wallet = { + createKey: jest.fn().mockResolvedValue(recipientKey), + // with satisfies Partial we still get type errors when the interface changes +} const agentContext = getAgentContext({ - wallet, + wallet: wallet as unknown as Wallet, agentConfig, }) const mediationRecipientService = new MediationRecipientServiceMock() const routingService = new RoutingService(mediationRecipientService, eventEmitter) -const recipientKey = Key.fromFingerprint('z6Mkk7yqnGF3YwTrLpqrW6PGsKci7dNqh1CjnvMbzrMerSeL') - const routing = { endpoints: ['http://endpoint.com'], recipientKey, routingKeys: [], } mockFunction(mediationRecipientService.addMediationRouting).mockResolvedValue(routing) -mockFunction(wallet.createKey).mockResolvedValue(recipientKey) describe('RoutingService', () => { afterEach(() => { diff --git a/packages/core/src/modules/routing/services/__tests__/V2MessagePickupService.test.ts b/packages/core/src/modules/routing/services/__tests__/V2MessagePickupService.test.ts deleted file mode 100644 index b4130c2a9e..0000000000 --- a/packages/core/src/modules/routing/services/__tests__/V2MessagePickupService.test.ts +++ /dev/null @@ -1,252 +0,0 @@ -import type { EncryptedMessage } from '../../../../didcomm/types' -import type { MessageRepository } from '../../../../storage/MessageRepository' - -import { getAgentContext, getMockConnection, mockFunction } from '../../../../../tests/helpers' -import { Dispatcher } from '../../../../agent/Dispatcher' -import { InboundMessageContext } from '../../../../agent/models/InboundMessageContext' -import { InMemoryMessageRepository } from '../../../../storage/InMemoryMessageRepository' -import { DidExchangeState } from '../../../connections' -import { - DeliveryRequestMessage, - MessageDeliveryMessage, - MessagesReceivedMessage, - StatusMessage, - StatusRequestMessage, - V2MessagePickupService, -} from '../../protocol' -import { MediationRecipientService } from '../MediationRecipientService' - -const mockConnection = getMockConnection({ - state: DidExchangeState.Completed, -}) - -// Mock classes -jest.mock('../MediationRecipientService') -jest.mock('../../../../storage/InMemoryMessageRepository') -jest.mock('../../../../agent/Dispatcher') - -// Mock typed object -const MediationRecipientServiceMock = MediationRecipientService as jest.Mock -const DispatcherMock = Dispatcher as jest.Mock -const InMessageRepositoryMock = InMemoryMessageRepository as jest.Mock - -const agentContext = getAgentContext() - -const encryptedMessage: EncryptedMessage = { - recipients: [], - protected: 'base64url', - iv: 'base64url', - ciphertext: 'base64url', - tag: 'base64url', -} -const queuedMessages = [encryptedMessage, encryptedMessage, encryptedMessage] - -describe('V2MessagePickupService', () => { - let pickupService: V2MessagePickupService - let messageRepository: MessageRepository - - beforeEach(async () => { - const dispatcher = new DispatcherMock() - const mediationRecipientService = new MediationRecipientServiceMock() - - messageRepository = new InMessageRepositoryMock() - pickupService = new V2MessagePickupService(messageRepository, dispatcher, mediationRecipientService) - }) - - describe('processStatusRequest', () => { - test('no available messages in queue', async () => { - mockFunction(messageRepository.getAvailableMessageCount).mockResolvedValue(0) - - const statusRequest = new StatusRequestMessage({}) - - const messageContext = new InboundMessageContext(statusRequest, { connection: mockConnection, agentContext }) - - const { connection, message } = await pickupService.processStatusRequest(messageContext) - - expect(connection).toEqual(mockConnection) - expect(message).toEqual( - new StatusMessage({ - id: message.id, - threadId: statusRequest.threadId, - messageCount: 0, - }) - ) - expect(messageRepository.getAvailableMessageCount).toHaveBeenCalledWith(mockConnection.id) - }) - - test('multiple messages in queue', async () => { - mockFunction(messageRepository.getAvailableMessageCount).mockResolvedValue(5) - const statusRequest = new StatusRequestMessage({}) - - const messageContext = new InboundMessageContext(statusRequest, { connection: mockConnection, agentContext }) - - const { connection, message } = await pickupService.processStatusRequest(messageContext) - - expect(connection).toEqual(mockConnection) - expect(message).toEqual( - new StatusMessage({ - id: message.id, - threadId: statusRequest.threadId, - messageCount: 5, - }) - ) - expect(messageRepository.getAvailableMessageCount).toHaveBeenCalledWith(mockConnection.id) - }) - - test('status request specifying recipient key', async () => { - mockFunction(messageRepository.getAvailableMessageCount).mockResolvedValue(10) - - const statusRequest = new StatusRequestMessage({ - recipientKey: 'recipientKey', - }) - - const messageContext = new InboundMessageContext(statusRequest, { connection: mockConnection, agentContext }) - - await expect(pickupService.processStatusRequest(messageContext)).rejects.toThrowError( - 'recipient_key parameter not supported' - ) - }) - }) - - describe('processDeliveryRequest', () => { - test('no available messages in queue', async () => { - mockFunction(messageRepository.takeFromQueue).mockResolvedValue([]) - - const deliveryRequest = new DeliveryRequestMessage({ limit: 10 }) - - const messageContext = new InboundMessageContext(deliveryRequest, { connection: mockConnection, agentContext }) - - const { connection, message } = await pickupService.processDeliveryRequest(messageContext) - - expect(connection).toEqual(mockConnection) - expect(message).toEqual( - new StatusMessage({ - id: message.id, - threadId: deliveryRequest.threadId, - messageCount: 0, - }) - ) - expect(messageRepository.takeFromQueue).toHaveBeenCalledWith(mockConnection.id, 10, true) - }) - - test('less messages in queue than limit', async () => { - mockFunction(messageRepository.takeFromQueue).mockResolvedValue(queuedMessages) - - const deliveryRequest = new DeliveryRequestMessage({ limit: 10 }) - - const messageContext = new InboundMessageContext(deliveryRequest, { connection: mockConnection, agentContext }) - - const { connection, message } = await pickupService.processDeliveryRequest(messageContext) - - expect(connection).toEqual(mockConnection) - expect(message).toBeInstanceOf(MessageDeliveryMessage) - expect(message.threadId).toEqual(deliveryRequest.threadId) - expect(message.appendedAttachments?.length).toEqual(3) - expect(message.appendedAttachments).toEqual( - expect.arrayContaining( - queuedMessages.map((msg) => - expect.objectContaining({ - data: { - json: msg, - }, - }) - ) - ) - ) - expect(messageRepository.takeFromQueue).toHaveBeenCalledWith(mockConnection.id, 10, true) - }) - - test('more messages in queue than limit', async () => { - mockFunction(messageRepository.takeFromQueue).mockResolvedValue(queuedMessages.slice(0, 2)) - - const deliveryRequest = new DeliveryRequestMessage({ limit: 2 }) - - const messageContext = new InboundMessageContext(deliveryRequest, { connection: mockConnection, agentContext }) - - const { connection, message } = await pickupService.processDeliveryRequest(messageContext) - - expect(connection).toEqual(mockConnection) - expect(message).toBeInstanceOf(MessageDeliveryMessage) - expect(message.threadId).toEqual(deliveryRequest.threadId) - expect(message.appendedAttachments?.length).toEqual(2) - expect(message.appendedAttachments).toEqual( - expect.arrayContaining( - queuedMessages.slice(0, 2).map((msg) => - expect.objectContaining({ - data: { - json: msg, - }, - }) - ) - ) - ) - expect(messageRepository.takeFromQueue).toHaveBeenCalledWith(mockConnection.id, 2, true) - }) - - test('delivery request specifying recipient key', async () => { - mockFunction(messageRepository.takeFromQueue).mockResolvedValue(queuedMessages) - - const statusRequest = new DeliveryRequestMessage({ - limit: 10, - recipientKey: 'recipientKey', - }) - - const messageContext = new InboundMessageContext(statusRequest, { connection: mockConnection, agentContext }) - - await expect(pickupService.processStatusRequest(messageContext)).rejects.toThrowError( - 'recipient_key parameter not supported' - ) - }) - }) - - describe('processMessagesReceived', () => { - test('messages received partially', async () => { - mockFunction(messageRepository.takeFromQueue).mockResolvedValue(queuedMessages) - mockFunction(messageRepository.getAvailableMessageCount).mockResolvedValue(4) - - const messagesReceived = new MessagesReceivedMessage({ - messageIdList: ['1', '2'], - }) - - const messageContext = new InboundMessageContext(messagesReceived, { connection: mockConnection, agentContext }) - - const { connection, message } = await pickupService.processMessagesReceived(messageContext) - - expect(connection).toEqual(mockConnection) - expect(message).toEqual( - new StatusMessage({ - id: message.id, - threadId: messagesReceived.threadId, - messageCount: 4, - }) - ) - expect(messageRepository.getAvailableMessageCount).toHaveBeenCalledWith(mockConnection.id) - expect(messageRepository.takeFromQueue).toHaveBeenCalledWith(mockConnection.id, 2) - }) - - test('all messages have been received', async () => { - mockFunction(messageRepository.takeFromQueue).mockResolvedValue(queuedMessages) - mockFunction(messageRepository.getAvailableMessageCount).mockResolvedValue(0) - - const messagesReceived = new MessagesReceivedMessage({ - messageIdList: ['1', '2'], - }) - - const messageContext = new InboundMessageContext(messagesReceived, { connection: mockConnection, agentContext }) - - const { connection, message } = await pickupService.processMessagesReceived(messageContext) - - expect(connection).toEqual(mockConnection) - expect(message).toEqual( - new StatusMessage({ - id: message.id, - threadId: messagesReceived.threadId, - messageCount: 0, - }) - ) - - expect(messageRepository.getAvailableMessageCount).toHaveBeenCalledWith(mockConnection.id) - expect(messageRepository.takeFromQueue).toHaveBeenCalledWith(mockConnection.id, 2) - }) - }) -}) diff --git a/packages/core/src/modules/vc/W3cCredentialService.ts b/packages/core/src/modules/vc/W3cCredentialService.ts index 46ac773401..214911d02c 100644 --- a/packages/core/src/modules/vc/W3cCredentialService.ts +++ b/packages/core/src/modules/vc/W3cCredentialService.ts @@ -1,6 +1,3 @@ -import type { AgentContext } from '../../agent/context' -import type { Key } from '../../crypto/Key' -import type { Query } from '../../storage/StorageService' import type { W3cVerifyCredentialResult } from './models' import type { CreatePresentationOptions, @@ -12,16 +9,19 @@ import type { VerifyPresentationOptions, } from './models/W3cCredentialServiceOptions' import type { VerifyPresentationResult } from './models/presentation/VerifyPresentationResult' +import type { AgentContext } from '../../agent/context' +import type { Key } from '../../crypto/Key' +import type { Query } from '../../storage/StorageService' import { createWalletKeyPairClass } from '../../crypto/WalletKeyPair' import { AriesFrameworkError } from '../../error' import { injectable } from '../../plugins' import { JsonTransformer } from '../../utils' import { VerificationMethod } from '../dids' -import { getKeyDidMappingByVerificationMethod } from '../dids/domain/key-type' +import { getKeyFromVerificationMethod } from '../dids/domain/key-type' import { SignatureSuiteRegistry } from './SignatureSuiteRegistry' -import { W3cVcModuleConfig } from './W3cVcModuleConfig' +import { W3cCredentialsModuleConfig } from './W3cCredentialsModuleConfig' import { deriveProof } from './deriveProof' import { orArrayToArray, w3cDate } from './jsonldUtil' import jsonld from './libraries/jsonld' @@ -35,16 +35,16 @@ import { W3cCredentialRecord, W3cCredentialRepository } from './repository' export class W3cCredentialService { private w3cCredentialRepository: W3cCredentialRepository private signatureSuiteRegistry: SignatureSuiteRegistry - private w3cVcModuleConfig: W3cVcModuleConfig + private w3cCredentialsModuleConfig: W3cCredentialsModuleConfig public constructor( w3cCredentialRepository: W3cCredentialRepository, signatureSuiteRegistry: SignatureSuiteRegistry, - w3cVcModuleConfig: W3cVcModuleConfig + w3cCredentialsModuleConfig: W3cCredentialsModuleConfig ) { this.w3cCredentialRepository = w3cCredentialRepository this.signatureSuiteRegistry = signatureSuiteRegistry - this.w3cVcModuleConfig = w3cVcModuleConfig + this.w3cCredentialsModuleConfig = w3cCredentialsModuleConfig } /** @@ -89,7 +89,7 @@ export class W3cCredentialService { credential: JsonTransformer.toJSON(options.credential), suite: suite, purpose: options.proofPurpose, - documentLoader: this.w3cVcModuleConfig.documentLoader(agentContext), + documentLoader: this.w3cCredentialsModuleConfig.documentLoader(agentContext), }) return JsonTransformer.fromJSON(result, W3cVerifiableCredential) @@ -105,12 +105,22 @@ export class W3cCredentialService { agentContext: AgentContext, options: VerifyCredentialOptions ): Promise { + const verifyRevocationState = options.verifyRevocationState ?? true + const suites = this.getSignatureSuitesForCredential(agentContext, options.credential) const verifyOptions: Record = { credential: JsonTransformer.toJSON(options.credential), suite: suites, - documentLoader: this.w3cVcModuleConfig.documentLoader(agentContext), + documentLoader: this.w3cCredentialsModuleConfig.documentLoader(agentContext), + checkStatus: () => { + if (verifyRevocationState) { + throw new AriesFrameworkError('Revocation for W3C credentials is currently not supported') + } + return { + verified: true, + } + }, } // this is a hack because vcjs throws if purpose is passed as undefined or null @@ -172,7 +182,7 @@ export class W3cCredentialService { throw new AriesFrameworkError('The key type of the verification method does not match the suite') } - const documentLoader = this.w3cVcModuleConfig.documentLoader(agentContext) + const documentLoader = this.w3cCredentialsModuleConfig.documentLoader(agentContext) const verificationMethodObject = (await documentLoader(options.verificationMethod)).document as Record< string, unknown @@ -199,7 +209,7 @@ export class W3cCredentialService { presentation: JsonTransformer.toJSON(options.presentation), suite: suite, challenge: options.challenge, - documentLoader: this.w3cVcModuleConfig.documentLoader(agentContext), + documentLoader: this.w3cCredentialsModuleConfig.documentLoader(agentContext), }) return JsonTransformer.fromJSON(result, W3cVerifiablePresentation) @@ -252,7 +262,7 @@ export class W3cCredentialService { presentation: JsonTransformer.toJSON(options.presentation), suite: allSuites, challenge: options.challenge, - documentLoader: this.w3cVcModuleConfig.documentLoader(agentContext), + documentLoader: this.w3cCredentialsModuleConfig.documentLoader(agentContext), } // this is a hack because vcjs throws if purpose is passed as undefined or null @@ -274,7 +284,7 @@ export class W3cCredentialService { const proof = await deriveProof(JsonTransformer.toJSON(options.credential), options.revealDocument, { suite: suite, - documentLoader: this.w3cVcModuleConfig.documentLoader(agentContext), + documentLoader: this.w3cCredentialsModuleConfig.documentLoader(agentContext), }) return proof @@ -284,13 +294,12 @@ export class W3cCredentialService { agentContext: AgentContext, verificationMethod: string ): Promise { - const documentLoader = this.w3cVcModuleConfig.documentLoader(agentContext) + const documentLoader = this.w3cCredentialsModuleConfig.documentLoader(agentContext) const verificationMethodObject = await documentLoader(verificationMethod) const verificationMethodClass = JsonTransformer.fromJSON(verificationMethodObject.document, VerificationMethod) - const key = getKeyDidMappingByVerificationMethod(verificationMethodClass) - - return key.getKeyFromVerificationMethod(verificationMethodClass) + const key = getKeyFromVerificationMethod(verificationMethodClass) + return key } /** @@ -306,7 +315,7 @@ export class W3cCredentialService { // Get the expanded types const expandedTypes = ( await jsonld.expand(JsonTransformer.toJSON(options.credential), { - documentLoader: this.w3cVcModuleConfig.documentLoader(agentContext), + documentLoader: this.w3cCredentialsModuleConfig.documentLoader(agentContext), }) )[0]['@type'] @@ -335,7 +344,7 @@ export class W3cCredentialService { return await this.w3cCredentialRepository.getById(agentContext, id) } - public async findCredentialRecordsByQuery( + public async findCredentialsByQuery( agentContext: AgentContext, query: Query ): Promise { @@ -358,6 +367,15 @@ export class W3cCredentialService { const result = await this.w3cCredentialRepository.findSingleByQuery(agentContext, query) return result?.credential } + public getProofTypeByVerificationMethodType(verificationMethodType: string): string { + const suite = this.signatureSuiteRegistry.getByVerificationMethodType(verificationMethodType) + + if (!suite) { + throw new AriesFrameworkError(`No suite found for verification method type ${verificationMethodType}}`) + } + + return suite.proofType + } private getSignatureSuitesForCredential(agentContext: AgentContext, credential: W3cVerifiableCredential) { const WalletKeyPair = createWalletKeyPairClass(agentContext.wallet) diff --git a/packages/core/src/modules/vc/W3cCredentialsApi.ts b/packages/core/src/modules/vc/W3cCredentialsApi.ts new file mode 100644 index 0000000000..8f18f2f459 --- /dev/null +++ b/packages/core/src/modules/vc/W3cCredentialsApi.ts @@ -0,0 +1,42 @@ +import type { W3cVerifiableCredential, StoreCredentialOptions } from './models' +import type { W3cCredentialRecord } from './repository' +import type { Query } from '../../storage/StorageService' + +import { AgentContext } from '../../agent' +import { injectable } from '../../plugins' + +import { W3cCredentialService } from './W3cCredentialService' + +/** + * @public + */ +@injectable() +export class W3cCredentialsApi { + private agentContext: AgentContext + private w3cCredentialService: W3cCredentialService + + public constructor(agentContext: AgentContext, w3cCredentialService: W3cCredentialService) { + this.agentContext = agentContext + this.w3cCredentialService = w3cCredentialService + } + + public async storeCredential(options: StoreCredentialOptions): Promise { + return this.w3cCredentialService.storeCredential(this.agentContext, options) + } + + public async removeCredentialRecord(id: string) { + return this.w3cCredentialService.removeCredentialRecord(this.agentContext, id) + } + + public async getAllCredentialRecords(): Promise { + return this.w3cCredentialService.getAllCredentialRecords(this.agentContext) + } + + public async getCredentialRecordById(id: string): Promise { + return this.w3cCredentialService.getCredentialRecordById(this.agentContext, id) + } + + public async findCredentialRecordsByQuery(query: Query): Promise { + return this.w3cCredentialService.findCredentialsByQuery(this.agentContext, query) + } +} diff --git a/packages/core/src/modules/vc/W3cVcModule.ts b/packages/core/src/modules/vc/W3cCredentialsModule.ts similarity index 65% rename from packages/core/src/modules/vc/W3cVcModule.ts rename to packages/core/src/modules/vc/W3cCredentialsModule.ts index 96231aa168..3c6886fdf4 100644 --- a/packages/core/src/modules/vc/W3cVcModule.ts +++ b/packages/core/src/modules/vc/W3cCredentialsModule.ts @@ -1,5 +1,5 @@ +import type { W3cCredentialsModuleConfigOptions } from './W3cCredentialsModuleConfig' import type { DependencyManager, Module } from '../../plugins' -import type { W3cVcModuleConfigOptions } from './W3cVcModuleConfig' import { KeyType } from '../../crypto' import { @@ -9,25 +9,31 @@ import { import { SignatureSuiteRegistry, SignatureSuiteToken } from './SignatureSuiteRegistry' import { W3cCredentialService } from './W3cCredentialService' -import { W3cVcModuleConfig } from './W3cVcModuleConfig' +import { W3cCredentialsApi } from './W3cCredentialsApi' +import { W3cCredentialsModuleConfig } from './W3cCredentialsModuleConfig' import { W3cCredentialRepository } from './repository/W3cCredentialRepository' import { Ed25519Signature2018 } from './signature-suites' -export class W3cVcModule implements Module { - public readonly config: W3cVcModuleConfig +/** + * @public + */ +export class W3cCredentialsModule implements Module { + public readonly config: W3cCredentialsModuleConfig + public readonly api = W3cCredentialsApi - public constructor(config?: W3cVcModuleConfigOptions) { - this.config = new W3cVcModuleConfig(config) + public constructor(config?: W3cCredentialsModuleConfigOptions) { + this.config = new W3cCredentialsModuleConfig(config) } public register(dependencyManager: DependencyManager) { + dependencyManager.registerContextScoped(W3cCredentialsApi) dependencyManager.registerSingleton(W3cCredentialService) dependencyManager.registerSingleton(W3cCredentialRepository) dependencyManager.registerSingleton(SignatureSuiteRegistry) // Register the config - dependencyManager.registerInstance(W3cVcModuleConfig, this.config) + dependencyManager.registerInstance(W3cCredentialsModuleConfig, this.config) // Always register ed25519 signature suite dependencyManager.registerInstance(SignatureSuiteToken, { diff --git a/packages/core/src/modules/vc/W3cVcModuleConfig.ts b/packages/core/src/modules/vc/W3cCredentialsModuleConfig.ts similarity index 71% rename from packages/core/src/modules/vc/W3cVcModuleConfig.ts rename to packages/core/src/modules/vc/W3cCredentialsModuleConfig.ts index 2c05a446a9..ef7c5e5947 100644 --- a/packages/core/src/modules/vc/W3cVcModuleConfig.ts +++ b/packages/core/src/modules/vc/W3cCredentialsModuleConfig.ts @@ -3,10 +3,10 @@ import type { DocumentLoaderWithContext } from './libraries/documentLoader' import { defaultDocumentLoader } from './libraries/documentLoader' /** - * W3cVcModuleConfigOptions defines the interface for the options of the W3cVcModuleConfig class. + * W3cCredentialsModuleConfigOptions defines the interface for the options of the W3cCredentialsModuleConfig class. * This can contain optional parameters that have default values in the config class itself. */ -export interface W3cVcModuleConfigOptions { +export interface W3cCredentialsModuleConfigOptions { /** * Document loader to use for resolving JSON-LD objects. Takes a {@link AgentContext} as parameter, * and must return a {@link DocumentLoader} function. @@ -32,14 +32,14 @@ export interface W3cVcModuleConfigOptions { documentLoader?: DocumentLoaderWithContext } -export class W3cVcModuleConfig { - private options: W3cVcModuleConfigOptions +export class W3cCredentialsModuleConfig { + private options: W3cCredentialsModuleConfigOptions - public constructor(options?: W3cVcModuleConfigOptions) { + public constructor(options?: W3cCredentialsModuleConfigOptions) { this.options = options ?? {} } - /** See {@link W3cVcModuleConfigOptions.documentLoader} */ + /** See {@link W3cCredentialsModuleConfigOptions.documentLoader} */ public get documentLoader() { return this.options.documentLoader ?? defaultDocumentLoader } diff --git a/packages/core/src/modules/vc/__tests__/W3cVcModule.test.ts b/packages/core/src/modules/vc/__tests__/W3CredentialsModule.test.ts similarity index 77% rename from packages/core/src/modules/vc/__tests__/W3cVcModule.test.ts rename to packages/core/src/modules/vc/__tests__/W3CredentialsModule.test.ts index 873c8274e6..b0cdc58451 100644 --- a/packages/core/src/modules/vc/__tests__/W3cVcModule.test.ts +++ b/packages/core/src/modules/vc/__tests__/W3CredentialsModule.test.ts @@ -2,8 +2,9 @@ import { KeyType } from '../../../crypto' import { DependencyManager } from '../../../plugins/DependencyManager' import { SignatureSuiteRegistry, SignatureSuiteToken } from '../SignatureSuiteRegistry' import { W3cCredentialService } from '../W3cCredentialService' -import { W3cVcModule } from '../W3cVcModule' -import { W3cVcModuleConfig } from '../W3cVcModuleConfig' +import { W3cCredentialsApi } from '../W3cCredentialsApi' +import { W3cCredentialsModule } from '../W3cCredentialsModule' +import { W3cCredentialsModuleConfig } from '../W3cCredentialsModuleConfig' import { W3cCredentialRepository } from '../repository' import { Ed25519Signature2018 } from '../signature-suites' @@ -12,19 +13,20 @@ const DependencyManagerMock = DependencyManager as jest.Mock const dependencyManager = new DependencyManagerMock() -describe('W3cVcModule', () => { +describe('W3cCredentialsModule', () => { test('registers dependencies on the dependency manager', () => { - const module = new W3cVcModule() + const module = new W3cCredentialsModule() module.register(dependencyManager) expect(dependencyManager.registerSingleton).toHaveBeenCalledTimes(3) + expect(dependencyManager.registerContextScoped).toHaveBeenCalledWith(W3cCredentialsApi) expect(dependencyManager.registerSingleton).toHaveBeenCalledWith(W3cCredentialService) expect(dependencyManager.registerSingleton).toHaveBeenCalledWith(W3cCredentialRepository) expect(dependencyManager.registerSingleton).toHaveBeenCalledWith(SignatureSuiteRegistry) expect(dependencyManager.registerInstance).toHaveBeenCalledTimes(2) - expect(dependencyManager.registerInstance).toHaveBeenCalledWith(W3cVcModuleConfig, module.config) + expect(dependencyManager.registerInstance).toHaveBeenCalledWith(W3cCredentialsModuleConfig, module.config) expect(dependencyManager.registerInstance).toHaveBeenCalledWith(SignatureSuiteToken, { suiteClass: Ed25519Signature2018, diff --git a/packages/core/src/modules/vc/__tests__/W3cCredentialService.test.ts b/packages/core/src/modules/vc/__tests__/W3cCredentialService.test.ts index 70042dfefc..69b1b5304e 100644 --- a/packages/core/src/modules/vc/__tests__/W3cCredentialService.test.ts +++ b/packages/core/src/modules/vc/__tests__/W3cCredentialService.test.ts @@ -1,10 +1,13 @@ import type { AgentContext } from '../../../agent' +import type { Wallet } from '../../../wallet' +import { IndySdkWallet } from '../../../../../indy-sdk/src' +import { indySdk } from '../../../../../indy-sdk/tests/setupIndySdkModule' import { getAgentConfig, getAgentContext, mockFunction } from '../../../../tests/helpers' import { KeyType } from '../../../crypto' import { KeyProviderRegistry } from '../../../crypto/key-provider' +import { TypedArrayEncoder } from '../../../utils' import { JsonTransformer } from '../../../utils/JsonTransformer' -import { IndyWallet } from '../../../wallet/IndyWallet' import { WalletError } from '../../../wallet/error' import { DidKey } from '../../dids' import { @@ -13,7 +16,7 @@ import { } from '../../dids/domain/key-type/ed25519' import { SignatureSuiteRegistry } from '../SignatureSuiteRegistry' import { W3cCredentialService } from '../W3cCredentialService' -import { W3cVcModuleConfig } from '../W3cVcModuleConfig' +import { W3cCredentialsModuleConfig } from '../W3cCredentialsModuleConfig' import { orArrayToArray } from '../jsonldUtil' import jsonld from '../libraries/jsonld' import { W3cCredential, W3cVerifiableCredential } from '../models' @@ -42,10 +45,8 @@ const signatureSuiteRegistry = new SignatureSuiteRegistry([ const keyProviderRegistry = new KeyProviderRegistry([]) -jest.mock('../../ledger/services/IndyLedgerService') - jest.mock('../repository/W3cCredentialRepository') -const W3cCredentialRepositoryMock = W3cCredentialRepository as jest.Mock +const W3cCredentialsRepositoryMock = W3cCredentialRepository as jest.Mock const agentConfig = getAgentConfig('W3cCredentialServiceTest') @@ -62,26 +63,25 @@ const credentialRecordFactory = async (credential: W3cVerifiableCredential) => { }) } -describe('W3cCredentialService', () => { - let wallet: IndyWallet +describe('W3cCredentialsService', () => { + let wallet: Wallet let agentContext: AgentContext - let w3cCredentialService: W3cCredentialService - let w3cCredentialRepository: W3cCredentialRepository - const seed = 'testseed000000000000000000000001' + let w3cCredentialsService: W3cCredentialService + let w3cCredentialsRepository: W3cCredentialRepository + const privateKey = TypedArrayEncoder.fromString('testseed000000000000000000000001') beforeAll(async () => { - wallet = new IndyWallet(agentConfig.agentDependencies, agentConfig.logger, keyProviderRegistry) - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - await wallet.createAndOpen(agentConfig.walletConfig!) + wallet = new IndySdkWallet(indySdk, agentConfig.logger, keyProviderRegistry) + await wallet.createAndOpen(agentConfig.walletConfig) agentContext = getAgentContext({ agentConfig, wallet, }) - w3cCredentialRepository = new W3cCredentialRepositoryMock() - w3cCredentialService = new W3cCredentialService( - w3cCredentialRepository, + w3cCredentialsRepository = new W3cCredentialsRepositoryMock() + w3cCredentialsService = new W3cCredentialService( + w3cCredentialsRepository, signatureSuiteRegistry, - new W3cVcModuleConfig({ + new W3cCredentialsModuleConfig({ documentLoader: customDocumentLoader, }) ) @@ -94,7 +94,7 @@ describe('W3cCredentialService', () => { describe('Utility methods', () => { describe('getKeyTypesByProofType', () => { it('should return the correct key types for Ed25519Signature2018 proof type', async () => { - const keyTypes = w3cCredentialService.getKeyTypesByProofType('Ed25519Signature2018') + const keyTypes = w3cCredentialsService.getKeyTypesByProofType('Ed25519Signature2018') expect(keyTypes).toEqual([KeyType.Ed25519]) }) }) @@ -102,7 +102,7 @@ describe('W3cCredentialService', () => { describe('getVerificationMethodTypesByProofType', () => { it('should return the correct key types for Ed25519Signature2018 proof type', async () => { const verificationMethodTypes = - w3cCredentialService.getVerificationMethodTypesByProofType('Ed25519Signature2018') + w3cCredentialsService.getVerificationMethodTypesByProofType('Ed25519Signature2018') expect(verificationMethodTypes).toEqual([ VERIFICATION_METHOD_TYPE_ED25519_VERIFICATION_KEY_2018, VERIFICATION_METHOD_TYPE_ED25519_VERIFICATION_KEY_2020, @@ -116,7 +116,10 @@ describe('W3cCredentialService', () => { let verificationMethod: string beforeAll(async () => { // TODO: update to use did registrar - const issuerKey = await wallet.createKey({ keyType: KeyType.Ed25519, seed }) + const issuerKey = await wallet.createKey({ + keyType: KeyType.Ed25519, + privateKey, + }) issuerDidKey = new DidKey(issuerKey) verificationMethod = `${issuerDidKey.did}#${issuerDidKey.key.fingerprint}` }) @@ -126,7 +129,7 @@ describe('W3cCredentialService', () => { const credential = JsonTransformer.fromJSON(credentialJson, W3cCredential) - const vc = await w3cCredentialService.signCredential(agentContext, { + const vc = await w3cCredentialsService.signCredential(agentContext, { credential, proofType: 'Ed25519Signature2018', verificationMethod: verificationMethod, @@ -148,7 +151,7 @@ describe('W3cCredentialService', () => { const credential = JsonTransformer.fromJSON(credentialJson, W3cCredential) expect(async () => { - await w3cCredentialService.signCredential(agentContext, { + await w3cCredentialsService.signCredential(agentContext, { credential, proofType: 'Ed25519Signature2018', verificationMethod: @@ -163,7 +166,7 @@ describe('W3cCredentialService', () => { Ed25519Signature2018Fixtures.TEST_LD_DOCUMENT_SIGNED, W3cVerifiableCredential ) - const result = await w3cCredentialService.verifyCredential(agentContext, { credential: vc }) + const result = await w3cCredentialsService.verifyCredential(agentContext, { credential: vc }) expect(result.verified).toBe(true) expect(result.error).toBeUndefined() @@ -178,7 +181,7 @@ describe('W3cCredentialService', () => { Ed25519Signature2018Fixtures.TEST_LD_DOCUMENT_BAD_SIGNED, W3cVerifiableCredential ) - const result = await w3cCredentialService.verifyCredential(agentContext, { credential: vc }) + const result = await w3cCredentialsService.verifyCredential(agentContext, { credential: vc }) expect(result.verified).toBe(false) expect(result.error).toBeDefined() @@ -198,7 +201,7 @@ describe('W3cCredentialService', () => { } const vc = JsonTransformer.fromJSON(vcJson, W3cVerifiableCredential) - const result = await w3cCredentialService.verifyCredential(agentContext, { credential: vc }) + const result = await w3cCredentialsService.verifyCredential(agentContext, { credential: vc }) expect(result.verified).toBe(false) @@ -220,7 +223,7 @@ describe('W3cCredentialService', () => { } const vc = JsonTransformer.fromJSON(vcJson, W3cVerifiableCredential) - const result = await w3cCredentialService.verifyCredential(agentContext, { credential: vc }) + const result = await w3cCredentialsService.verifyCredential(agentContext, { credential: vc }) expect(result.verified).toBe(false) @@ -236,7 +239,7 @@ describe('W3cCredentialService', () => { Ed25519Signature2018Fixtures.TEST_LD_DOCUMENT_SIGNED, W3cVerifiableCredential ) - const result = await w3cCredentialService.createPresentation({ credentials: vc }) + const result = await w3cCredentialsService.createPresentation({ credentials: vc }) expect(result).toBeInstanceOf(W3cPresentation) @@ -256,7 +259,7 @@ describe('W3cCredentialService', () => { ) const vcs = [vc1, vc2] - const result = await w3cCredentialService.createPresentation({ credentials: vcs }) + const result = await w3cCredentialsService.createPresentation({ credentials: vcs }) expect(result).toBeInstanceOf(W3cPresentation) @@ -277,7 +280,7 @@ describe('W3cCredentialService', () => { date: new Date().toISOString(), }) - const verifiablePresentation = await w3cCredentialService.signPresentation(agentContext, { + const verifiablePresentation = await w3cCredentialsService.signPresentation(agentContext, { presentation: presentation, purpose: purpose, signatureType: 'Ed25519Signature2018', @@ -295,12 +298,9 @@ describe('W3cCredentialService', () => { W3cVerifiablePresentation ) - const result = await w3cCredentialService.verifyPresentation(agentContext, { + const result = await w3cCredentialsService.verifyPresentation(agentContext, { presentation: vp, - proofType: 'Ed25519Signature2018', challenge: '7bf32d0b-39d4-41f3-96b6-45de52988e4c', - verificationMethod: - 'did:key:z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL#z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL', }) expect(result.verified).toBe(true) @@ -310,7 +310,7 @@ describe('W3cCredentialService', () => { describe('Credential Storage', () => { let w3cCredentialRecord: W3cCredentialRecord - let w3cCredentialRepositoryDeleteMock: jest.MockedFunction + let w3cCredentialRepositoryDeleteMock: jest.MockedFunction<(typeof w3cCredentialsRepository)['delete']> beforeEach(async () => { const credential = JsonTransformer.fromJSON( @@ -320,9 +320,9 @@ describe('W3cCredentialService', () => { w3cCredentialRecord = await credentialRecordFactory(credential) - mockFunction(w3cCredentialRepository.getById).mockResolvedValue(w3cCredentialRecord) - mockFunction(w3cCredentialRepository.getAll).mockResolvedValue([w3cCredentialRecord]) - w3cCredentialRepositoryDeleteMock = mockFunction(w3cCredentialRepository.delete).mockResolvedValue() + mockFunction(w3cCredentialsRepository.getById).mockResolvedValue(w3cCredentialRecord) + mockFunction(w3cCredentialsRepository.getAll).mockResolvedValue([w3cCredentialRecord]) + w3cCredentialRepositoryDeleteMock = mockFunction(w3cCredentialsRepository.delete).mockResolvedValue() }) describe('storeCredential', () => { it('should store a credential and expand the tags correctly', async () => { @@ -331,7 +331,7 @@ describe('W3cCredentialService', () => { W3cVerifiableCredential ) - w3cCredentialRecord = await w3cCredentialService.storeCredential(agentContext, { credential: credential }) + w3cCredentialRecord = await w3cCredentialsService.storeCredential(agentContext, { credential: credential }) expect(w3cCredentialRecord).toMatchObject({ type: 'W3cCredentialRecord', @@ -357,7 +357,7 @@ describe('W3cCredentialService', () => { ) // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - await w3cCredentialService.removeCredentialRecord(agentContext, credential.id!) + await w3cCredentialsService.removeCredentialRecord(agentContext, credential.id!) expect(w3cCredentialRepositoryDeleteMock).toBeCalledWith(agentContext, w3cCredentialRecord) }) @@ -369,16 +369,16 @@ describe('W3cCredentialService', () => { Ed25519Signature2018Fixtures.TEST_LD_DOCUMENT_SIGNED, W3cVerifiableCredential ) - await w3cCredentialService.storeCredential(agentContext, { credential: credential }) + await w3cCredentialsService.storeCredential(agentContext, { credential: credential }) - const records = await w3cCredentialService.getAllCredentialRecords(agentContext) + const records = await w3cCredentialsService.getAllCredentialRecords(agentContext) expect(records.length).toEqual(1) }) }) describe('getCredentialRecordById', () => { it('should retrieve a W3cCredentialRecord by id', async () => { - const credential = await w3cCredentialService.getCredentialRecordById(agentContext, w3cCredentialRecord.id) + const credential = await w3cCredentialsService.getCredentialRecordById(agentContext, w3cCredentialRecord.id) expect(credential.id).toEqual(w3cCredentialRecord.id) }) diff --git a/packages/core/src/modules/vc/__tests__/W3cCredentialsApi.test.ts b/packages/core/src/modules/vc/__tests__/W3cCredentialsApi.test.ts new file mode 100644 index 0000000000..1af3b044b1 --- /dev/null +++ b/packages/core/src/modules/vc/__tests__/W3cCredentialsApi.test.ts @@ -0,0 +1,96 @@ +import { IndySdkModule } from '../../../../../indy-sdk/src' +import { getAgentOptions, indySdk } from '../../../../tests' +import { Agent } from '../../../agent/Agent' +import { JsonTransformer } from '../../../utils' +import { W3cCredentialService } from '../W3cCredentialService' +import { W3cCredentialsModule } from '../W3cCredentialsModule' +import { W3cVerifiableCredential } from '../models' +import { W3cCredentialRepository } from '../repository' + +import { customDocumentLoader } from './documentLoader' +import { Ed25519Signature2018Fixtures } from './fixtures' + +const modules = { + indySdk: new IndySdkModule({ + indySdk, + }), + w3cCredentials: new W3cCredentialsModule({ + documentLoader: customDocumentLoader, + }), +} + +const agentOptions = getAgentOptions('W3cCredentialsApi', {}, modules) + +const agent = new Agent(agentOptions) + +let w3cCredentialRepository: W3cCredentialRepository +let w3cCredentialService: W3cCredentialService + +const testCredential = JsonTransformer.fromJSON( + Ed25519Signature2018Fixtures.TEST_LD_DOCUMENT_SIGNED, + W3cVerifiableCredential +) + +describe('W3cCredentialsApi', () => { + beforeAll(() => { + w3cCredentialRepository = agent.dependencyManager.resolve(W3cCredentialRepository) + w3cCredentialService = agent.dependencyManager.resolve(W3cCredentialService) + }) + + beforeEach(async () => { + await agent.initialize() + }) + + afterEach(async () => { + await agent.shutdown() + await agent.wallet.delete() + }) + + it('Should successfully store a credential', async () => { + const repoSpy = jest.spyOn(w3cCredentialRepository, 'save') + const serviceSpy = jest.spyOn(w3cCredentialService, 'storeCredential') + + await agent.w3cCredentials.storeCredential({ + credential: testCredential, + }) + + expect(repoSpy).toHaveBeenCalledTimes(1) + expect(serviceSpy).toHaveBeenCalledTimes(1) + }) + + it('Should successfully retrieve a credential by id', async () => { + const repoSpy = jest.spyOn(w3cCredentialRepository, 'getById') + const serviceSpy = jest.spyOn(w3cCredentialService, 'getCredentialRecordById') + + const storedCredential = await agent.w3cCredentials.storeCredential({ + credential: testCredential, + }) + + const retrievedCredential = await agent.w3cCredentials.getCredentialRecordById(storedCredential.id) + expect(storedCredential.id).toEqual(retrievedCredential.id) + + expect(repoSpy).toHaveBeenCalledTimes(1) + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + expect(repoSpy).toHaveBeenCalledWith((agent as any).agentContext, storedCredential.id) + expect(serviceSpy).toHaveBeenCalledTimes(1) + }) + + it('Should successfully remove a credential by id', async () => { + const repoSpy = jest.spyOn(w3cCredentialRepository, 'delete') + const serviceSpy = jest.spyOn(w3cCredentialService, 'removeCredentialRecord') + + const storedCredential = await agent.w3cCredentials.storeCredential({ + credential: testCredential, + }) + + await agent.w3cCredentials.removeCredentialRecord(storedCredential.id) + + expect(repoSpy).toHaveBeenCalledTimes(1) + expect(serviceSpy).toHaveBeenCalledTimes(1) + expect(serviceSpy).toHaveBeenCalledWith(agent.context, storedCredential.id) + + const allCredentials = await agent.w3cCredentials.getAllCredentialRecords() + expect(allCredentials).toHaveLength(0) + }) +}) diff --git a/packages/core/src/modules/vc/__tests__/W3cVcModuleConfig.test.ts b/packages/core/src/modules/vc/__tests__/W3cCredentialsModuleConfig.test.ts similarity index 59% rename from packages/core/src/modules/vc/__tests__/W3cVcModuleConfig.test.ts rename to packages/core/src/modules/vc/__tests__/W3cCredentialsModuleConfig.test.ts index e97d51389d..8ccbf3b919 100644 --- a/packages/core/src/modules/vc/__tests__/W3cVcModuleConfig.test.ts +++ b/packages/core/src/modules/vc/__tests__/W3cCredentialsModuleConfig.test.ts @@ -1,16 +1,16 @@ -import { W3cVcModuleConfig } from '../W3cVcModuleConfig' +import { W3cCredentialsModuleConfig } from '../W3cCredentialsModuleConfig' import { defaultDocumentLoader } from '../libraries/documentLoader' -describe('W3cVcModuleConfig', () => { +describe('W3cCredentialsModuleConfig', () => { test('sets default values', () => { - const config = new W3cVcModuleConfig() + const config = new W3cCredentialsModuleConfig() expect(config.documentLoader).toBe(defaultDocumentLoader) }) test('sets values', () => { const documentLoader = jest.fn() - const config = new W3cVcModuleConfig({ + const config = new W3cCredentialsModuleConfig({ documentLoader, }) diff --git a/packages/core/src/modules/vc/__tests__/contexts/citizenship_v2.ts b/packages/core/src/modules/vc/__tests__/contexts/citizenship_v2.ts new file mode 100644 index 0000000000..667571bea2 --- /dev/null +++ b/packages/core/src/modules/vc/__tests__/contexts/citizenship_v2.ts @@ -0,0 +1,45 @@ +export const CITIZENSHIP_V2 = { + '@context': { + '@version': 1.1, + '@protected': true, + name: 'http://schema.org/name', + description: 'http://schema.org/description', + identifier: 'http://schema.org/identifier', + image: { '@id': 'http://schema.org/image', '@type': '@id' }, + PermanentResidentCard: { + '@id': 'https://w3id.org/citizenship#PermanentResidentCard', + '@context': { + '@version': 1.1, + '@protected': true, + id: '@id', + type: '@type', + description: 'http://schema.org/description', + name: 'http://schema.org/name', + identifier: 'http://schema.org/identifier', + image: { '@id': 'http://schema.org/image', '@type': '@id' }, + }, + }, + PermanentResident: { + '@id': 'https://w3id.org/citizenship#PermanentResident', + '@context': { + '@version': 1.1, + '@protected': true, + id: '@id', + type: '@type', + ctzn: 'https://w3id.org/citizenship#', + schema: 'http://schema.org/', + xsd: 'http://www.w3.org/2001/XMLSchema#', + birthCountry: 'ctzn:birthCountry', + birthDate: { '@id': 'schema:birthDate', '@type': 'xsd:dateTime' }, + commuterClassification: 'ctzn:commuterClassification', + familyName: 'schema:familyName', + gender: 'schema:gender', + givenName: 'schema:givenName', + lprCategory: 'ctzn:lprCategory', + lprNumber: 'ctzn:lprNumber', + residentSince: { '@id': 'ctzn:residentSince', '@type': 'xsd:dateTime' }, + }, + }, + Person: 'http://schema.org/Person', + }, +} diff --git a/packages/core/src/modules/vc/__tests__/contexts/index.ts b/packages/core/src/modules/vc/__tests__/contexts/index.ts index c66801c24a..0d5bfff11a 100644 --- a/packages/core/src/modules/vc/__tests__/contexts/index.ts +++ b/packages/core/src/modules/vc/__tests__/contexts/index.ts @@ -8,4 +8,6 @@ export * from './schema_org' export * from './security_v1' export * from './security_v2' export * from './security_v3_unstable' +export * from './submission' export * from './vaccination_v1' +export * from './vaccination_v2' diff --git a/packages/core/src/modules/vc/__tests__/contexts/mattr_vc_extension_v1.ts b/packages/core/src/modules/vc/__tests__/contexts/mattr_vc_extension_v1.ts new file mode 100644 index 0000000000..aaadf21bb5 --- /dev/null +++ b/packages/core/src/modules/vc/__tests__/contexts/mattr_vc_extension_v1.ts @@ -0,0 +1,17 @@ +export const MATTR_VC_EXTENSION_V1 = { + '@context': { + '@version': 1.1, + '@protected': true, + VerifiableCredentialExtension: { + '@id': 'https://mattr.global/contexts/vc-extensions/v1#VerifiableCredentialExtension', + '@context': { + '@version': 1.1, + '@protected': true, + id: '@id', + type: '@type', + name: 'https://mattr.global/contexts/vc-extensions/v1#name', + description: 'https://mattr.global/contexts/vc-extensions/v1#description', + }, + }, + }, +} diff --git a/packages/core/src/modules/vc/__tests__/contexts/purl_ob_v3po.ts b/packages/core/src/modules/vc/__tests__/contexts/purl_ob_v3po.ts new file mode 100644 index 0000000000..3b2ffe28f6 --- /dev/null +++ b/packages/core/src/modules/vc/__tests__/contexts/purl_ob_v3po.ts @@ -0,0 +1,438 @@ +export const PURL_OB_V3P0 = { + '@context': { + id: '@id', + type: '@type', + xsd: 'https://www.w3.org/2001/XMLSchema#', + OpenBadgeCredential: { + '@id': 'https://purl.imsglobal.org/spec/vc/ob/vocab.html#OpenBadgeCredential', + }, + Achievement: { + '@id': 'https://purl.imsglobal.org/spec/vc/ob/vocab.html#Achievement', + '@context': { + achievementType: { + '@id': 'https://purl.imsglobal.org/spec/vc/ob/vocab.html#achievementType', + '@type': 'xsd:string', + }, + alignment: { + '@id': 'https://purl.imsglobal.org/spec/vc/ob/vocab.html#alignment', + '@type': 'https://purl.imsglobal.org/spec/vc/ob/vocab.html#Alignment', + '@container': '@set', + }, + creator: { + '@id': 'https://purl.imsglobal.org/spec/vc/ob/vocab.html#Profile', + }, + creditsAvailable: { + '@id': 'https://purl.imsglobal.org/spec/vc/ob/vocab.html#creditsAvailable', + '@type': 'xsd:float', + }, + criteria: { + '@id': 'https://purl.imsglobal.org/spec/vc/ob/vocab.html#Criteria', + '@type': '@id', + }, + fieldOfStudy: { + '@id': 'https://purl.imsglobal.org/spec/vc/ob/vocab.html#fieldOfStudy', + '@type': 'xsd:string', + }, + humanCode: { + '@id': 'https://purl.imsglobal.org/spec/vc/ob/vocab.html#humanCode', + '@type': 'xsd:string', + }, + otherIdentifier: { + '@id': 'https://purl.imsglobal.org/spec/vc/ob/vocab.html#otherIdentifier', + '@type': 'https://purl.imsglobal.org/spec/vc/ob/vocab.html#IdentifierEntry', + '@container': '@set', + }, + related: { + '@id': 'https://purl.imsglobal.org/spec/vc/ob/vocab.html#related', + '@type': 'https://purl.imsglobal.org/spec/vc/ob/vocab.html#Related', + '@container': '@set', + }, + resultDescription: { + '@id': 'https://purl.imsglobal.org/spec/vc/ob/vocab.html#resultDescription', + '@type': 'https://purl.imsglobal.org/spec/vc/ob/vocab.html#ResultDescription', + '@container': '@set', + }, + specialization: { + '@id': 'https://purl.imsglobal.org/spec/vc/ob/vocab.html#specialization', + '@type': 'xsd:string', + }, + tag: { + '@id': 'https://schema.org/keywords', + '@type': 'xsd:string', + '@container': '@set', + }, + version: { + '@id': 'https://purl.imsglobal.org/spec/vc/ob/vocab.html#version', + '@type': 'xsd:string', + }, + }, + }, + AchievementCredential: { + '@id': 'OpenBadgeCredential', + }, + AchievementSubject: { + '@id': 'https://purl.imsglobal.org/spec/vc/ob/vocab.html#AchievementSubject', + '@context': { + achievement: { + '@id': 'https://purl.imsglobal.org/spec/vc/ob/vocab.html#Achievement', + }, + activityEndDate: { + '@id': 'https://purl.imsglobal.org/spec/vc/ob/vocab.html#activityEndDate', + '@type': 'xsd:date', + }, + activityStartDate: { + '@id': 'https://purl.imsglobal.org/spec/vc/ob/vocab.html#activityStartDate', + '@type': 'xsd:date', + }, + creditsEarned: { + '@id': 'https://purl.imsglobal.org/spec/vc/ob/vocab.html#creditsEarned', + '@type': 'xsd:float', + }, + identifier: { + '@id': 'https://purl.imsglobal.org/spec/vc/ob/vocab.html#identifier', + '@type': 'https://purl.imsglobal.org/spec/vc/ob/vocab.html#IdentityObject', + '@container': '@set', + }, + licenseNumber: { + '@id': 'https://purl.imsglobal.org/spec/vc/ob/vocab.html#licenseNumber', + '@type': 'xsd:string', + }, + result: { + '@id': 'https://purl.imsglobal.org/spec/vc/ob/vocab.html#result', + '@type': 'https://purl.imsglobal.org/spec/vc/ob/vocab.html#Result', + '@container': '@set', + }, + role: { + '@id': 'https://purl.imsglobal.org/spec/vc/ob/vocab.html#role', + '@type': 'xsd:string', + }, + source: { + '@id': 'https://purl.imsglobal.org/spec/vc/ob/vocab.html#source', + '@type': 'https://purl.imsglobal.org/spec/vc/ob/vocab.html#Profile', + }, + term: { + '@id': 'https://purl.imsglobal.org/spec/vc/ob/vocab.html#term', + '@type': 'xsd:string', + }, + }, + }, + Address: { + '@id': 'https://purl.imsglobal.org/spec/vc/ob/vocab.html#Address', + '@context': { + addressCountry: { + '@id': 'https://schema.org/addressCountry', + '@type': 'xsd:string', + }, + addressCountryCode: { + '@id': 'https://purl.imsglobal.org/spec/vc/ob/vocab.html#CountryCode', + '@type': 'xsd:string', + }, + addressLocality: { + '@id': 'https://schema.org/addressLocality', + '@type': 'xsd:string', + }, + addressRegion: { + '@id': 'https://schema.org/addressRegion', + '@type': 'xsd:string', + }, + geo: { + '@id': 'https://purl.imsglobal.org/spec/vc/ob/vocab.html#GeoCoordinates', + }, + postOfficeBoxNumber: { + '@id': 'https://schema.org/postOfficeBoxNumber', + '@type': 'xsd:string', + }, + postalCode: { + '@id': 'https://schema.org/postalCode', + '@type': 'xsd:string', + }, + streetAddress: { + '@id': 'https://schema.org/streetAddress', + '@type': 'xsd:string', + }, + }, + }, + Alignment: { + '@id': 'https://schema.org/Alignment', + '@context': { + targetCode: { + '@id': 'https://purl.imsglobal.org/spec/vc/ob/vocab.html#targetCode', + '@type': 'xsd:string', + }, + targetDescription: { + '@id': 'https://schema.org/targetDescription', + '@type': 'xsd:string', + }, + targetFramework: { + '@id': 'https://schema.org/targetFramework', + '@type': 'xsd:string', + }, + targetName: { + '@id': 'https://schema.org/targetName', + '@type': 'xsd:string', + }, + targetType: { + '@id': 'https://purl.imsglobal.org/spec/vc/ob/vocab.html#targetType', + '@type': 'xsd:string', + }, + targetUrl: { + '@id': 'https://schema.org/targetUrl', + '@type': 'xsd:anyURI', + }, + }, + }, + Criteria: { + '@id': 'https://purl.imsglobal.org/spec/vc/ob/vocab.html#Criteria', + }, + EndorsementCredential: { + '@id': 'https://purl.imsglobal.org/spec/vc/ob/vocab.html#EndorsementCredential', + }, + EndorsementSubject: { + '@id': 'https://purl.imsglobal.org/spec/vc/ob/vocab.html#EndorsementSubject', + '@context': { + endorsementComment: { + '@id': 'https://purl.imsglobal.org/spec/vc/ob/vocab.html#endorsementComment', + '@type': 'xsd:string', + }, + }, + }, + Evidence: { + '@id': 'https://purl.imsglobal.org/spec/vc/ob/vocab.html#Evidence', + '@context': { + audience: { + '@id': 'https://schema.org/audience', + '@type': 'xsd:string', + }, + genre: { + '@id': 'https://schema.org/genre', + '@type': 'xsd:string', + }, + }, + }, + GeoCoordinates: { + '@id': 'https://purl.imsglobal.org/spec/vc/ob/vocab.html#GeoCoordinates', + '@context': { + latitude: { + '@id': 'https://schema.org/latitude', + '@type': 'xsd:string', + }, + longitude: { + '@id': 'https://schema.org/longitude', + '@type': 'xsd:string', + }, + }, + }, + IdentifierEntry: { + '@id': 'https://purl.imsglobal.org/spec/vc/ob/vocab.html#IdentifierEntry', + '@context': { + identifier: { + '@id': 'https://purl.imsglobal.org/spec/vc/ob/vocab.html#identifier', + '@type': 'xsd:string', + }, + identifierType: { + '@id': 'https://purl.imsglobal.org/spec/vc/ob/vocab.html#identifierType', + '@type': 'xsd:string', + }, + }, + }, + IdentityObject: { + '@id': 'https://purl.imsglobal.org/spec/vc/ob/vocab.html#IdentityObject', + '@context': { + hashed: { + '@id': 'https://purl.imsglobal.org/spec/vc/ob/vocab.html#hashed', + '@type': 'xsd:boolean', + }, + identityHash: { + '@id': 'https://purl.imsglobal.org/spec/vc/ob/vocab.html#identityHash', + '@type': 'xsd:string', + }, + identityType: { + '@id': 'https://purl.imsglobal.org/spec/vc/ob/vocab.html#identityType', + '@type': 'xsd:string', + }, + salt: { + '@id': 'https://purl.imsglobal.org/spec/vc/ob/vocab.html#salt', + '@type': 'xsd:string', + }, + }, + }, + Image: { + '@id': 'https://purl.imsglobal.org/spec/vc/ob/vocab.html#Image', + '@context': { + caption: { + '@id': 'https://schema.org/caption', + '@type': 'xsd:string', + }, + }, + }, + Profile: { + '@id': 'https://purl.imsglobal.org/spec/vc/ob/vocab.html#Profile', + '@context': { + additionalName: { + '@id': 'https://schema.org/additionalName', + '@type': 'xsd:string', + }, + address: { + '@id': 'https://purl.imsglobal.org/spec/vc/ob/vocab.html#Address', + }, + dateOfBirth: { + '@id': 'https://purl.imsglobal.org/spec/vc/ob/vocab.html#dateOfBirth', + '@type': 'xsd:date', + }, + email: { + '@id': 'https://schema.org/email', + '@type': 'xsd:string', + }, + familyName: { + '@id': 'https://schema.org/familyName', + '@type': 'xsd:string', + }, + familyNamePrefix: { + '@id': 'https://purl.imsglobal.org/spec/vc/ob/vocab.html#familyNamePrefix', + '@type': 'xsd:string', + }, + givenName: { + '@id': 'https://schema.org/givenName', + '@type': 'xsd:string', + }, + honorificPrefix: { + '@id': 'https://schema.org/honorificPrefix', + '@type': 'xsd:string', + }, + honorificSuffix: { + '@id': 'https://schema.org/honorificSuffix', + '@type': 'xsd:string', + }, + otherIdentifier: { + '@id': 'https://purl.imsglobal.org/spec/vc/ob/vocab.html#otherIdentifier', + '@type': 'https://purl.imsglobal.org/spec/vc/ob/vocab.html#IdentifierEntry', + '@container': '@set', + }, + parentOrg: { + '@id': 'https://purl.imsglobal.org/spec/vc/ob/vocab.html#parentOrg', + '@type': 'xsd:string', + }, + patronymicName: { + '@id': 'https://purl.imsglobal.org/spec/vc/ob/vocab.html#patronymicName', + '@type': 'xsd:string', + }, + phone: { + '@id': 'https://purl.imsglobal.org/spec/vc/ob/vocab.html#PhoneNumber', + '@type': 'xsd:string', + }, + official: { + '@id': 'https://purl.imsglobal.org/spec/vc/ob/vocab.html#official', + '@type': 'xsd:string', + }, + }, + }, + Related: { + '@id': 'https://purl.imsglobal.org/spec/vc/ob/vocab.html#Related', + '@context': { + version: { + '@id': 'https://purl.imsglobal.org/spec/vc/ob/vocab.html#version', + '@type': 'xsd:string', + }, + }, + }, + Result: { + '@id': 'https://purl.imsglobal.org/spec/vc/ob/vocab.html#Result', + '@context': { + achievedLevel: { + '@id': 'https://purl.imsglobal.org/spec/vc/ob/vocab.html#achievedLevel', + '@type': 'xsd:anyURI', + }, + resultDescription: { + '@id': 'https://purl.imsglobal.org/spec/vc/ob/vocab.html#resultDescription', + '@type': 'xsd:anyURI', + }, + status: { + '@id': 'https://purl.imsglobal.org/spec/vc/ob/vocab.html#status', + '@type': 'xsd:string', + }, + value: { + '@id': 'https://schema.org/value', + '@type': 'xsd:string', + }, + }, + }, + ResultDescription: { + '@id': 'https://purl.imsglobal.org/spec/vc/ob/vocab.html#ResultDescription', + '@context': { + allowedValue: { + '@id': 'https://purl.imsglobal.org/spec/vc/ob/vocab.html#allowedValue', + '@type': 'xsd:string', + '@container': '@set', + }, + requiredLevel: { + '@id': 'https://purl.imsglobal.org/spec/vc/ob/vocab.html#requiredLevel', + '@type': 'xsd:anyURI', + }, + requiredValue: { + '@id': 'https://purl.imsglobal.org/spec/vc/ob/vocab.html#requiredValue', + '@type': 'xsd:string', + }, + resultType: { + '@id': 'https://purl.imsglobal.org/spec/vc/ob/vocab.html#resultType', + '@type': 'xsd:string', + }, + rubricCriterionLevel: { + '@id': 'https://purl.imsglobal.org/spec/vc/ob/vocab.html#rubricCriterionLevel', + '@type': 'https://purl.imsglobal.org/spec/vc/ob/vocab.html#RubricCriterionLevel', + '@container': '@set', + }, + valueMax: { + '@id': 'https://purl.imsglobal.org/spec/vc/ob/vocab.html#valueMax', + '@type': 'xsd:string', + }, + valueMin: { + '@id': 'https://purl.imsglobal.org/spec/vc/ob/vocab.html#valueMin', + '@type': 'xsd:string', + }, + }, + }, + RubricCriterionLevel: { + '@id': 'https://purl.imsglobal.org/spec/vc/ob/vocab.html#RubricCriterionLevel', + '@context': { + level: { + '@id': 'https://purl.imsglobal.org/spec/vc/ob/vocab.html#level', + '@type': 'xsd:string', + }, + points: { + '@id': 'https://purl.imsglobal.org/spec/vc/ob/vocab.html#points', + '@type': 'xsd:string', + }, + }, + }, + alignment: { + '@id': 'https://purl.imsglobal.org/spec/vc/ob/vocab.html#alignment', + '@type': 'https://purl.imsglobal.org/spec/vc/ob/vocab.html#Alignment', + '@container': '@set', + }, + description: { + '@id': 'https://schema.org/description', + '@type': 'xsd:string', + }, + endorsement: { + '@id': 'https://purl.imsglobal.org/spec/vc/ob/vocab.html#endorsement', + '@type': 'https://purl.imsglobal.org/spec/vc/ob/vocab.html#EndorsementCredential', + '@container': '@set', + }, + image: { + '@id': 'https://purl.imsglobal.org/spec/vc/ob/vocab.html#image', + '@type': 'https://purl.imsglobal.org/spec/vc/ob/vocab.html#Image', + }, + name: { + '@id': 'https://schema.org/name', + '@type': 'xsd:string', + }, + narrative: { + '@id': 'https://purl.imsglobal.org/spec/vc/ob/vocab.html#narrative', + '@type': 'xsd:string', + }, + url: { + '@id': 'https://schema.org/url', + '@type': 'xsd:anyURI', + }, + }, +} diff --git a/packages/core/src/modules/vc/__tests__/contexts/submission.ts b/packages/core/src/modules/vc/__tests__/contexts/submission.ts new file mode 100644 index 0000000000..4df5ca9b4f --- /dev/null +++ b/packages/core/src/modules/vc/__tests__/contexts/submission.ts @@ -0,0 +1,15 @@ +export const PRESENTATION_SUBMISSION = { + '@context': { + '@version': 1.1, + PresentationSubmission: { + '@id': 'https://identity.foundation/presentation-exchange/#presentation-submission', + '@context': { + '@version': 1.1, + presentation_submission: { + '@id': 'https://identity.foundation/presentation-exchange/#presentation-submission', + '@type': '@json', + }, + }, + }, + }, +} diff --git a/packages/core/src/modules/vc/__tests__/contexts/vaccination_v2.ts b/packages/core/src/modules/vc/__tests__/contexts/vaccination_v2.ts new file mode 100644 index 0000000000..483c87134b --- /dev/null +++ b/packages/core/src/modules/vc/__tests__/contexts/vaccination_v2.ts @@ -0,0 +1,88 @@ +export const VACCINATION_V2 = { + '@context': { + '@version': 1.1, + '@protected': true, + id: '@id', + type: '@type', + description: 'http://schema.org/description', + identifier: 'http://schema.org/identifier', + name: 'http://schema.org/name', + image: 'http://schema.org/image', + VaccinationCertificate: { + '@id': 'https://w3id.org/vaccination#VaccinationCertificate', + '@context': { + '@version': 1.1, + '@protected': true, + id: '@id', + type: '@type', + description: 'http://schema.org/description', + identifier: 'http://schema.org/identifier', + name: 'http://schema.org/name', + image: 'http://schema.org/image', + }, + }, + VaccinationEvent: { + '@id': 'https://w3id.org/vaccination#VaccinationEvent', + '@context': { + '@version': 1.1, + '@protected': true, + id: '@id', + type: '@type', + administeringCentre: 'https://w3id.org/vaccination#administeringCentre', + batchNumber: 'https://w3id.org/vaccination#batchNumber', + countryOfVaccination: 'https://w3id.org/vaccination#countryOfVaccination', + dateOfVaccination: { + '@id': 'https://w3id.org/vaccination#dateOfVaccination', + '@type': 'http://www.w3.org/2001/XMLSchema#dateTime', + }, + healthProfessional: 'https://w3id.org/vaccination#healthProfessional', + nextVaccinationDate: { + '@id': 'https://w3id.org/vaccination#nextVaccinationDate', + '@type': 'http://www.w3.org/2001/XMLSchema#dateTime', + }, + order: 'https://w3id.org/vaccination#order', + recipient: { + '@id': 'https://w3id.org/vaccination#recipient', + '@type': 'https://w3id.org/vaccination#VaccineRecipient', + }, + vaccine: { + '@id': 'https://w3id.org/vaccination#VaccineEventVaccine', + '@type': 'https://w3id.org/vaccination#Vaccine', + }, + }, + }, + VaccineRecipient: { + '@id': 'https://w3id.org/vaccination#VaccineRecipient', + '@context': { + '@version': 1.1, + '@protected': true, + id: '@id', + type: '@type', + birthDate: { + '@id': 'http://schema.org/birthDate', + '@type': 'http://www.w3.org/2001/XMLSchema#dateTime', + }, + familyName: 'http://schema.org/familyName', + gender: 'http://schema.org/gender', + givenName: 'http://schema.org/givenName', + }, + }, + Vaccine: { + '@id': 'https://w3id.org/vaccination#Vaccine', + '@context': { + '@version': 1.1, + '@protected': true, + id: '@id', + type: '@type', + atcCode: 'https://w3id.org/vaccination#atc-code', + disease: 'https://w3id.org/vaccination#disease', + event: { + '@id': 'https://w3id.org/vaccination#VaccineRecipientVaccineEvent', + '@type': 'https://w3id.org/vaccination#VaccineEvent', + }, + marketingAuthorizationHolder: 'https://w3id.org/vaccination#marketingAuthorizationHolder', + medicinalProductName: 'https://w3id.org/vaccination#medicinalProductName', + }, + }, + }, +} diff --git a/packages/core/src/modules/vc/__tests__/contexts/vc_revocation_list_2020.ts b/packages/core/src/modules/vc/__tests__/contexts/vc_revocation_list_2020.ts new file mode 100644 index 0000000000..d0646eaa25 --- /dev/null +++ b/packages/core/src/modules/vc/__tests__/contexts/vc_revocation_list_2020.ts @@ -0,0 +1,37 @@ +export const VC_REVOCATION_LIST_2020 = { + '@context': { + '@protected': true, + RevocationList2020Credential: { + '@id': 'https://w3id.org/vc-revocation-list-2020#RevocationList2020Credential', + '@context': { + '@protected': true, + id: '@id', + type: '@type', + description: 'http://schema.org/description', + name: 'http://schema.org/name', + }, + }, + RevocationList2020: { + '@id': 'https://w3id.org/vc-revocation-list-2020#RevocationList2020', + '@context': { + '@protected': true, + id: '@id', + type: '@type', + encodedList: 'https://w3id.org/vc-revocation-list-2020#encodedList', + }, + }, + RevocationList2020Status: { + '@id': 'https://w3id.org/vc-revocation-list-2020#RevocationList2020Status', + '@context': { + '@protected': true, + id: '@id', + type: '@type', + revocationListCredential: { + '@id': 'https://w3id.org/vc-revocation-list-2020#revocationListCredential', + '@type': '@id', + }, + revocationListIndex: 'https://w3id.org/vc-revocation-list-2020#revocationListIndex', + }, + }, + }, +} diff --git a/packages/core/src/modules/vc/__tests__/dids/did_web_launchpad.ts b/packages/core/src/modules/vc/__tests__/dids/did_web_launchpad.ts new file mode 100644 index 0000000000..81c02d5555 --- /dev/null +++ b/packages/core/src/modules/vc/__tests__/dids/did_web_launchpad.ts @@ -0,0 +1,24 @@ +export const DID_WEB_LAUNCHPAD = { + id: 'did:web:launchpad.vii.electron.mattrlabs.io', + '@context': ['https://w3.org/ns/did/v1', 'https://w3id.org/security/suites/ed25519-2018/v1'], + verificationMethod: [ + { + id: 'did:web:launchpad.vii.electron.mattrlabs.io#6BhFMCGTJg', + type: 'Ed25519VerificationKey2018', + controller: 'did:web:launchpad.vii.electron.mattrlabs.io', + publicKeyBase58: '6BhFMCGTJg9DnpXZe7zbiTrtuwion5FVV6Z2NUpwDMVT', + }, + ], + keyAgreement: [ + { + id: 'did:web:launchpad.vii.electron.mattrlabs.io#9eS8Tqsus1', + type: 'X25519KeyAgreementKey2019', + controller: 'did:web:launchpad.vii.electron.mattrlabs.io', + publicKeyBase58: '9eS8Tqsus1uJmQpf37S8CnEeBrEehsC3qz8RMq67KoLB', + }, + ], + authentication: ['did:web:launchpad.vii.electron.mattrlabs.io#6BhFMCGTJg'], + assertionMethod: ['did:web:launchpad.vii.electron.mattrlabs.io#6BhFMCGTJg'], + capabilityDelegation: ['did:web:launchpad.vii.electron.mattrlabs.io#6BhFMCGTJg'], + capabilityInvocation: ['did:web:launchpad.vii.electron.mattrlabs.io#6BhFMCGTJg'], +} diff --git a/packages/core/src/modules/vc/__tests__/documentLoader.ts b/packages/core/src/modules/vc/__tests__/documentLoader.ts index 29c47d7d91..adf72dba7f 100644 --- a/packages/core/src/modules/vc/__tests__/documentLoader.ts +++ b/packages/core/src/modules/vc/__tests__/documentLoader.ts @@ -4,17 +4,30 @@ import type { DocumentLoaderResult } from '../libraries/jsonld' import jsonld from '../libraries/jsonld' -import { BBS_V1, EXAMPLES_V1, ODRL, SCHEMA_ORG, VACCINATION_V1 } from './contexts' +import { + BBS_V1, + EXAMPLES_V1, + ODRL, + PRESENTATION_SUBMISSION, + SCHEMA_ORG, + VACCINATION_V1, + VACCINATION_V2, +} from './contexts' import { X25519_V1 } from './contexts/X25519_v1' import { CITIZENSHIP_V1 } from './contexts/citizenship_v1' +import { CITIZENSHIP_V2 } from './contexts/citizenship_v2' import { CREDENTIALS_V1 } from './contexts/credentials_v1' import { DID_V1 } from './contexts/did_v1' import { ED25519_V1 } from './contexts/ed25519_v1' +import { MATTR_VC_EXTENSION_V1 } from './contexts/mattr_vc_extension_v1' +import { PURL_OB_V3P0 } from './contexts/purl_ob_v3po' import { SECURITY_V1 } from './contexts/security_v1' import { SECURITY_V2 } from './contexts/security_v2' import { SECURITY_V3_UNSTABLE } from './contexts/security_v3_unstable' +import { VC_REVOCATION_LIST_2020 } from './contexts/vc_revocation_list_2020' import { DID_EXAMPLE_48939859 } from './dids/did_example_489398593' import { DID_SOV_QqEfJxe752NCmWqR5TssZ5 } from './dids/did_sov_QqEfJxe752NCmWqR5TssZ5' +import { DID_WEB_LAUNCHPAD } from './dids/did_web_launchpad' import { DID_z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL } from './dids/did_z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL' import { DID_z6MkvePyWAApUVeDboZhNbckaWHnqtD6pCETd6xoqGbcpEBV } from './dids/did_z6MkvePyWAApUVeDboZhNbckaWHnqtD6pCETd6xoqGbcpEBV' import { DID_zUC72Q7XD4PE4CrMiDVXuvZng3sBvMmaGgNeTUJuzavH2BS7ThbHL9FhsZM9QYY5fqAQ4MB8M9oudz3tfuaX36Ajr97QRW7LBt6WWmrtESe6Bs5NYzFtLWEmeVtvRYVAgjFcJSa } from './dids/did_zUC72Q7XD4PE4CrMiDVXuvZng3sBvMmaGgNeTUJuzavH2BS7ThbHL9FhsZM9QYY5fqAQ4MB8M9oudz3tfuaX36Ajr97QRW7LBt6WWmrtESe6Bs5NYzFtLWEmeVtvRYVAgjFcJSa' @@ -32,36 +45,47 @@ export const DOCUMENTS = { [DID_z6MkvePyWAApUVeDboZhNbckaWHnqtD6pCETd6xoqGbcpEBV['id']]: DID_z6MkvePyWAApUVeDboZhNbckaWHnqtD6pCETd6xoqGbcpEBV, [DID_zUC72Q7XD4PE4CrMiDVXuvZng3sBvMmaGgNeTUJuzavH2BS7ThbHL9FhsZM9QYY5fqAQ4MB8M9oudz3tfuaX36Ajr97QRW7LBt6WWmrtESe6Bs5NYzFtLWEmeVtvRYVAgjFcJSa[ 'id' - ]]: DID_zUC72Q7XD4PE4CrMiDVXuvZng3sBvMmaGgNeTUJuzavH2BS7ThbHL9FhsZM9QYY5fqAQ4MB8M9oudz3tfuaX36Ajr97QRW7LBt6WWmrtESe6Bs5NYzFtLWEmeVtvRYVAgjFcJSa, + ]]: + DID_zUC72Q7XD4PE4CrMiDVXuvZng3sBvMmaGgNeTUJuzavH2BS7ThbHL9FhsZM9QYY5fqAQ4MB8M9oudz3tfuaX36Ajr97QRW7LBt6WWmrtESe6Bs5NYzFtLWEmeVtvRYVAgjFcJSa, [DID_EXAMPLE_48939859['id']]: DID_EXAMPLE_48939859, [DID_zUC73JKGpX1WG4CWbFM15ni3faANPet6m8WJ6vaF5xyFsM3MeoBVNgQ6jjVPCcUnTAnJy6RVKqsUXa4AvdRKwV5hhQhwhMWFT9so9jrPekKmqpikTjYBXa3RYWqRpCWHY4u4hxh[ 'id' - ]]: DID_zUC73JKGpX1WG4CWbFM15ni3faANPet6m8WJ6vaF5xyFsM3MeoBVNgQ6jjVPCcUnTAnJy6RVKqsUXa4AvdRKwV5hhQhwhMWFT9so9jrPekKmqpikTjYBXa3RYWqRpCWHY4u4hxh, + ]]: + DID_zUC73JKGpX1WG4CWbFM15ni3faANPet6m8WJ6vaF5xyFsM3MeoBVNgQ6jjVPCcUnTAnJy6RVKqsUXa4AvdRKwV5hhQhwhMWFT9so9jrPekKmqpikTjYBXa3RYWqRpCWHY4u4hxh, [DID_zUC73YqdRJ3t8bZsFUoxYFPNVruHzn4o7u78GSrMXVSkcb3xAYtUxRD2kSt2bDcmQpRjKfygwLJ1HEGfkosSN7gr4acjGkXLbLRXREueknFN4AU19m8BxEgWnLM84CAvsw6bhYn[ 'id' - ]]: DID_zUC73YqdRJ3t8bZsFUoxYFPNVruHzn4o7u78GSrMXVSkcb3xAYtUxRD2kSt2bDcmQpRjKfygwLJ1HEGfkosSN7gr4acjGkXLbLRXREueknFN4AU19m8BxEgWnLM84CAvsw6bhYn, + ]]: + DID_zUC73YqdRJ3t8bZsFUoxYFPNVruHzn4o7u78GSrMXVSkcb3xAYtUxRD2kSt2bDcmQpRjKfygwLJ1HEGfkosSN7gr4acjGkXLbLRXREueknFN4AU19m8BxEgWnLM84CAvsw6bhYn, [DID_zUC76qMTDAaupy19pEk8JKH5LJwPwmscNQn24SYpqrgqEoYWPFgCSm4CnTfupADRfbB6CxdwYhVaTFjT4fmPvMh7gWY87LauhaLmNpPamCv4LAepcRfBDndSdtCpZKSTELMjzGJ[ 'id' - ]]: DID_zUC76qMTDAaupy19pEk8JKH5LJwPwmscNQn24SYpqrgqEoYWPFgCSm4CnTfupADRfbB6CxdwYhVaTFjT4fmPvMh7gWY87LauhaLmNpPamCv4LAepcRfBDndSdtCpZKSTELMjzGJ, + ]]: + DID_zUC76qMTDAaupy19pEk8JKH5LJwPwmscNQn24SYpqrgqEoYWPFgCSm4CnTfupADRfbB6CxdwYhVaTFjT4fmPvMh7gWY87LauhaLmNpPamCv4LAepcRfBDndSdtCpZKSTELMjzGJ, [DID_zUC7DMETzdZM6woUjvs2fieEyFTbHABXwBvLYPBs4NDWKut4H41h8V3KTqGNRUziXLYqa1sFYYw9Zjpt6pFUf7hra4Q1zXMA9JjXcXxDpxuDNpUKEpiDPSYYUztVchUJHQJJhox[ 'id' - ]]: DID_zUC7DMETzdZM6woUjvs2fieEyFTbHABXwBvLYPBs4NDWKut4H41h8V3KTqGNRUziXLYqa1sFYYw9Zjpt6pFUf7hra4Q1zXMA9JjXcXxDpxuDNpUKEpiDPSYYUztVchUJHQJJhox, + ]]: + DID_zUC7DMETzdZM6woUjvs2fieEyFTbHABXwBvLYPBs4NDWKut4H41h8V3KTqGNRUziXLYqa1sFYYw9Zjpt6pFUf7hra4Q1zXMA9JjXcXxDpxuDNpUKEpiDPSYYUztVchUJHQJJhox, [DID_zUC7F9Jt6YzVW9fGhwYjVrjdS8Xzg7oQc2CeDcVNgEcEAaJXAtPz3eXu2sewq4xtwRK3DAhQRYwwoYiT3nNzLCPsrKoP72UGZKhh4cNuZD7RkmwzAa1Bye4C5a9DcyYBGKZrE5F[ 'id' - ]]: DID_zUC7F9Jt6YzVW9fGhwYjVrjdS8Xzg7oQc2CeDcVNgEcEAaJXAtPz3eXu2sewq4xtwRK3DAhQRYwwoYiT3nNzLCPsrKoP72UGZKhh4cNuZD7RkmwzAa1Bye4C5a9DcyYBGKZrE5F, + ]]: + DID_zUC7F9Jt6YzVW9fGhwYjVrjdS8Xzg7oQc2CeDcVNgEcEAaJXAtPz3eXu2sewq4xtwRK3DAhQRYwwoYiT3nNzLCPsrKoP72UGZKhh4cNuZD7RkmwzAa1Bye4C5a9DcyYBGKZrE5F, [DID_zUC7H7TxvhWmvfptpu2zSwo5EZ1kr3MPNsjovaD2ipbuzj6zi1vk4FHTiunCJrFvUYV77Mk3QcWUUAHojPZdU8oG476cvMK2ozP1gVq63x5ovj6e4oQ9qg9eF4YjPhWJs6FPuT4[ 'id' - ]]: DID_zUC7H7TxvhWmvfptpu2zSwo5EZ1kr3MPNsjovaD2ipbuzj6zi1vk4FHTiunCJrFvUYV77Mk3QcWUUAHojPZdU8oG476cvMK2ozP1gVq63x5ovj6e4oQ9qg9eF4YjPhWJs6FPuT4, + ]]: + DID_zUC7H7TxvhWmvfptpu2zSwo5EZ1kr3MPNsjovaD2ipbuzj6zi1vk4FHTiunCJrFvUYV77Mk3QcWUUAHojPZdU8oG476cvMK2ozP1gVq63x5ovj6e4oQ9qg9eF4YjPhWJs6FPuT4, [DID_zUC7H7TxvhWmvfptpu2zSwo5EZ1kr3MPNsjovaD2ipbuzj6zi1vk4FHTiunCJrFvUYV77Mk3QcWUUAHojPZdU8oG476cvMK2ozP1gVq63x5ovj6e4oQ9qg9eF4YjPhWJs6FPuT4[ 'id' - ]]: DID_zUC7H7TxvhWmvfptpu2zSwo5EZ1kr3MPNsjovaD2ipbuzj6zi1vk4FHTiunCJrFvUYV77Mk3QcWUUAHojPZdU8oG476cvMK2ozP1gVq63x5ovj6e4oQ9qg9eF4YjPhWJs6FPuT4, + ]]: + DID_zUC7H7TxvhWmvfptpu2zSwo5EZ1kr3MPNsjovaD2ipbuzj6zi1vk4FHTiunCJrFvUYV77Mk3QcWUUAHojPZdU8oG476cvMK2ozP1gVq63x5ovj6e4oQ9qg9eF4YjPhWJs6FPuT4, [DID_zUC72to2eJiFMrt8a89LoaEPHC76QcfAxQdFys3nFGCmDKAmLbdE4ByyQ54kh42XgECCyZfVKe3m41Kk35nzrBKYbk6s9K7EjyLJcGGPkA7N15tDNBQJaY7cHD4RRaTwF6qXpmD[ 'id' - ]]: DID_zUC72to2eJiFMrt8a89LoaEPHC76QcfAxQdFys3nFGCmDKAmLbdE4ByyQ54kh42XgECCyZfVKe3m41Kk35nzrBKYbk6s9K7EjyLJcGGPkA7N15tDNBQJaY7cHD4RRaTwF6qXpmD, + ]]: + DID_zUC72to2eJiFMrt8a89LoaEPHC76QcfAxQdFys3nFGCmDKAmLbdE4ByyQ54kh42XgECCyZfVKe3m41Kk35nzrBKYbk6s9K7EjyLJcGGPkA7N15tDNBQJaY7cHD4RRaTwF6qXpmD, [DID_zUC74VEqqhEHQcgv4zagSPkqFJxuNWuoBPKjJuHETEUeHLoSqWt92viSsmaWjy82y2cgguc8e9hsGBifnVK67pQ4gve3m6iSboDkmJjxVEb1d6mRAx5fpMAejooNzNqqbTMVeUN[ 'id' - ]]: DID_zUC74VEqqhEHQcgv4zagSPkqFJxuNWuoBPKjJuHETEUeHLoSqWt92viSsmaWjy82y2cgguc8e9hsGBifnVK67pQ4gve3m6iSboDkmJjxVEb1d6mRAx5fpMAejooNzNqqbTMVeUN, + ]]: + DID_zUC74VEqqhEHQcgv4zagSPkqFJxuNWuoBPKjJuHETEUeHLoSqWt92viSsmaWjy82y2cgguc8e9hsGBifnVK67pQ4gve3m6iSboDkmJjxVEb1d6mRAx5fpMAejooNzNqqbTMVeUN, [DID_SOV_QqEfJxe752NCmWqR5TssZ5['id']]: DID_SOV_QqEfJxe752NCmWqR5TssZ5, + [DID_WEB_LAUNCHPAD['id']]: DID_WEB_LAUNCHPAD, SECURITY_CONTEXT_V1_URL: SECURITY_V1, SECURITY_CONTEXT_V2_URL: SECURITY_V2, SECURITY_CONTEXT_V3_URL: SECURITY_V3_UNSTABLE, @@ -78,10 +102,17 @@ export const DOCUMENTS = { 'https://www.w3.org/2018/credentials/v1': CREDENTIALS_V1, 'https://w3id.org/did/v1': DID_V1, 'https://www.w3.org/ns/did/v1': DID_V1, + 'https://w3.org/ns/did/v1': DID_V1, 'https://w3id.org/citizenship/v1': CITIZENSHIP_V1, + 'https://w3id.org/citizenship/v2': CITIZENSHIP_V2, 'https://www.w3.org/ns/odrl.jsonld': ODRL, 'http://schema.org/': SCHEMA_ORG, 'https://w3id.org/vaccination/v1': VACCINATION_V1, + 'https://w3id.org/vaccination/v2': VACCINATION_V2, + 'https://identity.foundation/presentation-exchange/submission/v1': PRESENTATION_SUBMISSION, + 'https://mattr.global/contexts/vc-extensions/v1': MATTR_VC_EXTENSION_V1, + 'https://purl.imsglobal.org/spec/ob/v3p0/context.json': PURL_OB_V3P0, + 'https://w3c-ccg.github.io/vc-status-rl-2020/contexts/vc-revocation-list-2020/v1.jsonld': VC_REVOCATION_LIST_2020, } async function _customDocumentLoader(url: string): Promise { diff --git a/packages/core/src/modules/vc/__tests__/fixtures.ts b/packages/core/src/modules/vc/__tests__/fixtures.ts index 491a388f98..9e8a6caa16 100644 --- a/packages/core/src/modules/vc/__tests__/fixtures.ts +++ b/packages/core/src/modules/vc/__tests__/fixtures.ts @@ -13,6 +13,36 @@ export const Ed25519Signature2018Fixtures = { }, }, }, + TEST_LD_DOCUMENT_2: { + '@context': [ + 'https://www.w3.org/2018/credentials/v1', + 'https://w3id.org/security/suites/ed25519-2020/v1', + 'https://w3id.org/citizenship/v1', + ], + id: 'https://issuer.oidp.uscis.gov/credentials/83627465', + type: ['VerifiableCredential', 'PermanentResidentCard'], + issuer: '', + identifier: '83627465', + name: 'Permanent Resident Card', + description: 'Government of Example Permanent Resident Card.', + issuanceDate: '2019-12-03T12:19:52Z', + expirationDate: '2029-12-03T12:19:52Z', + credentialSubject: { + id: 'did:example:b34ca6cd37bbf23', + type: ['PermanentResident', 'Person'], + givenName: 'JOHN', + familyName: 'SMITH', + gender: 'Male', + image: 'data:image/png;base64,iVBORw0KGgokJggg==', + residentSince: '2015-01-01', + lprCategory: 'C09', + lprNumber: '999-999-999', + commuterClassification: 'C1', + birthCountry: 'Bahamas', + birthDate: '1958-07-17', + }, + }, + TEST_LD_DOCUMENT_SIGNED: { '@context': [CREDENTIALS_CONTEXT_V1_URL, 'https://www.w3.org/2018/credentials/examples/v1'], type: ['VerifiableCredential', 'UniversityDegreeCredential'], diff --git a/packages/core/src/modules/vc/constants.ts b/packages/core/src/modules/vc/constants.ts index 6b298625df..a9636cf016 100644 --- a/packages/core/src/modules/vc/constants.ts +++ b/packages/core/src/modules/vc/constants.ts @@ -5,6 +5,7 @@ export const SECURITY_CONTEXT_URL = SECURITY_CONTEXT_V2_URL export const SECURITY_X25519_CONTEXT_URL = 'https://w3id.org/security/suites/x25519-2019/v1' export const DID_V1_CONTEXT_URL = 'https://www.w3.org/ns/did/v1' export const CREDENTIALS_CONTEXT_V1_URL = 'https://www.w3.org/2018/credentials/v1' +export const BANKACCOUNT_CONTEXT_V1_URL = 'https://www.w3.org/2018/bankaccount/v1' export const SECURITY_CONTEXT_BBS_URL = 'https://w3id.org/security/bbs/v1' export const CREDENTIALS_ISSUER_URL = 'https://www.w3.org/2018/credentials#issuer' export const SECURITY_PROOF_URL = 'https://w3id.org/security#proof' @@ -12,3 +13,4 @@ export const SECURITY_SIGNATURE_URL = 'https://w3id.org/security#signature' export const VERIFIABLE_CREDENTIAL_TYPE = 'VerifiableCredential' export const VERIFIABLE_PRESENTATION_TYPE = 'VerifiablePresentation' export const EXPANDED_TYPE_CREDENTIALS_CONTEXT_V1_VC_TYPE = 'https://www.w3.org/2018/credentials#VerifiableCredential' +export const SECURITY_JWS_CONTEXT_URL = 'https://w3id.org/security/suites/jws-2020/v1' diff --git a/packages/core/src/modules/vc/index.ts b/packages/core/src/modules/vc/index.ts index 289e8fbcd9..d97dd0dcd2 100644 --- a/packages/core/src/modules/vc/index.ts +++ b/packages/core/src/modules/vc/index.ts @@ -1,6 +1,6 @@ export * from './W3cCredentialService' export * from './repository/W3cCredentialRecord' -export * from './W3cVcModule' +export * from './W3cCredentialsModule' export * from './models' export type { DocumentLoader, Proof } from './jsonldUtil' export { w3cDate, orArrayToArray } from './jsonldUtil' diff --git a/packages/core/src/modules/vc/jsonldUtil.ts b/packages/core/src/modules/vc/jsonldUtil.ts index 761e22726f..b4500c3ba1 100644 --- a/packages/core/src/modules/vc/jsonldUtil.ts +++ b/packages/core/src/modules/vc/jsonldUtil.ts @@ -1,6 +1,6 @@ +import type { GetProofsOptions, GetProofsResult, GetTypeOptions } from './models' import type { JsonObject, JsonValue } from '../../types' import type { SingleOrArray } from '../../utils/type' -import type { GetProofsOptions, GetProofsResult, GetTypeOptions } from './models' import { SECURITY_CONTEXT_URL } from './constants' import jsonld from './libraries/jsonld' diff --git a/packages/core/src/modules/vc/libraries/documentLoader.ts b/packages/core/src/modules/vc/libraries/documentLoader.ts index d8679884d8..50fcde95d0 100644 --- a/packages/core/src/modules/vc/libraries/documentLoader.ts +++ b/packages/core/src/modules/vc/libraries/documentLoader.ts @@ -1,5 +1,5 @@ -import type { AgentContext } from '../../../agent/context/AgentContext' import type { DocumentLoader } from './jsonld' +import type { AgentContext } from '../../../agent/context/AgentContext' import { AriesFrameworkError } from '../../../error/AriesFrameworkError' import { DidResolverService } from '../../dids' diff --git a/packages/core/src/modules/vc/models/W3cCredentialServiceOptions.ts b/packages/core/src/modules/vc/models/W3cCredentialServiceOptions.ts index c15302a7a8..419a11dcda 100644 --- a/packages/core/src/modules/vc/models/W3cCredentialServiceOptions.ts +++ b/packages/core/src/modules/vc/models/W3cCredentialServiceOptions.ts @@ -1,10 +1,10 @@ -import type { JsonObject } from '../../../types' -import type { SingleOrArray } from '../../../utils/type' -import type { ProofPurpose } from '../proof-purposes/ProofPurpose' import type { W3cCredential } from './credential/W3cCredential' import type { W3cVerifiableCredential } from './credential/W3cVerifiableCredential' import type { W3cPresentation } from './presentation/W3cPresentation' import type { W3cVerifiablePresentation } from './presentation/W3cVerifiablePresentation' +import type { JsonObject } from '../../../types' +import type { SingleOrArray } from '../../../utils/type' +import type { ProofPurpose } from '../proof-purposes/ProofPurpose' export interface SignCredentialOptions { credential: W3cCredential @@ -17,6 +17,7 @@ export interface SignCredentialOptions { export interface VerifyCredentialOptions { credential: W3cVerifiableCredential proofPurpose?: ProofPurpose + verifyRevocationState?: boolean } export interface StoreCredentialOptions { @@ -39,8 +40,6 @@ export interface SignPresentationOptions { export interface VerifyPresentationOptions { presentation: W3cVerifiablePresentation - proofType: string - verificationMethod: string purpose?: ProofPurpose challenge?: string } diff --git a/packages/core/src/modules/vc/models/credential/W3cCredential.ts b/packages/core/src/modules/vc/models/credential/W3cCredential.ts index beed50d700..16f0b3f71c 100644 --- a/packages/core/src/modules/vc/models/credential/W3cCredential.ts +++ b/packages/core/src/modules/vc/models/credential/W3cCredential.ts @@ -1,6 +1,6 @@ -import type { JsonObject } from '../../../../types' import type { CredentialSubjectOptions } from './CredentialSubject' import type { IssuerOptions } from './Issuer' +import type { JsonObject } from '../../../../types' import type { ValidationOptions } from 'class-validator' import { Expose, Type } from 'class-transformer' @@ -40,7 +40,7 @@ export class W3cCredential { @Expose({ name: '@context' }) @IsJsonLdContext() - public context!: Array | JsonObject + public context!: Array | JsonObject @IsOptional() @IsUri() @@ -91,7 +91,7 @@ export class W3cCredential { return [this.credentialSubject.id] } - public get contexts(): Array { + public get contexts(): Array { if (Array.isArray(this.context)) { return this.context.filter((x) => typeof x === 'string') } diff --git a/packages/core/src/modules/vc/models/credential/W3cVerifiableCredential.ts b/packages/core/src/modules/vc/models/credential/W3cVerifiableCredential.ts index 4fbce4e41e..67c09e1ac2 100644 --- a/packages/core/src/modules/vc/models/credential/W3cVerifiableCredential.ts +++ b/packages/core/src/modules/vc/models/credential/W3cVerifiableCredential.ts @@ -1,5 +1,5 @@ -import type { LinkedDataProofOptions } from '../LinkedDataProof' import type { W3cCredentialOptions } from './W3cCredential' +import type { LinkedDataProofOptions } from '../LinkedDataProof' import { instanceToPlain, plainToInstance, Transform, TransformationType } from 'class-transformer' diff --git a/packages/core/src/modules/vc/models/credential/W3cVerifyCredentialResult.ts b/packages/core/src/modules/vc/models/credential/W3cVerifyCredentialResult.ts index 9f8880467a..aaecf7c931 100644 --- a/packages/core/src/modules/vc/models/credential/W3cVerifyCredentialResult.ts +++ b/packages/core/src/modules/vc/models/credential/W3cVerifyCredentialResult.ts @@ -1,5 +1,5 @@ -import type { JsonObject } from '../../../../types' import type { W3cVerifiableCredential } from './W3cVerifiableCredential' +import type { JsonObject } from '../../../../types' export interface VerifyCredentialResult { credential: W3cVerifiableCredential diff --git a/packages/core/src/modules/vc/models/index.ts b/packages/core/src/modules/vc/models/index.ts index 45d0a281f7..6dcfacbc39 100644 --- a/packages/core/src/modules/vc/models/index.ts +++ b/packages/core/src/modules/vc/models/index.ts @@ -1,6 +1,7 @@ export * from './credential/W3cCredential' export * from './credential/W3cVerifiableCredential' export * from './credential/W3cVerifyCredentialResult' +export * from './W3cCredentialServiceOptions' export * from './presentation/VerifyPresentationResult' export * from './presentation/W3cPresentation' export * from './presentation/W3cVerifiablePresentation' diff --git a/packages/core/src/modules/vc/models/presentation/W3cVerifiablePresentation.ts b/packages/core/src/modules/vc/models/presentation/W3cVerifiablePresentation.ts index fa1fd6001a..67a106ee59 100644 --- a/packages/core/src/modules/vc/models/presentation/W3cVerifiablePresentation.ts +++ b/packages/core/src/modules/vc/models/presentation/W3cVerifiablePresentation.ts @@ -1,5 +1,5 @@ -import type { LinkedDataProofOptions } from '../LinkedDataProof' import type { W3cPresentationOptions } from './W3cPresentation' +import type { LinkedDataProofOptions } from '../LinkedDataProof' import { SingleOrArray } from '../../../../utils/type' import { IsInstanceOrArrayOfInstances } from '../../../../utils/validators' diff --git a/packages/core/src/modules/vc/repository/W3cCredentialRecord.ts b/packages/core/src/modules/vc/repository/W3cCredentialRecord.ts index 3c10234210..6ba94480be 100644 --- a/packages/core/src/modules/vc/repository/W3cCredentialRecord.ts +++ b/packages/core/src/modules/vc/repository/W3cCredentialRecord.ts @@ -44,12 +44,16 @@ export class W3cCredentialRecord extends BaseRecord typeof ctx === 'string') as string[] + return { ...this._tags, issuerId: this.credential.issuerId, subjectIds: this.credential.credentialSubjectIds, schemaIds: this.credential.credentialSchemaIds, - contexts: this.credential.contexts, + contexts: stringContexts, proofTypes: this.credential.proofTypes, givenId: this.credential.id, } diff --git a/packages/core/src/modules/vc/validators.ts b/packages/core/src/modules/vc/validators.ts index 0bce78fa79..317b286cf6 100644 --- a/packages/core/src/modules/vc/validators.ts +++ b/packages/core/src/modules/vc/validators.ts @@ -2,6 +2,8 @@ import type { ValidationOptions } from 'class-validator' import { buildMessage, isString, isURL, ValidateBy } from 'class-validator' +import { isJsonObject } from '../../utils/type' + import { CREDENTIALS_CONTEXT_V1_URL } from './constants' export function IsJsonLdContext(validationOptions?: ValidationOptions): PropertyDecorator { @@ -13,8 +15,11 @@ export function IsJsonLdContext(validationOptions?: ValidationOptions): Property // If value is an array, check if all items are strings, are URLs and that // the first entry is a verifiable credential context if (Array.isArray(value)) { - return value.every((v) => isString(v) && isURL(v)) && value[0] === CREDENTIALS_CONTEXT_V1_URL + return value.every( + (v) => (isString(v) && isURL(v)) || (isJsonObject(v) && value[0] === CREDENTIALS_CONTEXT_V1_URL) + ) } + // If value is not an array, check if it is an object (assuming it's a JSON-LD context definition) if (typeof value === 'object') { return true diff --git a/packages/core/src/plugins/Module.ts b/packages/core/src/plugins/Module.ts index 8170b159de..183a47c7f8 100644 --- a/packages/core/src/plugins/Module.ts +++ b/packages/core/src/plugins/Module.ts @@ -1,10 +1,18 @@ +import type { DependencyManager } from './DependencyManager' +import type { AgentContext } from '../agent' import type { FeatureRegistry } from '../agent/FeatureRegistry' +import type { Update } from '../storage/migration/updates' import type { Constructor } from '../utils/mixins' -import type { DependencyManager } from './DependencyManager' export interface Module { api?: Constructor register(dependencyManager: DependencyManager, featureRegistry: FeatureRegistry): void + initialize?(agentContext: AgentContext): Promise + + /** + * List of updates that should be executed when the framework version is updated. + */ + updates?: Update[] } export interface ApiModule extends Module { diff --git a/packages/core/src/storage/BaseRecord.ts b/packages/core/src/storage/BaseRecord.ts index 9a8408032d..5b63bcfe86 100644 --- a/packages/core/src/storage/BaseRecord.ts +++ b/packages/core/src/storage/BaseRecord.ts @@ -18,7 +18,10 @@ export type RecordTags = ReturnType + // We want an empty object, as Record will make typescript + // not infer the types correctly + // eslint-disable-next-line @typescript-eslint/ban-types + MetadataValues = {} > { protected _tags: CustomTags = {} as CustomTags diff --git a/packages/core/src/storage/FileSystem.ts b/packages/core/src/storage/FileSystem.ts index 6673bc333c..9a5710835e 100644 --- a/packages/core/src/storage/FileSystem.ts +++ b/packages/core/src/storage/FileSystem.ts @@ -1,8 +1,19 @@ +import type { Buffer } from '../utils/buffer' + +export interface DownloadToFileOptions { + verifyHash?: { algorithm: 'sha256'; hash: Buffer } +} + export interface FileSystem { - readonly basePath: string + readonly dataPath: string + readonly cachePath: string + readonly tempPath: string exists(path: string): Promise + createDirectory(path: string): Promise + copyFile(sourcePath: string, destinationPath: string): Promise write(path: string, data: string): Promise read(path: string): Promise - downloadToFile(url: string, path: string): Promise + delete(path: string): Promise + downloadToFile(url: string, path: string, options?: DownloadToFileOptions): Promise } diff --git a/packages/core/src/storage/InMemoryMessageRepository.ts b/packages/core/src/storage/InMemoryMessageRepository.ts index 45fb61f0e4..842d24f5d8 100644 --- a/packages/core/src/storage/InMemoryMessageRepository.ts +++ b/packages/core/src/storage/InMemoryMessageRepository.ts @@ -1,5 +1,5 @@ -import type { EncryptedMessage } from '../didcomm/types' import type { MessageRepository } from './MessageRepository' +import type { EncryptedMessage } from '../didcomm/types' import { InjectionSymbols } from '../constants' import { Logger } from '../logger' diff --git a/packages/core/src/storage/Metadata.ts b/packages/core/src/storage/Metadata.ts index 87c3e0d298..c635c1c2c5 100644 --- a/packages/core/src/storage/Metadata.ts +++ b/packages/core/src/storage/Metadata.ts @@ -1,5 +1,9 @@ +// Any is used to prevent frustrating TS errors if we just want to store arbitrary json data +// eslint-disable-next-line @typescript-eslint/no-explicit-any +type MetadataValue = Record + export type MetadataBase = { - [key: string]: Record + [key: string]: MetadataValue } /** @@ -31,7 +35,7 @@ export class Metadata { * @returns the value saved in the key value pair * @returns null when the key could not be found */ - public get, Key extends string = string>( + public get( key: Key ): (Key extends keyof MetadataTypes ? MetadataTypes[Key] : Value) | null { return (this.data[key] as Key extends keyof MetadataTypes ? MetadataTypes[Key] : Value) ?? null @@ -43,11 +47,11 @@ export class Metadata { * @param key the key to set the metadata by * @param value the value to set in the metadata */ - public set, Key extends string = string>( + public set( key: Key, value: Key extends keyof MetadataTypes ? MetadataTypes[Key] : Value ): void { - this.data[key] = value as Record + this.data[key] = value as MetadataValue } /** @@ -56,7 +60,7 @@ export class Metadata { * @param key the key to add the metadata at * @param value the value to add in the metadata */ - public add, Key extends string = string>( + public add( key: Key, value: Partial ): void { diff --git a/packages/core/src/storage/Repository.ts b/packages/core/src/storage/Repository.ts index 674d2e3e7a..f2cd0ed671 100644 --- a/packages/core/src/storage/Repository.ts +++ b/packages/core/src/storage/Repository.ts @@ -1,8 +1,8 @@ -import type { AgentContext } from '../agent' -import type { EventEmitter } from '../agent/EventEmitter' import type { BaseRecord } from './BaseRecord' import type { RecordSavedEvent, RecordUpdatedEvent, RecordDeletedEvent } from './RepositoryEvents' import type { BaseRecordConstructor, Query, StorageService } from './StorageService' +import type { AgentContext } from '../agent' +import type { EventEmitter } from '../agent/EventEmitter' import { RecordDuplicateError, RecordNotFoundError } from '../error' import { JsonTransformer } from '../utils/JsonTransformer' @@ -68,12 +68,19 @@ export class Repository> { } /** - * Delete record by id. Returns null if no record is found + * Delete record by id. Throws {RecordNotFoundError} if no record is found * @param id the id of the record to delete * @returns */ public async deleteById(agentContext: AgentContext, id: string): Promise { await this.storageService.deleteById(agentContext, this.recordClass, id) + + this.eventEmitter.emit>(agentContext, { + type: RepositoryEventTypes.RecordDeleted, + payload: { + record: { id, type: this.recordClass.type }, + }, + }) } /** @inheritDoc {StorageService#getById} */ diff --git a/packages/core/src/storage/RepositoryEvents.ts b/packages/core/src/storage/RepositoryEvents.ts index cf9a0d3157..ac6524eb01 100644 --- a/packages/core/src/storage/RepositoryEvents.ts +++ b/packages/core/src/storage/RepositoryEvents.ts @@ -1,5 +1,5 @@ -import type { BaseEvent } from '../agent/Events' import type { BaseRecord } from './BaseRecord' +import type { BaseEvent } from '../agent/Events' export enum RepositoryEventTypes { RecordSaved = 'RecordSaved', @@ -27,6 +27,6 @@ export interface RecordUpdatedEvent> extends export interface RecordDeletedEvent> extends BaseEvent { type: typeof RepositoryEventTypes.RecordDeleted payload: { - record: T + record: T | { id: string; type: string } } } diff --git a/packages/core/src/storage/StorageService.ts b/packages/core/src/storage/StorageService.ts index 6ea701df56..5a80b83781 100644 --- a/packages/core/src/storage/StorageService.ts +++ b/packages/core/src/storage/StorageService.ts @@ -1,10 +1,10 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ +import type { BaseRecord, TagsBase } from './BaseRecord' import type { AgentContext } from '../agent' import type { Constructor } from '../utils/mixins' -import type { BaseRecord, TagsBase } from './BaseRecord' // https://stackoverflow.com/questions/51954558/how-can-i-remove-a-wider-type-from-a-union-type-without-removing-its-subtypes-in/51955852#51955852 -export type SimpleQuery = Partial> & TagsBase +export type SimpleQuery> = Partial> & TagsBase interface AdvancedQuery { $and?: Query[] diff --git a/packages/core/src/storage/__tests__/DidCommMessageRepository.test.ts b/packages/core/src/storage/__tests__/DidCommMessageRepository.test.ts index 067a290dcb..1329595f56 100644 --- a/packages/core/src/storage/__tests__/DidCommMessageRepository.test.ts +++ b/packages/core/src/storage/__tests__/DidCommMessageRepository.test.ts @@ -1,15 +1,17 @@ +import type { StorageService } from '../StorageService' + import { Subject } from 'rxjs' +import { InMemoryStorageService } from '../../../../../tests/InMemoryStorageService' import { getAgentConfig, getAgentContext, mockFunction } from '../../../tests/helpers' import { EventEmitter } from '../../agent/EventEmitter' import { ConnectionInvitationMessage } from '../../modules/connections' import { JsonTransformer } from '../../utils/JsonTransformer' -import { IndyStorageService } from '../IndyStorageService' import { DidCommMessageRecord, DidCommMessageRepository, DidCommMessageRole } from '../didcomm' -jest.mock('../IndyStorageService') +jest.mock('../../../../../tests/InMemoryStorageService') -const StorageMock = IndyStorageService as unknown as jest.Mock> +const StorageMock = InMemoryStorageService as unknown as jest.Mock> const invitationJson = { '@type': 'https://didcomm.org/connections/1.0/invitation', @@ -24,7 +26,7 @@ const agentContext = getAgentContext() describe('DidCommMessageRepository', () => { let repository: DidCommMessageRepository - let storageMock: IndyStorageService + let storageMock: StorageService let eventEmitter: EventEmitter beforeEach(async () => { diff --git a/packages/core/src/storage/__tests__/Repository.test.ts b/packages/core/src/storage/__tests__/Repository.test.ts index 11cb3dd9cc..ae56c636af 100644 --- a/packages/core/src/storage/__tests__/Repository.test.ts +++ b/packages/core/src/storage/__tests__/Repository.test.ts @@ -1,27 +1,28 @@ import type { AgentContext } from '../../agent' import type { TagsBase } from '../BaseRecord' import type { RecordDeletedEvent, RecordSavedEvent, RecordUpdatedEvent } from '../RepositoryEvents' +import type { StorageService } from '../StorageService' import { Subject } from 'rxjs' +import { InMemoryStorageService } from '../../../../../tests/InMemoryStorageService' import { getAgentConfig, getAgentContext, mockFunction } from '../../../tests/helpers' import { EventEmitter } from '../../agent/EventEmitter' import { AriesFrameworkError, RecordDuplicateError, RecordNotFoundError } from '../../error' -import { IndyStorageService } from '../IndyStorageService' import { Repository } from '../Repository' import { RepositoryEventTypes } from '../RepositoryEvents' import { TestRecord } from './TestRecord' -jest.mock('../IndyStorageService') +jest.mock('../../../../../tests/InMemoryStorageService') -const StorageMock = IndyStorageService as unknown as jest.Mock> +const StorageMock = InMemoryStorageService as unknown as jest.Mock> const config = getAgentConfig('Repository') describe('Repository', () => { let repository: Repository - let storageMock: IndyStorageService + let storageMock: StorageService let agentContext: AgentContext let eventEmitter: EventEmitter @@ -145,6 +146,28 @@ describe('Repository', () => { expect(storageMock.deleteById).toBeCalledWith(agentContext, TestRecord, 'test-id') }) + + it(`should emit deleted event`, async () => { + const eventListenerMock = jest.fn() + eventEmitter.on>(RepositoryEventTypes.RecordDeleted, eventListenerMock) + + const record = getRecord({ id: 'test-id' }) + + await repository.deleteById(agentContext, record.id) + + expect(eventListenerMock).toHaveBeenCalledWith({ + type: 'RecordDeleted', + metadata: { + contextCorrelationId: 'mock', + }, + payload: { + record: expect.objectContaining({ + id: record.id, + type: record.type, + }), + }, + }) + }) }) describe('getById()', () => { diff --git a/packages/core/src/storage/didcomm/DidCommMessageRecord.ts b/packages/core/src/storage/didcomm/DidCommMessageRecord.ts index 4a962af3f2..ed01dfa714 100644 --- a/packages/core/src/storage/didcomm/DidCommMessageRecord.ts +++ b/packages/core/src/storage/didcomm/DidCommMessageRecord.ts @@ -1,6 +1,6 @@ +import type { DidCommMessageRole } from './DidCommMessageRole' import type { ConstructableDidCommMessage } from '../../didcomm' import type { JsonObject } from '../../types' -import type { DidCommMessageRole } from './DidCommMessageRole' import { AriesFrameworkError } from '../../error' import { JsonTransformer } from '../../utils/JsonTransformer' diff --git a/packages/core/src/storage/didcomm/DidCommMessageRepository.ts b/packages/core/src/storage/didcomm/DidCommMessageRepository.ts index baae24096a..bea56cfda8 100644 --- a/packages/core/src/storage/didcomm/DidCommMessageRepository.ts +++ b/packages/core/src/storage/didcomm/DidCommMessageRepository.ts @@ -1,8 +1,8 @@ +import type { DidCommMessageRole } from './DidCommMessageRole' import type { AgentContext } from '../../agent' import type { AgentMessage } from '../../agent/AgentMessage' import type { ConstructableDidCommMessage } from '../../didcomm' import type { JsonObject } from '../../types' -import type { DidCommMessageRole } from './DidCommMessageRole' import { EventEmitter } from '../../agent/EventEmitter' import { InjectionSymbols } from '../../constants' diff --git a/packages/core/src/storage/migration/StorageUpdateService.ts b/packages/core/src/storage/migration/StorageUpdateService.ts index eef16af7ff..b5b196406d 100644 --- a/packages/core/src/storage/migration/StorageUpdateService.ts +++ b/packages/core/src/storage/migration/StorageUpdateService.ts @@ -1,6 +1,6 @@ +import type { UpdateToVersion } from './updates' import type { AgentContext } from '../../agent' import type { VersionString } from '../../utils/version' -import type { UpdateToVersion } from './updates' import { InjectionSymbols } from '../../constants' import { Logger } from '../../logger' diff --git a/packages/core/src/storage/migration/UpdateAssistant.ts b/packages/core/src/storage/migration/UpdateAssistant.ts index 756da0e093..7a99b8d408 100644 --- a/packages/core/src/storage/migration/UpdateAssistant.ts +++ b/packages/core/src/storage/migration/UpdateAssistant.ts @@ -1,11 +1,12 @@ +import type { Update, UpdateConfig, UpdateToVersion } from './updates' import type { BaseAgent } from '../../agent/BaseAgent' +import type { Module } from '../../plugins' import type { FileSystem } from '../FileSystem' -import type { UpdateConfig, UpdateToVersion } from './updates' import { InjectionSymbols } from '../../constants' import { AriesFrameworkError } from '../../error' -import { isIndyError } from '../../utils/indyError' import { isFirstVersionEqualToSecond, isFirstVersionHigherThanSecond, parseVersionString } from '../../utils/version' +import { WalletExportPathExistsError } from '../../wallet/error' import { WalletError } from '../../wallet/error/WalletError' import { StorageUpdateService } from './StorageUpdateService' @@ -129,7 +130,7 @@ export class UpdateAssistant = BaseAgent> { ) } - if (neededUpdates.length == 0) { + if (neededUpdates.length === 0) { this.agent.config.logger.info('No update needed. Agent storage is up to date.') return } @@ -146,17 +147,50 @@ export class UpdateAssistant = BaseAgent> { try { for (const update of neededUpdates) { + const registeredModules = Object.values(this.agent.dependencyManager.registeredModules) + const modulesWithUpdate: Array<{ module: Module; update: Update }> = [] + + // Filter modules that have an update script for the current update + for (const registeredModule of registeredModules) { + const moduleUpdate = registeredModule.updates?.find( + (module) => module.fromVersion === update.fromVersion && module.toVersion === update.toVersion + ) + + if (moduleUpdate) { + modulesWithUpdate.push({ + module: registeredModule, + update: moduleUpdate, + }) + } + } + this.agent.config.logger.info( - `Starting update of agent storage from version ${update.fromVersion} to version ${update.toVersion}` + `Starting update of agent storage from version ${update.fromVersion} to version ${update.toVersion}. Found ${modulesWithUpdate.length} extension module(s) with update scripts` ) await update.doUpdate(this.agent, this.updateConfig) + this.agent.config.logger.info( + `Finished update of core agent storage from version ${update.fromVersion} to version ${update.toVersion}. Starting update of extension modules` + ) + + for (const moduleWithUpdate of modulesWithUpdate) { + this.agent.config.logger.info( + `Starting update of extension module ${moduleWithUpdate.module.constructor.name} from version ${moduleWithUpdate.update.fromVersion} to version ${moduleWithUpdate.update.toVersion}` + ) + await moduleWithUpdate.update.doUpdate(this.agent, this.updateConfig) + this.agent.config.logger.info( + `Finished update of extension module ${moduleWithUpdate.module.constructor.name} from version ${moduleWithUpdate.update.fromVersion} to version ${moduleWithUpdate.update.toVersion}` + ) + } + // Update the framework version in storage await this.storageUpdateService.setCurrentStorageVersion(this.agent.context, update.toVersion) this.agent.config.logger.info( `Successfully updated agent storage from version ${update.fromVersion} to version ${update.toVersion}` ) } + // Delete backup file, as it is not needed anymore + await this.fileSystem.delete(this.getBackupPath(updateIdentifier)) } catch (error) { this.agent.config.logger.fatal('An error occurred while updating the wallet. Restoring backup', { error, @@ -164,13 +198,16 @@ export class UpdateAssistant = BaseAgent> { // In the case of an error we want to restore the backup await this.restoreBackup(updateIdentifier) + // Delete backup file, as wallet was already restored (backup-error file will persist though) + await this.fileSystem.delete(this.getBackupPath(updateIdentifier)) + throw error } } catch (error) { // Backup already exists at path - if (error instanceof AriesFrameworkError && isIndyError(error.cause, 'CommonIOError')) { + if (error instanceof WalletExportPathExistsError) { const backupPath = this.getBackupPath(updateIdentifier) - const errorMessage = `Error updating storage with updateIdentifier ${updateIdentifier} because of an IO error. This is probably because the backup at path ${backupPath} already exists` + const errorMessage = `Error updating storage with updateIdentifier ${updateIdentifier} because the backup at path ${backupPath} already exists` this.agent.config.logger.fatal(errorMessage, { error, updateIdentifier, @@ -192,7 +229,7 @@ export class UpdateAssistant = BaseAgent> { } private getBackupPath(backupIdentifier: string) { - return `${this.fileSystem.basePath}/afj/migration/backup/${backupIdentifier}` + return `${this.fileSystem.dataPath}/migration/backup/${backupIdentifier}` } private async createBackup(backupIdentifier: string) { diff --git a/packages/core/src/storage/migration/__tests__/0.1.test.ts b/packages/core/src/storage/migration/__tests__/0.1.test.ts index 139b73024a..ff2033f3ba 100644 --- a/packages/core/src/storage/migration/__tests__/0.1.test.ts +++ b/packages/core/src/storage/migration/__tests__/0.1.test.ts @@ -1,10 +1,12 @@ -import type { FileSystem } from '../../../../src' import type { V0_1ToV0_2UpdateConfig } from '../updates/0.1-0.2' -import { unlinkSync, readFileSync } from 'fs' +import { readFileSync } from 'fs' import path from 'path' import { InMemoryStorageService } from '../../../../../../tests/InMemoryStorageService' +import { IndySdkWallet } from '../../../../../indy-sdk/src' +import { IndySdkSymbol } from '../../../../../indy-sdk/src/types' +import { indySdk } from '../../../../../indy-sdk/tests/setupIndySdkModule' import { Agent } from '../../../../src' import { agentDependencies as dependencies } from '../../../../tests/helpers' import { InjectionSymbols } from '../../../constants' @@ -14,7 +16,6 @@ import { UpdateAssistant } from '../UpdateAssistant' const backupDate = new Date('2022-01-21T22:50:20.522Z') jest.useFakeTimers().setSystemTime(backupDate) -const backupIdentifier = backupDate.getTime() const walletConfig = { id: `Wallet: 0.1 Update`, @@ -39,6 +40,9 @@ describe('UpdateAssistant | v0.1 - v0.2', () => { const dependencyManager = new DependencyManager() const storageService = new InMemoryStorageService() dependencyManager.registerInstance(InjectionSymbols.StorageService, storageService) + // If we register the IndySdkModule it will register the storage service, but we use in memory storage here + dependencyManager.registerContextScoped(InjectionSymbols.Wallet, IndySdkWallet) + dependencyManager.registerInstance(IndySdkSymbol, indySdk) const agent = new Agent( { @@ -48,8 +52,6 @@ describe('UpdateAssistant | v0.1 - v0.2', () => { dependencyManager ) - const fileSystem = agent.injectionContainer.resolve(InjectionSymbols.FileSystem) - const updateAssistant = new UpdateAssistant(agent, { v0_1ToV0_2: { mediationRoleUpdateStrategy, @@ -79,10 +81,6 @@ describe('UpdateAssistant | v0.1 - v0.2', () => { delete storageService.records.MEDIATOR_ROUTING_RECORD expect(storageService.records).toMatchSnapshot(mediationRoleUpdateStrategy) - // Need to remove backupFiles after each run so we don't get IOErrors - const backupPath = `${fileSystem.basePath}/afj/migration/backup/${backupIdentifier}` - unlinkSync(backupPath) - await agent.shutdown() await agent.wallet.delete() } @@ -101,6 +99,9 @@ describe('UpdateAssistant | v0.1 - v0.2', () => { const dependencyManager = new DependencyManager() const storageService = new InMemoryStorageService() dependencyManager.registerInstance(InjectionSymbols.StorageService, storageService) + // If we register the IndySdkModule it will register the storage service, but we use in memory storage here + dependencyManager.registerContextScoped(InjectionSymbols.Wallet, IndySdkWallet) + dependencyManager.registerInstance(IndySdkSymbol, indySdk) const agent = new Agent( { @@ -110,8 +111,6 @@ describe('UpdateAssistant | v0.1 - v0.2', () => { dependencyManager ) - const fileSystem = agent.injectionContainer.resolve(InjectionSymbols.FileSystem) - const updateAssistant = new UpdateAssistant(agent, { v0_1ToV0_2: { mediationRoleUpdateStrategy: 'doNotChange', @@ -142,10 +141,6 @@ describe('UpdateAssistant | v0.1 - v0.2', () => { delete storageService.records.MEDIATOR_ROUTING_RECORD expect(storageService.records).toMatchSnapshot() - // Need to remove backupFiles after each run so we don't get IOErrors - const backupPath = `${fileSystem.basePath}/afj/migration/backup/${backupIdentifier}` - unlinkSync(backupPath) - await agent.shutdown() await agent.wallet.delete() @@ -165,6 +160,9 @@ describe('UpdateAssistant | v0.1 - v0.2', () => { const dependencyManager = new DependencyManager() const storageService = new InMemoryStorageService() dependencyManager.registerInstance(InjectionSymbols.StorageService, storageService) + // If we register the IndySdkModule it will register the storage service, but we use in memory storage here + dependencyManager.registerContextScoped(InjectionSymbols.Wallet, IndySdkWallet) + dependencyManager.registerInstance(IndySdkSymbol, indySdk) const agent = new Agent( { @@ -174,8 +172,6 @@ describe('UpdateAssistant | v0.1 - v0.2', () => { dependencyManager ) - const fileSystem = agent.injectionContainer.resolve(InjectionSymbols.FileSystem) - const updateAssistant = new UpdateAssistant(agent, { v0_1ToV0_2: { mediationRoleUpdateStrategy: 'doNotChange', @@ -206,10 +202,6 @@ describe('UpdateAssistant | v0.1 - v0.2', () => { delete storageService.records.MEDIATOR_ROUTING_RECORD expect(storageService.records).toMatchSnapshot() - // Need to remove backupFiles after each run so we don't get IOErrors - const backupPath = `${fileSystem.basePath}/afj/migration/backup/${backupIdentifier}` - unlinkSync(backupPath) - await agent.shutdown() await agent.wallet.delete() @@ -229,6 +221,9 @@ describe('UpdateAssistant | v0.1 - v0.2', () => { const dependencyManager = new DependencyManager() const storageService = new InMemoryStorageService() dependencyManager.registerInstance(InjectionSymbols.StorageService, storageService) + // If we register the IndySdkModule it will register the storage service, but we use in memory storage here + dependencyManager.registerContextScoped(InjectionSymbols.Wallet, IndySdkWallet) + dependencyManager.registerInstance(IndySdkSymbol, indySdk) const agent = new Agent( { @@ -242,8 +237,6 @@ describe('UpdateAssistant | v0.1 - v0.2', () => { dependencyManager ) - const fileSystem = agent.injectionContainer.resolve(InjectionSymbols.FileSystem) - const updateAssistant = new UpdateAssistant(agent, { v0_1ToV0_2: { mediationRoleUpdateStrategy: 'doNotChange', @@ -274,10 +267,6 @@ describe('UpdateAssistant | v0.1 - v0.2', () => { delete storageService.records.MEDIATOR_ROUTING_RECORD expect(storageService.records).toMatchSnapshot() - // Need to remove backupFiles after each run so we don't get IOErrors - const backupPath = `${fileSystem.basePath}/afj/migration/backup/${backupIdentifier}` - unlinkSync(backupPath) - await agent.shutdown() await agent.wallet.delete() diff --git a/packages/core/src/storage/migration/__tests__/0.2.test.ts b/packages/core/src/storage/migration/__tests__/0.2.test.ts index b67e361855..a66ef7c832 100644 --- a/packages/core/src/storage/migration/__tests__/0.2.test.ts +++ b/packages/core/src/storage/migration/__tests__/0.2.test.ts @@ -1,10 +1,11 @@ -import type { FileSystem } from '../../../storage/FileSystem' - -import { unlinkSync, readFileSync } from 'fs' +import { readFileSync } from 'fs' import path from 'path' import { InMemoryStorageService } from '../../../../../../tests/InMemoryStorageService' +import { IndySdkWallet } from '../../../../../indy-sdk/src' +import { IndySdkSymbol } from '../../../../../indy-sdk/src/types' import { Agent } from '../../../../src' +import { indySdk } from '../../../../tests' import { agentDependencies } from '../../../../tests/helpers' import { InjectionSymbols } from '../../../constants' import { DependencyManager } from '../../../plugins' @@ -13,7 +14,6 @@ import { UpdateAssistant } from '../UpdateAssistant' const backupDate = new Date('2022-01-21T22:50:20.522Z') jest.useFakeTimers().setSystemTime(backupDate) -const backupIdentifier = backupDate.getTime() const walletConfig = { id: `Wallet: 0.2 Update`, @@ -34,6 +34,9 @@ describe('UpdateAssistant | v0.2 - v0.3.1', () => { const dependencyManager = new DependencyManager() const storageService = new InMemoryStorageService() dependencyManager.registerInstance(InjectionSymbols.StorageService, storageService) + // If we register the IndySdkModule it will register the storage service, but we use in memory storage here + dependencyManager.registerContextScoped(InjectionSymbols.Wallet, IndySdkWallet) + dependencyManager.registerInstance(IndySdkSymbol, indySdk) const agent = new Agent( { @@ -46,8 +49,6 @@ describe('UpdateAssistant | v0.2 - v0.3.1', () => { dependencyManager ) - const fileSystem = agent.injectionContainer.resolve(InjectionSymbols.FileSystem) - const updateAssistant = new UpdateAssistant(agent, { v0_1ToV0_2: { mediationRoleUpdateStrategy: 'doNotChange', @@ -61,7 +62,7 @@ describe('UpdateAssistant | v0.2 - v0.3.1', () => { storageService.records = JSON.parse(aliceCredentialRecordsString) expect(await updateAssistant.isUpToDate()).toBe(false) - expect(await updateAssistant.getNeededUpdates()).toEqual([ + expect(await updateAssistant.getNeededUpdates('0.3.1')).toEqual([ { fromVersion: '0.2', toVersion: '0.3', @@ -83,10 +84,6 @@ describe('UpdateAssistant | v0.2 - v0.3.1', () => { delete storageService.records.MEDIATOR_ROUTING_RECORD expect(storageService.records).toMatchSnapshot() - // Need to remove backupFiles after each run so we don't get IOErrors - const backupPath = `${fileSystem.basePath}/afj/migration/backup/${backupIdentifier}` - unlinkSync(backupPath) - await agent.shutdown() await agent.wallet.delete() @@ -106,6 +103,9 @@ describe('UpdateAssistant | v0.2 - v0.3.1', () => { const dependencyManager = new DependencyManager() const storageService = new InMemoryStorageService() dependencyManager.registerInstance(InjectionSymbols.StorageService, storageService) + // If we register the IndySdkModule it will register the storage service, but we use in memory storage here + dependencyManager.registerContextScoped(InjectionSymbols.Wallet, IndySdkWallet) + dependencyManager.registerInstance(IndySdkSymbol, indySdk) const agent = new Agent( { @@ -119,8 +119,6 @@ describe('UpdateAssistant | v0.2 - v0.3.1', () => { dependencyManager ) - const fileSystem = agent.injectionContainer.resolve(InjectionSymbols.FileSystem) - // We need to manually initialize the wallet as we're using the in memory wallet service // When we call agent.initialize() it will create the wallet and store the current framework // version in the in memory storage service. We need to manually set the records between initializing @@ -137,10 +135,6 @@ describe('UpdateAssistant | v0.2 - v0.3.1', () => { delete storageService.records.MEDIATOR_ROUTING_RECORD expect(storageService.records).toMatchSnapshot() - // Need to remove backupFiles after each run so we don't get IOErrors - const backupPath = `${fileSystem.basePath}/afj/migration/backup/${backupIdentifier}` - unlinkSync(backupPath) - await agent.shutdown() await agent.wallet.delete() @@ -156,6 +150,9 @@ describe('UpdateAssistant | v0.2 - v0.3.1', () => { const dependencyManager = new DependencyManager() const storageService = new InMemoryStorageService() + // If we register the IndySdkModule it will register the storage service, but we use in memory storage here + dependencyManager.registerContextScoped(InjectionSymbols.Wallet, IndySdkWallet) + dependencyManager.registerInstance(IndySdkSymbol, indySdk) dependencyManager.registerInstance(InjectionSymbols.StorageService, storageService) const agent = new Agent( @@ -170,8 +167,6 @@ describe('UpdateAssistant | v0.2 - v0.3.1', () => { dependencyManager ) - const fileSystem = agent.injectionContainer.resolve(InjectionSymbols.FileSystem) - // We need to manually initialize the wallet as we're using the in memory wallet service // When we call agent.initialize() it will create the wallet and store the current framework // version in the in memory storage service. We need to manually set the records between initializing @@ -189,10 +184,6 @@ describe('UpdateAssistant | v0.2 - v0.3.1', () => { expect(storageService.records).toMatchSnapshot() - // Need to remove backupFiles after each run so we don't get IOErrors - const backupPath = `${fileSystem.basePath}/afj/migration/backup/${backupIdentifier}` - unlinkSync(backupPath) - await agent.shutdown() await agent.wallet.delete() diff --git a/packages/core/src/storage/migration/__tests__/0.3.test.ts b/packages/core/src/storage/migration/__tests__/0.3.test.ts index a7ec0f6adb..e8479803fc 100644 --- a/packages/core/src/storage/migration/__tests__/0.3.test.ts +++ b/packages/core/src/storage/migration/__tests__/0.3.test.ts @@ -1,9 +1,10 @@ -import type { FileSystem } from '../../FileSystem' - -import { unlinkSync, readFileSync } from 'fs' +import { readFileSync } from 'fs' import path from 'path' import { InMemoryStorageService } from '../../../../../../tests/InMemoryStorageService' +import { IndySdkWallet } from '../../../../../indy-sdk/src' +import { IndySdkSymbol } from '../../../../../indy-sdk/src/types' +import { indySdk } from '../../../../tests' import { agentDependencies } from '../../../../tests/helpers' import { Agent } from '../../../agent/Agent' import { InjectionSymbols } from '../../../constants' @@ -11,26 +12,31 @@ import { DependencyManager } from '../../../plugins' import * as uuid from '../../../utils/uuid' import { UpdateAssistant } from '../UpdateAssistant' -const backupDate = new Date('2022-01-21T22:50:20.522Z') +const backupDate = new Date('2023-03-18T22:50:20.522Z') jest.useFakeTimers().setSystemTime(backupDate) -const backupIdentifier = backupDate.getTime() const walletConfig = { - id: `Wallet: 0.3 Update`, - key: `Key: 0.3 Update`, + id: `Wallet: 0.4 Update`, + key: `Key: 0.4 Update`, } -describe('UpdateAssistant | v0.3 - v0.3.1', () => { - it(`should correctly update the did records`, async () => { +describe('UpdateAssistant | v0.3.1 - v0.4', () => { + it(`should correctly update the did records and remove cache records`, async () => { // We need to mock the uuid generation to make sure we generate consistent uuids for the new records created. let uuidCounter = 1 const uuidSpy = jest.spyOn(uuid, 'uuid').mockImplementation(() => `${uuidCounter++}-4e4f-41d9-94c4-f49351b811f1`) - const aliceDidRecordsString = readFileSync(path.join(__dirname, '__fixtures__/alice-8-dids-0.3.json'), 'utf8') + const aliceDidRecordsString = readFileSync( + path.join(__dirname, '__fixtures__/alice-2-sov-dids-one-cache-record-0.3.json'), + 'utf8' + ) const dependencyManager = new DependencyManager() const storageService = new InMemoryStorageService() dependencyManager.registerInstance(InjectionSymbols.StorageService, storageService) + // If we register the IndySdkModule it will register the storage service, but we use in memory storage here + dependencyManager.registerContextScoped(InjectionSymbols.Wallet, IndySdkWallet) + dependencyManager.registerInstance(IndySdkSymbol, indySdk) const agent = new Agent( { @@ -43,8 +49,6 @@ describe('UpdateAssistant | v0.3 - v0.3.1', () => { dependencyManager ) - const fileSystem = agent.injectionContainer.resolve(InjectionSymbols.FileSystem) - const updateAssistant = new UpdateAssistant(agent, { v0_1ToV0_2: { mediationRoleUpdateStrategy: 'doNotChange', @@ -58,10 +62,10 @@ describe('UpdateAssistant | v0.3 - v0.3.1', () => { storageService.records = JSON.parse(aliceDidRecordsString) expect(await updateAssistant.isUpToDate()).toBe(false) - expect(await updateAssistant.getNeededUpdates()).toEqual([ + expect(await updateAssistant.getNeededUpdates('0.4')).toEqual([ { - fromVersion: '0.3', - toVersion: '0.3.1', + fromVersion: '0.3.1', + toVersion: '0.4', doUpdate: expect.any(Function), }, ]) @@ -75,10 +79,6 @@ describe('UpdateAssistant | v0.3 - v0.3.1', () => { delete storageService.records.MEDIATOR_ROUTING_RECORD expect(storageService.records).toMatchSnapshot() - // Need to remove backupFiles after each run so we don't get IOErrors - const backupPath = `${fileSystem.basePath}/afj/migration/backup/${backupIdentifier}` - unlinkSync(backupPath) - await agent.shutdown() await agent.wallet.delete() diff --git a/packages/core/src/storage/migration/__tests__/UpdateAssistant.test.ts b/packages/core/src/storage/migration/__tests__/UpdateAssistant.test.ts index d1677a5648..2f9e3e80a3 100644 --- a/packages/core/src/storage/migration/__tests__/UpdateAssistant.test.ts +++ b/packages/core/src/storage/migration/__tests__/UpdateAssistant.test.ts @@ -1,6 +1,9 @@ import type { BaseRecord } from '../../BaseRecord' import { InMemoryStorageService } from '../../../../../../tests/InMemoryStorageService' +import { IndySdkWallet } from '../../../../../indy-sdk/src' +import { IndySdkSymbol } from '../../../../../indy-sdk/src/types' +import { indySdk } from '../../../../../indy-sdk/tests/setupIndySdkModule' import { getAgentOptions } from '../../../../tests/helpers' import { Agent } from '../../../agent/Agent' import { InjectionSymbols } from '../../../constants' @@ -8,7 +11,7 @@ import { DependencyManager } from '../../../plugins' import { UpdateAssistant } from '../UpdateAssistant' import { CURRENT_FRAMEWORK_STORAGE_VERSION } from '../updates' -const agentOptions = getAgentOptions('UpdateAssistant') +const agentOptions = getAgentOptions('UpdateAssistant', {}) describe('UpdateAssistant', () => { let updateAssistant: UpdateAssistant @@ -18,6 +21,9 @@ describe('UpdateAssistant', () => { beforeEach(async () => { const dependencyManager = new DependencyManager() storageService = new InMemoryStorageService() + // If we register the IndySdkModule it will register the storage service, but we use in memory storage here + dependencyManager.registerContextScoped(InjectionSymbols.Wallet, IndySdkWallet) + dependencyManager.registerInstance(IndySdkSymbol, indySdk) dependencyManager.registerInstance(InjectionSymbols.StorageService, storageService) agent = new Agent(agentOptions, dependencyManager) diff --git a/packages/core/src/storage/migration/__tests__/__fixtures__/alice-2-sov-dids-one-cache-record-0.3.json b/packages/core/src/storage/migration/__tests__/__fixtures__/alice-2-sov-dids-one-cache-record-0.3.json new file mode 100644 index 0000000000..1edebe9e11 --- /dev/null +++ b/packages/core/src/storage/migration/__tests__/__fixtures__/alice-2-sov-dids-one-cache-record-0.3.json @@ -0,0 +1,87 @@ +{ + "STORAGE_VERSION_RECORD_ID": { + "value": { + "_tags": {}, + "metadata": {}, + "id": "STORAGE_VERSION_RECORD_ID", + "createdAt": "2023-03-18T18:35:02.888Z", + "storageVersion": "0.3.1", + "updatedAt": "2023-03-18T18:35:02.888Z" + }, + "id": "STORAGE_VERSION_RECORD_ID", + "type": "StorageVersionRecord", + "tags": {} + }, + "DID_POOL_CACHE": { + "value": { + "metadata": {}, + "id": "DID_POOL_CACHE", + "createdAt": "2023-03-18T18:53:44.165Z", + "entries": [ + { + "key": "A4CYPASJYRZRt98YWrac3H", + "value": { + "nymResponse": { + "did": "A4CYPASJYRZRt98YWrac3H", + "verkey": "5wFaN9wUdLipt6rFjhep9pp2aanJKqe5MywkEAHqhRNS", + "role": "101" + }, + "poolId": "bcovrin:test2" + } + } + ], + "updatedAt": "2023-03-18T18:53:44.166Z" + }, + "id": "DID_POOL_CACHE", + "type": "CacheRecord", + "tags": {} + }, + "8168612b-73d1-4917-9a61-84e8102988f0": { + "value": { + "_tags": { + "recipientKeyFingerprints": [], + "qualifiedIndyDid": "did:indy:bcovrin:test:8DFqUo6UtQLLZETE7Gm29k" + }, + "metadata": {}, + "id": "8168612b-73d1-4917-9a61-84e8102988f0", + "did": "did:sov:8DFqUo6UtQLLZETE7Gm29k", + "role": "created", + "createdAt": "2023-03-18T18:35:04.191Z", + "updatedAt": "2023-03-18T18:35:04.191Z" + }, + "id": "8168612b-73d1-4917-9a61-84e8102988f0", + "type": "DidRecord", + "tags": { + "recipientKeyFingerprints": [], + "qualifiedIndyDid": "did:indy:bcovrin:test:8DFqUo6UtQLLZETE7Gm29k", + "role": "created", + "method": "sov", + "did": "did:sov:8DFqUo6UtQLLZETE7Gm29k", + "methodSpecificIdentifier": "8DFqUo6UtQLLZETE7Gm29k" + } + }, + "4993c740-5cd9-4c79-a7d8-23d1266d31be": { + "value": { + "_tags": { + "recipientKeyFingerprints": [], + "qualifiedIndyDid": "did:indy:bcovrin:test:Pow4pdnPgTS7JAXvWkoF2c" + }, + "metadata": {}, + "id": "4993c740-5cd9-4c79-a7d8-23d1266d31be", + "did": "did:sov:Pow4pdnPgTS7JAXvWkoF2c", + "role": "created", + "createdAt": "2023-03-18T18:35:07.208Z", + "updatedAt": "2023-03-18T18:35:07.208Z" + }, + "id": "4993c740-5cd9-4c79-a7d8-23d1266d31be", + "type": "DidRecord", + "tags": { + "recipientKeyFingerprints": [], + "qualifiedIndyDid": "did:indy:bcovrin:test:Pow4pdnPgTS7JAXvWkoF2c", + "role": "created", + "method": "sov", + "did": "did:sov:Pow4pdnPgTS7JAXvWkoF2c", + "methodSpecificIdentifier": "Pow4pdnPgTS7JAXvWkoF2c" + } + } +} diff --git a/packages/core/src/storage/migration/__tests__/__snapshots__/0.1.test.ts.snap b/packages/core/src/storage/migration/__tests__/__snapshots__/0.1.test.ts.snap index 4579773db0..e37cd412d5 100644 --- a/packages/core/src/storage/migration/__tests__/__snapshots__/0.1.test.ts.snap +++ b/packages/core/src/storage/migration/__tests__/__snapshots__/0.1.test.ts.snap @@ -1,10 +1,10 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`UpdateAssistant | v0.1 - v0.2 should correctly update credential records and create didcomm records 1`] = ` -Object { - "1-4e4f-41d9-94c4-f49351b811f1": Object { +{ + "1-4e4f-41d9-94c4-f49351b811f1": { "id": "1-4e4f-41d9-94c4-f49351b811f1", - "tags": Object { + "tags": { "associatedRecordId": "574b2a37-1db1-4af1-a3bf-35c6cb9e1d7a", "messageId": "578e73da-c3be-43d4-949b-7aadfd5a6eae", "messageName": "offer-credential", @@ -16,51 +16,52 @@ Object { "threadId": "578e73da-c3be-43d4-949b-7aadfd5a6eae", }, "type": "DidCommMessageRecord", - "value": Object { - "_tags": Object {}, + "value": { + "_tags": {}, "associatedRecordId": "574b2a37-1db1-4af1-a3bf-35c6cb9e1d7a", "createdAt": "2022-01-21T22:50:20.522Z", "id": "1-4e4f-41d9-94c4-f49351b811f1", - "message": Object { + "message": { "@id": "578e73da-c3be-43d4-949b-7aadfd5a6eae", "@type": "https://didcomm.org/issue-credential/1.0/offer-credential", - "credential_preview": Object { + "credential_preview": { "@type": "https://didcomm.org/issue-credential/1.0/credential-preview", - "attributes": Array [ - Object { + "attributes": [ + { "mime-type": "text/plain", "name": "name", "value": "Alice", }, - Object { + { "mime-type": "text/plain", "name": "age", "value": "25", }, - Object { + { "mime-type": "text/plain", "name": "dateOfBirth", "value": "2020-01-01", }, ], }, - "offers~attach": Array [ - Object { + "offers~attach": [ + { "@id": "libindy-cred-offer-0", - "data": Object { + "data": { "base64": "eyJzY2hlbWFfaWQiOiJUTDFFYVBGQ1o4U2k1YVVycVNjQkR0OjI6c2NoZW1hLTgwZjdlZWM1LThlNWEtNDNjYS1hZDRkLTMyNzRmYjkzNjFiODoxLjAiLCJjcmVkX2RlZl9pZCI6IlRMMUVhUEZDWjhTaTVhVXJxU2NCRHQ6MzpDTDo2ODE6ZGVmYXVsdCIsImtleV9jb3JyZWN0bmVzc19wcm9vZiI6eyJjIjoiMTEzOTY4MTg4OTM2OTQ5MzcyNzU3NjU2NzI1MjU1MTQ3NDk1OTI5NTM0MjQ5NjU1MzY4NTMzMTY4OTIzMjU4NTA2OTUzOTk3MTI2MDEyIiwieHpfY2FwIjoiMjM5NDkwMjQ4MjE4MTExOTQ1MjIxOTQ1ODcyMTE4MjQzNzA3NjE5OTQ4MzQ0MjU1ODM5ODI4NTU3NjkyNTE3NDExNDMwNDgwNDgwMTkxMTMwMjM0OTg5ODk0NzIyNDE2Nzk1MzUzODAwMDk3NDUxMjI5NDE4MzQ0MjEyOTI3NDk1NjI2NTc4MTk2ODUxMTcwMTI0MDI1NDk1MDExMjc0NjU1NjQzNjkzNTE1ODczMjA5OTczMzgwNjA3MzQxNzQzMzIwNTY0NjkwMzcxOTgyNDIxNTQyMzQzMTMzNTMxOTcxMTk4NTA4NDk5MjYyNzUxNDMyODg0NzgzMzc1MTAzODI0OTE3NzEwODAxOTE3OTc1OTM2OTg4OTIwMzYyMzA5NjE4NTgxMzY0ODE5NTA5ODYxNTE3NjI2ODc3OTUzMzMzMTkzMDExMjA2NDA5NDQ1MzA0MzEwMjUzMTU5OTE1NzYxOTI1MDY1MTg3Mjk1OTg1MzU0NDY1NjY5MTMxNjgwMzc5MzY0ODk0Mzc3NTYxODcyMjcxNDY5Mjk1NzY4MTc4NjQ2NDMxODI3NjI1MTQ3Mzk4MDg1ODI3NTUzMDAzNjIyMDM1ODM1MDg2NzE1NjgyMDA5MzgyNjgxNDc3NDc4ODQ0MDEyNDQ5NTE2NjYwODMwNDMwODQ5ODMxNjAxNDk3MTk3MjczMTIzNjg1NTE0NDMwMjY5OTkxMzMzNDI1Nzk0NjAwMzc3Mzk3NDMwMjg0MjIyMjQ1OTgyMjI1NTE3MjQ4NzA5NTczMzEwNjM5NzQyNzc2NjMyMzM3MDM0Nzk4NDY3MTAwNDczNDUxNTMzMTg1NDg5NDU0NjUyNTgwMjcxNjgyMDQzOTc4MDY4NDc4MjM1MjM5NjMzMTk0MzE4NDcxNDM2MjMwOTg1NTQ1MzAyMDQwNiIsInhyX2NhcCI6W1siZGF0ZW9mYmlydGgiLCIyNjMyMjI0MDEyMDg0NjM3ODA0NjA4MDE5MTM2MzIyNDkzMTE1MzA1MTA2ODQ1MTA0NTE4MDE4MjY2MjU1NjMyNDM0MTMzODY0MTIwNzUxNDkyMDAyMTI4MzU3NTcwMTY4MjE0OTU3OTgzNTQ3Mjk0NTczOTExMzk3MTQwNDAxNDk4MzQ0NzE0NTA5MjEyNDA2NTYzNzgyOTc4ODUzNDgzMTM4NTA1NTA3OTcxNTY3MTgyOTQ1ODQ5ODI3MDU0Nzg4NjA3ODgwOTU5NDE1MTYwMjU0OTE2MDExODkyNTIzNjUzODA1NTk2MDQ3NjA5Mjg3ODA4ODg2MzcwOTM5NDA3Njc5NTE3MjUzOTUxOTUzNDgxNDkzOTMzNDI3NTA5OTUwNTY5NjUwMTc4MjQwNTg0ODI4NzgzOTAxOTA0MjI3MzE1OTEyNzQ0NDYyODc3NDI3Njg3MDEzMDkyNDY1NTc5NzY0OTk2MTc2NTU1NDg1Nzc2Njc0NTcxMjcwNjU4NjAyMTk3OTExNTYzMjY0MzEyNTgzNzMyNTE3NjI3MjY3MzQ1MzEwODcwMjk2NTk4NTEyMDc0NzczNDQyMTExNjY4NjM0MzYzNjkzMDkxNzQ2MjE1MDQwODg1NTcyMzAzMDU4MDUwNzc3Nzg4Mzc5NDY3MzMyNDUwOTY5NTg1NDE2NzU2NzA2MzcxMzMyOTk5MDg1NjM5MDU4Nzk4ODE4MjkwNzEyNDk2NDk0NTY1MzgxMjI0NzUyMTA2OTQyMTg2OTc3MzUwNzE2NjI0MTYxMjIwNjExMDY3MzcxNTM0NjYyNTc0MzY1NTM0MzEzNjAwMTQyODk2MDkwMDQ3MTI5NjQ3OTEzOTgzMjk0Mjc2MDI2OTA2MTA3NzM4MDM2MjI2MTA4NzU0MTE3OTIwMTg2NDM1MDkwOTU2Il0sWyJuYW1lIiwiMzE2Nzc0MzUwOTgzMjI4Nzg1MDcxNDg1NjYzOTM0MTYyNjQ1MzE2NjQzMTc5Njg2NTg2MTU2MjgwMjg3Njg5Nzg0NTI1Mjg5MzY0NDg5NDMxNjEwMTc1MzUwMDgzMzUzMTg3MTIwMTE1MTU1NjUxOTYzMjcyODM4MjcyMTgyNTI2MzUyNjMyMzI5MDY2NDIxMjM3OTM3NTc4MjY4MjkwNzU4MDgwNjI0MjE3MDM0NDU5MDUyMjY2Njk5NjQzMTg2MjkyNjcxNDk1ODEwMDU4Mjg5MzA1MjI4OTQ2MTQ2MTkzMDAzNDk0NDgwNzY0NDY5Nzc1NzM5Njg0NzcxNjMyNDIzNTM5MjE1MzIxMjc0NTQ4NDU5NzE4NTM3NTMzMzE0MjYwMjcwNzE0MTkzNjc4MTEzMDg5MDUxODI1MjA2MTAyMjQ0MTc3NzAwNDk3MTYxMDM2NjgwNDYzMDUxNDcxNDk3MTgzNzc3Nzc5Nzc2Nzc0MTUzNzQ1NjEwNzc3NzgyMDYyODA3NjY5MjE2ODA2NDgxNzAzNTU5NDk5MTAyNTc5ODAwNjgxNTQxMjg3MTk0MTAwNzg4MDMxNDE3Nzg5MzQyNjk2NTQyNTA3NjE5MTgzNDIyMTc0OTk5NzQ2ODM3NjY4NTg0Mzk0NzQxNzI5MDY2MjgwNjYyMzAyMDI3NTczMDgwMDY5NTE5MDgxOTA5OTA3MzQzODAxNzg3MjgyMTEyODY2NzkwNTI2MDIwMjk4NjM2ODY3Njg2NTE5MjQ0NTg2MTg1NzMxMTE0ODk1ODU3NjkzNTQxMTIwOTc2NTQ5MzYwNDE1MTkxMjI4ODA4MzE5NTcxOTU4NTkxNDc4NDYwNzMwMTg2NDQ4MTU3NjU3OTkyOTI4NTM2MjgxODU2NjAzNjU5NjM3OTE2NzE0OTk0NTU0NSJdLFsiYWdlIiwiMTY5MjgwMDQ2OTQyNDI3NDY1ODE5OTE3MzEyNTkzMzEyMzkyNDYzMDQxODc5MzIwMjM0NDU4NjQ4Mzg2MzI4MTE5MDQ5OTIyMzc1NjkxNzI0ODYzMDM3NDMyODU4OTQ4MTIyNzI2ODQ5NDU5NDcxODA2NTU4NzYyMzU4MTgzMzU3OTYwNDk3NTMyNjUzNzE4ODE0NzQ1NzY2ODc3OTI2NzU2NTQ3NjQwMjUzMTczMDYzMjY0Mzc0OTAzNTgwNjEwNDMxMTM2NTA2NjQ1NjE5NzYzNTE1Nzc3MTkyNjU4ODk0OTc1MDMyODAzNzM0MTE5MjM5NjcxMjgwOTQyOTkxMTg2MjYyNjYzNTM5NDU3ODc1NjY1NDcwMTAxMTEzOTUyOTY2MDQyMzU4NDQ4ODE1NTk5MDgzNTU4NTIyMDQ1OTI3NDI0NjI5Njk4MTgzMTUzNzUyMDA4MzM5NTI1NTYxMDI0ODg2MzUzOTc3NzA1ODE5Mjc1MzQzOTg3MzMzODMxNjU0NzA4ODI3NDI0NzMzNzcyNjI3MTA2OTgxNjE5NDY0MzUwNDU3NzE4NzM2MDA0NjEyNzQ0OTAyNDA5NjA0Njk4NzkzNzI0MTc1MDA4OTUzMDMyMDgxMTQ2OTE3MTM4ODc4NzQ4MDM0Mzg1NDQxNTIxMTU5ODM2NDIwMDEzNTQ1NTQyMDk2NDIwODA1MDYxMzI5MTkwNzczNzIzMDYxMjE2NDIzNjczNTM1MzU1OTc5MzY1Njg0MzM2NjEyOTU2NjkxNDA4NDQ5MjE4MjcwOTYyODUyMzQwMTQ2MDAwMDYzNzA3NzU5MzUxODM0MTQ2MDI4MTYyMTEyMzU4MzAzNDQ1OTcwMTg3MTk3OTQ5MDcwNzE4NzQ4OTI4NjM5MDkyMzY1MjExMjgyODY0NDE3OTcwOTg5OCJdLFsibWFzdGVyX3NlY3JldCIsIjk4NjY2MDAzNjA2Njc3MjkxODM1MjEwMzA1NDczNTA3NjU0NTM1OTgxNDYxODkyNjY5NjI5OTE1MzQ5NjU3NjY5MTI5NzAxNTUwNzUyNTU2NjMxMzU4MzU3NjEzNjg3OTgyNTQzNTcyNTc5ODEzOTgxMzI4MDM4NzY5OTcxMzAwNTE0NDI2NzAxNTM2ODE1ODI3MDgzNDY5MzEzOTQ0ODAzMzIzMDUzMzUyNDkxMTIwMDUwNzkyNzA4NTQzMTE2NTM1NjA2NTQyNDY3OTcxNTUxODA4MzQyOTk1OTM4NzQ2NDQ4NjMyNTY4NjU0NjA4MzI1NDk2NjM3Mzc5OTQ0NjA5MDU3Mjc2OTE0OTQxNzE4MzU2MTYzODYyMzI2MDc3MDUzNjIyMDI3OTE2MzIzNzAzMDE2MTc4NDQ3MDEwMTc1MjI1MTM0NjE3NTcxMTgzMjcyNzMwNjQxNzI3Mzk2MzM4ODk2NTUyNzM4NzUwMDA4MTQ3MDExNjkyNzIwNzI4NDY2MjcwNDQ2MDg4NjEyMDg2MDExMzg2NzQxOTMzODM4ODQ1NjkzMTM1NzcyODk0MDIwNTM4NTU3ODI1MjA3OTkxNDAwODIyNjg4OTgwNTg4MjgzMTY0MzAxNTY1NjAyNDAzNTI4MDE2MTczNTk5MTM4NzI5ODEyOTE2MTYxNDEwNjgyODU4MzU4MDE3ODI1ODUyMzY2OTgzMDQzMzM4ODY2MzI3MzM3MTE0NzcyMzM5NTUyNTYzNzU0NzQ0NTA5MTYzNzYzNjAyNTI0NjgxNTUyNjIyOTYwNjM4MzE2OTc0NjI4MjQyNjQ5NjYyMjQyMTkzODMxOTUyMDE0MTAxNTA3Njk2MjIxMDU5NDE5MTcyNzMwNjE2NDE2NzEwNTMzMzc2NjI4MjQ4NzkxMTUwNjMxMDMxOCJdXX0sIm5vbmNlIjoiMTE4MTE3NTM4MDU1MjM2NjMxNjAwNjM1NyJ9", }, "mime-type": "application/json", }, ], }, - "metadata": Object {}, + "metadata": {}, "role": "sender", + "updatedAt": "2022-01-21T22:50:20.522Z", }, }, - "10-4e4f-41d9-94c4-f49351b811f1": Object { + "10-4e4f-41d9-94c4-f49351b811f1": { "id": "10-4e4f-41d9-94c4-f49351b811f1", - "tags": Object { + "tags": { "associatedRecordId": "c7e0a752-7f1c-41c0-b0ae-a68c2d97ca8c", "messageId": "e2c2194c-6ac6-4b27-9030-18887c79b5eb", "messageName": "offer-credential", @@ -72,51 +73,52 @@ Object { "threadId": "e2c2194c-6ac6-4b27-9030-18887c79b5eb", }, "type": "DidCommMessageRecord", - "value": Object { - "_tags": Object {}, + "value": { + "_tags": {}, "associatedRecordId": "c7e0a752-7f1c-41c0-b0ae-a68c2d97ca8c", "createdAt": "2022-01-21T22:50:20.522Z", "id": "10-4e4f-41d9-94c4-f49351b811f1", - "message": Object { + "message": { "@id": "e2c2194c-6ac6-4b27-9030-18887c79b5eb", "@type": "https://didcomm.org/issue-credential/1.0/offer-credential", - "credential_preview": Object { + "credential_preview": { "@type": "https://didcomm.org/issue-credential/1.0/credential-preview", - "attributes": Array [ - Object { + "attributes": [ + { "mime-type": "text/plain", "name": "name", "value": "Alice", }, - Object { + { "mime-type": "text/plain", "name": "age", "value": "25", }, - Object { + { "mime-type": "text/plain", "name": "dateOfBirth", "value": "2020-01-01", }, ], }, - "offers~attach": Array [ - Object { + "offers~attach": [ + { "@id": "libindy-cred-offer-0", - "data": Object { + "data": { "base64": "eyJzY2hlbWFfaWQiOiJUTDFFYVBGQ1o4U2k1YVVycVNjQkR0OjI6c2NoZW1hLTgwZjdlZWM1LThlNWEtNDNjYS1hZDRkLTMyNzRmYjkzNjFiODoxLjAiLCJjcmVkX2RlZl9pZCI6IlRMMUVhUEZDWjhTaTVhVXJxU2NCRHQ6MzpDTDo2ODE6ZGVmYXVsdCIsImtleV9jb3JyZWN0bmVzc19wcm9vZiI6eyJjIjoiMTEzOTY4MTg4OTM2OTQ5MzcyNzU3NjU2NzI1MjU1MTQ3NDk1OTI5NTM0MjQ5NjU1MzY4NTMzMTY4OTIzMjU4NTA2OTUzOTk3MTI2MDEyIiwieHpfY2FwIjoiMjM5NDkwMjQ4MjE4MTExOTQ1MjIxOTQ1ODcyMTE4MjQzNzA3NjE5OTQ4MzQ0MjU1ODM5ODI4NTU3NjkyNTE3NDExNDMwNDgwNDgwMTkxMTMwMjM0OTg5ODk0NzIyNDE2Nzk1MzUzODAwMDk3NDUxMjI5NDE4MzQ0MjEyOTI3NDk1NjI2NTc4MTk2ODUxMTcwMTI0MDI1NDk1MDExMjc0NjU1NjQzNjkzNTE1ODczMjA5OTczMzgwNjA3MzQxNzQzMzIwNTY0NjkwMzcxOTgyNDIxNTQyMzQzMTMzNTMxOTcxMTk4NTA4NDk5MjYyNzUxNDMyODg0NzgzMzc1MTAzODI0OTE3NzEwODAxOTE3OTc1OTM2OTg4OTIwMzYyMzA5NjE4NTgxMzY0ODE5NTA5ODYxNTE3NjI2ODc3OTUzMzMzMTkzMDExMjA2NDA5NDQ1MzA0MzEwMjUzMTU5OTE1NzYxOTI1MDY1MTg3Mjk1OTg1MzU0NDY1NjY5MTMxNjgwMzc5MzY0ODk0Mzc3NTYxODcyMjcxNDY5Mjk1NzY4MTc4NjQ2NDMxODI3NjI1MTQ3Mzk4MDg1ODI3NTUzMDAzNjIyMDM1ODM1MDg2NzE1NjgyMDA5MzgyNjgxNDc3NDc4ODQ0MDEyNDQ5NTE2NjYwODMwNDMwODQ5ODMxNjAxNDk3MTk3MjczMTIzNjg1NTE0NDMwMjY5OTkxMzMzNDI1Nzk0NjAwMzc3Mzk3NDMwMjg0MjIyMjQ1OTgyMjI1NTE3MjQ4NzA5NTczMzEwNjM5NzQyNzc2NjMyMzM3MDM0Nzk4NDY3MTAwNDczNDUxNTMzMTg1NDg5NDU0NjUyNTgwMjcxNjgyMDQzOTc4MDY4NDc4MjM1MjM5NjMzMTk0MzE4NDcxNDM2MjMwOTg1NTQ1MzAyMDQwNiIsInhyX2NhcCI6W1siZGF0ZW9mYmlydGgiLCIyNjMyMjI0MDEyMDg0NjM3ODA0NjA4MDE5MTM2MzIyNDkzMTE1MzA1MTA2ODQ1MTA0NTE4MDE4MjY2MjU1NjMyNDM0MTMzODY0MTIwNzUxNDkyMDAyMTI4MzU3NTcwMTY4MjE0OTU3OTgzNTQ3Mjk0NTczOTExMzk3MTQwNDAxNDk4MzQ0NzE0NTA5MjEyNDA2NTYzNzgyOTc4ODUzNDgzMTM4NTA1NTA3OTcxNTY3MTgyOTQ1ODQ5ODI3MDU0Nzg4NjA3ODgwOTU5NDE1MTYwMjU0OTE2MDExODkyNTIzNjUzODA1NTk2MDQ3NjA5Mjg3ODA4ODg2MzcwOTM5NDA3Njc5NTE3MjUzOTUxOTUzNDgxNDkzOTMzNDI3NTA5OTUwNTY5NjUwMTc4MjQwNTg0ODI4NzgzOTAxOTA0MjI3MzE1OTEyNzQ0NDYyODc3NDI3Njg3MDEzMDkyNDY1NTc5NzY0OTk2MTc2NTU1NDg1Nzc2Njc0NTcxMjcwNjU4NjAyMTk3OTExNTYzMjY0MzEyNTgzNzMyNTE3NjI3MjY3MzQ1MzEwODcwMjk2NTk4NTEyMDc0NzczNDQyMTExNjY4NjM0MzYzNjkzMDkxNzQ2MjE1MDQwODg1NTcyMzAzMDU4MDUwNzc3Nzg4Mzc5NDY3MzMyNDUwOTY5NTg1NDE2NzU2NzA2MzcxMzMyOTk5MDg1NjM5MDU4Nzk4ODE4MjkwNzEyNDk2NDk0NTY1MzgxMjI0NzUyMTA2OTQyMTg2OTc3MzUwNzE2NjI0MTYxMjIwNjExMDY3MzcxNTM0NjYyNTc0MzY1NTM0MzEzNjAwMTQyODk2MDkwMDQ3MTI5NjQ3OTEzOTgzMjk0Mjc2MDI2OTA2MTA3NzM4MDM2MjI2MTA4NzU0MTE3OTIwMTg2NDM1MDkwOTU2Il0sWyJuYW1lIiwiMzE2Nzc0MzUwOTgzMjI4Nzg1MDcxNDg1NjYzOTM0MTYyNjQ1MzE2NjQzMTc5Njg2NTg2MTU2MjgwMjg3Njg5Nzg0NTI1Mjg5MzY0NDg5NDMxNjEwMTc1MzUwMDgzMzUzMTg3MTIwMTE1MTU1NjUxOTYzMjcyODM4MjcyMTgyNTI2MzUyNjMyMzI5MDY2NDIxMjM3OTM3NTc4MjY4MjkwNzU4MDgwNjI0MjE3MDM0NDU5MDUyMjY2Njk5NjQzMTg2MjkyNjcxNDk1ODEwMDU4Mjg5MzA1MjI4OTQ2MTQ2MTkzMDAzNDk0NDgwNzY0NDY5Nzc1NzM5Njg0NzcxNjMyNDIzNTM5MjE1MzIxMjc0NTQ4NDU5NzE4NTM3NTMzMzE0MjYwMjcwNzE0MTkzNjc4MTEzMDg5MDUxODI1MjA2MTAyMjQ0MTc3NzAwNDk3MTYxMDM2NjgwNDYzMDUxNDcxNDk3MTgzNzc3Nzc5Nzc2Nzc0MTUzNzQ1NjEwNzc3NzgyMDYyODA3NjY5MjE2ODA2NDgxNzAzNTU5NDk5MTAyNTc5ODAwNjgxNTQxMjg3MTk0MTAwNzg4MDMxNDE3Nzg5MzQyNjk2NTQyNTA3NjE5MTgzNDIyMTc0OTk5NzQ2ODM3NjY4NTg0Mzk0NzQxNzI5MDY2MjgwNjYyMzAyMDI3NTczMDgwMDY5NTE5MDgxOTA5OTA3MzQzODAxNzg3MjgyMTEyODY2NzkwNTI2MDIwMjk4NjM2ODY3Njg2NTE5MjQ0NTg2MTg1NzMxMTE0ODk1ODU3NjkzNTQxMTIwOTc2NTQ5MzYwNDE1MTkxMjI4ODA4MzE5NTcxOTU4NTkxNDc4NDYwNzMwMTg2NDQ4MTU3NjU3OTkyOTI4NTM2MjgxODU2NjAzNjU5NjM3OTE2NzE0OTk0NTU0NSJdLFsiYWdlIiwiMTY5MjgwMDQ2OTQyNDI3NDY1ODE5OTE3MzEyNTkzMzEyMzkyNDYzMDQxODc5MzIwMjM0NDU4NjQ4Mzg2MzI4MTE5MDQ5OTIyMzc1NjkxNzI0ODYzMDM3NDMyODU4OTQ4MTIyNzI2ODQ5NDU5NDcxODA2NTU4NzYyMzU4MTgzMzU3OTYwNDk3NTMyNjUzNzE4ODE0NzQ1NzY2ODc3OTI2NzU2NTQ3NjQwMjUzMTczMDYzMjY0Mzc0OTAzNTgwNjEwNDMxMTM2NTA2NjQ1NjE5NzYzNTE1Nzc3MTkyNjU4ODk0OTc1MDMyODAzNzM0MTE5MjM5NjcxMjgwOTQyOTkxMTg2MjYyNjYzNTM5NDU3ODc1NjY1NDcwMTAxMTEzOTUyOTY2MDQyMzU4NDQ4ODE1NTk5MDgzNTU4NTIyMDQ1OTI3NDI0NjI5Njk4MTgzMTUzNzUyMDA4MzM5NTI1NTYxMDI0ODg2MzUzOTc3NzA1ODE5Mjc1MzQzOTg3MzMzODMxNjU0NzA4ODI3NDI0NzMzNzcyNjI3MTA2OTgxNjE5NDY0MzUwNDU3NzE4NzM2MDA0NjEyNzQ0OTAyNDA5NjA0Njk4NzkzNzI0MTc1MDA4OTUzMDMyMDgxMTQ2OTE3MTM4ODc4NzQ4MDM0Mzg1NDQxNTIxMTU5ODM2NDIwMDEzNTQ1NTQyMDk2NDIwODA1MDYxMzI5MTkwNzczNzIzMDYxMjE2NDIzNjczNTM1MzU1OTc5MzY1Njg0MzM2NjEyOTU2NjkxNDA4NDQ5MjE4MjcwOTYyODUyMzQwMTQ2MDAwMDYzNzA3NzU5MzUxODM0MTQ2MDI4MTYyMTEyMzU4MzAzNDQ1OTcwMTg3MTk3OTQ5MDcwNzE4NzQ4OTI4NjM5MDkyMzY1MjExMjgyODY0NDE3OTcwOTg5OCJdLFsibWFzdGVyX3NlY3JldCIsIjk4NjY2MDAzNjA2Njc3MjkxODM1MjEwMzA1NDczNTA3NjU0NTM1OTgxNDYxODkyNjY5NjI5OTE1MzQ5NjU3NjY5MTI5NzAxNTUwNzUyNTU2NjMxMzU4MzU3NjEzNjg3OTgyNTQzNTcyNTc5ODEzOTgxMzI4MDM4NzY5OTcxMzAwNTE0NDI2NzAxNTM2ODE1ODI3MDgzNDY5MzEzOTQ0ODAzMzIzMDUzMzUyNDkxMTIwMDUwNzkyNzA4NTQzMTE2NTM1NjA2NTQyNDY3OTcxNTUxODA4MzQyOTk1OTM4NzQ2NDQ4NjMyNTY4NjU0NjA4MzI1NDk2NjM3Mzc5OTQ0NjA5MDU3Mjc2OTE0OTQxNzE4MzU2MTYzODYyMzI2MDc3MDUzNjIyMDI3OTE2MzIzNzAzMDE2MTc4NDQ3MDEwMTc1MjI1MTM0NjE3NTcxMTgzMjcyNzMwNjQxNzI3Mzk2MzM4ODk2NTUyNzM4NzUwMDA4MTQ3MDExNjkyNzIwNzI4NDY2MjcwNDQ2MDg4NjEyMDg2MDExMzg2NzQxOTMzODM4ODQ1NjkzMTM1NzcyODk0MDIwNTM4NTU3ODI1MjA3OTkxNDAwODIyNjg4OTgwNTg4MjgzMTY0MzAxNTY1NjAyNDAzNTI4MDE2MTczNTk5MTM4NzI5ODEyOTE2MTYxNDEwNjgyODU4MzU4MDE3ODI1ODUyMzY2OTgzMDQzMzM4ODY2MzI3MzM3MTE0NzcyMzM5NTUyNTYzNzU0NzQ0NTA5MTYzNzYzNjAyNTI0NjgxNTUyNjIyOTYwNjM4MzE2OTc0NjI4MjQyNjQ5NjYyMjQyMTkzODMxOTUyMDE0MTAxNTA3Njk2MjIxMDU5NDE5MTcyNzMwNjE2NDE2NzEwNTMzMzc2NjI4MjQ4NzkxMTUwNjMxMDMxOCJdXX0sIm5vbmNlIjoiNTQzODYyOTczNzUxMjk1OTk2MTAzNjA3In0=", }, "mime-type": "application/json", }, ], }, - "metadata": Object {}, + "metadata": {}, "role": "receiver", + "updatedAt": "2022-01-21T22:50:20.522Z", }, }, - "11-4e4f-41d9-94c4-f49351b811f1": Object { + "11-4e4f-41d9-94c4-f49351b811f1": { "id": "11-4e4f-41d9-94c4-f49351b811f1", - "tags": Object { + "tags": { "associatedRecordId": "c7e0a752-7f1c-41c0-b0ae-a68c2d97ca8c", "messageId": "edba1c87-51d3-4c70-aff2-ab8016e1060e", "messageName": "request-credential", @@ -128,34 +130,35 @@ Object { "threadId": "e2c2194c-6ac6-4b27-9030-18887c79b5eb", }, "type": "DidCommMessageRecord", - "value": Object { - "_tags": Object {}, + "value": { + "_tags": {}, "associatedRecordId": "c7e0a752-7f1c-41c0-b0ae-a68c2d97ca8c", "createdAt": "2022-01-21T22:50:20.522Z", "id": "11-4e4f-41d9-94c4-f49351b811f1", - "message": Object { + "message": { "@id": "edba1c87-51d3-4c70-aff2-ab8016e1060e", "@type": "https://didcomm.org/issue-credential/1.0/request-credential", - "requests~attach": Array [ - Object { + "requests~attach": [ + { "@id": "libindy-cred-request-0", - "data": Object { + "data": { "base64": "eyJwcm92ZXJfZGlkIjoiRUg2OTVENjRRd2hWRmtyazFtcDQ5aiIsImNyZWRfZGVmX2lkIjoiVEwxRWFQRkNaOFNpNWFVcnFTY0JEdDozOkNMOjY4MTpkZWZhdWx0IiwiYmxpbmRlZF9tcyI6eyJ1IjoiOTcwNjA5MzQ1NDAxNzE0NDIxNjQzNDg0NDE0MTQwOTA0NzMzNTQ4NTA4NzY0OTk5MTgxNzY1ODI3MjM3ODg3NjQ2MzQzNDYyMTY0ODA3MTUxMjg1ODk2MDczODAyNDY1MDMwMTcxNTIyODE4ODY4Mjc2ODUwMzE0NzQzMjM3ODc3NDMyMDgxMTQwMzE5ODU5OTM5NjM0MTI4NzkzOTk4NDcwMTUzNTQ0NjgxNTM5NDg4NzEyNDE5MTk3NzcxODE1NjU5Nzg1NDE5NTA1ODEyODI4NzYxOTI4MzExODczNjA5NDYwMjExMzQ4OTAyNDk4NzYxNjc5OTIzNzA2NjUwMDIzODg4ODU4NzE1MTYxMTIyMzA5MTc1MTE3MTk0NDMwNTI1ODY5NjcwMDEzMTgxNTkzNjI4NDQzMjk2MDI0MDE5NTc4MzIzODQwNzk0OTQ0MjIyODE1MTQwNTM2Mjc3ODQwMjU2ODc2MDE1MzUwNDgzOTE2MjYzMDgyODM5NzI2NzAxNjg4NjcxNDY0MjA3MzQxMTgwMjg3Mjk0Njg4NDA3NzQ3MDk1NjA0NzQ3NzA3OTc2Nzk2MjU0MTQ2NDQ0NTY5NzQ4MTk4OTMwMjkyOTkzNjY1ODk2MTUyMDMyNzQwODY3OTgwMjczMTMxMDM3NjkwNDkzNDU1Mjc4NDc3MDc3NjE0OTU2NjgzNjgxNDc5NzY3Njg0MDI4MzU5NzE4NzM0ODEyNzcyMDIyOTIwODQ3NDIyNDYyOTAwMjczMTcwMTU2NzQyMzUyMDQ2NDYyODI4NzAxMTE2MzU0MTkwMDY5MDE0NTIwMSIsInVyIjpudWxsLCJoaWRkZW5fYXR0cmlidXRlcyI6WyJtYXN0ZXJfc2VjcmV0Il0sImNvbW1pdHRlZF9hdHRyaWJ1dGVzIjp7fX0sImJsaW5kZWRfbXNfY29ycmVjdG5lc3NfcHJvb2YiOnsiYyI6IjE4MzE5MTUyNjg0NDkyMTM0OTc4MzkzNDE3OTY5NjQ5MDIyNzMzNzQzMTQ5NTUwNzAwNzc5ODk0Nzg1MTg3MTA1OTkyNjk4Mzg5MjAzIiwidl9kYXNoX2NhcCI6IjQ0NzA4MzAwOTUyNzA3MjA3NjI3NjA3NzM4MTI2NDgxNzA3OTA1MDcwNjEyMzQ5OTIxNTAxNTYyOTA2MzgyMzE0MDE4MzQ4MTAxMTE4MDI4MDIyMjk5OTgyNTEwMjI5ODM1OTQxMzY1MTM4Njg0MTU1OTEyOTE3NzYwMjgwOTIyNDk1MjE1ODA2NTM3MzA5NDU5NDA3NjcxNDgyNDA0NDMwODU4MzU5ODU3MDgzNzg1Njk1MTYzMjkwMzEyNzMzNDAxNjY1NDk5MjUwMDQ0NzkwODk4OTA4NzIzNzE1OTc0MDYwNzgyNDEzODYzMTU0MTUxNjg2OTM3ODY4MDM5MDU1Nzc1MzA5MjQ0MTYzOTUxNzgwMTgxNDk5MDM5MDgyMjcxNzgzNTgzNTkxMDIyNjYwMDYyMDQ3MDQ2NTEyMTM0NDU5OTI1MTgyOTg2MTkxOTAwNTQwMDg4MjE3Mzc2NjM4MjEzNTI0MDUxNjcxNzg3ODY0ODQ2NzIxNjk5NjQzNDk0MTI2MjA3NTg2MjgwNjQ5OTc4ODE2ODEwMjM5OTAzMzU0NzIyOTI2NTUxODYyNTQwMjc4ODU2OTEyNDQ2MDUzMTg4MzI3Mjk4NDc0NjgzMjkwMjU4MjgwNjY0OTgyOTM2NTYyODcwNTIyODA2NzYwMjE1OTI0MDc3ODQ2NjA2NTM0NzI4MjkyODQ2MDQyOTk1NjgxMTQzOTQ5MTU0MTU0NTU2NzQzMDYzMzY1OTIzMzU2OTg1MjQ5ODI1Njk2NzE3MzE2MDk1NzE5MDU2MTE5NTE0NTYwODY0MzUyMDc4ODMyOTYzNjI3Mjk0Njk1ODQ0MTE5NDA1NTMzNTY5MTI2ODExODE3NDYyNjczMzM3OTg4MzA0MDcwMzk5ODYyNTk2ODMyNDk2OTU3NzA4Nzc0NzI5NzAyOTEyNjY3Njk0OTgwMjc3OTI5MDgzNiIsIm1fY2FwcyI6eyJtYXN0ZXJfc2VjcmV0IjoiMjIxNTAyNTExNTYzODg1MTM1NzIwNjg4MzE5Njk5NzE5ODAxNDI4NDgzMTI2MjY0NjI0NTE5OTA4MjM5NTM0MjQ3MDQ3NTIwODgyNDE4Mzk0ODMzNjUwODM2NTI0NDk2MzUzMDkwNzIzOTI1MDc2NDY2ODQ3NjIxNzE4MDA4MDAxNTQyNTMzOTk4NTU1MzA1MDYwNjUzMzkwMjc4MDc2MzE2Mjg5MzcwMzA2MDcyMDYxNCJ9LCJyX2NhcHMiOnt9fSwibm9uY2UiOiI2OTgzNzA2MTYwMjM4ODM3MzA0OTgzNzUifQ==", }, "mime-type": "application/json", }, ], - "~thread": Object { + "~thread": { "thid": "e2c2194c-6ac6-4b27-9030-18887c79b5eb", }, }, - "metadata": Object {}, + "metadata": {}, "role": "sender", + "updatedAt": "2022-01-21T22:50:20.522Z", }, }, - "12-4e4f-41d9-94c4-f49351b811f1": Object { + "12-4e4f-41d9-94c4-f49351b811f1": { "id": "12-4e4f-41d9-94c4-f49351b811f1", - "tags": Object { + "tags": { "associatedRecordId": "c7e0a752-7f1c-41c0-b0ae-a68c2d97ca8c", "messageId": "a340a7b9-f4d4-4892-b7d2-1d3d40e4be48", "messageName": "issue-credential", @@ -167,35 +170,36 @@ Object { "threadId": "e2c2194c-6ac6-4b27-9030-18887c79b5eb", }, "type": "DidCommMessageRecord", - "value": Object { - "_tags": Object {}, + "value": { + "_tags": {}, "associatedRecordId": "c7e0a752-7f1c-41c0-b0ae-a68c2d97ca8c", "createdAt": "2022-01-21T22:50:20.522Z", "id": "12-4e4f-41d9-94c4-f49351b811f1", - "message": Object { + "message": { "@id": "a340a7b9-f4d4-4892-b7d2-1d3d40e4be48", "@type": "https://didcomm.org/issue-credential/1.0/issue-credential", - "credentials~attach": Array [ - Object { + "credentials~attach": [ + { "@id": "libindy-cred-0", - "data": Object { + "data": { "base64": "eyJzY2hlbWFfaWQiOiJUTDFFYVBGQ1o4U2k1YVVycVNjQkR0OjI6c2NoZW1hLTgwZjdlZWM1LThlNWEtNDNjYS1hZDRkLTMyNzRmYjkzNjFiODoxLjAiLCJjcmVkX2RlZl9pZCI6IlRMMUVhUEZDWjhTaTVhVXJxU2NCRHQ6MzpDTDo2ODE6ZGVmYXVsdCIsInJldl9yZWdfaWQiOm51bGwsInZhbHVlcyI6eyJuYW1lIjp7InJhdyI6IkFsaWNlIiwiZW5jb2RlZCI6IjI3MDM0NjQwMDI0MTE3MzMxMDMzMDYzMTI4MDQ0MDA0MzE4MjE4NDg2ODE2OTMxNTIwODg2NDA1NTM1NjU5OTM0NDE3NDM4NzgxNTA3In0sImFnZSI6eyJyYXciOiIyNSIsImVuY29kZWQiOiIyNSJ9LCJkYXRlT2ZCaXJ0aCI6eyJyYXciOiIyMDIwLTAxLTAxIiwiZW5jb2RlZCI6IjQxMDYxMjkzNzgwNDYyMDc1NTE0MjA4MjIzODk3NTkwNDA3ODAwNzQ4NTQzMDE0OTkxNTg4NTIyNzIxMTkzODg2ODk4MTE5NzUzNjI0In19LCJzaWduYXR1cmUiOnsicF9jcmVkZW50aWFsIjp7Im1fMiI6IjEwMzE4MTcwNjEzMDMzODg1ODkxNDkzMzk2MjU5NDYyNDIxMzM3MjY3ODcyNzkyNzczNjgwMDQwMTgyNTE4NDM1MzEzMDYyNjE3MTcxMCIsImEiOiI3MTM3NDExNDU3NjI3MDE5MDcyNjY0ODMzNTQ5MjAzMDQyMTg0MDQ0OTUxNTU5MzYwMDczMzk1MjM4NjkwMzkwNjgxNzA3ODAyNzY0MTE2MzQ4NjE4NjgxOTgzNTM2MTczNTE3MzgxODc5NzQ0MzQyODIzMDkzNzc4MTk1NzYxNzgzMjQ3NDcxOTQ2NDgwODc0OTY2OTQyNjY0NzU4NjIwNzEyNzExODExNTY5NTc1NzMwNTg4NTQwNDI1MjEzNjY0OTg0OTQyOTIzODU5NzQ3MjIzNjA5ODQ1NjIwMjE5NTY1NzYxODY2OTMwMzI3OTYzNTIwNzE5MTg2NjMyNTQ4NzkzNDI3MTQ0NTY0NTAxNjE1NTg1MTI2NzkxNzM5Njg3ODc2MzIxMTQ1NTAzNDU1OTM0MzUxODc0MjQ3NjA0NzAwODYxMTgxNjY4NjUxNzQ5NTExMjY0MzExMDU2NjI5MjM4NjQwNDY2NzM4ODA0NjI0NzU1MzEzODgxMjQzMjkwNDM5ODI4MDE0NzY0MTQ3NDM1MzE3NTY3MjY3MzQ2NTQxMTY2ODIwNTI2MDkyMDAyMDE0NzY5NjE1MzI3Mjg4MTYwMzM0MDg1MTQ1MzQ0Mzc5MzAxMDg1NDc1MDEyNzUzNDIzNzkxMjI4NzM5NDE3MzA0OTM2NDUzNDEyMDYxNjY0MTUzNTM4MjM5MDA0OTUxMDgyODQxMTE1MTIyNDQ1MzkzNzc5Mzc5NDI5NjQzMjMxNDE2NDQzNDM4NDQ1NzI1NTAzMDc0MTM5MzEwOTc2MjkwMTQ2MTIwMDI2MDI1NTczNzIyNTUyOCIsImUiOiIyNTkzNDQ3MjMwNTUwNjIwNTk5MDcwMjU0OTE0ODA2OTc1NzE5MzgyNzc4ODk1MTUxNTIzMDYyNDk3Mjg1ODMxMDU2NjU4MDA3MTMzMDY3NTkxNDk5ODE2OTA1NTkxOTM5ODcxNDMwMTIzNjc5MTMyMDYyOTkzMjM4OTk2OTY5NDIyMTMyMzU5NTY3NDI5MzAxNTQ3NTU5NjM2MjQ0MTcyOTU5NjM1MjY1ODc1MTkyNjIwNTEiLCJ2IjoiODMxMTU0NzI2ODU4Mzg3ODc5NTU5NDUzNzcwNjg3MzIyMzE1NTgyMjYzMTk3NDUzMjMxNTI1ODIzNDIzNjkxMDk5Mzc2NTExNzI0ODAxMzA1MTU0NzY2Mjc0NjQ1OTMzMTAwOTIzMjIxOTgwNDkyNzE0NDQxMTY0NTYxNTIwMDIzMjYzMDYzMzQ4NjQ4NzkzMjkxMzEwNDc0NTU5NDIwMTkzMTA1MjE5NzMxNTAwMTc0ODg0NzQ0MDk1MjU3MDYyMjczODA2OTYzNjg5MjY3NzA1NTg4NTQ4MzU4NzQ2NDc1Mzc1MzAzMjI1OTI3MDkxMzA0NzQ2NTg5MzA1MDEzNjc1ODk0MzIxMDkzNjE0NzIxMjQwNDAzNDE5OTM5OTk0Mjg5NTU2MzY0MDExMTg2ODQ2NjMxNTA2OTU0NDg0NjM4NjgwMzEyMDA2Njk0MjcwOTkwNDU3NTk3NjAyNzc5MjUzMDc3MTg3NDgwNTg5NDMyNzU4ODgwMjY3NzA1NzMyMjg3Nzc0ODczOTI3MDExMTQ1MDE1NzgyNjE5NzI4NTAxNjI5MTE4ODE1ODM2NjU2OTMzMzcwMzgwNDk4NDk5MDE0MDEzNDI1NDMwMjMwODQzODc0OTk3NTg0NTY3NTA0Mzg3OTE4MjQxMzMxNTM1NDk5MTQxMjU1NzQzNjQ0MzgwNTQ4ODAxNDUwNDEyMzQzMTAxODc4Nzg3NzIzOTIxMDU5NjQyNDg2MjE3NjE0MzQyMDc0MTQ3ODk1ODA2MzQ1NTQ0Njk3NjI2MzcwMDY1MjYxMjQ3OTM2NzMwMzMyNTkyMjM3NDY1MjIyNTQ1MjE4ODc4MDk0ODk0NTE0ODU2ODUyNTU1NjI4MjIwNDg2MTU5MjcyMjIxMjM3MDkwMjc4NjUyNDM2MzcyNTE4MDgzOTUxNjAwNzI1ODA1MDYwNjExMTkyNzEyOTI3MjQ2MTUxMDU4NzU5NDk2MzQ3NjA0NDQwNDcxODcwODEyNzMwODE3MTU4NzQxNDQ1MDA0OTg2MTgyNDk5NDA4OTQxMzk2NjcwMzEyOTU0NDQ1MTk1NTM5MTM5MzIwMDI4MTI3NzMyMDQ0MSJ9LCJyX2NyZWRlbnRpYWwiOm51bGx9LCJzaWduYXR1cmVfY29ycmVjdG5lc3NfcHJvb2YiOnsic2UiOiIyOTQ0ODU2NTEyNDk3MjM2MTc2OTQ3OTMxNzM1Mjk0NDc4MTU5NTE2ODM4OTExNDEyMDE3MTI1NTAyNjc1ODM2Nzk1NTQ4OTczNDMyMTg2NjI0MTc2NjQwNzAxNjczOTk4MjI3NTEzMTA0OTU5OTgzMjk1NjI0MTA0MjkwNjgzMjU0OTI4NTg1MDkwMTI2Mjk5ODczMjY4NDYyODA5NTY4NjMxNDg3MDk2ODgzMDE0OTcwMTcyMzM0MzIwMTM4OTg5ODkzMzcyODIyODU2MTQxMTM5NTQ0MzQyMzExNDEzNDgyMDU0OTYyNjE5NTgzNjU5NjMwMzYxNTc3Nzg0MTQ4NjY3NTgwNjMxODc4NDIwNTgzMDk5OTM1NzE0NDYyMzE5Njg4NDE5MTM4NjY3Nzk2Nzc2MzQwMDk2MDIxMTgwMTU0ODU0Njg1MDEzNzY1MTM0ODMwNjA0MzEyMjI0MjIwNzA5MzQwODExMTI4MDczMDk3NjEyMDA0MTE4NjA0MDI3MTczNjExNTE2ODA0NTQxMzUzODA2OTkxMTg0MDY3MzY1MDE5Nzk2NDM2MDA3NDI0NTM5NzQ4ODQxMjU4NjA1MTUxNTQxNzQzMDk4MTE5NzI1Mzc4MTQ4MTgyNDQ2Njg3NDQ0MjE0ODk1NTE2NDc3MTM4Mjk1OTI1Mzc4Nzg1MTA3OTc5MTYxNTIyNTYyNDk2Njg2MTAzMjg3MDUxNDUyMTE3NTQzMzc4Mzc0MDM5Mjg3NzgxMjAwNzgxMTkyNDQyOTY5MDU1NjIwNTcyODg1NDM5NjU2MTU3Njk2Mzc5MTAyNDc2MTQ2IiwiYyI6IjI4NjYxMjE3ODQ5OTg3ODI4MTA2Nzk5MTAxOTIxNDcyNzgxNDMzNzgzMDE5Mzg0NzAwMDUzMjU4MTU5NDUxNjc5MjMwODI0NzA5NjIifSwicmV2X3JlZyI6bnVsbCwid2l0bmVzcyI6bnVsbH0=", }, "mime-type": "application/json", }, ], - "~please_ack": Object {}, - "~thread": Object { + "~please_ack": {}, + "~thread": { "thid": "e2c2194c-6ac6-4b27-9030-18887c79b5eb", }, }, - "metadata": Object {}, + "metadata": {}, "role": "receiver", + "updatedAt": "2022-01-21T22:50:20.522Z", }, }, - "2-4e4f-41d9-94c4-f49351b811f1": Object { + "2-4e4f-41d9-94c4-f49351b811f1": { "id": "2-4e4f-41d9-94c4-f49351b811f1", - "tags": Object { + "tags": { "associatedRecordId": "574b2a37-1db1-4af1-a3bf-35c6cb9e1d7a", "messageId": "1284ae78-f3d3-4fed-a5ff-0e2aba968c3c", "messageName": "request-credential", @@ -207,34 +211,35 @@ Object { "threadId": "578e73da-c3be-43d4-949b-7aadfd5a6eae", }, "type": "DidCommMessageRecord", - "value": Object { - "_tags": Object {}, + "value": { + "_tags": {}, "associatedRecordId": "574b2a37-1db1-4af1-a3bf-35c6cb9e1d7a", "createdAt": "2022-01-21T22:50:20.522Z", "id": "2-4e4f-41d9-94c4-f49351b811f1", - "message": Object { + "message": { "@id": "1284ae78-f3d3-4fed-a5ff-0e2aba968c3c", "@type": "https://didcomm.org/issue-credential/1.0/request-credential", - "requests~attach": Array [ - Object { + "requests~attach": [ + { "@id": "libindy-cred-request-0", - "data": Object { + "data": { "base64": "eyJwcm92ZXJfZGlkIjoiUVZveGd3d25WUGtBQlRMVmNtQ013TCIsImNyZWRfZGVmX2lkIjoiVEwxRWFQRkNaOFNpNWFVcnFTY0JEdDozOkNMOjY4MTpkZWZhdWx0IiwiYmxpbmRlZF9tcyI6eyJ1IjoiMTkwNzM1MzQyMjkwNjk4Mzk3NzgyNTUyMTM2NTA3NzAxMDA4NzYwMzcxMjg3NTQ1MzI0NDM2NDIyMTMwNzQ3MDI3NTk4NDM2NTM5Mzc0NzM0MjgxOTk4MDY5Mjg1OTg4MzAzMDE1MzAzMTYxNjExMTEzMTY2MzQxMzkyOTkzMTk0ODUxNzM5Njk4NzcxNDYzNzMzMDA4MjUxMzQ5NjM4OTkzMDE5NTk5MTY1NDUyOTk4OTA4OTY4MDE5NTUxODM2NDg1NzYxMzMxNTgxNTY0MzgxNTkxNjMwOTcyNTc5NTkyODMyNDk3MDI0NTMyMjQxNzk4MDMzMjI1NDg4NjA5NjEwNjEzNTU1NDMxODc3NDQzODk2ODUyMzA4NjIxNDA1NjI5NjA5MTg5Nzg2MjYzMTcyODU0MjA1MTI3ODMxNjc5NzM5NTkxODQyMTYwOTAyNjczMDE4Mzc0MzE5NjUyODA3Njc2MDQzNzc0ODcxMjQzMzkzNTIwNTkwODE5MDgxOTI4NzY3MjU5NDQ2OTIxNTM2MjU3MjQ2NjIxMjgzNjc2NDM1MDIwNzUwODI4NDI2NTM2MTU2ODA5NDgwMTU3MTQ0NDkxNTY2MTM0ODYzNjU4Mjg5ODIyMTE1NjI4MzMxNjMxMTQ3ODM1NzQ4MjAxMzkyNjY4NzQyMTQ5NTI1OTQ0OTc1NzY3NTYwMTQyNzQ5MTU3MTY2NzE0MDY0OTM2OTQ1MzEwMzEwMzU1NjgwNTcyNDgzNDgyNTYyMzk5Nzc0OTY5NTYwMTA1Njk2MzczMDU4MDMzODgyMTAwOTY2ODUwMTk5MjEzMzAiLCJ1ciI6bnVsbCwiaGlkZGVuX2F0dHJpYnV0ZXMiOlsibWFzdGVyX3NlY3JldCJdLCJjb21taXR0ZWRfYXR0cmlidXRlcyI6e319LCJibGluZGVkX21zX2NvcnJlY3RuZXNzX3Byb29mIjp7ImMiOiI1MDg3Mzk4NDExNzQ3Mzc5NjY5MDkyNzU5ODQ5OTEwMDI2OTYzMTk2NjExNjc5MzU5NDYwNDMxMjYyMDE4NzgyNzY4NTM2NTUzNzUwMiIsInZfZGFzaF9jYXAiOiIxODU0NzEwMDA5NDM4NTg5MTc4MzkzMjgzMDk1MzM5NDUzMDQ3OTkwOTYyMjE2NzEyNzk2ODkyMzcyMzA5NjU5NTU3MDY2MzQxMTMxOTY0Mjg5NjA3MTI5MDg4MjMxMDY0NTk5ODY4NDg4MTIzNDMwMzY5OTkxMjI1OTMxMTIyMjY4NjU5MDEwMDA0NDA1OTIzNTcyMzgyMzQzODczNjkxODg3NDQzMjQ5MTcwNTQwNDk4Nzk5MTkxOTIzMjc4NDU4MzcyMzk2NzIyMjM5NDE1NjY3ODIxMDQ4ODA0NDk1NDQ5ODQ3MjcwOTg1MDcwNzY3NjU2NDU4NDM0MTYzNTI3NDAyMzA1NTg5MTg4NzcyNDg4NzE1NjcwOTgxNTc0NzQxMTI1NzYxOTIxNTE3NzYyNzg1Nzk4MjU5MTQ0OTYzMDQyMjg0NzUwMDE5MjAwMjQ4NjgxNjkxOTE5Njg3MDA1MDA4MDUzMjYwODY5NzEyNTEwNzIwNDg5NDAwMjM0NDU3Njk2MDk4NjI0Nzk1MDUwMzQ2NzM2Mjg1MDE3MTU5Mjk1OTA0NTU1NTk0MzMxNzI4MTQ5MzgyODE5NTI2NTc3MDg3NjA0ODMwNDk3MTE0Mjc3MTkyMDU3ODk1MzYxNzI5NTE3NTgxNzg5ODEyMDY3MjcxNTU5MTMyNTI1MzEzNTc0MDEwNTM3NDMxMDY2NzUwNzAzMDgxMTQxNDIyODg5MzUxOTY0MDUyMDU0NjY0MTA0ODE0MzY1Njg3NTcxNjU5MTk3ODQxMjU5NjE3OTI4MDg4NjM0ODY1NTI2MjI4NTkyNTI2NTgwODgzMzIzNjEwNTc5NzU4ODgzMjgwNDcyNTA0OTQ0MjM2ODY5MTYyNzM3NzUwNTI0NTIyMjE5NTM4NjE4OTk2ODQzODU3MzU3MjUxODI5NTIyODgxOTA0NTAwMDU2MjU1NTMwNDgyIiwibV9jYXBzIjp7Im1hc3Rlcl9zZWNyZXQiOiIxMjM5OTQ3ODE4MDA1MTQ3MjQ5MjcyODIxNDI2OTgwNjQ2NTIwMjAwMTc0MzUwMzkzMzQ0NzU1NTU0NDAxNjg1NzQ2NzExMzkzMjEzMjA3NjI3ODI5MTczMjc2OTA2MDg4MDAxMDIxMTMxMzY4NzY4MjI0ODgxNTExMjEwNjY0NzA5OTAxOTQwODA0MzA0OTc1NTUyMzExNzAyMjU3MTYwOTAxNTE0MzIxNzYyNzM0ODUwMSJ9LCJyX2NhcHMiOnt9fSwibm9uY2UiOiIzNzM5ODQyNzAxNTA3ODY4NjQ0MzMxNjMifQ==", }, "mime-type": "application/json", }, ], - "~thread": Object { + "~thread": { "thid": "578e73da-c3be-43d4-949b-7aadfd5a6eae", }, }, - "metadata": Object {}, + "metadata": {}, "role": "receiver", + "updatedAt": "2022-01-21T22:50:20.522Z", }, }, - "3-4e4f-41d9-94c4-f49351b811f1": Object { + "3-4e4f-41d9-94c4-f49351b811f1": { "id": "3-4e4f-41d9-94c4-f49351b811f1", - "tags": Object { + "tags": { "associatedRecordId": "574b2a37-1db1-4af1-a3bf-35c6cb9e1d7a", "messageId": "d14cf505-4903-4dd9-95c2-a7dbc1c048b6", "messageName": "issue-credential", @@ -246,35 +251,36 @@ Object { "threadId": "578e73da-c3be-43d4-949b-7aadfd5a6eae", }, "type": "DidCommMessageRecord", - "value": Object { - "_tags": Object {}, + "value": { + "_tags": {}, "associatedRecordId": "574b2a37-1db1-4af1-a3bf-35c6cb9e1d7a", "createdAt": "2022-01-21T22:50:20.522Z", "id": "3-4e4f-41d9-94c4-f49351b811f1", - "message": Object { + "message": { "@id": "d14cf505-4903-4dd9-95c2-a7dbc1c048b6", "@type": "https://didcomm.org/issue-credential/1.0/issue-credential", - "credentials~attach": Array [ - Object { + "credentials~attach": [ + { "@id": "libindy-cred-0", - "data": Object { + "data": { "base64": "eyJzY2hlbWFfaWQiOiJUTDFFYVBGQ1o4U2k1YVVycVNjQkR0OjI6c2NoZW1hLTgwZjdlZWM1LThlNWEtNDNjYS1hZDRkLTMyNzRmYjkzNjFiODoxLjAiLCJjcmVkX2RlZl9pZCI6IlRMMUVhUEZDWjhTaTVhVXJxU2NCRHQ6MzpDTDo2ODE6ZGVmYXVsdCIsInJldl9yZWdfaWQiOm51bGwsInZhbHVlcyI6eyJuYW1lIjp7InJhdyI6IkFsaWNlIiwiZW5jb2RlZCI6IjI3MDM0NjQwMDI0MTE3MzMxMDMzMDYzMTI4MDQ0MDA0MzE4MjE4NDg2ODE2OTMxNTIwODg2NDA1NTM1NjU5OTM0NDE3NDM4NzgxNTA3In0sImRhdGVPZkJpcnRoIjp7InJhdyI6IjIwMjAtMDEtMDEiLCJlbmNvZGVkIjoiNDEwNjEyOTM3ODA0NjIwNzU1MTQyMDgyMjM4OTc1OTA0MDc4MDA3NDg1NDMwMTQ5OTE1ODg1MjI3MjExOTM4ODY4OTgxMTk3NTM2MjQifSwiYWdlIjp7InJhdyI6IjI1IiwiZW5jb2RlZCI6IjI1In19LCJzaWduYXR1cmUiOnsicF9jcmVkZW50aWFsIjp7Im1fMiI6Ijg5NjQzNDI5NjIzMzI5NjQ1MDM2NDU5MjU4NzU2Nzc5MDY3ODczOTc4MDg1ODc3MDM3MzU1NzMxMjk1MzA0NzY5ODAxNDM5MjI2MDI4IiwiYSI6IjUzOTQ3MDU1MTU0OTEyMTE2MzM0MzU2NTMyMjEyMTM5MjMxNjE2NTcwMzU3MjkwNzcyOTkxNjM4NTU1MjU2Mjc2NjgyODY5NDM2MTI5OTEzNzk2MzQwOTA1Nzk3Mzc2OTI5NDE0MTIwNzMwMjI5NjQzNjMwNTI2OTcyOTUxNDk1NjA5NDcwMDU0NzEzOTY1OTE5NTU3MzM3NDkwMjUyNTI1NTM5NzI4MjA0NTI2NTU0MDcyMDYyOTcxMTA0MTM3NTQ4NzEzMzIzNTUzMTYwOTEzODQ0MDk0MDczOTQ3MjM2OTQyNzgyODIzNDA2NDUyNzIzODY2NjMzMzc2MjI4Mzk2ODE5ODI0MTQ2MjgyNTI4NDIyOTIzMjM1NzEzNTI5MjQ3ODkxMTQzMDE4OTM3ODQ0ODM3NzQ2MDE4MTc5Mjk5ODI5ODQ1MTI4MTgxMDUxOTE4MjE0ODU5Mzg5MjIzOTEzNjUzMjE0MjIxMTk2NDI2OTA2NDM1NDYwNDQwNTgxNDkxNTg5MzMxMzIyMDU1MDU1NjE5NDY0OTEwNTc3OTcyODAyNDM1MDY3OTMxMDczOTI5OTgyOTQ2Njg4NDg5MTIwNTQ1MjA1MzQ0MjQ1NTIzNTExNDc5NjUzNjI5ODIxNTA4NTc3MjI2MzU5MjUyMDA5MjUyNTU2NDA5NTg0MDgwNDY5NDI4NzE1NDQ0MDkyOTA4NzAxNjMwMzE3NzI5MTE3NTYwMzg2NjAyNDA4OTE3Mzg2NjM4NzQ2MDY1NjU0NzQ2OTAxOTA1NjA4MjE5MzgzNzczNjg3NDcxODI3NjE2OTU5MDk3NDU4IiwiZSI6IjI1OTM0NDcyMzA1NTA2MjA1OTkwNzAyNTQ5MTQ4MDY5NzU3MTkzODI3Nzg4OTUxNTE1MjMwNjI0OTcyODU4MzEwNTY2NTgwMDcxMzMwNjc1OTE0OTk4MTY5MDU1OTE5Mzk4NzE0MzAxMjM2NzkxMzIwNjI5OTMyMzg5OTY5Njk0MjIxMzIzNTk1Njc0MjkzMDMwMzU1ODY2NDUxMDY0MDQ4Mzk4OTcyMjU0Nzk2ODY2NDA2OSIsInYiOiI4NzE4MTIwMTY2NjA3NTg4MjIxMDczMTE0NTgxNTcxMDA5NTEwNDUwMzkyNDk0MDM1MTg3MDk5MTQyNzA5NDcyMjYxMDAyMjg5MzI5MzcyMzk1MDUwMzgzOTA3ODUxODc1MjIxODU0NjI4ODc4OTcxNzQ0Njg3MDgxMDM4Mzk2Njc2MzgyMTI0NjEyNDY0NjQxMDQ4NDMxNDkwMTYzMDgwOTU0NTc3MjA3OTkxNjg3NzU0NjQ4MTYwNzM5MjQ2ODUyMjMxMjU2NTk0MTg4MTAxNDU2MjgzNjc3MTA4NTMwNjY1NDQwNTUwMDY0MjgxODI3NzUxNjA3NjM1ODE2MjczNDU3MTc4MzAyNjEyOTI5ODMyNDMzNDc3ODk0ODMzMDc2MDA5OTE4MTc1MzI4OTUzNjg1MjEwOTQ1ODg0MTQyMDg0Nzk4ODMzNzM2OTExNDcwMTkwOTYwMjI3MzAyMzI2NjQ2NjE2NjUwMjY4OTU3NDcyMTI3MTA2Mjk3NzQ0NDg3MDUzODY2NjI0NTk4Njg1MzA0MzA0OTMzNjAxNDczNjY4Njg1NDg5MzYyMzQ2NzE5ODA4MjgxNzYxNjc0ODQyMzE5NTY5Mzk1Nzk1NTQ5OTA4MjAyODI0MjgyOTc1MTI3MDA0MzM0NTkwNTYzMTI3NjU3NDM3MjQ2MDQ3OTUyOTk0OTIyODQ3MzcxMzY4NDM0OTE3MDM1Njc4ODA2NjM1OTQ0ODY4Njc5MDY1NDc5Njk1MDU5NzkyNzUyMzk5NDcwMzUzMDI3MjEyNTg2OTc1Mjk5MTk1NDcwNjY0NDMzMDIyNTQyODg4MzI4OTA0Mjg2NTIxMzM5Nzc2OTkxMDYzNzA1MTI2NjA4MDY4OTA0Mzg2MDc4NzA5NTE3NjU0OTE3MzI0NjExMzkzNTM4MDkyNTQ3NzQ0OTM2NTM1NDkwODcwNDU4NjQ3NjY2OTU3MjA5MDk4MDU2NzIwMjAzOTAxMjI3MjU2NDM5NTkwNTA0MzIwOTI3NTc1ODA2NjE0NzA4NTU3MjAxMTAxODczODc4NDg1NjM4MzQ2Nzk1NjE4NDQxOTQxMjQyODc5ODMyNjQ5ODEyIn0sInJfY3JlZGVudGlhbCI6bnVsbH0sInNpZ25hdHVyZV9jb3JyZWN0bmVzc19wcm9vZiI6eyJzZSI6IjIxOTkzMzcwNTI0MjIxNTM0MTM0MTc4MDM3MDIyMDEyMzE4NjQ2MDE4MTk0MDIwMjgxNzY4NTQ4OTUyNjM5ODg4MDE2NDQ1NTM2NTQ2MzczMzkwMDU5NjY1Nzg4OTc2MDE0OTUzMTU2MjA1MTA0ODU1NTM1NjEyODY5Mjg5NjgyOTQ1ODI4MDQxOTMxMzc1ODY4NjE4OTE0NjUwNTc5ODM2NzI0NDE0MDMxMjU3MjU2MzkxNjg2OTQ0NjQyMzg3NTIwNjExNDQ5ODM1NTgxNDMzMDMzOTQ4MTA4OTE0MzI2NzkyNDU5MjQ0Mzc0MTgyOTQ4MDQxODIzMTg3NjY3MjE2MDI5OTEwMTAzMTM1MjE4NjY5ODc5MDk5ODA0NTA0NjI1NTAzNDM2NTAxOTk5ODkwODIyNjcyMjYwNzc1NDIzMzIxNTQ0MDk0Mjk2NDI0Nzc2Njg2MDI1MzU2MjMwOTY0OTQyMzc3NTY0NDUwMDk4NTgyMzg2Nzg0ODc3OTQwNTI2ODg0MzgzOTcxOTE4OTE3ODIyOTkzMDIzMDU2NjU1NTg2NDI3MDAwMzQ2OTcwMTI1MjA2ODg0NTkyNzkwMDU4NTAyMzkxMzUwODIyNjg1NDYyMzY0MzE5NTMwOTI0MjM4Mzg2MjkwMDI5MTQ1ODAzNTgxODA2MDQ1MTYzNDMzNTQ2OTAwMzQzNDg0MTg2NTEyMjIzODYwODY3NTI3ODExOTkyOTQwMjMxMzgzOTM4MjI0NjEwMTk0NDc1NjczMDYyMDI3MDgwOTI5NDYzMjU0NjIxMjI1NTg3MDg0NTUyODEyMDgxMDE3IiwiYyI6Ijg2NzE3OTAzNTAxODI5MzU5NDk4MjE3NDU5NjE3NjgyNTM4NzIxNzQwMjE5MDM5MDUzNjQzNTE3NDU0Nzc2NTUzMzA3MzU1OTkxMjE5In0sInJldl9yZWciOm51bGwsIndpdG5lc3MiOm51bGx9", }, "mime-type": "application/json", }, ], - "~please_ack": Object {}, - "~thread": Object { + "~please_ack": {}, + "~thread": { "thid": "578e73da-c3be-43d4-949b-7aadfd5a6eae", }, }, - "metadata": Object {}, + "metadata": {}, "role": "sender", + "updatedAt": "2022-01-21T22:50:20.522Z", }, }, - "4-4e4f-41d9-94c4-f49351b811f1": Object { + "4-4e4f-41d9-94c4-f49351b811f1": { "id": "4-4e4f-41d9-94c4-f49351b811f1", - "tags": Object { + "tags": { "associatedRecordId": "ad644d8a-48a2-4c55-b46d-7a7f1a9278c7", "messageId": "e2c2194c-6ac6-4b27-9030-18887c79b5eb", "messageName": "offer-credential", @@ -286,51 +292,52 @@ Object { "threadId": "e2c2194c-6ac6-4b27-9030-18887c79b5eb", }, "type": "DidCommMessageRecord", - "value": Object { - "_tags": Object {}, + "value": { + "_tags": {}, "associatedRecordId": "ad644d8a-48a2-4c55-b46d-7a7f1a9278c7", "createdAt": "2022-01-21T22:50:20.522Z", "id": "4-4e4f-41d9-94c4-f49351b811f1", - "message": Object { + "message": { "@id": "e2c2194c-6ac6-4b27-9030-18887c79b5eb", "@type": "https://didcomm.org/issue-credential/1.0/offer-credential", - "credential_preview": Object { + "credential_preview": { "@type": "https://didcomm.org/issue-credential/1.0/credential-preview", - "attributes": Array [ - Object { + "attributes": [ + { "mime-type": "text/plain", "name": "name", "value": "Alice", }, - Object { + { "mime-type": "text/plain", "name": "age", "value": "25", }, - Object { + { "mime-type": "text/plain", "name": "dateOfBirth", "value": "2020-01-01", }, ], }, - "offers~attach": Array [ - Object { + "offers~attach": [ + { "@id": "libindy-cred-offer-0", - "data": Object { + "data": { "base64": "eyJzY2hlbWFfaWQiOiJUTDFFYVBGQ1o4U2k1YVVycVNjQkR0OjI6c2NoZW1hLTgwZjdlZWM1LThlNWEtNDNjYS1hZDRkLTMyNzRmYjkzNjFiODoxLjAiLCJjcmVkX2RlZl9pZCI6IlRMMUVhUEZDWjhTaTVhVXJxU2NCRHQ6MzpDTDo2ODE6ZGVmYXVsdCIsImtleV9jb3JyZWN0bmVzc19wcm9vZiI6eyJjIjoiMTEzOTY4MTg4OTM2OTQ5MzcyNzU3NjU2NzI1MjU1MTQ3NDk1OTI5NTM0MjQ5NjU1MzY4NTMzMTY4OTIzMjU4NTA2OTUzOTk3MTI2MDEyIiwieHpfY2FwIjoiMjM5NDkwMjQ4MjE4MTExOTQ1MjIxOTQ1ODcyMTE4MjQzNzA3NjE5OTQ4MzQ0MjU1ODM5ODI4NTU3NjkyNTE3NDExNDMwNDgwNDgwMTkxMTMwMjM0OTg5ODk0NzIyNDE2Nzk1MzUzODAwMDk3NDUxMjI5NDE4MzQ0MjEyOTI3NDk1NjI2NTc4MTk2ODUxMTcwMTI0MDI1NDk1MDExMjc0NjU1NjQzNjkzNTE1ODczMjA5OTczMzgwNjA3MzQxNzQzMzIwNTY0NjkwMzcxOTgyNDIxNTQyMzQzMTMzNTMxOTcxMTk4NTA4NDk5MjYyNzUxNDMyODg0NzgzMzc1MTAzODI0OTE3NzEwODAxOTE3OTc1OTM2OTg4OTIwMzYyMzA5NjE4NTgxMzY0ODE5NTA5ODYxNTE3NjI2ODc3OTUzMzMzMTkzMDExMjA2NDA5NDQ1MzA0MzEwMjUzMTU5OTE1NzYxOTI1MDY1MTg3Mjk1OTg1MzU0NDY1NjY5MTMxNjgwMzc5MzY0ODk0Mzc3NTYxODcyMjcxNDY5Mjk1NzY4MTc4NjQ2NDMxODI3NjI1MTQ3Mzk4MDg1ODI3NTUzMDAzNjIyMDM1ODM1MDg2NzE1NjgyMDA5MzgyNjgxNDc3NDc4ODQ0MDEyNDQ5NTE2NjYwODMwNDMwODQ5ODMxNjAxNDk3MTk3MjczMTIzNjg1NTE0NDMwMjY5OTkxMzMzNDI1Nzk0NjAwMzc3Mzk3NDMwMjg0MjIyMjQ1OTgyMjI1NTE3MjQ4NzA5NTczMzEwNjM5NzQyNzc2NjMyMzM3MDM0Nzk4NDY3MTAwNDczNDUxNTMzMTg1NDg5NDU0NjUyNTgwMjcxNjgyMDQzOTc4MDY4NDc4MjM1MjM5NjMzMTk0MzE4NDcxNDM2MjMwOTg1NTQ1MzAyMDQwNiIsInhyX2NhcCI6W1siZGF0ZW9mYmlydGgiLCIyNjMyMjI0MDEyMDg0NjM3ODA0NjA4MDE5MTM2MzIyNDkzMTE1MzA1MTA2ODQ1MTA0NTE4MDE4MjY2MjU1NjMyNDM0MTMzODY0MTIwNzUxNDkyMDAyMTI4MzU3NTcwMTY4MjE0OTU3OTgzNTQ3Mjk0NTczOTExMzk3MTQwNDAxNDk4MzQ0NzE0NTA5MjEyNDA2NTYzNzgyOTc4ODUzNDgzMTM4NTA1NTA3OTcxNTY3MTgyOTQ1ODQ5ODI3MDU0Nzg4NjA3ODgwOTU5NDE1MTYwMjU0OTE2MDExODkyNTIzNjUzODA1NTk2MDQ3NjA5Mjg3ODA4ODg2MzcwOTM5NDA3Njc5NTE3MjUzOTUxOTUzNDgxNDkzOTMzNDI3NTA5OTUwNTY5NjUwMTc4MjQwNTg0ODI4NzgzOTAxOTA0MjI3MzE1OTEyNzQ0NDYyODc3NDI3Njg3MDEzMDkyNDY1NTc5NzY0OTk2MTc2NTU1NDg1Nzc2Njc0NTcxMjcwNjU4NjAyMTk3OTExNTYzMjY0MzEyNTgzNzMyNTE3NjI3MjY3MzQ1MzEwODcwMjk2NTk4NTEyMDc0NzczNDQyMTExNjY4NjM0MzYzNjkzMDkxNzQ2MjE1MDQwODg1NTcyMzAzMDU4MDUwNzc3Nzg4Mzc5NDY3MzMyNDUwOTY5NTg1NDE2NzU2NzA2MzcxMzMyOTk5MDg1NjM5MDU4Nzk4ODE4MjkwNzEyNDk2NDk0NTY1MzgxMjI0NzUyMTA2OTQyMTg2OTc3MzUwNzE2NjI0MTYxMjIwNjExMDY3MzcxNTM0NjYyNTc0MzY1NTM0MzEzNjAwMTQyODk2MDkwMDQ3MTI5NjQ3OTEzOTgzMjk0Mjc2MDI2OTA2MTA3NzM4MDM2MjI2MTA4NzU0MTE3OTIwMTg2NDM1MDkwOTU2Il0sWyJuYW1lIiwiMzE2Nzc0MzUwOTgzMjI4Nzg1MDcxNDg1NjYzOTM0MTYyNjQ1MzE2NjQzMTc5Njg2NTg2MTU2MjgwMjg3Njg5Nzg0NTI1Mjg5MzY0NDg5NDMxNjEwMTc1MzUwMDgzMzUzMTg3MTIwMTE1MTU1NjUxOTYzMjcyODM4MjcyMTgyNTI2MzUyNjMyMzI5MDY2NDIxMjM3OTM3NTc4MjY4MjkwNzU4MDgwNjI0MjE3MDM0NDU5MDUyMjY2Njk5NjQzMTg2MjkyNjcxNDk1ODEwMDU4Mjg5MzA1MjI4OTQ2MTQ2MTkzMDAzNDk0NDgwNzY0NDY5Nzc1NzM5Njg0NzcxNjMyNDIzNTM5MjE1MzIxMjc0NTQ4NDU5NzE4NTM3NTMzMzE0MjYwMjcwNzE0MTkzNjc4MTEzMDg5MDUxODI1MjA2MTAyMjQ0MTc3NzAwNDk3MTYxMDM2NjgwNDYzMDUxNDcxNDk3MTgzNzc3Nzc5Nzc2Nzc0MTUzNzQ1NjEwNzc3NzgyMDYyODA3NjY5MjE2ODA2NDgxNzAzNTU5NDk5MTAyNTc5ODAwNjgxNTQxMjg3MTk0MTAwNzg4MDMxNDE3Nzg5MzQyNjk2NTQyNTA3NjE5MTgzNDIyMTc0OTk5NzQ2ODM3NjY4NTg0Mzk0NzQxNzI5MDY2MjgwNjYyMzAyMDI3NTczMDgwMDY5NTE5MDgxOTA5OTA3MzQzODAxNzg3MjgyMTEyODY2NzkwNTI2MDIwMjk4NjM2ODY3Njg2NTE5MjQ0NTg2MTg1NzMxMTE0ODk1ODU3NjkzNTQxMTIwOTc2NTQ5MzYwNDE1MTkxMjI4ODA4MzE5NTcxOTU4NTkxNDc4NDYwNzMwMTg2NDQ4MTU3NjU3OTkyOTI4NTM2MjgxODU2NjAzNjU5NjM3OTE2NzE0OTk0NTU0NSJdLFsiYWdlIiwiMTY5MjgwMDQ2OTQyNDI3NDY1ODE5OTE3MzEyNTkzMzEyMzkyNDYzMDQxODc5MzIwMjM0NDU4NjQ4Mzg2MzI4MTE5MDQ5OTIyMzc1NjkxNzI0ODYzMDM3NDMyODU4OTQ4MTIyNzI2ODQ5NDU5NDcxODA2NTU4NzYyMzU4MTgzMzU3OTYwNDk3NTMyNjUzNzE4ODE0NzQ1NzY2ODc3OTI2NzU2NTQ3NjQwMjUzMTczMDYzMjY0Mzc0OTAzNTgwNjEwNDMxMTM2NTA2NjQ1NjE5NzYzNTE1Nzc3MTkyNjU4ODk0OTc1MDMyODAzNzM0MTE5MjM5NjcxMjgwOTQyOTkxMTg2MjYyNjYzNTM5NDU3ODc1NjY1NDcwMTAxMTEzOTUyOTY2MDQyMzU4NDQ4ODE1NTk5MDgzNTU4NTIyMDQ1OTI3NDI0NjI5Njk4MTgzMTUzNzUyMDA4MzM5NTI1NTYxMDI0ODg2MzUzOTc3NzA1ODE5Mjc1MzQzOTg3MzMzODMxNjU0NzA4ODI3NDI0NzMzNzcyNjI3MTA2OTgxNjE5NDY0MzUwNDU3NzE4NzM2MDA0NjEyNzQ0OTAyNDA5NjA0Njk4NzkzNzI0MTc1MDA4OTUzMDMyMDgxMTQ2OTE3MTM4ODc4NzQ4MDM0Mzg1NDQxNTIxMTU5ODM2NDIwMDEzNTQ1NTQyMDk2NDIwODA1MDYxMzI5MTkwNzczNzIzMDYxMjE2NDIzNjczNTM1MzU1OTc5MzY1Njg0MzM2NjEyOTU2NjkxNDA4NDQ5MjE4MjcwOTYyODUyMzQwMTQ2MDAwMDYzNzA3NzU5MzUxODM0MTQ2MDI4MTYyMTEyMzU4MzAzNDQ1OTcwMTg3MTk3OTQ5MDcwNzE4NzQ4OTI4NjM5MDkyMzY1MjExMjgyODY0NDE3OTcwOTg5OCJdLFsibWFzdGVyX3NlY3JldCIsIjk4NjY2MDAzNjA2Njc3MjkxODM1MjEwMzA1NDczNTA3NjU0NTM1OTgxNDYxODkyNjY5NjI5OTE1MzQ5NjU3NjY5MTI5NzAxNTUwNzUyNTU2NjMxMzU4MzU3NjEzNjg3OTgyNTQzNTcyNTc5ODEzOTgxMzI4MDM4NzY5OTcxMzAwNTE0NDI2NzAxNTM2ODE1ODI3MDgzNDY5MzEzOTQ0ODAzMzIzMDUzMzUyNDkxMTIwMDUwNzkyNzA4NTQzMTE2NTM1NjA2NTQyNDY3OTcxNTUxODA4MzQyOTk1OTM4NzQ2NDQ4NjMyNTY4NjU0NjA4MzI1NDk2NjM3Mzc5OTQ0NjA5MDU3Mjc2OTE0OTQxNzE4MzU2MTYzODYyMzI2MDc3MDUzNjIyMDI3OTE2MzIzNzAzMDE2MTc4NDQ3MDEwMTc1MjI1MTM0NjE3NTcxMTgzMjcyNzMwNjQxNzI3Mzk2MzM4ODk2NTUyNzM4NzUwMDA4MTQ3MDExNjkyNzIwNzI4NDY2MjcwNDQ2MDg4NjEyMDg2MDExMzg2NzQxOTMzODM4ODQ1NjkzMTM1NzcyODk0MDIwNTM4NTU3ODI1MjA3OTkxNDAwODIyNjg4OTgwNTg4MjgzMTY0MzAxNTY1NjAyNDAzNTI4MDE2MTczNTk5MTM4NzI5ODEyOTE2MTYxNDEwNjgyODU4MzU4MDE3ODI1ODUyMzY2OTgzMDQzMzM4ODY2MzI3MzM3MTE0NzcyMzM5NTUyNTYzNzU0NzQ0NTA5MTYzNzYzNjAyNTI0NjgxNTUyNjIyOTYwNjM4MzE2OTc0NjI4MjQyNjQ5NjYyMjQyMTkzODMxOTUyMDE0MTAxNTA3Njk2MjIxMDU5NDE5MTcyNzMwNjE2NDE2NzEwNTMzMzc2NjI4MjQ4NzkxMTUwNjMxMDMxOCJdXX0sIm5vbmNlIjoiNTQzODYyOTczNzUxMjk1OTk2MTAzNjA3In0=", }, "mime-type": "application/json", }, ], }, - "metadata": Object {}, + "metadata": {}, "role": "sender", + "updatedAt": "2022-01-21T22:50:20.522Z", }, }, - "5-4e4f-41d9-94c4-f49351b811f1": Object { + "5-4e4f-41d9-94c4-f49351b811f1": { "id": "5-4e4f-41d9-94c4-f49351b811f1", - "tags": Object { + "tags": { "associatedRecordId": "ad644d8a-48a2-4c55-b46d-7a7f1a9278c7", "messageId": "edba1c87-51d3-4c70-aff2-ab8016e1060e", "messageName": "request-credential", @@ -342,67 +349,66 @@ Object { "threadId": "e2c2194c-6ac6-4b27-9030-18887c79b5eb", }, "type": "DidCommMessageRecord", - "value": Object { - "_tags": Object {}, + "value": { + "_tags": {}, "associatedRecordId": "ad644d8a-48a2-4c55-b46d-7a7f1a9278c7", "createdAt": "2022-01-21T22:50:20.522Z", "id": "5-4e4f-41d9-94c4-f49351b811f1", - "message": Object { + "message": { "@id": "edba1c87-51d3-4c70-aff2-ab8016e1060e", "@type": "https://didcomm.org/issue-credential/1.0/request-credential", - "requests~attach": Array [ - Object { + "requests~attach": [ + { "@id": "libindy-cred-request-0", - "data": Object { + "data": { "base64": "eyJwcm92ZXJfZGlkIjoiRUg2OTVENjRRd2hWRmtyazFtcDQ5aiIsImNyZWRfZGVmX2lkIjoiVEwxRWFQRkNaOFNpNWFVcnFTY0JEdDozOkNMOjY4MTpkZWZhdWx0IiwiYmxpbmRlZF9tcyI6eyJ1IjoiOTcwNjA5MzQ1NDAxNzE0NDIxNjQzNDg0NDE0MTQwOTA0NzMzNTQ4NTA4NzY0OTk5MTgxNzY1ODI3MjM3ODg3NjQ2MzQzNDYyMTY0ODA3MTUxMjg1ODk2MDczODAyNDY1MDMwMTcxNTIyODE4ODY4Mjc2ODUwMzE0NzQzMjM3ODc3NDMyMDgxMTQwMzE5ODU5OTM5NjM0MTI4NzkzOTk4NDcwMTUzNTQ0NjgxNTM5NDg4NzEyNDE5MTk3NzcxODE1NjU5Nzg1NDE5NTA1ODEyODI4NzYxOTI4MzExODczNjA5NDYwMjExMzQ4OTAyNDk4NzYxNjc5OTIzNzA2NjUwMDIzODg4ODU4NzE1MTYxMTIyMzA5MTc1MTE3MTk0NDMwNTI1ODY5NjcwMDEzMTgxNTkzNjI4NDQzMjk2MDI0MDE5NTc4MzIzODQwNzk0OTQ0MjIyODE1MTQwNTM2Mjc3ODQwMjU2ODc2MDE1MzUwNDgzOTE2MjYzMDgyODM5NzI2NzAxNjg4NjcxNDY0MjA3MzQxMTgwMjg3Mjk0Njg4NDA3NzQ3MDk1NjA0NzQ3NzA3OTc2Nzk2MjU0MTQ2NDQ0NTY5NzQ4MTk4OTMwMjkyOTkzNjY1ODk2MTUyMDMyNzQwODY3OTgwMjczMTMxMDM3NjkwNDkzNDU1Mjc4NDc3MDc3NjE0OTU2NjgzNjgxNDc5NzY3Njg0MDI4MzU5NzE4NzM0ODEyNzcyMDIyOTIwODQ3NDIyNDYyOTAwMjczMTcwMTU2NzQyMzUyMDQ2NDYyODI4NzAxMTE2MzU0MTkwMDY5MDE0NTIwMSIsInVyIjpudWxsLCJoaWRkZW5fYXR0cmlidXRlcyI6WyJtYXN0ZXJfc2VjcmV0Il0sImNvbW1pdHRlZF9hdHRyaWJ1dGVzIjp7fX0sImJsaW5kZWRfbXNfY29ycmVjdG5lc3NfcHJvb2YiOnsiYyI6IjE4MzE5MTUyNjg0NDkyMTM0OTc4MzkzNDE3OTY5NjQ5MDIyNzMzNzQzMTQ5NTUwNzAwNzc5ODk0Nzg1MTg3MTA1OTkyNjk4Mzg5MjAzIiwidl9kYXNoX2NhcCI6IjQ0NzA4MzAwOTUyNzA3MjA3NjI3NjA3NzM4MTI2NDgxNzA3OTA1MDcwNjEyMzQ5OTIxNTAxNTYyOTA2MzgyMzE0MDE4MzQ4MTAxMTE4MDI4MDIyMjk5OTgyNTEwMjI5ODM1OTQxMzY1MTM4Njg0MTU1OTEyOTE3NzYwMjgwOTIyNDk1MjE1ODA2NTM3MzA5NDU5NDA3NjcxNDgyNDA0NDMwODU4MzU5ODU3MDgzNzg1Njk1MTYzMjkwMzEyNzMzNDAxNjY1NDk5MjUwMDQ0NzkwODk4OTA4NzIzNzE1OTc0MDYwNzgyNDEzODYzMTU0MTUxNjg2OTM3ODY4MDM5MDU1Nzc1MzA5MjQ0MTYzOTUxNzgwMTgxNDk5MDM5MDgyMjcxNzgzNTgzNTkxMDIyNjYwMDYyMDQ3MDQ2NTEyMTM0NDU5OTI1MTgyOTg2MTkxOTAwNTQwMDg4MjE3Mzc2NjM4MjEzNTI0MDUxNjcxNzg3ODY0ODQ2NzIxNjk5NjQzNDk0MTI2MjA3NTg2MjgwNjQ5OTc4ODE2ODEwMjM5OTAzMzU0NzIyOTI2NTUxODYyNTQwMjc4ODU2OTEyNDQ2MDUzMTg4MzI3Mjk4NDc0NjgzMjkwMjU4MjgwNjY0OTgyOTM2NTYyODcwNTIyODA2NzYwMjE1OTI0MDc3ODQ2NjA2NTM0NzI4MjkyODQ2MDQyOTk1NjgxMTQzOTQ5MTU0MTU0NTU2NzQzMDYzMzY1OTIzMzU2OTg1MjQ5ODI1Njk2NzE3MzE2MDk1NzE5MDU2MTE5NTE0NTYwODY0MzUyMDc4ODMyOTYzNjI3Mjk0Njk1ODQ0MTE5NDA1NTMzNTY5MTI2ODExODE3NDYyNjczMzM3OTg4MzA0MDcwMzk5ODYyNTk2ODMyNDk2OTU3NzA4Nzc0NzI5NzAyOTEyNjY3Njk0OTgwMjc3OTI5MDgzNiIsIm1fY2FwcyI6eyJtYXN0ZXJfc2VjcmV0IjoiMjIxNTAyNTExNTYzODg1MTM1NzIwNjg4MzE5Njk5NzE5ODAxNDI4NDgzMTI2MjY0NjI0NTE5OTA4MjM5NTM0MjQ3MDQ3NTIwODgyNDE4Mzk0ODMzNjUwODM2NTI0NDk2MzUzMDkwNzIzOTI1MDc2NDY2ODQ3NjIxNzE4MDA4MDAxNTQyNTMzOTk4NTU1MzA1MDYwNjUzMzkwMjc4MDc2MzE2Mjg5MzcwMzA2MDcyMDYxNCJ9LCJyX2NhcHMiOnt9fSwibm9uY2UiOiI2OTgzNzA2MTYwMjM4ODM3MzA0OTgzNzUifQ==", }, "mime-type": "application/json", }, ], - "~thread": Object { + "~thread": { "thid": "e2c2194c-6ac6-4b27-9030-18887c79b5eb", }, }, - "metadata": Object {}, + "metadata": {}, "role": "receiver", + "updatedAt": "2022-01-21T22:50:20.522Z", }, }, - "574b2a37-1db1-4af1-a3bf-35c6cb9e1d7a": Object { + "574b2a37-1db1-4af1-a3bf-35c6cb9e1d7a": { "id": "574b2a37-1db1-4af1-a3bf-35c6cb9e1d7a", - "tags": Object { + "tags": { "connectionId": "0b6de73d-b376-430f-b2b4-f6e51407bb66", - "credentialIds": Array [], - "indyCredentialRevocationId": undefined, - "indyRevocationRegistryId": undefined, + "credentialIds": [], "state": "done", "threadId": "578e73da-c3be-43d4-949b-7aadfd5a6eae", }, "type": "CredentialRecord", - "value": Object { + "value": { "autoAcceptCredential": "contentApproved", "connectionId": "0b6de73d-b376-430f-b2b4-f6e51407bb66", "createdAt": "2022-03-21T22:50:20.522Z", - "credentialAttributes": Array [ - Object { + "credentialAttributes": [ + { "mime-type": "text/plain", "name": "name", "value": "Alice", }, - Object { + { "mime-type": "text/plain", "name": "age", "value": "25", }, - Object { + { "mime-type": "text/plain", "name": "dateOfBirth", "value": "2020-01-01", }, ], - "credentials": Array [], + "credentials": [], "id": "574b2a37-1db1-4af1-a3bf-35c6cb9e1d7a", - "metadata": Object { - "_internal/indyCredential": Object { + "metadata": { + "_internal/indyCredential": { "credentialDefinitionId": "TL1EaPFCZ8Si5aUrqScBDt:3:CL:681:default", "schemaId": "TL1EaPFCZ8Si5aUrqScBDt:2:schema-80f7eec5-8e5a-43ca-ad4d-3274fb9361b8:1.0", }, @@ -410,57 +416,56 @@ Object { "protocolVersion": "v1", "state": "done", "threadId": "578e73da-c3be-43d4-949b-7aadfd5a6eae", + "updatedAt": "2022-01-21T22:50:20.522Z", }, }, - "5f2b7bc7-edfd-47e7-a1d4-aae050df2c4a": Object { + "5f2b7bc7-edfd-47e7-a1d4-aae050df2c4a": { "id": "5f2b7bc7-edfd-47e7-a1d4-aae050df2c4a", - "tags": Object { + "tags": { "connectionId": "54b61a2c-59ae-4e63-a441-7f1286350132", "credentialId": "a77114e1-c812-4bff-a53c-3d5003fcc278", - "credentialIds": Array [ + "credentialIds": [ "a77114e1-c812-4bff-a53c-3d5003fcc278", ], - "indyCredentialRevocationId": undefined, - "indyRevocationRegistryId": undefined, "state": "done", "threadId": "578e73da-c3be-43d4-949b-7aadfd5a6eae", }, "type": "CredentialRecord", - "value": Object { + "value": { "autoAcceptCredential": "contentApproved", "connectionId": "54b61a2c-59ae-4e63-a441-7f1286350132", "createdAt": "2022-03-21T22:50:20.535Z", - "credentialAttributes": Array [ - Object { + "credentialAttributes": [ + { "mime-type": "text/plain", "name": "name", "value": "Alice", }, - Object { + { "mime-type": "text/plain", "name": "age", "value": "25", }, - Object { + { "mime-type": "text/plain", "name": "dateOfBirth", "value": "2020-01-01", }, ], - "credentials": Array [ - Object { + "credentials": [ + { "credentialRecordId": "a77114e1-c812-4bff-a53c-3d5003fcc278", "credentialRecordType": "indy", }, ], "id": "5f2b7bc7-edfd-47e7-a1d4-aae050df2c4a", - "metadata": Object { - "_internal/indyCredential": Object { + "metadata": { + "_internal/indyCredential": { "credentialDefinitionId": "TL1EaPFCZ8Si5aUrqScBDt:3:CL:681:default", "schemaId": "TL1EaPFCZ8Si5aUrqScBDt:2:schema-80f7eec5-8e5a-43ca-ad4d-3274fb9361b8:1.0", }, - "_internal/indyRequest": Object { - "master_secret_blinding_data": Object { + "_internal/indyRequest": { + "master_secret_blinding_data": { "v_prime": "36456944381549782028917743247126995038265466209293312755125557271456380841610111892515020379470931691048072348420844231863825225515560265358581756565441268878364665494094789024845049226122885121039335781567964878826549149370097276812152226343824116049855825405977949749345353074025294938300401262824951638782220004732873597724698990420932910079362747837952520524827009393981876443737452031919055976088763615615890946142630576421462920865811255312740184209214306243871230276622595183415487741608569800898909023830922654063814555128779494528740438076748829436757078504882332589744263200806138145494157659396691564807976032319024007464003538934", "vr_prime": null, }, @@ -471,11 +476,12 @@ Object { "protocolVersion": "v1", "state": "done", "threadId": "578e73da-c3be-43d4-949b-7aadfd5a6eae", + "updatedAt": "2022-01-21T22:50:20.522Z", }, }, - "6-4e4f-41d9-94c4-f49351b811f1": Object { + "6-4e4f-41d9-94c4-f49351b811f1": { "id": "6-4e4f-41d9-94c4-f49351b811f1", - "tags": Object { + "tags": { "associatedRecordId": "ad644d8a-48a2-4c55-b46d-7a7f1a9278c7", "messageId": "a340a7b9-f4d4-4892-b7d2-1d3d40e4be48", "messageName": "issue-credential", @@ -487,35 +493,36 @@ Object { "threadId": "e2c2194c-6ac6-4b27-9030-18887c79b5eb", }, "type": "DidCommMessageRecord", - "value": Object { - "_tags": Object {}, + "value": { + "_tags": {}, "associatedRecordId": "ad644d8a-48a2-4c55-b46d-7a7f1a9278c7", "createdAt": "2022-01-21T22:50:20.522Z", "id": "6-4e4f-41d9-94c4-f49351b811f1", - "message": Object { + "message": { "@id": "a340a7b9-f4d4-4892-b7d2-1d3d40e4be48", "@type": "https://didcomm.org/issue-credential/1.0/issue-credential", - "credentials~attach": Array [ - Object { + "credentials~attach": [ + { "@id": "libindy-cred-0", - "data": Object { + "data": { "base64": "eyJzY2hlbWFfaWQiOiJUTDFFYVBGQ1o4U2k1YVVycVNjQkR0OjI6c2NoZW1hLTgwZjdlZWM1LThlNWEtNDNjYS1hZDRkLTMyNzRmYjkzNjFiODoxLjAiLCJjcmVkX2RlZl9pZCI6IlRMMUVhUEZDWjhTaTVhVXJxU2NCRHQ6MzpDTDo2ODE6ZGVmYXVsdCIsInJldl9yZWdfaWQiOm51bGwsInZhbHVlcyI6eyJuYW1lIjp7InJhdyI6IkFsaWNlIiwiZW5jb2RlZCI6IjI3MDM0NjQwMDI0MTE3MzMxMDMzMDYzMTI4MDQ0MDA0MzE4MjE4NDg2ODE2OTMxNTIwODg2NDA1NTM1NjU5OTM0NDE3NDM4NzgxNTA3In0sImFnZSI6eyJyYXciOiIyNSIsImVuY29kZWQiOiIyNSJ9LCJkYXRlT2ZCaXJ0aCI6eyJyYXciOiIyMDIwLTAxLTAxIiwiZW5jb2RlZCI6IjQxMDYxMjkzNzgwNDYyMDc1NTE0MjA4MjIzODk3NTkwNDA3ODAwNzQ4NTQzMDE0OTkxNTg4NTIyNzIxMTkzODg2ODk4MTE5NzUzNjI0In19LCJzaWduYXR1cmUiOnsicF9jcmVkZW50aWFsIjp7Im1fMiI6IjEwMzE4MTcwNjEzMDMzODg1ODkxNDkzMzk2MjU5NDYyNDIxMzM3MjY3ODcyNzkyNzczNjgwMDQwMTgyNTE4NDM1MzEzMDYyNjE3MTcxMCIsImEiOiI3MTM3NDExNDU3NjI3MDE5MDcyNjY0ODMzNTQ5MjAzMDQyMTg0MDQ0OTUxNTU5MzYwMDczMzk1MjM4NjkwMzkwNjgxNzA3ODAyNzY0MTE2MzQ4NjE4NjgxOTgzNTM2MTczNTE3MzgxODc5NzQ0MzQyODIzMDkzNzc4MTk1NzYxNzgzMjQ3NDcxOTQ2NDgwODc0OTY2OTQyNjY0NzU4NjIwNzEyNzExODExNTY5NTc1NzMwNTg4NTQwNDI1MjEzNjY0OTg0OTQyOTIzODU5NzQ3MjIzNjA5ODQ1NjIwMjE5NTY1NzYxODY2OTMwMzI3OTYzNTIwNzE5MTg2NjMyNTQ4NzkzNDI3MTQ0NTY0NTAxNjE1NTg1MTI2NzkxNzM5Njg3ODc2MzIxMTQ1NTAzNDU1OTM0MzUxODc0MjQ3NjA0NzAwODYxMTgxNjY4NjUxNzQ5NTExMjY0MzExMDU2NjI5MjM4NjQwNDY2NzM4ODA0NjI0NzU1MzEzODgxMjQzMjkwNDM5ODI4MDE0NzY0MTQ3NDM1MzE3NTY3MjY3MzQ2NTQxMTY2ODIwNTI2MDkyMDAyMDE0NzY5NjE1MzI3Mjg4MTYwMzM0MDg1MTQ1MzQ0Mzc5MzAxMDg1NDc1MDEyNzUzNDIzNzkxMjI4NzM5NDE3MzA0OTM2NDUzNDEyMDYxNjY0MTUzNTM4MjM5MDA0OTUxMDgyODQxMTE1MTIyNDQ1MzkzNzc5Mzc5NDI5NjQzMjMxNDE2NDQzNDM4NDQ1NzI1NTAzMDc0MTM5MzEwOTc2MjkwMTQ2MTIwMDI2MDI1NTczNzIyNTUyOCIsImUiOiIyNTkzNDQ3MjMwNTUwNjIwNTk5MDcwMjU0OTE0ODA2OTc1NzE5MzgyNzc4ODk1MTUxNTIzMDYyNDk3Mjg1ODMxMDU2NjU4MDA3MTMzMDY3NTkxNDk5ODE2OTA1NTkxOTM5ODcxNDMwMTIzNjc5MTMyMDYyOTkzMjM4OTk2OTY5NDIyMTMyMzU5NTY3NDI5MzAxNTQ3NTU5NjM2MjQ0MTcyOTU5NjM1MjY1ODc1MTkyNjIwNTEiLCJ2IjoiODMxMTU0NzI2ODU4Mzg3ODc5NTU5NDUzNzcwNjg3MzIyMzE1NTgyMjYzMTk3NDUzMjMxNTI1ODIzNDIzNjkxMDk5Mzc2NTExNzI0ODAxMzA1MTU0NzY2Mjc0NjQ1OTMzMTAwOTIzMjIxOTgwNDkyNzE0NDQxMTY0NTYxNTIwMDIzMjYzMDYzMzQ4NjQ4NzkzMjkxMzEwNDc0NTU5NDIwMTkzMTA1MjE5NzMxNTAwMTc0ODg0NzQ0MDk1MjU3MDYyMjczODA2OTYzNjg5MjY3NzA1NTg4NTQ4MzU4NzQ2NDc1Mzc1MzAzMjI1OTI3MDkxMzA0NzQ2NTg5MzA1MDEzNjc1ODk0MzIxMDkzNjE0NzIxMjQwNDAzNDE5OTM5OTk0Mjg5NTU2MzY0MDExMTg2ODQ2NjMxNTA2OTU0NDg0NjM4NjgwMzEyMDA2Njk0MjcwOTkwNDU3NTk3NjAyNzc5MjUzMDc3MTg3NDgwNTg5NDMyNzU4ODgwMjY3NzA1NzMyMjg3Nzc0ODczOTI3MDExMTQ1MDE1NzgyNjE5NzI4NTAxNjI5MTE4ODE1ODM2NjU2OTMzMzcwMzgwNDk4NDk5MDE0MDEzNDI1NDMwMjMwODQzODc0OTk3NTg0NTY3NTA0Mzg3OTE4MjQxMzMxNTM1NDk5MTQxMjU1NzQzNjQ0MzgwNTQ4ODAxNDUwNDEyMzQzMTAxODc4Nzg3NzIzOTIxMDU5NjQyNDg2MjE3NjE0MzQyMDc0MTQ3ODk1ODA2MzQ1NTQ0Njk3NjI2MzcwMDY1MjYxMjQ3OTM2NzMwMzMyNTkyMjM3NDY1MjIyNTQ1MjE4ODc4MDk0ODk0NTE0ODU2ODUyNTU1NjI4MjIwNDg2MTU5MjcyMjIxMjM3MDkwMjc4NjUyNDM2MzcyNTE4MDgzOTUxNjAwNzI1ODA1MDYwNjExMTkyNzEyOTI3MjQ2MTUxMDU4NzU5NDk2MzQ3NjA0NDQwNDcxODcwODEyNzMwODE3MTU4NzQxNDQ1MDA0OTg2MTgyNDk5NDA4OTQxMzk2NjcwMzEyOTU0NDQ1MTk1NTM5MTM5MzIwMDI4MTI3NzMyMDQ0MSJ9LCJyX2NyZWRlbnRpYWwiOm51bGx9LCJzaWduYXR1cmVfY29ycmVjdG5lc3NfcHJvb2YiOnsic2UiOiIyOTQ0ODU2NTEyNDk3MjM2MTc2OTQ3OTMxNzM1Mjk0NDc4MTU5NTE2ODM4OTExNDEyMDE3MTI1NTAyNjc1ODM2Nzk1NTQ4OTczNDMyMTg2NjI0MTc2NjQwNzAxNjczOTk4MjI3NTEzMTA0OTU5OTgzMjk1NjI0MTA0MjkwNjgzMjU0OTI4NTg1MDkwMTI2Mjk5ODczMjY4NDYyODA5NTY4NjMxNDg3MDk2ODgzMDE0OTcwMTcyMzM0MzIwMTM4OTg5ODkzMzcyODIyODU2MTQxMTM5NTQ0MzQyMzExNDEzNDgyMDU0OTYyNjE5NTgzNjU5NjMwMzYxNTc3Nzg0MTQ4NjY3NTgwNjMxODc4NDIwNTgzMDk5OTM1NzE0NDYyMzE5Njg4NDE5MTM4NjY3Nzk2Nzc2MzQwMDk2MDIxMTgwMTU0ODU0Njg1MDEzNzY1MTM0ODMwNjA0MzEyMjI0MjIwNzA5MzQwODExMTI4MDczMDk3NjEyMDA0MTE4NjA0MDI3MTczNjExNTE2ODA0NTQxMzUzODA2OTkxMTg0MDY3MzY1MDE5Nzk2NDM2MDA3NDI0NTM5NzQ4ODQxMjU4NjA1MTUxNTQxNzQzMDk4MTE5NzI1Mzc4MTQ4MTgyNDQ2Njg3NDQ0MjE0ODk1NTE2NDc3MTM4Mjk1OTI1Mzc4Nzg1MTA3OTc5MTYxNTIyNTYyNDk2Njg2MTAzMjg3MDUxNDUyMTE3NTQzMzc4Mzc0MDM5Mjg3NzgxMjAwNzgxMTkyNDQyOTY5MDU1NjIwNTcyODg1NDM5NjU2MTU3Njk2Mzc5MTAyNDc2MTQ2IiwiYyI6IjI4NjYxMjE3ODQ5OTg3ODI4MTA2Nzk5MTAxOTIxNDcyNzgxNDMzNzgzMDE5Mzg0NzAwMDUzMjU4MTU5NDUxNjc5MjMwODI0NzA5NjIifSwicmV2X3JlZyI6bnVsbCwid2l0bmVzcyI6bnVsbH0=", }, "mime-type": "application/json", }, ], - "~please_ack": Object {}, - "~thread": Object { + "~please_ack": {}, + "~thread": { "thid": "e2c2194c-6ac6-4b27-9030-18887c79b5eb", }, }, - "metadata": Object {}, + "metadata": {}, "role": "sender", + "updatedAt": "2022-01-21T22:50:20.522Z", }, }, - "7-4e4f-41d9-94c4-f49351b811f1": Object { + "7-4e4f-41d9-94c4-f49351b811f1": { "id": "7-4e4f-41d9-94c4-f49351b811f1", - "tags": Object { + "tags": { "associatedRecordId": "5f2b7bc7-edfd-47e7-a1d4-aae050df2c4a", "messageId": "578e73da-c3be-43d4-949b-7aadfd5a6eae", "messageName": "offer-credential", @@ -527,51 +534,52 @@ Object { "threadId": "578e73da-c3be-43d4-949b-7aadfd5a6eae", }, "type": "DidCommMessageRecord", - "value": Object { - "_tags": Object {}, + "value": { + "_tags": {}, "associatedRecordId": "5f2b7bc7-edfd-47e7-a1d4-aae050df2c4a", "createdAt": "2022-01-21T22:50:20.522Z", "id": "7-4e4f-41d9-94c4-f49351b811f1", - "message": Object { + "message": { "@id": "578e73da-c3be-43d4-949b-7aadfd5a6eae", "@type": "https://didcomm.org/issue-credential/1.0/offer-credential", - "credential_preview": Object { + "credential_preview": { "@type": "https://didcomm.org/issue-credential/1.0/credential-preview", - "attributes": Array [ - Object { + "attributes": [ + { "mime-type": "text/plain", "name": "name", "value": "Alice", }, - Object { + { "mime-type": "text/plain", "name": "age", "value": "25", }, - Object { + { "mime-type": "text/plain", "name": "dateOfBirth", "value": "2020-01-01", }, ], }, - "offers~attach": Array [ - Object { + "offers~attach": [ + { "@id": "libindy-cred-offer-0", - "data": Object { + "data": { "base64": "eyJzY2hlbWFfaWQiOiJUTDFFYVBGQ1o4U2k1YVVycVNjQkR0OjI6c2NoZW1hLTgwZjdlZWM1LThlNWEtNDNjYS1hZDRkLTMyNzRmYjkzNjFiODoxLjAiLCJjcmVkX2RlZl9pZCI6IlRMMUVhUEZDWjhTaTVhVXJxU2NCRHQ6MzpDTDo2ODE6ZGVmYXVsdCIsImtleV9jb3JyZWN0bmVzc19wcm9vZiI6eyJjIjoiMTEzOTY4MTg4OTM2OTQ5MzcyNzU3NjU2NzI1MjU1MTQ3NDk1OTI5NTM0MjQ5NjU1MzY4NTMzMTY4OTIzMjU4NTA2OTUzOTk3MTI2MDEyIiwieHpfY2FwIjoiMjM5NDkwMjQ4MjE4MTExOTQ1MjIxOTQ1ODcyMTE4MjQzNzA3NjE5OTQ4MzQ0MjU1ODM5ODI4NTU3NjkyNTE3NDExNDMwNDgwNDgwMTkxMTMwMjM0OTg5ODk0NzIyNDE2Nzk1MzUzODAwMDk3NDUxMjI5NDE4MzQ0MjEyOTI3NDk1NjI2NTc4MTk2ODUxMTcwMTI0MDI1NDk1MDExMjc0NjU1NjQzNjkzNTE1ODczMjA5OTczMzgwNjA3MzQxNzQzMzIwNTY0NjkwMzcxOTgyNDIxNTQyMzQzMTMzNTMxOTcxMTk4NTA4NDk5MjYyNzUxNDMyODg0NzgzMzc1MTAzODI0OTE3NzEwODAxOTE3OTc1OTM2OTg4OTIwMzYyMzA5NjE4NTgxMzY0ODE5NTA5ODYxNTE3NjI2ODc3OTUzMzMzMTkzMDExMjA2NDA5NDQ1MzA0MzEwMjUzMTU5OTE1NzYxOTI1MDY1MTg3Mjk1OTg1MzU0NDY1NjY5MTMxNjgwMzc5MzY0ODk0Mzc3NTYxODcyMjcxNDY5Mjk1NzY4MTc4NjQ2NDMxODI3NjI1MTQ3Mzk4MDg1ODI3NTUzMDAzNjIyMDM1ODM1MDg2NzE1NjgyMDA5MzgyNjgxNDc3NDc4ODQ0MDEyNDQ5NTE2NjYwODMwNDMwODQ5ODMxNjAxNDk3MTk3MjczMTIzNjg1NTE0NDMwMjY5OTkxMzMzNDI1Nzk0NjAwMzc3Mzk3NDMwMjg0MjIyMjQ1OTgyMjI1NTE3MjQ4NzA5NTczMzEwNjM5NzQyNzc2NjMyMzM3MDM0Nzk4NDY3MTAwNDczNDUxNTMzMTg1NDg5NDU0NjUyNTgwMjcxNjgyMDQzOTc4MDY4NDc4MjM1MjM5NjMzMTk0MzE4NDcxNDM2MjMwOTg1NTQ1MzAyMDQwNiIsInhyX2NhcCI6W1siZGF0ZW9mYmlydGgiLCIyNjMyMjI0MDEyMDg0NjM3ODA0NjA4MDE5MTM2MzIyNDkzMTE1MzA1MTA2ODQ1MTA0NTE4MDE4MjY2MjU1NjMyNDM0MTMzODY0MTIwNzUxNDkyMDAyMTI4MzU3NTcwMTY4MjE0OTU3OTgzNTQ3Mjk0NTczOTExMzk3MTQwNDAxNDk4MzQ0NzE0NTA5MjEyNDA2NTYzNzgyOTc4ODUzNDgzMTM4NTA1NTA3OTcxNTY3MTgyOTQ1ODQ5ODI3MDU0Nzg4NjA3ODgwOTU5NDE1MTYwMjU0OTE2MDExODkyNTIzNjUzODA1NTk2MDQ3NjA5Mjg3ODA4ODg2MzcwOTM5NDA3Njc5NTE3MjUzOTUxOTUzNDgxNDkzOTMzNDI3NTA5OTUwNTY5NjUwMTc4MjQwNTg0ODI4NzgzOTAxOTA0MjI3MzE1OTEyNzQ0NDYyODc3NDI3Njg3MDEzMDkyNDY1NTc5NzY0OTk2MTc2NTU1NDg1Nzc2Njc0NTcxMjcwNjU4NjAyMTk3OTExNTYzMjY0MzEyNTgzNzMyNTE3NjI3MjY3MzQ1MzEwODcwMjk2NTk4NTEyMDc0NzczNDQyMTExNjY4NjM0MzYzNjkzMDkxNzQ2MjE1MDQwODg1NTcyMzAzMDU4MDUwNzc3Nzg4Mzc5NDY3MzMyNDUwOTY5NTg1NDE2NzU2NzA2MzcxMzMyOTk5MDg1NjM5MDU4Nzk4ODE4MjkwNzEyNDk2NDk0NTY1MzgxMjI0NzUyMTA2OTQyMTg2OTc3MzUwNzE2NjI0MTYxMjIwNjExMDY3MzcxNTM0NjYyNTc0MzY1NTM0MzEzNjAwMTQyODk2MDkwMDQ3MTI5NjQ3OTEzOTgzMjk0Mjc2MDI2OTA2MTA3NzM4MDM2MjI2MTA4NzU0MTE3OTIwMTg2NDM1MDkwOTU2Il0sWyJuYW1lIiwiMzE2Nzc0MzUwOTgzMjI4Nzg1MDcxNDg1NjYzOTM0MTYyNjQ1MzE2NjQzMTc5Njg2NTg2MTU2MjgwMjg3Njg5Nzg0NTI1Mjg5MzY0NDg5NDMxNjEwMTc1MzUwMDgzMzUzMTg3MTIwMTE1MTU1NjUxOTYzMjcyODM4MjcyMTgyNTI2MzUyNjMyMzI5MDY2NDIxMjM3OTM3NTc4MjY4MjkwNzU4MDgwNjI0MjE3MDM0NDU5MDUyMjY2Njk5NjQzMTg2MjkyNjcxNDk1ODEwMDU4Mjg5MzA1MjI4OTQ2MTQ2MTkzMDAzNDk0NDgwNzY0NDY5Nzc1NzM5Njg0NzcxNjMyNDIzNTM5MjE1MzIxMjc0NTQ4NDU5NzE4NTM3NTMzMzE0MjYwMjcwNzE0MTkzNjc4MTEzMDg5MDUxODI1MjA2MTAyMjQ0MTc3NzAwNDk3MTYxMDM2NjgwNDYzMDUxNDcxNDk3MTgzNzc3Nzc5Nzc2Nzc0MTUzNzQ1NjEwNzc3NzgyMDYyODA3NjY5MjE2ODA2NDgxNzAzNTU5NDk5MTAyNTc5ODAwNjgxNTQxMjg3MTk0MTAwNzg4MDMxNDE3Nzg5MzQyNjk2NTQyNTA3NjE5MTgzNDIyMTc0OTk5NzQ2ODM3NjY4NTg0Mzk0NzQxNzI5MDY2MjgwNjYyMzAyMDI3NTczMDgwMDY5NTE5MDgxOTA5OTA3MzQzODAxNzg3MjgyMTEyODY2NzkwNTI2MDIwMjk4NjM2ODY3Njg2NTE5MjQ0NTg2MTg1NzMxMTE0ODk1ODU3NjkzNTQxMTIwOTc2NTQ5MzYwNDE1MTkxMjI4ODA4MzE5NTcxOTU4NTkxNDc4NDYwNzMwMTg2NDQ4MTU3NjU3OTkyOTI4NTM2MjgxODU2NjAzNjU5NjM3OTE2NzE0OTk0NTU0NSJdLFsiYWdlIiwiMTY5MjgwMDQ2OTQyNDI3NDY1ODE5OTE3MzEyNTkzMzEyMzkyNDYzMDQxODc5MzIwMjM0NDU4NjQ4Mzg2MzI4MTE5MDQ5OTIyMzc1NjkxNzI0ODYzMDM3NDMyODU4OTQ4MTIyNzI2ODQ5NDU5NDcxODA2NTU4NzYyMzU4MTgzMzU3OTYwNDk3NTMyNjUzNzE4ODE0NzQ1NzY2ODc3OTI2NzU2NTQ3NjQwMjUzMTczMDYzMjY0Mzc0OTAzNTgwNjEwNDMxMTM2NTA2NjQ1NjE5NzYzNTE1Nzc3MTkyNjU4ODk0OTc1MDMyODAzNzM0MTE5MjM5NjcxMjgwOTQyOTkxMTg2MjYyNjYzNTM5NDU3ODc1NjY1NDcwMTAxMTEzOTUyOTY2MDQyMzU4NDQ4ODE1NTk5MDgzNTU4NTIyMDQ1OTI3NDI0NjI5Njk4MTgzMTUzNzUyMDA4MzM5NTI1NTYxMDI0ODg2MzUzOTc3NzA1ODE5Mjc1MzQzOTg3MzMzODMxNjU0NzA4ODI3NDI0NzMzNzcyNjI3MTA2OTgxNjE5NDY0MzUwNDU3NzE4NzM2MDA0NjEyNzQ0OTAyNDA5NjA0Njk4NzkzNzI0MTc1MDA4OTUzMDMyMDgxMTQ2OTE3MTM4ODc4NzQ4MDM0Mzg1NDQxNTIxMTU5ODM2NDIwMDEzNTQ1NTQyMDk2NDIwODA1MDYxMzI5MTkwNzczNzIzMDYxMjE2NDIzNjczNTM1MzU1OTc5MzY1Njg0MzM2NjEyOTU2NjkxNDA4NDQ5MjE4MjcwOTYyODUyMzQwMTQ2MDAwMDYzNzA3NzU5MzUxODM0MTQ2MDI4MTYyMTEyMzU4MzAzNDQ1OTcwMTg3MTk3OTQ5MDcwNzE4NzQ4OTI4NjM5MDkyMzY1MjExMjgyODY0NDE3OTcwOTg5OCJdLFsibWFzdGVyX3NlY3JldCIsIjk4NjY2MDAzNjA2Njc3MjkxODM1MjEwMzA1NDczNTA3NjU0NTM1OTgxNDYxODkyNjY5NjI5OTE1MzQ5NjU3NjY5MTI5NzAxNTUwNzUyNTU2NjMxMzU4MzU3NjEzNjg3OTgyNTQzNTcyNTc5ODEzOTgxMzI4MDM4NzY5OTcxMzAwNTE0NDI2NzAxNTM2ODE1ODI3MDgzNDY5MzEzOTQ0ODAzMzIzMDUzMzUyNDkxMTIwMDUwNzkyNzA4NTQzMTE2NTM1NjA2NTQyNDY3OTcxNTUxODA4MzQyOTk1OTM4NzQ2NDQ4NjMyNTY4NjU0NjA4MzI1NDk2NjM3Mzc5OTQ0NjA5MDU3Mjc2OTE0OTQxNzE4MzU2MTYzODYyMzI2MDc3MDUzNjIyMDI3OTE2MzIzNzAzMDE2MTc4NDQ3MDEwMTc1MjI1MTM0NjE3NTcxMTgzMjcyNzMwNjQxNzI3Mzk2MzM4ODk2NTUyNzM4NzUwMDA4MTQ3MDExNjkyNzIwNzI4NDY2MjcwNDQ2MDg4NjEyMDg2MDExMzg2NzQxOTMzODM4ODQ1NjkzMTM1NzcyODk0MDIwNTM4NTU3ODI1MjA3OTkxNDAwODIyNjg4OTgwNTg4MjgzMTY0MzAxNTY1NjAyNDAzNTI4MDE2MTczNTk5MTM4NzI5ODEyOTE2MTYxNDEwNjgyODU4MzU4MDE3ODI1ODUyMzY2OTgzMDQzMzM4ODY2MzI3MzM3MTE0NzcyMzM5NTUyNTYzNzU0NzQ0NTA5MTYzNzYzNjAyNTI0NjgxNTUyNjIyOTYwNjM4MzE2OTc0NjI4MjQyNjQ5NjYyMjQyMTkzODMxOTUyMDE0MTAxNTA3Njk2MjIxMDU5NDE5MTcyNzMwNjE2NDE2NzEwNTMzMzc2NjI4MjQ4NzkxMTUwNjMxMDMxOCJdXX0sIm5vbmNlIjoiMTE4MTE3NTM4MDU1MjM2NjMxNjAwNjM1NyJ9", }, "mime-type": "application/json", }, ], }, - "metadata": Object {}, + "metadata": {}, "role": "receiver", + "updatedAt": "2022-01-21T22:50:20.522Z", }, }, - "8-4e4f-41d9-94c4-f49351b811f1": Object { + "8-4e4f-41d9-94c4-f49351b811f1": { "id": "8-4e4f-41d9-94c4-f49351b811f1", - "tags": Object { + "tags": { "associatedRecordId": "5f2b7bc7-edfd-47e7-a1d4-aae050df2c4a", "messageId": "1284ae78-f3d3-4fed-a5ff-0e2aba968c3c", "messageName": "request-credential", @@ -583,34 +591,35 @@ Object { "threadId": "578e73da-c3be-43d4-949b-7aadfd5a6eae", }, "type": "DidCommMessageRecord", - "value": Object { - "_tags": Object {}, + "value": { + "_tags": {}, "associatedRecordId": "5f2b7bc7-edfd-47e7-a1d4-aae050df2c4a", "createdAt": "2022-01-21T22:50:20.522Z", "id": "8-4e4f-41d9-94c4-f49351b811f1", - "message": Object { + "message": { "@id": "1284ae78-f3d3-4fed-a5ff-0e2aba968c3c", "@type": "https://didcomm.org/issue-credential/1.0/request-credential", - "requests~attach": Array [ - Object { + "requests~attach": [ + { "@id": "libindy-cred-request-0", - "data": Object { + "data": { "base64": "eyJwcm92ZXJfZGlkIjoiUVZveGd3d25WUGtBQlRMVmNtQ013TCIsImNyZWRfZGVmX2lkIjoiVEwxRWFQRkNaOFNpNWFVcnFTY0JEdDozOkNMOjY4MTpkZWZhdWx0IiwiYmxpbmRlZF9tcyI6eyJ1IjoiMTkwNzM1MzQyMjkwNjk4Mzk3NzgyNTUyMTM2NTA3NzAxMDA4NzYwMzcxMjg3NTQ1MzI0NDM2NDIyMTMwNzQ3MDI3NTk4NDM2NTM5Mzc0NzM0MjgxOTk4MDY5Mjg1OTg4MzAzMDE1MzAzMTYxNjExMTEzMTY2MzQxMzkyOTkzMTk0ODUxNzM5Njk4NzcxNDYzNzMzMDA4MjUxMzQ5NjM4OTkzMDE5NTk5MTY1NDUyOTk4OTA4OTY4MDE5NTUxODM2NDg1NzYxMzMxNTgxNTY0MzgxNTkxNjMwOTcyNTc5NTkyODMyNDk3MDI0NTMyMjQxNzk4MDMzMjI1NDg4NjA5NjEwNjEzNTU1NDMxODc3NDQzODk2ODUyMzA4NjIxNDA1NjI5NjA5MTg5Nzg2MjYzMTcyODU0MjA1MTI3ODMxNjc5NzM5NTkxODQyMTYwOTAyNjczMDE4Mzc0MzE5NjUyODA3Njc2MDQzNzc0ODcxMjQzMzkzNTIwNTkwODE5MDgxOTI4NzY3MjU5NDQ2OTIxNTM2MjU3MjQ2NjIxMjgzNjc2NDM1MDIwNzUwODI4NDI2NTM2MTU2ODA5NDgwMTU3MTQ0NDkxNTY2MTM0ODYzNjU4Mjg5ODIyMTE1NjI4MzMxNjMxMTQ3ODM1NzQ4MjAxMzkyNjY4NzQyMTQ5NTI1OTQ0OTc1NzY3NTYwMTQyNzQ5MTU3MTY2NzE0MDY0OTM2OTQ1MzEwMzEwMzU1NjgwNTcyNDgzNDgyNTYyMzk5Nzc0OTY5NTYwMTA1Njk2MzczMDU4MDMzODgyMTAwOTY2ODUwMTk5MjEzMzAiLCJ1ciI6bnVsbCwiaGlkZGVuX2F0dHJpYnV0ZXMiOlsibWFzdGVyX3NlY3JldCJdLCJjb21taXR0ZWRfYXR0cmlidXRlcyI6e319LCJibGluZGVkX21zX2NvcnJlY3RuZXNzX3Byb29mIjp7ImMiOiI1MDg3Mzk4NDExNzQ3Mzc5NjY5MDkyNzU5ODQ5OTEwMDI2OTYzMTk2NjExNjc5MzU5NDYwNDMxMjYyMDE4NzgyNzY4NTM2NTUzNzUwMiIsInZfZGFzaF9jYXAiOiIxODU0NzEwMDA5NDM4NTg5MTc4MzkzMjgzMDk1MzM5NDUzMDQ3OTkwOTYyMjE2NzEyNzk2ODkyMzcyMzA5NjU5NTU3MDY2MzQxMTMxOTY0Mjg5NjA3MTI5MDg4MjMxMDY0NTk5ODY4NDg4MTIzNDMwMzY5OTkxMjI1OTMxMTIyMjY4NjU5MDEwMDA0NDA1OTIzNTcyMzgyMzQzODczNjkxODg3NDQzMjQ5MTcwNTQwNDk4Nzk5MTkxOTIzMjc4NDU4MzcyMzk2NzIyMjM5NDE1NjY3ODIxMDQ4ODA0NDk1NDQ5ODQ3MjcwOTg1MDcwNzY3NjU2NDU4NDM0MTYzNTI3NDAyMzA1NTg5MTg4NzcyNDg4NzE1NjcwOTgxNTc0NzQxMTI1NzYxOTIxNTE3NzYyNzg1Nzk4MjU5MTQ0OTYzMDQyMjg0NzUwMDE5MjAwMjQ4NjgxNjkxOTE5Njg3MDA1MDA4MDUzMjYwODY5NzEyNTEwNzIwNDg5NDAwMjM0NDU3Njk2MDk4NjI0Nzk1MDUwMzQ2NzM2Mjg1MDE3MTU5Mjk1OTA0NTU1NTk0MzMxNzI4MTQ5MzgyODE5NTI2NTc3MDg3NjA0ODMwNDk3MTE0Mjc3MTkyMDU3ODk1MzYxNzI5NTE3NTgxNzg5ODEyMDY3MjcxNTU5MTMyNTI1MzEzNTc0MDEwNTM3NDMxMDY2NzUwNzAzMDgxMTQxNDIyODg5MzUxOTY0MDUyMDU0NjY0MTA0ODE0MzY1Njg3NTcxNjU5MTk3ODQxMjU5NjE3OTI4MDg4NjM0ODY1NTI2MjI4NTkyNTI2NTgwODgzMzIzNjEwNTc5NzU4ODgzMjgwNDcyNTA0OTQ0MjM2ODY5MTYyNzM3NzUwNTI0NTIyMjE5NTM4NjE4OTk2ODQzODU3MzU3MjUxODI5NTIyODgxOTA0NTAwMDU2MjU1NTMwNDgyIiwibV9jYXBzIjp7Im1hc3Rlcl9zZWNyZXQiOiIxMjM5OTQ3ODE4MDA1MTQ3MjQ5MjcyODIxNDI2OTgwNjQ2NTIwMjAwMTc0MzUwMzkzMzQ0NzU1NTU0NDAxNjg1NzQ2NzExMzkzMjEzMjA3NjI3ODI5MTczMjc2OTA2MDg4MDAxMDIxMTMxMzY4NzY4MjI0ODgxNTExMjEwNjY0NzA5OTAxOTQwODA0MzA0OTc1NTUyMzExNzAyMjU3MTYwOTAxNTE0MzIxNzYyNzM0ODUwMSJ9LCJyX2NhcHMiOnt9fSwibm9uY2UiOiIzNzM5ODQyNzAxNTA3ODY4NjQ0MzMxNjMifQ==", }, "mime-type": "application/json", }, ], - "~thread": Object { + "~thread": { "thid": "578e73da-c3be-43d4-949b-7aadfd5a6eae", }, }, - "metadata": Object {}, + "metadata": {}, "role": "sender", + "updatedAt": "2022-01-21T22:50:20.522Z", }, }, - "9-4e4f-41d9-94c4-f49351b811f1": Object { + "9-4e4f-41d9-94c4-f49351b811f1": { "id": "9-4e4f-41d9-94c4-f49351b811f1", - "tags": Object { + "tags": { "associatedRecordId": "5f2b7bc7-edfd-47e7-a1d4-aae050df2c4a", "messageId": "d14cf505-4903-4dd9-95c2-a7dbc1c048b6", "messageName": "issue-credential", @@ -622,79 +631,79 @@ Object { "threadId": "578e73da-c3be-43d4-949b-7aadfd5a6eae", }, "type": "DidCommMessageRecord", - "value": Object { - "_tags": Object {}, + "value": { + "_tags": {}, "associatedRecordId": "5f2b7bc7-edfd-47e7-a1d4-aae050df2c4a", "createdAt": "2022-01-21T22:50:20.522Z", "id": "9-4e4f-41d9-94c4-f49351b811f1", - "message": Object { + "message": { "@id": "d14cf505-4903-4dd9-95c2-a7dbc1c048b6", "@type": "https://didcomm.org/issue-credential/1.0/issue-credential", - "credentials~attach": Array [ - Object { + "credentials~attach": [ + { "@id": "libindy-cred-0", - "data": Object { + "data": { "base64": "eyJzY2hlbWFfaWQiOiJUTDFFYVBGQ1o4U2k1YVVycVNjQkR0OjI6c2NoZW1hLTgwZjdlZWM1LThlNWEtNDNjYS1hZDRkLTMyNzRmYjkzNjFiODoxLjAiLCJjcmVkX2RlZl9pZCI6IlRMMUVhUEZDWjhTaTVhVXJxU2NCRHQ6MzpDTDo2ODE6ZGVmYXVsdCIsInJldl9yZWdfaWQiOm51bGwsInZhbHVlcyI6eyJuYW1lIjp7InJhdyI6IkFsaWNlIiwiZW5jb2RlZCI6IjI3MDM0NjQwMDI0MTE3MzMxMDMzMDYzMTI4MDQ0MDA0MzE4MjE4NDg2ODE2OTMxNTIwODg2NDA1NTM1NjU5OTM0NDE3NDM4NzgxNTA3In0sImRhdGVPZkJpcnRoIjp7InJhdyI6IjIwMjAtMDEtMDEiLCJlbmNvZGVkIjoiNDEwNjEyOTM3ODA0NjIwNzU1MTQyMDgyMjM4OTc1OTA0MDc4MDA3NDg1NDMwMTQ5OTE1ODg1MjI3MjExOTM4ODY4OTgxMTk3NTM2MjQifSwiYWdlIjp7InJhdyI6IjI1IiwiZW5jb2RlZCI6IjI1In19LCJzaWduYXR1cmUiOnsicF9jcmVkZW50aWFsIjp7Im1fMiI6Ijg5NjQzNDI5NjIzMzI5NjQ1MDM2NDU5MjU4NzU2Nzc5MDY3ODczOTc4MDg1ODc3MDM3MzU1NzMxMjk1MzA0NzY5ODAxNDM5MjI2MDI4IiwiYSI6IjUzOTQ3MDU1MTU0OTEyMTE2MzM0MzU2NTMyMjEyMTM5MjMxNjE2NTcwMzU3MjkwNzcyOTkxNjM4NTU1MjU2Mjc2NjgyODY5NDM2MTI5OTEzNzk2MzQwOTA1Nzk3Mzc2OTI5NDE0MTIwNzMwMjI5NjQzNjMwNTI2OTcyOTUxNDk1NjA5NDcwMDU0NzEzOTY1OTE5NTU3MzM3NDkwMjUyNTI1NTM5NzI4MjA0NTI2NTU0MDcyMDYyOTcxMTA0MTM3NTQ4NzEzMzIzNTUzMTYwOTEzODQ0MDk0MDczOTQ3MjM2OTQyNzgyODIzNDA2NDUyNzIzODY2NjMzMzc2MjI4Mzk2ODE5ODI0MTQ2MjgyNTI4NDIyOTIzMjM1NzEzNTI5MjQ3ODkxMTQzMDE4OTM3ODQ0ODM3NzQ2MDE4MTc5Mjk5ODI5ODQ1MTI4MTgxMDUxOTE4MjE0ODU5Mzg5MjIzOTEzNjUzMjE0MjIxMTk2NDI2OTA2NDM1NDYwNDQwNTgxNDkxNTg5MzMxMzIyMDU1MDU1NjE5NDY0OTEwNTc3OTcyODAyNDM1MDY3OTMxMDczOTI5OTgyOTQ2Njg4NDg5MTIwNTQ1MjA1MzQ0MjQ1NTIzNTExNDc5NjUzNjI5ODIxNTA4NTc3MjI2MzU5MjUyMDA5MjUyNTU2NDA5NTg0MDgwNDY5NDI4NzE1NDQ0MDkyOTA4NzAxNjMwMzE3NzI5MTE3NTYwMzg2NjAyNDA4OTE3Mzg2NjM4NzQ2MDY1NjU0NzQ2OTAxOTA1NjA4MjE5MzgzNzczNjg3NDcxODI3NjE2OTU5MDk3NDU4IiwiZSI6IjI1OTM0NDcyMzA1NTA2MjA1OTkwNzAyNTQ5MTQ4MDY5NzU3MTkzODI3Nzg4OTUxNTE1MjMwNjI0OTcyODU4MzEwNTY2NTgwMDcxMzMwNjc1OTE0OTk4MTY5MDU1OTE5Mzk4NzE0MzAxMjM2NzkxMzIwNjI5OTMyMzg5OTY5Njk0MjIxMzIzNTk1Njc0MjkzMDMwMzU1ODY2NDUxMDY0MDQ4Mzk4OTcyMjU0Nzk2ODY2NDA2OSIsInYiOiI4NzE4MTIwMTY2NjA3NTg4MjIxMDczMTE0NTgxNTcxMDA5NTEwNDUwMzkyNDk0MDM1MTg3MDk5MTQyNzA5NDcyMjYxMDAyMjg5MzI5MzcyMzk1MDUwMzgzOTA3ODUxODc1MjIxODU0NjI4ODc4OTcxNzQ0Njg3MDgxMDM4Mzk2Njc2MzgyMTI0NjEyNDY0NjQxMDQ4NDMxNDkwMTYzMDgwOTU0NTc3MjA3OTkxNjg3NzU0NjQ4MTYwNzM5MjQ2ODUyMjMxMjU2NTk0MTg4MTAxNDU2MjgzNjc3MTA4NTMwNjY1NDQwNTUwMDY0MjgxODI3NzUxNjA3NjM1ODE2MjczNDU3MTc4MzAyNjEyOTI5ODMyNDMzNDc3ODk0ODMzMDc2MDA5OTE4MTc1MzI4OTUzNjg1MjEwOTQ1ODg0MTQyMDg0Nzk4ODMzNzM2OTExNDcwMTkwOTYwMjI3MzAyMzI2NjQ2NjE2NjUwMjY4OTU3NDcyMTI3MTA2Mjk3NzQ0NDg3MDUzODY2NjI0NTk4Njg1MzA0MzA0OTMzNjAxNDczNjY4Njg1NDg5MzYyMzQ2NzE5ODA4MjgxNzYxNjc0ODQyMzE5NTY5Mzk1Nzk1NTQ5OTA4MjAyODI0MjgyOTc1MTI3MDA0MzM0NTkwNTYzMTI3NjU3NDM3MjQ2MDQ3OTUyOTk0OTIyODQ3MzcxMzY4NDM0OTE3MDM1Njc4ODA2NjM1OTQ0ODY4Njc5MDY1NDc5Njk1MDU5NzkyNzUyMzk5NDcwMzUzMDI3MjEyNTg2OTc1Mjk5MTk1NDcwNjY0NDMzMDIyNTQyODg4MzI4OTA0Mjg2NTIxMzM5Nzc2OTkxMDYzNzA1MTI2NjA4MDY4OTA0Mzg2MDc4NzA5NTE3NjU0OTE3MzI0NjExMzkzNTM4MDkyNTQ3NzQ0OTM2NTM1NDkwODcwNDU4NjQ3NjY2OTU3MjA5MDk4MDU2NzIwMjAzOTAxMjI3MjU2NDM5NTkwNTA0MzIwOTI3NTc1ODA2NjE0NzA4NTU3MjAxMTAxODczODc4NDg1NjM4MzQ2Nzk1NjE4NDQxOTQxMjQyODc5ODMyNjQ5ODEyIn0sInJfY3JlZGVudGlhbCI6bnVsbH0sInNpZ25hdHVyZV9jb3JyZWN0bmVzc19wcm9vZiI6eyJzZSI6IjIxOTkzMzcwNTI0MjIxNTM0MTM0MTc4MDM3MDIyMDEyMzE4NjQ2MDE4MTk0MDIwMjgxNzY4NTQ4OTUyNjM5ODg4MDE2NDQ1NTM2NTQ2MzczMzkwMDU5NjY1Nzg4OTc2MDE0OTUzMTU2MjA1MTA0ODU1NTM1NjEyODY5Mjg5NjgyOTQ1ODI4MDQxOTMxMzc1ODY4NjE4OTE0NjUwNTc5ODM2NzI0NDE0MDMxMjU3MjU2MzkxNjg2OTQ0NjQyMzg3NTIwNjExNDQ5ODM1NTgxNDMzMDMzOTQ4MTA4OTE0MzI2NzkyNDU5MjQ0Mzc0MTgyOTQ4MDQxODIzMTg3NjY3MjE2MDI5OTEwMTAzMTM1MjE4NjY5ODc5MDk5ODA0NTA0NjI1NTAzNDM2NTAxOTk5ODkwODIyNjcyMjYwNzc1NDIzMzIxNTQ0MDk0Mjk2NDI0Nzc2Njg2MDI1MzU2MjMwOTY0OTQyMzc3NTY0NDUwMDk4NTgyMzg2Nzg0ODc3OTQwNTI2ODg0MzgzOTcxOTE4OTE3ODIyOTkzMDIzMDU2NjU1NTg2NDI3MDAwMzQ2OTcwMTI1MjA2ODg0NTkyNzkwMDU4NTAyMzkxMzUwODIyNjg1NDYyMzY0MzE5NTMwOTI0MjM4Mzg2MjkwMDI5MTQ1ODAzNTgxODA2MDQ1MTYzNDMzNTQ2OTAwMzQzNDg0MTg2NTEyMjIzODYwODY3NTI3ODExOTkyOTQwMjMxMzgzOTM4MjI0NjEwMTk0NDc1NjczMDYyMDI3MDgwOTI5NDYzMjU0NjIxMjI1NTg3MDg0NTUyODEyMDgxMDE3IiwiYyI6Ijg2NzE3OTAzNTAxODI5MzU5NDk4MjE3NDU5NjE3NjgyNTM4NzIxNzQwMjE5MDM5MDUzNjQzNTE3NDU0Nzc2NTUzMzA3MzU1OTkxMjE5In0sInJldl9yZWciOm51bGwsIndpdG5lc3MiOm51bGx9", }, "mime-type": "application/json", }, ], - "~please_ack": Object {}, - "~thread": Object { + "~please_ack": {}, + "~thread": { "thid": "578e73da-c3be-43d4-949b-7aadfd5a6eae", }, }, - "metadata": Object {}, + "metadata": {}, "role": "receiver", + "updatedAt": "2022-01-21T22:50:20.522Z", }, }, - "STORAGE_VERSION_RECORD_ID": Object { + "STORAGE_VERSION_RECORD_ID": { "id": "STORAGE_VERSION_RECORD_ID", - "tags": Object {}, + "tags": {}, "type": "StorageVersionRecord", - "value": Object { + "value": { "createdAt": "2022-01-21T22:50:20.522Z", "id": "STORAGE_VERSION_RECORD_ID", - "metadata": Object {}, + "metadata": {}, "storageVersion": "0.2", + "updatedAt": "2022-01-21T22:50:20.522Z", }, }, - "ad644d8a-48a2-4c55-b46d-7a7f1a9278c7": Object { + "ad644d8a-48a2-4c55-b46d-7a7f1a9278c7": { "id": "ad644d8a-48a2-4c55-b46d-7a7f1a9278c7", - "tags": Object { + "tags": { "connectionId": "cd66cbf1-5721-449e-8724-f4d8dcef1bc4", - "credentialIds": Array [], - "indyCredentialRevocationId": undefined, - "indyRevocationRegistryId": undefined, + "credentialIds": [], "state": "done", "threadId": "e2c2194c-6ac6-4b27-9030-18887c79b5eb", }, "type": "CredentialRecord", - "value": Object { + "value": { "autoAcceptCredential": "contentApproved", "connectionId": "cd66cbf1-5721-449e-8724-f4d8dcef1bc4", "createdAt": "2022-03-21T22:50:20.740Z", - "credentialAttributes": Array [ - Object { + "credentialAttributes": [ + { "mime-type": "text/plain", "name": "name", "value": "Alice", }, - Object { + { "mime-type": "text/plain", "name": "age", "value": "25", }, - Object { + { "mime-type": "text/plain", "name": "dateOfBirth", "value": "2020-01-01", }, ], - "credentials": Array [], + "credentials": [], "id": "ad644d8a-48a2-4c55-b46d-7a7f1a9278c7", - "metadata": Object { - "_internal/indyCredential": Object { + "metadata": { + "_internal/indyCredential": { "credentialDefinitionId": "TL1EaPFCZ8Si5aUrqScBDt:3:CL:681:default", "schemaId": "TL1EaPFCZ8Si5aUrqScBDt:2:schema-80f7eec5-8e5a-43ca-ad4d-3274fb9361b8:1.0", }, @@ -702,57 +711,56 @@ Object { "protocolVersion": "v1", "state": "done", "threadId": "e2c2194c-6ac6-4b27-9030-18887c79b5eb", + "updatedAt": "2022-01-21T22:50:20.522Z", }, }, - "c7e0a752-7f1c-41c0-b0ae-a68c2d97ca8c": Object { + "c7e0a752-7f1c-41c0-b0ae-a68c2d97ca8c": { "id": "c7e0a752-7f1c-41c0-b0ae-a68c2d97ca8c", - "tags": Object { + "tags": { "connectionId": "d8f23338-9e99-469a-bd57-1c9a26c0080f", "credentialId": "19c1f29f-d2df-486c-b8c6-950c403fa7d9", - "credentialIds": Array [ + "credentialIds": [ "19c1f29f-d2df-486c-b8c6-950c403fa7d9", ], - "indyCredentialRevocationId": undefined, - "indyRevocationRegistryId": undefined, "state": "done", "threadId": "e2c2194c-6ac6-4b27-9030-18887c79b5eb", }, "type": "CredentialRecord", - "value": Object { + "value": { "autoAcceptCredential": "contentApproved", "connectionId": "d8f23338-9e99-469a-bd57-1c9a26c0080f", "createdAt": "2022-03-21T22:50:20.746Z", - "credentialAttributes": Array [ - Object { + "credentialAttributes": [ + { "mime-type": "text/plain", "name": "name", "value": "Alice", }, - Object { + { "mime-type": "text/plain", "name": "age", "value": "25", }, - Object { + { "mime-type": "text/plain", "name": "dateOfBirth", "value": "2020-01-01", }, ], - "credentials": Array [ - Object { + "credentials": [ + { "credentialRecordId": "19c1f29f-d2df-486c-b8c6-950c403fa7d9", "credentialRecordType": "indy", }, ], "id": "c7e0a752-7f1c-41c0-b0ae-a68c2d97ca8c", - "metadata": Object { - "_internal/indyCredential": Object { + "metadata": { + "_internal/indyCredential": { "credentialDefinitionId": "TL1EaPFCZ8Si5aUrqScBDt:3:CL:681:default", "schemaId": "TL1EaPFCZ8Si5aUrqScBDt:2:schema-80f7eec5-8e5a-43ca-ad4d-3274fb9361b8:1.0", }, - "_internal/indyRequest": Object { - "master_secret_blinding_data": Object { + "_internal/indyRequest": { + "master_secret_blinding_data": { "v_prime": "24405223168730122709164916892481085040205443709643249329100687534344659826655374235392514476392517756663433844139774514430993889493707631169979521764390851593418941181409704266182779162417466204970949168472702858363964258641437554267668466400711344128132909691514606077477555576087059339291048485225394874964325220472232903203038212033940680060605090839733163438385288769519855418153181511119637865605476043416048121313638627002888436809192752657860306784733123742838413845299796745569824223645588826964796075250758249133953560017373025169692866449286962430731916293683231375510684692358406054381559324718715654332979447698704161714028193478", "vr_prime": null, }, @@ -763,27 +771,29 @@ Object { "protocolVersion": "v1", "state": "done", "threadId": "e2c2194c-6ac6-4b27-9030-18887c79b5eb", + "updatedAt": "2022-01-21T22:50:20.522Z", }, }, } `; exports[`UpdateAssistant | v0.1 - v0.2 should correctly update the connection record and create the did and oob records 1`] = ` -Object { - "1-4e4f-41d9-94c4-f49351b811f1": Object { +{ + "1-4e4f-41d9-94c4-f49351b811f1": { "id": "1-4e4f-41d9-94c4-f49351b811f1", - "tags": Object { + "tags": { "invitationId": "d56fd7af-852e-458e-b750-7a4f4e53d6e6", - "recipientKeyFingerprints": Array [ + "recipientKeyFingerprints": [ "z6MkfiPMPxCQeSDZGMkCvm1Y2rBoPsmw4ZHMv71jXtcWRRiM", ], "role": "receiver", "state": "done", + "threadId": "d56fd7af-852e-458e-b750-7a4f4e53d6e6", }, "type": "OutOfBandRecord", - "value": Object { - "_tags": Object { - "recipientKeyFingerprints": Array [ + "value": { + "_tags": { + "recipientKeyFingerprints": [ "z6MkfiPMPxCQeSDZGMkCvm1Y2rBoPsmw4ZHMv71jXtcWRRiM", ], }, @@ -792,30 +802,30 @@ Object { "createdAt": "2022-04-30T13:02:21.577Z", "id": "1-4e4f-41d9-94c4-f49351b811f1", "mediatorId": undefined, - "metadata": Object {}, - "outOfBandInvitation": Object { + "metadata": {}, + "outOfBandInvitation": { "@id": "d56fd7af-852e-458e-b750-7a4f4e53d6e6", "@type": "https://didcomm.org/out-of-band/1.1/invitation", - "accept": Array [ + "accept": [ "didcomm/aip1", "didcomm/aip2;env=rfc19", ], "goal": undefined, "goal_code": undefined, - "handshake_protocols": Array [ + "handshake_protocols": [ "https://didcomm.org/connections/1.0", ], "imageUrl": undefined, "label": "Agent: PopulateWallet2", "requests~attach": undefined, - "services": Array [ - Object { + "services": [ + { "accept": undefined, "id": "#inline", - "recipientKeys": Array [ + "recipientKeys": [ "did:key:z6MkfiPMPxCQeSDZGMkCvm1Y2rBoPsmw4ZHMv71jXtcWRRiM", ], - "routingKeys": Array [], + "routingKeys": [], "serviceEndpoint": "rxjs:faber", "type": "did-communication", }, @@ -832,23 +842,25 @@ Object { "reuseConnectionId": undefined, "role": "receiver", "state": "done", + "updatedAt": "2022-01-21T22:50:20.522Z", "v2OutOfBandInvitation": undefined, }, }, - "2-4e4f-41d9-94c4-f49351b811f1": Object { + "2-4e4f-41d9-94c4-f49351b811f1": { "id": "2-4e4f-41d9-94c4-f49351b811f1", - "tags": Object { + "tags": { "invitationId": "d939d371-3155-4d9c-87d1-46447f624f44", - "recipientKeyFingerprints": Array [ + "recipientKeyFingerprints": [ "z6MktCZAQNGvWb4WHAjwBqPtXhZdDYorbSJkGW9vj1uhw1HD", ], "role": "sender", "state": "done", + "threadId": "d939d371-3155-4d9c-87d1-46447f624f44", }, "type": "OutOfBandRecord", - "value": Object { - "_tags": Object { - "recipientKeyFingerprints": Array [ + "value": { + "_tags": { + "recipientKeyFingerprints": [ "z6MktCZAQNGvWb4WHAjwBqPtXhZdDYorbSJkGW9vj1uhw1HD", ], }, @@ -857,30 +869,30 @@ Object { "createdAt": "2022-04-30T13:02:21.608Z", "id": "2-4e4f-41d9-94c4-f49351b811f1", "mediatorId": undefined, - "metadata": Object {}, - "outOfBandInvitation": Object { + "metadata": {}, + "outOfBandInvitation": { "@id": "d939d371-3155-4d9c-87d1-46447f624f44", "@type": "https://didcomm.org/out-of-band/1.1/invitation", - "accept": Array [ + "accept": [ "didcomm/aip1", "didcomm/aip2;env=rfc19", ], "goal": undefined, "goal_code": undefined, - "handshake_protocols": Array [ + "handshake_protocols": [ "https://didcomm.org/connections/1.0", ], "imageUrl": undefined, "label": "Agent: PopulateWallet", "requests~attach": undefined, - "services": Array [ - Object { + "services": [ + { "accept": undefined, "id": "#inline", - "recipientKeys": Array [ + "recipientKeys": [ "did:key:z6MktCZAQNGvWb4WHAjwBqPtXhZdDYorbSJkGW9vj1uhw1HD", ], - "routingKeys": Array [], + "routingKeys": [], "serviceEndpoint": "rxjs:alice", "type": "did-communication", }, @@ -897,23 +909,25 @@ Object { "reuseConnectionId": undefined, "role": "sender", "state": "done", + "updatedAt": "2022-01-21T22:50:20.522Z", "v2OutOfBandInvitation": undefined, }, }, - "3-4e4f-41d9-94c4-f49351b811f1": Object { + "3-4e4f-41d9-94c4-f49351b811f1": { "id": "3-4e4f-41d9-94c4-f49351b811f1", - "tags": Object { + "tags": { "invitationId": "21ef606f-b25b-48c6-bafa-e79193732413", - "recipientKeyFingerprints": Array [ + "recipientKeyFingerprints": [ "z6Mkt1tsp15cnDD7wBCFgehiR2SxHX1aPxt4sueE24twH9Bd", ], "role": "sender", "state": "done", + "threadId": "21ef606f-b25b-48c6-bafa-e79193732413", }, "type": "OutOfBandRecord", - "value": Object { - "_tags": Object { - "recipientKeyFingerprints": Array [ + "value": { + "_tags": { + "recipientKeyFingerprints": [ "z6Mkt1tsp15cnDD7wBCFgehiR2SxHX1aPxt4sueE24twH9Bd", ], }, @@ -922,30 +936,30 @@ Object { "createdAt": "2022-04-30T13:02:21.628Z", "id": "3-4e4f-41d9-94c4-f49351b811f1", "mediatorId": undefined, - "metadata": Object {}, - "outOfBandInvitation": Object { + "metadata": {}, + "outOfBandInvitation": { "@id": "21ef606f-b25b-48c6-bafa-e79193732413", "@type": "https://didcomm.org/out-of-band/1.1/invitation", - "accept": Array [ + "accept": [ "didcomm/aip1", "didcomm/aip2;env=rfc19", ], "goal": undefined, "goal_code": undefined, - "handshake_protocols": Array [ + "handshake_protocols": [ "https://didcomm.org/connections/1.0", ], "imageUrl": undefined, "label": "Agent: PopulateWallet", "requests~attach": undefined, - "services": Array [ - Object { + "services": [ + { "accept": undefined, "id": "#inline", - "recipientKeys": Array [ + "recipientKeys": [ "did:key:z6Mkt1tsp15cnDD7wBCFgehiR2SxHX1aPxt4sueE24twH9Bd", ], - "routingKeys": Array [], + "routingKeys": [], "serviceEndpoint": "rxjs:alice", "type": "did-communication", }, @@ -962,23 +976,25 @@ Object { "reuseConnectionId": undefined, "role": "sender", "state": "done", + "updatedAt": "2022-01-21T22:50:20.522Z", "v2OutOfBandInvitation": undefined, }, }, - "4-4e4f-41d9-94c4-f49351b811f1": Object { + "4-4e4f-41d9-94c4-f49351b811f1": { "id": "4-4e4f-41d9-94c4-f49351b811f1", - "tags": Object { + "tags": { "invitationId": "08eb8d8b-67cf-4ce2-9aca-c7d260a5c143", - "recipientKeyFingerprints": Array [ + "recipientKeyFingerprints": [ "z6Mkmod8vp2nURVktVC5ceQeyr2VUz26iu2ZANLNVg9pMawa", ], "role": "receiver", "state": "done", + "threadId": "08eb8d8b-67cf-4ce2-9aca-c7d260a5c143", }, "type": "OutOfBandRecord", - "value": Object { - "_tags": Object { - "recipientKeyFingerprints": Array [ + "value": { + "_tags": { + "recipientKeyFingerprints": [ "z6Mkmod8vp2nURVktVC5ceQeyr2VUz26iu2ZANLNVg9pMawa", ], }, @@ -987,30 +1003,30 @@ Object { "createdAt": "2022-04-30T13:02:21.635Z", "id": "4-4e4f-41d9-94c4-f49351b811f1", "mediatorId": undefined, - "metadata": Object {}, - "outOfBandInvitation": Object { + "metadata": {}, + "outOfBandInvitation": { "@id": "08eb8d8b-67cf-4ce2-9aca-c7d260a5c143", "@type": "https://didcomm.org/out-of-band/1.1/invitation", - "accept": Array [ + "accept": [ "didcomm/aip1", "didcomm/aip2;env=rfc19", ], "goal": undefined, "goal_code": undefined, - "handshake_protocols": Array [ + "handshake_protocols": [ "https://didcomm.org/connections/1.0", ], "imageUrl": undefined, "label": "Agent: PopulateWallet2", "requests~attach": undefined, - "services": Array [ - Object { + "services": [ + { "accept": undefined, "id": "#inline", - "recipientKeys": Array [ + "recipientKeys": [ "did:key:z6Mkmod8vp2nURVktVC5ceQeyr2VUz26iu2ZANLNVg9pMawa", ], - "routingKeys": Array [], + "routingKeys": [], "serviceEndpoint": "rxjs:faber", "type": "did-communication", }, @@ -1027,23 +1043,25 @@ Object { "reuseConnectionId": undefined, "role": "receiver", "state": "done", + "updatedAt": "2022-01-21T22:50:20.522Z", "v2OutOfBandInvitation": undefined, }, }, - "5-4e4f-41d9-94c4-f49351b811f1": Object { + "5-4e4f-41d9-94c4-f49351b811f1": { "id": "5-4e4f-41d9-94c4-f49351b811f1", - "tags": Object { + "tags": { "invitationId": "cc67fb5e-1414-4ba6-9030-7456ccd2aaea", - "recipientKeyFingerprints": Array [ + "recipientKeyFingerprints": [ "z6MkjDJL4X7YGoH6gjamhZR2NzowPZqtJfX5kPuNuWiVdjMr", ], "role": "receiver", "state": "done", + "threadId": "cc67fb5e-1414-4ba6-9030-7456ccd2aaea", }, "type": "OutOfBandRecord", - "value": Object { - "_tags": Object { - "recipientKeyFingerprints": Array [ + "value": { + "_tags": { + "recipientKeyFingerprints": [ "z6MkjDJL4X7YGoH6gjamhZR2NzowPZqtJfX5kPuNuWiVdjMr", ], }, @@ -1052,30 +1070,30 @@ Object { "createdAt": "2022-04-30T13:02:21.641Z", "id": "5-4e4f-41d9-94c4-f49351b811f1", "mediatorId": undefined, - "metadata": Object {}, - "outOfBandInvitation": Object { + "metadata": {}, + "outOfBandInvitation": { "@id": "cc67fb5e-1414-4ba6-9030-7456ccd2aaea", "@type": "https://didcomm.org/out-of-band/1.1/invitation", - "accept": Array [ + "accept": [ "didcomm/aip1", "didcomm/aip2;env=rfc19", ], "goal": undefined, "goal_code": undefined, - "handshake_protocols": Array [ + "handshake_protocols": [ "https://didcomm.org/connections/1.0", ], "imageUrl": undefined, "label": "Agent: PopulateWallet2", "requests~attach": undefined, - "services": Array [ - Object { + "services": [ + { "accept": undefined, "id": "#inline", - "recipientKeys": Array [ + "recipientKeys": [ "did:key:z6MkjDJL4X7YGoH6gjamhZR2NzowPZqtJfX5kPuNuWiVdjMr", ], - "routingKeys": Array [], + "routingKeys": [], "serviceEndpoint": "rxjs:faber", "type": "did-communication", }, @@ -1092,50 +1110,52 @@ Object { "reuseConnectionId": undefined, "role": "receiver", "state": "done", + "updatedAt": "2022-01-21T22:50:20.522Z", "v2OutOfBandInvitation": undefined, }, }, - "6-4e4f-41d9-94c4-f49351b811f1": Object { + "6-4e4f-41d9-94c4-f49351b811f1": { "id": "6-4e4f-41d9-94c4-f49351b811f1", - "tags": Object { + "tags": { "invitationId": "f0ca03d8-2e11-4ff2-a5fc-e0137a434b7e", - "recipientKeyFingerprints": Array [ + "recipientKeyFingerprints": [ "z6Mko31DNE3gqMRZj1JNhv2BHb1caQshcd9njgKkEQXsgFRp", ], "role": "sender", "state": "await-response", + "threadId": "f0ca03d8-2e11-4ff2-a5fc-e0137a434b7e", }, "type": "OutOfBandRecord", - "value": Object { + "value": { "alias": undefined, "autoAcceptConnection": true, "createdAt": "2022-04-30T13:02:21.646Z", "id": "6-4e4f-41d9-94c4-f49351b811f1", "mediatorId": undefined, - "metadata": Object {}, - "outOfBandInvitation": Object { + "metadata": {}, + "outOfBandInvitation": { "@id": "f0ca03d8-2e11-4ff2-a5fc-e0137a434b7e", "@type": "https://didcomm.org/out-of-band/1.1/invitation", - "accept": Array [ + "accept": [ "didcomm/aip1", "didcomm/aip2;env=rfc19", ], "goal": undefined, "goal_code": undefined, - "handshake_protocols": Array [ + "handshake_protocols": [ "https://didcomm.org/connections/1.0", ], "imageUrl": undefined, "label": "Agent: PopulateWallet", "requests~attach": undefined, - "services": Array [ - Object { + "services": [ + { "accept": undefined, "id": "#inline", - "recipientKeys": Array [ + "recipientKeys": [ "did:key:z6Mko31DNE3gqMRZj1JNhv2BHb1caQshcd9njgKkEQXsgFRp", ], - "routingKeys": Array [], + "routingKeys": [], "serviceEndpoint": "rxjs:alice", "type": "did-communication", }, @@ -1152,23 +1172,25 @@ Object { "reuseConnectionId": undefined, "role": "sender", "state": "await-response", + "updatedAt": "2022-01-21T22:50:20.522Z", "v2OutOfBandInvitation": undefined, }, }, - "7-4e4f-41d9-94c4-f49351b811f1": Object { + "7-4e4f-41d9-94c4-f49351b811f1": { "id": "7-4e4f-41d9-94c4-f49351b811f1", - "tags": Object { + "tags": { "invitationId": "1f516e35-08d3-43d8-900c-99d5239f54da", - "recipientKeyFingerprints": Array [ + "recipientKeyFingerprints": [ "z6MkuWTEmH1mUo6W96zSWyH612hFHowRzNEscPYBL2CCMyC2", ], "role": "sender", "state": "await-response", + "threadId": "1f516e35-08d3-43d8-900c-99d5239f54da", }, "type": "OutOfBandRecord", - "value": Object { - "_tags": Object { - "recipientKeyFingerprints": Array [ + "value": { + "_tags": { + "recipientKeyFingerprints": [ "z6MkuWTEmH1mUo6W96zSWyH612hFHowRzNEscPYBL2CCMyC2", ], }, @@ -1177,30 +1199,30 @@ Object { "createdAt": "2022-04-30T13:02:21.653Z", "id": "7-4e4f-41d9-94c4-f49351b811f1", "mediatorId": undefined, - "metadata": Object {}, - "outOfBandInvitation": Object { + "metadata": {}, + "outOfBandInvitation": { "@id": "1f516e35-08d3-43d8-900c-99d5239f54da", "@type": "https://didcomm.org/out-of-band/1.1/invitation", - "accept": Array [ + "accept": [ "didcomm/aip1", "didcomm/aip2;env=rfc19", ], "goal": undefined, "goal_code": undefined, - "handshake_protocols": Array [ + "handshake_protocols": [ "https://didcomm.org/connections/1.0", ], "imageUrl": undefined, "label": "Agent: PopulateWallet", "requests~attach": undefined, - "services": Array [ - Object { + "services": [ + { "accept": undefined, "id": "#inline", - "recipientKeys": Array [ + "recipientKeys": [ "did:key:z6MkuWTEmH1mUo6W96zSWyH612hFHowRzNEscPYBL2CCMyC2", ], - "routingKeys": Array [], + "routingKeys": [], "serviceEndpoint": "rxjs:alice", "type": "did-communication", }, @@ -1217,13 +1239,14 @@ Object { "reuseConnectionId": undefined, "role": "sender", "state": "await-response", + "updatedAt": "2022-01-21T22:50:20.522Z", "v2OutOfBandInvitation": undefined, }, }, - "7781341d-be29-441b-9b79-4a957d8c6d37": Object { + "7781341d-be29-441b-9b79-4a957d8c6d37": { "id": "7781341d-be29-441b-9b79-4a957d8c6d37", - "tags": Object { - "connectionTypes": Array [], + "tags": { + "connectionTypes": [], "did": "did:peer:1zQmSMBVNMDrh7fyE8bkAmk1ZatshjinpsEqPA3nx8JYjuKb", "invitationDid": "did:peer:2.SeyJzIjoicnhqczphbGljZSIsInQiOiJkaWQtY29tbXVuaWNhdGlvbiIsInByaW9yaXR5IjowLCJyZWNpcGllbnRLZXlzIjpbImRpZDprZXk6ejZNa3QxdHNwMTVjbkREN3dCQ0ZnZWhpUjJTeEhYMWFQeHQ0c3VlRTI0dHdIOUJkI3o2TWt0MXRzcDE1Y25ERDd3QkNGZ2VoaVIyU3hIWDFhUHh0NHN1ZUUyNHR3SDlCZCJdLCJyIjpbXX0", "invitationKey": "EZdqDkqBSfiepgMZ15jsZvtxTwjiz5diBtjJBnvvMvQF", @@ -1237,26 +1260,27 @@ Object { "verkey": "EZdqDkqBSfiepgMZ15jsZvtxTwjiz5diBtjJBnvvMvQF", }, "type": "ConnectionRecord", - "value": Object { + "value": { "autoAcceptConnection": false, - "connectionTypes": Array [], + "connectionTypes": [], "createdAt": "2022-04-30T13:02:21.628Z", "did": "did:peer:1zQmSMBVNMDrh7fyE8bkAmk1ZatshjinpsEqPA3nx8JYjuKb", "id": "7781341d-be29-441b-9b79-4a957d8c6d37", "invitationDid": "did:peer:2.SeyJzIjoicnhqczphbGljZSIsInQiOiJkaWQtY29tbXVuaWNhdGlvbiIsInByaW9yaXR5IjowLCJyZWNpcGllbnRLZXlzIjpbImRpZDprZXk6ejZNa3QxdHNwMTVjbkREN3dCQ0ZnZWhpUjJTeEhYMWFQeHQ0c3VlRTI0dHdIOUJkI3o2TWt0MXRzcDE1Y25ERDd3QkNGZ2VoaVIyU3hIWDFhUHh0NHN1ZUUyNHR3SDlCZCJdLCJyIjpbXX0", - "metadata": Object {}, + "metadata": {}, "outOfBandId": "3-4e4f-41d9-94c4-f49351b811f1", "role": "responder", "state": "request-received", "theirDid": "did:peer:1zQmc3BZoTinpVaG3oZ4PmRVN4JMdNZGCmPkS6smmTNLnvEZ", "theirLabel": "Agent: PopulateWallet2", "threadId": "a0c0e4d2-1501-42a2-a09b-7d5adc90b353", + "updatedAt": "2022-01-21T22:50:20.522Z", }, }, - "8f4908ee-15ad-4058-9106-eda26eae735c": Object { + "8f4908ee-15ad-4058-9106-eda26eae735c": { "id": "8f4908ee-15ad-4058-9106-eda26eae735c", - "tags": Object { - "connectionTypes": Array [], + "tags": { + "connectionTypes": [], "did": "did:peer:1zQmRAfQ6J5qk4qcbHyoStFVkhusazLT9xQcFhdC9dhhQ1cJ", "invitationDid": "did:peer:2.SeyJzIjoicnhqczpmYWJlciIsInQiOiJkaWQtY29tbXVuaWNhdGlvbiIsInByaW9yaXR5IjowLCJyZWNpcGllbnRLZXlzIjpbImRpZDprZXk6ejZNa2ZpUE1QeENRZVNEWkdNa0N2bTFZMnJCb1BzbXc0WkhNdjcxalh0Y1dSUmlNI3o2TWtmaVBNUHhDUWVTRFpHTWtDdm0xWTJyQm9Qc213NFpITXY3MWpYdGNXUlJpTSJdLCJyIjpbXX0", "invitationKey": "2G8JohwyJtj69ruWFC3hBkdoaJW5eg31E66ohceVWCvy", @@ -1270,26 +1294,27 @@ Object { "verkey": "HfkCHGAHTz3j33TRDkKMabYLdnr2FKuWcaXTLzZkZcCp", }, "type": "ConnectionRecord", - "value": Object { + "value": { "alias": "connection alias", - "connectionTypes": Array [], + "connectionTypes": [], "createdAt": "2022-04-30T13:02:21.577Z", "did": "did:peer:1zQmRAfQ6J5qk4qcbHyoStFVkhusazLT9xQcFhdC9dhhQ1cJ", "id": "8f4908ee-15ad-4058-9106-eda26eae735c", "invitationDid": "did:peer:2.SeyJzIjoicnhqczpmYWJlciIsInQiOiJkaWQtY29tbXVuaWNhdGlvbiIsInByaW9yaXR5IjowLCJyZWNpcGllbnRLZXlzIjpbImRpZDprZXk6ejZNa2ZpUE1QeENRZVNEWkdNa0N2bTFZMnJCb1BzbXc0WkhNdjcxalh0Y1dSUmlNI3o2TWtmaVBNUHhDUWVTRFpHTWtDdm0xWTJyQm9Qc213NFpITXY3MWpYdGNXUlJpTSJdLCJyIjpbXX0", - "metadata": Object {}, + "metadata": {}, "outOfBandId": "1-4e4f-41d9-94c4-f49351b811f1", "role": "requester", "state": "completed", "theirDid": "did:peer:1zQmeHpGaZ48DnAP2k3KntXB1vmd8MgLEdcb4EQzqWJDHcbX", "theirLabel": "Agent: PopulateWallet2", "threadId": "fe287ec6-711b-4582-bb2b-d155aee86e61", + "updatedAt": "2022-01-21T22:50:20.522Z", }, }, - "9383d8e5-c002-4aae-8300-4a21384c919e": Object { + "9383d8e5-c002-4aae-8300-4a21384c919e": { "id": "9383d8e5-c002-4aae-8300-4a21384c919e", - "tags": Object { - "connectionTypes": Array [], + "tags": { + "connectionTypes": [], "did": "did:peer:1zQmP96nW6vbNjzwPt19z1NYqhnAfgnAFqfLHcktkmdUFzhT", "invitationDid": "did:peer:2.SeyJzIjoicnhqczphbGljZSIsInQiOiJkaWQtY29tbXVuaWNhdGlvbiIsInByaW9yaXR5IjowLCJyZWNpcGllbnRLZXlzIjpbImRpZDprZXk6ejZNa3RDWkFRTkd2V2I0V0hBandCcVB0WGhaZERZb3JiU0prR1c5dmoxdWh3MUhEI3o2TWt0Q1pBUU5HdldiNFdIQWp3QnFQdFhoWmREWW9yYlNKa0dXOXZqMXVodzFIRCJdLCJyIjpbXX0", "invitationKey": "EkJ7p82VB3a3AfuEWGS3gc1dPyY1BZ4PaVEztjwh1nVq", @@ -1303,36 +1328,38 @@ Object { "verkey": "EkJ7p82VB3a3AfuEWGS3gc1dPyY1BZ4PaVEztjwh1nVq", }, "type": "ConnectionRecord", - "value": Object { - "connectionTypes": Array [], + "value": { + "connectionTypes": [], "createdAt": "2022-04-30T13:02:21.608Z", "did": "did:peer:1zQmP96nW6vbNjzwPt19z1NYqhnAfgnAFqfLHcktkmdUFzhT", "id": "9383d8e5-c002-4aae-8300-4a21384c919e", "invitationDid": "did:peer:2.SeyJzIjoicnhqczphbGljZSIsInQiOiJkaWQtY29tbXVuaWNhdGlvbiIsInByaW9yaXR5IjowLCJyZWNpcGllbnRLZXlzIjpbImRpZDprZXk6ejZNa3RDWkFRTkd2V2I0V0hBandCcVB0WGhaZERZb3JiU0prR1c5dmoxdWh3MUhEI3o2TWt0Q1pBUU5HdldiNFdIQWp3QnFQdFhoWmREWW9yYlNKa0dXOXZqMXVodzFIRCJdLCJyIjpbXX0", - "metadata": Object {}, + "metadata": {}, "outOfBandId": "2-4e4f-41d9-94c4-f49351b811f1", "role": "responder", "state": "completed", "theirDid": "did:peer:1zQmXYj3nNwsF37WXXdb8XkCAtsTCBpJJbsLKPPGfi2PWCTU", "theirLabel": "Agent: PopulateWallet2", "threadId": "0b2f1133-ced9-49f1-83a1-eb6ba1c24cdf", + "updatedAt": "2022-01-21T22:50:20.522Z", }, }, - "STORAGE_VERSION_RECORD_ID": Object { + "STORAGE_VERSION_RECORD_ID": { "id": "STORAGE_VERSION_RECORD_ID", - "tags": Object {}, + "tags": {}, "type": "StorageVersionRecord", - "value": Object { + "value": { "createdAt": "2022-01-21T22:50:20.522Z", "id": "STORAGE_VERSION_RECORD_ID", - "metadata": Object {}, + "metadata": {}, "storageVersion": "0.2", + "updatedAt": "2022-01-21T22:50:20.522Z", }, }, - "b65c2ccd-277c-4140-9d87-c8dd30e7a98c": Object { + "b65c2ccd-277c-4140-9d87-c8dd30e7a98c": { "id": "b65c2ccd-277c-4140-9d87-c8dd30e7a98c", - "tags": Object { - "connectionTypes": Array [], + "tags": { + "connectionTypes": [], "did": "did:peer:1zQmfDAtfDZcK4trJBsvVTXrBx9uaLCHSUZH9X2LFaAd3JKv", "invitationDid": "did:peer:2.SeyJzIjoicnhqczphbGljZSIsInQiOiJkaWQtY29tbXVuaWNhdGlvbiIsInByaW9yaXR5IjowLCJyZWNpcGllbnRLZXlzIjpbImRpZDprZXk6ejZNa3VXVEVtSDFtVW82Vzk2elNXeUg2MTJoRkhvd1J6TkVzY1BZQkwyQ0NNeUMyI3o2TWt1V1RFbUgxbVVvNlc5NnpTV3lINjEyaEZIb3dSek5Fc2NQWUJMMkNDTXlDMiJdLCJyIjpbXX0", "invitationKey": "G4CCB2mL9Fc32c9jqQKF9w9FUEfaaUzWvNdFVkEBSkQe", @@ -1345,23 +1372,24 @@ Object { "verkey": "G4CCB2mL9Fc32c9jqQKF9w9FUEfaaUzWvNdFVkEBSkQe", }, "type": "ConnectionRecord", - "value": Object { + "value": { "autoAcceptConnection": true, - "connectionTypes": Array [], + "connectionTypes": [], "createdAt": "2022-04-30T13:02:21.653Z", "did": "did:peer:1zQmfDAtfDZcK4trJBsvVTXrBx9uaLCHSUZH9X2LFaAd3JKv", "id": "b65c2ccd-277c-4140-9d87-c8dd30e7a98c", "invitationDid": "did:peer:2.SeyJzIjoicnhqczphbGljZSIsInQiOiJkaWQtY29tbXVuaWNhdGlvbiIsInByaW9yaXR5IjowLCJyZWNpcGllbnRLZXlzIjpbImRpZDprZXk6ejZNa3VXVEVtSDFtVW82Vzk2elNXeUg2MTJoRkhvd1J6TkVzY1BZQkwyQ0NNeUMyI3o2TWt1V1RFbUgxbVVvNlc5NnpTV3lINjEyaEZIb3dSek5Fc2NQWUJMMkNDTXlDMiJdLCJyIjpbXX0", - "metadata": Object {}, + "metadata": {}, "outOfBandId": "7-4e4f-41d9-94c4-f49351b811f1", "role": "responder", "state": "invitation-sent", + "updatedAt": "2022-01-21T22:50:20.522Z", }, }, - "da518433-0e55-4b74-a05b-aa75c1095a99": Object { + "da518433-0e55-4b74-a05b-aa75c1095a99": { "id": "da518433-0e55-4b74-a05b-aa75c1095a99", - "tags": Object { - "connectionTypes": Array [], + "tags": { + "connectionTypes": [], "did": "did:peer:1zQmPbGa8KDwyjcw9UgwCCgJMV7jU5kKCyvBuwFVc88WxA56", "invitationDid": "did:peer:2.SeyJzIjoicnhqczphbGljZSIsInQiOiJkaWQtY29tbXVuaWNhdGlvbiIsInByaW9yaXR5IjowLCJyZWNpcGllbnRLZXlzIjpbImRpZDprZXk6ejZNa28zMURORTNncU1SWmoxSk5odjJCSGIxY2FRc2hjZDluamdLa0VRWHNnRlJwI3o2TWtvMzFETkUzZ3FNUlpqMUpOaHYyQkhiMWNhUXNoY2Q5bmpnS2tFUVhzZ0ZScCJdLCJyIjpbXX0", "invitationKey": "9akAmyoFVow6cWTg2M4LSVTckqbrCjuS3fQpQ8Zrm2eS", @@ -1375,50 +1403,51 @@ Object { "verkey": "9akAmyoFVow6cWTg2M4LSVTckqbrCjuS3fQpQ8Zrm2eS", }, "type": "ConnectionRecord", - "value": Object { + "value": { "autoAcceptConnection": true, - "connectionTypes": Array [], + "connectionTypes": [], "createdAt": "2022-04-30T13:02:21.646Z", "did": "did:peer:1zQmPbGa8KDwyjcw9UgwCCgJMV7jU5kKCyvBuwFVc88WxA56", "id": "da518433-0e55-4b74-a05b-aa75c1095a99", "invitationDid": "did:peer:2.SeyJzIjoicnhqczphbGljZSIsInQiOiJkaWQtY29tbXVuaWNhdGlvbiIsInByaW9yaXR5IjowLCJyZWNpcGllbnRLZXlzIjpbImRpZDprZXk6ejZNa28zMURORTNncU1SWmoxSk5odjJCSGIxY2FRc2hjZDluamdLa0VRWHNnRlJwI3o2TWtvMzFETkUzZ3FNUlpqMUpOaHYyQkhiMWNhUXNoY2Q5bmpnS2tFUVhzZ0ZScCJdLCJyIjpbXX0", - "metadata": Object {}, + "metadata": {}, "outOfBandId": "6-4e4f-41d9-94c4-f49351b811f1", "role": "responder", "state": "response-sent", "theirDid": "did:peer:1zQmadmBfngrYSWhYYxZ24fpW29iwhKhQ6CB6euLabbSK6ga", "theirLabel": "Agent: PopulateWallet2", "threadId": "6eeb6a80-cd75-491d-b2e0-7bae65ced1c3", + "updatedAt": "2022-01-21T22:50:20.522Z", }, }, - "did:peer:1zQmP96nW6vbNjzwPt19z1NYqhnAfgnAFqfLHcktkmdUFzhT": Object { + "did:peer:1zQmP96nW6vbNjzwPt19z1NYqhnAfgnAFqfLHcktkmdUFzhT": { "id": "did:peer:1zQmP96nW6vbNjzwPt19z1NYqhnAfgnAFqfLHcktkmdUFzhT", - "tags": Object { + "tags": { "did": "did:peer:1zQmP96nW6vbNjzwPt19z1NYqhnAfgnAFqfLHcktkmdUFzhT", "legacyUnqualifiedDid": "SDqTzbVuCowusqGBNbNDjH", "method": "peer", "methodSpecificIdentifier": "1zQmP96nW6vbNjzwPt19z1NYqhnAfgnAFqfLHcktkmdUFzhT", - "recipientKeyFingerprints": Array [ + "recipientKeyFingerprints": [ "z6MktCZAQNGvWb4WHAjwBqPtXhZdDYorbSJkGW9vj1uhw1HD", ], "role": "created", }, "type": "DidRecord", - "value": Object { - "_tags": Object { - "recipientKeyFingerprints": Array [ + "value": { + "_tags": { + "recipientKeyFingerprints": [ "z6MktCZAQNGvWb4WHAjwBqPtXhZdDYorbSJkGW9vj1uhw1HD", ], }, "createdAt": "2022-04-30T13:02:21.608Z", "did": "did:peer:1zQmP96nW6vbNjzwPt19z1NYqhnAfgnAFqfLHcktkmdUFzhT", - "didDocument": Object { - "@context": Array [ + "didDocument": { + "@context": [ "https://w3id.org/did/v1", ], "alsoKnownAs": undefined, "assertionMethod": undefined, - "authentication": Array [ + "authentication": [ "#EkJ7p82V", ], "capabilityDelegation": undefined, @@ -1426,20 +1455,20 @@ Object { "controller": undefined, "id": "did:peer:1zQmP96nW6vbNjzwPt19z1NYqhnAfgnAFqfLHcktkmdUFzhT", "keyAgreement": undefined, - "service": Array [ - Object { + "service": [ + { "id": "#IndyAgentService", "priority": 0, - "recipientKeys": Array [ + "recipientKeys": [ "EkJ7p82VB3a3AfuEWGS3gc1dPyY1BZ4PaVEztjwh1nVq", ], - "routingKeys": Array [], + "routingKeys": [], "serviceEndpoint": "rxjs:alice", "type": "IndyAgent", }, ], - "verificationMethod": Array [ - Object { + "verificationMethod": [ + { "blockchainAccountId": undefined, "controller": "#id", "ethereumAddress": undefined, @@ -1455,43 +1484,44 @@ Object { ], }, "id": "did:peer:1zQmP96nW6vbNjzwPt19z1NYqhnAfgnAFqfLHcktkmdUFzhT", - "metadata": Object { - "_internal/legacyDid": Object { - "didDocumentString": "{\\"@context\\":\\"https://w3id.org/did/v1\\",\\"publicKey\\":[{\\"id\\":\\"SDqTzbVuCowusqGBNbNDjH#1\\",\\"controller\\":\\"SDqTzbVuCowusqGBNbNDjH\\",\\"type\\":\\"Ed25519VerificationKey2018\\",\\"publicKeyBase58\\":\\"EkJ7p82VB3a3AfuEWGS3gc1dPyY1BZ4PaVEztjwh1nVq\\"}],\\"service\\":[{\\"id\\":\\"SDqTzbVuCowusqGBNbNDjH#IndyAgentService\\",\\"serviceEndpoint\\":\\"rxjs:alice\\",\\"type\\":\\"IndyAgent\\",\\"priority\\":0,\\"recipientKeys\\":[\\"EkJ7p82VB3a3AfuEWGS3gc1dPyY1BZ4PaVEztjwh1nVq\\"],\\"routingKeys\\":[]}],\\"authentication\\":[{\\"publicKey\\":\\"SDqTzbVuCowusqGBNbNDjH#1\\",\\"type\\":\\"Ed25519SignatureAuthentication2018\\"}],\\"id\\":\\"SDqTzbVuCowusqGBNbNDjH\\"}", + "metadata": { + "_internal/legacyDid": { + "didDocumentString": "{"@context":"https://w3id.org/did/v1","publicKey":[{"id":"SDqTzbVuCowusqGBNbNDjH#1","controller":"SDqTzbVuCowusqGBNbNDjH","type":"Ed25519VerificationKey2018","publicKeyBase58":"EkJ7p82VB3a3AfuEWGS3gc1dPyY1BZ4PaVEztjwh1nVq"}],"service":[{"id":"SDqTzbVuCowusqGBNbNDjH#IndyAgentService","serviceEndpoint":"rxjs:alice","type":"IndyAgent","priority":0,"recipientKeys":["EkJ7p82VB3a3AfuEWGS3gc1dPyY1BZ4PaVEztjwh1nVq"],"routingKeys":[]}],"authentication":[{"publicKey":"SDqTzbVuCowusqGBNbNDjH#1","type":"Ed25519SignatureAuthentication2018"}],"id":"SDqTzbVuCowusqGBNbNDjH"}", "unqualifiedDid": "SDqTzbVuCowusqGBNbNDjH", }, }, "role": "created", + "updatedAt": "2022-01-21T22:50:20.522Z", }, }, - "did:peer:1zQmPbGa8KDwyjcw9UgwCCgJMV7jU5kKCyvBuwFVc88WxA56": Object { + "did:peer:1zQmPbGa8KDwyjcw9UgwCCgJMV7jU5kKCyvBuwFVc88WxA56": { "id": "did:peer:1zQmPbGa8KDwyjcw9UgwCCgJMV7jU5kKCyvBuwFVc88WxA56", - "tags": Object { + "tags": { "did": "did:peer:1zQmPbGa8KDwyjcw9UgwCCgJMV7jU5kKCyvBuwFVc88WxA56", "legacyUnqualifiedDid": "GkEeb96MGT94K1HyQQzpj1", "method": "peer", "methodSpecificIdentifier": "1zQmPbGa8KDwyjcw9UgwCCgJMV7jU5kKCyvBuwFVc88WxA56", - "recipientKeyFingerprints": Array [ + "recipientKeyFingerprints": [ "z6Mko31DNE3gqMRZj1JNhv2BHb1caQshcd9njgKkEQXsgFRp", ], "role": "created", }, "type": "DidRecord", - "value": Object { - "_tags": Object { - "recipientKeyFingerprints": Array [ + "value": { + "_tags": { + "recipientKeyFingerprints": [ "z6Mko31DNE3gqMRZj1JNhv2BHb1caQshcd9njgKkEQXsgFRp", ], }, "createdAt": "2022-04-30T13:02:21.646Z", "did": "did:peer:1zQmPbGa8KDwyjcw9UgwCCgJMV7jU5kKCyvBuwFVc88WxA56", - "didDocument": Object { - "@context": Array [ + "didDocument": { + "@context": [ "https://w3id.org/did/v1", ], "alsoKnownAs": undefined, "assertionMethod": undefined, - "authentication": Array [ + "authentication": [ "#9akAmyoF", ], "capabilityDelegation": undefined, @@ -1499,20 +1529,20 @@ Object { "controller": undefined, "id": "did:peer:1zQmPbGa8KDwyjcw9UgwCCgJMV7jU5kKCyvBuwFVc88WxA56", "keyAgreement": undefined, - "service": Array [ - Object { + "service": [ + { "id": "#IndyAgentService", "priority": 0, - "recipientKeys": Array [ + "recipientKeys": [ "9akAmyoFVow6cWTg2M4LSVTckqbrCjuS3fQpQ8Zrm2eS", ], - "routingKeys": Array [], + "routingKeys": [], "serviceEndpoint": "rxjs:alice", "type": "IndyAgent", }, ], - "verificationMethod": Array [ - Object { + "verificationMethod": [ + { "blockchainAccountId": undefined, "controller": "#id", "ethereumAddress": undefined, @@ -1528,43 +1558,44 @@ Object { ], }, "id": "did:peer:1zQmPbGa8KDwyjcw9UgwCCgJMV7jU5kKCyvBuwFVc88WxA56", - "metadata": Object { - "_internal/legacyDid": Object { - "didDocumentString": "{\\"@context\\":\\"https://w3id.org/did/v1\\",\\"publicKey\\":[{\\"id\\":\\"GkEeb96MGT94K1HyQQzpj1#1\\",\\"controller\\":\\"GkEeb96MGT94K1HyQQzpj1\\",\\"type\\":\\"Ed25519VerificationKey2018\\",\\"publicKeyBase58\\":\\"9akAmyoFVow6cWTg2M4LSVTckqbrCjuS3fQpQ8Zrm2eS\\"}],\\"service\\":[{\\"id\\":\\"GkEeb96MGT94K1HyQQzpj1#IndyAgentService\\",\\"serviceEndpoint\\":\\"rxjs:alice\\",\\"type\\":\\"IndyAgent\\",\\"priority\\":0,\\"recipientKeys\\":[\\"9akAmyoFVow6cWTg2M4LSVTckqbrCjuS3fQpQ8Zrm2eS\\"],\\"routingKeys\\":[]}],\\"authentication\\":[{\\"publicKey\\":\\"GkEeb96MGT94K1HyQQzpj1#1\\",\\"type\\":\\"Ed25519SignatureAuthentication2018\\"}],\\"id\\":\\"GkEeb96MGT94K1HyQQzpj1\\"}", + "metadata": { + "_internal/legacyDid": { + "didDocumentString": "{"@context":"https://w3id.org/did/v1","publicKey":[{"id":"GkEeb96MGT94K1HyQQzpj1#1","controller":"GkEeb96MGT94K1HyQQzpj1","type":"Ed25519VerificationKey2018","publicKeyBase58":"9akAmyoFVow6cWTg2M4LSVTckqbrCjuS3fQpQ8Zrm2eS"}],"service":[{"id":"GkEeb96MGT94K1HyQQzpj1#IndyAgentService","serviceEndpoint":"rxjs:alice","type":"IndyAgent","priority":0,"recipientKeys":["9akAmyoFVow6cWTg2M4LSVTckqbrCjuS3fQpQ8Zrm2eS"],"routingKeys":[]}],"authentication":[{"publicKey":"GkEeb96MGT94K1HyQQzpj1#1","type":"Ed25519SignatureAuthentication2018"}],"id":"GkEeb96MGT94K1HyQQzpj1"}", "unqualifiedDid": "GkEeb96MGT94K1HyQQzpj1", }, }, "role": "created", + "updatedAt": "2022-01-21T22:50:20.522Z", }, }, - "did:peer:1zQmRAfQ6J5qk4qcbHyoStFVkhusazLT9xQcFhdC9dhhQ1cJ": Object { + "did:peer:1zQmRAfQ6J5qk4qcbHyoStFVkhusazLT9xQcFhdC9dhhQ1cJ": { "id": "did:peer:1zQmRAfQ6J5qk4qcbHyoStFVkhusazLT9xQcFhdC9dhhQ1cJ", - "tags": Object { + "tags": { "did": "did:peer:1zQmRAfQ6J5qk4qcbHyoStFVkhusazLT9xQcFhdC9dhhQ1cJ", "legacyUnqualifiedDid": "XajWZZmHGAWUvYCi7CApaG", "method": "peer", "methodSpecificIdentifier": "1zQmRAfQ6J5qk4qcbHyoStFVkhusazLT9xQcFhdC9dhhQ1cJ", - "recipientKeyFingerprints": Array [ + "recipientKeyFingerprints": [ "z6Mkw81EsWQioXYC9YJ7uKHCRh6LTN7sfD9sJbSPBGXmUpzC", ], "role": "created", }, "type": "DidRecord", - "value": Object { - "_tags": Object { - "recipientKeyFingerprints": Array [ + "value": { + "_tags": { + "recipientKeyFingerprints": [ "z6Mkw81EsWQioXYC9YJ7uKHCRh6LTN7sfD9sJbSPBGXmUpzC", ], }, "createdAt": "2022-04-30T13:02:21.577Z", "did": "did:peer:1zQmRAfQ6J5qk4qcbHyoStFVkhusazLT9xQcFhdC9dhhQ1cJ", - "didDocument": Object { - "@context": Array [ + "didDocument": { + "@context": [ "https://w3id.org/did/v1", ], "alsoKnownAs": undefined, "assertionMethod": undefined, - "authentication": Array [ + "authentication": [ "#HfkCHGAH", ], "capabilityDelegation": undefined, @@ -1572,20 +1603,20 @@ Object { "controller": undefined, "id": "did:peer:1zQmRAfQ6J5qk4qcbHyoStFVkhusazLT9xQcFhdC9dhhQ1cJ", "keyAgreement": undefined, - "service": Array [ - Object { + "service": [ + { "id": "#IndyAgentService", "priority": 0, - "recipientKeys": Array [ + "recipientKeys": [ "HfkCHGAHTz3j33TRDkKMabYLdnr2FKuWcaXTLzZkZcCp", ], - "routingKeys": Array [], + "routingKeys": [], "serviceEndpoint": "rxjs:alice", "type": "IndyAgent", }, ], - "verificationMethod": Array [ - Object { + "verificationMethod": [ + { "blockchainAccountId": undefined, "controller": "#id", "ethereumAddress": undefined, @@ -1601,43 +1632,44 @@ Object { ], }, "id": "did:peer:1zQmRAfQ6J5qk4qcbHyoStFVkhusazLT9xQcFhdC9dhhQ1cJ", - "metadata": Object { - "_internal/legacyDid": Object { - "didDocumentString": "{\\"@context\\":\\"https://w3id.org/did/v1\\",\\"publicKey\\":[{\\"id\\":\\"XajWZZmHGAWUvYCi7CApaG#1\\",\\"controller\\":\\"XajWZZmHGAWUvYCi7CApaG\\",\\"type\\":\\"Ed25519VerificationKey2018\\",\\"publicKeyBase58\\":\\"HfkCHGAHTz3j33TRDkKMabYLdnr2FKuWcaXTLzZkZcCp\\"}],\\"service\\":[{\\"id\\":\\"XajWZZmHGAWUvYCi7CApaG#IndyAgentService\\",\\"serviceEndpoint\\":\\"rxjs:alice\\",\\"type\\":\\"IndyAgent\\",\\"priority\\":0,\\"recipientKeys\\":[\\"HfkCHGAHTz3j33TRDkKMabYLdnr2FKuWcaXTLzZkZcCp\\"],\\"routingKeys\\":[]}],\\"authentication\\":[{\\"publicKey\\":\\"XajWZZmHGAWUvYCi7CApaG#1\\",\\"type\\":\\"Ed25519SignatureAuthentication2018\\"}],\\"id\\":\\"XajWZZmHGAWUvYCi7CApaG\\"}", + "metadata": { + "_internal/legacyDid": { + "didDocumentString": "{"@context":"https://w3id.org/did/v1","publicKey":[{"id":"XajWZZmHGAWUvYCi7CApaG#1","controller":"XajWZZmHGAWUvYCi7CApaG","type":"Ed25519VerificationKey2018","publicKeyBase58":"HfkCHGAHTz3j33TRDkKMabYLdnr2FKuWcaXTLzZkZcCp"}],"service":[{"id":"XajWZZmHGAWUvYCi7CApaG#IndyAgentService","serviceEndpoint":"rxjs:alice","type":"IndyAgent","priority":0,"recipientKeys":["HfkCHGAHTz3j33TRDkKMabYLdnr2FKuWcaXTLzZkZcCp"],"routingKeys":[]}],"authentication":[{"publicKey":"XajWZZmHGAWUvYCi7CApaG#1","type":"Ed25519SignatureAuthentication2018"}],"id":"XajWZZmHGAWUvYCi7CApaG"}", "unqualifiedDid": "XajWZZmHGAWUvYCi7CApaG", }, }, "role": "created", + "updatedAt": "2022-01-21T22:50:20.522Z", }, }, - "did:peer:1zQmSMBVNMDrh7fyE8bkAmk1ZatshjinpsEqPA3nx8JYjuKb": Object { + "did:peer:1zQmSMBVNMDrh7fyE8bkAmk1ZatshjinpsEqPA3nx8JYjuKb": { "id": "did:peer:1zQmSMBVNMDrh7fyE8bkAmk1ZatshjinpsEqPA3nx8JYjuKb", - "tags": Object { + "tags": { "did": "did:peer:1zQmSMBVNMDrh7fyE8bkAmk1ZatshjinpsEqPA3nx8JYjuKb", "legacyUnqualifiedDid": "RtH4qxVPL1Dpmdv7GytjBv", "method": "peer", "methodSpecificIdentifier": "1zQmSMBVNMDrh7fyE8bkAmk1ZatshjinpsEqPA3nx8JYjuKb", - "recipientKeyFingerprints": Array [ + "recipientKeyFingerprints": [ "z6Mkt1tsp15cnDD7wBCFgehiR2SxHX1aPxt4sueE24twH9Bd", ], "role": "created", }, "type": "DidRecord", - "value": Object { - "_tags": Object { - "recipientKeyFingerprints": Array [ + "value": { + "_tags": { + "recipientKeyFingerprints": [ "z6Mkt1tsp15cnDD7wBCFgehiR2SxHX1aPxt4sueE24twH9Bd", ], }, "createdAt": "2022-04-30T13:02:21.628Z", "did": "did:peer:1zQmSMBVNMDrh7fyE8bkAmk1ZatshjinpsEqPA3nx8JYjuKb", - "didDocument": Object { - "@context": Array [ + "didDocument": { + "@context": [ "https://w3id.org/did/v1", ], "alsoKnownAs": undefined, "assertionMethod": undefined, - "authentication": Array [ + "authentication": [ "#EZdqDkqB", ], "capabilityDelegation": undefined, @@ -1645,20 +1677,20 @@ Object { "controller": undefined, "id": "did:peer:1zQmSMBVNMDrh7fyE8bkAmk1ZatshjinpsEqPA3nx8JYjuKb", "keyAgreement": undefined, - "service": Array [ - Object { + "service": [ + { "id": "#IndyAgentService", "priority": 0, - "recipientKeys": Array [ + "recipientKeys": [ "EZdqDkqBSfiepgMZ15jsZvtxTwjiz5diBtjJBnvvMvQF", ], - "routingKeys": Array [], + "routingKeys": [], "serviceEndpoint": "rxjs:alice", "type": "IndyAgent", }, ], - "verificationMethod": Array [ - Object { + "verificationMethod": [ + { "blockchainAccountId": undefined, "controller": "#id", "ethereumAddress": undefined, @@ -1674,43 +1706,44 @@ Object { ], }, "id": "did:peer:1zQmSMBVNMDrh7fyE8bkAmk1ZatshjinpsEqPA3nx8JYjuKb", - "metadata": Object { - "_internal/legacyDid": Object { - "didDocumentString": "{\\"@context\\":\\"https://w3id.org/did/v1\\",\\"publicKey\\":[{\\"id\\":\\"RtH4qxVPL1Dpmdv7GytjBv#1\\",\\"controller\\":\\"RtH4qxVPL1Dpmdv7GytjBv\\",\\"type\\":\\"Ed25519VerificationKey2018\\",\\"publicKeyBase58\\":\\"EZdqDkqBSfiepgMZ15jsZvtxTwjiz5diBtjJBnvvMvQF\\"}],\\"service\\":[{\\"id\\":\\"RtH4qxVPL1Dpmdv7GytjBv#IndyAgentService\\",\\"serviceEndpoint\\":\\"rxjs:alice\\",\\"type\\":\\"IndyAgent\\",\\"priority\\":0,\\"recipientKeys\\":[\\"EZdqDkqBSfiepgMZ15jsZvtxTwjiz5diBtjJBnvvMvQF\\"],\\"routingKeys\\":[]}],\\"authentication\\":[{\\"publicKey\\":\\"RtH4qxVPL1Dpmdv7GytjBv#1\\",\\"type\\":\\"Ed25519SignatureAuthentication2018\\"}],\\"id\\":\\"RtH4qxVPL1Dpmdv7GytjBv\\"}", + "metadata": { + "_internal/legacyDid": { + "didDocumentString": "{"@context":"https://w3id.org/did/v1","publicKey":[{"id":"RtH4qxVPL1Dpmdv7GytjBv#1","controller":"RtH4qxVPL1Dpmdv7GytjBv","type":"Ed25519VerificationKey2018","publicKeyBase58":"EZdqDkqBSfiepgMZ15jsZvtxTwjiz5diBtjJBnvvMvQF"}],"service":[{"id":"RtH4qxVPL1Dpmdv7GytjBv#IndyAgentService","serviceEndpoint":"rxjs:alice","type":"IndyAgent","priority":0,"recipientKeys":["EZdqDkqBSfiepgMZ15jsZvtxTwjiz5diBtjJBnvvMvQF"],"routingKeys":[]}],"authentication":[{"publicKey":"RtH4qxVPL1Dpmdv7GytjBv#1","type":"Ed25519SignatureAuthentication2018"}],"id":"RtH4qxVPL1Dpmdv7GytjBv"}", "unqualifiedDid": "RtH4qxVPL1Dpmdv7GytjBv", }, }, "role": "created", + "updatedAt": "2022-01-21T22:50:20.522Z", }, }, - "did:peer:1zQmXYj3nNwsF37WXXdb8XkCAtsTCBpJJbsLKPPGfi2PWCTU": Object { + "did:peer:1zQmXYj3nNwsF37WXXdb8XkCAtsTCBpJJbsLKPPGfi2PWCTU": { "id": "did:peer:1zQmXYj3nNwsF37WXXdb8XkCAtsTCBpJJbsLKPPGfi2PWCTU", - "tags": Object { + "tags": { "did": "did:peer:1zQmXYj3nNwsF37WXXdb8XkCAtsTCBpJJbsLKPPGfi2PWCTU", "legacyUnqualifiedDid": "YUH4t3KMkEJiXgmqsncrY9", "method": "peer", "methodSpecificIdentifier": "1zQmXYj3nNwsF37WXXdb8XkCAtsTCBpJJbsLKPPGfi2PWCTU", - "recipientKeyFingerprints": Array [ + "recipientKeyFingerprints": [ "z6Mkwc6efk75y4Y1agRx4NGpvtrpKxtKvMfgBEdQkHBwU8Xu", ], "role": "received", }, "type": "DidRecord", - "value": Object { - "_tags": Object { - "recipientKeyFingerprints": Array [ + "value": { + "_tags": { + "recipientKeyFingerprints": [ "z6Mkwc6efk75y4Y1agRx4NGpvtrpKxtKvMfgBEdQkHBwU8Xu", ], }, "createdAt": "2022-04-30T13:02:21.608Z", "did": "did:peer:1zQmXYj3nNwsF37WXXdb8XkCAtsTCBpJJbsLKPPGfi2PWCTU", - "didDocument": Object { - "@context": Array [ + "didDocument": { + "@context": [ "https://w3id.org/did/v1", ], "alsoKnownAs": undefined, "assertionMethod": undefined, - "authentication": Array [ + "authentication": [ "#J9qc5Vre", ], "capabilityDelegation": undefined, @@ -1718,20 +1751,20 @@ Object { "controller": undefined, "id": "did:peer:1zQmXYj3nNwsF37WXXdb8XkCAtsTCBpJJbsLKPPGfi2PWCTU", "keyAgreement": undefined, - "service": Array [ - Object { + "service": [ + { "id": "#IndyAgentService", "priority": 0, - "recipientKeys": Array [ + "recipientKeys": [ "J9qc5VredX3YUBbFNoJz5oJpWPcUWURKVDiUv1DvYukX", ], - "routingKeys": Array [], + "routingKeys": [], "serviceEndpoint": "rxjs:faber", "type": "IndyAgent", }, ], - "verificationMethod": Array [ - Object { + "verificationMethod": [ + { "blockchainAccountId": undefined, "controller": "#id", "ethereumAddress": undefined, @@ -1747,43 +1780,44 @@ Object { ], }, "id": "did:peer:1zQmXYj3nNwsF37WXXdb8XkCAtsTCBpJJbsLKPPGfi2PWCTU", - "metadata": Object { - "_internal/legacyDid": Object { - "didDocumentString": "{\\"@context\\":\\"https://w3id.org/did/v1\\",\\"publicKey\\":[{\\"id\\":\\"YUH4t3KMkEJiXgmqsncrY9#1\\",\\"controller\\":\\"YUH4t3KMkEJiXgmqsncrY9\\",\\"type\\":\\"Ed25519VerificationKey2018\\",\\"publicKeyBase58\\":\\"J9qc5VredX3YUBbFNoJz5oJpWPcUWURKVDiUv1DvYukX\\"}],\\"service\\":[{\\"id\\":\\"YUH4t3KMkEJiXgmqsncrY9#IndyAgentService\\",\\"serviceEndpoint\\":\\"rxjs:faber\\",\\"type\\":\\"IndyAgent\\",\\"priority\\":0,\\"recipientKeys\\":[\\"J9qc5VredX3YUBbFNoJz5oJpWPcUWURKVDiUv1DvYukX\\"],\\"routingKeys\\":[]}],\\"authentication\\":[{\\"publicKey\\":\\"YUH4t3KMkEJiXgmqsncrY9#1\\",\\"type\\":\\"Ed25519SignatureAuthentication2018\\"}],\\"id\\":\\"YUH4t3KMkEJiXgmqsncrY9\\"}", + "metadata": { + "_internal/legacyDid": { + "didDocumentString": "{"@context":"https://w3id.org/did/v1","publicKey":[{"id":"YUH4t3KMkEJiXgmqsncrY9#1","controller":"YUH4t3KMkEJiXgmqsncrY9","type":"Ed25519VerificationKey2018","publicKeyBase58":"J9qc5VredX3YUBbFNoJz5oJpWPcUWURKVDiUv1DvYukX"}],"service":[{"id":"YUH4t3KMkEJiXgmqsncrY9#IndyAgentService","serviceEndpoint":"rxjs:faber","type":"IndyAgent","priority":0,"recipientKeys":["J9qc5VredX3YUBbFNoJz5oJpWPcUWURKVDiUv1DvYukX"],"routingKeys":[]}],"authentication":[{"publicKey":"YUH4t3KMkEJiXgmqsncrY9#1","type":"Ed25519SignatureAuthentication2018"}],"id":"YUH4t3KMkEJiXgmqsncrY9"}", "unqualifiedDid": "YUH4t3KMkEJiXgmqsncrY9", }, }, "role": "received", + "updatedAt": "2022-01-21T22:50:20.522Z", }, }, - "did:peer:1zQmZ2tdw35SaLncSHhf9zBv3e9QmJmLErZRSLsDdYowPHXy": Object { + "did:peer:1zQmZ2tdw35SaLncSHhf9zBv3e9QmJmLErZRSLsDdYowPHXy": { "id": "did:peer:1zQmZ2tdw35SaLncSHhf9zBv3e9QmJmLErZRSLsDdYowPHXy", - "tags": Object { + "tags": { "did": "did:peer:1zQmZ2tdw35SaLncSHhf9zBv3e9QmJmLErZRSLsDdYowPHXy", "legacyUnqualifiedDid": "WSwJQMBHGZbQsq9LDBTWjX", "method": "peer", "methodSpecificIdentifier": "1zQmZ2tdw35SaLncSHhf9zBv3e9QmJmLErZRSLsDdYowPHXy", - "recipientKeyFingerprints": Array [ + "recipientKeyFingerprints": [ "z6MkvW9GxjjUdL9qpaj2qQW6YBhCjZY7Zkzrks3cgpJaRjxR", ], "role": "created", }, "type": "DidRecord", - "value": Object { - "_tags": Object { - "recipientKeyFingerprints": Array [ + "value": { + "_tags": { + "recipientKeyFingerprints": [ "z6MkvW9GxjjUdL9qpaj2qQW6YBhCjZY7Zkzrks3cgpJaRjxR", ], }, "createdAt": "2022-04-20T13:02:21.646Z", "did": "did:peer:1zQmZ2tdw35SaLncSHhf9zBv3e9QmJmLErZRSLsDdYowPHXy", - "didDocument": Object { - "@context": Array [ + "didDocument": { + "@context": [ "https://w3id.org/did/v1", ], "alsoKnownAs": undefined, "assertionMethod": undefined, - "authentication": Array [ + "authentication": [ "#H3tENVV3", ], "capabilityDelegation": undefined, @@ -1791,20 +1825,20 @@ Object { "controller": undefined, "id": "did:peer:1zQmZ2tdw35SaLncSHhf9zBv3e9QmJmLErZRSLsDdYowPHXy", "keyAgreement": undefined, - "service": Array [ - Object { + "service": [ + { "id": "#IndyAgentService", "priority": 0, - "recipientKeys": Array [ + "recipientKeys": [ "H3tENVV3HnfNi5tL9qYFh69CuzGG9skW4r8grYLZWXB3", ], - "routingKeys": Array [], + "routingKeys": [], "serviceEndpoint": "rxjs:alice", "type": "IndyAgent", }, ], - "verificationMethod": Array [ - Object { + "verificationMethod": [ + { "blockchainAccountId": undefined, "controller": "#id", "ethereumAddress": undefined, @@ -1820,43 +1854,44 @@ Object { ], }, "id": "did:peer:1zQmZ2tdw35SaLncSHhf9zBv3e9QmJmLErZRSLsDdYowPHXy", - "metadata": Object { - "_internal/legacyDid": Object { - "didDocumentString": "{\\"@context\\":\\"https://w3id.org/did/v1\\",\\"publicKey\\":[{\\"id\\":\\"WSwJQMBHGZbQsq9LDBTWjX#1\\",\\"controller\\":\\"WSwJQMBHGZbQsq9LDBTWjX\\",\\"type\\":\\"Ed25519VerificationKey2018\\",\\"publicKeyBase58\\":\\"H3tENVV3HnfNi5tL9qYFh69CuzGG9skW4r8grYLZWXB3\\"}],\\"service\\":[{\\"id\\":\\"WSwJQMBHGZbQsq9LDBTWjX#IndyAgentService\\",\\"serviceEndpoint\\":\\"rxjs:alice\\",\\"type\\":\\"IndyAgent\\",\\"priority\\":0,\\"recipientKeys\\":[\\"H3tENVV3HnfNi5tL9qYFh69CuzGG9skW4r8grYLZWXB3\\"],\\"routingKeys\\":[]}],\\"authentication\\":[{\\"publicKey\\":\\"WSwJQMBHGZbQsq9LDBTWjX#1\\",\\"type\\":\\"Ed25519SignatureAuthentication2018\\"}],\\"id\\":\\"WSwJQMBHGZbQsq9LDBTWjX\\"}", + "metadata": { + "_internal/legacyDid": { + "didDocumentString": "{"@context":"https://w3id.org/did/v1","publicKey":[{"id":"WSwJQMBHGZbQsq9LDBTWjX#1","controller":"WSwJQMBHGZbQsq9LDBTWjX","type":"Ed25519VerificationKey2018","publicKeyBase58":"H3tENVV3HnfNi5tL9qYFh69CuzGG9skW4r8grYLZWXB3"}],"service":[{"id":"WSwJQMBHGZbQsq9LDBTWjX#IndyAgentService","serviceEndpoint":"rxjs:alice","type":"IndyAgent","priority":0,"recipientKeys":["H3tENVV3HnfNi5tL9qYFh69CuzGG9skW4r8grYLZWXB3"],"routingKeys":[]}],"authentication":[{"publicKey":"WSwJQMBHGZbQsq9LDBTWjX#1","type":"Ed25519SignatureAuthentication2018"}],"id":"WSwJQMBHGZbQsq9LDBTWjX"}", "unqualifiedDid": "WSwJQMBHGZbQsq9LDBTWjX", }, }, "role": "created", + "updatedAt": "2022-01-21T22:50:20.522Z", }, }, - "did:peer:1zQma8LpnJ22GxQdyASV5jP6psacAGtJ6ytk4pVayYp4erRf": Object { + "did:peer:1zQma8LpnJ22GxQdyASV5jP6psacAGtJ6ytk4pVayYp4erRf": { "id": "did:peer:1zQma8LpnJ22GxQdyASV5jP6psacAGtJ6ytk4pVayYp4erRf", - "tags": Object { + "tags": { "did": "did:peer:1zQma8LpnJ22GxQdyASV5jP6psacAGtJ6ytk4pVayYp4erRf", "legacyUnqualifiedDid": "TMnQftvJJJwoYogYkQgVjg", "method": "peer", "methodSpecificIdentifier": "1zQma8LpnJ22GxQdyASV5jP6psacAGtJ6ytk4pVayYp4erRf", - "recipientKeyFingerprints": Array [ + "recipientKeyFingerprints": [ "z6MktpVtPC5j91aycGPT5pceiu8EGKDzM5RLwqAZBuCgxw4V", ], "role": "created", }, "type": "DidRecord", - "value": Object { - "_tags": Object { - "recipientKeyFingerprints": Array [ + "value": { + "_tags": { + "recipientKeyFingerprints": [ "z6MktpVtPC5j91aycGPT5pceiu8EGKDzM5RLwqAZBuCgxw4V", ], }, "createdAt": "2022-04-30T13:02:21.641Z", "did": "did:peer:1zQma8LpnJ22GxQdyASV5jP6psacAGtJ6ytk4pVayYp4erRf", - "didDocument": Object { - "@context": Array [ + "didDocument": { + "@context": [ "https://w3id.org/did/v1", ], "alsoKnownAs": undefined, "assertionMethod": undefined, - "authentication": Array [ + "authentication": [ "#FNEqnwqH", ], "capabilityDelegation": undefined, @@ -1864,20 +1899,20 @@ Object { "controller": undefined, "id": "did:peer:1zQma8LpnJ22GxQdyASV5jP6psacAGtJ6ytk4pVayYp4erRf", "keyAgreement": undefined, - "service": Array [ - Object { + "service": [ + { "id": "#IndyAgentService", "priority": 0, - "recipientKeys": Array [ + "recipientKeys": [ "FNEqnwqHoU6WVmYkQFeosoaESjx8wCAzFpFdMdEg3iH7", ], - "routingKeys": Array [], + "routingKeys": [], "serviceEndpoint": "rxjs:alice", "type": "IndyAgent", }, ], - "verificationMethod": Array [ - Object { + "verificationMethod": [ + { "blockchainAccountId": undefined, "controller": "#id", "ethereumAddress": undefined, @@ -1893,43 +1928,44 @@ Object { ], }, "id": "did:peer:1zQma8LpnJ22GxQdyASV5jP6psacAGtJ6ytk4pVayYp4erRf", - "metadata": Object { - "_internal/legacyDid": Object { - "didDocumentString": "{\\"@context\\":\\"https://w3id.org/did/v1\\",\\"publicKey\\":[{\\"id\\":\\"TMnQftvJJJwoYogYkQgVjg#1\\",\\"controller\\":\\"TMnQftvJJJwoYogYkQgVjg\\",\\"type\\":\\"Ed25519VerificationKey2018\\",\\"publicKeyBase58\\":\\"FNEqnwqHoU6WVmYkQFeosoaESjx8wCAzFpFdMdEg3iH7\\"}],\\"service\\":[{\\"id\\":\\"TMnQftvJJJwoYogYkQgVjg#IndyAgentService\\",\\"serviceEndpoint\\":\\"rxjs:alice\\",\\"type\\":\\"IndyAgent\\",\\"priority\\":0,\\"recipientKeys\\":[\\"FNEqnwqHoU6WVmYkQFeosoaESjx8wCAzFpFdMdEg3iH7\\"],\\"routingKeys\\":[]}],\\"authentication\\":[{\\"publicKey\\":\\"TMnQftvJJJwoYogYkQgVjg#1\\",\\"type\\":\\"Ed25519SignatureAuthentication2018\\"}],\\"id\\":\\"TMnQftvJJJwoYogYkQgVjg\\"}", + "metadata": { + "_internal/legacyDid": { + "didDocumentString": "{"@context":"https://w3id.org/did/v1","publicKey":[{"id":"TMnQftvJJJwoYogYkQgVjg#1","controller":"TMnQftvJJJwoYogYkQgVjg","type":"Ed25519VerificationKey2018","publicKeyBase58":"FNEqnwqHoU6WVmYkQFeosoaESjx8wCAzFpFdMdEg3iH7"}],"service":[{"id":"TMnQftvJJJwoYogYkQgVjg#IndyAgentService","serviceEndpoint":"rxjs:alice","type":"IndyAgent","priority":0,"recipientKeys":["FNEqnwqHoU6WVmYkQFeosoaESjx8wCAzFpFdMdEg3iH7"],"routingKeys":[]}],"authentication":[{"publicKey":"TMnQftvJJJwoYogYkQgVjg#1","type":"Ed25519SignatureAuthentication2018"}],"id":"TMnQftvJJJwoYogYkQgVjg"}", "unqualifiedDid": "TMnQftvJJJwoYogYkQgVjg", }, }, "role": "created", + "updatedAt": "2022-01-21T22:50:20.522Z", }, }, - "did:peer:1zQmadmBfngrYSWhYYxZ24fpW29iwhKhQ6CB6euLabbSK6ga": Object { + "did:peer:1zQmadmBfngrYSWhYYxZ24fpW29iwhKhQ6CB6euLabbSK6ga": { "id": "did:peer:1zQmadmBfngrYSWhYYxZ24fpW29iwhKhQ6CB6euLabbSK6ga", - "tags": Object { + "tags": { "did": "did:peer:1zQmadmBfngrYSWhYYxZ24fpW29iwhKhQ6CB6euLabbSK6ga", "legacyUnqualifiedDid": "YKc7qhYN1TckZAMUf7jgwc", "method": "peer", "methodSpecificIdentifier": "1zQmadmBfngrYSWhYYxZ24fpW29iwhKhQ6CB6euLabbSK6ga", - "recipientKeyFingerprints": Array [ + "recipientKeyFingerprints": [ "z6MkwXNXTehVH7YijDmN1PtaXaSaCniTyaVepmY1EJgS15xq", ], "role": "received", }, "type": "DidRecord", - "value": Object { - "_tags": Object { - "recipientKeyFingerprints": Array [ + "value": { + "_tags": { + "recipientKeyFingerprints": [ "z6MkwXNXTehVH7YijDmN1PtaXaSaCniTyaVepmY1EJgS15xq", ], }, "createdAt": "2022-04-30T13:02:21.646Z", "did": "did:peer:1zQmadmBfngrYSWhYYxZ24fpW29iwhKhQ6CB6euLabbSK6ga", - "didDocument": Object { - "@context": Array [ + "didDocument": { + "@context": [ "https://w3id.org/did/v1", ], "alsoKnownAs": undefined, "assertionMethod": undefined, - "authentication": Array [ + "authentication": [ "#J57UsQT3", ], "capabilityDelegation": undefined, @@ -1937,20 +1973,20 @@ Object { "controller": undefined, "id": "did:peer:1zQmadmBfngrYSWhYYxZ24fpW29iwhKhQ6CB6euLabbSK6ga", "keyAgreement": undefined, - "service": Array [ - Object { + "service": [ + { "id": "#IndyAgentService", "priority": 0, - "recipientKeys": Array [ + "recipientKeys": [ "J57UsQT3wa4FcivfKpvjgUtaPDScZhFJ8kd5Q2iR5sBT", ], - "routingKeys": Array [], + "routingKeys": [], "serviceEndpoint": "rxjs:faber", "type": "IndyAgent", }, ], - "verificationMethod": Array [ - Object { + "verificationMethod": [ + { "blockchainAccountId": undefined, "controller": "#id", "ethereumAddress": undefined, @@ -1966,43 +2002,44 @@ Object { ], }, "id": "did:peer:1zQmadmBfngrYSWhYYxZ24fpW29iwhKhQ6CB6euLabbSK6ga", - "metadata": Object { - "_internal/legacyDid": Object { - "didDocumentString": "{\\"@context\\":\\"https://w3id.org/did/v1\\",\\"publicKey\\":[{\\"id\\":\\"YKc7qhYN1TckZAMUf7jgwc#1\\",\\"controller\\":\\"YKc7qhYN1TckZAMUf7jgwc\\",\\"type\\":\\"Ed25519VerificationKey2018\\",\\"publicKeyBase58\\":\\"J57UsQT3wa4FcivfKpvjgUtaPDScZhFJ8kd5Q2iR5sBT\\"}],\\"service\\":[{\\"id\\":\\"YKc7qhYN1TckZAMUf7jgwc#IndyAgentService\\",\\"serviceEndpoint\\":\\"rxjs:faber\\",\\"type\\":\\"IndyAgent\\",\\"priority\\":0,\\"recipientKeys\\":[\\"J57UsQT3wa4FcivfKpvjgUtaPDScZhFJ8kd5Q2iR5sBT\\"],\\"routingKeys\\":[]}],\\"authentication\\":[{\\"publicKey\\":\\"YKc7qhYN1TckZAMUf7jgwc#1\\",\\"type\\":\\"Ed25519SignatureAuthentication2018\\"}],\\"id\\":\\"YKc7qhYN1TckZAMUf7jgwc\\"}", + "metadata": { + "_internal/legacyDid": { + "didDocumentString": "{"@context":"https://w3id.org/did/v1","publicKey":[{"id":"YKc7qhYN1TckZAMUf7jgwc#1","controller":"YKc7qhYN1TckZAMUf7jgwc","type":"Ed25519VerificationKey2018","publicKeyBase58":"J57UsQT3wa4FcivfKpvjgUtaPDScZhFJ8kd5Q2iR5sBT"}],"service":[{"id":"YKc7qhYN1TckZAMUf7jgwc#IndyAgentService","serviceEndpoint":"rxjs:faber","type":"IndyAgent","priority":0,"recipientKeys":["J57UsQT3wa4FcivfKpvjgUtaPDScZhFJ8kd5Q2iR5sBT"],"routingKeys":[]}],"authentication":[{"publicKey":"YKc7qhYN1TckZAMUf7jgwc#1","type":"Ed25519SignatureAuthentication2018"}],"id":"YKc7qhYN1TckZAMUf7jgwc"}", "unqualifiedDid": "YKc7qhYN1TckZAMUf7jgwc", }, }, "role": "received", + "updatedAt": "2022-01-21T22:50:20.522Z", }, }, - "did:peer:1zQmc3BZoTinpVaG3oZ4PmRVN4JMdNZGCmPkS6smmTNLnvEZ": Object { + "did:peer:1zQmc3BZoTinpVaG3oZ4PmRVN4JMdNZGCmPkS6smmTNLnvEZ": { "id": "did:peer:1zQmc3BZoTinpVaG3oZ4PmRVN4JMdNZGCmPkS6smmTNLnvEZ", - "tags": Object { + "tags": { "did": "did:peer:1zQmc3BZoTinpVaG3oZ4PmRVN4JMdNZGCmPkS6smmTNLnvEZ", "legacyUnqualifiedDid": "Ak15GBhMYpdS8XX3QDMv31", "method": "peer", "methodSpecificIdentifier": "1zQmc3BZoTinpVaG3oZ4PmRVN4JMdNZGCmPkS6smmTNLnvEZ", - "recipientKeyFingerprints": Array [ + "recipientKeyFingerprints": [ "z6MkjmCrDWJVf8H2pCHcu11UDs4jb6FVu8nn5yQW24rrgez6", ], "role": "received", }, "type": "DidRecord", - "value": Object { - "_tags": Object { - "recipientKeyFingerprints": Array [ + "value": { + "_tags": { + "recipientKeyFingerprints": [ "z6MkjmCrDWJVf8H2pCHcu11UDs4jb6FVu8nn5yQW24rrgez6", ], }, "createdAt": "2022-04-30T13:02:21.628Z", "did": "did:peer:1zQmc3BZoTinpVaG3oZ4PmRVN4JMdNZGCmPkS6smmTNLnvEZ", - "didDocument": Object { - "@context": Array [ + "didDocument": { + "@context": [ "https://w3id.org/did/v1", ], "alsoKnownAs": undefined, "assertionMethod": undefined, - "authentication": Array [ + "authentication": [ "#6JwodG44", ], "capabilityDelegation": undefined, @@ -2010,20 +2047,20 @@ Object { "controller": undefined, "id": "did:peer:1zQmc3BZoTinpVaG3oZ4PmRVN4JMdNZGCmPkS6smmTNLnvEZ", "keyAgreement": undefined, - "service": Array [ - Object { + "service": [ + { "id": "#IndyAgentService", "priority": 0, - "recipientKeys": Array [ + "recipientKeys": [ "6JwodG44KanZhhSvDS3dNmWjmWyeVFYRPxVaBntqmSCi", ], - "routingKeys": Array [], + "routingKeys": [], "serviceEndpoint": "rxjs:faber", "type": "IndyAgent", }, ], - "verificationMethod": Array [ - Object { + "verificationMethod": [ + { "blockchainAccountId": undefined, "controller": "#id", "ethereumAddress": undefined, @@ -2039,43 +2076,44 @@ Object { ], }, "id": "did:peer:1zQmc3BZoTinpVaG3oZ4PmRVN4JMdNZGCmPkS6smmTNLnvEZ", - "metadata": Object { - "_internal/legacyDid": Object { - "didDocumentString": "{\\"@context\\":\\"https://w3id.org/did/v1\\",\\"publicKey\\":[{\\"id\\":\\"Ak15GBhMYpdS8XX3QDMv31#1\\",\\"controller\\":\\"Ak15GBhMYpdS8XX3QDMv31\\",\\"type\\":\\"Ed25519VerificationKey2018\\",\\"publicKeyBase58\\":\\"6JwodG44KanZhhSvDS3dNmWjmWyeVFYRPxVaBntqmSCi\\"}],\\"service\\":[{\\"id\\":\\"Ak15GBhMYpdS8XX3QDMv31#IndyAgentService\\",\\"serviceEndpoint\\":\\"rxjs:faber\\",\\"type\\":\\"IndyAgent\\",\\"priority\\":0,\\"recipientKeys\\":[\\"6JwodG44KanZhhSvDS3dNmWjmWyeVFYRPxVaBntqmSCi\\"],\\"routingKeys\\":[]}],\\"authentication\\":[{\\"publicKey\\":\\"Ak15GBhMYpdS8XX3QDMv31#1\\",\\"type\\":\\"Ed25519SignatureAuthentication2018\\"}],\\"id\\":\\"Ak15GBhMYpdS8XX3QDMv31\\"}", + "metadata": { + "_internal/legacyDid": { + "didDocumentString": "{"@context":"https://w3id.org/did/v1","publicKey":[{"id":"Ak15GBhMYpdS8XX3QDMv31#1","controller":"Ak15GBhMYpdS8XX3QDMv31","type":"Ed25519VerificationKey2018","publicKeyBase58":"6JwodG44KanZhhSvDS3dNmWjmWyeVFYRPxVaBntqmSCi"}],"service":[{"id":"Ak15GBhMYpdS8XX3QDMv31#IndyAgentService","serviceEndpoint":"rxjs:faber","type":"IndyAgent","priority":0,"recipientKeys":["6JwodG44KanZhhSvDS3dNmWjmWyeVFYRPxVaBntqmSCi"],"routingKeys":[]}],"authentication":[{"publicKey":"Ak15GBhMYpdS8XX3QDMv31#1","type":"Ed25519SignatureAuthentication2018"}],"id":"Ak15GBhMYpdS8XX3QDMv31"}", "unqualifiedDid": "Ak15GBhMYpdS8XX3QDMv31", }, }, "role": "received", + "updatedAt": "2022-01-21T22:50:20.522Z", }, }, - "did:peer:1zQmcXZepLE55VGCMELEFjMd4nKrzp3GGyRR3r3MYermagui": Object { + "did:peer:1zQmcXZepLE55VGCMELEFjMd4nKrzp3GGyRR3r3MYermagui": { "id": "did:peer:1zQmcXZepLE55VGCMELEFjMd4nKrzp3GGyRR3r3MYermagui", - "tags": Object { + "tags": { "did": "did:peer:1zQmcXZepLE55VGCMELEFjMd4nKrzp3GGyRR3r3MYermagui", "legacyUnqualifiedDid": "9jTqUnV4k5ucxbyxumAaV7", "method": "peer", "methodSpecificIdentifier": "1zQmcXZepLE55VGCMELEFjMd4nKrzp3GGyRR3r3MYermagui", - "recipientKeyFingerprints": Array [ + "recipientKeyFingerprints": [ "z6MkjDJL4X7YGoH6gjamhZR2NzowPZqtJfX5kPuNuWiVdjMr", ], "role": "received", }, "type": "DidRecord", - "value": Object { - "_tags": Object { - "recipientKeyFingerprints": Array [ + "value": { + "_tags": { + "recipientKeyFingerprints": [ "z6MkjDJL4X7YGoH6gjamhZR2NzowPZqtJfX5kPuNuWiVdjMr", ], }, "createdAt": "2022-04-30T13:02:21.641Z", "did": "did:peer:1zQmcXZepLE55VGCMELEFjMd4nKrzp3GGyRR3r3MYermagui", - "didDocument": Object { - "@context": Array [ + "didDocument": { + "@context": [ "https://w3id.org/did/v1", ], "alsoKnownAs": undefined, "assertionMethod": undefined, - "authentication": Array [ + "authentication": [ "#5m3HUGs6", ], "capabilityDelegation": undefined, @@ -2083,20 +2121,20 @@ Object { "controller": undefined, "id": "did:peer:1zQmcXZepLE55VGCMELEFjMd4nKrzp3GGyRR3r3MYermagui", "keyAgreement": undefined, - "service": Array [ - Object { + "service": [ + { "id": "#IndyAgentService", "priority": 0, - "recipientKeys": Array [ + "recipientKeys": [ "5m3HUGs6wFndaEk51zTBXuFwZza2tnGj4NzT5EkUiWaU", ], - "routingKeys": Array [], + "routingKeys": [], "serviceEndpoint": "rxjs:faber", "type": "IndyAgent", }, ], - "verificationMethod": Array [ - Object { + "verificationMethod": [ + { "blockchainAccountId": undefined, "controller": "#id", "ethereumAddress": undefined, @@ -2112,43 +2150,44 @@ Object { ], }, "id": "did:peer:1zQmcXZepLE55VGCMELEFjMd4nKrzp3GGyRR3r3MYermagui", - "metadata": Object { - "_internal/legacyDid": Object { - "didDocumentString": "{\\"@context\\":\\"https://w3id.org/did/v1\\",\\"publicKey\\":[{\\"id\\":\\"9jTqUnV4k5ucxbyxumAaV7#1\\",\\"controller\\":\\"9jTqUnV4k5ucxbyxumAaV7\\",\\"type\\":\\"Ed25519VerificationKey2018\\",\\"publicKeyBase58\\":\\"5m3HUGs6wFndaEk51zTBXuFwZza2tnGj4NzT5EkUiWaU\\"}],\\"service\\":[{\\"id\\":\\"9jTqUnV4k5ucxbyxumAaV7#IndyAgentService\\",\\"serviceEndpoint\\":\\"rxjs:faber\\",\\"type\\":\\"IndyAgent\\",\\"priority\\":0,\\"recipientKeys\\":[\\"5m3HUGs6wFndaEk51zTBXuFwZza2tnGj4NzT5EkUiWaU\\"],\\"routingKeys\\":[]}],\\"authentication\\":[{\\"publicKey\\":\\"9jTqUnV4k5ucxbyxumAaV7#1\\",\\"type\\":\\"Ed25519SignatureAuthentication2018\\"}],\\"id\\":\\"9jTqUnV4k5ucxbyxumAaV7\\"}", + "metadata": { + "_internal/legacyDid": { + "didDocumentString": "{"@context":"https://w3id.org/did/v1","publicKey":[{"id":"9jTqUnV4k5ucxbyxumAaV7#1","controller":"9jTqUnV4k5ucxbyxumAaV7","type":"Ed25519VerificationKey2018","publicKeyBase58":"5m3HUGs6wFndaEk51zTBXuFwZza2tnGj4NzT5EkUiWaU"}],"service":[{"id":"9jTqUnV4k5ucxbyxumAaV7#IndyAgentService","serviceEndpoint":"rxjs:faber","type":"IndyAgent","priority":0,"recipientKeys":["5m3HUGs6wFndaEk51zTBXuFwZza2tnGj4NzT5EkUiWaU"],"routingKeys":[]}],"authentication":[{"publicKey":"9jTqUnV4k5ucxbyxumAaV7#1","type":"Ed25519SignatureAuthentication2018"}],"id":"9jTqUnV4k5ucxbyxumAaV7"}", "unqualifiedDid": "9jTqUnV4k5ucxbyxumAaV7", }, }, "role": "received", + "updatedAt": "2022-01-21T22:50:20.522Z", }, }, - "did:peer:1zQmduuYkxRKJuVyvDqttdd9eDfBwDnF1DAU5FFQo4whx7Uw": Object { + "did:peer:1zQmduuYkxRKJuVyvDqttdd9eDfBwDnF1DAU5FFQo4whx7Uw": { "id": "did:peer:1zQmduuYkxRKJuVyvDqttdd9eDfBwDnF1DAU5FFQo4whx7Uw", - "tags": Object { + "tags": { "did": "did:peer:1zQmduuYkxRKJuVyvDqttdd9eDfBwDnF1DAU5FFQo4whx7Uw", "legacyUnqualifiedDid": "WewvCdyBi4HL8ogyGviYVS", "method": "peer", "methodSpecificIdentifier": "1zQmduuYkxRKJuVyvDqttdd9eDfBwDnF1DAU5FFQo4whx7Uw", - "recipientKeyFingerprints": Array [ + "recipientKeyFingerprints": [ "z6MkvcgxQSsX5WA8vcBokLZ46znnhRBH6aKAGYnonEUfUnQV", ], "role": "created", }, "type": "DidRecord", - "value": Object { - "_tags": Object { - "recipientKeyFingerprints": Array [ + "value": { + "_tags": { + "recipientKeyFingerprints": [ "z6MkvcgxQSsX5WA8vcBokLZ46znnhRBH6aKAGYnonEUfUnQV", ], }, "createdAt": "2022-04-30T13:02:21.635Z", "did": "did:peer:1zQmduuYkxRKJuVyvDqttdd9eDfBwDnF1DAU5FFQo4whx7Uw", - "didDocument": Object { - "@context": Array [ + "didDocument": { + "@context": [ "https://w3id.org/did/v1", ], "alsoKnownAs": undefined, "assertionMethod": undefined, - "authentication": Array [ + "authentication": [ "#HARupCd5", ], "capabilityDelegation": undefined, @@ -2156,20 +2195,20 @@ Object { "controller": undefined, "id": "did:peer:1zQmduuYkxRKJuVyvDqttdd9eDfBwDnF1DAU5FFQo4whx7Uw", "keyAgreement": undefined, - "service": Array [ - Object { + "service": [ + { "id": "#IndyAgentService", "priority": 0, - "recipientKeys": Array [ + "recipientKeys": [ "HARupCd5jxffp7M74mbDFuEnsquRgh4oaXsswxWeZZd7", ], - "routingKeys": Array [], + "routingKeys": [], "serviceEndpoint": "rxjs:alice", "type": "IndyAgent", }, ], - "verificationMethod": Array [ - Object { + "verificationMethod": [ + { "blockchainAccountId": undefined, "controller": "#id", "ethereumAddress": undefined, @@ -2185,43 +2224,44 @@ Object { ], }, "id": "did:peer:1zQmduuYkxRKJuVyvDqttdd9eDfBwDnF1DAU5FFQo4whx7Uw", - "metadata": Object { - "_internal/legacyDid": Object { - "didDocumentString": "{\\"@context\\":\\"https://w3id.org/did/v1\\",\\"publicKey\\":[{\\"id\\":\\"WewvCdyBi4HL8ogyGviYVS#1\\",\\"controller\\":\\"WewvCdyBi4HL8ogyGviYVS\\",\\"type\\":\\"Ed25519VerificationKey2018\\",\\"publicKeyBase58\\":\\"HARupCd5jxffp7M74mbDFuEnsquRgh4oaXsswxWeZZd7\\"}],\\"service\\":[{\\"id\\":\\"WewvCdyBi4HL8ogyGviYVS#IndyAgentService\\",\\"serviceEndpoint\\":\\"rxjs:alice\\",\\"type\\":\\"IndyAgent\\",\\"priority\\":0,\\"recipientKeys\\":[\\"HARupCd5jxffp7M74mbDFuEnsquRgh4oaXsswxWeZZd7\\"],\\"routingKeys\\":[]}],\\"authentication\\":[{\\"publicKey\\":\\"WewvCdyBi4HL8ogyGviYVS#1\\",\\"type\\":\\"Ed25519SignatureAuthentication2018\\"}],\\"id\\":\\"WewvCdyBi4HL8ogyGviYVS\\"}", + "metadata": { + "_internal/legacyDid": { + "didDocumentString": "{"@context":"https://w3id.org/did/v1","publicKey":[{"id":"WewvCdyBi4HL8ogyGviYVS#1","controller":"WewvCdyBi4HL8ogyGviYVS","type":"Ed25519VerificationKey2018","publicKeyBase58":"HARupCd5jxffp7M74mbDFuEnsquRgh4oaXsswxWeZZd7"}],"service":[{"id":"WewvCdyBi4HL8ogyGviYVS#IndyAgentService","serviceEndpoint":"rxjs:alice","type":"IndyAgent","priority":0,"recipientKeys":["HARupCd5jxffp7M74mbDFuEnsquRgh4oaXsswxWeZZd7"],"routingKeys":[]}],"authentication":[{"publicKey":"WewvCdyBi4HL8ogyGviYVS#1","type":"Ed25519SignatureAuthentication2018"}],"id":"WewvCdyBi4HL8ogyGviYVS"}", "unqualifiedDid": "WewvCdyBi4HL8ogyGviYVS", }, }, "role": "created", + "updatedAt": "2022-01-21T22:50:20.522Z", }, }, - "did:peer:1zQmeHpGaZ48DnAP2k3KntXB1vmd8MgLEdcb4EQzqWJDHcbX": Object { + "did:peer:1zQmeHpGaZ48DnAP2k3KntXB1vmd8MgLEdcb4EQzqWJDHcbX": { "id": "did:peer:1zQmeHpGaZ48DnAP2k3KntXB1vmd8MgLEdcb4EQzqWJDHcbX", - "tags": Object { + "tags": { "did": "did:peer:1zQmeHpGaZ48DnAP2k3KntXB1vmd8MgLEdcb4EQzqWJDHcbX", "legacyUnqualifiedDid": "3KAjJWF5NjiDTUm6JpPBQD", "method": "peer", "methodSpecificIdentifier": "1zQmeHpGaZ48DnAP2k3KntXB1vmd8MgLEdcb4EQzqWJDHcbX", - "recipientKeyFingerprints": Array [ + "recipientKeyFingerprints": [ "z6MkfiPMPxCQeSDZGMkCvm1Y2rBoPsmw4ZHMv71jXtcWRRiM", ], "role": "received", }, "type": "DidRecord", - "value": Object { - "_tags": Object { - "recipientKeyFingerprints": Array [ + "value": { + "_tags": { + "recipientKeyFingerprints": [ "z6MkfiPMPxCQeSDZGMkCvm1Y2rBoPsmw4ZHMv71jXtcWRRiM", ], }, "createdAt": "2022-04-30T13:02:21.577Z", "did": "did:peer:1zQmeHpGaZ48DnAP2k3KntXB1vmd8MgLEdcb4EQzqWJDHcbX", - "didDocument": Object { - "@context": Array [ + "didDocument": { + "@context": [ "https://w3id.org/did/v1", ], "alsoKnownAs": undefined, "assertionMethod": undefined, - "authentication": Array [ + "authentication": [ "#2G8Johwy", ], "capabilityDelegation": undefined, @@ -2229,20 +2269,20 @@ Object { "controller": undefined, "id": "did:peer:1zQmeHpGaZ48DnAP2k3KntXB1vmd8MgLEdcb4EQzqWJDHcbX", "keyAgreement": undefined, - "service": Array [ - Object { + "service": [ + { "id": "#IndyAgentService", "priority": 0, - "recipientKeys": Array [ + "recipientKeys": [ "2G8JohwyJtj69ruWFC3hBkdoaJW5eg31E66ohceVWCvy", ], - "routingKeys": Array [], + "routingKeys": [], "serviceEndpoint": "rxjs:faber", "type": "IndyAgent", }, ], - "verificationMethod": Array [ - Object { + "verificationMethod": [ + { "blockchainAccountId": undefined, "controller": "#id", "ethereumAddress": undefined, @@ -2258,43 +2298,44 @@ Object { ], }, "id": "did:peer:1zQmeHpGaZ48DnAP2k3KntXB1vmd8MgLEdcb4EQzqWJDHcbX", - "metadata": Object { - "_internal/legacyDid": Object { - "didDocumentString": "{\\"@context\\":\\"https://w3id.org/did/v1\\",\\"publicKey\\":[{\\"id\\":\\"3KAjJWF5NjiDTUm6JpPBQD#1\\",\\"controller\\":\\"3KAjJWF5NjiDTUm6JpPBQD\\",\\"type\\":\\"Ed25519VerificationKey2018\\",\\"publicKeyBase58\\":\\"2G8JohwyJtj69ruWFC3hBkdoaJW5eg31E66ohceVWCvy\\"}],\\"service\\":[{\\"id\\":\\"3KAjJWF5NjiDTUm6JpPBQD#IndyAgentService\\",\\"serviceEndpoint\\":\\"rxjs:faber\\",\\"type\\":\\"IndyAgent\\",\\"priority\\":0,\\"recipientKeys\\":[\\"2G8JohwyJtj69ruWFC3hBkdoaJW5eg31E66ohceVWCvy\\"],\\"routingKeys\\":[]}],\\"authentication\\":[{\\"publicKey\\":\\"3KAjJWF5NjiDTUm6JpPBQD#1\\",\\"type\\":\\"Ed25519SignatureAuthentication2018\\"}],\\"id\\":\\"3KAjJWF5NjiDTUm6JpPBQD\\"}", + "metadata": { + "_internal/legacyDid": { + "didDocumentString": "{"@context":"https://w3id.org/did/v1","publicKey":[{"id":"3KAjJWF5NjiDTUm6JpPBQD#1","controller":"3KAjJWF5NjiDTUm6JpPBQD","type":"Ed25519VerificationKey2018","publicKeyBase58":"2G8JohwyJtj69ruWFC3hBkdoaJW5eg31E66ohceVWCvy"}],"service":[{"id":"3KAjJWF5NjiDTUm6JpPBQD#IndyAgentService","serviceEndpoint":"rxjs:faber","type":"IndyAgent","priority":0,"recipientKeys":["2G8JohwyJtj69ruWFC3hBkdoaJW5eg31E66ohceVWCvy"],"routingKeys":[]}],"authentication":[{"publicKey":"3KAjJWF5NjiDTUm6JpPBQD#1","type":"Ed25519SignatureAuthentication2018"}],"id":"3KAjJWF5NjiDTUm6JpPBQD"}", "unqualifiedDid": "3KAjJWF5NjiDTUm6JpPBQD", }, }, "role": "received", + "updatedAt": "2022-01-21T22:50:20.522Z", }, }, - "did:peer:1zQmfDAtfDZcK4trJBsvVTXrBx9uaLCHSUZH9X2LFaAd3JKv": Object { + "did:peer:1zQmfDAtfDZcK4trJBsvVTXrBx9uaLCHSUZH9X2LFaAd3JKv": { "id": "did:peer:1zQmfDAtfDZcK4trJBsvVTXrBx9uaLCHSUZH9X2LFaAd3JKv", - "tags": Object { + "tags": { "did": "did:peer:1zQmfDAtfDZcK4trJBsvVTXrBx9uaLCHSUZH9X2LFaAd3JKv", "legacyUnqualifiedDid": "Ud6AWCk6WrwfYKZUw5tJmt", "method": "peer", "methodSpecificIdentifier": "1zQmfDAtfDZcK4trJBsvVTXrBx9uaLCHSUZH9X2LFaAd3JKv", - "recipientKeyFingerprints": Array [ + "recipientKeyFingerprints": [ "z6MkuWTEmH1mUo6W96zSWyH612hFHowRzNEscPYBL2CCMyC2", ], "role": "created", }, "type": "DidRecord", - "value": Object { - "_tags": Object { - "recipientKeyFingerprints": Array [ + "value": { + "_tags": { + "recipientKeyFingerprints": [ "z6MkuWTEmH1mUo6W96zSWyH612hFHowRzNEscPYBL2CCMyC2", ], }, "createdAt": "2022-04-30T13:02:21.653Z", "did": "did:peer:1zQmfDAtfDZcK4trJBsvVTXrBx9uaLCHSUZH9X2LFaAd3JKv", - "didDocument": Object { - "@context": Array [ + "didDocument": { + "@context": [ "https://w3id.org/did/v1", ], "alsoKnownAs": undefined, "assertionMethod": undefined, - "authentication": Array [ + "authentication": [ "#G4CCB2mL", ], "capabilityDelegation": undefined, @@ -2302,20 +2343,20 @@ Object { "controller": undefined, "id": "did:peer:1zQmfDAtfDZcK4trJBsvVTXrBx9uaLCHSUZH9X2LFaAd3JKv", "keyAgreement": undefined, - "service": Array [ - Object { + "service": [ + { "id": "#IndyAgentService", "priority": 0, - "recipientKeys": Array [ + "recipientKeys": [ "G4CCB2mL9Fc32c9jqQKF9w9FUEfaaUzWvNdFVkEBSkQe", ], - "routingKeys": Array [], + "routingKeys": [], "serviceEndpoint": "rxjs:alice", "type": "IndyAgent", }, ], - "verificationMethod": Array [ - Object { + "verificationMethod": [ + { "blockchainAccountId": undefined, "controller": "#id", "ethereumAddress": undefined, @@ -2331,19 +2372,20 @@ Object { ], }, "id": "did:peer:1zQmfDAtfDZcK4trJBsvVTXrBx9uaLCHSUZH9X2LFaAd3JKv", - "metadata": Object { - "_internal/legacyDid": Object { - "didDocumentString": "{\\"@context\\":\\"https://w3id.org/did/v1\\",\\"publicKey\\":[{\\"id\\":\\"Ud6AWCk6WrwfYKZUw5tJmt#1\\",\\"controller\\":\\"Ud6AWCk6WrwfYKZUw5tJmt\\",\\"type\\":\\"Ed25519VerificationKey2018\\",\\"publicKeyBase58\\":\\"G4CCB2mL9Fc32c9jqQKF9w9FUEfaaUzWvNdFVkEBSkQe\\"}],\\"service\\":[{\\"id\\":\\"Ud6AWCk6WrwfYKZUw5tJmt#IndyAgentService\\",\\"serviceEndpoint\\":\\"rxjs:alice\\",\\"type\\":\\"IndyAgent\\",\\"priority\\":0,\\"recipientKeys\\":[\\"G4CCB2mL9Fc32c9jqQKF9w9FUEfaaUzWvNdFVkEBSkQe\\"],\\"routingKeys\\":[]}],\\"authentication\\":[{\\"publicKey\\":\\"Ud6AWCk6WrwfYKZUw5tJmt#1\\",\\"type\\":\\"Ed25519SignatureAuthentication2018\\"}],\\"id\\":\\"Ud6AWCk6WrwfYKZUw5tJmt\\"}", + "metadata": { + "_internal/legacyDid": { + "didDocumentString": "{"@context":"https://w3id.org/did/v1","publicKey":[{"id":"Ud6AWCk6WrwfYKZUw5tJmt#1","controller":"Ud6AWCk6WrwfYKZUw5tJmt","type":"Ed25519VerificationKey2018","publicKeyBase58":"G4CCB2mL9Fc32c9jqQKF9w9FUEfaaUzWvNdFVkEBSkQe"}],"service":[{"id":"Ud6AWCk6WrwfYKZUw5tJmt#IndyAgentService","serviceEndpoint":"rxjs:alice","type":"IndyAgent","priority":0,"recipientKeys":["G4CCB2mL9Fc32c9jqQKF9w9FUEfaaUzWvNdFVkEBSkQe"],"routingKeys":[]}],"authentication":[{"publicKey":"Ud6AWCk6WrwfYKZUw5tJmt#1","type":"Ed25519SignatureAuthentication2018"}],"id":"Ud6AWCk6WrwfYKZUw5tJmt"}", "unqualifiedDid": "Ud6AWCk6WrwfYKZUw5tJmt", }, }, "role": "created", + "updatedAt": "2022-01-21T22:50:20.522Z", }, }, - "e3f9bc2b-f0a1-4a2c-ab81-2f0a3488c199": Object { + "e3f9bc2b-f0a1-4a2c-ab81-2f0a3488c199": { "id": "e3f9bc2b-f0a1-4a2c-ab81-2f0a3488c199", - "tags": Object { - "connectionTypes": Array [], + "tags": { + "connectionTypes": [], "did": "did:peer:1zQma8LpnJ22GxQdyASV5jP6psacAGtJ6ytk4pVayYp4erRf", "invitationDid": "did:peer:2.SeyJzIjoicnhqczpmYWJlciIsInQiOiJkaWQtY29tbXVuaWNhdGlvbiIsInByaW9yaXR5IjowLCJyZWNpcGllbnRLZXlzIjpbImRpZDprZXk6ejZNa2pESkw0WDdZR29INmdqYW1oWlIyTnpvd1BacXRKZlg1a1B1TnVXaVZkak1yI3o2TWtqREpMNFg3WUdvSDZnamFtaFpSMk56b3dQWnF0SmZYNWtQdU51V2lWZGpNciJdLCJyIjpbXX0", "invitationKey": "5m3HUGs6wFndaEk51zTBXuFwZza2tnGj4NzT5EkUiWaU", @@ -2357,26 +2399,27 @@ Object { "verkey": "FNEqnwqHoU6WVmYkQFeosoaESjx8wCAzFpFdMdEg3iH7", }, "type": "ConnectionRecord", - "value": Object { + "value": { "autoAcceptConnection": false, - "connectionTypes": Array [], + "connectionTypes": [], "createdAt": "2022-04-30T13:02:21.641Z", "did": "did:peer:1zQma8LpnJ22GxQdyASV5jP6psacAGtJ6ytk4pVayYp4erRf", "id": "e3f9bc2b-f0a1-4a2c-ab81-2f0a3488c199", "invitationDid": "did:peer:2.SeyJzIjoicnhqczpmYWJlciIsInQiOiJkaWQtY29tbXVuaWNhdGlvbiIsInByaW9yaXR5IjowLCJyZWNpcGllbnRLZXlzIjpbImRpZDprZXk6ejZNa2pESkw0WDdZR29INmdqYW1oWlIyTnpvd1BacXRKZlg1a1B1TnVXaVZkak1yI3o2TWtqREpMNFg3WUdvSDZnamFtaFpSMk56b3dQWnF0SmZYNWtQdU51V2lWZGpNciJdLCJyIjpbXX0", - "metadata": Object {}, + "metadata": {}, "outOfBandId": "5-4e4f-41d9-94c4-f49351b811f1", "role": "requester", "state": "response-received", "theirDid": "did:peer:1zQmcXZepLE55VGCMELEFjMd4nKrzp3GGyRR3r3MYermagui", "theirLabel": "Agent: PopulateWallet2", "threadId": "daf3372c-1ee2-4246-a1f4-f62f54f7d68b", + "updatedAt": "2022-01-21T22:50:20.522Z", }, }, - "ee88e2e1-e27e-46a6-a910-f87690109e32": Object { + "ee88e2e1-e27e-46a6-a910-f87690109e32": { "id": "ee88e2e1-e27e-46a6-a910-f87690109e32", - "tags": Object { - "connectionTypes": Array [], + "tags": { + "connectionTypes": [], "did": "did:peer:1zQmduuYkxRKJuVyvDqttdd9eDfBwDnF1DAU5FFQo4whx7Uw", "invitationDid": "did:peer:2.SeyJzIjoicnhqczpmYWJlciIsInQiOiJkaWQtY29tbXVuaWNhdGlvbiIsInByaW9yaXR5IjowLCJyZWNpcGllbnRLZXlzIjpbImRpZDprZXk6ejZNa21vZDh2cDJuVVJWa3RWQzVjZVFleXIyVlV6MjZpdTJaQU5MTlZnOXBNYXdhI3o2TWttb2Q4dnAyblVSVmt0VkM1Y2VRZXlyMlZVejI2aXUyWkFOTE5WZzlwTWF3YSJdLCJyIjpbXX0", "invitationKey": "8MN6LZnM8t1HmzMNw5Sp8kUVfQkFK1nCUMRSfQBoSNAC", @@ -2389,27 +2432,28 @@ Object { "verkey": "HARupCd5jxffp7M74mbDFuEnsquRgh4oaXsswxWeZZd7", }, "type": "ConnectionRecord", - "value": Object { - "connectionTypes": Array [], + "value": { + "connectionTypes": [], "createdAt": "2022-04-30T13:02:21.635Z", "did": "did:peer:1zQmduuYkxRKJuVyvDqttdd9eDfBwDnF1DAU5FFQo4whx7Uw", "id": "ee88e2e1-e27e-46a6-a910-f87690109e32", "invitationDid": "did:peer:2.SeyJzIjoicnhqczpmYWJlciIsInQiOiJkaWQtY29tbXVuaWNhdGlvbiIsInByaW9yaXR5IjowLCJyZWNpcGllbnRLZXlzIjpbImRpZDprZXk6ejZNa21vZDh2cDJuVVJWa3RWQzVjZVFleXIyVlV6MjZpdTJaQU5MTlZnOXBNYXdhI3o2TWttb2Q4dnAyblVSVmt0VkM1Y2VRZXlyMlZVejI2aXUyWkFOTE5WZzlwTWF3YSJdLCJyIjpbXX0", - "metadata": Object {}, + "metadata": {}, "outOfBandId": "4-4e4f-41d9-94c4-f49351b811f1", "role": "requester", "state": "request-sent", "theirLabel": "Agent: PopulateWallet2", + "updatedAt": "2022-01-21T22:50:20.522Z", }, }, } `; exports[`UpdateAssistant | v0.1 - v0.2 should correctly update the credential records and create didcomm records with auto update 1`] = ` -Object { - "1-4e4f-41d9-94c4-f49351b811f1": Object { +{ + "1-4e4f-41d9-94c4-f49351b811f1": { "id": "1-4e4f-41d9-94c4-f49351b811f1", - "tags": Object { + "tags": { "associatedRecordId": "574b2a37-1db1-4af1-a3bf-35c6cb9e1d7a", "messageId": "578e73da-c3be-43d4-949b-7aadfd5a6eae", "messageName": "offer-credential", @@ -2421,51 +2465,52 @@ Object { "threadId": "578e73da-c3be-43d4-949b-7aadfd5a6eae", }, "type": "DidCommMessageRecord", - "value": Object { - "_tags": Object {}, + "value": { + "_tags": {}, "associatedRecordId": "574b2a37-1db1-4af1-a3bf-35c6cb9e1d7a", "createdAt": "2022-01-21T22:50:20.522Z", "id": "1-4e4f-41d9-94c4-f49351b811f1", - "message": Object { + "message": { "@id": "578e73da-c3be-43d4-949b-7aadfd5a6eae", "@type": "https://didcomm.org/issue-credential/1.0/offer-credential", - "credential_preview": Object { + "credential_preview": { "@type": "https://didcomm.org/issue-credential/1.0/credential-preview", - "attributes": Array [ - Object { + "attributes": [ + { "mime-type": "text/plain", "name": "name", "value": "Alice", }, - Object { + { "mime-type": "text/plain", "name": "age", "value": "25", }, - Object { + { "mime-type": "text/plain", "name": "dateOfBirth", "value": "2020-01-01", }, ], }, - "offers~attach": Array [ - Object { + "offers~attach": [ + { "@id": "libindy-cred-offer-0", - "data": Object { + "data": { "base64": "eyJzY2hlbWFfaWQiOiJUTDFFYVBGQ1o4U2k1YVVycVNjQkR0OjI6c2NoZW1hLTgwZjdlZWM1LThlNWEtNDNjYS1hZDRkLTMyNzRmYjkzNjFiODoxLjAiLCJjcmVkX2RlZl9pZCI6IlRMMUVhUEZDWjhTaTVhVXJxU2NCRHQ6MzpDTDo2ODE6ZGVmYXVsdCIsImtleV9jb3JyZWN0bmVzc19wcm9vZiI6eyJjIjoiMTEzOTY4MTg4OTM2OTQ5MzcyNzU3NjU2NzI1MjU1MTQ3NDk1OTI5NTM0MjQ5NjU1MzY4NTMzMTY4OTIzMjU4NTA2OTUzOTk3MTI2MDEyIiwieHpfY2FwIjoiMjM5NDkwMjQ4MjE4MTExOTQ1MjIxOTQ1ODcyMTE4MjQzNzA3NjE5OTQ4MzQ0MjU1ODM5ODI4NTU3NjkyNTE3NDExNDMwNDgwNDgwMTkxMTMwMjM0OTg5ODk0NzIyNDE2Nzk1MzUzODAwMDk3NDUxMjI5NDE4MzQ0MjEyOTI3NDk1NjI2NTc4MTk2ODUxMTcwMTI0MDI1NDk1MDExMjc0NjU1NjQzNjkzNTE1ODczMjA5OTczMzgwNjA3MzQxNzQzMzIwNTY0NjkwMzcxOTgyNDIxNTQyMzQzMTMzNTMxOTcxMTk4NTA4NDk5MjYyNzUxNDMyODg0NzgzMzc1MTAzODI0OTE3NzEwODAxOTE3OTc1OTM2OTg4OTIwMzYyMzA5NjE4NTgxMzY0ODE5NTA5ODYxNTE3NjI2ODc3OTUzMzMzMTkzMDExMjA2NDA5NDQ1MzA0MzEwMjUzMTU5OTE1NzYxOTI1MDY1MTg3Mjk1OTg1MzU0NDY1NjY5MTMxNjgwMzc5MzY0ODk0Mzc3NTYxODcyMjcxNDY5Mjk1NzY4MTc4NjQ2NDMxODI3NjI1MTQ3Mzk4MDg1ODI3NTUzMDAzNjIyMDM1ODM1MDg2NzE1NjgyMDA5MzgyNjgxNDc3NDc4ODQ0MDEyNDQ5NTE2NjYwODMwNDMwODQ5ODMxNjAxNDk3MTk3MjczMTIzNjg1NTE0NDMwMjY5OTkxMzMzNDI1Nzk0NjAwMzc3Mzk3NDMwMjg0MjIyMjQ1OTgyMjI1NTE3MjQ4NzA5NTczMzEwNjM5NzQyNzc2NjMyMzM3MDM0Nzk4NDY3MTAwNDczNDUxNTMzMTg1NDg5NDU0NjUyNTgwMjcxNjgyMDQzOTc4MDY4NDc4MjM1MjM5NjMzMTk0MzE4NDcxNDM2MjMwOTg1NTQ1MzAyMDQwNiIsInhyX2NhcCI6W1siZGF0ZW9mYmlydGgiLCIyNjMyMjI0MDEyMDg0NjM3ODA0NjA4MDE5MTM2MzIyNDkzMTE1MzA1MTA2ODQ1MTA0NTE4MDE4MjY2MjU1NjMyNDM0MTMzODY0MTIwNzUxNDkyMDAyMTI4MzU3NTcwMTY4MjE0OTU3OTgzNTQ3Mjk0NTczOTExMzk3MTQwNDAxNDk4MzQ0NzE0NTA5MjEyNDA2NTYzNzgyOTc4ODUzNDgzMTM4NTA1NTA3OTcxNTY3MTgyOTQ1ODQ5ODI3MDU0Nzg4NjA3ODgwOTU5NDE1MTYwMjU0OTE2MDExODkyNTIzNjUzODA1NTk2MDQ3NjA5Mjg3ODA4ODg2MzcwOTM5NDA3Njc5NTE3MjUzOTUxOTUzNDgxNDkzOTMzNDI3NTA5OTUwNTY5NjUwMTc4MjQwNTg0ODI4NzgzOTAxOTA0MjI3MzE1OTEyNzQ0NDYyODc3NDI3Njg3MDEzMDkyNDY1NTc5NzY0OTk2MTc2NTU1NDg1Nzc2Njc0NTcxMjcwNjU4NjAyMTk3OTExNTYzMjY0MzEyNTgzNzMyNTE3NjI3MjY3MzQ1MzEwODcwMjk2NTk4NTEyMDc0NzczNDQyMTExNjY4NjM0MzYzNjkzMDkxNzQ2MjE1MDQwODg1NTcyMzAzMDU4MDUwNzc3Nzg4Mzc5NDY3MzMyNDUwOTY5NTg1NDE2NzU2NzA2MzcxMzMyOTk5MDg1NjM5MDU4Nzk4ODE4MjkwNzEyNDk2NDk0NTY1MzgxMjI0NzUyMTA2OTQyMTg2OTc3MzUwNzE2NjI0MTYxMjIwNjExMDY3MzcxNTM0NjYyNTc0MzY1NTM0MzEzNjAwMTQyODk2MDkwMDQ3MTI5NjQ3OTEzOTgzMjk0Mjc2MDI2OTA2MTA3NzM4MDM2MjI2MTA4NzU0MTE3OTIwMTg2NDM1MDkwOTU2Il0sWyJuYW1lIiwiMzE2Nzc0MzUwOTgzMjI4Nzg1MDcxNDg1NjYzOTM0MTYyNjQ1MzE2NjQzMTc5Njg2NTg2MTU2MjgwMjg3Njg5Nzg0NTI1Mjg5MzY0NDg5NDMxNjEwMTc1MzUwMDgzMzUzMTg3MTIwMTE1MTU1NjUxOTYzMjcyODM4MjcyMTgyNTI2MzUyNjMyMzI5MDY2NDIxMjM3OTM3NTc4MjY4MjkwNzU4MDgwNjI0MjE3MDM0NDU5MDUyMjY2Njk5NjQzMTg2MjkyNjcxNDk1ODEwMDU4Mjg5MzA1MjI4OTQ2MTQ2MTkzMDAzNDk0NDgwNzY0NDY5Nzc1NzM5Njg0NzcxNjMyNDIzNTM5MjE1MzIxMjc0NTQ4NDU5NzE4NTM3NTMzMzE0MjYwMjcwNzE0MTkzNjc4MTEzMDg5MDUxODI1MjA2MTAyMjQ0MTc3NzAwNDk3MTYxMDM2NjgwNDYzMDUxNDcxNDk3MTgzNzc3Nzc5Nzc2Nzc0MTUzNzQ1NjEwNzc3NzgyMDYyODA3NjY5MjE2ODA2NDgxNzAzNTU5NDk5MTAyNTc5ODAwNjgxNTQxMjg3MTk0MTAwNzg4MDMxNDE3Nzg5MzQyNjk2NTQyNTA3NjE5MTgzNDIyMTc0OTk5NzQ2ODM3NjY4NTg0Mzk0NzQxNzI5MDY2MjgwNjYyMzAyMDI3NTczMDgwMDY5NTE5MDgxOTA5OTA3MzQzODAxNzg3MjgyMTEyODY2NzkwNTI2MDIwMjk4NjM2ODY3Njg2NTE5MjQ0NTg2MTg1NzMxMTE0ODk1ODU3NjkzNTQxMTIwOTc2NTQ5MzYwNDE1MTkxMjI4ODA4MzE5NTcxOTU4NTkxNDc4NDYwNzMwMTg2NDQ4MTU3NjU3OTkyOTI4NTM2MjgxODU2NjAzNjU5NjM3OTE2NzE0OTk0NTU0NSJdLFsiYWdlIiwiMTY5MjgwMDQ2OTQyNDI3NDY1ODE5OTE3MzEyNTkzMzEyMzkyNDYzMDQxODc5MzIwMjM0NDU4NjQ4Mzg2MzI4MTE5MDQ5OTIyMzc1NjkxNzI0ODYzMDM3NDMyODU4OTQ4MTIyNzI2ODQ5NDU5NDcxODA2NTU4NzYyMzU4MTgzMzU3OTYwNDk3NTMyNjUzNzE4ODE0NzQ1NzY2ODc3OTI2NzU2NTQ3NjQwMjUzMTczMDYzMjY0Mzc0OTAzNTgwNjEwNDMxMTM2NTA2NjQ1NjE5NzYzNTE1Nzc3MTkyNjU4ODk0OTc1MDMyODAzNzM0MTE5MjM5NjcxMjgwOTQyOTkxMTg2MjYyNjYzNTM5NDU3ODc1NjY1NDcwMTAxMTEzOTUyOTY2MDQyMzU4NDQ4ODE1NTk5MDgzNTU4NTIyMDQ1OTI3NDI0NjI5Njk4MTgzMTUzNzUyMDA4MzM5NTI1NTYxMDI0ODg2MzUzOTc3NzA1ODE5Mjc1MzQzOTg3MzMzODMxNjU0NzA4ODI3NDI0NzMzNzcyNjI3MTA2OTgxNjE5NDY0MzUwNDU3NzE4NzM2MDA0NjEyNzQ0OTAyNDA5NjA0Njk4NzkzNzI0MTc1MDA4OTUzMDMyMDgxMTQ2OTE3MTM4ODc4NzQ4MDM0Mzg1NDQxNTIxMTU5ODM2NDIwMDEzNTQ1NTQyMDk2NDIwODA1MDYxMzI5MTkwNzczNzIzMDYxMjE2NDIzNjczNTM1MzU1OTc5MzY1Njg0MzM2NjEyOTU2NjkxNDA4NDQ5MjE4MjcwOTYyODUyMzQwMTQ2MDAwMDYzNzA3NzU5MzUxODM0MTQ2MDI4MTYyMTEyMzU4MzAzNDQ1OTcwMTg3MTk3OTQ5MDcwNzE4NzQ4OTI4NjM5MDkyMzY1MjExMjgyODY0NDE3OTcwOTg5OCJdLFsibWFzdGVyX3NlY3JldCIsIjk4NjY2MDAzNjA2Njc3MjkxODM1MjEwMzA1NDczNTA3NjU0NTM1OTgxNDYxODkyNjY5NjI5OTE1MzQ5NjU3NjY5MTI5NzAxNTUwNzUyNTU2NjMxMzU4MzU3NjEzNjg3OTgyNTQzNTcyNTc5ODEzOTgxMzI4MDM4NzY5OTcxMzAwNTE0NDI2NzAxNTM2ODE1ODI3MDgzNDY5MzEzOTQ0ODAzMzIzMDUzMzUyNDkxMTIwMDUwNzkyNzA4NTQzMTE2NTM1NjA2NTQyNDY3OTcxNTUxODA4MzQyOTk1OTM4NzQ2NDQ4NjMyNTY4NjU0NjA4MzI1NDk2NjM3Mzc5OTQ0NjA5MDU3Mjc2OTE0OTQxNzE4MzU2MTYzODYyMzI2MDc3MDUzNjIyMDI3OTE2MzIzNzAzMDE2MTc4NDQ3MDEwMTc1MjI1MTM0NjE3NTcxMTgzMjcyNzMwNjQxNzI3Mzk2MzM4ODk2NTUyNzM4NzUwMDA4MTQ3MDExNjkyNzIwNzI4NDY2MjcwNDQ2MDg4NjEyMDg2MDExMzg2NzQxOTMzODM4ODQ1NjkzMTM1NzcyODk0MDIwNTM4NTU3ODI1MjA3OTkxNDAwODIyNjg4OTgwNTg4MjgzMTY0MzAxNTY1NjAyNDAzNTI4MDE2MTczNTk5MTM4NzI5ODEyOTE2MTYxNDEwNjgyODU4MzU4MDE3ODI1ODUyMzY2OTgzMDQzMzM4ODY2MzI3MzM3MTE0NzcyMzM5NTUyNTYzNzU0NzQ0NTA5MTYzNzYzNjAyNTI0NjgxNTUyNjIyOTYwNjM4MzE2OTc0NjI4MjQyNjQ5NjYyMjQyMTkzODMxOTUyMDE0MTAxNTA3Njk2MjIxMDU5NDE5MTcyNzMwNjE2NDE2NzEwNTMzMzc2NjI4MjQ4NzkxMTUwNjMxMDMxOCJdXX0sIm5vbmNlIjoiMTE4MTE3NTM4MDU1MjM2NjMxNjAwNjM1NyJ9", }, "mime-type": "application/json", }, ], }, - "metadata": Object {}, + "metadata": {}, "role": "sender", + "updatedAt": "2022-01-21T22:50:20.522Z", }, }, - "10-4e4f-41d9-94c4-f49351b811f1": Object { + "10-4e4f-41d9-94c4-f49351b811f1": { "id": "10-4e4f-41d9-94c4-f49351b811f1", - "tags": Object { + "tags": { "associatedRecordId": "c7e0a752-7f1c-41c0-b0ae-a68c2d97ca8c", "messageId": "e2c2194c-6ac6-4b27-9030-18887c79b5eb", "messageName": "offer-credential", @@ -2477,51 +2522,52 @@ Object { "threadId": "e2c2194c-6ac6-4b27-9030-18887c79b5eb", }, "type": "DidCommMessageRecord", - "value": Object { - "_tags": Object {}, + "value": { + "_tags": {}, "associatedRecordId": "c7e0a752-7f1c-41c0-b0ae-a68c2d97ca8c", "createdAt": "2022-01-21T22:50:20.522Z", "id": "10-4e4f-41d9-94c4-f49351b811f1", - "message": Object { + "message": { "@id": "e2c2194c-6ac6-4b27-9030-18887c79b5eb", "@type": "https://didcomm.org/issue-credential/1.0/offer-credential", - "credential_preview": Object { + "credential_preview": { "@type": "https://didcomm.org/issue-credential/1.0/credential-preview", - "attributes": Array [ - Object { + "attributes": [ + { "mime-type": "text/plain", "name": "name", "value": "Alice", }, - Object { + { "mime-type": "text/plain", "name": "age", "value": "25", }, - Object { + { "mime-type": "text/plain", "name": "dateOfBirth", "value": "2020-01-01", }, ], }, - "offers~attach": Array [ - Object { + "offers~attach": [ + { "@id": "libindy-cred-offer-0", - "data": Object { + "data": { "base64": "eyJzY2hlbWFfaWQiOiJUTDFFYVBGQ1o4U2k1YVVycVNjQkR0OjI6c2NoZW1hLTgwZjdlZWM1LThlNWEtNDNjYS1hZDRkLTMyNzRmYjkzNjFiODoxLjAiLCJjcmVkX2RlZl9pZCI6IlRMMUVhUEZDWjhTaTVhVXJxU2NCRHQ6MzpDTDo2ODE6ZGVmYXVsdCIsImtleV9jb3JyZWN0bmVzc19wcm9vZiI6eyJjIjoiMTEzOTY4MTg4OTM2OTQ5MzcyNzU3NjU2NzI1MjU1MTQ3NDk1OTI5NTM0MjQ5NjU1MzY4NTMzMTY4OTIzMjU4NTA2OTUzOTk3MTI2MDEyIiwieHpfY2FwIjoiMjM5NDkwMjQ4MjE4MTExOTQ1MjIxOTQ1ODcyMTE4MjQzNzA3NjE5OTQ4MzQ0MjU1ODM5ODI4NTU3NjkyNTE3NDExNDMwNDgwNDgwMTkxMTMwMjM0OTg5ODk0NzIyNDE2Nzk1MzUzODAwMDk3NDUxMjI5NDE4MzQ0MjEyOTI3NDk1NjI2NTc4MTk2ODUxMTcwMTI0MDI1NDk1MDExMjc0NjU1NjQzNjkzNTE1ODczMjA5OTczMzgwNjA3MzQxNzQzMzIwNTY0NjkwMzcxOTgyNDIxNTQyMzQzMTMzNTMxOTcxMTk4NTA4NDk5MjYyNzUxNDMyODg0NzgzMzc1MTAzODI0OTE3NzEwODAxOTE3OTc1OTM2OTg4OTIwMzYyMzA5NjE4NTgxMzY0ODE5NTA5ODYxNTE3NjI2ODc3OTUzMzMzMTkzMDExMjA2NDA5NDQ1MzA0MzEwMjUzMTU5OTE1NzYxOTI1MDY1MTg3Mjk1OTg1MzU0NDY1NjY5MTMxNjgwMzc5MzY0ODk0Mzc3NTYxODcyMjcxNDY5Mjk1NzY4MTc4NjQ2NDMxODI3NjI1MTQ3Mzk4MDg1ODI3NTUzMDAzNjIyMDM1ODM1MDg2NzE1NjgyMDA5MzgyNjgxNDc3NDc4ODQ0MDEyNDQ5NTE2NjYwODMwNDMwODQ5ODMxNjAxNDk3MTk3MjczMTIzNjg1NTE0NDMwMjY5OTkxMzMzNDI1Nzk0NjAwMzc3Mzk3NDMwMjg0MjIyMjQ1OTgyMjI1NTE3MjQ4NzA5NTczMzEwNjM5NzQyNzc2NjMyMzM3MDM0Nzk4NDY3MTAwNDczNDUxNTMzMTg1NDg5NDU0NjUyNTgwMjcxNjgyMDQzOTc4MDY4NDc4MjM1MjM5NjMzMTk0MzE4NDcxNDM2MjMwOTg1NTQ1MzAyMDQwNiIsInhyX2NhcCI6W1siZGF0ZW9mYmlydGgiLCIyNjMyMjI0MDEyMDg0NjM3ODA0NjA4MDE5MTM2MzIyNDkzMTE1MzA1MTA2ODQ1MTA0NTE4MDE4MjY2MjU1NjMyNDM0MTMzODY0MTIwNzUxNDkyMDAyMTI4MzU3NTcwMTY4MjE0OTU3OTgzNTQ3Mjk0NTczOTExMzk3MTQwNDAxNDk4MzQ0NzE0NTA5MjEyNDA2NTYzNzgyOTc4ODUzNDgzMTM4NTA1NTA3OTcxNTY3MTgyOTQ1ODQ5ODI3MDU0Nzg4NjA3ODgwOTU5NDE1MTYwMjU0OTE2MDExODkyNTIzNjUzODA1NTk2MDQ3NjA5Mjg3ODA4ODg2MzcwOTM5NDA3Njc5NTE3MjUzOTUxOTUzNDgxNDkzOTMzNDI3NTA5OTUwNTY5NjUwMTc4MjQwNTg0ODI4NzgzOTAxOTA0MjI3MzE1OTEyNzQ0NDYyODc3NDI3Njg3MDEzMDkyNDY1NTc5NzY0OTk2MTc2NTU1NDg1Nzc2Njc0NTcxMjcwNjU4NjAyMTk3OTExNTYzMjY0MzEyNTgzNzMyNTE3NjI3MjY3MzQ1MzEwODcwMjk2NTk4NTEyMDc0NzczNDQyMTExNjY4NjM0MzYzNjkzMDkxNzQ2MjE1MDQwODg1NTcyMzAzMDU4MDUwNzc3Nzg4Mzc5NDY3MzMyNDUwOTY5NTg1NDE2NzU2NzA2MzcxMzMyOTk5MDg1NjM5MDU4Nzk4ODE4MjkwNzEyNDk2NDk0NTY1MzgxMjI0NzUyMTA2OTQyMTg2OTc3MzUwNzE2NjI0MTYxMjIwNjExMDY3MzcxNTM0NjYyNTc0MzY1NTM0MzEzNjAwMTQyODk2MDkwMDQ3MTI5NjQ3OTEzOTgzMjk0Mjc2MDI2OTA2MTA3NzM4MDM2MjI2MTA4NzU0MTE3OTIwMTg2NDM1MDkwOTU2Il0sWyJuYW1lIiwiMzE2Nzc0MzUwOTgzMjI4Nzg1MDcxNDg1NjYzOTM0MTYyNjQ1MzE2NjQzMTc5Njg2NTg2MTU2MjgwMjg3Njg5Nzg0NTI1Mjg5MzY0NDg5NDMxNjEwMTc1MzUwMDgzMzUzMTg3MTIwMTE1MTU1NjUxOTYzMjcyODM4MjcyMTgyNTI2MzUyNjMyMzI5MDY2NDIxMjM3OTM3NTc4MjY4MjkwNzU4MDgwNjI0MjE3MDM0NDU5MDUyMjY2Njk5NjQzMTg2MjkyNjcxNDk1ODEwMDU4Mjg5MzA1MjI4OTQ2MTQ2MTkzMDAzNDk0NDgwNzY0NDY5Nzc1NzM5Njg0NzcxNjMyNDIzNTM5MjE1MzIxMjc0NTQ4NDU5NzE4NTM3NTMzMzE0MjYwMjcwNzE0MTkzNjc4MTEzMDg5MDUxODI1MjA2MTAyMjQ0MTc3NzAwNDk3MTYxMDM2NjgwNDYzMDUxNDcxNDk3MTgzNzc3Nzc5Nzc2Nzc0MTUzNzQ1NjEwNzc3NzgyMDYyODA3NjY5MjE2ODA2NDgxNzAzNTU5NDk5MTAyNTc5ODAwNjgxNTQxMjg3MTk0MTAwNzg4MDMxNDE3Nzg5MzQyNjk2NTQyNTA3NjE5MTgzNDIyMTc0OTk5NzQ2ODM3NjY4NTg0Mzk0NzQxNzI5MDY2MjgwNjYyMzAyMDI3NTczMDgwMDY5NTE5MDgxOTA5OTA3MzQzODAxNzg3MjgyMTEyODY2NzkwNTI2MDIwMjk4NjM2ODY3Njg2NTE5MjQ0NTg2MTg1NzMxMTE0ODk1ODU3NjkzNTQxMTIwOTc2NTQ5MzYwNDE1MTkxMjI4ODA4MzE5NTcxOTU4NTkxNDc4NDYwNzMwMTg2NDQ4MTU3NjU3OTkyOTI4NTM2MjgxODU2NjAzNjU5NjM3OTE2NzE0OTk0NTU0NSJdLFsiYWdlIiwiMTY5MjgwMDQ2OTQyNDI3NDY1ODE5OTE3MzEyNTkzMzEyMzkyNDYzMDQxODc5MzIwMjM0NDU4NjQ4Mzg2MzI4MTE5MDQ5OTIyMzc1NjkxNzI0ODYzMDM3NDMyODU4OTQ4MTIyNzI2ODQ5NDU5NDcxODA2NTU4NzYyMzU4MTgzMzU3OTYwNDk3NTMyNjUzNzE4ODE0NzQ1NzY2ODc3OTI2NzU2NTQ3NjQwMjUzMTczMDYzMjY0Mzc0OTAzNTgwNjEwNDMxMTM2NTA2NjQ1NjE5NzYzNTE1Nzc3MTkyNjU4ODk0OTc1MDMyODAzNzM0MTE5MjM5NjcxMjgwOTQyOTkxMTg2MjYyNjYzNTM5NDU3ODc1NjY1NDcwMTAxMTEzOTUyOTY2MDQyMzU4NDQ4ODE1NTk5MDgzNTU4NTIyMDQ1OTI3NDI0NjI5Njk4MTgzMTUzNzUyMDA4MzM5NTI1NTYxMDI0ODg2MzUzOTc3NzA1ODE5Mjc1MzQzOTg3MzMzODMxNjU0NzA4ODI3NDI0NzMzNzcyNjI3MTA2OTgxNjE5NDY0MzUwNDU3NzE4NzM2MDA0NjEyNzQ0OTAyNDA5NjA0Njk4NzkzNzI0MTc1MDA4OTUzMDMyMDgxMTQ2OTE3MTM4ODc4NzQ4MDM0Mzg1NDQxNTIxMTU5ODM2NDIwMDEzNTQ1NTQyMDk2NDIwODA1MDYxMzI5MTkwNzczNzIzMDYxMjE2NDIzNjczNTM1MzU1OTc5MzY1Njg0MzM2NjEyOTU2NjkxNDA4NDQ5MjE4MjcwOTYyODUyMzQwMTQ2MDAwMDYzNzA3NzU5MzUxODM0MTQ2MDI4MTYyMTEyMzU4MzAzNDQ1OTcwMTg3MTk3OTQ5MDcwNzE4NzQ4OTI4NjM5MDkyMzY1MjExMjgyODY0NDE3OTcwOTg5OCJdLFsibWFzdGVyX3NlY3JldCIsIjk4NjY2MDAzNjA2Njc3MjkxODM1MjEwMzA1NDczNTA3NjU0NTM1OTgxNDYxODkyNjY5NjI5OTE1MzQ5NjU3NjY5MTI5NzAxNTUwNzUyNTU2NjMxMzU4MzU3NjEzNjg3OTgyNTQzNTcyNTc5ODEzOTgxMzI4MDM4NzY5OTcxMzAwNTE0NDI2NzAxNTM2ODE1ODI3MDgzNDY5MzEzOTQ0ODAzMzIzMDUzMzUyNDkxMTIwMDUwNzkyNzA4NTQzMTE2NTM1NjA2NTQyNDY3OTcxNTUxODA4MzQyOTk1OTM4NzQ2NDQ4NjMyNTY4NjU0NjA4MzI1NDk2NjM3Mzc5OTQ0NjA5MDU3Mjc2OTE0OTQxNzE4MzU2MTYzODYyMzI2MDc3MDUzNjIyMDI3OTE2MzIzNzAzMDE2MTc4NDQ3MDEwMTc1MjI1MTM0NjE3NTcxMTgzMjcyNzMwNjQxNzI3Mzk2MzM4ODk2NTUyNzM4NzUwMDA4MTQ3MDExNjkyNzIwNzI4NDY2MjcwNDQ2MDg4NjEyMDg2MDExMzg2NzQxOTMzODM4ODQ1NjkzMTM1NzcyODk0MDIwNTM4NTU3ODI1MjA3OTkxNDAwODIyNjg4OTgwNTg4MjgzMTY0MzAxNTY1NjAyNDAzNTI4MDE2MTczNTk5MTM4NzI5ODEyOTE2MTYxNDEwNjgyODU4MzU4MDE3ODI1ODUyMzY2OTgzMDQzMzM4ODY2MzI3MzM3MTE0NzcyMzM5NTUyNTYzNzU0NzQ0NTA5MTYzNzYzNjAyNTI0NjgxNTUyNjIyOTYwNjM4MzE2OTc0NjI4MjQyNjQ5NjYyMjQyMTkzODMxOTUyMDE0MTAxNTA3Njk2MjIxMDU5NDE5MTcyNzMwNjE2NDE2NzEwNTMzMzc2NjI4MjQ4NzkxMTUwNjMxMDMxOCJdXX0sIm5vbmNlIjoiNTQzODYyOTczNzUxMjk1OTk2MTAzNjA3In0=", }, "mime-type": "application/json", }, ], }, - "metadata": Object {}, + "metadata": {}, "role": "receiver", + "updatedAt": "2022-01-21T22:50:20.522Z", }, }, - "11-4e4f-41d9-94c4-f49351b811f1": Object { + "11-4e4f-41d9-94c4-f49351b811f1": { "id": "11-4e4f-41d9-94c4-f49351b811f1", - "tags": Object { + "tags": { "associatedRecordId": "c7e0a752-7f1c-41c0-b0ae-a68c2d97ca8c", "messageId": "edba1c87-51d3-4c70-aff2-ab8016e1060e", "messageName": "request-credential", @@ -2533,34 +2579,35 @@ Object { "threadId": "e2c2194c-6ac6-4b27-9030-18887c79b5eb", }, "type": "DidCommMessageRecord", - "value": Object { - "_tags": Object {}, + "value": { + "_tags": {}, "associatedRecordId": "c7e0a752-7f1c-41c0-b0ae-a68c2d97ca8c", "createdAt": "2022-01-21T22:50:20.522Z", "id": "11-4e4f-41d9-94c4-f49351b811f1", - "message": Object { + "message": { "@id": "edba1c87-51d3-4c70-aff2-ab8016e1060e", "@type": "https://didcomm.org/issue-credential/1.0/request-credential", - "requests~attach": Array [ - Object { + "requests~attach": [ + { "@id": "libindy-cred-request-0", - "data": Object { + "data": { "base64": "eyJwcm92ZXJfZGlkIjoiRUg2OTVENjRRd2hWRmtyazFtcDQ5aiIsImNyZWRfZGVmX2lkIjoiVEwxRWFQRkNaOFNpNWFVcnFTY0JEdDozOkNMOjY4MTpkZWZhdWx0IiwiYmxpbmRlZF9tcyI6eyJ1IjoiOTcwNjA5MzQ1NDAxNzE0NDIxNjQzNDg0NDE0MTQwOTA0NzMzNTQ4NTA4NzY0OTk5MTgxNzY1ODI3MjM3ODg3NjQ2MzQzNDYyMTY0ODA3MTUxMjg1ODk2MDczODAyNDY1MDMwMTcxNTIyODE4ODY4Mjc2ODUwMzE0NzQzMjM3ODc3NDMyMDgxMTQwMzE5ODU5OTM5NjM0MTI4NzkzOTk4NDcwMTUzNTQ0NjgxNTM5NDg4NzEyNDE5MTk3NzcxODE1NjU5Nzg1NDE5NTA1ODEyODI4NzYxOTI4MzExODczNjA5NDYwMjExMzQ4OTAyNDk4NzYxNjc5OTIzNzA2NjUwMDIzODg4ODU4NzE1MTYxMTIyMzA5MTc1MTE3MTk0NDMwNTI1ODY5NjcwMDEzMTgxNTkzNjI4NDQzMjk2MDI0MDE5NTc4MzIzODQwNzk0OTQ0MjIyODE1MTQwNTM2Mjc3ODQwMjU2ODc2MDE1MzUwNDgzOTE2MjYzMDgyODM5NzI2NzAxNjg4NjcxNDY0MjA3MzQxMTgwMjg3Mjk0Njg4NDA3NzQ3MDk1NjA0NzQ3NzA3OTc2Nzk2MjU0MTQ2NDQ0NTY5NzQ4MTk4OTMwMjkyOTkzNjY1ODk2MTUyMDMyNzQwODY3OTgwMjczMTMxMDM3NjkwNDkzNDU1Mjc4NDc3MDc3NjE0OTU2NjgzNjgxNDc5NzY3Njg0MDI4MzU5NzE4NzM0ODEyNzcyMDIyOTIwODQ3NDIyNDYyOTAwMjczMTcwMTU2NzQyMzUyMDQ2NDYyODI4NzAxMTE2MzU0MTkwMDY5MDE0NTIwMSIsInVyIjpudWxsLCJoaWRkZW5fYXR0cmlidXRlcyI6WyJtYXN0ZXJfc2VjcmV0Il0sImNvbW1pdHRlZF9hdHRyaWJ1dGVzIjp7fX0sImJsaW5kZWRfbXNfY29ycmVjdG5lc3NfcHJvb2YiOnsiYyI6IjE4MzE5MTUyNjg0NDkyMTM0OTc4MzkzNDE3OTY5NjQ5MDIyNzMzNzQzMTQ5NTUwNzAwNzc5ODk0Nzg1MTg3MTA1OTkyNjk4Mzg5MjAzIiwidl9kYXNoX2NhcCI6IjQ0NzA4MzAwOTUyNzA3MjA3NjI3NjA3NzM4MTI2NDgxNzA3OTA1MDcwNjEyMzQ5OTIxNTAxNTYyOTA2MzgyMzE0MDE4MzQ4MTAxMTE4MDI4MDIyMjk5OTgyNTEwMjI5ODM1OTQxMzY1MTM4Njg0MTU1OTEyOTE3NzYwMjgwOTIyNDk1MjE1ODA2NTM3MzA5NDU5NDA3NjcxNDgyNDA0NDMwODU4MzU5ODU3MDgzNzg1Njk1MTYzMjkwMzEyNzMzNDAxNjY1NDk5MjUwMDQ0NzkwODk4OTA4NzIzNzE1OTc0MDYwNzgyNDEzODYzMTU0MTUxNjg2OTM3ODY4MDM5MDU1Nzc1MzA5MjQ0MTYzOTUxNzgwMTgxNDk5MDM5MDgyMjcxNzgzNTgzNTkxMDIyNjYwMDYyMDQ3MDQ2NTEyMTM0NDU5OTI1MTgyOTg2MTkxOTAwNTQwMDg4MjE3Mzc2NjM4MjEzNTI0MDUxNjcxNzg3ODY0ODQ2NzIxNjk5NjQzNDk0MTI2MjA3NTg2MjgwNjQ5OTc4ODE2ODEwMjM5OTAzMzU0NzIyOTI2NTUxODYyNTQwMjc4ODU2OTEyNDQ2MDUzMTg4MzI3Mjk4NDc0NjgzMjkwMjU4MjgwNjY0OTgyOTM2NTYyODcwNTIyODA2NzYwMjE1OTI0MDc3ODQ2NjA2NTM0NzI4MjkyODQ2MDQyOTk1NjgxMTQzOTQ5MTU0MTU0NTU2NzQzMDYzMzY1OTIzMzU2OTg1MjQ5ODI1Njk2NzE3MzE2MDk1NzE5MDU2MTE5NTE0NTYwODY0MzUyMDc4ODMyOTYzNjI3Mjk0Njk1ODQ0MTE5NDA1NTMzNTY5MTI2ODExODE3NDYyNjczMzM3OTg4MzA0MDcwMzk5ODYyNTk2ODMyNDk2OTU3NzA4Nzc0NzI5NzAyOTEyNjY3Njk0OTgwMjc3OTI5MDgzNiIsIm1fY2FwcyI6eyJtYXN0ZXJfc2VjcmV0IjoiMjIxNTAyNTExNTYzODg1MTM1NzIwNjg4MzE5Njk5NzE5ODAxNDI4NDgzMTI2MjY0NjI0NTE5OTA4MjM5NTM0MjQ3MDQ3NTIwODgyNDE4Mzk0ODMzNjUwODM2NTI0NDk2MzUzMDkwNzIzOTI1MDc2NDY2ODQ3NjIxNzE4MDA4MDAxNTQyNTMzOTk4NTU1MzA1MDYwNjUzMzkwMjc4MDc2MzE2Mjg5MzcwMzA2MDcyMDYxNCJ9LCJyX2NhcHMiOnt9fSwibm9uY2UiOiI2OTgzNzA2MTYwMjM4ODM3MzA0OTgzNzUifQ==", }, "mime-type": "application/json", }, ], - "~thread": Object { + "~thread": { "thid": "e2c2194c-6ac6-4b27-9030-18887c79b5eb", }, }, - "metadata": Object {}, + "metadata": {}, "role": "sender", + "updatedAt": "2022-01-21T22:50:20.522Z", }, }, - "12-4e4f-41d9-94c4-f49351b811f1": Object { + "12-4e4f-41d9-94c4-f49351b811f1": { "id": "12-4e4f-41d9-94c4-f49351b811f1", - "tags": Object { + "tags": { "associatedRecordId": "c7e0a752-7f1c-41c0-b0ae-a68c2d97ca8c", "messageId": "a340a7b9-f4d4-4892-b7d2-1d3d40e4be48", "messageName": "issue-credential", @@ -2572,35 +2619,36 @@ Object { "threadId": "e2c2194c-6ac6-4b27-9030-18887c79b5eb", }, "type": "DidCommMessageRecord", - "value": Object { - "_tags": Object {}, + "value": { + "_tags": {}, "associatedRecordId": "c7e0a752-7f1c-41c0-b0ae-a68c2d97ca8c", "createdAt": "2022-01-21T22:50:20.522Z", "id": "12-4e4f-41d9-94c4-f49351b811f1", - "message": Object { + "message": { "@id": "a340a7b9-f4d4-4892-b7d2-1d3d40e4be48", "@type": "https://didcomm.org/issue-credential/1.0/issue-credential", - "credentials~attach": Array [ - Object { + "credentials~attach": [ + { "@id": "libindy-cred-0", - "data": Object { + "data": { "base64": "eyJzY2hlbWFfaWQiOiJUTDFFYVBGQ1o4U2k1YVVycVNjQkR0OjI6c2NoZW1hLTgwZjdlZWM1LThlNWEtNDNjYS1hZDRkLTMyNzRmYjkzNjFiODoxLjAiLCJjcmVkX2RlZl9pZCI6IlRMMUVhUEZDWjhTaTVhVXJxU2NCRHQ6MzpDTDo2ODE6ZGVmYXVsdCIsInJldl9yZWdfaWQiOm51bGwsInZhbHVlcyI6eyJuYW1lIjp7InJhdyI6IkFsaWNlIiwiZW5jb2RlZCI6IjI3MDM0NjQwMDI0MTE3MzMxMDMzMDYzMTI4MDQ0MDA0MzE4MjE4NDg2ODE2OTMxNTIwODg2NDA1NTM1NjU5OTM0NDE3NDM4NzgxNTA3In0sImFnZSI6eyJyYXciOiIyNSIsImVuY29kZWQiOiIyNSJ9LCJkYXRlT2ZCaXJ0aCI6eyJyYXciOiIyMDIwLTAxLTAxIiwiZW5jb2RlZCI6IjQxMDYxMjkzNzgwNDYyMDc1NTE0MjA4MjIzODk3NTkwNDA3ODAwNzQ4NTQzMDE0OTkxNTg4NTIyNzIxMTkzODg2ODk4MTE5NzUzNjI0In19LCJzaWduYXR1cmUiOnsicF9jcmVkZW50aWFsIjp7Im1fMiI6IjEwMzE4MTcwNjEzMDMzODg1ODkxNDkzMzk2MjU5NDYyNDIxMzM3MjY3ODcyNzkyNzczNjgwMDQwMTgyNTE4NDM1MzEzMDYyNjE3MTcxMCIsImEiOiI3MTM3NDExNDU3NjI3MDE5MDcyNjY0ODMzNTQ5MjAzMDQyMTg0MDQ0OTUxNTU5MzYwMDczMzk1MjM4NjkwMzkwNjgxNzA3ODAyNzY0MTE2MzQ4NjE4NjgxOTgzNTM2MTczNTE3MzgxODc5NzQ0MzQyODIzMDkzNzc4MTk1NzYxNzgzMjQ3NDcxOTQ2NDgwODc0OTY2OTQyNjY0NzU4NjIwNzEyNzExODExNTY5NTc1NzMwNTg4NTQwNDI1MjEzNjY0OTg0OTQyOTIzODU5NzQ3MjIzNjA5ODQ1NjIwMjE5NTY1NzYxODY2OTMwMzI3OTYzNTIwNzE5MTg2NjMyNTQ4NzkzNDI3MTQ0NTY0NTAxNjE1NTg1MTI2NzkxNzM5Njg3ODc2MzIxMTQ1NTAzNDU1OTM0MzUxODc0MjQ3NjA0NzAwODYxMTgxNjY4NjUxNzQ5NTExMjY0MzExMDU2NjI5MjM4NjQwNDY2NzM4ODA0NjI0NzU1MzEzODgxMjQzMjkwNDM5ODI4MDE0NzY0MTQ3NDM1MzE3NTY3MjY3MzQ2NTQxMTY2ODIwNTI2MDkyMDAyMDE0NzY5NjE1MzI3Mjg4MTYwMzM0MDg1MTQ1MzQ0Mzc5MzAxMDg1NDc1MDEyNzUzNDIzNzkxMjI4NzM5NDE3MzA0OTM2NDUzNDEyMDYxNjY0MTUzNTM4MjM5MDA0OTUxMDgyODQxMTE1MTIyNDQ1MzkzNzc5Mzc5NDI5NjQzMjMxNDE2NDQzNDM4NDQ1NzI1NTAzMDc0MTM5MzEwOTc2MjkwMTQ2MTIwMDI2MDI1NTczNzIyNTUyOCIsImUiOiIyNTkzNDQ3MjMwNTUwNjIwNTk5MDcwMjU0OTE0ODA2OTc1NzE5MzgyNzc4ODk1MTUxNTIzMDYyNDk3Mjg1ODMxMDU2NjU4MDA3MTMzMDY3NTkxNDk5ODE2OTA1NTkxOTM5ODcxNDMwMTIzNjc5MTMyMDYyOTkzMjM4OTk2OTY5NDIyMTMyMzU5NTY3NDI5MzAxNTQ3NTU5NjM2MjQ0MTcyOTU5NjM1MjY1ODc1MTkyNjIwNTEiLCJ2IjoiODMxMTU0NzI2ODU4Mzg3ODc5NTU5NDUzNzcwNjg3MzIyMzE1NTgyMjYzMTk3NDUzMjMxNTI1ODIzNDIzNjkxMDk5Mzc2NTExNzI0ODAxMzA1MTU0NzY2Mjc0NjQ1OTMzMTAwOTIzMjIxOTgwNDkyNzE0NDQxMTY0NTYxNTIwMDIzMjYzMDYzMzQ4NjQ4NzkzMjkxMzEwNDc0NTU5NDIwMTkzMTA1MjE5NzMxNTAwMTc0ODg0NzQ0MDk1MjU3MDYyMjczODA2OTYzNjg5MjY3NzA1NTg4NTQ4MzU4NzQ2NDc1Mzc1MzAzMjI1OTI3MDkxMzA0NzQ2NTg5MzA1MDEzNjc1ODk0MzIxMDkzNjE0NzIxMjQwNDAzNDE5OTM5OTk0Mjg5NTU2MzY0MDExMTg2ODQ2NjMxNTA2OTU0NDg0NjM4NjgwMzEyMDA2Njk0MjcwOTkwNDU3NTk3NjAyNzc5MjUzMDc3MTg3NDgwNTg5NDMyNzU4ODgwMjY3NzA1NzMyMjg3Nzc0ODczOTI3MDExMTQ1MDE1NzgyNjE5NzI4NTAxNjI5MTE4ODE1ODM2NjU2OTMzMzcwMzgwNDk4NDk5MDE0MDEzNDI1NDMwMjMwODQzODc0OTk3NTg0NTY3NTA0Mzg3OTE4MjQxMzMxNTM1NDk5MTQxMjU1NzQzNjQ0MzgwNTQ4ODAxNDUwNDEyMzQzMTAxODc4Nzg3NzIzOTIxMDU5NjQyNDg2MjE3NjE0MzQyMDc0MTQ3ODk1ODA2MzQ1NTQ0Njk3NjI2MzcwMDY1MjYxMjQ3OTM2NzMwMzMyNTkyMjM3NDY1MjIyNTQ1MjE4ODc4MDk0ODk0NTE0ODU2ODUyNTU1NjI4MjIwNDg2MTU5MjcyMjIxMjM3MDkwMjc4NjUyNDM2MzcyNTE4MDgzOTUxNjAwNzI1ODA1MDYwNjExMTkyNzEyOTI3MjQ2MTUxMDU4NzU5NDk2MzQ3NjA0NDQwNDcxODcwODEyNzMwODE3MTU4NzQxNDQ1MDA0OTg2MTgyNDk5NDA4OTQxMzk2NjcwMzEyOTU0NDQ1MTk1NTM5MTM5MzIwMDI4MTI3NzMyMDQ0MSJ9LCJyX2NyZWRlbnRpYWwiOm51bGx9LCJzaWduYXR1cmVfY29ycmVjdG5lc3NfcHJvb2YiOnsic2UiOiIyOTQ0ODU2NTEyNDk3MjM2MTc2OTQ3OTMxNzM1Mjk0NDc4MTU5NTE2ODM4OTExNDEyMDE3MTI1NTAyNjc1ODM2Nzk1NTQ4OTczNDMyMTg2NjI0MTc2NjQwNzAxNjczOTk4MjI3NTEzMTA0OTU5OTgzMjk1NjI0MTA0MjkwNjgzMjU0OTI4NTg1MDkwMTI2Mjk5ODczMjY4NDYyODA5NTY4NjMxNDg3MDk2ODgzMDE0OTcwMTcyMzM0MzIwMTM4OTg5ODkzMzcyODIyODU2MTQxMTM5NTQ0MzQyMzExNDEzNDgyMDU0OTYyNjE5NTgzNjU5NjMwMzYxNTc3Nzg0MTQ4NjY3NTgwNjMxODc4NDIwNTgzMDk5OTM1NzE0NDYyMzE5Njg4NDE5MTM4NjY3Nzk2Nzc2MzQwMDk2MDIxMTgwMTU0ODU0Njg1MDEzNzY1MTM0ODMwNjA0MzEyMjI0MjIwNzA5MzQwODExMTI4MDczMDk3NjEyMDA0MTE4NjA0MDI3MTczNjExNTE2ODA0NTQxMzUzODA2OTkxMTg0MDY3MzY1MDE5Nzk2NDM2MDA3NDI0NTM5NzQ4ODQxMjU4NjA1MTUxNTQxNzQzMDk4MTE5NzI1Mzc4MTQ4MTgyNDQ2Njg3NDQ0MjE0ODk1NTE2NDc3MTM4Mjk1OTI1Mzc4Nzg1MTA3OTc5MTYxNTIyNTYyNDk2Njg2MTAzMjg3MDUxNDUyMTE3NTQzMzc4Mzc0MDM5Mjg3NzgxMjAwNzgxMTkyNDQyOTY5MDU1NjIwNTcyODg1NDM5NjU2MTU3Njk2Mzc5MTAyNDc2MTQ2IiwiYyI6IjI4NjYxMjE3ODQ5OTg3ODI4MTA2Nzk5MTAxOTIxNDcyNzgxNDMzNzgzMDE5Mzg0NzAwMDUzMjU4MTU5NDUxNjc5MjMwODI0NzA5NjIifSwicmV2X3JlZyI6bnVsbCwid2l0bmVzcyI6bnVsbH0=", }, "mime-type": "application/json", }, ], - "~please_ack": Object {}, - "~thread": Object { + "~please_ack": {}, + "~thread": { "thid": "e2c2194c-6ac6-4b27-9030-18887c79b5eb", }, }, - "metadata": Object {}, + "metadata": {}, "role": "receiver", + "updatedAt": "2022-01-21T22:50:20.522Z", }, }, - "2-4e4f-41d9-94c4-f49351b811f1": Object { + "2-4e4f-41d9-94c4-f49351b811f1": { "id": "2-4e4f-41d9-94c4-f49351b811f1", - "tags": Object { + "tags": { "associatedRecordId": "574b2a37-1db1-4af1-a3bf-35c6cb9e1d7a", "messageId": "1284ae78-f3d3-4fed-a5ff-0e2aba968c3c", "messageName": "request-credential", @@ -2612,34 +2660,35 @@ Object { "threadId": "578e73da-c3be-43d4-949b-7aadfd5a6eae", }, "type": "DidCommMessageRecord", - "value": Object { - "_tags": Object {}, + "value": { + "_tags": {}, "associatedRecordId": "574b2a37-1db1-4af1-a3bf-35c6cb9e1d7a", "createdAt": "2022-01-21T22:50:20.522Z", "id": "2-4e4f-41d9-94c4-f49351b811f1", - "message": Object { + "message": { "@id": "1284ae78-f3d3-4fed-a5ff-0e2aba968c3c", "@type": "https://didcomm.org/issue-credential/1.0/request-credential", - "requests~attach": Array [ - Object { + "requests~attach": [ + { "@id": "libindy-cred-request-0", - "data": Object { + "data": { "base64": "eyJwcm92ZXJfZGlkIjoiUVZveGd3d25WUGtBQlRMVmNtQ013TCIsImNyZWRfZGVmX2lkIjoiVEwxRWFQRkNaOFNpNWFVcnFTY0JEdDozOkNMOjY4MTpkZWZhdWx0IiwiYmxpbmRlZF9tcyI6eyJ1IjoiMTkwNzM1MzQyMjkwNjk4Mzk3NzgyNTUyMTM2NTA3NzAxMDA4NzYwMzcxMjg3NTQ1MzI0NDM2NDIyMTMwNzQ3MDI3NTk4NDM2NTM5Mzc0NzM0MjgxOTk4MDY5Mjg1OTg4MzAzMDE1MzAzMTYxNjExMTEzMTY2MzQxMzkyOTkzMTk0ODUxNzM5Njk4NzcxNDYzNzMzMDA4MjUxMzQ5NjM4OTkzMDE5NTk5MTY1NDUyOTk4OTA4OTY4MDE5NTUxODM2NDg1NzYxMzMxNTgxNTY0MzgxNTkxNjMwOTcyNTc5NTkyODMyNDk3MDI0NTMyMjQxNzk4MDMzMjI1NDg4NjA5NjEwNjEzNTU1NDMxODc3NDQzODk2ODUyMzA4NjIxNDA1NjI5NjA5MTg5Nzg2MjYzMTcyODU0MjA1MTI3ODMxNjc5NzM5NTkxODQyMTYwOTAyNjczMDE4Mzc0MzE5NjUyODA3Njc2MDQzNzc0ODcxMjQzMzkzNTIwNTkwODE5MDgxOTI4NzY3MjU5NDQ2OTIxNTM2MjU3MjQ2NjIxMjgzNjc2NDM1MDIwNzUwODI4NDI2NTM2MTU2ODA5NDgwMTU3MTQ0NDkxNTY2MTM0ODYzNjU4Mjg5ODIyMTE1NjI4MzMxNjMxMTQ3ODM1NzQ4MjAxMzkyNjY4NzQyMTQ5NTI1OTQ0OTc1NzY3NTYwMTQyNzQ5MTU3MTY2NzE0MDY0OTM2OTQ1MzEwMzEwMzU1NjgwNTcyNDgzNDgyNTYyMzk5Nzc0OTY5NTYwMTA1Njk2MzczMDU4MDMzODgyMTAwOTY2ODUwMTk5MjEzMzAiLCJ1ciI6bnVsbCwiaGlkZGVuX2F0dHJpYnV0ZXMiOlsibWFzdGVyX3NlY3JldCJdLCJjb21taXR0ZWRfYXR0cmlidXRlcyI6e319LCJibGluZGVkX21zX2NvcnJlY3RuZXNzX3Byb29mIjp7ImMiOiI1MDg3Mzk4NDExNzQ3Mzc5NjY5MDkyNzU5ODQ5OTEwMDI2OTYzMTk2NjExNjc5MzU5NDYwNDMxMjYyMDE4NzgyNzY4NTM2NTUzNzUwMiIsInZfZGFzaF9jYXAiOiIxODU0NzEwMDA5NDM4NTg5MTc4MzkzMjgzMDk1MzM5NDUzMDQ3OTkwOTYyMjE2NzEyNzk2ODkyMzcyMzA5NjU5NTU3MDY2MzQxMTMxOTY0Mjg5NjA3MTI5MDg4MjMxMDY0NTk5ODY4NDg4MTIzNDMwMzY5OTkxMjI1OTMxMTIyMjY4NjU5MDEwMDA0NDA1OTIzNTcyMzgyMzQzODczNjkxODg3NDQzMjQ5MTcwNTQwNDk4Nzk5MTkxOTIzMjc4NDU4MzcyMzk2NzIyMjM5NDE1NjY3ODIxMDQ4ODA0NDk1NDQ5ODQ3MjcwOTg1MDcwNzY3NjU2NDU4NDM0MTYzNTI3NDAyMzA1NTg5MTg4NzcyNDg4NzE1NjcwOTgxNTc0NzQxMTI1NzYxOTIxNTE3NzYyNzg1Nzk4MjU5MTQ0OTYzMDQyMjg0NzUwMDE5MjAwMjQ4NjgxNjkxOTE5Njg3MDA1MDA4MDUzMjYwODY5NzEyNTEwNzIwNDg5NDAwMjM0NDU3Njk2MDk4NjI0Nzk1MDUwMzQ2NzM2Mjg1MDE3MTU5Mjk1OTA0NTU1NTk0MzMxNzI4MTQ5MzgyODE5NTI2NTc3MDg3NjA0ODMwNDk3MTE0Mjc3MTkyMDU3ODk1MzYxNzI5NTE3NTgxNzg5ODEyMDY3MjcxNTU5MTMyNTI1MzEzNTc0MDEwNTM3NDMxMDY2NzUwNzAzMDgxMTQxNDIyODg5MzUxOTY0MDUyMDU0NjY0MTA0ODE0MzY1Njg3NTcxNjU5MTk3ODQxMjU5NjE3OTI4MDg4NjM0ODY1NTI2MjI4NTkyNTI2NTgwODgzMzIzNjEwNTc5NzU4ODgzMjgwNDcyNTA0OTQ0MjM2ODY5MTYyNzM3NzUwNTI0NTIyMjE5NTM4NjE4OTk2ODQzODU3MzU3MjUxODI5NTIyODgxOTA0NTAwMDU2MjU1NTMwNDgyIiwibV9jYXBzIjp7Im1hc3Rlcl9zZWNyZXQiOiIxMjM5OTQ3ODE4MDA1MTQ3MjQ5MjcyODIxNDI2OTgwNjQ2NTIwMjAwMTc0MzUwMzkzMzQ0NzU1NTU0NDAxNjg1NzQ2NzExMzkzMjEzMjA3NjI3ODI5MTczMjc2OTA2MDg4MDAxMDIxMTMxMzY4NzY4MjI0ODgxNTExMjEwNjY0NzA5OTAxOTQwODA0MzA0OTc1NTUyMzExNzAyMjU3MTYwOTAxNTE0MzIxNzYyNzM0ODUwMSJ9LCJyX2NhcHMiOnt9fSwibm9uY2UiOiIzNzM5ODQyNzAxNTA3ODY4NjQ0MzMxNjMifQ==", }, "mime-type": "application/json", }, ], - "~thread": Object { + "~thread": { "thid": "578e73da-c3be-43d4-949b-7aadfd5a6eae", }, }, - "metadata": Object {}, + "metadata": {}, "role": "receiver", + "updatedAt": "2022-01-21T22:50:20.522Z", }, }, - "3-4e4f-41d9-94c4-f49351b811f1": Object { + "3-4e4f-41d9-94c4-f49351b811f1": { "id": "3-4e4f-41d9-94c4-f49351b811f1", - "tags": Object { + "tags": { "associatedRecordId": "574b2a37-1db1-4af1-a3bf-35c6cb9e1d7a", "messageId": "d14cf505-4903-4dd9-95c2-a7dbc1c048b6", "messageName": "issue-credential", @@ -2651,35 +2700,36 @@ Object { "threadId": "578e73da-c3be-43d4-949b-7aadfd5a6eae", }, "type": "DidCommMessageRecord", - "value": Object { - "_tags": Object {}, + "value": { + "_tags": {}, "associatedRecordId": "574b2a37-1db1-4af1-a3bf-35c6cb9e1d7a", "createdAt": "2022-01-21T22:50:20.522Z", "id": "3-4e4f-41d9-94c4-f49351b811f1", - "message": Object { + "message": { "@id": "d14cf505-4903-4dd9-95c2-a7dbc1c048b6", "@type": "https://didcomm.org/issue-credential/1.0/issue-credential", - "credentials~attach": Array [ - Object { + "credentials~attach": [ + { "@id": "libindy-cred-0", - "data": Object { + "data": { "base64": "eyJzY2hlbWFfaWQiOiJUTDFFYVBGQ1o4U2k1YVVycVNjQkR0OjI6c2NoZW1hLTgwZjdlZWM1LThlNWEtNDNjYS1hZDRkLTMyNzRmYjkzNjFiODoxLjAiLCJjcmVkX2RlZl9pZCI6IlRMMUVhUEZDWjhTaTVhVXJxU2NCRHQ6MzpDTDo2ODE6ZGVmYXVsdCIsInJldl9yZWdfaWQiOm51bGwsInZhbHVlcyI6eyJuYW1lIjp7InJhdyI6IkFsaWNlIiwiZW5jb2RlZCI6IjI3MDM0NjQwMDI0MTE3MzMxMDMzMDYzMTI4MDQ0MDA0MzE4MjE4NDg2ODE2OTMxNTIwODg2NDA1NTM1NjU5OTM0NDE3NDM4NzgxNTA3In0sImRhdGVPZkJpcnRoIjp7InJhdyI6IjIwMjAtMDEtMDEiLCJlbmNvZGVkIjoiNDEwNjEyOTM3ODA0NjIwNzU1MTQyMDgyMjM4OTc1OTA0MDc4MDA3NDg1NDMwMTQ5OTE1ODg1MjI3MjExOTM4ODY4OTgxMTk3NTM2MjQifSwiYWdlIjp7InJhdyI6IjI1IiwiZW5jb2RlZCI6IjI1In19LCJzaWduYXR1cmUiOnsicF9jcmVkZW50aWFsIjp7Im1fMiI6Ijg5NjQzNDI5NjIzMzI5NjQ1MDM2NDU5MjU4NzU2Nzc5MDY3ODczOTc4MDg1ODc3MDM3MzU1NzMxMjk1MzA0NzY5ODAxNDM5MjI2MDI4IiwiYSI6IjUzOTQ3MDU1MTU0OTEyMTE2MzM0MzU2NTMyMjEyMTM5MjMxNjE2NTcwMzU3MjkwNzcyOTkxNjM4NTU1MjU2Mjc2NjgyODY5NDM2MTI5OTEzNzk2MzQwOTA1Nzk3Mzc2OTI5NDE0MTIwNzMwMjI5NjQzNjMwNTI2OTcyOTUxNDk1NjA5NDcwMDU0NzEzOTY1OTE5NTU3MzM3NDkwMjUyNTI1NTM5NzI4MjA0NTI2NTU0MDcyMDYyOTcxMTA0MTM3NTQ4NzEzMzIzNTUzMTYwOTEzODQ0MDk0MDczOTQ3MjM2OTQyNzgyODIzNDA2NDUyNzIzODY2NjMzMzc2MjI4Mzk2ODE5ODI0MTQ2MjgyNTI4NDIyOTIzMjM1NzEzNTI5MjQ3ODkxMTQzMDE4OTM3ODQ0ODM3NzQ2MDE4MTc5Mjk5ODI5ODQ1MTI4MTgxMDUxOTE4MjE0ODU5Mzg5MjIzOTEzNjUzMjE0MjIxMTk2NDI2OTA2NDM1NDYwNDQwNTgxNDkxNTg5MzMxMzIyMDU1MDU1NjE5NDY0OTEwNTc3OTcyODAyNDM1MDY3OTMxMDczOTI5OTgyOTQ2Njg4NDg5MTIwNTQ1MjA1MzQ0MjQ1NTIzNTExNDc5NjUzNjI5ODIxNTA4NTc3MjI2MzU5MjUyMDA5MjUyNTU2NDA5NTg0MDgwNDY5NDI4NzE1NDQ0MDkyOTA4NzAxNjMwMzE3NzI5MTE3NTYwMzg2NjAyNDA4OTE3Mzg2NjM4NzQ2MDY1NjU0NzQ2OTAxOTA1NjA4MjE5MzgzNzczNjg3NDcxODI3NjE2OTU5MDk3NDU4IiwiZSI6IjI1OTM0NDcyMzA1NTA2MjA1OTkwNzAyNTQ5MTQ4MDY5NzU3MTkzODI3Nzg4OTUxNTE1MjMwNjI0OTcyODU4MzEwNTY2NTgwMDcxMzMwNjc1OTE0OTk4MTY5MDU1OTE5Mzk4NzE0MzAxMjM2NzkxMzIwNjI5OTMyMzg5OTY5Njk0MjIxMzIzNTk1Njc0MjkzMDMwMzU1ODY2NDUxMDY0MDQ4Mzk4OTcyMjU0Nzk2ODY2NDA2OSIsInYiOiI4NzE4MTIwMTY2NjA3NTg4MjIxMDczMTE0NTgxNTcxMDA5NTEwNDUwMzkyNDk0MDM1MTg3MDk5MTQyNzA5NDcyMjYxMDAyMjg5MzI5MzcyMzk1MDUwMzgzOTA3ODUxODc1MjIxODU0NjI4ODc4OTcxNzQ0Njg3MDgxMDM4Mzk2Njc2MzgyMTI0NjEyNDY0NjQxMDQ4NDMxNDkwMTYzMDgwOTU0NTc3MjA3OTkxNjg3NzU0NjQ4MTYwNzM5MjQ2ODUyMjMxMjU2NTk0MTg4MTAxNDU2MjgzNjc3MTA4NTMwNjY1NDQwNTUwMDY0MjgxODI3NzUxNjA3NjM1ODE2MjczNDU3MTc4MzAyNjEyOTI5ODMyNDMzNDc3ODk0ODMzMDc2MDA5OTE4MTc1MzI4OTUzNjg1MjEwOTQ1ODg0MTQyMDg0Nzk4ODMzNzM2OTExNDcwMTkwOTYwMjI3MzAyMzI2NjQ2NjE2NjUwMjY4OTU3NDcyMTI3MTA2Mjk3NzQ0NDg3MDUzODY2NjI0NTk4Njg1MzA0MzA0OTMzNjAxNDczNjY4Njg1NDg5MzYyMzQ2NzE5ODA4MjgxNzYxNjc0ODQyMzE5NTY5Mzk1Nzk1NTQ5OTA4MjAyODI0MjgyOTc1MTI3MDA0MzM0NTkwNTYzMTI3NjU3NDM3MjQ2MDQ3OTUyOTk0OTIyODQ3MzcxMzY4NDM0OTE3MDM1Njc4ODA2NjM1OTQ0ODY4Njc5MDY1NDc5Njk1MDU5NzkyNzUyMzk5NDcwMzUzMDI3MjEyNTg2OTc1Mjk5MTk1NDcwNjY0NDMzMDIyNTQyODg4MzI4OTA0Mjg2NTIxMzM5Nzc2OTkxMDYzNzA1MTI2NjA4MDY4OTA0Mzg2MDc4NzA5NTE3NjU0OTE3MzI0NjExMzkzNTM4MDkyNTQ3NzQ0OTM2NTM1NDkwODcwNDU4NjQ3NjY2OTU3MjA5MDk4MDU2NzIwMjAzOTAxMjI3MjU2NDM5NTkwNTA0MzIwOTI3NTc1ODA2NjE0NzA4NTU3MjAxMTAxODczODc4NDg1NjM4MzQ2Nzk1NjE4NDQxOTQxMjQyODc5ODMyNjQ5ODEyIn0sInJfY3JlZGVudGlhbCI6bnVsbH0sInNpZ25hdHVyZV9jb3JyZWN0bmVzc19wcm9vZiI6eyJzZSI6IjIxOTkzMzcwNTI0MjIxNTM0MTM0MTc4MDM3MDIyMDEyMzE4NjQ2MDE4MTk0MDIwMjgxNzY4NTQ4OTUyNjM5ODg4MDE2NDQ1NTM2NTQ2MzczMzkwMDU5NjY1Nzg4OTc2MDE0OTUzMTU2MjA1MTA0ODU1NTM1NjEyODY5Mjg5NjgyOTQ1ODI4MDQxOTMxMzc1ODY4NjE4OTE0NjUwNTc5ODM2NzI0NDE0MDMxMjU3MjU2MzkxNjg2OTQ0NjQyMzg3NTIwNjExNDQ5ODM1NTgxNDMzMDMzOTQ4MTA4OTE0MzI2NzkyNDU5MjQ0Mzc0MTgyOTQ4MDQxODIzMTg3NjY3MjE2MDI5OTEwMTAzMTM1MjE4NjY5ODc5MDk5ODA0NTA0NjI1NTAzNDM2NTAxOTk5ODkwODIyNjcyMjYwNzc1NDIzMzIxNTQ0MDk0Mjk2NDI0Nzc2Njg2MDI1MzU2MjMwOTY0OTQyMzc3NTY0NDUwMDk4NTgyMzg2Nzg0ODc3OTQwNTI2ODg0MzgzOTcxOTE4OTE3ODIyOTkzMDIzMDU2NjU1NTg2NDI3MDAwMzQ2OTcwMTI1MjA2ODg0NTkyNzkwMDU4NTAyMzkxMzUwODIyNjg1NDYyMzY0MzE5NTMwOTI0MjM4Mzg2MjkwMDI5MTQ1ODAzNTgxODA2MDQ1MTYzNDMzNTQ2OTAwMzQzNDg0MTg2NTEyMjIzODYwODY3NTI3ODExOTkyOTQwMjMxMzgzOTM4MjI0NjEwMTk0NDc1NjczMDYyMDI3MDgwOTI5NDYzMjU0NjIxMjI1NTg3MDg0NTUyODEyMDgxMDE3IiwiYyI6Ijg2NzE3OTAzNTAxODI5MzU5NDk4MjE3NDU5NjE3NjgyNTM4NzIxNzQwMjE5MDM5MDUzNjQzNTE3NDU0Nzc2NTUzMzA3MzU1OTkxMjE5In0sInJldl9yZWciOm51bGwsIndpdG5lc3MiOm51bGx9", }, "mime-type": "application/json", }, ], - "~please_ack": Object {}, - "~thread": Object { + "~please_ack": {}, + "~thread": { "thid": "578e73da-c3be-43d4-949b-7aadfd5a6eae", }, }, - "metadata": Object {}, + "metadata": {}, "role": "sender", + "updatedAt": "2022-01-21T22:50:20.522Z", }, }, - "4-4e4f-41d9-94c4-f49351b811f1": Object { + "4-4e4f-41d9-94c4-f49351b811f1": { "id": "4-4e4f-41d9-94c4-f49351b811f1", - "tags": Object { + "tags": { "associatedRecordId": "ad644d8a-48a2-4c55-b46d-7a7f1a9278c7", "messageId": "e2c2194c-6ac6-4b27-9030-18887c79b5eb", "messageName": "offer-credential", @@ -2691,51 +2741,52 @@ Object { "threadId": "e2c2194c-6ac6-4b27-9030-18887c79b5eb", }, "type": "DidCommMessageRecord", - "value": Object { - "_tags": Object {}, + "value": { + "_tags": {}, "associatedRecordId": "ad644d8a-48a2-4c55-b46d-7a7f1a9278c7", "createdAt": "2022-01-21T22:50:20.522Z", "id": "4-4e4f-41d9-94c4-f49351b811f1", - "message": Object { + "message": { "@id": "e2c2194c-6ac6-4b27-9030-18887c79b5eb", "@type": "https://didcomm.org/issue-credential/1.0/offer-credential", - "credential_preview": Object { + "credential_preview": { "@type": "https://didcomm.org/issue-credential/1.0/credential-preview", - "attributes": Array [ - Object { + "attributes": [ + { "mime-type": "text/plain", "name": "name", "value": "Alice", }, - Object { + { "mime-type": "text/plain", "name": "age", "value": "25", }, - Object { + { "mime-type": "text/plain", "name": "dateOfBirth", "value": "2020-01-01", }, ], }, - "offers~attach": Array [ - Object { + "offers~attach": [ + { "@id": "libindy-cred-offer-0", - "data": Object { + "data": { "base64": "eyJzY2hlbWFfaWQiOiJUTDFFYVBGQ1o4U2k1YVVycVNjQkR0OjI6c2NoZW1hLTgwZjdlZWM1LThlNWEtNDNjYS1hZDRkLTMyNzRmYjkzNjFiODoxLjAiLCJjcmVkX2RlZl9pZCI6IlRMMUVhUEZDWjhTaTVhVXJxU2NCRHQ6MzpDTDo2ODE6ZGVmYXVsdCIsImtleV9jb3JyZWN0bmVzc19wcm9vZiI6eyJjIjoiMTEzOTY4MTg4OTM2OTQ5MzcyNzU3NjU2NzI1MjU1MTQ3NDk1OTI5NTM0MjQ5NjU1MzY4NTMzMTY4OTIzMjU4NTA2OTUzOTk3MTI2MDEyIiwieHpfY2FwIjoiMjM5NDkwMjQ4MjE4MTExOTQ1MjIxOTQ1ODcyMTE4MjQzNzA3NjE5OTQ4MzQ0MjU1ODM5ODI4NTU3NjkyNTE3NDExNDMwNDgwNDgwMTkxMTMwMjM0OTg5ODk0NzIyNDE2Nzk1MzUzODAwMDk3NDUxMjI5NDE4MzQ0MjEyOTI3NDk1NjI2NTc4MTk2ODUxMTcwMTI0MDI1NDk1MDExMjc0NjU1NjQzNjkzNTE1ODczMjA5OTczMzgwNjA3MzQxNzQzMzIwNTY0NjkwMzcxOTgyNDIxNTQyMzQzMTMzNTMxOTcxMTk4NTA4NDk5MjYyNzUxNDMyODg0NzgzMzc1MTAzODI0OTE3NzEwODAxOTE3OTc1OTM2OTg4OTIwMzYyMzA5NjE4NTgxMzY0ODE5NTA5ODYxNTE3NjI2ODc3OTUzMzMzMTkzMDExMjA2NDA5NDQ1MzA0MzEwMjUzMTU5OTE1NzYxOTI1MDY1MTg3Mjk1OTg1MzU0NDY1NjY5MTMxNjgwMzc5MzY0ODk0Mzc3NTYxODcyMjcxNDY5Mjk1NzY4MTc4NjQ2NDMxODI3NjI1MTQ3Mzk4MDg1ODI3NTUzMDAzNjIyMDM1ODM1MDg2NzE1NjgyMDA5MzgyNjgxNDc3NDc4ODQ0MDEyNDQ5NTE2NjYwODMwNDMwODQ5ODMxNjAxNDk3MTk3MjczMTIzNjg1NTE0NDMwMjY5OTkxMzMzNDI1Nzk0NjAwMzc3Mzk3NDMwMjg0MjIyMjQ1OTgyMjI1NTE3MjQ4NzA5NTczMzEwNjM5NzQyNzc2NjMyMzM3MDM0Nzk4NDY3MTAwNDczNDUxNTMzMTg1NDg5NDU0NjUyNTgwMjcxNjgyMDQzOTc4MDY4NDc4MjM1MjM5NjMzMTk0MzE4NDcxNDM2MjMwOTg1NTQ1MzAyMDQwNiIsInhyX2NhcCI6W1siZGF0ZW9mYmlydGgiLCIyNjMyMjI0MDEyMDg0NjM3ODA0NjA4MDE5MTM2MzIyNDkzMTE1MzA1MTA2ODQ1MTA0NTE4MDE4MjY2MjU1NjMyNDM0MTMzODY0MTIwNzUxNDkyMDAyMTI4MzU3NTcwMTY4MjE0OTU3OTgzNTQ3Mjk0NTczOTExMzk3MTQwNDAxNDk4MzQ0NzE0NTA5MjEyNDA2NTYzNzgyOTc4ODUzNDgzMTM4NTA1NTA3OTcxNTY3MTgyOTQ1ODQ5ODI3MDU0Nzg4NjA3ODgwOTU5NDE1MTYwMjU0OTE2MDExODkyNTIzNjUzODA1NTk2MDQ3NjA5Mjg3ODA4ODg2MzcwOTM5NDA3Njc5NTE3MjUzOTUxOTUzNDgxNDkzOTMzNDI3NTA5OTUwNTY5NjUwMTc4MjQwNTg0ODI4NzgzOTAxOTA0MjI3MzE1OTEyNzQ0NDYyODc3NDI3Njg3MDEzMDkyNDY1NTc5NzY0OTk2MTc2NTU1NDg1Nzc2Njc0NTcxMjcwNjU4NjAyMTk3OTExNTYzMjY0MzEyNTgzNzMyNTE3NjI3MjY3MzQ1MzEwODcwMjk2NTk4NTEyMDc0NzczNDQyMTExNjY4NjM0MzYzNjkzMDkxNzQ2MjE1MDQwODg1NTcyMzAzMDU4MDUwNzc3Nzg4Mzc5NDY3MzMyNDUwOTY5NTg1NDE2NzU2NzA2MzcxMzMyOTk5MDg1NjM5MDU4Nzk4ODE4MjkwNzEyNDk2NDk0NTY1MzgxMjI0NzUyMTA2OTQyMTg2OTc3MzUwNzE2NjI0MTYxMjIwNjExMDY3MzcxNTM0NjYyNTc0MzY1NTM0MzEzNjAwMTQyODk2MDkwMDQ3MTI5NjQ3OTEzOTgzMjk0Mjc2MDI2OTA2MTA3NzM4MDM2MjI2MTA4NzU0MTE3OTIwMTg2NDM1MDkwOTU2Il0sWyJuYW1lIiwiMzE2Nzc0MzUwOTgzMjI4Nzg1MDcxNDg1NjYzOTM0MTYyNjQ1MzE2NjQzMTc5Njg2NTg2MTU2MjgwMjg3Njg5Nzg0NTI1Mjg5MzY0NDg5NDMxNjEwMTc1MzUwMDgzMzUzMTg3MTIwMTE1MTU1NjUxOTYzMjcyODM4MjcyMTgyNTI2MzUyNjMyMzI5MDY2NDIxMjM3OTM3NTc4MjY4MjkwNzU4MDgwNjI0MjE3MDM0NDU5MDUyMjY2Njk5NjQzMTg2MjkyNjcxNDk1ODEwMDU4Mjg5MzA1MjI4OTQ2MTQ2MTkzMDAzNDk0NDgwNzY0NDY5Nzc1NzM5Njg0NzcxNjMyNDIzNTM5MjE1MzIxMjc0NTQ4NDU5NzE4NTM3NTMzMzE0MjYwMjcwNzE0MTkzNjc4MTEzMDg5MDUxODI1MjA2MTAyMjQ0MTc3NzAwNDk3MTYxMDM2NjgwNDYzMDUxNDcxNDk3MTgzNzc3Nzc5Nzc2Nzc0MTUzNzQ1NjEwNzc3NzgyMDYyODA3NjY5MjE2ODA2NDgxNzAzNTU5NDk5MTAyNTc5ODAwNjgxNTQxMjg3MTk0MTAwNzg4MDMxNDE3Nzg5MzQyNjk2NTQyNTA3NjE5MTgzNDIyMTc0OTk5NzQ2ODM3NjY4NTg0Mzk0NzQxNzI5MDY2MjgwNjYyMzAyMDI3NTczMDgwMDY5NTE5MDgxOTA5OTA3MzQzODAxNzg3MjgyMTEyODY2NzkwNTI2MDIwMjk4NjM2ODY3Njg2NTE5MjQ0NTg2MTg1NzMxMTE0ODk1ODU3NjkzNTQxMTIwOTc2NTQ5MzYwNDE1MTkxMjI4ODA4MzE5NTcxOTU4NTkxNDc4NDYwNzMwMTg2NDQ4MTU3NjU3OTkyOTI4NTM2MjgxODU2NjAzNjU5NjM3OTE2NzE0OTk0NTU0NSJdLFsiYWdlIiwiMTY5MjgwMDQ2OTQyNDI3NDY1ODE5OTE3MzEyNTkzMzEyMzkyNDYzMDQxODc5MzIwMjM0NDU4NjQ4Mzg2MzI4MTE5MDQ5OTIyMzc1NjkxNzI0ODYzMDM3NDMyODU4OTQ4MTIyNzI2ODQ5NDU5NDcxODA2NTU4NzYyMzU4MTgzMzU3OTYwNDk3NTMyNjUzNzE4ODE0NzQ1NzY2ODc3OTI2NzU2NTQ3NjQwMjUzMTczMDYzMjY0Mzc0OTAzNTgwNjEwNDMxMTM2NTA2NjQ1NjE5NzYzNTE1Nzc3MTkyNjU4ODk0OTc1MDMyODAzNzM0MTE5MjM5NjcxMjgwOTQyOTkxMTg2MjYyNjYzNTM5NDU3ODc1NjY1NDcwMTAxMTEzOTUyOTY2MDQyMzU4NDQ4ODE1NTk5MDgzNTU4NTIyMDQ1OTI3NDI0NjI5Njk4MTgzMTUzNzUyMDA4MzM5NTI1NTYxMDI0ODg2MzUzOTc3NzA1ODE5Mjc1MzQzOTg3MzMzODMxNjU0NzA4ODI3NDI0NzMzNzcyNjI3MTA2OTgxNjE5NDY0MzUwNDU3NzE4NzM2MDA0NjEyNzQ0OTAyNDA5NjA0Njk4NzkzNzI0MTc1MDA4OTUzMDMyMDgxMTQ2OTE3MTM4ODc4NzQ4MDM0Mzg1NDQxNTIxMTU5ODM2NDIwMDEzNTQ1NTQyMDk2NDIwODA1MDYxMzI5MTkwNzczNzIzMDYxMjE2NDIzNjczNTM1MzU1OTc5MzY1Njg0MzM2NjEyOTU2NjkxNDA4NDQ5MjE4MjcwOTYyODUyMzQwMTQ2MDAwMDYzNzA3NzU5MzUxODM0MTQ2MDI4MTYyMTEyMzU4MzAzNDQ1OTcwMTg3MTk3OTQ5MDcwNzE4NzQ4OTI4NjM5MDkyMzY1MjExMjgyODY0NDE3OTcwOTg5OCJdLFsibWFzdGVyX3NlY3JldCIsIjk4NjY2MDAzNjA2Njc3MjkxODM1MjEwMzA1NDczNTA3NjU0NTM1OTgxNDYxODkyNjY5NjI5OTE1MzQ5NjU3NjY5MTI5NzAxNTUwNzUyNTU2NjMxMzU4MzU3NjEzNjg3OTgyNTQzNTcyNTc5ODEzOTgxMzI4MDM4NzY5OTcxMzAwNTE0NDI2NzAxNTM2ODE1ODI3MDgzNDY5MzEzOTQ0ODAzMzIzMDUzMzUyNDkxMTIwMDUwNzkyNzA4NTQzMTE2NTM1NjA2NTQyNDY3OTcxNTUxODA4MzQyOTk1OTM4NzQ2NDQ4NjMyNTY4NjU0NjA4MzI1NDk2NjM3Mzc5OTQ0NjA5MDU3Mjc2OTE0OTQxNzE4MzU2MTYzODYyMzI2MDc3MDUzNjIyMDI3OTE2MzIzNzAzMDE2MTc4NDQ3MDEwMTc1MjI1MTM0NjE3NTcxMTgzMjcyNzMwNjQxNzI3Mzk2MzM4ODk2NTUyNzM4NzUwMDA4MTQ3MDExNjkyNzIwNzI4NDY2MjcwNDQ2MDg4NjEyMDg2MDExMzg2NzQxOTMzODM4ODQ1NjkzMTM1NzcyODk0MDIwNTM4NTU3ODI1MjA3OTkxNDAwODIyNjg4OTgwNTg4MjgzMTY0MzAxNTY1NjAyNDAzNTI4MDE2MTczNTk5MTM4NzI5ODEyOTE2MTYxNDEwNjgyODU4MzU4MDE3ODI1ODUyMzY2OTgzMDQzMzM4ODY2MzI3MzM3MTE0NzcyMzM5NTUyNTYzNzU0NzQ0NTA5MTYzNzYzNjAyNTI0NjgxNTUyNjIyOTYwNjM4MzE2OTc0NjI4MjQyNjQ5NjYyMjQyMTkzODMxOTUyMDE0MTAxNTA3Njk2MjIxMDU5NDE5MTcyNzMwNjE2NDE2NzEwNTMzMzc2NjI4MjQ4NzkxMTUwNjMxMDMxOCJdXX0sIm5vbmNlIjoiNTQzODYyOTczNzUxMjk1OTk2MTAzNjA3In0=", }, "mime-type": "application/json", }, ], }, - "metadata": Object {}, + "metadata": {}, "role": "sender", + "updatedAt": "2022-01-21T22:50:20.522Z", }, }, - "5-4e4f-41d9-94c4-f49351b811f1": Object { + "5-4e4f-41d9-94c4-f49351b811f1": { "id": "5-4e4f-41d9-94c4-f49351b811f1", - "tags": Object { + "tags": { "associatedRecordId": "ad644d8a-48a2-4c55-b46d-7a7f1a9278c7", "messageId": "edba1c87-51d3-4c70-aff2-ab8016e1060e", "messageName": "request-credential", @@ -2747,67 +2798,66 @@ Object { "threadId": "e2c2194c-6ac6-4b27-9030-18887c79b5eb", }, "type": "DidCommMessageRecord", - "value": Object { - "_tags": Object {}, + "value": { + "_tags": {}, "associatedRecordId": "ad644d8a-48a2-4c55-b46d-7a7f1a9278c7", "createdAt": "2022-01-21T22:50:20.522Z", "id": "5-4e4f-41d9-94c4-f49351b811f1", - "message": Object { + "message": { "@id": "edba1c87-51d3-4c70-aff2-ab8016e1060e", "@type": "https://didcomm.org/issue-credential/1.0/request-credential", - "requests~attach": Array [ - Object { + "requests~attach": [ + { "@id": "libindy-cred-request-0", - "data": Object { + "data": { "base64": "eyJwcm92ZXJfZGlkIjoiRUg2OTVENjRRd2hWRmtyazFtcDQ5aiIsImNyZWRfZGVmX2lkIjoiVEwxRWFQRkNaOFNpNWFVcnFTY0JEdDozOkNMOjY4MTpkZWZhdWx0IiwiYmxpbmRlZF9tcyI6eyJ1IjoiOTcwNjA5MzQ1NDAxNzE0NDIxNjQzNDg0NDE0MTQwOTA0NzMzNTQ4NTA4NzY0OTk5MTgxNzY1ODI3MjM3ODg3NjQ2MzQzNDYyMTY0ODA3MTUxMjg1ODk2MDczODAyNDY1MDMwMTcxNTIyODE4ODY4Mjc2ODUwMzE0NzQzMjM3ODc3NDMyMDgxMTQwMzE5ODU5OTM5NjM0MTI4NzkzOTk4NDcwMTUzNTQ0NjgxNTM5NDg4NzEyNDE5MTk3NzcxODE1NjU5Nzg1NDE5NTA1ODEyODI4NzYxOTI4MzExODczNjA5NDYwMjExMzQ4OTAyNDk4NzYxNjc5OTIzNzA2NjUwMDIzODg4ODU4NzE1MTYxMTIyMzA5MTc1MTE3MTk0NDMwNTI1ODY5NjcwMDEzMTgxNTkzNjI4NDQzMjk2MDI0MDE5NTc4MzIzODQwNzk0OTQ0MjIyODE1MTQwNTM2Mjc3ODQwMjU2ODc2MDE1MzUwNDgzOTE2MjYzMDgyODM5NzI2NzAxNjg4NjcxNDY0MjA3MzQxMTgwMjg3Mjk0Njg4NDA3NzQ3MDk1NjA0NzQ3NzA3OTc2Nzk2MjU0MTQ2NDQ0NTY5NzQ4MTk4OTMwMjkyOTkzNjY1ODk2MTUyMDMyNzQwODY3OTgwMjczMTMxMDM3NjkwNDkzNDU1Mjc4NDc3MDc3NjE0OTU2NjgzNjgxNDc5NzY3Njg0MDI4MzU5NzE4NzM0ODEyNzcyMDIyOTIwODQ3NDIyNDYyOTAwMjczMTcwMTU2NzQyMzUyMDQ2NDYyODI4NzAxMTE2MzU0MTkwMDY5MDE0NTIwMSIsInVyIjpudWxsLCJoaWRkZW5fYXR0cmlidXRlcyI6WyJtYXN0ZXJfc2VjcmV0Il0sImNvbW1pdHRlZF9hdHRyaWJ1dGVzIjp7fX0sImJsaW5kZWRfbXNfY29ycmVjdG5lc3NfcHJvb2YiOnsiYyI6IjE4MzE5MTUyNjg0NDkyMTM0OTc4MzkzNDE3OTY5NjQ5MDIyNzMzNzQzMTQ5NTUwNzAwNzc5ODk0Nzg1MTg3MTA1OTkyNjk4Mzg5MjAzIiwidl9kYXNoX2NhcCI6IjQ0NzA4MzAwOTUyNzA3MjA3NjI3NjA3NzM4MTI2NDgxNzA3OTA1MDcwNjEyMzQ5OTIxNTAxNTYyOTA2MzgyMzE0MDE4MzQ4MTAxMTE4MDI4MDIyMjk5OTgyNTEwMjI5ODM1OTQxMzY1MTM4Njg0MTU1OTEyOTE3NzYwMjgwOTIyNDk1MjE1ODA2NTM3MzA5NDU5NDA3NjcxNDgyNDA0NDMwODU4MzU5ODU3MDgzNzg1Njk1MTYzMjkwMzEyNzMzNDAxNjY1NDk5MjUwMDQ0NzkwODk4OTA4NzIzNzE1OTc0MDYwNzgyNDEzODYzMTU0MTUxNjg2OTM3ODY4MDM5MDU1Nzc1MzA5MjQ0MTYzOTUxNzgwMTgxNDk5MDM5MDgyMjcxNzgzNTgzNTkxMDIyNjYwMDYyMDQ3MDQ2NTEyMTM0NDU5OTI1MTgyOTg2MTkxOTAwNTQwMDg4MjE3Mzc2NjM4MjEzNTI0MDUxNjcxNzg3ODY0ODQ2NzIxNjk5NjQzNDk0MTI2MjA3NTg2MjgwNjQ5OTc4ODE2ODEwMjM5OTAzMzU0NzIyOTI2NTUxODYyNTQwMjc4ODU2OTEyNDQ2MDUzMTg4MzI3Mjk4NDc0NjgzMjkwMjU4MjgwNjY0OTgyOTM2NTYyODcwNTIyODA2NzYwMjE1OTI0MDc3ODQ2NjA2NTM0NzI4MjkyODQ2MDQyOTk1NjgxMTQzOTQ5MTU0MTU0NTU2NzQzMDYzMzY1OTIzMzU2OTg1MjQ5ODI1Njk2NzE3MzE2MDk1NzE5MDU2MTE5NTE0NTYwODY0MzUyMDc4ODMyOTYzNjI3Mjk0Njk1ODQ0MTE5NDA1NTMzNTY5MTI2ODExODE3NDYyNjczMzM3OTg4MzA0MDcwMzk5ODYyNTk2ODMyNDk2OTU3NzA4Nzc0NzI5NzAyOTEyNjY3Njk0OTgwMjc3OTI5MDgzNiIsIm1fY2FwcyI6eyJtYXN0ZXJfc2VjcmV0IjoiMjIxNTAyNTExNTYzODg1MTM1NzIwNjg4MzE5Njk5NzE5ODAxNDI4NDgzMTI2MjY0NjI0NTE5OTA4MjM5NTM0MjQ3MDQ3NTIwODgyNDE4Mzk0ODMzNjUwODM2NTI0NDk2MzUzMDkwNzIzOTI1MDc2NDY2ODQ3NjIxNzE4MDA4MDAxNTQyNTMzOTk4NTU1MzA1MDYwNjUzMzkwMjc4MDc2MzE2Mjg5MzcwMzA2MDcyMDYxNCJ9LCJyX2NhcHMiOnt9fSwibm9uY2UiOiI2OTgzNzA2MTYwMjM4ODM3MzA0OTgzNzUifQ==", }, "mime-type": "application/json", }, ], - "~thread": Object { + "~thread": { "thid": "e2c2194c-6ac6-4b27-9030-18887c79b5eb", }, }, - "metadata": Object {}, + "metadata": {}, "role": "receiver", + "updatedAt": "2022-01-21T22:50:20.522Z", }, }, - "574b2a37-1db1-4af1-a3bf-35c6cb9e1d7a": Object { + "574b2a37-1db1-4af1-a3bf-35c6cb9e1d7a": { "id": "574b2a37-1db1-4af1-a3bf-35c6cb9e1d7a", - "tags": Object { + "tags": { "connectionId": "0b6de73d-b376-430f-b2b4-f6e51407bb66", - "credentialIds": Array [], - "indyCredentialRevocationId": undefined, - "indyRevocationRegistryId": undefined, + "credentialIds": [], "state": "done", "threadId": "578e73da-c3be-43d4-949b-7aadfd5a6eae", }, "type": "CredentialRecord", - "value": Object { + "value": { "autoAcceptCredential": "contentApproved", "connectionId": "0b6de73d-b376-430f-b2b4-f6e51407bb66", "createdAt": "2022-03-21T22:50:20.522Z", - "credentialAttributes": Array [ - Object { + "credentialAttributes": [ + { "mime-type": "text/plain", "name": "name", "value": "Alice", }, - Object { + { "mime-type": "text/plain", "name": "age", "value": "25", }, - Object { + { "mime-type": "text/plain", "name": "dateOfBirth", "value": "2020-01-01", }, ], - "credentials": Array [], + "credentials": [], "id": "574b2a37-1db1-4af1-a3bf-35c6cb9e1d7a", - "metadata": Object { - "_internal/indyCredential": Object { + "metadata": { + "_internal/indyCredential": { "credentialDefinitionId": "TL1EaPFCZ8Si5aUrqScBDt:3:CL:681:default", "schemaId": "TL1EaPFCZ8Si5aUrqScBDt:2:schema-80f7eec5-8e5a-43ca-ad4d-3274fb9361b8:1.0", }, @@ -2815,57 +2865,56 @@ Object { "protocolVersion": "v1", "state": "done", "threadId": "578e73da-c3be-43d4-949b-7aadfd5a6eae", + "updatedAt": "2022-01-21T22:50:20.522Z", }, }, - "5f2b7bc7-edfd-47e7-a1d4-aae050df2c4a": Object { + "5f2b7bc7-edfd-47e7-a1d4-aae050df2c4a": { "id": "5f2b7bc7-edfd-47e7-a1d4-aae050df2c4a", - "tags": Object { + "tags": { "connectionId": "54b61a2c-59ae-4e63-a441-7f1286350132", "credentialId": "a77114e1-c812-4bff-a53c-3d5003fcc278", - "credentialIds": Array [ + "credentialIds": [ "a77114e1-c812-4bff-a53c-3d5003fcc278", ], - "indyCredentialRevocationId": undefined, - "indyRevocationRegistryId": undefined, "state": "done", "threadId": "578e73da-c3be-43d4-949b-7aadfd5a6eae", }, "type": "CredentialRecord", - "value": Object { + "value": { "autoAcceptCredential": "contentApproved", "connectionId": "54b61a2c-59ae-4e63-a441-7f1286350132", "createdAt": "2022-03-21T22:50:20.535Z", - "credentialAttributes": Array [ - Object { + "credentialAttributes": [ + { "mime-type": "text/plain", "name": "name", "value": "Alice", }, - Object { + { "mime-type": "text/plain", "name": "age", "value": "25", }, - Object { + { "mime-type": "text/plain", "name": "dateOfBirth", "value": "2020-01-01", }, ], - "credentials": Array [ - Object { + "credentials": [ + { "credentialRecordId": "a77114e1-c812-4bff-a53c-3d5003fcc278", "credentialRecordType": "indy", }, ], "id": "5f2b7bc7-edfd-47e7-a1d4-aae050df2c4a", - "metadata": Object { - "_internal/indyCredential": Object { + "metadata": { + "_internal/indyCredential": { "credentialDefinitionId": "TL1EaPFCZ8Si5aUrqScBDt:3:CL:681:default", "schemaId": "TL1EaPFCZ8Si5aUrqScBDt:2:schema-80f7eec5-8e5a-43ca-ad4d-3274fb9361b8:1.0", }, - "_internal/indyRequest": Object { - "master_secret_blinding_data": Object { + "_internal/indyRequest": { + "master_secret_blinding_data": { "v_prime": "36456944381549782028917743247126995038265466209293312755125557271456380841610111892515020379470931691048072348420844231863825225515560265358581756565441268878364665494094789024845049226122885121039335781567964878826549149370097276812152226343824116049855825405977949749345353074025294938300401262824951638782220004732873597724698990420932910079362747837952520524827009393981876443737452031919055976088763615615890946142630576421462920865811255312740184209214306243871230276622595183415487741608569800898909023830922654063814555128779494528740438076748829436757078504882332589744263200806138145494157659396691564807976032319024007464003538934", "vr_prime": null, }, @@ -2876,11 +2925,12 @@ Object { "protocolVersion": "v1", "state": "done", "threadId": "578e73da-c3be-43d4-949b-7aadfd5a6eae", + "updatedAt": "2022-01-21T22:50:20.522Z", }, }, - "6-4e4f-41d9-94c4-f49351b811f1": Object { + "6-4e4f-41d9-94c4-f49351b811f1": { "id": "6-4e4f-41d9-94c4-f49351b811f1", - "tags": Object { + "tags": { "associatedRecordId": "ad644d8a-48a2-4c55-b46d-7a7f1a9278c7", "messageId": "a340a7b9-f4d4-4892-b7d2-1d3d40e4be48", "messageName": "issue-credential", @@ -2892,35 +2942,36 @@ Object { "threadId": "e2c2194c-6ac6-4b27-9030-18887c79b5eb", }, "type": "DidCommMessageRecord", - "value": Object { - "_tags": Object {}, + "value": { + "_tags": {}, "associatedRecordId": "ad644d8a-48a2-4c55-b46d-7a7f1a9278c7", "createdAt": "2022-01-21T22:50:20.522Z", "id": "6-4e4f-41d9-94c4-f49351b811f1", - "message": Object { + "message": { "@id": "a340a7b9-f4d4-4892-b7d2-1d3d40e4be48", "@type": "https://didcomm.org/issue-credential/1.0/issue-credential", - "credentials~attach": Array [ - Object { + "credentials~attach": [ + { "@id": "libindy-cred-0", - "data": Object { + "data": { "base64": "eyJzY2hlbWFfaWQiOiJUTDFFYVBGQ1o4U2k1YVVycVNjQkR0OjI6c2NoZW1hLTgwZjdlZWM1LThlNWEtNDNjYS1hZDRkLTMyNzRmYjkzNjFiODoxLjAiLCJjcmVkX2RlZl9pZCI6IlRMMUVhUEZDWjhTaTVhVXJxU2NCRHQ6MzpDTDo2ODE6ZGVmYXVsdCIsInJldl9yZWdfaWQiOm51bGwsInZhbHVlcyI6eyJuYW1lIjp7InJhdyI6IkFsaWNlIiwiZW5jb2RlZCI6IjI3MDM0NjQwMDI0MTE3MzMxMDMzMDYzMTI4MDQ0MDA0MzE4MjE4NDg2ODE2OTMxNTIwODg2NDA1NTM1NjU5OTM0NDE3NDM4NzgxNTA3In0sImFnZSI6eyJyYXciOiIyNSIsImVuY29kZWQiOiIyNSJ9LCJkYXRlT2ZCaXJ0aCI6eyJyYXciOiIyMDIwLTAxLTAxIiwiZW5jb2RlZCI6IjQxMDYxMjkzNzgwNDYyMDc1NTE0MjA4MjIzODk3NTkwNDA3ODAwNzQ4NTQzMDE0OTkxNTg4NTIyNzIxMTkzODg2ODk4MTE5NzUzNjI0In19LCJzaWduYXR1cmUiOnsicF9jcmVkZW50aWFsIjp7Im1fMiI6IjEwMzE4MTcwNjEzMDMzODg1ODkxNDkzMzk2MjU5NDYyNDIxMzM3MjY3ODcyNzkyNzczNjgwMDQwMTgyNTE4NDM1MzEzMDYyNjE3MTcxMCIsImEiOiI3MTM3NDExNDU3NjI3MDE5MDcyNjY0ODMzNTQ5MjAzMDQyMTg0MDQ0OTUxNTU5MzYwMDczMzk1MjM4NjkwMzkwNjgxNzA3ODAyNzY0MTE2MzQ4NjE4NjgxOTgzNTM2MTczNTE3MzgxODc5NzQ0MzQyODIzMDkzNzc4MTk1NzYxNzgzMjQ3NDcxOTQ2NDgwODc0OTY2OTQyNjY0NzU4NjIwNzEyNzExODExNTY5NTc1NzMwNTg4NTQwNDI1MjEzNjY0OTg0OTQyOTIzODU5NzQ3MjIzNjA5ODQ1NjIwMjE5NTY1NzYxODY2OTMwMzI3OTYzNTIwNzE5MTg2NjMyNTQ4NzkzNDI3MTQ0NTY0NTAxNjE1NTg1MTI2NzkxNzM5Njg3ODc2MzIxMTQ1NTAzNDU1OTM0MzUxODc0MjQ3NjA0NzAwODYxMTgxNjY4NjUxNzQ5NTExMjY0MzExMDU2NjI5MjM4NjQwNDY2NzM4ODA0NjI0NzU1MzEzODgxMjQzMjkwNDM5ODI4MDE0NzY0MTQ3NDM1MzE3NTY3MjY3MzQ2NTQxMTY2ODIwNTI2MDkyMDAyMDE0NzY5NjE1MzI3Mjg4MTYwMzM0MDg1MTQ1MzQ0Mzc5MzAxMDg1NDc1MDEyNzUzNDIzNzkxMjI4NzM5NDE3MzA0OTM2NDUzNDEyMDYxNjY0MTUzNTM4MjM5MDA0OTUxMDgyODQxMTE1MTIyNDQ1MzkzNzc5Mzc5NDI5NjQzMjMxNDE2NDQzNDM4NDQ1NzI1NTAzMDc0MTM5MzEwOTc2MjkwMTQ2MTIwMDI2MDI1NTczNzIyNTUyOCIsImUiOiIyNTkzNDQ3MjMwNTUwNjIwNTk5MDcwMjU0OTE0ODA2OTc1NzE5MzgyNzc4ODk1MTUxNTIzMDYyNDk3Mjg1ODMxMDU2NjU4MDA3MTMzMDY3NTkxNDk5ODE2OTA1NTkxOTM5ODcxNDMwMTIzNjc5MTMyMDYyOTkzMjM4OTk2OTY5NDIyMTMyMzU5NTY3NDI5MzAxNTQ3NTU5NjM2MjQ0MTcyOTU5NjM1MjY1ODc1MTkyNjIwNTEiLCJ2IjoiODMxMTU0NzI2ODU4Mzg3ODc5NTU5NDUzNzcwNjg3MzIyMzE1NTgyMjYzMTk3NDUzMjMxNTI1ODIzNDIzNjkxMDk5Mzc2NTExNzI0ODAxMzA1MTU0NzY2Mjc0NjQ1OTMzMTAwOTIzMjIxOTgwNDkyNzE0NDQxMTY0NTYxNTIwMDIzMjYzMDYzMzQ4NjQ4NzkzMjkxMzEwNDc0NTU5NDIwMTkzMTA1MjE5NzMxNTAwMTc0ODg0NzQ0MDk1MjU3MDYyMjczODA2OTYzNjg5MjY3NzA1NTg4NTQ4MzU4NzQ2NDc1Mzc1MzAzMjI1OTI3MDkxMzA0NzQ2NTg5MzA1MDEzNjc1ODk0MzIxMDkzNjE0NzIxMjQwNDAzNDE5OTM5OTk0Mjg5NTU2MzY0MDExMTg2ODQ2NjMxNTA2OTU0NDg0NjM4NjgwMzEyMDA2Njk0MjcwOTkwNDU3NTk3NjAyNzc5MjUzMDc3MTg3NDgwNTg5NDMyNzU4ODgwMjY3NzA1NzMyMjg3Nzc0ODczOTI3MDExMTQ1MDE1NzgyNjE5NzI4NTAxNjI5MTE4ODE1ODM2NjU2OTMzMzcwMzgwNDk4NDk5MDE0MDEzNDI1NDMwMjMwODQzODc0OTk3NTg0NTY3NTA0Mzg3OTE4MjQxMzMxNTM1NDk5MTQxMjU1NzQzNjQ0MzgwNTQ4ODAxNDUwNDEyMzQzMTAxODc4Nzg3NzIzOTIxMDU5NjQyNDg2MjE3NjE0MzQyMDc0MTQ3ODk1ODA2MzQ1NTQ0Njk3NjI2MzcwMDY1MjYxMjQ3OTM2NzMwMzMyNTkyMjM3NDY1MjIyNTQ1MjE4ODc4MDk0ODk0NTE0ODU2ODUyNTU1NjI4MjIwNDg2MTU5MjcyMjIxMjM3MDkwMjc4NjUyNDM2MzcyNTE4MDgzOTUxNjAwNzI1ODA1MDYwNjExMTkyNzEyOTI3MjQ2MTUxMDU4NzU5NDk2MzQ3NjA0NDQwNDcxODcwODEyNzMwODE3MTU4NzQxNDQ1MDA0OTg2MTgyNDk5NDA4OTQxMzk2NjcwMzEyOTU0NDQ1MTk1NTM5MTM5MzIwMDI4MTI3NzMyMDQ0MSJ9LCJyX2NyZWRlbnRpYWwiOm51bGx9LCJzaWduYXR1cmVfY29ycmVjdG5lc3NfcHJvb2YiOnsic2UiOiIyOTQ0ODU2NTEyNDk3MjM2MTc2OTQ3OTMxNzM1Mjk0NDc4MTU5NTE2ODM4OTExNDEyMDE3MTI1NTAyNjc1ODM2Nzk1NTQ4OTczNDMyMTg2NjI0MTc2NjQwNzAxNjczOTk4MjI3NTEzMTA0OTU5OTgzMjk1NjI0MTA0MjkwNjgzMjU0OTI4NTg1MDkwMTI2Mjk5ODczMjY4NDYyODA5NTY4NjMxNDg3MDk2ODgzMDE0OTcwMTcyMzM0MzIwMTM4OTg5ODkzMzcyODIyODU2MTQxMTM5NTQ0MzQyMzExNDEzNDgyMDU0OTYyNjE5NTgzNjU5NjMwMzYxNTc3Nzg0MTQ4NjY3NTgwNjMxODc4NDIwNTgzMDk5OTM1NzE0NDYyMzE5Njg4NDE5MTM4NjY3Nzk2Nzc2MzQwMDk2MDIxMTgwMTU0ODU0Njg1MDEzNzY1MTM0ODMwNjA0MzEyMjI0MjIwNzA5MzQwODExMTI4MDczMDk3NjEyMDA0MTE4NjA0MDI3MTczNjExNTE2ODA0NTQxMzUzODA2OTkxMTg0MDY3MzY1MDE5Nzk2NDM2MDA3NDI0NTM5NzQ4ODQxMjU4NjA1MTUxNTQxNzQzMDk4MTE5NzI1Mzc4MTQ4MTgyNDQ2Njg3NDQ0MjE0ODk1NTE2NDc3MTM4Mjk1OTI1Mzc4Nzg1MTA3OTc5MTYxNTIyNTYyNDk2Njg2MTAzMjg3MDUxNDUyMTE3NTQzMzc4Mzc0MDM5Mjg3NzgxMjAwNzgxMTkyNDQyOTY5MDU1NjIwNTcyODg1NDM5NjU2MTU3Njk2Mzc5MTAyNDc2MTQ2IiwiYyI6IjI4NjYxMjE3ODQ5OTg3ODI4MTA2Nzk5MTAxOTIxNDcyNzgxNDMzNzgzMDE5Mzg0NzAwMDUzMjU4MTU5NDUxNjc5MjMwODI0NzA5NjIifSwicmV2X3JlZyI6bnVsbCwid2l0bmVzcyI6bnVsbH0=", }, "mime-type": "application/json", }, ], - "~please_ack": Object {}, - "~thread": Object { + "~please_ack": {}, + "~thread": { "thid": "e2c2194c-6ac6-4b27-9030-18887c79b5eb", }, }, - "metadata": Object {}, + "metadata": {}, "role": "sender", + "updatedAt": "2022-01-21T22:50:20.522Z", }, }, - "7-4e4f-41d9-94c4-f49351b811f1": Object { + "7-4e4f-41d9-94c4-f49351b811f1": { "id": "7-4e4f-41d9-94c4-f49351b811f1", - "tags": Object { + "tags": { "associatedRecordId": "5f2b7bc7-edfd-47e7-a1d4-aae050df2c4a", "messageId": "578e73da-c3be-43d4-949b-7aadfd5a6eae", "messageName": "offer-credential", @@ -2932,51 +2983,52 @@ Object { "threadId": "578e73da-c3be-43d4-949b-7aadfd5a6eae", }, "type": "DidCommMessageRecord", - "value": Object { - "_tags": Object {}, + "value": { + "_tags": {}, "associatedRecordId": "5f2b7bc7-edfd-47e7-a1d4-aae050df2c4a", "createdAt": "2022-01-21T22:50:20.522Z", "id": "7-4e4f-41d9-94c4-f49351b811f1", - "message": Object { + "message": { "@id": "578e73da-c3be-43d4-949b-7aadfd5a6eae", "@type": "https://didcomm.org/issue-credential/1.0/offer-credential", - "credential_preview": Object { + "credential_preview": { "@type": "https://didcomm.org/issue-credential/1.0/credential-preview", - "attributes": Array [ - Object { + "attributes": [ + { "mime-type": "text/plain", "name": "name", "value": "Alice", }, - Object { + { "mime-type": "text/plain", "name": "age", "value": "25", }, - Object { + { "mime-type": "text/plain", "name": "dateOfBirth", "value": "2020-01-01", }, ], }, - "offers~attach": Array [ - Object { + "offers~attach": [ + { "@id": "libindy-cred-offer-0", - "data": Object { + "data": { "base64": "eyJzY2hlbWFfaWQiOiJUTDFFYVBGQ1o4U2k1YVVycVNjQkR0OjI6c2NoZW1hLTgwZjdlZWM1LThlNWEtNDNjYS1hZDRkLTMyNzRmYjkzNjFiODoxLjAiLCJjcmVkX2RlZl9pZCI6IlRMMUVhUEZDWjhTaTVhVXJxU2NCRHQ6MzpDTDo2ODE6ZGVmYXVsdCIsImtleV9jb3JyZWN0bmVzc19wcm9vZiI6eyJjIjoiMTEzOTY4MTg4OTM2OTQ5MzcyNzU3NjU2NzI1MjU1MTQ3NDk1OTI5NTM0MjQ5NjU1MzY4NTMzMTY4OTIzMjU4NTA2OTUzOTk3MTI2MDEyIiwieHpfY2FwIjoiMjM5NDkwMjQ4MjE4MTExOTQ1MjIxOTQ1ODcyMTE4MjQzNzA3NjE5OTQ4MzQ0MjU1ODM5ODI4NTU3NjkyNTE3NDExNDMwNDgwNDgwMTkxMTMwMjM0OTg5ODk0NzIyNDE2Nzk1MzUzODAwMDk3NDUxMjI5NDE4MzQ0MjEyOTI3NDk1NjI2NTc4MTk2ODUxMTcwMTI0MDI1NDk1MDExMjc0NjU1NjQzNjkzNTE1ODczMjA5OTczMzgwNjA3MzQxNzQzMzIwNTY0NjkwMzcxOTgyNDIxNTQyMzQzMTMzNTMxOTcxMTk4NTA4NDk5MjYyNzUxNDMyODg0NzgzMzc1MTAzODI0OTE3NzEwODAxOTE3OTc1OTM2OTg4OTIwMzYyMzA5NjE4NTgxMzY0ODE5NTA5ODYxNTE3NjI2ODc3OTUzMzMzMTkzMDExMjA2NDA5NDQ1MzA0MzEwMjUzMTU5OTE1NzYxOTI1MDY1MTg3Mjk1OTg1MzU0NDY1NjY5MTMxNjgwMzc5MzY0ODk0Mzc3NTYxODcyMjcxNDY5Mjk1NzY4MTc4NjQ2NDMxODI3NjI1MTQ3Mzk4MDg1ODI3NTUzMDAzNjIyMDM1ODM1MDg2NzE1NjgyMDA5MzgyNjgxNDc3NDc4ODQ0MDEyNDQ5NTE2NjYwODMwNDMwODQ5ODMxNjAxNDk3MTk3MjczMTIzNjg1NTE0NDMwMjY5OTkxMzMzNDI1Nzk0NjAwMzc3Mzk3NDMwMjg0MjIyMjQ1OTgyMjI1NTE3MjQ4NzA5NTczMzEwNjM5NzQyNzc2NjMyMzM3MDM0Nzk4NDY3MTAwNDczNDUxNTMzMTg1NDg5NDU0NjUyNTgwMjcxNjgyMDQzOTc4MDY4NDc4MjM1MjM5NjMzMTk0MzE4NDcxNDM2MjMwOTg1NTQ1MzAyMDQwNiIsInhyX2NhcCI6W1siZGF0ZW9mYmlydGgiLCIyNjMyMjI0MDEyMDg0NjM3ODA0NjA4MDE5MTM2MzIyNDkzMTE1MzA1MTA2ODQ1MTA0NTE4MDE4MjY2MjU1NjMyNDM0MTMzODY0MTIwNzUxNDkyMDAyMTI4MzU3NTcwMTY4MjE0OTU3OTgzNTQ3Mjk0NTczOTExMzk3MTQwNDAxNDk4MzQ0NzE0NTA5MjEyNDA2NTYzNzgyOTc4ODUzNDgzMTM4NTA1NTA3OTcxNTY3MTgyOTQ1ODQ5ODI3MDU0Nzg4NjA3ODgwOTU5NDE1MTYwMjU0OTE2MDExODkyNTIzNjUzODA1NTk2MDQ3NjA5Mjg3ODA4ODg2MzcwOTM5NDA3Njc5NTE3MjUzOTUxOTUzNDgxNDkzOTMzNDI3NTA5OTUwNTY5NjUwMTc4MjQwNTg0ODI4NzgzOTAxOTA0MjI3MzE1OTEyNzQ0NDYyODc3NDI3Njg3MDEzMDkyNDY1NTc5NzY0OTk2MTc2NTU1NDg1Nzc2Njc0NTcxMjcwNjU4NjAyMTk3OTExNTYzMjY0MzEyNTgzNzMyNTE3NjI3MjY3MzQ1MzEwODcwMjk2NTk4NTEyMDc0NzczNDQyMTExNjY4NjM0MzYzNjkzMDkxNzQ2MjE1MDQwODg1NTcyMzAzMDU4MDUwNzc3Nzg4Mzc5NDY3MzMyNDUwOTY5NTg1NDE2NzU2NzA2MzcxMzMyOTk5MDg1NjM5MDU4Nzk4ODE4MjkwNzEyNDk2NDk0NTY1MzgxMjI0NzUyMTA2OTQyMTg2OTc3MzUwNzE2NjI0MTYxMjIwNjExMDY3MzcxNTM0NjYyNTc0MzY1NTM0MzEzNjAwMTQyODk2MDkwMDQ3MTI5NjQ3OTEzOTgzMjk0Mjc2MDI2OTA2MTA3NzM4MDM2MjI2MTA4NzU0MTE3OTIwMTg2NDM1MDkwOTU2Il0sWyJuYW1lIiwiMzE2Nzc0MzUwOTgzMjI4Nzg1MDcxNDg1NjYzOTM0MTYyNjQ1MzE2NjQzMTc5Njg2NTg2MTU2MjgwMjg3Njg5Nzg0NTI1Mjg5MzY0NDg5NDMxNjEwMTc1MzUwMDgzMzUzMTg3MTIwMTE1MTU1NjUxOTYzMjcyODM4MjcyMTgyNTI2MzUyNjMyMzI5MDY2NDIxMjM3OTM3NTc4MjY4MjkwNzU4MDgwNjI0MjE3MDM0NDU5MDUyMjY2Njk5NjQzMTg2MjkyNjcxNDk1ODEwMDU4Mjg5MzA1MjI4OTQ2MTQ2MTkzMDAzNDk0NDgwNzY0NDY5Nzc1NzM5Njg0NzcxNjMyNDIzNTM5MjE1MzIxMjc0NTQ4NDU5NzE4NTM3NTMzMzE0MjYwMjcwNzE0MTkzNjc4MTEzMDg5MDUxODI1MjA2MTAyMjQ0MTc3NzAwNDk3MTYxMDM2NjgwNDYzMDUxNDcxNDk3MTgzNzc3Nzc5Nzc2Nzc0MTUzNzQ1NjEwNzc3NzgyMDYyODA3NjY5MjE2ODA2NDgxNzAzNTU5NDk5MTAyNTc5ODAwNjgxNTQxMjg3MTk0MTAwNzg4MDMxNDE3Nzg5MzQyNjk2NTQyNTA3NjE5MTgzNDIyMTc0OTk5NzQ2ODM3NjY4NTg0Mzk0NzQxNzI5MDY2MjgwNjYyMzAyMDI3NTczMDgwMDY5NTE5MDgxOTA5OTA3MzQzODAxNzg3MjgyMTEyODY2NzkwNTI2MDIwMjk4NjM2ODY3Njg2NTE5MjQ0NTg2MTg1NzMxMTE0ODk1ODU3NjkzNTQxMTIwOTc2NTQ5MzYwNDE1MTkxMjI4ODA4MzE5NTcxOTU4NTkxNDc4NDYwNzMwMTg2NDQ4MTU3NjU3OTkyOTI4NTM2MjgxODU2NjAzNjU5NjM3OTE2NzE0OTk0NTU0NSJdLFsiYWdlIiwiMTY5MjgwMDQ2OTQyNDI3NDY1ODE5OTE3MzEyNTkzMzEyMzkyNDYzMDQxODc5MzIwMjM0NDU4NjQ4Mzg2MzI4MTE5MDQ5OTIyMzc1NjkxNzI0ODYzMDM3NDMyODU4OTQ4MTIyNzI2ODQ5NDU5NDcxODA2NTU4NzYyMzU4MTgzMzU3OTYwNDk3NTMyNjUzNzE4ODE0NzQ1NzY2ODc3OTI2NzU2NTQ3NjQwMjUzMTczMDYzMjY0Mzc0OTAzNTgwNjEwNDMxMTM2NTA2NjQ1NjE5NzYzNTE1Nzc3MTkyNjU4ODk0OTc1MDMyODAzNzM0MTE5MjM5NjcxMjgwOTQyOTkxMTg2MjYyNjYzNTM5NDU3ODc1NjY1NDcwMTAxMTEzOTUyOTY2MDQyMzU4NDQ4ODE1NTk5MDgzNTU4NTIyMDQ1OTI3NDI0NjI5Njk4MTgzMTUzNzUyMDA4MzM5NTI1NTYxMDI0ODg2MzUzOTc3NzA1ODE5Mjc1MzQzOTg3MzMzODMxNjU0NzA4ODI3NDI0NzMzNzcyNjI3MTA2OTgxNjE5NDY0MzUwNDU3NzE4NzM2MDA0NjEyNzQ0OTAyNDA5NjA0Njk4NzkzNzI0MTc1MDA4OTUzMDMyMDgxMTQ2OTE3MTM4ODc4NzQ4MDM0Mzg1NDQxNTIxMTU5ODM2NDIwMDEzNTQ1NTQyMDk2NDIwODA1MDYxMzI5MTkwNzczNzIzMDYxMjE2NDIzNjczNTM1MzU1OTc5MzY1Njg0MzM2NjEyOTU2NjkxNDA4NDQ5MjE4MjcwOTYyODUyMzQwMTQ2MDAwMDYzNzA3NzU5MzUxODM0MTQ2MDI4MTYyMTEyMzU4MzAzNDQ1OTcwMTg3MTk3OTQ5MDcwNzE4NzQ4OTI4NjM5MDkyMzY1MjExMjgyODY0NDE3OTcwOTg5OCJdLFsibWFzdGVyX3NlY3JldCIsIjk4NjY2MDAzNjA2Njc3MjkxODM1MjEwMzA1NDczNTA3NjU0NTM1OTgxNDYxODkyNjY5NjI5OTE1MzQ5NjU3NjY5MTI5NzAxNTUwNzUyNTU2NjMxMzU4MzU3NjEzNjg3OTgyNTQzNTcyNTc5ODEzOTgxMzI4MDM4NzY5OTcxMzAwNTE0NDI2NzAxNTM2ODE1ODI3MDgzNDY5MzEzOTQ0ODAzMzIzMDUzMzUyNDkxMTIwMDUwNzkyNzA4NTQzMTE2NTM1NjA2NTQyNDY3OTcxNTUxODA4MzQyOTk1OTM4NzQ2NDQ4NjMyNTY4NjU0NjA4MzI1NDk2NjM3Mzc5OTQ0NjA5MDU3Mjc2OTE0OTQxNzE4MzU2MTYzODYyMzI2MDc3MDUzNjIyMDI3OTE2MzIzNzAzMDE2MTc4NDQ3MDEwMTc1MjI1MTM0NjE3NTcxMTgzMjcyNzMwNjQxNzI3Mzk2MzM4ODk2NTUyNzM4NzUwMDA4MTQ3MDExNjkyNzIwNzI4NDY2MjcwNDQ2MDg4NjEyMDg2MDExMzg2NzQxOTMzODM4ODQ1NjkzMTM1NzcyODk0MDIwNTM4NTU3ODI1MjA3OTkxNDAwODIyNjg4OTgwNTg4MjgzMTY0MzAxNTY1NjAyNDAzNTI4MDE2MTczNTk5MTM4NzI5ODEyOTE2MTYxNDEwNjgyODU4MzU4MDE3ODI1ODUyMzY2OTgzMDQzMzM4ODY2MzI3MzM3MTE0NzcyMzM5NTUyNTYzNzU0NzQ0NTA5MTYzNzYzNjAyNTI0NjgxNTUyNjIyOTYwNjM4MzE2OTc0NjI4MjQyNjQ5NjYyMjQyMTkzODMxOTUyMDE0MTAxNTA3Njk2MjIxMDU5NDE5MTcyNzMwNjE2NDE2NzEwNTMzMzc2NjI4MjQ4NzkxMTUwNjMxMDMxOCJdXX0sIm5vbmNlIjoiMTE4MTE3NTM4MDU1MjM2NjMxNjAwNjM1NyJ9", }, "mime-type": "application/json", }, ], }, - "metadata": Object {}, + "metadata": {}, "role": "receiver", + "updatedAt": "2022-01-21T22:50:20.522Z", }, }, - "8-4e4f-41d9-94c4-f49351b811f1": Object { + "8-4e4f-41d9-94c4-f49351b811f1": { "id": "8-4e4f-41d9-94c4-f49351b811f1", - "tags": Object { + "tags": { "associatedRecordId": "5f2b7bc7-edfd-47e7-a1d4-aae050df2c4a", "messageId": "1284ae78-f3d3-4fed-a5ff-0e2aba968c3c", "messageName": "request-credential", @@ -2988,34 +3040,35 @@ Object { "threadId": "578e73da-c3be-43d4-949b-7aadfd5a6eae", }, "type": "DidCommMessageRecord", - "value": Object { - "_tags": Object {}, + "value": { + "_tags": {}, "associatedRecordId": "5f2b7bc7-edfd-47e7-a1d4-aae050df2c4a", "createdAt": "2022-01-21T22:50:20.522Z", "id": "8-4e4f-41d9-94c4-f49351b811f1", - "message": Object { + "message": { "@id": "1284ae78-f3d3-4fed-a5ff-0e2aba968c3c", "@type": "https://didcomm.org/issue-credential/1.0/request-credential", - "requests~attach": Array [ - Object { + "requests~attach": [ + { "@id": "libindy-cred-request-0", - "data": Object { + "data": { "base64": "eyJwcm92ZXJfZGlkIjoiUVZveGd3d25WUGtBQlRMVmNtQ013TCIsImNyZWRfZGVmX2lkIjoiVEwxRWFQRkNaOFNpNWFVcnFTY0JEdDozOkNMOjY4MTpkZWZhdWx0IiwiYmxpbmRlZF9tcyI6eyJ1IjoiMTkwNzM1MzQyMjkwNjk4Mzk3NzgyNTUyMTM2NTA3NzAxMDA4NzYwMzcxMjg3NTQ1MzI0NDM2NDIyMTMwNzQ3MDI3NTk4NDM2NTM5Mzc0NzM0MjgxOTk4MDY5Mjg1OTg4MzAzMDE1MzAzMTYxNjExMTEzMTY2MzQxMzkyOTkzMTk0ODUxNzM5Njk4NzcxNDYzNzMzMDA4MjUxMzQ5NjM4OTkzMDE5NTk5MTY1NDUyOTk4OTA4OTY4MDE5NTUxODM2NDg1NzYxMzMxNTgxNTY0MzgxNTkxNjMwOTcyNTc5NTkyODMyNDk3MDI0NTMyMjQxNzk4MDMzMjI1NDg4NjA5NjEwNjEzNTU1NDMxODc3NDQzODk2ODUyMzA4NjIxNDA1NjI5NjA5MTg5Nzg2MjYzMTcyODU0MjA1MTI3ODMxNjc5NzM5NTkxODQyMTYwOTAyNjczMDE4Mzc0MzE5NjUyODA3Njc2MDQzNzc0ODcxMjQzMzkzNTIwNTkwODE5MDgxOTI4NzY3MjU5NDQ2OTIxNTM2MjU3MjQ2NjIxMjgzNjc2NDM1MDIwNzUwODI4NDI2NTM2MTU2ODA5NDgwMTU3MTQ0NDkxNTY2MTM0ODYzNjU4Mjg5ODIyMTE1NjI4MzMxNjMxMTQ3ODM1NzQ4MjAxMzkyNjY4NzQyMTQ5NTI1OTQ0OTc1NzY3NTYwMTQyNzQ5MTU3MTY2NzE0MDY0OTM2OTQ1MzEwMzEwMzU1NjgwNTcyNDgzNDgyNTYyMzk5Nzc0OTY5NTYwMTA1Njk2MzczMDU4MDMzODgyMTAwOTY2ODUwMTk5MjEzMzAiLCJ1ciI6bnVsbCwiaGlkZGVuX2F0dHJpYnV0ZXMiOlsibWFzdGVyX3NlY3JldCJdLCJjb21taXR0ZWRfYXR0cmlidXRlcyI6e319LCJibGluZGVkX21zX2NvcnJlY3RuZXNzX3Byb29mIjp7ImMiOiI1MDg3Mzk4NDExNzQ3Mzc5NjY5MDkyNzU5ODQ5OTEwMDI2OTYzMTk2NjExNjc5MzU5NDYwNDMxMjYyMDE4NzgyNzY4NTM2NTUzNzUwMiIsInZfZGFzaF9jYXAiOiIxODU0NzEwMDA5NDM4NTg5MTc4MzkzMjgzMDk1MzM5NDUzMDQ3OTkwOTYyMjE2NzEyNzk2ODkyMzcyMzA5NjU5NTU3MDY2MzQxMTMxOTY0Mjg5NjA3MTI5MDg4MjMxMDY0NTk5ODY4NDg4MTIzNDMwMzY5OTkxMjI1OTMxMTIyMjY4NjU5MDEwMDA0NDA1OTIzNTcyMzgyMzQzODczNjkxODg3NDQzMjQ5MTcwNTQwNDk4Nzk5MTkxOTIzMjc4NDU4MzcyMzk2NzIyMjM5NDE1NjY3ODIxMDQ4ODA0NDk1NDQ5ODQ3MjcwOTg1MDcwNzY3NjU2NDU4NDM0MTYzNTI3NDAyMzA1NTg5MTg4NzcyNDg4NzE1NjcwOTgxNTc0NzQxMTI1NzYxOTIxNTE3NzYyNzg1Nzk4MjU5MTQ0OTYzMDQyMjg0NzUwMDE5MjAwMjQ4NjgxNjkxOTE5Njg3MDA1MDA4MDUzMjYwODY5NzEyNTEwNzIwNDg5NDAwMjM0NDU3Njk2MDk4NjI0Nzk1MDUwMzQ2NzM2Mjg1MDE3MTU5Mjk1OTA0NTU1NTk0MzMxNzI4MTQ5MzgyODE5NTI2NTc3MDg3NjA0ODMwNDk3MTE0Mjc3MTkyMDU3ODk1MzYxNzI5NTE3NTgxNzg5ODEyMDY3MjcxNTU5MTMyNTI1MzEzNTc0MDEwNTM3NDMxMDY2NzUwNzAzMDgxMTQxNDIyODg5MzUxOTY0MDUyMDU0NjY0MTA0ODE0MzY1Njg3NTcxNjU5MTk3ODQxMjU5NjE3OTI4MDg4NjM0ODY1NTI2MjI4NTkyNTI2NTgwODgzMzIzNjEwNTc5NzU4ODgzMjgwNDcyNTA0OTQ0MjM2ODY5MTYyNzM3NzUwNTI0NTIyMjE5NTM4NjE4OTk2ODQzODU3MzU3MjUxODI5NTIyODgxOTA0NTAwMDU2MjU1NTMwNDgyIiwibV9jYXBzIjp7Im1hc3Rlcl9zZWNyZXQiOiIxMjM5OTQ3ODE4MDA1MTQ3MjQ5MjcyODIxNDI2OTgwNjQ2NTIwMjAwMTc0MzUwMzkzMzQ0NzU1NTU0NDAxNjg1NzQ2NzExMzkzMjEzMjA3NjI3ODI5MTczMjc2OTA2MDg4MDAxMDIxMTMxMzY4NzY4MjI0ODgxNTExMjEwNjY0NzA5OTAxOTQwODA0MzA0OTc1NTUyMzExNzAyMjU3MTYwOTAxNTE0MzIxNzYyNzM0ODUwMSJ9LCJyX2NhcHMiOnt9fSwibm9uY2UiOiIzNzM5ODQyNzAxNTA3ODY4NjQ0MzMxNjMifQ==", }, "mime-type": "application/json", }, ], - "~thread": Object { + "~thread": { "thid": "578e73da-c3be-43d4-949b-7aadfd5a6eae", }, }, - "metadata": Object {}, + "metadata": {}, "role": "sender", + "updatedAt": "2022-01-21T22:50:20.522Z", }, }, - "9-4e4f-41d9-94c4-f49351b811f1": Object { + "9-4e4f-41d9-94c4-f49351b811f1": { "id": "9-4e4f-41d9-94c4-f49351b811f1", - "tags": Object { + "tags": { "associatedRecordId": "5f2b7bc7-edfd-47e7-a1d4-aae050df2c4a", "messageId": "d14cf505-4903-4dd9-95c2-a7dbc1c048b6", "messageName": "issue-credential", @@ -3027,79 +3080,79 @@ Object { "threadId": "578e73da-c3be-43d4-949b-7aadfd5a6eae", }, "type": "DidCommMessageRecord", - "value": Object { - "_tags": Object {}, + "value": { + "_tags": {}, "associatedRecordId": "5f2b7bc7-edfd-47e7-a1d4-aae050df2c4a", "createdAt": "2022-01-21T22:50:20.522Z", "id": "9-4e4f-41d9-94c4-f49351b811f1", - "message": Object { + "message": { "@id": "d14cf505-4903-4dd9-95c2-a7dbc1c048b6", "@type": "https://didcomm.org/issue-credential/1.0/issue-credential", - "credentials~attach": Array [ - Object { + "credentials~attach": [ + { "@id": "libindy-cred-0", - "data": Object { + "data": { "base64": "eyJzY2hlbWFfaWQiOiJUTDFFYVBGQ1o4U2k1YVVycVNjQkR0OjI6c2NoZW1hLTgwZjdlZWM1LThlNWEtNDNjYS1hZDRkLTMyNzRmYjkzNjFiODoxLjAiLCJjcmVkX2RlZl9pZCI6IlRMMUVhUEZDWjhTaTVhVXJxU2NCRHQ6MzpDTDo2ODE6ZGVmYXVsdCIsInJldl9yZWdfaWQiOm51bGwsInZhbHVlcyI6eyJuYW1lIjp7InJhdyI6IkFsaWNlIiwiZW5jb2RlZCI6IjI3MDM0NjQwMDI0MTE3MzMxMDMzMDYzMTI4MDQ0MDA0MzE4MjE4NDg2ODE2OTMxNTIwODg2NDA1NTM1NjU5OTM0NDE3NDM4NzgxNTA3In0sImRhdGVPZkJpcnRoIjp7InJhdyI6IjIwMjAtMDEtMDEiLCJlbmNvZGVkIjoiNDEwNjEyOTM3ODA0NjIwNzU1MTQyMDgyMjM4OTc1OTA0MDc4MDA3NDg1NDMwMTQ5OTE1ODg1MjI3MjExOTM4ODY4OTgxMTk3NTM2MjQifSwiYWdlIjp7InJhdyI6IjI1IiwiZW5jb2RlZCI6IjI1In19LCJzaWduYXR1cmUiOnsicF9jcmVkZW50aWFsIjp7Im1fMiI6Ijg5NjQzNDI5NjIzMzI5NjQ1MDM2NDU5MjU4NzU2Nzc5MDY3ODczOTc4MDg1ODc3MDM3MzU1NzMxMjk1MzA0NzY5ODAxNDM5MjI2MDI4IiwiYSI6IjUzOTQ3MDU1MTU0OTEyMTE2MzM0MzU2NTMyMjEyMTM5MjMxNjE2NTcwMzU3MjkwNzcyOTkxNjM4NTU1MjU2Mjc2NjgyODY5NDM2MTI5OTEzNzk2MzQwOTA1Nzk3Mzc2OTI5NDE0MTIwNzMwMjI5NjQzNjMwNTI2OTcyOTUxNDk1NjA5NDcwMDU0NzEzOTY1OTE5NTU3MzM3NDkwMjUyNTI1NTM5NzI4MjA0NTI2NTU0MDcyMDYyOTcxMTA0MTM3NTQ4NzEzMzIzNTUzMTYwOTEzODQ0MDk0MDczOTQ3MjM2OTQyNzgyODIzNDA2NDUyNzIzODY2NjMzMzc2MjI4Mzk2ODE5ODI0MTQ2MjgyNTI4NDIyOTIzMjM1NzEzNTI5MjQ3ODkxMTQzMDE4OTM3ODQ0ODM3NzQ2MDE4MTc5Mjk5ODI5ODQ1MTI4MTgxMDUxOTE4MjE0ODU5Mzg5MjIzOTEzNjUzMjE0MjIxMTk2NDI2OTA2NDM1NDYwNDQwNTgxNDkxNTg5MzMxMzIyMDU1MDU1NjE5NDY0OTEwNTc3OTcyODAyNDM1MDY3OTMxMDczOTI5OTgyOTQ2Njg4NDg5MTIwNTQ1MjA1MzQ0MjQ1NTIzNTExNDc5NjUzNjI5ODIxNTA4NTc3MjI2MzU5MjUyMDA5MjUyNTU2NDA5NTg0MDgwNDY5NDI4NzE1NDQ0MDkyOTA4NzAxNjMwMzE3NzI5MTE3NTYwMzg2NjAyNDA4OTE3Mzg2NjM4NzQ2MDY1NjU0NzQ2OTAxOTA1NjA4MjE5MzgzNzczNjg3NDcxODI3NjE2OTU5MDk3NDU4IiwiZSI6IjI1OTM0NDcyMzA1NTA2MjA1OTkwNzAyNTQ5MTQ4MDY5NzU3MTkzODI3Nzg4OTUxNTE1MjMwNjI0OTcyODU4MzEwNTY2NTgwMDcxMzMwNjc1OTE0OTk4MTY5MDU1OTE5Mzk4NzE0MzAxMjM2NzkxMzIwNjI5OTMyMzg5OTY5Njk0MjIxMzIzNTk1Njc0MjkzMDMwMzU1ODY2NDUxMDY0MDQ4Mzk4OTcyMjU0Nzk2ODY2NDA2OSIsInYiOiI4NzE4MTIwMTY2NjA3NTg4MjIxMDczMTE0NTgxNTcxMDA5NTEwNDUwMzkyNDk0MDM1MTg3MDk5MTQyNzA5NDcyMjYxMDAyMjg5MzI5MzcyMzk1MDUwMzgzOTA3ODUxODc1MjIxODU0NjI4ODc4OTcxNzQ0Njg3MDgxMDM4Mzk2Njc2MzgyMTI0NjEyNDY0NjQxMDQ4NDMxNDkwMTYzMDgwOTU0NTc3MjA3OTkxNjg3NzU0NjQ4MTYwNzM5MjQ2ODUyMjMxMjU2NTk0MTg4MTAxNDU2MjgzNjc3MTA4NTMwNjY1NDQwNTUwMDY0MjgxODI3NzUxNjA3NjM1ODE2MjczNDU3MTc4MzAyNjEyOTI5ODMyNDMzNDc3ODk0ODMzMDc2MDA5OTE4MTc1MzI4OTUzNjg1MjEwOTQ1ODg0MTQyMDg0Nzk4ODMzNzM2OTExNDcwMTkwOTYwMjI3MzAyMzI2NjQ2NjE2NjUwMjY4OTU3NDcyMTI3MTA2Mjk3NzQ0NDg3MDUzODY2NjI0NTk4Njg1MzA0MzA0OTMzNjAxNDczNjY4Njg1NDg5MzYyMzQ2NzE5ODA4MjgxNzYxNjc0ODQyMzE5NTY5Mzk1Nzk1NTQ5OTA4MjAyODI0MjgyOTc1MTI3MDA0MzM0NTkwNTYzMTI3NjU3NDM3MjQ2MDQ3OTUyOTk0OTIyODQ3MzcxMzY4NDM0OTE3MDM1Njc4ODA2NjM1OTQ0ODY4Njc5MDY1NDc5Njk1MDU5NzkyNzUyMzk5NDcwMzUzMDI3MjEyNTg2OTc1Mjk5MTk1NDcwNjY0NDMzMDIyNTQyODg4MzI4OTA0Mjg2NTIxMzM5Nzc2OTkxMDYzNzA1MTI2NjA4MDY4OTA0Mzg2MDc4NzA5NTE3NjU0OTE3MzI0NjExMzkzNTM4MDkyNTQ3NzQ0OTM2NTM1NDkwODcwNDU4NjQ3NjY2OTU3MjA5MDk4MDU2NzIwMjAzOTAxMjI3MjU2NDM5NTkwNTA0MzIwOTI3NTc1ODA2NjE0NzA4NTU3MjAxMTAxODczODc4NDg1NjM4MzQ2Nzk1NjE4NDQxOTQxMjQyODc5ODMyNjQ5ODEyIn0sInJfY3JlZGVudGlhbCI6bnVsbH0sInNpZ25hdHVyZV9jb3JyZWN0bmVzc19wcm9vZiI6eyJzZSI6IjIxOTkzMzcwNTI0MjIxNTM0MTM0MTc4MDM3MDIyMDEyMzE4NjQ2MDE4MTk0MDIwMjgxNzY4NTQ4OTUyNjM5ODg4MDE2NDQ1NTM2NTQ2MzczMzkwMDU5NjY1Nzg4OTc2MDE0OTUzMTU2MjA1MTA0ODU1NTM1NjEyODY5Mjg5NjgyOTQ1ODI4MDQxOTMxMzc1ODY4NjE4OTE0NjUwNTc5ODM2NzI0NDE0MDMxMjU3MjU2MzkxNjg2OTQ0NjQyMzg3NTIwNjExNDQ5ODM1NTgxNDMzMDMzOTQ4MTA4OTE0MzI2NzkyNDU5MjQ0Mzc0MTgyOTQ4MDQxODIzMTg3NjY3MjE2MDI5OTEwMTAzMTM1MjE4NjY5ODc5MDk5ODA0NTA0NjI1NTAzNDM2NTAxOTk5ODkwODIyNjcyMjYwNzc1NDIzMzIxNTQ0MDk0Mjk2NDI0Nzc2Njg2MDI1MzU2MjMwOTY0OTQyMzc3NTY0NDUwMDk4NTgyMzg2Nzg0ODc3OTQwNTI2ODg0MzgzOTcxOTE4OTE3ODIyOTkzMDIzMDU2NjU1NTg2NDI3MDAwMzQ2OTcwMTI1MjA2ODg0NTkyNzkwMDU4NTAyMzkxMzUwODIyNjg1NDYyMzY0MzE5NTMwOTI0MjM4Mzg2MjkwMDI5MTQ1ODAzNTgxODA2MDQ1MTYzNDMzNTQ2OTAwMzQzNDg0MTg2NTEyMjIzODYwODY3NTI3ODExOTkyOTQwMjMxMzgzOTM4MjI0NjEwMTk0NDc1NjczMDYyMDI3MDgwOTI5NDYzMjU0NjIxMjI1NTg3MDg0NTUyODEyMDgxMDE3IiwiYyI6Ijg2NzE3OTAzNTAxODI5MzU5NDk4MjE3NDU5NjE3NjgyNTM4NzIxNzQwMjE5MDM5MDUzNjQzNTE3NDU0Nzc2NTUzMzA3MzU1OTkxMjE5In0sInJldl9yZWciOm51bGwsIndpdG5lc3MiOm51bGx9", }, "mime-type": "application/json", }, ], - "~please_ack": Object {}, - "~thread": Object { + "~please_ack": {}, + "~thread": { "thid": "578e73da-c3be-43d4-949b-7aadfd5a6eae", }, }, - "metadata": Object {}, + "metadata": {}, "role": "receiver", + "updatedAt": "2022-01-21T22:50:20.522Z", }, }, - "STORAGE_VERSION_RECORD_ID": Object { + "STORAGE_VERSION_RECORD_ID": { "id": "STORAGE_VERSION_RECORD_ID", - "tags": Object {}, + "tags": {}, "type": "StorageVersionRecord", - "value": Object { + "value": { "createdAt": "2022-01-21T22:50:20.522Z", "id": "STORAGE_VERSION_RECORD_ID", - "metadata": Object {}, + "metadata": {}, "storageVersion": "0.2", + "updatedAt": "2022-01-21T22:50:20.522Z", }, }, - "ad644d8a-48a2-4c55-b46d-7a7f1a9278c7": Object { + "ad644d8a-48a2-4c55-b46d-7a7f1a9278c7": { "id": "ad644d8a-48a2-4c55-b46d-7a7f1a9278c7", - "tags": Object { + "tags": { "connectionId": "cd66cbf1-5721-449e-8724-f4d8dcef1bc4", - "credentialIds": Array [], - "indyCredentialRevocationId": undefined, - "indyRevocationRegistryId": undefined, + "credentialIds": [], "state": "done", "threadId": "e2c2194c-6ac6-4b27-9030-18887c79b5eb", }, "type": "CredentialRecord", - "value": Object { + "value": { "autoAcceptCredential": "contentApproved", "connectionId": "cd66cbf1-5721-449e-8724-f4d8dcef1bc4", "createdAt": "2022-03-21T22:50:20.740Z", - "credentialAttributes": Array [ - Object { + "credentialAttributes": [ + { "mime-type": "text/plain", "name": "name", "value": "Alice", }, - Object { + { "mime-type": "text/plain", "name": "age", "value": "25", }, - Object { + { "mime-type": "text/plain", "name": "dateOfBirth", "value": "2020-01-01", }, ], - "credentials": Array [], + "credentials": [], "id": "ad644d8a-48a2-4c55-b46d-7a7f1a9278c7", - "metadata": Object { - "_internal/indyCredential": Object { + "metadata": { + "_internal/indyCredential": { "credentialDefinitionId": "TL1EaPFCZ8Si5aUrqScBDt:3:CL:681:default", "schemaId": "TL1EaPFCZ8Si5aUrqScBDt:2:schema-80f7eec5-8e5a-43ca-ad4d-3274fb9361b8:1.0", }, @@ -3107,57 +3160,56 @@ Object { "protocolVersion": "v1", "state": "done", "threadId": "e2c2194c-6ac6-4b27-9030-18887c79b5eb", + "updatedAt": "2022-01-21T22:50:20.522Z", }, }, - "c7e0a752-7f1c-41c0-b0ae-a68c2d97ca8c": Object { + "c7e0a752-7f1c-41c0-b0ae-a68c2d97ca8c": { "id": "c7e0a752-7f1c-41c0-b0ae-a68c2d97ca8c", - "tags": Object { + "tags": { "connectionId": "d8f23338-9e99-469a-bd57-1c9a26c0080f", "credentialId": "19c1f29f-d2df-486c-b8c6-950c403fa7d9", - "credentialIds": Array [ + "credentialIds": [ "19c1f29f-d2df-486c-b8c6-950c403fa7d9", ], - "indyCredentialRevocationId": undefined, - "indyRevocationRegistryId": undefined, "state": "done", "threadId": "e2c2194c-6ac6-4b27-9030-18887c79b5eb", }, "type": "CredentialRecord", - "value": Object { + "value": { "autoAcceptCredential": "contentApproved", "connectionId": "d8f23338-9e99-469a-bd57-1c9a26c0080f", "createdAt": "2022-03-21T22:50:20.746Z", - "credentialAttributes": Array [ - Object { + "credentialAttributes": [ + { "mime-type": "text/plain", "name": "name", "value": "Alice", }, - Object { + { "mime-type": "text/plain", "name": "age", "value": "25", }, - Object { + { "mime-type": "text/plain", "name": "dateOfBirth", "value": "2020-01-01", }, ], - "credentials": Array [ - Object { + "credentials": [ + { "credentialRecordId": "19c1f29f-d2df-486c-b8c6-950c403fa7d9", "credentialRecordType": "indy", }, ], "id": "c7e0a752-7f1c-41c0-b0ae-a68c2d97ca8c", - "metadata": Object { - "_internal/indyCredential": Object { + "metadata": { + "_internal/indyCredential": { "credentialDefinitionId": "TL1EaPFCZ8Si5aUrqScBDt:3:CL:681:default", "schemaId": "TL1EaPFCZ8Si5aUrqScBDt:2:schema-80f7eec5-8e5a-43ca-ad4d-3274fb9361b8:1.0", }, - "_internal/indyRequest": Object { - "master_secret_blinding_data": Object { + "_internal/indyRequest": { + "master_secret_blinding_data": { "v_prime": "24405223168730122709164916892481085040205443709643249329100687534344659826655374235392514476392517756663433844139774514430993889493707631169979521764390851593418941181409704266182779162417466204970949168472702858363964258641437554267668466400711344128132909691514606077477555576087059339291048485225394874964325220472232903203038212033940680060605090839733163438385288769519855418153181511119637865605476043416048121313638627002888436809192752657860306784733123742838413845299796745569824223645588826964796075250758249133953560017373025169692866449286962430731916293683231375510684692358406054381559324718715654332979447698704161714028193478", "vr_prime": null, }, @@ -3168,6 +3220,467 @@ Object { "protocolVersion": "v1", "state": "done", "threadId": "e2c2194c-6ac6-4b27-9030-18887c79b5eb", + "updatedAt": "2022-01-21T22:50:20.522Z", + }, + }, +} +`; + +exports[`UpdateAssistant | v0.1 - v0.2 should correctly update the role in the mediation record: allMediator 1`] = ` +{ + "0b47db94-c0fa-4476-87cf-a5f664440412": { + "id": "0b47db94-c0fa-4476-87cf-a5f664440412", + "tags": { + "connectionId": "88e2093e-97b9-4665-aff0-ffdcb4afee60", + "recipientKeys": [], + "role": "MEDIATOR", + "state": "granted", + "threadId": "e9aeea8f-2c7a-4fd0-9353-f8b5b76094e7", + }, + "type": "MediationRecord", + "value": { + "connectionId": "88e2093e-97b9-4665-aff0-ffdcb4afee60", + "createdAt": "2022-03-21T22:50:17.157Z", + "endpoint": "rxjs:alice", + "id": "0b47db94-c0fa-4476-87cf-a5f664440412", + "metadata": {}, + "recipientKeys": [], + "role": "MEDIATOR", + "routingKeys": [ + "D86mPByntjjYMuoaVwxotLY8RMwyRSkRkUL3XPrpwcDu", + ], + "state": "granted", + "threadId": "e9aeea8f-2c7a-4fd0-9353-f8b5b76094e7", + "updatedAt": "2022-01-21T22:50:20.522Z", + }, + }, + "7f14c1ec-514c-49b2-a00b-04af7e600060": { + "id": "7f14c1ec-514c-49b2-a00b-04af7e600060", + "tags": { + "connectionId": "85a78484-105d-4844-8c01-9f9877362708", + "recipientKeys": [], + "role": "MEDIATOR", + "state": "granted", + "threadId": "a401880b-8129-4ed9-bcaa-57d0e38026cd", + }, + "type": "MediationRecord", + "value": { + "connectionId": "85a78484-105d-4844-8c01-9f9877362708", + "createdAt": "2022-03-21T22:50:17.126Z", + "endpoint": "rxjs:alice", + "id": "7f14c1ec-514c-49b2-a00b-04af7e600060", + "metadata": {}, + "recipientKeys": [], + "role": "MEDIATOR", + "routingKeys": [ + "D86mPByntjjYMuoaVwxotLY8RMwyRSkRkUL3XPrpwcDu", + ], + "state": "granted", + "threadId": "a401880b-8129-4ed9-bcaa-57d0e38026cd", + "updatedAt": "2022-01-21T22:50:20.522Z", + }, + }, + "802ef124-36b7-490f-b152-e9d090ddf073": { + "id": "802ef124-36b7-490f-b152-e9d090ddf073", + "tags": { + "connectionId": "ca5b148a-250f-46b0-8537-ae88014d8bd7", + "recipientKeys": [], + "role": "MEDIATOR", + "state": "granted", + "threadId": "e9aeea8f-2c7a-4fd0-9353-f8b5b76094e7", + }, + "type": "MediationRecord", + "value": { + "connectionId": "ca5b148a-250f-46b0-8537-ae88014d8bd7", + "createdAt": "2022-03-21T22:50:17.161Z", + "id": "802ef124-36b7-490f-b152-e9d090ddf073", + "metadata": {}, + "recipientKeys": [], + "role": "MEDIATOR", + "routingKeys": [], + "state": "granted", + "threadId": "e9aeea8f-2c7a-4fd0-9353-f8b5b76094e7", + "updatedAt": "2022-01-21T22:50:20.522Z", + }, + }, + "STORAGE_VERSION_RECORD_ID": { + "id": "STORAGE_VERSION_RECORD_ID", + "tags": {}, + "type": "StorageVersionRecord", + "value": { + "createdAt": "2022-01-21T22:50:20.522Z", + "id": "STORAGE_VERSION_RECORD_ID", + "metadata": {}, + "storageVersion": "0.2", + "updatedAt": "2022-01-21T22:50:20.522Z", + }, + }, + "a29b39fb-f030-41ac-b6e1-ed7f3f6a05cd": { + "id": "a29b39fb-f030-41ac-b6e1-ed7f3f6a05cd", + "tags": { + "connectionId": "dbb3367e-55aa-4c03-b10a-d1fc34392bea", + "recipientKeys": [], + "role": "MEDIATOR", + "state": "granted", + "threadId": "a401880b-8129-4ed9-bcaa-57d0e38026cd", + }, + "type": "MediationRecord", + "value": { + "connectionId": "dbb3367e-55aa-4c03-b10a-d1fc34392bea", + "createdAt": "2022-03-21T22:50:17.132Z", + "id": "a29b39fb-f030-41ac-b6e1-ed7f3f6a05cd", + "metadata": {}, + "recipientKeys": [], + "role": "MEDIATOR", + "routingKeys": [], + "state": "granted", + "threadId": "a401880b-8129-4ed9-bcaa-57d0e38026cd", + "updatedAt": "2022-01-21T22:50:20.522Z", + }, + }, +} +`; + +exports[`UpdateAssistant | v0.1 - v0.2 should correctly update the role in the mediation record: allRecipient 1`] = ` +{ + "0b47db94-c0fa-4476-87cf-a5f664440412": { + "id": "0b47db94-c0fa-4476-87cf-a5f664440412", + "tags": { + "connectionId": "88e2093e-97b9-4665-aff0-ffdcb4afee60", + "recipientKeys": [], + "role": "RECIPIENT", + "state": "granted", + "threadId": "e9aeea8f-2c7a-4fd0-9353-f8b5b76094e7", + }, + "type": "MediationRecord", + "value": { + "connectionId": "88e2093e-97b9-4665-aff0-ffdcb4afee60", + "createdAt": "2022-03-21T22:50:17.157Z", + "endpoint": "rxjs:alice", + "id": "0b47db94-c0fa-4476-87cf-a5f664440412", + "metadata": {}, + "recipientKeys": [], + "role": "RECIPIENT", + "routingKeys": [ + "D86mPByntjjYMuoaVwxotLY8RMwyRSkRkUL3XPrpwcDu", + ], + "state": "granted", + "threadId": "e9aeea8f-2c7a-4fd0-9353-f8b5b76094e7", + "updatedAt": "2022-01-21T22:50:20.522Z", + }, + }, + "7f14c1ec-514c-49b2-a00b-04af7e600060": { + "id": "7f14c1ec-514c-49b2-a00b-04af7e600060", + "tags": { + "connectionId": "85a78484-105d-4844-8c01-9f9877362708", + "recipientKeys": [], + "role": "RECIPIENT", + "state": "granted", + "threadId": "a401880b-8129-4ed9-bcaa-57d0e38026cd", + }, + "type": "MediationRecord", + "value": { + "connectionId": "85a78484-105d-4844-8c01-9f9877362708", + "createdAt": "2022-03-21T22:50:17.126Z", + "endpoint": "rxjs:alice", + "id": "7f14c1ec-514c-49b2-a00b-04af7e600060", + "metadata": {}, + "recipientKeys": [], + "role": "RECIPIENT", + "routingKeys": [ + "D86mPByntjjYMuoaVwxotLY8RMwyRSkRkUL3XPrpwcDu", + ], + "state": "granted", + "threadId": "a401880b-8129-4ed9-bcaa-57d0e38026cd", + "updatedAt": "2022-01-21T22:50:20.522Z", + }, + }, + "802ef124-36b7-490f-b152-e9d090ddf073": { + "id": "802ef124-36b7-490f-b152-e9d090ddf073", + "tags": { + "connectionId": "ca5b148a-250f-46b0-8537-ae88014d8bd7", + "recipientKeys": [], + "role": "RECIPIENT", + "state": "granted", + "threadId": "e9aeea8f-2c7a-4fd0-9353-f8b5b76094e7", + }, + "type": "MediationRecord", + "value": { + "connectionId": "ca5b148a-250f-46b0-8537-ae88014d8bd7", + "createdAt": "2022-03-21T22:50:17.161Z", + "id": "802ef124-36b7-490f-b152-e9d090ddf073", + "metadata": {}, + "recipientKeys": [], + "role": "RECIPIENT", + "routingKeys": [], + "state": "granted", + "threadId": "e9aeea8f-2c7a-4fd0-9353-f8b5b76094e7", + "updatedAt": "2022-01-21T22:50:20.522Z", + }, + }, + "STORAGE_VERSION_RECORD_ID": { + "id": "STORAGE_VERSION_RECORD_ID", + "tags": {}, + "type": "StorageVersionRecord", + "value": { + "createdAt": "2022-01-21T22:50:20.522Z", + "id": "STORAGE_VERSION_RECORD_ID", + "metadata": {}, + "storageVersion": "0.2", + "updatedAt": "2022-01-21T22:50:20.522Z", + }, + }, + "a29b39fb-f030-41ac-b6e1-ed7f3f6a05cd": { + "id": "a29b39fb-f030-41ac-b6e1-ed7f3f6a05cd", + "tags": { + "connectionId": "dbb3367e-55aa-4c03-b10a-d1fc34392bea", + "recipientKeys": [], + "role": "RECIPIENT", + "state": "granted", + "threadId": "a401880b-8129-4ed9-bcaa-57d0e38026cd", + }, + "type": "MediationRecord", + "value": { + "connectionId": "dbb3367e-55aa-4c03-b10a-d1fc34392bea", + "createdAt": "2022-03-21T22:50:17.132Z", + "id": "a29b39fb-f030-41ac-b6e1-ed7f3f6a05cd", + "metadata": {}, + "recipientKeys": [], + "role": "RECIPIENT", + "routingKeys": [], + "state": "granted", + "threadId": "a401880b-8129-4ed9-bcaa-57d0e38026cd", + "updatedAt": "2022-01-21T22:50:20.522Z", + }, + }, +} +`; + +exports[`UpdateAssistant | v0.1 - v0.2 should correctly update the role in the mediation record: doNotChange 1`] = ` +{ + "0b47db94-c0fa-4476-87cf-a5f664440412": { + "id": "0b47db94-c0fa-4476-87cf-a5f664440412", + "tags": { + "connectionId": "88e2093e-97b9-4665-aff0-ffdcb4afee60", + "recipientKeys": [], + "role": "MEDIATOR", + "state": "granted", + "threadId": "e9aeea8f-2c7a-4fd0-9353-f8b5b76094e7", + }, + "type": "MediationRecord", + "value": { + "connectionId": "88e2093e-97b9-4665-aff0-ffdcb4afee60", + "createdAt": "2022-03-21T22:50:17.157Z", + "endpoint": "rxjs:alice", + "id": "0b47db94-c0fa-4476-87cf-a5f664440412", + "metadata": {}, + "recipientKeys": [], + "role": "MEDIATOR", + "routingKeys": [ + "D86mPByntjjYMuoaVwxotLY8RMwyRSkRkUL3XPrpwcDu", + ], + "state": "granted", + "threadId": "e9aeea8f-2c7a-4fd0-9353-f8b5b76094e7", + "updatedAt": "2022-01-21T22:50:20.522Z", + }, + }, + "7f14c1ec-514c-49b2-a00b-04af7e600060": { + "id": "7f14c1ec-514c-49b2-a00b-04af7e600060", + "tags": { + "connectionId": "85a78484-105d-4844-8c01-9f9877362708", + "recipientKeys": [], + "role": "MEDIATOR", + "state": "granted", + "threadId": "a401880b-8129-4ed9-bcaa-57d0e38026cd", + }, + "type": "MediationRecord", + "value": { + "connectionId": "85a78484-105d-4844-8c01-9f9877362708", + "createdAt": "2022-03-21T22:50:17.126Z", + "endpoint": "rxjs:alice", + "id": "7f14c1ec-514c-49b2-a00b-04af7e600060", + "metadata": {}, + "recipientKeys": [], + "role": "MEDIATOR", + "routingKeys": [ + "D86mPByntjjYMuoaVwxotLY8RMwyRSkRkUL3XPrpwcDu", + ], + "state": "granted", + "threadId": "a401880b-8129-4ed9-bcaa-57d0e38026cd", + "updatedAt": "2022-01-21T22:50:20.522Z", + }, + }, + "802ef124-36b7-490f-b152-e9d090ddf073": { + "id": "802ef124-36b7-490f-b152-e9d090ddf073", + "tags": { + "connectionId": "ca5b148a-250f-46b0-8537-ae88014d8bd7", + "recipientKeys": [], + "role": "MEDIATOR", + "state": "granted", + "threadId": "e9aeea8f-2c7a-4fd0-9353-f8b5b76094e7", + }, + "type": "MediationRecord", + "value": { + "connectionId": "ca5b148a-250f-46b0-8537-ae88014d8bd7", + "createdAt": "2022-03-21T22:50:17.161Z", + "id": "802ef124-36b7-490f-b152-e9d090ddf073", + "metadata": {}, + "recipientKeys": [], + "role": "MEDIATOR", + "routingKeys": [], + "state": "granted", + "threadId": "e9aeea8f-2c7a-4fd0-9353-f8b5b76094e7", + "updatedAt": "2022-01-21T22:50:20.522Z", + }, + }, + "STORAGE_VERSION_RECORD_ID": { + "id": "STORAGE_VERSION_RECORD_ID", + "tags": {}, + "type": "StorageVersionRecord", + "value": { + "createdAt": "2022-01-21T22:50:20.522Z", + "id": "STORAGE_VERSION_RECORD_ID", + "metadata": {}, + "storageVersion": "0.2", + "updatedAt": "2022-01-21T22:50:20.522Z", + }, + }, + "a29b39fb-f030-41ac-b6e1-ed7f3f6a05cd": { + "id": "a29b39fb-f030-41ac-b6e1-ed7f3f6a05cd", + "tags": { + "connectionId": "dbb3367e-55aa-4c03-b10a-d1fc34392bea", + "recipientKeys": [], + "role": "MEDIATOR", + "state": "granted", + "threadId": "a401880b-8129-4ed9-bcaa-57d0e38026cd", + }, + "type": "MediationRecord", + "value": { + "connectionId": "dbb3367e-55aa-4c03-b10a-d1fc34392bea", + "createdAt": "2022-03-21T22:50:17.132Z", + "id": "a29b39fb-f030-41ac-b6e1-ed7f3f6a05cd", + "metadata": {}, + "recipientKeys": [], + "role": "MEDIATOR", + "routingKeys": [], + "state": "granted", + "threadId": "a401880b-8129-4ed9-bcaa-57d0e38026cd", + "updatedAt": "2022-01-21T22:50:20.522Z", + }, + }, +} +`; + +exports[`UpdateAssistant | v0.1 - v0.2 should correctly update the role in the mediation record: recipientIfEndpoint 1`] = ` +{ + "0b47db94-c0fa-4476-87cf-a5f664440412": { + "id": "0b47db94-c0fa-4476-87cf-a5f664440412", + "tags": { + "connectionId": "88e2093e-97b9-4665-aff0-ffdcb4afee60", + "recipientKeys": [], + "role": "RECIPIENT", + "state": "granted", + "threadId": "e9aeea8f-2c7a-4fd0-9353-f8b5b76094e7", + }, + "type": "MediationRecord", + "value": { + "connectionId": "88e2093e-97b9-4665-aff0-ffdcb4afee60", + "createdAt": "2022-03-21T22:50:17.157Z", + "endpoint": "rxjs:alice", + "id": "0b47db94-c0fa-4476-87cf-a5f664440412", + "metadata": {}, + "recipientKeys": [], + "role": "RECIPIENT", + "routingKeys": [ + "D86mPByntjjYMuoaVwxotLY8RMwyRSkRkUL3XPrpwcDu", + ], + "state": "granted", + "threadId": "e9aeea8f-2c7a-4fd0-9353-f8b5b76094e7", + "updatedAt": "2022-01-21T22:50:20.522Z", + }, + }, + "7f14c1ec-514c-49b2-a00b-04af7e600060": { + "id": "7f14c1ec-514c-49b2-a00b-04af7e600060", + "tags": { + "connectionId": "85a78484-105d-4844-8c01-9f9877362708", + "recipientKeys": [], + "role": "RECIPIENT", + "state": "granted", + "threadId": "a401880b-8129-4ed9-bcaa-57d0e38026cd", + }, + "type": "MediationRecord", + "value": { + "connectionId": "85a78484-105d-4844-8c01-9f9877362708", + "createdAt": "2022-03-21T22:50:17.126Z", + "endpoint": "rxjs:alice", + "id": "7f14c1ec-514c-49b2-a00b-04af7e600060", + "metadata": {}, + "recipientKeys": [], + "role": "RECIPIENT", + "routingKeys": [ + "D86mPByntjjYMuoaVwxotLY8RMwyRSkRkUL3XPrpwcDu", + ], + "state": "granted", + "threadId": "a401880b-8129-4ed9-bcaa-57d0e38026cd", + "updatedAt": "2022-01-21T22:50:20.522Z", + }, + }, + "802ef124-36b7-490f-b152-e9d090ddf073": { + "id": "802ef124-36b7-490f-b152-e9d090ddf073", + "tags": { + "connectionId": "ca5b148a-250f-46b0-8537-ae88014d8bd7", + "recipientKeys": [], + "role": "MEDIATOR", + "state": "granted", + "threadId": "e9aeea8f-2c7a-4fd0-9353-f8b5b76094e7", + }, + "type": "MediationRecord", + "value": { + "connectionId": "ca5b148a-250f-46b0-8537-ae88014d8bd7", + "createdAt": "2022-03-21T22:50:17.161Z", + "id": "802ef124-36b7-490f-b152-e9d090ddf073", + "metadata": {}, + "recipientKeys": [], + "role": "MEDIATOR", + "routingKeys": [], + "state": "granted", + "threadId": "e9aeea8f-2c7a-4fd0-9353-f8b5b76094e7", + "updatedAt": "2022-01-21T22:50:20.522Z", + }, + }, + "STORAGE_VERSION_RECORD_ID": { + "id": "STORAGE_VERSION_RECORD_ID", + "tags": {}, + "type": "StorageVersionRecord", + "value": { + "createdAt": "2022-01-21T22:50:20.522Z", + "id": "STORAGE_VERSION_RECORD_ID", + "metadata": {}, + "storageVersion": "0.2", + "updatedAt": "2022-01-21T22:50:20.522Z", + }, + }, + "a29b39fb-f030-41ac-b6e1-ed7f3f6a05cd": { + "id": "a29b39fb-f030-41ac-b6e1-ed7f3f6a05cd", + "tags": { + "connectionId": "dbb3367e-55aa-4c03-b10a-d1fc34392bea", + "recipientKeys": [], + "role": "MEDIATOR", + "state": "granted", + "threadId": "a401880b-8129-4ed9-bcaa-57d0e38026cd", + }, + "type": "MediationRecord", + "value": { + "connectionId": "dbb3367e-55aa-4c03-b10a-d1fc34392bea", + "createdAt": "2022-03-21T22:50:17.132Z", + "id": "a29b39fb-f030-41ac-b6e1-ed7f3f6a05cd", + "metadata": {}, + "recipientKeys": [], + "role": "MEDIATOR", + "routingKeys": [], + "state": "granted", + "threadId": "a401880b-8129-4ed9-bcaa-57d0e38026cd", + "updatedAt": "2022-01-21T22:50:20.522Z", }, }, } diff --git a/packages/core/src/storage/migration/__tests__/__snapshots__/0.2.test.ts.snap b/packages/core/src/storage/migration/__tests__/__snapshots__/0.2.test.ts.snap index a56e8065c1..c4767da0c1 100644 --- a/packages/core/src/storage/migration/__tests__/__snapshots__/0.2.test.ts.snap +++ b/packages/core/src/storage/migration/__tests__/__snapshots__/0.2.test.ts.snap @@ -1,43 +1,43 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`UpdateAssistant | v0.2 - v0.3.1 should correctly update proof records and create didcomm records 1`] = ` -Object { - "3d5d7ad4-f0aa-4b1b-8c2c-780ee383564e": Object { +{ + "3d5d7ad4-f0aa-4b1b-8c2c-780ee383564e": { "id": "3d5d7ad4-f0aa-4b1b-8c2c-780ee383564e", - "tags": Object { + "tags": { "connectionId": "946f660f-bfa6-4a98-a801-ebde5da95e2c", "state": "done", "threadId": "7fcfc074-43ac-43cc-92b9-76afceeebe82", }, "type": "ProofExchangeRecord", - "value": Object { + "value": { "connectionId": "946f660f-bfa6-4a98-a801-ebde5da95e2c", "createdAt": "2022-09-08T19:36:06.261Z", "id": "3d5d7ad4-f0aa-4b1b-8c2c-780ee383564e", - "metadata": Object {}, - "presentationMessage": Object { + "metadata": {}, + "presentationMessage": { "@id": "2481ce81-560b-4ce6-a22b-ee4b6ed369e8", "@type": "https://didcomm.org/present-proof/1.0/presentation", - "presentations~attach": Array [ - Object { + "presentations~attach": [ + { "@id": "libindy-presentation-0", - "data": Object { + "data": { "base64": "eyJwcm9vZiI6eyJwcm9vZnMiOlt7InByaW1hcnlfcHJvb2YiOnsiZXFfcHJvb2YiOnsicmV2ZWFsZWRfYXR0cnMiOnsibmFtZSI6IjI3MDM0NjQwMDI0MTE3MzMxMDMzMDYzMTI4MDQ0MDA0MzE4MjE4NDg2ODE2OTMxNTIwODg2NDA1NTM1NjU5OTM0NDE3NDM4NzgxNTA3In0sImFfcHJpbWUiOiI5MDg5MzE2Mjc0ODc5NTI4MjMzNDA1MTY0NTgwOTM1OTIxMjIwMjMyMzg5ODE0NjE4OTc3ODA3MjA0MDg4OTQ0ODkzNDc1OTE5NTE4Mjc0NDEwNTU3OTU3NjEwNjYzOTAxMTcwODM1NDM2Nzk1NDU4NDU1Mjg3NzEwOTk3NTk3OTA1OTM3NTYyODIyNjg5NTE3MjAyNzQ4NTUxODgzODQ5NjY3MjYwNTA3NjU0NDM5OTk0MjczNDQ0MTU5NTQyMzg3MzI0OTM5OTAzMDcyMDc2MjQ4Njg1MTgyMjA4NTA0OTkyOTg5MTk0NzUwMDgyODU1MTc1NDE1OTIzMjU3MzA0MTQ0NjYxMDc5MDU2NzExMTg3NzMzMDE3NDQ1MTEyOTQyNDAyMTEzNDg0NjM5MTMxMDY2MDc3ODE2NzQzMzY3OTMzMDI3MjY3MTQ3MDIxMTkxODQ0NTQzMzI5NzUzMTA1NTA3MDk0Mzc5OTA5OTYzNjcxMTQ4NzM3Mjk3NDA2MzUxMzk0NTcwNTM3Nzk0NDg1Njc1ODc3MDU5OTI2NTc3MDU4MzY1NTA0MDQwNjAzNDIxMDI3NDYyOTY0OTExNTc5MDAyNjg2NDAzMjMyOTc3OTU0ODM1Nzc2NzQwMDI3NDIxNjI0MTUzNDQ4NzYyODAxODM3OTU3MTQ3NzM0MDkxNDk3NjMwMjA3MTY3MzUzMjAwMTM5ODE4MDg1NjgwMDIzMTc1MDEyNzM4Mjk1NzIwODU2OTMwNDYxMzIxMDQ4NTIxMjQ0ODQ5MjQ5Njc5MDMwMzI0NDcyNjYyOTQxNjc5NDU3OTk3NzQ4NiIsImUiOiI0NTE0MTczNzExODM2MzMzOTk0NjA3MTMwNjQ5MjA0NjEyNzU2Njk1MDI4ODA2NTY0NzI4MzE3OTExNzYxNDA0NTE5Nzk0NjA3NDk4Njg5NjgyOTYxODk3MDc4MDcwMzQ5Nzk0MzUzMDQ1MTY3MTUyMTk2OTU4NTU0NTI5MzgxNjY3MDE5MDA2OSIsInYiOiI1MzM4NDg2NDY2MjE4MTg2ODg3MTUwNzY4NTQ0OTQyMTEyMDcyOTQ1MzczMDQ1MDAzNTk0MTk0MzAxMDA5NDUzMzk5MjMxMDM5NjExMjU4NTE3MTgzODUyMDc4NjI0NjMyMDExNDE2MzI3Mjc1MTM3Nzc1MTAxNjgxODcwMzI4MjY3MTE4MjExNjEwNzAwNDc2MjA5NzMwMTIwODI2NzMyMTkwMDg0ODkyOTc2NTEwMTgxODE2MTkzMzM5MTk0MjE5MDIxOTQ1OTI1NTg4NjEzODEwMjE1Nzg1NDk1NDk0NjQ0NzIwMDM4MjMwMTg1MDUyMDAxMTMxNjE3MjQwMDIyNjQzOTYxNTkwOTU5ODE3ODMxMzg2Mzc5NDQ1MzI2Mzg4NzYzNjQ5MDYxODk4Nzk1ODcwMjE2NTkxMDI3NDkwMzAwMjA0OTc1NzM0NDgyNDM1ODE4MjgwMTQxNzA0MzA0MjMzNDE5NTMyNjc1Mzk3MDE3MTc1MTE3ODI5NDUzNjAxNDM2OTM2MDM3NDMyMzg4OTYyMjMwOTAyNTk1MjE3MTA3MzkxOTMwOTA3NDI4NDQyNDg4ODE2NjQ4NTI4OTkyNjUwMzY0NjIyNDA2MTA5MDUxOTczMjYyOTM3MzYyMTg5NDcwNTUyNDQ2MjAzNTMzNTQzNTY4NjY5MzAwODY0MzQyMzQwMDgwNjg5Mjg5MjI0OTg1MjU4MjU5MTk1Nzc5NTA3MzgwMDI1ODcwNDk0MDIwNDkyMTE2MDExOTA3NjI0NjI0ODg1ODk5NjMxOTk4ODMwNjg4NTY2OTQ5OTgyNjI5Mjg2Mzk2MjIzNDc2NDE1ODM2Nzg3MDA2Mzg5Nzc0MjYxMzk5NjUxMTY3NTYwNzcyMDc5NjkzMDA1NzMwOTQzNTIzNjAwNzM4Mjc4MzA4NDE2MjA5NzQzNzA1ODQ1MzUxNjQzMDUyMTY1MTcyNTg5NTMwMTU0MTU3NjE2OTYzMDg5NjM4ODg4NDc0NDg3MDA3NjY0NTA2ODk5NTE1OTE5MDAxNzIyMDEyNzczMzU3MDc4MjI4OTIzMDMzNTA1NDQ2MzAxOTQxNzA2OTc2NTY3Mzg5NDk3MzgxMDI2NjIyNDEzNTYyODc5MjM0MTM0NTI5Nzk4NzY2ODY0Nzk1OTQ3NzY1ODcwNDgwMTIyNDk0ODE0MzU0MDQ3MzE2ODY0ODczODMzNDgyNDU5NTc1NTQxNDI4NTE0MTciLCJtIjp7Im1hc3Rlcl9zZWNyZXQiOiIxNjE3NTE3NzgwNjcyMjkxNDYzNTc4ODc1NDk1NTkxODgyOTA3ODYzNTk0NzgyMzk4NjczMTIwMDg2OTEwMjA3NzczODk0ODYyNzQxOTIxMzk2OTE2MDUxNDk2NjYzMjIxNDA5MzA3NjA4NTczMDg1ODExMzAyNTYxNDcyMzgxMjY1NjE4MzQyNzc1NTY5MjQ4OTQ3NzY4Mjc3OTQzMzIxMjcyMTY1MjEyMDAxNDI0NDAwMyJ9LCJtMiI6IjE1MDQ5MTk3MTU3NDcyNjQ0MDMzMzE4OTAxNTc5MDYyNTk5NzA2NzU5MzcwMDk1MTk3NzI1NTE3MTM4OTAyMzcwNDUwMTQ5NDk2NjU0MTEzMzA5NTQ4MTc4MDM3NDU1NjY3Njk2NDA0MDY1ODI5MTUzNDYzNDczNzgzMTk5ODA3MjEzNjg5NDE3MTM2NDI4NDg5NzUwNjUzNTc5MjU0NDY0ODk0OTM4MDkyODY2NTUzNjU5In0sImdlX3Byb29mcyI6W119LCJub25fcmV2b2NfcHJvb2YiOm51bGx9XSwiYWdncmVnYXRlZF9wcm9vZiI6eyJjX2hhc2giOiIzNjk4Mjk3ODU5OTY5Nzg3MjI5MTA5NDY2OTIwMDA3ODEwNDA2ODQ3NTI2MDE2NjgxMTIwNDE4OTQ1NDk0NzcwODQyNjI3MjA2MjEyNCIsImNfbGlzdCI6W1syLDIwOCwzLDUzLDIyOSwxMzksMTAyLDUxLDI0MCwxOTUsMTM1LDExNSwxNzYsMTcyLDE4NCw5OSwxMDksMTU2LDgzLDUyLDIxNSwyMjMsODQsMjU1LDY2LDIyNiwyMjMsMTA1LDExMSwyMjEsMTgwLDk1LDEyMiwxMzMsMjIyLDI3LDM5LDk5LDcwLDEzLDM3LDI0LDI1NSwxMTQsMjM1LDEwOSwxODMsNTEsMjEzLDE5MCwyMjYsMTI2LDExOCwyLDIyMCw3OCw0OSw5LDI0MCw1NSwxNzksMTQ3LDUxLDIwMSwyMTMsMjEzLDEzMCw0LDE4MCwxMDMsMTk1LDgsMjYsMTE4LDE0LDEzMCwxOCwxMzMsMTg3LDYyLDMsOTcsMjEwLDEwMiwxMiwxNjIsNzksMTg0LDU1LDIzMiwyMTksMjIwLDE3NSwyNTUsMTY5LDE5NywxMjMsMTI3LDE2MCwyNSwxNTEsMTg3LDg3LDE5MSwxMDksMTk4LDQ0LDcxLDM4LDUwLDEwNCwyNiwyMTYsMTgwLDIxOCwxNDUsMTAsNzYsMTgwLDE1Nyw5OCwyMzQsNzcsMTY5LDE1MSw2OCwxNzAsMTg5LDE1LDIxNyw5OCwyMzUsMTI0LDE2NywyMzYsMjUzLDExMiwyNDQsMTg5LDk1LDE3OCw2MCw3MiwyMjgsMjIzLDcwLDI3LDYwLDIzNiwyMTIsOTcsMjA1LDIyLDI1MCwzNCwyNDYsMTIyLDM0LDgsMjU1LDIyLDEyNywxNTEsMjQyLDE4MCwxNzEsMTIxLDIyNywzMiwxMDMsNTEsMTcwLDIzNCwyMDYsMjAsMTAyLDIwNCwxMTYsMTk5LDAsMTE5LDExNSwxODAsMjA3LDE2LDQzLDU5LDI0MiwxNzksMTksMTk5LDQ4LDEyNyw5LDYzLDg4LDIxLDAsMjE1LDE3NCw0NywxNzcsMjMyLDE4MiwyNTMsMjQ5LDI0OCwxMTgsMTk2LDI1NCwxMzksMTIsMjksMSw0OCwxMDUsMzMsNCwyMDgsMTA2LDIzNSwyNDcsMjEwLDExMiwyMTAsMTA2LDE5OSwxOTgsNDcsOCwyMzYsNTIsOSw2NywxMjgsMjQwLDI1NCwyMzIsMjEwLDQsMjM5LDE4MywzOSwxOTMsMjQyLDMyLDEzMywxOTQsMTQ4LDk4LDExMSw3NywxNTUsMjA1LDE3OCwxOTcsMTRdXX19LCJyZXF1ZXN0ZWRfcHJvb2YiOnsicmV2ZWFsZWRfYXR0cnMiOnsibmFtZSI6eyJzdWJfcHJvb2ZfaW5kZXgiOjAsInJhdyI6IkFsaWNlIiwiZW5jb2RlZCI6IjI3MDM0NjQwMDI0MTE3MzMxMDMzMDYzMTI4MDQ0MDA0MzE4MjE4NDg2ODE2OTMxNTIwODg2NDA1NTM1NjU5OTM0NDE3NDM4NzgxNTA3In19LCJzZWxmX2F0dGVzdGVkX2F0dHJzIjp7fSwidW5yZXZlYWxlZF9hdHRycyI6e30sInByZWRpY2F0ZXMiOnt9fSwiaWRlbnRpZmllcnMiOlt7InNjaGVtYV9pZCI6Ijd5VzZTb1RqSE5oRDN6WWdtNFBiSzg6Mjp0ZXN0LXNjaGVtYS00ZTk0YzJlNC00ZjQ3LTRmZjMtYTg4OC02ZjY0ZGE2YTkyZGM6MS4wIiwiY3JlZF9kZWZfaWQiOiI3eVc2U29UakhOaEQzellnbTRQYks4OjM6Q0w6NDcyMzE5OlRBRyIsInJldl9yZWdfaWQiOm51bGwsInRpbWVzdGFtcCI6bnVsbH1dfQ==", }, "mime-type": "application/json", }, ], - "~thread": Object { + "~thread": { "thid": "7fcfc074-43ac-43cc-92b9-76afceeebe82", }, }, - "requestMessage": Object { + "requestMessage": { "@id": "7fcfc074-43ac-43cc-92b9-76afceeebe82", "@type": "https://didcomm.org/present-proof/1.0/request-presentation", - "request_presentations~attach": Array [ - Object { + "request_presentations~attach": [ + { "@id": "libindy-request-presentation-0", - "data": Object { + "data": { "base64": "eyJuYW1lIjoicHJvb2YtcmVxdWVzdCIsInZlcnNpb24iOiIxLjAiLCJub25jZSI6IjUyODExNDc1NTIxNzg3NzExMjI1Mzc0NSIsInJlcXVlc3RlZF9hdHRyaWJ1dGVzIjp7Im5hbWUiOnsibmFtZSI6Im5hbWUiLCJyZXN0cmljdGlvbnMiOlt7ImNyZWRfZGVmX2lkIjoiN3lXNlNvVGpITmhEM3pZZ200UGJLODozOkNMOjQ3MjMxOTpUQUcifV19fSwicmVxdWVzdGVkX3ByZWRpY2F0ZXMiOnt9fQ==", }, "mime-type": "application/json", @@ -48,64 +48,64 @@ Object { "threadId": "7fcfc074-43ac-43cc-92b9-76afceeebe82", }, }, - "72c96cd1-1f26-4bf3-8a00-5c00926859a8": Object { + "72c96cd1-1f26-4bf3-8a00-5c00926859a8": { "id": "72c96cd1-1f26-4bf3-8a00-5c00926859a8", - "tags": Object { + "tags": { "connectionId": "946f660f-bfa6-4a98-a801-ebde5da95e2c", "state": "done", "threadId": "00cdd404-82fc-431d-9c18-fb643636d2a5", }, "type": "ProofExchangeRecord", - "value": Object { + "value": { "connectionId": "946f660f-bfa6-4a98-a801-ebde5da95e2c", "createdAt": "2022-09-08T19:36:06.208Z", "id": "72c96cd1-1f26-4bf3-8a00-5c00926859a8", "isVerified": true, - "metadata": Object {}, - "presentationMessage": Object { + "metadata": {}, + "presentationMessage": { "@id": "4185f336-f307-4022-a27d-78d1271586f6", "@type": "https://didcomm.org/present-proof/1.0/presentation", - "presentations~attach": Array [ - Object { + "presentations~attach": [ + { "@id": "libindy-presentation-0", - "data": Object { + "data": { "base64": "eyJwcm9vZiI6eyJwcm9vZnMiOlt7InByaW1hcnlfcHJvb2YiOnsiZXFfcHJvb2YiOnsicmV2ZWFsZWRfYXR0cnMiOnsibmFtZSI6IjI3MDM0NjQwMDI0MTE3MzMxMDMzMDYzMTI4MDQ0MDA0MzE4MjE4NDg2ODE2OTMxNTIwODg2NDA1NTM1NjU5OTM0NDE3NDM4NzgxNTA3In0sImFfcHJpbWUiOiI1MjE1MTkyMzYwMDA2NTg5OTUyMjM1MTYwNjkxMzU1OTc4MjA2NDUzODA3NDUzNTk5OTE5OTkzNzM1NTczNzA1OTA2OTY5MTAwMjIzOTM4MjMyMzE5NDE1Njk1NTMzNjQ4MDMzNzc3NzI4NTE3NzI4Mjk4NDU1OTI0NjQ1NTU1NjQyNjE1NTQ3NTI0MDc0MTAyOTQyNjg4NzQwNDcwMjg4MDAyNTgwMjI4OTg4MDk4MDk0NjQxODc5NjkxNzQwMTY2OTc0MzA2OTczNjQ2MDA3ODg3NTU1NDU2NjM3NTUyODk2NjkxNDc3NDg2MDM1MTIxMjU0NDY0NzA2MDY0Mzg2NzA1MzU0MDk3MDcxNDc5OTkwMzIwNzIxMzQ4MjM1NTQ2NjI5OTcxNzAyNzgwNjUwNTgyMDQ3MzMxOTIwNjE5OTIzODg4NjU1NjI0NjYzMDE4NTAzMDUzOTQ4OTEyNzk4MDE1ODA2Mzk5MTAxNTg3MzAzNTgwNzkyMzQ3OTU2NjE1MDgzODczNDQ5MTM0MjEyOTE2MDY3MTExMjM4MjQzMjM1MTk5OTk1MTUzNjIyMTM1NjA4Nzg5OTc2MTk2NTkzMTczNTU4NTA0NDYxODA0MTk5MzY3MTkwODY2MDY5MTk1MDE5MjA4NjQyMzAwNzE5NjAzMDk5MzI1MjA0Njg4MzkwMjc2NTM5NjQxMDk0Mjc4NTY2NTUzOTU1Mzg4NDAwMDUyNzEyNzk0MjUwODgxODg3NDIzMDM5NTgyMjM5MjQ4NTk0NjYyMDYzMDA0ODI2MzE5NDQxNTUwNzEyNDA3MTQ4NDc4Njc1MCIsImUiOiIxNTk2OTcwODg4Njk3NTIwMjIzMzA3ODgyOTM5MzI5OTY0NTM3NjA4Mzc3NjQ0MTc2MjIyODczMjc2MTc0Mjg5ODkzNjgyNDAzODg0NzkzOTk1NDIwMjQ2MTEzNDk5NjMxNDAyMTQxMzQ0OTYxOTg3NDAyNjAxNDc4NTQ3NjEwNjI5MTgzNzc2NDQiLCJ2IjoiOTE0NzEwNTI1NDAwMDA2MDgzNTMwMTMzOTIxMjQyNTQwMzI5ODAzNjI4MjgyMDA0MDAzMTY1ODIyMjMyOTM0NDYxODAyMjQ4MDk4OTU5MzY1MTY2ODA3MjEwODgzNzIyNjIyMzA1ODQyNjM4NzM4NjIzNzM5NzYwNjEzODc5NDcwOTU5NDkyNjg1ODY5MTY4NTQyMDU1MjAyNzcxMDYxODI4MDEwMTAzNDY3Nzk2MDA5NDIzMTc4NzU2NDQ5OTk3NDQzODk5NDkyMDEyMTE0OTYyMDk5MDgwMDg1MzAxMjUyMDU1NDk0MDUzMTM4MzU4NDE0MjM0MDg5MzQyNDY2Nzg0MTA4ODkwMjA2NjY0NDI1NDE1ODYzNDA5NTI1MjI3NDY0NTg1OTU0MzQ3OTk2OTIwNTU0NTg5Nzc3OTA1MzMzMDMxMzAzMzEzNTI1OTY3NDg3NzAyMDQxODEwNzA3ODQ1OTAyMTAyODAxMTM0NDk3ODAxNjQyNTgwNzQyNjg0Njk5OTk3ODM2NTY0NzM2NDE5NDc0NzM1Mzg2ODkzMTQwMDA4ODIzNjIzMjA1MDA5MzE3NjgzNTIyNjI5NzkwMDY1NDExNDE5MzY3MTc3MTgwMTQ3MDk3ODkwNTkxNDM4NTkyMjQyNzA1MTg5NDM4NDE2MjA4MTA0MjczNjI3MjUyNzc2NzY1NjI0NjExMjk3NjQzODA5NzIwMjcxMDUzOTY1MzM4MTA5Njk1MTM4NjA3NTA0NzExMDc0NDY0ODU5Mzc5MzUxNTI1ODY4NzgyMjk2MzIzNDIwMzIwMTAwMzQ4MDA5MDMyMzIxOTM2ODMzNDk0Nzc2NDUxMjQ0NzMzNTQxMzI3Mzc4MTc3OTYwMTMzOTg2NzE4NTA2NTk0MzE2MjMyMzEwMTk5NjE2NjQ4NzI3MjU0MTUwNjg2MjA4OTQ1MTM3Mzg5MTI5Mjg4NzUyNzE3MjA5NjI3ODcyNjE3NTM3MjEwMDk2NTU2MTkzMzgxMzUwNTgzMDM1NjI5OTg3MjE5MTk3OTQ4OTEyNDI3NzI0MTA1ODE2NzA0NDkyOTMzNzQyMjcxOTQ5NTk1ODAwOTUyMDA5NDczNzQ2OTM5ODU0Mzg0NDI4MzY0MjE0NjUyNjI4MDgyOTQwOTMzODcwNTE1MjE4NTc1MzY4NDgwMjM4OTM2MTc0NDM5NzExNTUzNDI3NDEwNzA2NTgxMTkzMzg2NDM2ODEyIiwibSI6eyJtYXN0ZXJfc2VjcmV0IjoiNDU1NjIzMDY3NjQ4NDQ0NTEzODAyNzM4ODkxODAyMTkxNDg1MzY0NzMzMDkxMTIwMjM5MDg1ODY0NTQxMzU5Njg0MDgyMDczNTgzNDMzMjM4MzU2Njg2MTgwNzI1MzIyMjU2MzgwMjYxNzYyMTc5MTg3MDU3Mjk4Nzg3NzEzNDcyODY5MjE5NDI1ODgyNTEyNzI0ODk4NjkzMDk3OTkwODI4MDIzMjUwNTkyMTAwNDI5OSJ9LCJtMiI6IjEyMjUwNjAyNjEwOTE0Njk5NDEzNDk3MjEyOTYwNTA3OTQ1MDkwOTk1OTMwNjYyMDMyOTA2NTIxNDUxNjI0MDUxMTk2MTg3MjIxNjgxNzEzMDc1OTg0MDY5MjY5MTY3MDQ5ODExMDc3NDk1NTM2MDg4OTE0NTc3MDk2NTgwNjMwODIzNDQyMTk4Nzk5Nzg4NzIyNDIzMzg4MDIyNjE5MzM4MzA2MjM3NTQ2MzI5OTI4MDEifSwiZ2VfcHJvb2ZzIjpbXX0sIm5vbl9yZXZvY19wcm9vZiI6bnVsbH1dLCJhZ2dyZWdhdGVkX3Byb29mIjp7ImNfaGFzaCI6IjU4Njg3MDc5MDA2NTc0OTY1OTA5ODk2MDc5OTU0NTA2NDM2ODg0NzMxOTIxMjk0NDA5NDYxNzI5NjkzMTI4MDM1OTcxMjAwMjY5NzgzIiwiY19saXN0IjpbWzEsMTU3LDMxLDExMiwxNjQsMjA1LDIyNCw3NiwzMiwyMTIsMTczLDc3LDIxNyw2MiwxNzMsMTc3LDQsMTU5LDE0OSwyMzMsMTE3LDEwNCw5MiwxMzgsMzUsMjYsNDgsODUsMjQwLDIzNCw0MywxOTYsNTksMTE0LDI0MiwxNTMsNDAsMTg3LDIzOSwxMDAsMTcwLDIsMzYsMTA3LDIyOSwzNywxMTEsNjAsOTIsOSwxMTcsMjM4LDE0MCwyNDMsMywyNDIsMjM0LDE1MiwxNjYsNiwxNjEsMTM0LDIyMiwxNjMsNDMsNjIsMTIsODEsMjUzLDE3NiwxMjUsMjQsOCw5MiwxMywyMjIsMTcxLDIzMywxODIsMjE1LDIyLDIzNCw5NSwxMTIsMTIyLDEwMiwxNTIsMTA2LDE4Miw0LDIxOSw1NiwxMDEsMTQ3LDIyNywyNDYsMTE1LDY0LDE4Myw0LDY3LDEyNCwxNDAsMTY2LDEzOCwxNCwyNCw1MSwxODUsMTk5LDEwOSwyLDUxLDE5MCwxODEsMjQ3LDYxLDE5MCw4NSwyNDcsMjE3LDIyLDE2NCwxOTIsMjM1LDE4NywxNDgsMjYsMTA5LDI1MywxMTYsMjQxLDY1LDEzNiw0MSwyMjUsMjI5LDE3OCwxMjUsNzksMjQ2LDUzLDQ0LDE5MiwxNDEsMjQzLDE4NCwxNTEsMTIsOSwxMDQsMjE5LDE2Miw3MSw3Miw3LDg5LDgzLDgsMjI1LDEzLDQ4LDEzMywyNDcsMTg3LDExNCw4NSw3Nyw1OSw5OSw3MCw2OSw3MSwxMzMsMTY5LDExMSwxMTAsNzksNDQsMTc0LDIzLDEwMywxNjYsNjksNSwxNDcsNjYsNzcsMTYzLDExMiw0MiwxOTksMTQyLDE0LDIyLDE1MSwyNDgsMzMsMzQsMTk4LDE1NiwyMCwyMzEsMjA0LDcwLDgyLDI1MywxMzgsMTMxLDQyLDIwNSwzMywxMDksMjMyLDE3LDEwNiw2OSw3Miw2MCwyMTAsMjMyLDExMywxMjQsNzYsMTgxLDIwOCwwLDE4NiwyNTEsMTQzLDExMyw0NSwxNzEsMTAsNDMsMTUsMzAsMjA4LDkzLDYsMTY4LDEwNiwyMDMsNDgsMjUwLDIxOSw5LDE3LDEwNCwzNSw1OCwxNTksMTgwLDExMyw2NiwxMzYsNjJdXX19LCJyZXF1ZXN0ZWRfcHJvb2YiOnsicmV2ZWFsZWRfYXR0cnMiOnsiMGZlYTE4OTctNDMxMS00OGYyLWEyYzItMzg3NGU5OTNhZWYwIjp7InN1Yl9wcm9vZl9pbmRleCI6MCwicmF3IjoiQWxpY2UiLCJlbmNvZGVkIjoiMjcwMzQ2NDAwMjQxMTczMzEwMzMwNjMxMjgwNDQwMDQzMTgyMTg0ODY4MTY5MzE1MjA4ODY0MDU1MzU2NTk5MzQ0MTc0Mzg3ODE1MDcifX0sInNlbGZfYXR0ZXN0ZWRfYXR0cnMiOnt9LCJ1bnJldmVhbGVkX2F0dHJzIjp7fSwicHJlZGljYXRlcyI6e319LCJpZGVudGlmaWVycyI6W3sic2NoZW1hX2lkIjoiN3lXNlNvVGpITmhEM3pZZ200UGJLODoyOnRlc3Qtc2NoZW1hLTRlOTRjMmU0LTRmNDctNGZmMy1hODg4LTZmNjRkYTZhOTJkYzoxLjAiLCJjcmVkX2RlZl9pZCI6Ijd5VzZTb1RqSE5oRDN6WWdtNFBiSzg6MzpDTDo0NzIzMTk6VEFHIiwicmV2X3JlZ19pZCI6bnVsbCwidGltZXN0YW1wIjpudWxsfV19", }, "mime-type": "application/json", }, ], - "~thread": Object { + "~thread": { "thid": "00cdd404-82fc-431d-9c18-fb643636d2a5", }, }, - "proposalMessage": Object { + "proposalMessage": { "@id": "00cdd404-82fc-431d-9c18-fb643636d2a5", "@type": "https://didcomm.org/present-proof/1.0/propose-presentation", - "presentation_proposal": Object { + "presentation_proposal": { "@type": "https://didcomm.org/present-proof/1.0/presentation-preview", - "attributes": Array [ - Object { + "attributes": [ + { "cred_def_id": "7yW6SoTjHNhD3zYgm4PbK8:3:CL:472319:TAG", "name": "name", "value": "Alice", }, ], - "predicates": Array [], + "predicates": [], }, }, - "requestMessage": Object { + "requestMessage": { "@id": "2862b25d-396c-46d7-ad09-f48ff4ea6db6", "@type": "https://didcomm.org/present-proof/1.0/request-presentation", - "request_presentations~attach": Array [ - Object { + "request_presentations~attach": [ + { "@id": "libindy-request-presentation-0", - "data": Object { + "data": { "base64": "eyJuYW1lIjoicHJvb2YtcmVxdWVzdCIsInZlcnNpb24iOiIxLjAiLCJub25jZSI6IjQwMzU1MDc0MDYxMTU0MzEwMzA5NzMyMiIsInJlcXVlc3RlZF9hdHRyaWJ1dGVzIjp7IjBmZWExODk3LTQzMTEtNDhmMi1hMmMyLTM4NzRlOTkzYWVmMCI6eyJuYW1lIjoibmFtZSIsInJlc3RyaWN0aW9ucyI6W3siY3JlZF9kZWZfaWQiOiI3eVc2U29UakhOaEQzellnbTRQYks4OjM6Q0w6NDcyMzE5OlRBRyJ9XX19LCJyZXF1ZXN0ZWRfcHJlZGljYXRlcyI6e319", }, "mime-type": "application/json", }, ], - "~thread": Object { + "~thread": { "thid": "00cdd404-82fc-431d-9c18-fb643636d2a5", }, }, @@ -113,54 +113,55 @@ Object { "threadId": "00cdd404-82fc-431d-9c18-fb643636d2a5", }, }, - "STORAGE_VERSION_RECORD_ID": Object { + "STORAGE_VERSION_RECORD_ID": { "id": "STORAGE_VERSION_RECORD_ID", - "tags": Object {}, + "tags": {}, "type": "StorageVersionRecord", - "value": Object { + "value": { "createdAt": "2022-09-08T19:35:53.872Z", "id": "STORAGE_VERSION_RECORD_ID", - "metadata": Object {}, - "storageVersion": "0.3.1", + "metadata": {}, + "storageVersion": "0.4", + "updatedAt": "2022-01-21T22:50:20.522Z", }, }, - "ea840186-3c77-45f4-a2e6-349811ad8994": Object { + "ea840186-3c77-45f4-a2e6-349811ad8994": { "id": "ea840186-3c77-45f4-a2e6-349811ad8994", - "tags": Object { + "tags": { "connectionId": "946f660f-bfa6-4a98-a801-ebde5da95e2c", "state": "done", "threadId": "7fcfc074-43ac-43cc-92b9-76afceeebe82", }, "type": "ProofExchangeRecord", - "value": Object { + "value": { "connectionId": "946f660f-bfa6-4a98-a801-ebde5da95e2c", "createdAt": "2022-09-08T19:36:06.261Z", "id": "ea840186-3c77-45f4-a2e6-349811ad8994", "isVerified": true, - "metadata": Object {}, - "presentationMessage": Object { + "metadata": {}, + "presentationMessage": { "@id": "2481ce81-560b-4ce6-a22b-ee4b6ed369e8", "@type": "https://didcomm.org/present-proof/1.0/presentation", - "presentations~attach": Array [ - Object { + "presentations~attach": [ + { "@id": "libindy-presentation-0", - "data": Object { + "data": { "base64": "eyJwcm9vZiI6eyJwcm9vZnMiOlt7InByaW1hcnlfcHJvb2YiOnsiZXFfcHJvb2YiOnsicmV2ZWFsZWRfYXR0cnMiOnsibmFtZSI6IjI3MDM0NjQwMDI0MTE3MzMxMDMzMDYzMTI4MDQ0MDA0MzE4MjE4NDg2ODE2OTMxNTIwODg2NDA1NTM1NjU5OTM0NDE3NDM4NzgxNTA3In0sImFfcHJpbWUiOiI5MDg5MzE2Mjc0ODc5NTI4MjMzNDA1MTY0NTgwOTM1OTIxMjIwMjMyMzg5ODE0NjE4OTc3ODA3MjA0MDg4OTQ0ODkzNDc1OTE5NTE4Mjc0NDEwNTU3OTU3NjEwNjYzOTAxMTcwODM1NDM2Nzk1NDU4NDU1Mjg3NzEwOTk3NTk3OTA1OTM3NTYyODIyNjg5NTE3MjAyNzQ4NTUxODgzODQ5NjY3MjYwNTA3NjU0NDM5OTk0MjczNDQ0MTU5NTQyMzg3MzI0OTM5OTAzMDcyMDc2MjQ4Njg1MTgyMjA4NTA0OTkyOTg5MTk0NzUwMDgyODU1MTc1NDE1OTIzMjU3MzA0MTQ0NjYxMDc5MDU2NzExMTg3NzMzMDE3NDQ1MTEyOTQyNDAyMTEzNDg0NjM5MTMxMDY2MDc3ODE2NzQzMzY3OTMzMDI3MjY3MTQ3MDIxMTkxODQ0NTQzMzI5NzUzMTA1NTA3MDk0Mzc5OTA5OTYzNjcxMTQ4NzM3Mjk3NDA2MzUxMzk0NTcwNTM3Nzk0NDg1Njc1ODc3MDU5OTI2NTc3MDU4MzY1NTA0MDQwNjAzNDIxMDI3NDYyOTY0OTExNTc5MDAyNjg2NDAzMjMyOTc3OTU0ODM1Nzc2NzQwMDI3NDIxNjI0MTUzNDQ4NzYyODAxODM3OTU3MTQ3NzM0MDkxNDk3NjMwMjA3MTY3MzUzMjAwMTM5ODE4MDg1NjgwMDIzMTc1MDEyNzM4Mjk1NzIwODU2OTMwNDYxMzIxMDQ4NTIxMjQ0ODQ5MjQ5Njc5MDMwMzI0NDcyNjYyOTQxNjc5NDU3OTk3NzQ4NiIsImUiOiI0NTE0MTczNzExODM2MzMzOTk0NjA3MTMwNjQ5MjA0NjEyNzU2Njk1MDI4ODA2NTY0NzI4MzE3OTExNzYxNDA0NTE5Nzk0NjA3NDk4Njg5NjgyOTYxODk3MDc4MDcwMzQ5Nzk0MzUzMDQ1MTY3MTUyMTk2OTU4NTU0NTI5MzgxNjY3MDE5MDA2OSIsInYiOiI1MzM4NDg2NDY2MjE4MTg2ODg3MTUwNzY4NTQ0OTQyMTEyMDcyOTQ1MzczMDQ1MDAzNTk0MTk0MzAxMDA5NDUzMzk5MjMxMDM5NjExMjU4NTE3MTgzODUyMDc4NjI0NjMyMDExNDE2MzI3Mjc1MTM3Nzc1MTAxNjgxODcwMzI4MjY3MTE4MjExNjEwNzAwNDc2MjA5NzMwMTIwODI2NzMyMTkwMDg0ODkyOTc2NTEwMTgxODE2MTkzMzM5MTk0MjE5MDIxOTQ1OTI1NTg4NjEzODEwMjE1Nzg1NDk1NDk0NjQ0NzIwMDM4MjMwMTg1MDUyMDAxMTMxNjE3MjQwMDIyNjQzOTYxNTkwOTU5ODE3ODMxMzg2Mzc5NDQ1MzI2Mzg4NzYzNjQ5MDYxODk4Nzk1ODcwMjE2NTkxMDI3NDkwMzAwMjA0OTc1NzM0NDgyNDM1ODE4MjgwMTQxNzA0MzA0MjMzNDE5NTMyNjc1Mzk3MDE3MTc1MTE3ODI5NDUzNjAxNDM2OTM2MDM3NDMyMzg4OTYyMjMwOTAyNTk1MjE3MTA3MzkxOTMwOTA3NDI4NDQyNDg4ODE2NjQ4NTI4OTkyNjUwMzY0NjIyNDA2MTA5MDUxOTczMjYyOTM3MzYyMTg5NDcwNTUyNDQ2MjAzNTMzNTQzNTY4NjY5MzAwODY0MzQyMzQwMDgwNjg5Mjg5MjI0OTg1MjU4MjU5MTk1Nzc5NTA3MzgwMDI1ODcwNDk0MDIwNDkyMTE2MDExOTA3NjI0NjI0ODg1ODk5NjMxOTk4ODMwNjg4NTY2OTQ5OTgyNjI5Mjg2Mzk2MjIzNDc2NDE1ODM2Nzg3MDA2Mzg5Nzc0MjYxMzk5NjUxMTY3NTYwNzcyMDc5NjkzMDA1NzMwOTQzNTIzNjAwNzM4Mjc4MzA4NDE2MjA5NzQzNzA1ODQ1MzUxNjQzMDUyMTY1MTcyNTg5NTMwMTU0MTU3NjE2OTYzMDg5NjM4ODg4NDc0NDg3MDA3NjY0NTA2ODk5NTE1OTE5MDAxNzIyMDEyNzczMzU3MDc4MjI4OTIzMDMzNTA1NDQ2MzAxOTQxNzA2OTc2NTY3Mzg5NDk3MzgxMDI2NjIyNDEzNTYyODc5MjM0MTM0NTI5Nzk4NzY2ODY0Nzk1OTQ3NzY1ODcwNDgwMTIyNDk0ODE0MzU0MDQ3MzE2ODY0ODczODMzNDgyNDU5NTc1NTQxNDI4NTE0MTciLCJtIjp7Im1hc3Rlcl9zZWNyZXQiOiIxNjE3NTE3NzgwNjcyMjkxNDYzNTc4ODc1NDk1NTkxODgyOTA3ODYzNTk0NzgyMzk4NjczMTIwMDg2OTEwMjA3NzczODk0ODYyNzQxOTIxMzk2OTE2MDUxNDk2NjYzMjIxNDA5MzA3NjA4NTczMDg1ODExMzAyNTYxNDcyMzgxMjY1NjE4MzQyNzc1NTY5MjQ4OTQ3NzY4Mjc3OTQzMzIxMjcyMTY1MjEyMDAxNDI0NDAwMyJ9LCJtMiI6IjE1MDQ5MTk3MTU3NDcyNjQ0MDMzMzE4OTAxNTc5MDYyNTk5NzA2NzU5MzcwMDk1MTk3NzI1NTE3MTM4OTAyMzcwNDUwMTQ5NDk2NjU0MTEzMzA5NTQ4MTc4MDM3NDU1NjY3Njk2NDA0MDY1ODI5MTUzNDYzNDczNzgzMTk5ODA3MjEzNjg5NDE3MTM2NDI4NDg5NzUwNjUzNTc5MjU0NDY0ODk0OTM4MDkyODY2NTUzNjU5In0sImdlX3Byb29mcyI6W119LCJub25fcmV2b2NfcHJvb2YiOm51bGx9XSwiYWdncmVnYXRlZF9wcm9vZiI6eyJjX2hhc2giOiIzNjk4Mjk3ODU5OTY5Nzg3MjI5MTA5NDY2OTIwMDA3ODEwNDA2ODQ3NTI2MDE2NjgxMTIwNDE4OTQ1NDk0NzcwODQyNjI3MjA2MjEyNCIsImNfbGlzdCI6W1syLDIwOCwzLDUzLDIyOSwxMzksMTAyLDUxLDI0MCwxOTUsMTM1LDExNSwxNzYsMTcyLDE4NCw5OSwxMDksMTU2LDgzLDUyLDIxNSwyMjMsODQsMjU1LDY2LDIyNiwyMjMsMTA1LDExMSwyMjEsMTgwLDk1LDEyMiwxMzMsMjIyLDI3LDM5LDk5LDcwLDEzLDM3LDI0LDI1NSwxMTQsMjM1LDEwOSwxODMsNTEsMjEzLDE5MCwyMjYsMTI2LDExOCwyLDIyMCw3OCw0OSw5LDI0MCw1NSwxNzksMTQ3LDUxLDIwMSwyMTMsMjEzLDEzMCw0LDE4MCwxMDMsMTk1LDgsMjYsMTE4LDE0LDEzMCwxOCwxMzMsMTg3LDYyLDMsOTcsMjEwLDEwMiwxMiwxNjIsNzksMTg0LDU1LDIzMiwyMTksMjIwLDE3NSwyNTUsMTY5LDE5NywxMjMsMTI3LDE2MCwyNSwxNTEsMTg3LDg3LDE5MSwxMDksMTk4LDQ0LDcxLDM4LDUwLDEwNCwyNiwyMTYsMTgwLDIxOCwxNDUsMTAsNzYsMTgwLDE1Nyw5OCwyMzQsNzcsMTY5LDE1MSw2OCwxNzAsMTg5LDE1LDIxNyw5OCwyMzUsMTI0LDE2NywyMzYsMjUzLDExMiwyNDQsMTg5LDk1LDE3OCw2MCw3MiwyMjgsMjIzLDcwLDI3LDYwLDIzNiwyMTIsOTcsMjA1LDIyLDI1MCwzNCwyNDYsMTIyLDM0LDgsMjU1LDIyLDEyNywxNTEsMjQyLDE4MCwxNzEsMTIxLDIyNywzMiwxMDMsNTEsMTcwLDIzNCwyMDYsMjAsMTAyLDIwNCwxMTYsMTk5LDAsMTE5LDExNSwxODAsMjA3LDE2LDQzLDU5LDI0MiwxNzksMTksMTk5LDQ4LDEyNyw5LDYzLDg4LDIxLDAsMjE1LDE3NCw0NywxNzcsMjMyLDE4MiwyNTMsMjQ5LDI0OCwxMTgsMTk2LDI1NCwxMzksMTIsMjksMSw0OCwxMDUsMzMsNCwyMDgsMTA2LDIzNSwyNDcsMjEwLDExMiwyMTAsMTA2LDE5OSwxOTgsNDcsOCwyMzYsNTIsOSw2NywxMjgsMjQwLDI1NCwyMzIsMjEwLDQsMjM5LDE4MywzOSwxOTMsMjQyLDMyLDEzMywxOTQsMTQ4LDk4LDExMSw3NywxNTUsMjA1LDE3OCwxOTcsMTRdXX19LCJyZXF1ZXN0ZWRfcHJvb2YiOnsicmV2ZWFsZWRfYXR0cnMiOnsibmFtZSI6eyJzdWJfcHJvb2ZfaW5kZXgiOjAsInJhdyI6IkFsaWNlIiwiZW5jb2RlZCI6IjI3MDM0NjQwMDI0MTE3MzMxMDMzMDYzMTI4MDQ0MDA0MzE4MjE4NDg2ODE2OTMxNTIwODg2NDA1NTM1NjU5OTM0NDE3NDM4NzgxNTA3In19LCJzZWxmX2F0dGVzdGVkX2F0dHJzIjp7fSwidW5yZXZlYWxlZF9hdHRycyI6e30sInByZWRpY2F0ZXMiOnt9fSwiaWRlbnRpZmllcnMiOlt7InNjaGVtYV9pZCI6Ijd5VzZTb1RqSE5oRDN6WWdtNFBiSzg6Mjp0ZXN0LXNjaGVtYS00ZTk0YzJlNC00ZjQ3LTRmZjMtYTg4OC02ZjY0ZGE2YTkyZGM6MS4wIiwiY3JlZF9kZWZfaWQiOiI3eVc2U29UakhOaEQzellnbTRQYks4OjM6Q0w6NDcyMzE5OlRBRyIsInJldl9yZWdfaWQiOm51bGwsInRpbWVzdGFtcCI6bnVsbH1dfQ==", }, "mime-type": "application/json", }, ], - "~thread": Object { + "~thread": { "thid": "7fcfc074-43ac-43cc-92b9-76afceeebe82", }, }, - "requestMessage": Object { + "requestMessage": { "@id": "7fcfc074-43ac-43cc-92b9-76afceeebe82", "@type": "https://didcomm.org/present-proof/1.0/request-presentation", - "request_presentations~attach": Array [ - Object { + "request_presentations~attach": [ + { "@id": "libindy-request-presentation-0", - "data": Object { + "data": { "base64": "eyJuYW1lIjoicHJvb2YtcmVxdWVzdCIsInZlcnNpb24iOiIxLjAiLCJub25jZSI6IjUyODExNDc1NTIxNzg3NzExMjI1Mzc0NSIsInJlcXVlc3RlZF9hdHRyaWJ1dGVzIjp7Im5hbWUiOnsibmFtZSI6Im5hbWUiLCJyZXN0cmljdGlvbnMiOlt7ImNyZWRfZGVmX2lkIjoiN3lXNlNvVGpITmhEM3pZZ200UGJLODozOkNMOjQ3MjMxOTpUQUcifV19fSwicmVxdWVzdGVkX3ByZWRpY2F0ZXMiOnt9fQ==", }, "mime-type": "application/json", @@ -171,63 +172,63 @@ Object { "threadId": "7fcfc074-43ac-43cc-92b9-76afceeebe82", }, }, - "ec02ba64-63e3-46bc-b2a4-9d549d642d30": Object { + "ec02ba64-63e3-46bc-b2a4-9d549d642d30": { "id": "ec02ba64-63e3-46bc-b2a4-9d549d642d30", - "tags": Object { + "tags": { "connectionId": "946f660f-bfa6-4a98-a801-ebde5da95e2c", "state": "done", "threadId": "00cdd404-82fc-431d-9c18-fb643636d2a5", }, "type": "ProofExchangeRecord", - "value": Object { + "value": { "connectionId": "946f660f-bfa6-4a98-a801-ebde5da95e2c", "createdAt": "2022-09-08T19:36:06.208Z", "id": "ec02ba64-63e3-46bc-b2a4-9d549d642d30", - "metadata": Object {}, - "presentationMessage": Object { + "metadata": {}, + "presentationMessage": { "@id": "4185f336-f307-4022-a27d-78d1271586f6", "@type": "https://didcomm.org/present-proof/1.0/presentation", - "presentations~attach": Array [ - Object { + "presentations~attach": [ + { "@id": "libindy-presentation-0", - "data": Object { + "data": { "base64": "eyJwcm9vZiI6eyJwcm9vZnMiOlt7InByaW1hcnlfcHJvb2YiOnsiZXFfcHJvb2YiOnsicmV2ZWFsZWRfYXR0cnMiOnsibmFtZSI6IjI3MDM0NjQwMDI0MTE3MzMxMDMzMDYzMTI4MDQ0MDA0MzE4MjE4NDg2ODE2OTMxNTIwODg2NDA1NTM1NjU5OTM0NDE3NDM4NzgxNTA3In0sImFfcHJpbWUiOiI1MjE1MTkyMzYwMDA2NTg5OTUyMjM1MTYwNjkxMzU1OTc4MjA2NDUzODA3NDUzNTk5OTE5OTkzNzM1NTczNzA1OTA2OTY5MTAwMjIzOTM4MjMyMzE5NDE1Njk1NTMzNjQ4MDMzNzc3NzI4NTE3NzI4Mjk4NDU1OTI0NjQ1NTU1NjQyNjE1NTQ3NTI0MDc0MTAyOTQyNjg4NzQwNDcwMjg4MDAyNTgwMjI4OTg4MDk4MDk0NjQxODc5NjkxNzQwMTY2OTc0MzA2OTczNjQ2MDA3ODg3NTU1NDU2NjM3NTUyODk2NjkxNDc3NDg2MDM1MTIxMjU0NDY0NzA2MDY0Mzg2NzA1MzU0MDk3MDcxNDc5OTkwMzIwNzIxMzQ4MjM1NTQ2NjI5OTcxNzAyNzgwNjUwNTgyMDQ3MzMxOTIwNjE5OTIzODg4NjU1NjI0NjYzMDE4NTAzMDUzOTQ4OTEyNzk4MDE1ODA2Mzk5MTAxNTg3MzAzNTgwNzkyMzQ3OTU2NjE1MDgzODczNDQ5MTM0MjEyOTE2MDY3MTExMjM4MjQzMjM1MTk5OTk1MTUzNjIyMTM1NjA4Nzg5OTc2MTk2NTkzMTczNTU4NTA0NDYxODA0MTk5MzY3MTkwODY2MDY5MTk1MDE5MjA4NjQyMzAwNzE5NjAzMDk5MzI1MjA0Njg4MzkwMjc2NTM5NjQxMDk0Mjc4NTY2NTUzOTU1Mzg4NDAwMDUyNzEyNzk0MjUwODgxODg3NDIzMDM5NTgyMjM5MjQ4NTk0NjYyMDYzMDA0ODI2MzE5NDQxNTUwNzEyNDA3MTQ4NDc4Njc1MCIsImUiOiIxNTk2OTcwODg4Njk3NTIwMjIzMzA3ODgyOTM5MzI5OTY0NTM3NjA4Mzc3NjQ0MTc2MjIyODczMjc2MTc0Mjg5ODkzNjgyNDAzODg0NzkzOTk1NDIwMjQ2MTEzNDk5NjMxNDAyMTQxMzQ0OTYxOTg3NDAyNjAxNDc4NTQ3NjEwNjI5MTgzNzc2NDQiLCJ2IjoiOTE0NzEwNTI1NDAwMDA2MDgzNTMwMTMzOTIxMjQyNTQwMzI5ODAzNjI4MjgyMDA0MDAzMTY1ODIyMjMyOTM0NDYxODAyMjQ4MDk4OTU5MzY1MTY2ODA3MjEwODgzNzIyNjIyMzA1ODQyNjM4NzM4NjIzNzM5NzYwNjEzODc5NDcwOTU5NDkyNjg1ODY5MTY4NTQyMDU1MjAyNzcxMDYxODI4MDEwMTAzNDY3Nzk2MDA5NDIzMTc4NzU2NDQ5OTk3NDQzODk5NDkyMDEyMTE0OTYyMDk5MDgwMDg1MzAxMjUyMDU1NDk0MDUzMTM4MzU4NDE0MjM0MDg5MzQyNDY2Nzg0MTA4ODkwMjA2NjY0NDI1NDE1ODYzNDA5NTI1MjI3NDY0NTg1OTU0MzQ3OTk2OTIwNTU0NTg5Nzc3OTA1MzMzMDMxMzAzMzEzNTI1OTY3NDg3NzAyMDQxODEwNzA3ODQ1OTAyMTAyODAxMTM0NDk3ODAxNjQyNTgwNzQyNjg0Njk5OTk3ODM2NTY0NzM2NDE5NDc0NzM1Mzg2ODkzMTQwMDA4ODIzNjIzMjA1MDA5MzE3NjgzNTIyNjI5NzkwMDY1NDExNDE5MzY3MTc3MTgwMTQ3MDk3ODkwNTkxNDM4NTkyMjQyNzA1MTg5NDM4NDE2MjA4MTA0MjczNjI3MjUyNzc2NzY1NjI0NjExMjk3NjQzODA5NzIwMjcxMDUzOTY1MzM4MTA5Njk1MTM4NjA3NTA0NzExMDc0NDY0ODU5Mzc5MzUxNTI1ODY4NzgyMjk2MzIzNDIwMzIwMTAwMzQ4MDA5MDMyMzIxOTM2ODMzNDk0Nzc2NDUxMjQ0NzMzNTQxMzI3Mzc4MTc3OTYwMTMzOTg2NzE4NTA2NTk0MzE2MjMyMzEwMTk5NjE2NjQ4NzI3MjU0MTUwNjg2MjA4OTQ1MTM3Mzg5MTI5Mjg4NzUyNzE3MjA5NjI3ODcyNjE3NTM3MjEwMDk2NTU2MTkzMzgxMzUwNTgzMDM1NjI5OTg3MjE5MTk3OTQ4OTEyNDI3NzI0MTA1ODE2NzA0NDkyOTMzNzQyMjcxOTQ5NTk1ODAwOTUyMDA5NDczNzQ2OTM5ODU0Mzg0NDI4MzY0MjE0NjUyNjI4MDgyOTQwOTMzODcwNTE1MjE4NTc1MzY4NDgwMjM4OTM2MTc0NDM5NzExNTUzNDI3NDEwNzA2NTgxMTkzMzg2NDM2ODEyIiwibSI6eyJtYXN0ZXJfc2VjcmV0IjoiNDU1NjIzMDY3NjQ4NDQ0NTEzODAyNzM4ODkxODAyMTkxNDg1MzY0NzMzMDkxMTIwMjM5MDg1ODY0NTQxMzU5Njg0MDgyMDczNTgzNDMzMjM4MzU2Njg2MTgwNzI1MzIyMjU2MzgwMjYxNzYyMTc5MTg3MDU3Mjk4Nzg3NzEzNDcyODY5MjE5NDI1ODgyNTEyNzI0ODk4NjkzMDk3OTkwODI4MDIzMjUwNTkyMTAwNDI5OSJ9LCJtMiI6IjEyMjUwNjAyNjEwOTE0Njk5NDEzNDk3MjEyOTYwNTA3OTQ1MDkwOTk1OTMwNjYyMDMyOTA2NTIxNDUxNjI0MDUxMTk2MTg3MjIxNjgxNzEzMDc1OTg0MDY5MjY5MTY3MDQ5ODExMDc3NDk1NTM2MDg4OTE0NTc3MDk2NTgwNjMwODIzNDQyMTk4Nzk5Nzg4NzIyNDIzMzg4MDIyNjE5MzM4MzA2MjM3NTQ2MzI5OTI4MDEifSwiZ2VfcHJvb2ZzIjpbXX0sIm5vbl9yZXZvY19wcm9vZiI6bnVsbH1dLCJhZ2dyZWdhdGVkX3Byb29mIjp7ImNfaGFzaCI6IjU4Njg3MDc5MDA2NTc0OTY1OTA5ODk2MDc5OTU0NTA2NDM2ODg0NzMxOTIxMjk0NDA5NDYxNzI5NjkzMTI4MDM1OTcxMjAwMjY5NzgzIiwiY19saXN0IjpbWzEsMTU3LDMxLDExMiwxNjQsMjA1LDIyNCw3NiwzMiwyMTIsMTczLDc3LDIxNyw2MiwxNzMsMTc3LDQsMTU5LDE0OSwyMzMsMTE3LDEwNCw5MiwxMzgsMzUsMjYsNDgsODUsMjQwLDIzNCw0MywxOTYsNTksMTE0LDI0MiwxNTMsNDAsMTg3LDIzOSwxMDAsMTcwLDIsMzYsMTA3LDIyOSwzNywxMTEsNjAsOTIsOSwxMTcsMjM4LDE0MCwyNDMsMywyNDIsMjM0LDE1MiwxNjYsNiwxNjEsMTM0LDIyMiwxNjMsNDMsNjIsMTIsODEsMjUzLDE3NiwxMjUsMjQsOCw5MiwxMywyMjIsMTcxLDIzMywxODIsMjE1LDIyLDIzNCw5NSwxMTIsMTIyLDEwMiwxNTIsMTA2LDE4Miw0LDIxOSw1NiwxMDEsMTQ3LDIyNywyNDYsMTE1LDY0LDE4Myw0LDY3LDEyNCwxNDAsMTY2LDEzOCwxNCwyNCw1MSwxODUsMTk5LDEwOSwyLDUxLDE5MCwxODEsMjQ3LDYxLDE5MCw4NSwyNDcsMjE3LDIyLDE2NCwxOTIsMjM1LDE4NywxNDgsMjYsMTA5LDI1MywxMTYsMjQxLDY1LDEzNiw0MSwyMjUsMjI5LDE3OCwxMjUsNzksMjQ2LDUzLDQ0LDE5MiwxNDEsMjQzLDE4NCwxNTEsMTIsOSwxMDQsMjE5LDE2Miw3MSw3Miw3LDg5LDgzLDgsMjI1LDEzLDQ4LDEzMywyNDcsMTg3LDExNCw4NSw3Nyw1OSw5OSw3MCw2OSw3MSwxMzMsMTY5LDExMSwxMTAsNzksNDQsMTc0LDIzLDEwMywxNjYsNjksNSwxNDcsNjYsNzcsMTYzLDExMiw0MiwxOTksMTQyLDE0LDIyLDE1MSwyNDgsMzMsMzQsMTk4LDE1NiwyMCwyMzEsMjA0LDcwLDgyLDI1MywxMzgsMTMxLDQyLDIwNSwzMywxMDksMjMyLDE3LDEwNiw2OSw3Miw2MCwyMTAsMjMyLDExMywxMjQsNzYsMTgxLDIwOCwwLDE4NiwyNTEsMTQzLDExMyw0NSwxNzEsMTAsNDMsMTUsMzAsMjA4LDkzLDYsMTY4LDEwNiwyMDMsNDgsMjUwLDIxOSw5LDE3LDEwNCwzNSw1OCwxNTksMTgwLDExMyw2NiwxMzYsNjJdXX19LCJyZXF1ZXN0ZWRfcHJvb2YiOnsicmV2ZWFsZWRfYXR0cnMiOnsiMGZlYTE4OTctNDMxMS00OGYyLWEyYzItMzg3NGU5OTNhZWYwIjp7InN1Yl9wcm9vZl9pbmRleCI6MCwicmF3IjoiQWxpY2UiLCJlbmNvZGVkIjoiMjcwMzQ2NDAwMjQxMTczMzEwMzMwNjMxMjgwNDQwMDQzMTgyMTg0ODY4MTY5MzE1MjA4ODY0MDU1MzU2NTk5MzQ0MTc0Mzg3ODE1MDcifX0sInNlbGZfYXR0ZXN0ZWRfYXR0cnMiOnt9LCJ1bnJldmVhbGVkX2F0dHJzIjp7fSwicHJlZGljYXRlcyI6e319LCJpZGVudGlmaWVycyI6W3sic2NoZW1hX2lkIjoiN3lXNlNvVGpITmhEM3pZZ200UGJLODoyOnRlc3Qtc2NoZW1hLTRlOTRjMmU0LTRmNDctNGZmMy1hODg4LTZmNjRkYTZhOTJkYzoxLjAiLCJjcmVkX2RlZl9pZCI6Ijd5VzZTb1RqSE5oRDN6WWdtNFBiSzg6MzpDTDo0NzIzMTk6VEFHIiwicmV2X3JlZ19pZCI6bnVsbCwidGltZXN0YW1wIjpudWxsfV19", }, "mime-type": "application/json", }, ], - "~thread": Object { + "~thread": { "thid": "00cdd404-82fc-431d-9c18-fb643636d2a5", }, }, - "proposalMessage": Object { + "proposalMessage": { "@id": "00cdd404-82fc-431d-9c18-fb643636d2a5", "@type": "https://didcomm.org/present-proof/1.0/propose-presentation", - "presentation_proposal": Object { + "presentation_proposal": { "@type": "https://didcomm.org/present-proof/1.0/presentation-preview", - "attributes": Array [ - Object { + "attributes": [ + { "cred_def_id": "7yW6SoTjHNhD3zYgm4PbK8:3:CL:472319:TAG", "name": "name", "value": "Alice", }, ], - "predicates": Array [], + "predicates": [], }, }, - "requestMessage": Object { + "requestMessage": { "@id": "2862b25d-396c-46d7-ad09-f48ff4ea6db6", "@type": "https://didcomm.org/present-proof/1.0/request-presentation", - "request_presentations~attach": Array [ - Object { + "request_presentations~attach": [ + { "@id": "libindy-request-presentation-0", - "data": Object { + "data": { "base64": "eyJuYW1lIjoicHJvb2YtcmVxdWVzdCIsInZlcnNpb24iOiIxLjAiLCJub25jZSI6IjQwMzU1MDc0MDYxMTU0MzEwMzA5NzMyMiIsInJlcXVlc3RlZF9hdHRyaWJ1dGVzIjp7IjBmZWExODk3LTQzMTEtNDhmMi1hMmMyLTM4NzRlOTkzYWVmMCI6eyJuYW1lIjoibmFtZSIsInJlc3RyaWN0aW9ucyI6W3siY3JlZF9kZWZfaWQiOiI3eVc2U29UakhOaEQzellnbTRQYks4OjM6Q0w6NDcyMzE5OlRBRyJ9XX19LCJyZXF1ZXN0ZWRfcHJlZGljYXRlcyI6e319", }, "mime-type": "application/json", }, ], - "~thread": Object { + "~thread": { "thid": "00cdd404-82fc-431d-9c18-fb643636d2a5", }, }, @@ -239,36 +240,36 @@ Object { `; exports[`UpdateAssistant | v0.2 - v0.3.1 should correctly update the did records 1`] = ` -Object { - "1-4e4f-41d9-94c4-f49351b811f1": Object { +{ + "1-4e4f-41d9-94c4-f49351b811f1": { "id": "1-4e4f-41d9-94c4-f49351b811f1", - "tags": Object { + "tags": { "did": "did:peer:1zQmWxKCTkKYQvzdUshMWy7b8vy3Y7uLtB9hp6BbQhKEtQCd", "legacyUnqualifiedDid": undefined, "method": "peer", "methodSpecificIdentifier": "1zQmWxKCTkKYQvzdUshMWy7b8vy3Y7uLtB9hp6BbQhKEtQCd", - "recipientKeyFingerprints": Array [ + "recipientKeyFingerprints": [ "z6Mkpg6nDPuYdLMuzNEjaa2Xprh3J2MC1WtNakWUEFqC4wvU", ], "role": "created", }, "type": "DidRecord", - "value": Object { - "_tags": Object { + "value": { + "_tags": { "method": "peer", - "recipientKeyFingerprints": Array [ + "recipientKeyFingerprints": [ "z6Mkpg6nDPuYdLMuzNEjaa2Xprh3J2MC1WtNakWUEFqC4wvU", ], "role": "created", }, "createdAt": "2022-12-27T13:51:21.344Z", "did": "did:peer:1zQmWxKCTkKYQvzdUshMWy7b8vy3Y7uLtB9hp6BbQhKEtQCd", - "didDocument": Object { - "@context": Array [ + "didDocument": { + "@context": [ "https://w3id.org/did/v1", ], - "authentication": Array [ - Object { + "authentication": [ + { "controller": "#id", "id": "#57a05508-1d1c-474c-8c68-1afcf3188720", "publicKeyBase58": "BDqjd9f7HnsSssQ2u14gym93UT5Lbde1tjbYPysB9j96", @@ -276,22 +277,22 @@ Object { }, ], "id": "did:peer:1zQmWxKCTkKYQvzdUshMWy7b8vy3Y7uLtB9hp6BbQhKEtQCd", - "keyAgreement": Array [ - Object { + "keyAgreement": [ + { "controller": "#id", "id": "#59d2ce6f-c9fc-49c6-a8f6-eab14820b028", "publicKeyBase58": "avivQP6GvWj6cBbxbXSSZnZTA4tGsvQ5DB9FXm45tZt", "type": "X25519KeyAgreementKey2019", }, ], - "service": Array [ - Object { + "service": [ + { "id": "#inline-0", "priority": 0, - "recipientKeys": Array [ + "recipientKeys": [ "#57a05508-1d1c-474c-8c68-1afcf3188720", ], - "routingKeys": Array [ + "routingKeys": [ "did:key:z6MkqhwtVT5V6kwsNkErPNgSWuRGd4QLFQ324FMfw5oWGHop#z6MkqhwtVT5V6kwsNkErPNgSWuRGd4QLFQ324FMfw5oWGHop", ], "serviceEndpoint": "ws://ssi.mediator.com", @@ -300,39 +301,40 @@ Object { ], }, "id": "1-4e4f-41d9-94c4-f49351b811f1", - "metadata": Object {}, + "metadata": {}, "role": "created", + "updatedAt": "2022-01-21T22:50:20.522Z", }, }, - "2-4e4f-41d9-94c4-f49351b811f1": Object { + "2-4e4f-41d9-94c4-f49351b811f1": { "id": "2-4e4f-41d9-94c4-f49351b811f1", - "tags": Object { + "tags": { "did": "did:peer:1zQmavZzrLPRYRGd5CgyKV4GBZG43eGzjic9FHXc7jLHY14W", "legacyUnqualifiedDid": undefined, "method": "peer", "methodSpecificIdentifier": "1zQmavZzrLPRYRGd5CgyKV4GBZG43eGzjic9FHXc7jLHY14W", - "recipientKeyFingerprints": Array [ + "recipientKeyFingerprints": [ "z6MkuLL6bQykGpposTrQhfYceQRR4JDzvdm133xpwb7Dv1oz", ], "role": "received", }, "type": "DidRecord", - "value": Object { - "_tags": Object { + "value": { + "_tags": { "method": "peer", - "recipientKeyFingerprints": Array [ + "recipientKeyFingerprints": [ "z6MkuLL6bQykGpposTrQhfYceQRR4JDzvdm133xpwb7Dv1oz", ], "role": "received", }, "createdAt": "2022-12-27T13:51:51.414Z", "did": "did:peer:1zQmavZzrLPRYRGd5CgyKV4GBZG43eGzjic9FHXc7jLHY14W", - "didDocument": Object { - "@context": Array [ + "didDocument": { + "@context": [ "https://w3id.org/did/v1", ], - "authentication": Array [ - Object { + "authentication": [ + { "controller": "#id", "id": "#6173438e-09a5-4b1e-895a-0563f5a169b7", "publicKeyBase58": "Ft541AjJwHLLky1i26amoJsREix9WkWeM33u7K9Czo2c", @@ -340,61 +342,62 @@ Object { }, ], "id": "did:peer:1zQmavZzrLPRYRGd5CgyKV4GBZG43eGzjic9FHXc7jLHY14W", - "keyAgreement": Array [ - Object { + "keyAgreement": [ + { "controller": "#id", "id": "#a0448ee3-093a-4b16-bc59-8bf8559d60a5", "publicKeyBase58": "JCfZJ72mtgGE9xuekJKV6yoAGzLgQCNkdHKkct8uaNKb", "type": "X25519KeyAgreementKey2019", }, ], - "service": Array [ - Object { + "service": [ + { "id": "#inline-0", "priority": 0, - "recipientKeys": Array [ + "recipientKeys": [ "#6173438e-09a5-4b1e-895a-0563f5a169b7", ], - "routingKeys": Array [], + "routingKeys": [], "serviceEndpoint": "http://ssi.verifier.com", "type": "did-communication", }, ], }, "id": "2-4e4f-41d9-94c4-f49351b811f1", - "metadata": Object {}, + "metadata": {}, "role": "received", + "updatedAt": "2022-01-21T22:50:20.522Z", }, }, - "3-4e4f-41d9-94c4-f49351b811f1": Object { + "3-4e4f-41d9-94c4-f49351b811f1": { "id": "3-4e4f-41d9-94c4-f49351b811f1", - "tags": Object { + "tags": { "did": "did:peer:1zQmZqQYzwqsYjj7z8kixxDXf9nux8TAsqj2izSpX1oCrd7q", "legacyUnqualifiedDid": undefined, "method": "peer", "methodSpecificIdentifier": "1zQmZqQYzwqsYjj7z8kixxDXf9nux8TAsqj2izSpX1oCrd7q", - "recipientKeyFingerprints": Array [ + "recipientKeyFingerprints": [ "z6Mkv6Kd744JcBL5P9gi5Ddtehxn1gSMTgM9kbTdfQ9soB8G", ], "role": "created", }, "type": "DidRecord", - "value": Object { - "_tags": Object { + "value": { + "_tags": { "method": "peer", - "recipientKeyFingerprints": Array [ + "recipientKeyFingerprints": [ "z6Mkv6Kd744JcBL5P9gi5Ddtehxn1gSMTgM9kbTdfQ9soB8G", ], "role": "created", }, "createdAt": "2022-12-27T13:50:32.815Z", "did": "did:peer:1zQmZqQYzwqsYjj7z8kixxDXf9nux8TAsqj2izSpX1oCrd7q", - "didDocument": Object { - "@context": Array [ + "didDocument": { + "@context": [ "https://w3id.org/did/v1", ], - "authentication": Array [ - Object { + "authentication": [ + { "controller": "#id", "id": "#d9d97b3e-5615-4e60-9366-a8ab1ec1a84d", "publicKeyBase58": "Ge4aWoosGdqcGer1Peg3ocQnC7AW3o6o4aYhq8BrsxLt", @@ -402,61 +405,62 @@ Object { }, ], "id": "did:peer:1zQmZqQYzwqsYjj7z8kixxDXf9nux8TAsqj2izSpX1oCrd7q", - "keyAgreement": Array [ - Object { + "keyAgreement": [ + { "controller": "#id", "id": "#c30ea91b-5f49-461f-b0bd-046f947ef668", "publicKeyBase58": "HKBdBGRK8uxgCwge2QHPBzVuuayEQFhC2LM3g1fzMFGE", "type": "X25519KeyAgreementKey2019", }, ], - "service": Array [ - Object { + "service": [ + { "id": "#inline-0", "priority": 0, - "recipientKeys": Array [ + "recipientKeys": [ "#d9d97b3e-5615-4e60-9366-a8ab1ec1a84d", ], - "routingKeys": Array [], + "routingKeys": [], "serviceEndpoint": "didcomm:transport/queue", "type": "did-communication", }, ], }, "id": "3-4e4f-41d9-94c4-f49351b811f1", - "metadata": Object {}, + "metadata": {}, "role": "created", + "updatedAt": "2022-01-21T22:50:20.522Z", }, }, - "4-4e4f-41d9-94c4-f49351b811f1": Object { + "4-4e4f-41d9-94c4-f49351b811f1": { "id": "4-4e4f-41d9-94c4-f49351b811f1", - "tags": Object { + "tags": { "did": "did:peer:1zQmRnGYmYBdSinH2953ZgfHWEpqp5W6kJmmYBgRaCs4Yudx", "legacyUnqualifiedDid": undefined, "method": "peer", "methodSpecificIdentifier": "1zQmRnGYmYBdSinH2953ZgfHWEpqp5W6kJmmYBgRaCs4Yudx", - "recipientKeyFingerprints": Array [ + "recipientKeyFingerprints": [ "z6MkvcuHjVcNRBM78E3xWy7MnVqQV9hAvTawBsPmg3bR1DaE", ], "role": "created", }, "type": "DidRecord", - "value": Object { - "_tags": Object { + "value": { + "_tags": { "method": "peer", - "recipientKeyFingerprints": Array [ + "recipientKeyFingerprints": [ "z6MkvcuHjVcNRBM78E3xWy7MnVqQV9hAvTawBsPmg3bR1DaE", ], "role": "created", }, "createdAt": "2022-12-27T13:51:50.193Z", "did": "did:peer:1zQmRnGYmYBdSinH2953ZgfHWEpqp5W6kJmmYBgRaCs4Yudx", - "didDocument": Object { - "@context": Array [ + "didDocument": { + "@context": [ "https://w3id.org/did/v1", ], - "authentication": Array [ - Object { + "authentication": [ + { "controller": "#id", "id": "#22219a28-b52a-4024-bc0f-62d3969131fd", "publicKeyBase58": "HAeF9FMw5dre1jDFqQ9WwQHQfaRKWaLaVrUqqmdQ5znr", @@ -464,22 +468,22 @@ Object { }, ], "id": "did:peer:1zQmRnGYmYBdSinH2953ZgfHWEpqp5W6kJmmYBgRaCs4Yudx", - "keyAgreement": Array [ - Object { + "keyAgreement": [ + { "controller": "#id", "id": "#0f9535d1-9c9c-4491-be1e-1628f365b513", "publicKeyBase58": "FztF8HCahTeL9gYHoHnDFo6HruwnKB19ZtbHFbLndAmE", "type": "X25519KeyAgreementKey2019", }, ], - "service": Array [ - Object { + "service": [ + { "id": "#inline-0", "priority": 0, - "recipientKeys": Array [ + "recipientKeys": [ "#22219a28-b52a-4024-bc0f-62d3969131fd", ], - "routingKeys": Array [ + "routingKeys": [ "did:key:z6MkqhwtVT5V6kwsNkErPNgSWuRGd4QLFQ324FMfw5oWGHop#z6MkqhwtVT5V6kwsNkErPNgSWuRGd4QLFQ324FMfw5oWGHop", ], "serviceEndpoint": "ws://ssi.mediator.com", @@ -488,39 +492,40 @@ Object { ], }, "id": "4-4e4f-41d9-94c4-f49351b811f1", - "metadata": Object {}, + "metadata": {}, "role": "created", + "updatedAt": "2022-01-21T22:50:20.522Z", }, }, - "5-4e4f-41d9-94c4-f49351b811f1": Object { + "5-4e4f-41d9-94c4-f49351b811f1": { "id": "5-4e4f-41d9-94c4-f49351b811f1", - "tags": Object { + "tags": { "did": "did:peer:1zQmb9oo8fn8AZ7rWh1DAfTbM3mnPp9Hq2bJFdHiqjnZX2Ce", "legacyUnqualifiedDid": undefined, "method": "peer", "methodSpecificIdentifier": "1zQmb9oo8fn8AZ7rWh1DAfTbM3mnPp9Hq2bJFdHiqjnZX2Ce", - "recipientKeyFingerprints": Array [ + "recipientKeyFingerprints": [ "z6Mkp1wyxzqoUrZ3TvnerfZBq48o1XLDSPH8ibFALkNABSgh", ], "role": "received", }, "type": "DidRecord", - "value": Object { - "_tags": Object { + "value": { + "_tags": { "method": "peer", - "recipientKeyFingerprints": Array [ + "recipientKeyFingerprints": [ "z6Mkp1wyxzqoUrZ3TvnerfZBq48o1XLDSPH8ibFALkNABSgh", ], "role": "received", }, "createdAt": "2022-12-27T13:50:44.957Z", "did": "did:peer:1zQmb9oo8fn8AZ7rWh1DAfTbM3mnPp9Hq2bJFdHiqjnZX2Ce", - "didDocument": Object { - "@context": Array [ + "didDocument": { + "@context": [ "https://w3id.org/did/v1", ], - "authentication": Array [ - Object { + "authentication": [ + { "controller": "#id", "id": "#075b7e8b-a7bd-41e1-9b01-044f1ccab1a6", "publicKeyBase58": "AZgwNkbN9K4aMRwxB6bLyxaoBx4N2W2n2aLEWUQ9GDuK", @@ -528,61 +533,62 @@ Object { }, ], "id": "did:peer:1zQmb9oo8fn8AZ7rWh1DAfTbM3mnPp9Hq2bJFdHiqjnZX2Ce", - "keyAgreement": Array [ - Object { + "keyAgreement": [ + { "controller": "#id", "id": "#bf888dc5-0f54-4e71-9058-c43bebfc7d01", "publicKeyBase58": "3Q8wpgxdCVdJfznYERZR1r9eVnCy7oxpjzGVucDLF2tG", "type": "X25519KeyAgreementKey2019", }, ], - "service": Array [ - Object { + "service": [ + { "id": "#inline-0", "priority": 0, - "recipientKeys": Array [ + "recipientKeys": [ "#075b7e8b-a7bd-41e1-9b01-044f1ccab1a6", ], - "routingKeys": Array [], + "routingKeys": [], "serviceEndpoint": "ws://ssi.mediator.com", "type": "did-communication", }, ], }, "id": "5-4e4f-41d9-94c4-f49351b811f1", - "metadata": Object {}, + "metadata": {}, "role": "received", + "updatedAt": "2022-01-21T22:50:20.522Z", }, }, - "6-4e4f-41d9-94c4-f49351b811f1": Object { + "6-4e4f-41d9-94c4-f49351b811f1": { "id": "6-4e4f-41d9-94c4-f49351b811f1", - "tags": Object { + "tags": { "did": "did:peer:1zQmSzC6uhWYcxtkr2fBepr66kjvSHuRsSfbmei7nrGj7HfN", "legacyUnqualifiedDid": undefined, "method": "peer", "methodSpecificIdentifier": "1zQmSzC6uhWYcxtkr2fBepr66kjvSHuRsSfbmei7nrGj7HfN", - "recipientKeyFingerprints": Array [ + "recipientKeyFingerprints": [ "z6MkrQzQqPtNjJHGLGjWC7H3mjMSNA36bCBTYJeoR44JoFvH", ], "role": "received", }, "type": "DidRecord", - "value": Object { - "_tags": Object { + "value": { + "_tags": { "method": "peer", - "recipientKeyFingerprints": Array [ + "recipientKeyFingerprints": [ "z6MkrQzQqPtNjJHGLGjWC7H3mjMSNA36bCBTYJeoR44JoFvH", ], "role": "received", }, "createdAt": "2022-12-27T13:50:34.057Z", "did": "did:peer:1zQmSzC6uhWYcxtkr2fBepr66kjvSHuRsSfbmei7nrGj7HfN", - "didDocument": Object { - "@context": Array [ + "didDocument": { + "@context": [ "https://w3id.org/did/v1", ], - "authentication": Array [ - Object { + "authentication": [ + { "controller": "#id", "id": "#b6d349fb-93fb-4298-b57a-3f2fecffccd8", "publicKeyBase58": "CxjNF9dwPknoDmtoWYKCvdoSYamFBJw6rHjsan6Ht38u", @@ -590,61 +596,62 @@ Object { }, ], "id": "did:peer:1zQmSzC6uhWYcxtkr2fBepr66kjvSHuRsSfbmei7nrGj7HfN", - "keyAgreement": Array [ - Object { + "keyAgreement": [ + { "controller": "#id", "id": "#c0917f9f-1102-4f13-800c-ff7b522999eb", "publicKeyBase58": "Eso3A6AmL5qiWr9syqHx8NgdQ8EMkYcitmrW5G7bX9uU", "type": "X25519KeyAgreementKey2019", }, ], - "service": Array [ - Object { + "service": [ + { "id": "#inline-0", "priority": 0, - "recipientKeys": Array [ + "recipientKeys": [ "#b6d349fb-93fb-4298-b57a-3f2fecffccd8", ], - "routingKeys": Array [], + "routingKeys": [], "serviceEndpoint": "ws://ssi.issuer.com", "type": "did-communication", }, ], }, "id": "6-4e4f-41d9-94c4-f49351b811f1", - "metadata": Object {}, + "metadata": {}, "role": "received", + "updatedAt": "2022-01-21T22:50:20.522Z", }, }, - "7-4e4f-41d9-94c4-f49351b811f1": Object { + "7-4e4f-41d9-94c4-f49351b811f1": { "id": "7-4e4f-41d9-94c4-f49351b811f1", - "tags": Object { + "tags": { "did": "did:peer:1zQmPaCELen1JWMWmhVZS16nDmrAC9yGKQcJCcBs5spXakA3", "legacyUnqualifiedDid": undefined, "method": "peer", "methodSpecificIdentifier": "1zQmPaCELen1JWMWmhVZS16nDmrAC9yGKQcJCcBs5spXakA3", - "recipientKeyFingerprints": Array [ + "recipientKeyFingerprints": [ "z6Mkt4ohp4uT74HdENeGVGAyVFTerCzzuDXP8kpU3yo6SqqM", ], "role": "received", }, "type": "DidRecord", - "value": Object { - "_tags": Object { + "value": { + "_tags": { "method": "peer", - "recipientKeyFingerprints": Array [ + "recipientKeyFingerprints": [ "z6Mkt4ohp4uT74HdENeGVGAyVFTerCzzuDXP8kpU3yo6SqqM", ], "role": "received", }, "createdAt": "2022-12-27T13:51:22.817Z", "did": "did:peer:1zQmPaCELen1JWMWmhVZS16nDmrAC9yGKQcJCcBs5spXakA3", - "didDocument": Object { - "@context": Array [ + "didDocument": { + "@context": [ "https://w3id.org/did/v1", ], - "authentication": Array [ - Object { + "authentication": [ + { "controller": "#id", "id": "#5ea98568-dfcd-4614-9495-ba95ec2665d3", "publicKeyBase58": "EcYfDpf1mWoA7soZohD8e9uf2dj9VLH2SjuYDhq5Xd3y", @@ -652,61 +659,62 @@ Object { }, ], "id": "did:peer:1zQmPaCELen1JWMWmhVZS16nDmrAC9yGKQcJCcBs5spXakA3", - "keyAgreement": Array [ - Object { + "keyAgreement": [ + { "controller": "#id", "id": "#506e5ead-ddbc-44ef-848b-6593a692a916", "publicKeyBase58": "EeQHhb6CqWGrQR9PfWpS1L8CedsbK2mZfPdaxaHN4s8b", "type": "X25519KeyAgreementKey2019", }, ], - "service": Array [ - Object { + "service": [ + { "id": "#inline-0", "priority": 0, - "recipientKeys": Array [ + "recipientKeys": [ "#5ea98568-dfcd-4614-9495-ba95ec2665d3", ], - "routingKeys": Array [], + "routingKeys": [], "serviceEndpoint": "http://ssi.verifier.com", "type": "did-communication", }, ], }, "id": "7-4e4f-41d9-94c4-f49351b811f1", - "metadata": Object {}, + "metadata": {}, "role": "received", + "updatedAt": "2022-01-21T22:50:20.522Z", }, }, - "8-4e4f-41d9-94c4-f49351b811f1": Object { + "8-4e4f-41d9-94c4-f49351b811f1": { "id": "8-4e4f-41d9-94c4-f49351b811f1", - "tags": Object { + "tags": { "did": "did:peer:1zQmUo1HoiciJS628w4SHweg4Pzs4bZLM4KLZgroSmUwBw7S", "legacyUnqualifiedDid": undefined, "method": "peer", "methodSpecificIdentifier": "1zQmUo1HoiciJS628w4SHweg4Pzs4bZLM4KLZgroSmUwBw7S", - "recipientKeyFingerprints": Array [ + "recipientKeyFingerprints": [ "z6MktdgEKUiH5kt5t2dKAH14WzFYhzj3JFobUf2PFFQAg2ma", ], "role": "created", }, "type": "DidRecord", - "value": Object { - "_tags": Object { + "value": { + "_tags": { "method": "peer", - "recipientKeyFingerprints": Array [ + "recipientKeyFingerprints": [ "z6MktdgEKUiH5kt5t2dKAH14WzFYhzj3JFobUf2PFFQAg2ma", ], "role": "created", }, "createdAt": "2022-12-27T13:50:43.937Z", "did": "did:peer:1zQmUo1HoiciJS628w4SHweg4Pzs4bZLM4KLZgroSmUwBw7S", - "didDocument": Object { - "@context": Array [ + "didDocument": { + "@context": [ "https://w3id.org/did/v1", ], - "authentication": Array [ - Object { + "authentication": [ + { "controller": "#id", "id": "#12b8b7d4-87b9-4638-a929-f98df2f1f566", "publicKeyBase58": "FBRBjETqkDPcmXncUi3DfthYtRTBtNZEne7TQyS9kozC", @@ -714,84 +722,86 @@ Object { }, ], "id": "did:peer:1zQmUo1HoiciJS628w4SHweg4Pzs4bZLM4KLZgroSmUwBw7S", - "keyAgreement": Array [ - Object { + "keyAgreement": [ + { "controller": "#id", "id": "#77b9cf84-2441-419f-b295-945d06e29edc", "publicKeyBase58": "EgsQArrmCUru9MxR1RNNiomnMFz6E3ia2GfjVvoCjAWY", "type": "X25519KeyAgreementKey2019", }, ], - "service": Array [ - Object { + "service": [ + { "id": "#inline-0", "priority": 0, - "recipientKeys": Array [ + "recipientKeys": [ "#12b8b7d4-87b9-4638-a929-f98df2f1f566", ], - "routingKeys": Array [], + "routingKeys": [], "serviceEndpoint": "didcomm:transport/queue", "type": "did-communication", }, ], }, "id": "8-4e4f-41d9-94c4-f49351b811f1", - "metadata": Object {}, + "metadata": {}, "role": "created", + "updatedAt": "2022-01-21T22:50:20.522Z", }, }, - "STORAGE_VERSION_RECORD_ID": Object { + "STORAGE_VERSION_RECORD_ID": { "id": "STORAGE_VERSION_RECORD_ID", - "tags": Object {}, + "tags": {}, "type": "StorageVersionRecord", - "value": Object { + "value": { "createdAt": "2022-09-08T19:35:53.872Z", "id": "STORAGE_VERSION_RECORD_ID", - "metadata": Object {}, - "storageVersion": "0.3.1", + "metadata": {}, + "storageVersion": "0.4", + "updatedAt": "2022-01-21T22:50:20.522Z", }, }, } `; exports[`UpdateAssistant | v0.2 - v0.3.1 should correctly update the proofs records and create didcomm records with auto update 1`] = ` -Object { - "3d5d7ad4-f0aa-4b1b-8c2c-780ee383564e": Object { +{ + "3d5d7ad4-f0aa-4b1b-8c2c-780ee383564e": { "id": "3d5d7ad4-f0aa-4b1b-8c2c-780ee383564e", - "tags": Object { + "tags": { "connectionId": "946f660f-bfa6-4a98-a801-ebde5da95e2c", "state": "done", "threadId": "7fcfc074-43ac-43cc-92b9-76afceeebe82", }, "type": "ProofExchangeRecord", - "value": Object { + "value": { "connectionId": "946f660f-bfa6-4a98-a801-ebde5da95e2c", "createdAt": "2022-09-08T19:36:06.261Z", "id": "3d5d7ad4-f0aa-4b1b-8c2c-780ee383564e", - "metadata": Object {}, - "presentationMessage": Object { + "metadata": {}, + "presentationMessage": { "@id": "2481ce81-560b-4ce6-a22b-ee4b6ed369e8", "@type": "https://didcomm.org/present-proof/1.0/presentation", - "presentations~attach": Array [ - Object { + "presentations~attach": [ + { "@id": "libindy-presentation-0", - "data": Object { + "data": { "base64": "eyJwcm9vZiI6eyJwcm9vZnMiOlt7InByaW1hcnlfcHJvb2YiOnsiZXFfcHJvb2YiOnsicmV2ZWFsZWRfYXR0cnMiOnsibmFtZSI6IjI3MDM0NjQwMDI0MTE3MzMxMDMzMDYzMTI4MDQ0MDA0MzE4MjE4NDg2ODE2OTMxNTIwODg2NDA1NTM1NjU5OTM0NDE3NDM4NzgxNTA3In0sImFfcHJpbWUiOiI5MDg5MzE2Mjc0ODc5NTI4MjMzNDA1MTY0NTgwOTM1OTIxMjIwMjMyMzg5ODE0NjE4OTc3ODA3MjA0MDg4OTQ0ODkzNDc1OTE5NTE4Mjc0NDEwNTU3OTU3NjEwNjYzOTAxMTcwODM1NDM2Nzk1NDU4NDU1Mjg3NzEwOTk3NTk3OTA1OTM3NTYyODIyNjg5NTE3MjAyNzQ4NTUxODgzODQ5NjY3MjYwNTA3NjU0NDM5OTk0MjczNDQ0MTU5NTQyMzg3MzI0OTM5OTAzMDcyMDc2MjQ4Njg1MTgyMjA4NTA0OTkyOTg5MTk0NzUwMDgyODU1MTc1NDE1OTIzMjU3MzA0MTQ0NjYxMDc5MDU2NzExMTg3NzMzMDE3NDQ1MTEyOTQyNDAyMTEzNDg0NjM5MTMxMDY2MDc3ODE2NzQzMzY3OTMzMDI3MjY3MTQ3MDIxMTkxODQ0NTQzMzI5NzUzMTA1NTA3MDk0Mzc5OTA5OTYzNjcxMTQ4NzM3Mjk3NDA2MzUxMzk0NTcwNTM3Nzk0NDg1Njc1ODc3MDU5OTI2NTc3MDU4MzY1NTA0MDQwNjAzNDIxMDI3NDYyOTY0OTExNTc5MDAyNjg2NDAzMjMyOTc3OTU0ODM1Nzc2NzQwMDI3NDIxNjI0MTUzNDQ4NzYyODAxODM3OTU3MTQ3NzM0MDkxNDk3NjMwMjA3MTY3MzUzMjAwMTM5ODE4MDg1NjgwMDIzMTc1MDEyNzM4Mjk1NzIwODU2OTMwNDYxMzIxMDQ4NTIxMjQ0ODQ5MjQ5Njc5MDMwMzI0NDcyNjYyOTQxNjc5NDU3OTk3NzQ4NiIsImUiOiI0NTE0MTczNzExODM2MzMzOTk0NjA3MTMwNjQ5MjA0NjEyNzU2Njk1MDI4ODA2NTY0NzI4MzE3OTExNzYxNDA0NTE5Nzk0NjA3NDk4Njg5NjgyOTYxODk3MDc4MDcwMzQ5Nzk0MzUzMDQ1MTY3MTUyMTk2OTU4NTU0NTI5MzgxNjY3MDE5MDA2OSIsInYiOiI1MzM4NDg2NDY2MjE4MTg2ODg3MTUwNzY4NTQ0OTQyMTEyMDcyOTQ1MzczMDQ1MDAzNTk0MTk0MzAxMDA5NDUzMzk5MjMxMDM5NjExMjU4NTE3MTgzODUyMDc4NjI0NjMyMDExNDE2MzI3Mjc1MTM3Nzc1MTAxNjgxODcwMzI4MjY3MTE4MjExNjEwNzAwNDc2MjA5NzMwMTIwODI2NzMyMTkwMDg0ODkyOTc2NTEwMTgxODE2MTkzMzM5MTk0MjE5MDIxOTQ1OTI1NTg4NjEzODEwMjE1Nzg1NDk1NDk0NjQ0NzIwMDM4MjMwMTg1MDUyMDAxMTMxNjE3MjQwMDIyNjQzOTYxNTkwOTU5ODE3ODMxMzg2Mzc5NDQ1MzI2Mzg4NzYzNjQ5MDYxODk4Nzk1ODcwMjE2NTkxMDI3NDkwMzAwMjA0OTc1NzM0NDgyNDM1ODE4MjgwMTQxNzA0MzA0MjMzNDE5NTMyNjc1Mzk3MDE3MTc1MTE3ODI5NDUzNjAxNDM2OTM2MDM3NDMyMzg4OTYyMjMwOTAyNTk1MjE3MTA3MzkxOTMwOTA3NDI4NDQyNDg4ODE2NjQ4NTI4OTkyNjUwMzY0NjIyNDA2MTA5MDUxOTczMjYyOTM3MzYyMTg5NDcwNTUyNDQ2MjAzNTMzNTQzNTY4NjY5MzAwODY0MzQyMzQwMDgwNjg5Mjg5MjI0OTg1MjU4MjU5MTk1Nzc5NTA3MzgwMDI1ODcwNDk0MDIwNDkyMTE2MDExOTA3NjI0NjI0ODg1ODk5NjMxOTk4ODMwNjg4NTY2OTQ5OTgyNjI5Mjg2Mzk2MjIzNDc2NDE1ODM2Nzg3MDA2Mzg5Nzc0MjYxMzk5NjUxMTY3NTYwNzcyMDc5NjkzMDA1NzMwOTQzNTIzNjAwNzM4Mjc4MzA4NDE2MjA5NzQzNzA1ODQ1MzUxNjQzMDUyMTY1MTcyNTg5NTMwMTU0MTU3NjE2OTYzMDg5NjM4ODg4NDc0NDg3MDA3NjY0NTA2ODk5NTE1OTE5MDAxNzIyMDEyNzczMzU3MDc4MjI4OTIzMDMzNTA1NDQ2MzAxOTQxNzA2OTc2NTY3Mzg5NDk3MzgxMDI2NjIyNDEzNTYyODc5MjM0MTM0NTI5Nzk4NzY2ODY0Nzk1OTQ3NzY1ODcwNDgwMTIyNDk0ODE0MzU0MDQ3MzE2ODY0ODczODMzNDgyNDU5NTc1NTQxNDI4NTE0MTciLCJtIjp7Im1hc3Rlcl9zZWNyZXQiOiIxNjE3NTE3NzgwNjcyMjkxNDYzNTc4ODc1NDk1NTkxODgyOTA3ODYzNTk0NzgyMzk4NjczMTIwMDg2OTEwMjA3NzczODk0ODYyNzQxOTIxMzk2OTE2MDUxNDk2NjYzMjIxNDA5MzA3NjA4NTczMDg1ODExMzAyNTYxNDcyMzgxMjY1NjE4MzQyNzc1NTY5MjQ4OTQ3NzY4Mjc3OTQzMzIxMjcyMTY1MjEyMDAxNDI0NDAwMyJ9LCJtMiI6IjE1MDQ5MTk3MTU3NDcyNjQ0MDMzMzE4OTAxNTc5MDYyNTk5NzA2NzU5MzcwMDk1MTk3NzI1NTE3MTM4OTAyMzcwNDUwMTQ5NDk2NjU0MTEzMzA5NTQ4MTc4MDM3NDU1NjY3Njk2NDA0MDY1ODI5MTUzNDYzNDczNzgzMTk5ODA3MjEzNjg5NDE3MTM2NDI4NDg5NzUwNjUzNTc5MjU0NDY0ODk0OTM4MDkyODY2NTUzNjU5In0sImdlX3Byb29mcyI6W119LCJub25fcmV2b2NfcHJvb2YiOm51bGx9XSwiYWdncmVnYXRlZF9wcm9vZiI6eyJjX2hhc2giOiIzNjk4Mjk3ODU5OTY5Nzg3MjI5MTA5NDY2OTIwMDA3ODEwNDA2ODQ3NTI2MDE2NjgxMTIwNDE4OTQ1NDk0NzcwODQyNjI3MjA2MjEyNCIsImNfbGlzdCI6W1syLDIwOCwzLDUzLDIyOSwxMzksMTAyLDUxLDI0MCwxOTUsMTM1LDExNSwxNzYsMTcyLDE4NCw5OSwxMDksMTU2LDgzLDUyLDIxNSwyMjMsODQsMjU1LDY2LDIyNiwyMjMsMTA1LDExMSwyMjEsMTgwLDk1LDEyMiwxMzMsMjIyLDI3LDM5LDk5LDcwLDEzLDM3LDI0LDI1NSwxMTQsMjM1LDEwOSwxODMsNTEsMjEzLDE5MCwyMjYsMTI2LDExOCwyLDIyMCw3OCw0OSw5LDI0MCw1NSwxNzksMTQ3LDUxLDIwMSwyMTMsMjEzLDEzMCw0LDE4MCwxMDMsMTk1LDgsMjYsMTE4LDE0LDEzMCwxOCwxMzMsMTg3LDYyLDMsOTcsMjEwLDEwMiwxMiwxNjIsNzksMTg0LDU1LDIzMiwyMTksMjIwLDE3NSwyNTUsMTY5LDE5NywxMjMsMTI3LDE2MCwyNSwxNTEsMTg3LDg3LDE5MSwxMDksMTk4LDQ0LDcxLDM4LDUwLDEwNCwyNiwyMTYsMTgwLDIxOCwxNDUsMTAsNzYsMTgwLDE1Nyw5OCwyMzQsNzcsMTY5LDE1MSw2OCwxNzAsMTg5LDE1LDIxNyw5OCwyMzUsMTI0LDE2NywyMzYsMjUzLDExMiwyNDQsMTg5LDk1LDE3OCw2MCw3MiwyMjgsMjIzLDcwLDI3LDYwLDIzNiwyMTIsOTcsMjA1LDIyLDI1MCwzNCwyNDYsMTIyLDM0LDgsMjU1LDIyLDEyNywxNTEsMjQyLDE4MCwxNzEsMTIxLDIyNywzMiwxMDMsNTEsMTcwLDIzNCwyMDYsMjAsMTAyLDIwNCwxMTYsMTk5LDAsMTE5LDExNSwxODAsMjA3LDE2LDQzLDU5LDI0MiwxNzksMTksMTk5LDQ4LDEyNyw5LDYzLDg4LDIxLDAsMjE1LDE3NCw0NywxNzcsMjMyLDE4MiwyNTMsMjQ5LDI0OCwxMTgsMTk2LDI1NCwxMzksMTIsMjksMSw0OCwxMDUsMzMsNCwyMDgsMTA2LDIzNSwyNDcsMjEwLDExMiwyMTAsMTA2LDE5OSwxOTgsNDcsOCwyMzYsNTIsOSw2NywxMjgsMjQwLDI1NCwyMzIsMjEwLDQsMjM5LDE4MywzOSwxOTMsMjQyLDMyLDEzMywxOTQsMTQ4LDk4LDExMSw3NywxNTUsMjA1LDE3OCwxOTcsMTRdXX19LCJyZXF1ZXN0ZWRfcHJvb2YiOnsicmV2ZWFsZWRfYXR0cnMiOnsibmFtZSI6eyJzdWJfcHJvb2ZfaW5kZXgiOjAsInJhdyI6IkFsaWNlIiwiZW5jb2RlZCI6IjI3MDM0NjQwMDI0MTE3MzMxMDMzMDYzMTI4MDQ0MDA0MzE4MjE4NDg2ODE2OTMxNTIwODg2NDA1NTM1NjU5OTM0NDE3NDM4NzgxNTA3In19LCJzZWxmX2F0dGVzdGVkX2F0dHJzIjp7fSwidW5yZXZlYWxlZF9hdHRycyI6e30sInByZWRpY2F0ZXMiOnt9fSwiaWRlbnRpZmllcnMiOlt7InNjaGVtYV9pZCI6Ijd5VzZTb1RqSE5oRDN6WWdtNFBiSzg6Mjp0ZXN0LXNjaGVtYS00ZTk0YzJlNC00ZjQ3LTRmZjMtYTg4OC02ZjY0ZGE2YTkyZGM6MS4wIiwiY3JlZF9kZWZfaWQiOiI3eVc2U29UakhOaEQzellnbTRQYks4OjM6Q0w6NDcyMzE5OlRBRyIsInJldl9yZWdfaWQiOm51bGwsInRpbWVzdGFtcCI6bnVsbH1dfQ==", }, "mime-type": "application/json", }, ], - "~thread": Object { + "~thread": { "thid": "7fcfc074-43ac-43cc-92b9-76afceeebe82", }, }, - "requestMessage": Object { + "requestMessage": { "@id": "7fcfc074-43ac-43cc-92b9-76afceeebe82", "@type": "https://didcomm.org/present-proof/1.0/request-presentation", - "request_presentations~attach": Array [ - Object { + "request_presentations~attach": [ + { "@id": "libindy-request-presentation-0", - "data": Object { + "data": { "base64": "eyJuYW1lIjoicHJvb2YtcmVxdWVzdCIsInZlcnNpb24iOiIxLjAiLCJub25jZSI6IjUyODExNDc1NTIxNzg3NzExMjI1Mzc0NSIsInJlcXVlc3RlZF9hdHRyaWJ1dGVzIjp7Im5hbWUiOnsibmFtZSI6Im5hbWUiLCJyZXN0cmljdGlvbnMiOlt7ImNyZWRfZGVmX2lkIjoiN3lXNlNvVGpITmhEM3pZZ200UGJLODozOkNMOjQ3MjMxOTpUQUcifV19fSwicmVxdWVzdGVkX3ByZWRpY2F0ZXMiOnt9fQ==", }, "mime-type": "application/json", @@ -802,64 +812,64 @@ Object { "threadId": "7fcfc074-43ac-43cc-92b9-76afceeebe82", }, }, - "72c96cd1-1f26-4bf3-8a00-5c00926859a8": Object { + "72c96cd1-1f26-4bf3-8a00-5c00926859a8": { "id": "72c96cd1-1f26-4bf3-8a00-5c00926859a8", - "tags": Object { + "tags": { "connectionId": "946f660f-bfa6-4a98-a801-ebde5da95e2c", "state": "done", "threadId": "00cdd404-82fc-431d-9c18-fb643636d2a5", }, "type": "ProofExchangeRecord", - "value": Object { + "value": { "connectionId": "946f660f-bfa6-4a98-a801-ebde5da95e2c", "createdAt": "2022-09-08T19:36:06.208Z", "id": "72c96cd1-1f26-4bf3-8a00-5c00926859a8", "isVerified": true, - "metadata": Object {}, - "presentationMessage": Object { + "metadata": {}, + "presentationMessage": { "@id": "4185f336-f307-4022-a27d-78d1271586f6", "@type": "https://didcomm.org/present-proof/1.0/presentation", - "presentations~attach": Array [ - Object { + "presentations~attach": [ + { "@id": "libindy-presentation-0", - "data": Object { + "data": { "base64": "eyJwcm9vZiI6eyJwcm9vZnMiOlt7InByaW1hcnlfcHJvb2YiOnsiZXFfcHJvb2YiOnsicmV2ZWFsZWRfYXR0cnMiOnsibmFtZSI6IjI3MDM0NjQwMDI0MTE3MzMxMDMzMDYzMTI4MDQ0MDA0MzE4MjE4NDg2ODE2OTMxNTIwODg2NDA1NTM1NjU5OTM0NDE3NDM4NzgxNTA3In0sImFfcHJpbWUiOiI1MjE1MTkyMzYwMDA2NTg5OTUyMjM1MTYwNjkxMzU1OTc4MjA2NDUzODA3NDUzNTk5OTE5OTkzNzM1NTczNzA1OTA2OTY5MTAwMjIzOTM4MjMyMzE5NDE1Njk1NTMzNjQ4MDMzNzc3NzI4NTE3NzI4Mjk4NDU1OTI0NjQ1NTU1NjQyNjE1NTQ3NTI0MDc0MTAyOTQyNjg4NzQwNDcwMjg4MDAyNTgwMjI4OTg4MDk4MDk0NjQxODc5NjkxNzQwMTY2OTc0MzA2OTczNjQ2MDA3ODg3NTU1NDU2NjM3NTUyODk2NjkxNDc3NDg2MDM1MTIxMjU0NDY0NzA2MDY0Mzg2NzA1MzU0MDk3MDcxNDc5OTkwMzIwNzIxMzQ4MjM1NTQ2NjI5OTcxNzAyNzgwNjUwNTgyMDQ3MzMxOTIwNjE5OTIzODg4NjU1NjI0NjYzMDE4NTAzMDUzOTQ4OTEyNzk4MDE1ODA2Mzk5MTAxNTg3MzAzNTgwNzkyMzQ3OTU2NjE1MDgzODczNDQ5MTM0MjEyOTE2MDY3MTExMjM4MjQzMjM1MTk5OTk1MTUzNjIyMTM1NjA4Nzg5OTc2MTk2NTkzMTczNTU4NTA0NDYxODA0MTk5MzY3MTkwODY2MDY5MTk1MDE5MjA4NjQyMzAwNzE5NjAzMDk5MzI1MjA0Njg4MzkwMjc2NTM5NjQxMDk0Mjc4NTY2NTUzOTU1Mzg4NDAwMDUyNzEyNzk0MjUwODgxODg3NDIzMDM5NTgyMjM5MjQ4NTk0NjYyMDYzMDA0ODI2MzE5NDQxNTUwNzEyNDA3MTQ4NDc4Njc1MCIsImUiOiIxNTk2OTcwODg4Njk3NTIwMjIzMzA3ODgyOTM5MzI5OTY0NTM3NjA4Mzc3NjQ0MTc2MjIyODczMjc2MTc0Mjg5ODkzNjgyNDAzODg0NzkzOTk1NDIwMjQ2MTEzNDk5NjMxNDAyMTQxMzQ0OTYxOTg3NDAyNjAxNDc4NTQ3NjEwNjI5MTgzNzc2NDQiLCJ2IjoiOTE0NzEwNTI1NDAwMDA2MDgzNTMwMTMzOTIxMjQyNTQwMzI5ODAzNjI4MjgyMDA0MDAzMTY1ODIyMjMyOTM0NDYxODAyMjQ4MDk4OTU5MzY1MTY2ODA3MjEwODgzNzIyNjIyMzA1ODQyNjM4NzM4NjIzNzM5NzYwNjEzODc5NDcwOTU5NDkyNjg1ODY5MTY4NTQyMDU1MjAyNzcxMDYxODI4MDEwMTAzNDY3Nzk2MDA5NDIzMTc4NzU2NDQ5OTk3NDQzODk5NDkyMDEyMTE0OTYyMDk5MDgwMDg1MzAxMjUyMDU1NDk0MDUzMTM4MzU4NDE0MjM0MDg5MzQyNDY2Nzg0MTA4ODkwMjA2NjY0NDI1NDE1ODYzNDA5NTI1MjI3NDY0NTg1OTU0MzQ3OTk2OTIwNTU0NTg5Nzc3OTA1MzMzMDMxMzAzMzEzNTI1OTY3NDg3NzAyMDQxODEwNzA3ODQ1OTAyMTAyODAxMTM0NDk3ODAxNjQyNTgwNzQyNjg0Njk5OTk3ODM2NTY0NzM2NDE5NDc0NzM1Mzg2ODkzMTQwMDA4ODIzNjIzMjA1MDA5MzE3NjgzNTIyNjI5NzkwMDY1NDExNDE5MzY3MTc3MTgwMTQ3MDk3ODkwNTkxNDM4NTkyMjQyNzA1MTg5NDM4NDE2MjA4MTA0MjczNjI3MjUyNzc2NzY1NjI0NjExMjk3NjQzODA5NzIwMjcxMDUzOTY1MzM4MTA5Njk1MTM4NjA3NTA0NzExMDc0NDY0ODU5Mzc5MzUxNTI1ODY4NzgyMjk2MzIzNDIwMzIwMTAwMzQ4MDA5MDMyMzIxOTM2ODMzNDk0Nzc2NDUxMjQ0NzMzNTQxMzI3Mzc4MTc3OTYwMTMzOTg2NzE4NTA2NTk0MzE2MjMyMzEwMTk5NjE2NjQ4NzI3MjU0MTUwNjg2MjA4OTQ1MTM3Mzg5MTI5Mjg4NzUyNzE3MjA5NjI3ODcyNjE3NTM3MjEwMDk2NTU2MTkzMzgxMzUwNTgzMDM1NjI5OTg3MjE5MTk3OTQ4OTEyNDI3NzI0MTA1ODE2NzA0NDkyOTMzNzQyMjcxOTQ5NTk1ODAwOTUyMDA5NDczNzQ2OTM5ODU0Mzg0NDI4MzY0MjE0NjUyNjI4MDgyOTQwOTMzODcwNTE1MjE4NTc1MzY4NDgwMjM4OTM2MTc0NDM5NzExNTUzNDI3NDEwNzA2NTgxMTkzMzg2NDM2ODEyIiwibSI6eyJtYXN0ZXJfc2VjcmV0IjoiNDU1NjIzMDY3NjQ4NDQ0NTEzODAyNzM4ODkxODAyMTkxNDg1MzY0NzMzMDkxMTIwMjM5MDg1ODY0NTQxMzU5Njg0MDgyMDczNTgzNDMzMjM4MzU2Njg2MTgwNzI1MzIyMjU2MzgwMjYxNzYyMTc5MTg3MDU3Mjk4Nzg3NzEzNDcyODY5MjE5NDI1ODgyNTEyNzI0ODk4NjkzMDk3OTkwODI4MDIzMjUwNTkyMTAwNDI5OSJ9LCJtMiI6IjEyMjUwNjAyNjEwOTE0Njk5NDEzNDk3MjEyOTYwNTA3OTQ1MDkwOTk1OTMwNjYyMDMyOTA2NTIxNDUxNjI0MDUxMTk2MTg3MjIxNjgxNzEzMDc1OTg0MDY5MjY5MTY3MDQ5ODExMDc3NDk1NTM2MDg4OTE0NTc3MDk2NTgwNjMwODIzNDQyMTk4Nzk5Nzg4NzIyNDIzMzg4MDIyNjE5MzM4MzA2MjM3NTQ2MzI5OTI4MDEifSwiZ2VfcHJvb2ZzIjpbXX0sIm5vbl9yZXZvY19wcm9vZiI6bnVsbH1dLCJhZ2dyZWdhdGVkX3Byb29mIjp7ImNfaGFzaCI6IjU4Njg3MDc5MDA2NTc0OTY1OTA5ODk2MDc5OTU0NTA2NDM2ODg0NzMxOTIxMjk0NDA5NDYxNzI5NjkzMTI4MDM1OTcxMjAwMjY5NzgzIiwiY19saXN0IjpbWzEsMTU3LDMxLDExMiwxNjQsMjA1LDIyNCw3NiwzMiwyMTIsMTczLDc3LDIxNyw2MiwxNzMsMTc3LDQsMTU5LDE0OSwyMzMsMTE3LDEwNCw5MiwxMzgsMzUsMjYsNDgsODUsMjQwLDIzNCw0MywxOTYsNTksMTE0LDI0MiwxNTMsNDAsMTg3LDIzOSwxMDAsMTcwLDIsMzYsMTA3LDIyOSwzNywxMTEsNjAsOTIsOSwxMTcsMjM4LDE0MCwyNDMsMywyNDIsMjM0LDE1MiwxNjYsNiwxNjEsMTM0LDIyMiwxNjMsNDMsNjIsMTIsODEsMjUzLDE3NiwxMjUsMjQsOCw5MiwxMywyMjIsMTcxLDIzMywxODIsMjE1LDIyLDIzNCw5NSwxMTIsMTIyLDEwMiwxNTIsMTA2LDE4Miw0LDIxOSw1NiwxMDEsMTQ3LDIyNywyNDYsMTE1LDY0LDE4Myw0LDY3LDEyNCwxNDAsMTY2LDEzOCwxNCwyNCw1MSwxODUsMTk5LDEwOSwyLDUxLDE5MCwxODEsMjQ3LDYxLDE5MCw4NSwyNDcsMjE3LDIyLDE2NCwxOTIsMjM1LDE4NywxNDgsMjYsMTA5LDI1MywxMTYsMjQxLDY1LDEzNiw0MSwyMjUsMjI5LDE3OCwxMjUsNzksMjQ2LDUzLDQ0LDE5MiwxNDEsMjQzLDE4NCwxNTEsMTIsOSwxMDQsMjE5LDE2Miw3MSw3Miw3LDg5LDgzLDgsMjI1LDEzLDQ4LDEzMywyNDcsMTg3LDExNCw4NSw3Nyw1OSw5OSw3MCw2OSw3MSwxMzMsMTY5LDExMSwxMTAsNzksNDQsMTc0LDIzLDEwMywxNjYsNjksNSwxNDcsNjYsNzcsMTYzLDExMiw0MiwxOTksMTQyLDE0LDIyLDE1MSwyNDgsMzMsMzQsMTk4LDE1NiwyMCwyMzEsMjA0LDcwLDgyLDI1MywxMzgsMTMxLDQyLDIwNSwzMywxMDksMjMyLDE3LDEwNiw2OSw3Miw2MCwyMTAsMjMyLDExMywxMjQsNzYsMTgxLDIwOCwwLDE4NiwyNTEsMTQzLDExMyw0NSwxNzEsMTAsNDMsMTUsMzAsMjA4LDkzLDYsMTY4LDEwNiwyMDMsNDgsMjUwLDIxOSw5LDE3LDEwNCwzNSw1OCwxNTksMTgwLDExMyw2NiwxMzYsNjJdXX19LCJyZXF1ZXN0ZWRfcHJvb2YiOnsicmV2ZWFsZWRfYXR0cnMiOnsiMGZlYTE4OTctNDMxMS00OGYyLWEyYzItMzg3NGU5OTNhZWYwIjp7InN1Yl9wcm9vZl9pbmRleCI6MCwicmF3IjoiQWxpY2UiLCJlbmNvZGVkIjoiMjcwMzQ2NDAwMjQxMTczMzEwMzMwNjMxMjgwNDQwMDQzMTgyMTg0ODY4MTY5MzE1MjA4ODY0MDU1MzU2NTk5MzQ0MTc0Mzg3ODE1MDcifX0sInNlbGZfYXR0ZXN0ZWRfYXR0cnMiOnt9LCJ1bnJldmVhbGVkX2F0dHJzIjp7fSwicHJlZGljYXRlcyI6e319LCJpZGVudGlmaWVycyI6W3sic2NoZW1hX2lkIjoiN3lXNlNvVGpITmhEM3pZZ200UGJLODoyOnRlc3Qtc2NoZW1hLTRlOTRjMmU0LTRmNDctNGZmMy1hODg4LTZmNjRkYTZhOTJkYzoxLjAiLCJjcmVkX2RlZl9pZCI6Ijd5VzZTb1RqSE5oRDN6WWdtNFBiSzg6MzpDTDo0NzIzMTk6VEFHIiwicmV2X3JlZ19pZCI6bnVsbCwidGltZXN0YW1wIjpudWxsfV19", }, "mime-type": "application/json", }, ], - "~thread": Object { + "~thread": { "thid": "00cdd404-82fc-431d-9c18-fb643636d2a5", }, }, - "proposalMessage": Object { + "proposalMessage": { "@id": "00cdd404-82fc-431d-9c18-fb643636d2a5", "@type": "https://didcomm.org/present-proof/1.0/propose-presentation", - "presentation_proposal": Object { + "presentation_proposal": { "@type": "https://didcomm.org/present-proof/1.0/presentation-preview", - "attributes": Array [ - Object { + "attributes": [ + { "cred_def_id": "7yW6SoTjHNhD3zYgm4PbK8:3:CL:472319:TAG", "name": "name", "value": "Alice", }, ], - "predicates": Array [], + "predicates": [], }, }, - "requestMessage": Object { + "requestMessage": { "@id": "2862b25d-396c-46d7-ad09-f48ff4ea6db6", "@type": "https://didcomm.org/present-proof/1.0/request-presentation", - "request_presentations~attach": Array [ - Object { + "request_presentations~attach": [ + { "@id": "libindy-request-presentation-0", - "data": Object { + "data": { "base64": "eyJuYW1lIjoicHJvb2YtcmVxdWVzdCIsInZlcnNpb24iOiIxLjAiLCJub25jZSI6IjQwMzU1MDc0MDYxMTU0MzEwMzA5NzMyMiIsInJlcXVlc3RlZF9hdHRyaWJ1dGVzIjp7IjBmZWExODk3LTQzMTEtNDhmMi1hMmMyLTM4NzRlOTkzYWVmMCI6eyJuYW1lIjoibmFtZSIsInJlc3RyaWN0aW9ucyI6W3siY3JlZF9kZWZfaWQiOiI3eVc2U29UakhOaEQzellnbTRQYks4OjM6Q0w6NDcyMzE5OlRBRyJ9XX19LCJyZXF1ZXN0ZWRfcHJlZGljYXRlcyI6e319", }, "mime-type": "application/json", }, ], - "~thread": Object { + "~thread": { "thid": "00cdd404-82fc-431d-9c18-fb643636d2a5", }, }, @@ -867,54 +877,55 @@ Object { "threadId": "00cdd404-82fc-431d-9c18-fb643636d2a5", }, }, - "STORAGE_VERSION_RECORD_ID": Object { + "STORAGE_VERSION_RECORD_ID": { "id": "STORAGE_VERSION_RECORD_ID", - "tags": Object {}, + "tags": {}, "type": "StorageVersionRecord", - "value": Object { + "value": { "createdAt": "2022-09-08T19:35:53.872Z", "id": "STORAGE_VERSION_RECORD_ID", - "metadata": Object {}, - "storageVersion": "0.3.1", + "metadata": {}, + "storageVersion": "0.4", + "updatedAt": "2022-01-21T22:50:20.522Z", }, }, - "ea840186-3c77-45f4-a2e6-349811ad8994": Object { + "ea840186-3c77-45f4-a2e6-349811ad8994": { "id": "ea840186-3c77-45f4-a2e6-349811ad8994", - "tags": Object { + "tags": { "connectionId": "946f660f-bfa6-4a98-a801-ebde5da95e2c", "state": "done", "threadId": "7fcfc074-43ac-43cc-92b9-76afceeebe82", }, "type": "ProofExchangeRecord", - "value": Object { + "value": { "connectionId": "946f660f-bfa6-4a98-a801-ebde5da95e2c", "createdAt": "2022-09-08T19:36:06.261Z", "id": "ea840186-3c77-45f4-a2e6-349811ad8994", "isVerified": true, - "metadata": Object {}, - "presentationMessage": Object { + "metadata": {}, + "presentationMessage": { "@id": "2481ce81-560b-4ce6-a22b-ee4b6ed369e8", "@type": "https://didcomm.org/present-proof/1.0/presentation", - "presentations~attach": Array [ - Object { + "presentations~attach": [ + { "@id": "libindy-presentation-0", - "data": Object { + "data": { "base64": "eyJwcm9vZiI6eyJwcm9vZnMiOlt7InByaW1hcnlfcHJvb2YiOnsiZXFfcHJvb2YiOnsicmV2ZWFsZWRfYXR0cnMiOnsibmFtZSI6IjI3MDM0NjQwMDI0MTE3MzMxMDMzMDYzMTI4MDQ0MDA0MzE4MjE4NDg2ODE2OTMxNTIwODg2NDA1NTM1NjU5OTM0NDE3NDM4NzgxNTA3In0sImFfcHJpbWUiOiI5MDg5MzE2Mjc0ODc5NTI4MjMzNDA1MTY0NTgwOTM1OTIxMjIwMjMyMzg5ODE0NjE4OTc3ODA3MjA0MDg4OTQ0ODkzNDc1OTE5NTE4Mjc0NDEwNTU3OTU3NjEwNjYzOTAxMTcwODM1NDM2Nzk1NDU4NDU1Mjg3NzEwOTk3NTk3OTA1OTM3NTYyODIyNjg5NTE3MjAyNzQ4NTUxODgzODQ5NjY3MjYwNTA3NjU0NDM5OTk0MjczNDQ0MTU5NTQyMzg3MzI0OTM5OTAzMDcyMDc2MjQ4Njg1MTgyMjA4NTA0OTkyOTg5MTk0NzUwMDgyODU1MTc1NDE1OTIzMjU3MzA0MTQ0NjYxMDc5MDU2NzExMTg3NzMzMDE3NDQ1MTEyOTQyNDAyMTEzNDg0NjM5MTMxMDY2MDc3ODE2NzQzMzY3OTMzMDI3MjY3MTQ3MDIxMTkxODQ0NTQzMzI5NzUzMTA1NTA3MDk0Mzc5OTA5OTYzNjcxMTQ4NzM3Mjk3NDA2MzUxMzk0NTcwNTM3Nzk0NDg1Njc1ODc3MDU5OTI2NTc3MDU4MzY1NTA0MDQwNjAzNDIxMDI3NDYyOTY0OTExNTc5MDAyNjg2NDAzMjMyOTc3OTU0ODM1Nzc2NzQwMDI3NDIxNjI0MTUzNDQ4NzYyODAxODM3OTU3MTQ3NzM0MDkxNDk3NjMwMjA3MTY3MzUzMjAwMTM5ODE4MDg1NjgwMDIzMTc1MDEyNzM4Mjk1NzIwODU2OTMwNDYxMzIxMDQ4NTIxMjQ0ODQ5MjQ5Njc5MDMwMzI0NDcyNjYyOTQxNjc5NDU3OTk3NzQ4NiIsImUiOiI0NTE0MTczNzExODM2MzMzOTk0NjA3MTMwNjQ5MjA0NjEyNzU2Njk1MDI4ODA2NTY0NzI4MzE3OTExNzYxNDA0NTE5Nzk0NjA3NDk4Njg5NjgyOTYxODk3MDc4MDcwMzQ5Nzk0MzUzMDQ1MTY3MTUyMTk2OTU4NTU0NTI5MzgxNjY3MDE5MDA2OSIsInYiOiI1MzM4NDg2NDY2MjE4MTg2ODg3MTUwNzY4NTQ0OTQyMTEyMDcyOTQ1MzczMDQ1MDAzNTk0MTk0MzAxMDA5NDUzMzk5MjMxMDM5NjExMjU4NTE3MTgzODUyMDc4NjI0NjMyMDExNDE2MzI3Mjc1MTM3Nzc1MTAxNjgxODcwMzI4MjY3MTE4MjExNjEwNzAwNDc2MjA5NzMwMTIwODI2NzMyMTkwMDg0ODkyOTc2NTEwMTgxODE2MTkzMzM5MTk0MjE5MDIxOTQ1OTI1NTg4NjEzODEwMjE1Nzg1NDk1NDk0NjQ0NzIwMDM4MjMwMTg1MDUyMDAxMTMxNjE3MjQwMDIyNjQzOTYxNTkwOTU5ODE3ODMxMzg2Mzc5NDQ1MzI2Mzg4NzYzNjQ5MDYxODk4Nzk1ODcwMjE2NTkxMDI3NDkwMzAwMjA0OTc1NzM0NDgyNDM1ODE4MjgwMTQxNzA0MzA0MjMzNDE5NTMyNjc1Mzk3MDE3MTc1MTE3ODI5NDUzNjAxNDM2OTM2MDM3NDMyMzg4OTYyMjMwOTAyNTk1MjE3MTA3MzkxOTMwOTA3NDI4NDQyNDg4ODE2NjQ4NTI4OTkyNjUwMzY0NjIyNDA2MTA5MDUxOTczMjYyOTM3MzYyMTg5NDcwNTUyNDQ2MjAzNTMzNTQzNTY4NjY5MzAwODY0MzQyMzQwMDgwNjg5Mjg5MjI0OTg1MjU4MjU5MTk1Nzc5NTA3MzgwMDI1ODcwNDk0MDIwNDkyMTE2MDExOTA3NjI0NjI0ODg1ODk5NjMxOTk4ODMwNjg4NTY2OTQ5OTgyNjI5Mjg2Mzk2MjIzNDc2NDE1ODM2Nzg3MDA2Mzg5Nzc0MjYxMzk5NjUxMTY3NTYwNzcyMDc5NjkzMDA1NzMwOTQzNTIzNjAwNzM4Mjc4MzA4NDE2MjA5NzQzNzA1ODQ1MzUxNjQzMDUyMTY1MTcyNTg5NTMwMTU0MTU3NjE2OTYzMDg5NjM4ODg4NDc0NDg3MDA3NjY0NTA2ODk5NTE1OTE5MDAxNzIyMDEyNzczMzU3MDc4MjI4OTIzMDMzNTA1NDQ2MzAxOTQxNzA2OTc2NTY3Mzg5NDk3MzgxMDI2NjIyNDEzNTYyODc5MjM0MTM0NTI5Nzk4NzY2ODY0Nzk1OTQ3NzY1ODcwNDgwMTIyNDk0ODE0MzU0MDQ3MzE2ODY0ODczODMzNDgyNDU5NTc1NTQxNDI4NTE0MTciLCJtIjp7Im1hc3Rlcl9zZWNyZXQiOiIxNjE3NTE3NzgwNjcyMjkxNDYzNTc4ODc1NDk1NTkxODgyOTA3ODYzNTk0NzgyMzk4NjczMTIwMDg2OTEwMjA3NzczODk0ODYyNzQxOTIxMzk2OTE2MDUxNDk2NjYzMjIxNDA5MzA3NjA4NTczMDg1ODExMzAyNTYxNDcyMzgxMjY1NjE4MzQyNzc1NTY5MjQ4OTQ3NzY4Mjc3OTQzMzIxMjcyMTY1MjEyMDAxNDI0NDAwMyJ9LCJtMiI6IjE1MDQ5MTk3MTU3NDcyNjQ0MDMzMzE4OTAxNTc5MDYyNTk5NzA2NzU5MzcwMDk1MTk3NzI1NTE3MTM4OTAyMzcwNDUwMTQ5NDk2NjU0MTEzMzA5NTQ4MTc4MDM3NDU1NjY3Njk2NDA0MDY1ODI5MTUzNDYzNDczNzgzMTk5ODA3MjEzNjg5NDE3MTM2NDI4NDg5NzUwNjUzNTc5MjU0NDY0ODk0OTM4MDkyODY2NTUzNjU5In0sImdlX3Byb29mcyI6W119LCJub25fcmV2b2NfcHJvb2YiOm51bGx9XSwiYWdncmVnYXRlZF9wcm9vZiI6eyJjX2hhc2giOiIzNjk4Mjk3ODU5OTY5Nzg3MjI5MTA5NDY2OTIwMDA3ODEwNDA2ODQ3NTI2MDE2NjgxMTIwNDE4OTQ1NDk0NzcwODQyNjI3MjA2MjEyNCIsImNfbGlzdCI6W1syLDIwOCwzLDUzLDIyOSwxMzksMTAyLDUxLDI0MCwxOTUsMTM1LDExNSwxNzYsMTcyLDE4NCw5OSwxMDksMTU2LDgzLDUyLDIxNSwyMjMsODQsMjU1LDY2LDIyNiwyMjMsMTA1LDExMSwyMjEsMTgwLDk1LDEyMiwxMzMsMjIyLDI3LDM5LDk5LDcwLDEzLDM3LDI0LDI1NSwxMTQsMjM1LDEwOSwxODMsNTEsMjEzLDE5MCwyMjYsMTI2LDExOCwyLDIyMCw3OCw0OSw5LDI0MCw1NSwxNzksMTQ3LDUxLDIwMSwyMTMsMjEzLDEzMCw0LDE4MCwxMDMsMTk1LDgsMjYsMTE4LDE0LDEzMCwxOCwxMzMsMTg3LDYyLDMsOTcsMjEwLDEwMiwxMiwxNjIsNzksMTg0LDU1LDIzMiwyMTksMjIwLDE3NSwyNTUsMTY5LDE5NywxMjMsMTI3LDE2MCwyNSwxNTEsMTg3LDg3LDE5MSwxMDksMTk4LDQ0LDcxLDM4LDUwLDEwNCwyNiwyMTYsMTgwLDIxOCwxNDUsMTAsNzYsMTgwLDE1Nyw5OCwyMzQsNzcsMTY5LDE1MSw2OCwxNzAsMTg5LDE1LDIxNyw5OCwyMzUsMTI0LDE2NywyMzYsMjUzLDExMiwyNDQsMTg5LDk1LDE3OCw2MCw3MiwyMjgsMjIzLDcwLDI3LDYwLDIzNiwyMTIsOTcsMjA1LDIyLDI1MCwzNCwyNDYsMTIyLDM0LDgsMjU1LDIyLDEyNywxNTEsMjQyLDE4MCwxNzEsMTIxLDIyNywzMiwxMDMsNTEsMTcwLDIzNCwyMDYsMjAsMTAyLDIwNCwxMTYsMTk5LDAsMTE5LDExNSwxODAsMjA3LDE2LDQzLDU5LDI0MiwxNzksMTksMTk5LDQ4LDEyNyw5LDYzLDg4LDIxLDAsMjE1LDE3NCw0NywxNzcsMjMyLDE4MiwyNTMsMjQ5LDI0OCwxMTgsMTk2LDI1NCwxMzksMTIsMjksMSw0OCwxMDUsMzMsNCwyMDgsMTA2LDIzNSwyNDcsMjEwLDExMiwyMTAsMTA2LDE5OSwxOTgsNDcsOCwyMzYsNTIsOSw2NywxMjgsMjQwLDI1NCwyMzIsMjEwLDQsMjM5LDE4MywzOSwxOTMsMjQyLDMyLDEzMywxOTQsMTQ4LDk4LDExMSw3NywxNTUsMjA1LDE3OCwxOTcsMTRdXX19LCJyZXF1ZXN0ZWRfcHJvb2YiOnsicmV2ZWFsZWRfYXR0cnMiOnsibmFtZSI6eyJzdWJfcHJvb2ZfaW5kZXgiOjAsInJhdyI6IkFsaWNlIiwiZW5jb2RlZCI6IjI3MDM0NjQwMDI0MTE3MzMxMDMzMDYzMTI4MDQ0MDA0MzE4MjE4NDg2ODE2OTMxNTIwODg2NDA1NTM1NjU5OTM0NDE3NDM4NzgxNTA3In19LCJzZWxmX2F0dGVzdGVkX2F0dHJzIjp7fSwidW5yZXZlYWxlZF9hdHRycyI6e30sInByZWRpY2F0ZXMiOnt9fSwiaWRlbnRpZmllcnMiOlt7InNjaGVtYV9pZCI6Ijd5VzZTb1RqSE5oRDN6WWdtNFBiSzg6Mjp0ZXN0LXNjaGVtYS00ZTk0YzJlNC00ZjQ3LTRmZjMtYTg4OC02ZjY0ZGE2YTkyZGM6MS4wIiwiY3JlZF9kZWZfaWQiOiI3eVc2U29UakhOaEQzellnbTRQYks4OjM6Q0w6NDcyMzE5OlRBRyIsInJldl9yZWdfaWQiOm51bGwsInRpbWVzdGFtcCI6bnVsbH1dfQ==", }, "mime-type": "application/json", }, ], - "~thread": Object { + "~thread": { "thid": "7fcfc074-43ac-43cc-92b9-76afceeebe82", }, }, - "requestMessage": Object { + "requestMessage": { "@id": "7fcfc074-43ac-43cc-92b9-76afceeebe82", "@type": "https://didcomm.org/present-proof/1.0/request-presentation", - "request_presentations~attach": Array [ - Object { + "request_presentations~attach": [ + { "@id": "libindy-request-presentation-0", - "data": Object { + "data": { "base64": "eyJuYW1lIjoicHJvb2YtcmVxdWVzdCIsInZlcnNpb24iOiIxLjAiLCJub25jZSI6IjUyODExNDc1NTIxNzg3NzExMjI1Mzc0NSIsInJlcXVlc3RlZF9hdHRyaWJ1dGVzIjp7Im5hbWUiOnsibmFtZSI6Im5hbWUiLCJyZXN0cmljdGlvbnMiOlt7ImNyZWRfZGVmX2lkIjoiN3lXNlNvVGpITmhEM3pZZ200UGJLODozOkNMOjQ3MjMxOTpUQUcifV19fSwicmVxdWVzdGVkX3ByZWRpY2F0ZXMiOnt9fQ==", }, "mime-type": "application/json", @@ -925,63 +936,63 @@ Object { "threadId": "7fcfc074-43ac-43cc-92b9-76afceeebe82", }, }, - "ec02ba64-63e3-46bc-b2a4-9d549d642d30": Object { + "ec02ba64-63e3-46bc-b2a4-9d549d642d30": { "id": "ec02ba64-63e3-46bc-b2a4-9d549d642d30", - "tags": Object { + "tags": { "connectionId": "946f660f-bfa6-4a98-a801-ebde5da95e2c", "state": "done", "threadId": "00cdd404-82fc-431d-9c18-fb643636d2a5", }, "type": "ProofExchangeRecord", - "value": Object { + "value": { "connectionId": "946f660f-bfa6-4a98-a801-ebde5da95e2c", "createdAt": "2022-09-08T19:36:06.208Z", "id": "ec02ba64-63e3-46bc-b2a4-9d549d642d30", - "metadata": Object {}, - "presentationMessage": Object { + "metadata": {}, + "presentationMessage": { "@id": "4185f336-f307-4022-a27d-78d1271586f6", "@type": "https://didcomm.org/present-proof/1.0/presentation", - "presentations~attach": Array [ - Object { + "presentations~attach": [ + { "@id": "libindy-presentation-0", - "data": Object { + "data": { "base64": "eyJwcm9vZiI6eyJwcm9vZnMiOlt7InByaW1hcnlfcHJvb2YiOnsiZXFfcHJvb2YiOnsicmV2ZWFsZWRfYXR0cnMiOnsibmFtZSI6IjI3MDM0NjQwMDI0MTE3MzMxMDMzMDYzMTI4MDQ0MDA0MzE4MjE4NDg2ODE2OTMxNTIwODg2NDA1NTM1NjU5OTM0NDE3NDM4NzgxNTA3In0sImFfcHJpbWUiOiI1MjE1MTkyMzYwMDA2NTg5OTUyMjM1MTYwNjkxMzU1OTc4MjA2NDUzODA3NDUzNTk5OTE5OTkzNzM1NTczNzA1OTA2OTY5MTAwMjIzOTM4MjMyMzE5NDE1Njk1NTMzNjQ4MDMzNzc3NzI4NTE3NzI4Mjk4NDU1OTI0NjQ1NTU1NjQyNjE1NTQ3NTI0MDc0MTAyOTQyNjg4NzQwNDcwMjg4MDAyNTgwMjI4OTg4MDk4MDk0NjQxODc5NjkxNzQwMTY2OTc0MzA2OTczNjQ2MDA3ODg3NTU1NDU2NjM3NTUyODk2NjkxNDc3NDg2MDM1MTIxMjU0NDY0NzA2MDY0Mzg2NzA1MzU0MDk3MDcxNDc5OTkwMzIwNzIxMzQ4MjM1NTQ2NjI5OTcxNzAyNzgwNjUwNTgyMDQ3MzMxOTIwNjE5OTIzODg4NjU1NjI0NjYzMDE4NTAzMDUzOTQ4OTEyNzk4MDE1ODA2Mzk5MTAxNTg3MzAzNTgwNzkyMzQ3OTU2NjE1MDgzODczNDQ5MTM0MjEyOTE2MDY3MTExMjM4MjQzMjM1MTk5OTk1MTUzNjIyMTM1NjA4Nzg5OTc2MTk2NTkzMTczNTU4NTA0NDYxODA0MTk5MzY3MTkwODY2MDY5MTk1MDE5MjA4NjQyMzAwNzE5NjAzMDk5MzI1MjA0Njg4MzkwMjc2NTM5NjQxMDk0Mjc4NTY2NTUzOTU1Mzg4NDAwMDUyNzEyNzk0MjUwODgxODg3NDIzMDM5NTgyMjM5MjQ4NTk0NjYyMDYzMDA0ODI2MzE5NDQxNTUwNzEyNDA3MTQ4NDc4Njc1MCIsImUiOiIxNTk2OTcwODg4Njk3NTIwMjIzMzA3ODgyOTM5MzI5OTY0NTM3NjA4Mzc3NjQ0MTc2MjIyODczMjc2MTc0Mjg5ODkzNjgyNDAzODg0NzkzOTk1NDIwMjQ2MTEzNDk5NjMxNDAyMTQxMzQ0OTYxOTg3NDAyNjAxNDc4NTQ3NjEwNjI5MTgzNzc2NDQiLCJ2IjoiOTE0NzEwNTI1NDAwMDA2MDgzNTMwMTMzOTIxMjQyNTQwMzI5ODAzNjI4MjgyMDA0MDAzMTY1ODIyMjMyOTM0NDYxODAyMjQ4MDk4OTU5MzY1MTY2ODA3MjEwODgzNzIyNjIyMzA1ODQyNjM4NzM4NjIzNzM5NzYwNjEzODc5NDcwOTU5NDkyNjg1ODY5MTY4NTQyMDU1MjAyNzcxMDYxODI4MDEwMTAzNDY3Nzk2MDA5NDIzMTc4NzU2NDQ5OTk3NDQzODk5NDkyMDEyMTE0OTYyMDk5MDgwMDg1MzAxMjUyMDU1NDk0MDUzMTM4MzU4NDE0MjM0MDg5MzQyNDY2Nzg0MTA4ODkwMjA2NjY0NDI1NDE1ODYzNDA5NTI1MjI3NDY0NTg1OTU0MzQ3OTk2OTIwNTU0NTg5Nzc3OTA1MzMzMDMxMzAzMzEzNTI1OTY3NDg3NzAyMDQxODEwNzA3ODQ1OTAyMTAyODAxMTM0NDk3ODAxNjQyNTgwNzQyNjg0Njk5OTk3ODM2NTY0NzM2NDE5NDc0NzM1Mzg2ODkzMTQwMDA4ODIzNjIzMjA1MDA5MzE3NjgzNTIyNjI5NzkwMDY1NDExNDE5MzY3MTc3MTgwMTQ3MDk3ODkwNTkxNDM4NTkyMjQyNzA1MTg5NDM4NDE2MjA4MTA0MjczNjI3MjUyNzc2NzY1NjI0NjExMjk3NjQzODA5NzIwMjcxMDUzOTY1MzM4MTA5Njk1MTM4NjA3NTA0NzExMDc0NDY0ODU5Mzc5MzUxNTI1ODY4NzgyMjk2MzIzNDIwMzIwMTAwMzQ4MDA5MDMyMzIxOTM2ODMzNDk0Nzc2NDUxMjQ0NzMzNTQxMzI3Mzc4MTc3OTYwMTMzOTg2NzE4NTA2NTk0MzE2MjMyMzEwMTk5NjE2NjQ4NzI3MjU0MTUwNjg2MjA4OTQ1MTM3Mzg5MTI5Mjg4NzUyNzE3MjA5NjI3ODcyNjE3NTM3MjEwMDk2NTU2MTkzMzgxMzUwNTgzMDM1NjI5OTg3MjE5MTk3OTQ4OTEyNDI3NzI0MTA1ODE2NzA0NDkyOTMzNzQyMjcxOTQ5NTk1ODAwOTUyMDA5NDczNzQ2OTM5ODU0Mzg0NDI4MzY0MjE0NjUyNjI4MDgyOTQwOTMzODcwNTE1MjE4NTc1MzY4NDgwMjM4OTM2MTc0NDM5NzExNTUzNDI3NDEwNzA2NTgxMTkzMzg2NDM2ODEyIiwibSI6eyJtYXN0ZXJfc2VjcmV0IjoiNDU1NjIzMDY3NjQ4NDQ0NTEzODAyNzM4ODkxODAyMTkxNDg1MzY0NzMzMDkxMTIwMjM5MDg1ODY0NTQxMzU5Njg0MDgyMDczNTgzNDMzMjM4MzU2Njg2MTgwNzI1MzIyMjU2MzgwMjYxNzYyMTc5MTg3MDU3Mjk4Nzg3NzEzNDcyODY5MjE5NDI1ODgyNTEyNzI0ODk4NjkzMDk3OTkwODI4MDIzMjUwNTkyMTAwNDI5OSJ9LCJtMiI6IjEyMjUwNjAyNjEwOTE0Njk5NDEzNDk3MjEyOTYwNTA3OTQ1MDkwOTk1OTMwNjYyMDMyOTA2NTIxNDUxNjI0MDUxMTk2MTg3MjIxNjgxNzEzMDc1OTg0MDY5MjY5MTY3MDQ5ODExMDc3NDk1NTM2MDg4OTE0NTc3MDk2NTgwNjMwODIzNDQyMTk4Nzk5Nzg4NzIyNDIzMzg4MDIyNjE5MzM4MzA2MjM3NTQ2MzI5OTI4MDEifSwiZ2VfcHJvb2ZzIjpbXX0sIm5vbl9yZXZvY19wcm9vZiI6bnVsbH1dLCJhZ2dyZWdhdGVkX3Byb29mIjp7ImNfaGFzaCI6IjU4Njg3MDc5MDA2NTc0OTY1OTA5ODk2MDc5OTU0NTA2NDM2ODg0NzMxOTIxMjk0NDA5NDYxNzI5NjkzMTI4MDM1OTcxMjAwMjY5NzgzIiwiY19saXN0IjpbWzEsMTU3LDMxLDExMiwxNjQsMjA1LDIyNCw3NiwzMiwyMTIsMTczLDc3LDIxNyw2MiwxNzMsMTc3LDQsMTU5LDE0OSwyMzMsMTE3LDEwNCw5MiwxMzgsMzUsMjYsNDgsODUsMjQwLDIzNCw0MywxOTYsNTksMTE0LDI0MiwxNTMsNDAsMTg3LDIzOSwxMDAsMTcwLDIsMzYsMTA3LDIyOSwzNywxMTEsNjAsOTIsOSwxMTcsMjM4LDE0MCwyNDMsMywyNDIsMjM0LDE1MiwxNjYsNiwxNjEsMTM0LDIyMiwxNjMsNDMsNjIsMTIsODEsMjUzLDE3NiwxMjUsMjQsOCw5MiwxMywyMjIsMTcxLDIzMywxODIsMjE1LDIyLDIzNCw5NSwxMTIsMTIyLDEwMiwxNTIsMTA2LDE4Miw0LDIxOSw1NiwxMDEsMTQ3LDIyNywyNDYsMTE1LDY0LDE4Myw0LDY3LDEyNCwxNDAsMTY2LDEzOCwxNCwyNCw1MSwxODUsMTk5LDEwOSwyLDUxLDE5MCwxODEsMjQ3LDYxLDE5MCw4NSwyNDcsMjE3LDIyLDE2NCwxOTIsMjM1LDE4NywxNDgsMjYsMTA5LDI1MywxMTYsMjQxLDY1LDEzNiw0MSwyMjUsMjI5LDE3OCwxMjUsNzksMjQ2LDUzLDQ0LDE5MiwxNDEsMjQzLDE4NCwxNTEsMTIsOSwxMDQsMjE5LDE2Miw3MSw3Miw3LDg5LDgzLDgsMjI1LDEzLDQ4LDEzMywyNDcsMTg3LDExNCw4NSw3Nyw1OSw5OSw3MCw2OSw3MSwxMzMsMTY5LDExMSwxMTAsNzksNDQsMTc0LDIzLDEwMywxNjYsNjksNSwxNDcsNjYsNzcsMTYzLDExMiw0MiwxOTksMTQyLDE0LDIyLDE1MSwyNDgsMzMsMzQsMTk4LDE1NiwyMCwyMzEsMjA0LDcwLDgyLDI1MywxMzgsMTMxLDQyLDIwNSwzMywxMDksMjMyLDE3LDEwNiw2OSw3Miw2MCwyMTAsMjMyLDExMywxMjQsNzYsMTgxLDIwOCwwLDE4NiwyNTEsMTQzLDExMyw0NSwxNzEsMTAsNDMsMTUsMzAsMjA4LDkzLDYsMTY4LDEwNiwyMDMsNDgsMjUwLDIxOSw5LDE3LDEwNCwzNSw1OCwxNTksMTgwLDExMyw2NiwxMzYsNjJdXX19LCJyZXF1ZXN0ZWRfcHJvb2YiOnsicmV2ZWFsZWRfYXR0cnMiOnsiMGZlYTE4OTctNDMxMS00OGYyLWEyYzItMzg3NGU5OTNhZWYwIjp7InN1Yl9wcm9vZl9pbmRleCI6MCwicmF3IjoiQWxpY2UiLCJlbmNvZGVkIjoiMjcwMzQ2NDAwMjQxMTczMzEwMzMwNjMxMjgwNDQwMDQzMTgyMTg0ODY4MTY5MzE1MjA4ODY0MDU1MzU2NTk5MzQ0MTc0Mzg3ODE1MDcifX0sInNlbGZfYXR0ZXN0ZWRfYXR0cnMiOnt9LCJ1bnJldmVhbGVkX2F0dHJzIjp7fSwicHJlZGljYXRlcyI6e319LCJpZGVudGlmaWVycyI6W3sic2NoZW1hX2lkIjoiN3lXNlNvVGpITmhEM3pZZ200UGJLODoyOnRlc3Qtc2NoZW1hLTRlOTRjMmU0LTRmNDctNGZmMy1hODg4LTZmNjRkYTZhOTJkYzoxLjAiLCJjcmVkX2RlZl9pZCI6Ijd5VzZTb1RqSE5oRDN6WWdtNFBiSzg6MzpDTDo0NzIzMTk6VEFHIiwicmV2X3JlZ19pZCI6bnVsbCwidGltZXN0YW1wIjpudWxsfV19", }, "mime-type": "application/json", }, ], - "~thread": Object { + "~thread": { "thid": "00cdd404-82fc-431d-9c18-fb643636d2a5", }, }, - "proposalMessage": Object { + "proposalMessage": { "@id": "00cdd404-82fc-431d-9c18-fb643636d2a5", "@type": "https://didcomm.org/present-proof/1.0/propose-presentation", - "presentation_proposal": Object { + "presentation_proposal": { "@type": "https://didcomm.org/present-proof/1.0/presentation-preview", - "attributes": Array [ - Object { + "attributes": [ + { "cred_def_id": "7yW6SoTjHNhD3zYgm4PbK8:3:CL:472319:TAG", "name": "name", "value": "Alice", }, ], - "predicates": Array [], + "predicates": [], }, }, - "requestMessage": Object { + "requestMessage": { "@id": "2862b25d-396c-46d7-ad09-f48ff4ea6db6", "@type": "https://didcomm.org/present-proof/1.0/request-presentation", - "request_presentations~attach": Array [ - Object { + "request_presentations~attach": [ + { "@id": "libindy-request-presentation-0", - "data": Object { + "data": { "base64": "eyJuYW1lIjoicHJvb2YtcmVxdWVzdCIsInZlcnNpb24iOiIxLjAiLCJub25jZSI6IjQwMzU1MDc0MDYxMTU0MzEwMzA5NzMyMiIsInJlcXVlc3RlZF9hdHRyaWJ1dGVzIjp7IjBmZWExODk3LTQzMTEtNDhmMi1hMmMyLTM4NzRlOTkzYWVmMCI6eyJuYW1lIjoibmFtZSIsInJlc3RyaWN0aW9ucyI6W3siY3JlZF9kZWZfaWQiOiI3eVc2U29UakhOaEQzellnbTRQYks4OjM6Q0w6NDcyMzE5OlRBRyJ9XX19LCJyZXF1ZXN0ZWRfcHJlZGljYXRlcyI6e319", }, "mime-type": "application/json", }, ], - "~thread": Object { + "~thread": { "thid": "00cdd404-82fc-431d-9c18-fb643636d2a5", }, }, diff --git a/packages/core/src/storage/migration/__tests__/__snapshots__/0.3.test.ts.snap b/packages/core/src/storage/migration/__tests__/__snapshots__/0.3.test.ts.snap index 8169373e57..92ecc32e40 100644 --- a/packages/core/src/storage/migration/__tests__/__snapshots__/0.3.test.ts.snap +++ b/packages/core/src/storage/migration/__tests__/__snapshots__/0.3.test.ts.snap @@ -1,516 +1,59 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`UpdateAssistant | v0.3 - v0.3.1 should correctly update the did records 1`] = ` -Object { - "1-4e4f-41d9-94c4-f49351b811f1": Object { - "id": "1-4e4f-41d9-94c4-f49351b811f1", - "tags": Object { - "did": "did:peer:1zQmWxKCTkKYQvzdUshMWy7b8vy3Y7uLtB9hp6BbQhKEtQCd", +exports[`UpdateAssistant | v0.3.1 - v0.4 should correctly update the did records and remove cache records 1`] = ` +{ + "4993c740-5cd9-4c79-a7d8-23d1266d31be": { + "id": "4993c740-5cd9-4c79-a7d8-23d1266d31be", + "tags": { + "did": "did:indy:bcovrin:test:Pow4pdnPgTS7JAXvWkoF2c", "legacyUnqualifiedDid": undefined, - "method": "peer", - "methodSpecificIdentifier": "1zQmWxKCTkKYQvzdUshMWy7b8vy3Y7uLtB9hp6BbQhKEtQCd", - "recipientKeyFingerprints": Array [ - "z6Mkpg6nDPuYdLMuzNEjaa2Xprh3J2MC1WtNakWUEFqC4wvU", - ], + "method": "indy", + "methodSpecificIdentifier": "bcovrin:test:Pow4pdnPgTS7JAXvWkoF2c", + "qualifiedIndyDid": undefined, + "recipientKeyFingerprints": [], "role": "created", }, "type": "DidRecord", - "value": Object { - "_tags": Object { - "method": "peer", - "recipientKeyFingerprints": Array [ - "z6Mkpg6nDPuYdLMuzNEjaa2Xprh3J2MC1WtNakWUEFqC4wvU", - ], - "role": "created", - }, - "createdAt": "2022-12-27T13:51:21.344Z", - "did": "did:peer:1zQmWxKCTkKYQvzdUshMWy7b8vy3Y7uLtB9hp6BbQhKEtQCd", - "didDocument": Object { - "@context": Array [ - "https://w3id.org/did/v1", - ], - "authentication": Array [ - Object { - "controller": "#id", - "id": "#57a05508-1d1c-474c-8c68-1afcf3188720", - "publicKeyBase58": "BDqjd9f7HnsSssQ2u14gym93UT5Lbde1tjbYPysB9j96", - "type": "Ed25519VerificationKey2018", - }, - ], - "id": "did:peer:1zQmWxKCTkKYQvzdUshMWy7b8vy3Y7uLtB9hp6BbQhKEtQCd", - "keyAgreement": Array [ - Object { - "controller": "#id", - "id": "#59d2ce6f-c9fc-49c6-a8f6-eab14820b028", - "publicKeyBase58": "avivQP6GvWj6cBbxbXSSZnZTA4tGsvQ5DB9FXm45tZt", - "type": "X25519KeyAgreementKey2019", - }, - ], - "service": Array [ - Object { - "id": "#inline-0", - "priority": 0, - "recipientKeys": Array [ - "#57a05508-1d1c-474c-8c68-1afcf3188720", - ], - "routingKeys": Array [ - "did:key:z6MkqhwtVT5V6kwsNkErPNgSWuRGd4QLFQ324FMfw5oWGHop#z6MkqhwtVT5V6kwsNkErPNgSWuRGd4QLFQ324FMfw5oWGHop", - ], - "serviceEndpoint": "ws://ssi.mediator.com", - "type": "did-communication", - }, - ], - }, - "id": "1-4e4f-41d9-94c4-f49351b811f1", - "metadata": Object {}, + "value": { + "createdAt": "2023-03-18T18:35:07.208Z", + "did": "did:indy:bcovrin:test:Pow4pdnPgTS7JAXvWkoF2c", + "id": "4993c740-5cd9-4c79-a7d8-23d1266d31be", + "metadata": {}, "role": "created", + "updatedAt": "2023-03-18T22:50:20.522Z", }, }, - "2-4e4f-41d9-94c4-f49351b811f1": Object { - "id": "2-4e4f-41d9-94c4-f49351b811f1", - "tags": Object { - "did": "did:peer:1zQmavZzrLPRYRGd5CgyKV4GBZG43eGzjic9FHXc7jLHY14W", + "8168612b-73d1-4917-9a61-84e8102988f0": { + "id": "8168612b-73d1-4917-9a61-84e8102988f0", + "tags": { + "did": "did:indy:bcovrin:test:8DFqUo6UtQLLZETE7Gm29k", "legacyUnqualifiedDid": undefined, - "method": "peer", - "methodSpecificIdentifier": "1zQmavZzrLPRYRGd5CgyKV4GBZG43eGzjic9FHXc7jLHY14W", - "recipientKeyFingerprints": Array [ - "z6MkuLL6bQykGpposTrQhfYceQRR4JDzvdm133xpwb7Dv1oz", - ], - "role": "received", - }, - "type": "DidRecord", - "value": Object { - "_tags": Object { - "method": "peer", - "recipientKeyFingerprints": Array [ - "z6MkuLL6bQykGpposTrQhfYceQRR4JDzvdm133xpwb7Dv1oz", - ], - "role": "received", - }, - "createdAt": "2022-12-27T13:51:51.414Z", - "did": "did:peer:1zQmavZzrLPRYRGd5CgyKV4GBZG43eGzjic9FHXc7jLHY14W", - "didDocument": Object { - "@context": Array [ - "https://w3id.org/did/v1", - ], - "authentication": Array [ - Object { - "controller": "#id", - "id": "#6173438e-09a5-4b1e-895a-0563f5a169b7", - "publicKeyBase58": "Ft541AjJwHLLky1i26amoJsREix9WkWeM33u7K9Czo2c", - "type": "Ed25519VerificationKey2018", - }, - ], - "id": "did:peer:1zQmavZzrLPRYRGd5CgyKV4GBZG43eGzjic9FHXc7jLHY14W", - "keyAgreement": Array [ - Object { - "controller": "#id", - "id": "#a0448ee3-093a-4b16-bc59-8bf8559d60a5", - "publicKeyBase58": "JCfZJ72mtgGE9xuekJKV6yoAGzLgQCNkdHKkct8uaNKb", - "type": "X25519KeyAgreementKey2019", - }, - ], - "service": Array [ - Object { - "id": "#inline-0", - "priority": 0, - "recipientKeys": Array [ - "#6173438e-09a5-4b1e-895a-0563f5a169b7", - ], - "routingKeys": Array [], - "serviceEndpoint": "http://ssi.verifier.com", - "type": "did-communication", - }, - ], - }, - "id": "2-4e4f-41d9-94c4-f49351b811f1", - "metadata": Object {}, - "role": "received", - }, - }, - "3-4e4f-41d9-94c4-f49351b811f1": Object { - "id": "3-4e4f-41d9-94c4-f49351b811f1", - "tags": Object { - "did": "did:peer:1zQmZqQYzwqsYjj7z8kixxDXf9nux8TAsqj2izSpX1oCrd7q", - "legacyUnqualifiedDid": undefined, - "method": "peer", - "methodSpecificIdentifier": "1zQmZqQYzwqsYjj7z8kixxDXf9nux8TAsqj2izSpX1oCrd7q", - "recipientKeyFingerprints": Array [ - "z6Mkv6Kd744JcBL5P9gi5Ddtehxn1gSMTgM9kbTdfQ9soB8G", - ], - "role": "created", - }, - "type": "DidRecord", - "value": Object { - "_tags": Object { - "method": "peer", - "recipientKeyFingerprints": Array [ - "z6Mkv6Kd744JcBL5P9gi5Ddtehxn1gSMTgM9kbTdfQ9soB8G", - ], - "role": "created", - }, - "createdAt": "2022-12-27T13:50:32.815Z", - "did": "did:peer:1zQmZqQYzwqsYjj7z8kixxDXf9nux8TAsqj2izSpX1oCrd7q", - "didDocument": Object { - "@context": Array [ - "https://w3id.org/did/v1", - ], - "authentication": Array [ - Object { - "controller": "#id", - "id": "#d9d97b3e-5615-4e60-9366-a8ab1ec1a84d", - "publicKeyBase58": "Ge4aWoosGdqcGer1Peg3ocQnC7AW3o6o4aYhq8BrsxLt", - "type": "Ed25519VerificationKey2018", - }, - ], - "id": "did:peer:1zQmZqQYzwqsYjj7z8kixxDXf9nux8TAsqj2izSpX1oCrd7q", - "keyAgreement": Array [ - Object { - "controller": "#id", - "id": "#c30ea91b-5f49-461f-b0bd-046f947ef668", - "publicKeyBase58": "HKBdBGRK8uxgCwge2QHPBzVuuayEQFhC2LM3g1fzMFGE", - "type": "X25519KeyAgreementKey2019", - }, - ], - "service": Array [ - Object { - "id": "#inline-0", - "priority": 0, - "recipientKeys": Array [ - "#d9d97b3e-5615-4e60-9366-a8ab1ec1a84d", - ], - "routingKeys": Array [], - "serviceEndpoint": "didcomm:transport/queue", - "type": "did-communication", - }, - ], - }, - "id": "3-4e4f-41d9-94c4-f49351b811f1", - "metadata": Object {}, - "role": "created", - }, - }, - "4-4e4f-41d9-94c4-f49351b811f1": Object { - "id": "4-4e4f-41d9-94c4-f49351b811f1", - "tags": Object { - "did": "did:peer:1zQmRnGYmYBdSinH2953ZgfHWEpqp5W6kJmmYBgRaCs4Yudx", - "legacyUnqualifiedDid": undefined, - "method": "peer", - "methodSpecificIdentifier": "1zQmRnGYmYBdSinH2953ZgfHWEpqp5W6kJmmYBgRaCs4Yudx", - "recipientKeyFingerprints": Array [ - "z6MkvcuHjVcNRBM78E3xWy7MnVqQV9hAvTawBsPmg3bR1DaE", - ], - "role": "created", - }, - "type": "DidRecord", - "value": Object { - "_tags": Object { - "method": "peer", - "recipientKeyFingerprints": Array [ - "z6MkvcuHjVcNRBM78E3xWy7MnVqQV9hAvTawBsPmg3bR1DaE", - ], - "role": "created", - }, - "createdAt": "2022-12-27T13:51:50.193Z", - "did": "did:peer:1zQmRnGYmYBdSinH2953ZgfHWEpqp5W6kJmmYBgRaCs4Yudx", - "didDocument": Object { - "@context": Array [ - "https://w3id.org/did/v1", - ], - "authentication": Array [ - Object { - "controller": "#id", - "id": "#22219a28-b52a-4024-bc0f-62d3969131fd", - "publicKeyBase58": "HAeF9FMw5dre1jDFqQ9WwQHQfaRKWaLaVrUqqmdQ5znr", - "type": "Ed25519VerificationKey2018", - }, - ], - "id": "did:peer:1zQmRnGYmYBdSinH2953ZgfHWEpqp5W6kJmmYBgRaCs4Yudx", - "keyAgreement": Array [ - Object { - "controller": "#id", - "id": "#0f9535d1-9c9c-4491-be1e-1628f365b513", - "publicKeyBase58": "FztF8HCahTeL9gYHoHnDFo6HruwnKB19ZtbHFbLndAmE", - "type": "X25519KeyAgreementKey2019", - }, - ], - "service": Array [ - Object { - "id": "#inline-0", - "priority": 0, - "recipientKeys": Array [ - "#22219a28-b52a-4024-bc0f-62d3969131fd", - ], - "routingKeys": Array [ - "did:key:z6MkqhwtVT5V6kwsNkErPNgSWuRGd4QLFQ324FMfw5oWGHop#z6MkqhwtVT5V6kwsNkErPNgSWuRGd4QLFQ324FMfw5oWGHop", - ], - "serviceEndpoint": "ws://ssi.mediator.com", - "type": "did-communication", - }, - ], - }, - "id": "4-4e4f-41d9-94c4-f49351b811f1", - "metadata": Object {}, - "role": "created", - }, - }, - "5-4e4f-41d9-94c4-f49351b811f1": Object { - "id": "5-4e4f-41d9-94c4-f49351b811f1", - "tags": Object { - "did": "did:peer:1zQmb9oo8fn8AZ7rWh1DAfTbM3mnPp9Hq2bJFdHiqjnZX2Ce", - "legacyUnqualifiedDid": undefined, - "method": "peer", - "methodSpecificIdentifier": "1zQmb9oo8fn8AZ7rWh1DAfTbM3mnPp9Hq2bJFdHiqjnZX2Ce", - "recipientKeyFingerprints": Array [ - "z6Mkp1wyxzqoUrZ3TvnerfZBq48o1XLDSPH8ibFALkNABSgh", - ], - "role": "received", - }, - "type": "DidRecord", - "value": Object { - "_tags": Object { - "method": "peer", - "recipientKeyFingerprints": Array [ - "z6Mkp1wyxzqoUrZ3TvnerfZBq48o1XLDSPH8ibFALkNABSgh", - ], - "role": "received", - }, - "createdAt": "2022-12-27T13:50:44.957Z", - "did": "did:peer:1zQmb9oo8fn8AZ7rWh1DAfTbM3mnPp9Hq2bJFdHiqjnZX2Ce", - "didDocument": Object { - "@context": Array [ - "https://w3id.org/did/v1", - ], - "authentication": Array [ - Object { - "controller": "#id", - "id": "#075b7e8b-a7bd-41e1-9b01-044f1ccab1a6", - "publicKeyBase58": "AZgwNkbN9K4aMRwxB6bLyxaoBx4N2W2n2aLEWUQ9GDuK", - "type": "Ed25519VerificationKey2018", - }, - ], - "id": "did:peer:1zQmb9oo8fn8AZ7rWh1DAfTbM3mnPp9Hq2bJFdHiqjnZX2Ce", - "keyAgreement": Array [ - Object { - "controller": "#id", - "id": "#bf888dc5-0f54-4e71-9058-c43bebfc7d01", - "publicKeyBase58": "3Q8wpgxdCVdJfznYERZR1r9eVnCy7oxpjzGVucDLF2tG", - "type": "X25519KeyAgreementKey2019", - }, - ], - "service": Array [ - Object { - "id": "#inline-0", - "priority": 0, - "recipientKeys": Array [ - "#075b7e8b-a7bd-41e1-9b01-044f1ccab1a6", - ], - "routingKeys": Array [], - "serviceEndpoint": "ws://ssi.mediator.com", - "type": "did-communication", - }, - ], - }, - "id": "5-4e4f-41d9-94c4-f49351b811f1", - "metadata": Object {}, - "role": "received", - }, - }, - "6-4e4f-41d9-94c4-f49351b811f1": Object { - "id": "6-4e4f-41d9-94c4-f49351b811f1", - "tags": Object { - "did": "did:peer:1zQmSzC6uhWYcxtkr2fBepr66kjvSHuRsSfbmei7nrGj7HfN", - "legacyUnqualifiedDid": undefined, - "method": "peer", - "methodSpecificIdentifier": "1zQmSzC6uhWYcxtkr2fBepr66kjvSHuRsSfbmei7nrGj7HfN", - "recipientKeyFingerprints": Array [ - "z6MkrQzQqPtNjJHGLGjWC7H3mjMSNA36bCBTYJeoR44JoFvH", - ], - "role": "received", - }, - "type": "DidRecord", - "value": Object { - "_tags": Object { - "method": "peer", - "recipientKeyFingerprints": Array [ - "z6MkrQzQqPtNjJHGLGjWC7H3mjMSNA36bCBTYJeoR44JoFvH", - ], - "role": "received", - }, - "createdAt": "2022-12-27T13:50:34.057Z", - "did": "did:peer:1zQmSzC6uhWYcxtkr2fBepr66kjvSHuRsSfbmei7nrGj7HfN", - "didDocument": Object { - "@context": Array [ - "https://w3id.org/did/v1", - ], - "authentication": Array [ - Object { - "controller": "#id", - "id": "#b6d349fb-93fb-4298-b57a-3f2fecffccd8", - "publicKeyBase58": "CxjNF9dwPknoDmtoWYKCvdoSYamFBJw6rHjsan6Ht38u", - "type": "Ed25519VerificationKey2018", - }, - ], - "id": "did:peer:1zQmSzC6uhWYcxtkr2fBepr66kjvSHuRsSfbmei7nrGj7HfN", - "keyAgreement": Array [ - Object { - "controller": "#id", - "id": "#c0917f9f-1102-4f13-800c-ff7b522999eb", - "publicKeyBase58": "Eso3A6AmL5qiWr9syqHx8NgdQ8EMkYcitmrW5G7bX9uU", - "type": "X25519KeyAgreementKey2019", - }, - ], - "service": Array [ - Object { - "id": "#inline-0", - "priority": 0, - "recipientKeys": Array [ - "#b6d349fb-93fb-4298-b57a-3f2fecffccd8", - ], - "routingKeys": Array [], - "serviceEndpoint": "ws://ssi.issuer.com", - "type": "did-communication", - }, - ], - }, - "id": "6-4e4f-41d9-94c4-f49351b811f1", - "metadata": Object {}, - "role": "received", - }, - }, - "7-4e4f-41d9-94c4-f49351b811f1": Object { - "id": "7-4e4f-41d9-94c4-f49351b811f1", - "tags": Object { - "did": "did:peer:1zQmPaCELen1JWMWmhVZS16nDmrAC9yGKQcJCcBs5spXakA3", - "legacyUnqualifiedDid": undefined, - "method": "peer", - "methodSpecificIdentifier": "1zQmPaCELen1JWMWmhVZS16nDmrAC9yGKQcJCcBs5spXakA3", - "recipientKeyFingerprints": Array [ - "z6Mkt4ohp4uT74HdENeGVGAyVFTerCzzuDXP8kpU3yo6SqqM", - ], - "role": "received", - }, - "type": "DidRecord", - "value": Object { - "_tags": Object { - "method": "peer", - "recipientKeyFingerprints": Array [ - "z6Mkt4ohp4uT74HdENeGVGAyVFTerCzzuDXP8kpU3yo6SqqM", - ], - "role": "received", - }, - "createdAt": "2022-12-27T13:51:22.817Z", - "did": "did:peer:1zQmPaCELen1JWMWmhVZS16nDmrAC9yGKQcJCcBs5spXakA3", - "didDocument": Object { - "@context": Array [ - "https://w3id.org/did/v1", - ], - "authentication": Array [ - Object { - "controller": "#id", - "id": "#5ea98568-dfcd-4614-9495-ba95ec2665d3", - "publicKeyBase58": "EcYfDpf1mWoA7soZohD8e9uf2dj9VLH2SjuYDhq5Xd3y", - "type": "Ed25519VerificationKey2018", - }, - ], - "id": "did:peer:1zQmPaCELen1JWMWmhVZS16nDmrAC9yGKQcJCcBs5spXakA3", - "keyAgreement": Array [ - Object { - "controller": "#id", - "id": "#506e5ead-ddbc-44ef-848b-6593a692a916", - "publicKeyBase58": "EeQHhb6CqWGrQR9PfWpS1L8CedsbK2mZfPdaxaHN4s8b", - "type": "X25519KeyAgreementKey2019", - }, - ], - "service": Array [ - Object { - "id": "#inline-0", - "priority": 0, - "recipientKeys": Array [ - "#5ea98568-dfcd-4614-9495-ba95ec2665d3", - ], - "routingKeys": Array [], - "serviceEndpoint": "http://ssi.verifier.com", - "type": "did-communication", - }, - ], - }, - "id": "7-4e4f-41d9-94c4-f49351b811f1", - "metadata": Object {}, - "role": "received", - }, - }, - "8-4e4f-41d9-94c4-f49351b811f1": Object { - "id": "8-4e4f-41d9-94c4-f49351b811f1", - "tags": Object { - "did": "did:peer:1zQmUo1HoiciJS628w4SHweg4Pzs4bZLM4KLZgroSmUwBw7S", - "legacyUnqualifiedDid": undefined, - "method": "peer", - "methodSpecificIdentifier": "1zQmUo1HoiciJS628w4SHweg4Pzs4bZLM4KLZgroSmUwBw7S", - "recipientKeyFingerprints": Array [ - "z6MktdgEKUiH5kt5t2dKAH14WzFYhzj3JFobUf2PFFQAg2ma", - ], + "method": "indy", + "methodSpecificIdentifier": "bcovrin:test:8DFqUo6UtQLLZETE7Gm29k", + "qualifiedIndyDid": undefined, + "recipientKeyFingerprints": [], "role": "created", }, "type": "DidRecord", - "value": Object { - "_tags": Object { - "method": "peer", - "recipientKeyFingerprints": Array [ - "z6MktdgEKUiH5kt5t2dKAH14WzFYhzj3JFobUf2PFFQAg2ma", - ], - "role": "created", - }, - "createdAt": "2022-12-27T13:50:43.937Z", - "did": "did:peer:1zQmUo1HoiciJS628w4SHweg4Pzs4bZLM4KLZgroSmUwBw7S", - "didDocument": Object { - "@context": Array [ - "https://w3id.org/did/v1", - ], - "authentication": Array [ - Object { - "controller": "#id", - "id": "#12b8b7d4-87b9-4638-a929-f98df2f1f566", - "publicKeyBase58": "FBRBjETqkDPcmXncUi3DfthYtRTBtNZEne7TQyS9kozC", - "type": "Ed25519VerificationKey2018", - }, - ], - "id": "did:peer:1zQmUo1HoiciJS628w4SHweg4Pzs4bZLM4KLZgroSmUwBw7S", - "keyAgreement": Array [ - Object { - "controller": "#id", - "id": "#77b9cf84-2441-419f-b295-945d06e29edc", - "publicKeyBase58": "EgsQArrmCUru9MxR1RNNiomnMFz6E3ia2GfjVvoCjAWY", - "type": "X25519KeyAgreementKey2019", - }, - ], - "service": Array [ - Object { - "id": "#inline-0", - "priority": 0, - "recipientKeys": Array [ - "#12b8b7d4-87b9-4638-a929-f98df2f1f566", - ], - "routingKeys": Array [], - "serviceEndpoint": "didcomm:transport/queue", - "type": "did-communication", - }, - ], - }, - "id": "8-4e4f-41d9-94c4-f49351b811f1", - "metadata": Object {}, + "value": { + "createdAt": "2023-03-18T18:35:04.191Z", + "did": "did:indy:bcovrin:test:8DFqUo6UtQLLZETE7Gm29k", + "id": "8168612b-73d1-4917-9a61-84e8102988f0", + "metadata": {}, "role": "created", + "updatedAt": "2023-03-18T22:50:20.522Z", }, }, - "STORAGE_VERSION_RECORD_ID": Object { + "STORAGE_VERSION_RECORD_ID": { "id": "STORAGE_VERSION_RECORD_ID", - "tags": Object {}, + "tags": {}, "type": "StorageVersionRecord", - "value": Object { - "createdAt": "2022-09-08T19:35:53.872Z", + "value": { + "createdAt": "2023-03-18T18:35:02.888Z", "id": "STORAGE_VERSION_RECORD_ID", - "metadata": Object {}, - "storageVersion": "0.3.1", + "metadata": {}, + "storageVersion": "0.4", + "updatedAt": "2023-03-18T22:50:20.522Z", }, }, } diff --git a/packages/core/src/storage/migration/__tests__/__snapshots__/backup-askar.test.ts.snap b/packages/core/src/storage/migration/__tests__/__snapshots__/backup-askar.test.ts.snap new file mode 100644 index 0000000000..0698482397 --- /dev/null +++ b/packages/core/src/storage/migration/__tests__/__snapshots__/backup-askar.test.ts.snap @@ -0,0 +1,196 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`UpdateAssistant | Backup | Aries Askar should create a backup 1`] = ` +[ + { + "_tags": { + "connectionId": "0b6de73d-b376-430f-b2b4-f6e51407bb66", + "state": "done", + "threadId": "578e73da-c3be-43d4-949b-7aadfd5a6eae", + }, + "autoAcceptCredential": "contentApproved", + "connectionId": "0b6de73d-b376-430f-b2b4-f6e51407bb66", + "createdAt": "2022-03-21T22:50:20.522Z", + "credentialAttributes": [ + { + "mime-type": "text/plain", + "name": "name", + "value": "Alice", + }, + { + "mime-type": "text/plain", + "name": "age", + "value": "25", + }, + { + "mime-type": "text/plain", + "name": "dateOfBirth", + "value": "2020-01-01", + }, + ], + "credentials": [], + "id": "574b2a37-1db1-4af1-a3bf-35c6cb9e1d7a", + "metadata": { + "_internal/indyCredential": { + "credentialDefinitionId": "TL1EaPFCZ8Si5aUrqScBDt:3:CL:681:default", + "schemaId": "TL1EaPFCZ8Si5aUrqScBDt:2:schema-80f7eec5-8e5a-43ca-ad4d-3274fb9361b8:1.0", + }, + }, + "protocolVersion": "v1", + "state": "done", + "threadId": "578e73da-c3be-43d4-949b-7aadfd5a6eae", + "updatedAt": "2022-03-22T22:50:20.522Z", + }, + { + "_tags": { + "connectionId": "54b61a2c-59ae-4e63-a441-7f1286350132", + "credentialId": "a77114e1-c812-4bff-a53c-3d5003fcc278", + "credentialIds": [ + "a77114e1-c812-4bff-a53c-3d5003fcc278", + ], + "state": "done", + "threadId": "578e73da-c3be-43d4-949b-7aadfd5a6eae", + }, + "autoAcceptCredential": "contentApproved", + "connectionId": "54b61a2c-59ae-4e63-a441-7f1286350132", + "createdAt": "2022-03-21T22:50:20.535Z", + "credentialAttributes": [ + { + "mime-type": "text/plain", + "name": "name", + "value": "Alice", + }, + { + "mime-type": "text/plain", + "name": "age", + "value": "25", + }, + { + "mime-type": "text/plain", + "name": "dateOfBirth", + "value": "2020-01-01", + }, + ], + "credentials": [ + { + "credentialRecordId": "a77114e1-c812-4bff-a53c-3d5003fcc278", + "credentialRecordType": "indy", + }, + ], + "id": "5f2b7bc7-edfd-47e7-a1d4-aae050df2c4a", + "metadata": { + "_internal/indyCredential": { + "credentialDefinitionId": "TL1EaPFCZ8Si5aUrqScBDt:3:CL:681:default", + "schemaId": "TL1EaPFCZ8Si5aUrqScBDt:2:schema-80f7eec5-8e5a-43ca-ad4d-3274fb9361b8:1.0", + }, + "_internal/indyRequest": { + "master_secret_blinding_data": { + "v_prime": "36456944381549782028917743247126995038265466209293312755125557271456380841610111892515020379470931691048072348420844231863825225515560265358581756565441268878364665494094789024845049226122885121039335781567964878826549149370097276812152226343824116049855825405977949749345353074025294938300401262824951638782220004732873597724698990420932910079362747837952520524827009393981876443737452031919055976088763615615890946142630576421462920865811255312740184209214306243871230276622595183415487741608569800898909023830922654063814555128779494528740438076748829436757078504882332589744263200806138145494157659396691564807976032319024007464003538934", + "vr_prime": null, + }, + "master_secret_name": "Wallet: PopulateWallet2", + "nonce": "373984270150786864433163", + }, + }, + "protocolVersion": "v1", + "state": "done", + "threadId": "578e73da-c3be-43d4-949b-7aadfd5a6eae", + "updatedAt": "2022-03-22T22:50:20.522Z", + }, + { + "_tags": { + "connectionId": "cd66cbf1-5721-449e-8724-f4d8dcef1bc4", + "state": "done", + "threadId": "e2c2194c-6ac6-4b27-9030-18887c79b5eb", + }, + "autoAcceptCredential": "contentApproved", + "connectionId": "cd66cbf1-5721-449e-8724-f4d8dcef1bc4", + "createdAt": "2022-03-21T22:50:20.740Z", + "credentialAttributes": [ + { + "mime-type": "text/plain", + "name": "name", + "value": "Alice", + }, + { + "mime-type": "text/plain", + "name": "age", + "value": "25", + }, + { + "mime-type": "text/plain", + "name": "dateOfBirth", + "value": "2020-01-01", + }, + ], + "credentials": [], + "id": "ad644d8a-48a2-4c55-b46d-7a7f1a9278c7", + "metadata": { + "_internal/indyCredential": { + "credentialDefinitionId": "TL1EaPFCZ8Si5aUrqScBDt:3:CL:681:default", + "schemaId": "TL1EaPFCZ8Si5aUrqScBDt:2:schema-80f7eec5-8e5a-43ca-ad4d-3274fb9361b8:1.0", + }, + }, + "protocolVersion": "v1", + "state": "done", + "threadId": "e2c2194c-6ac6-4b27-9030-18887c79b5eb", + "updatedAt": "2022-03-22T22:50:20.522Z", + }, + { + "_tags": { + "connectionId": "d8f23338-9e99-469a-bd57-1c9a26c0080f", + "credentialId": "19c1f29f-d2df-486c-b8c6-950c403fa7d9", + "credentialIds": [ + "19c1f29f-d2df-486c-b8c6-950c403fa7d9", + ], + "state": "done", + "threadId": "e2c2194c-6ac6-4b27-9030-18887c79b5eb", + }, + "autoAcceptCredential": "contentApproved", + "connectionId": "d8f23338-9e99-469a-bd57-1c9a26c0080f", + "createdAt": "2022-03-21T22:50:20.746Z", + "credentialAttributes": [ + { + "mime-type": "text/plain", + "name": "name", + "value": "Alice", + }, + { + "mime-type": "text/plain", + "name": "age", + "value": "25", + }, + { + "mime-type": "text/plain", + "name": "dateOfBirth", + "value": "2020-01-01", + }, + ], + "credentials": [ + { + "credentialRecordId": "19c1f29f-d2df-486c-b8c6-950c403fa7d9", + "credentialRecordType": "indy", + }, + ], + "id": "c7e0a752-7f1c-41c0-b0ae-a68c2d97ca8c", + "metadata": { + "_internal/indyCredential": { + "credentialDefinitionId": "TL1EaPFCZ8Si5aUrqScBDt:3:CL:681:default", + "schemaId": "TL1EaPFCZ8Si5aUrqScBDt:2:schema-80f7eec5-8e5a-43ca-ad4d-3274fb9361b8:1.0", + }, + "_internal/indyRequest": { + "master_secret_blinding_data": { + "v_prime": "24405223168730122709164916892481085040205443709643249329100687534344659826655374235392514476392517756663433844139774514430993889493707631169979521764390851593418941181409704266182779162417466204970949168472702858363964258641437554267668466400711344128132909691514606077477555576087059339291048485225394874964325220472232903203038212033940680060605090839733163438385288769519855418153181511119637865605476043416048121313638627002888436809192752657860306784733123742838413845299796745569824223645588826964796075250758249133953560017373025169692866449286962430731916293683231375510684692358406054381559324718715654332979447698704161714028193478", + "vr_prime": null, + }, + "master_secret_name": "Wallet: PopulateWallet2", + "nonce": "698370616023883730498375", + }, + }, + "protocolVersion": "v1", + "state": "done", + "threadId": "e2c2194c-6ac6-4b27-9030-18887c79b5eb", + "updatedAt": "2022-03-22T22:50:20.522Z", + }, +] +`; diff --git a/packages/core/src/storage/migration/__tests__/__snapshots__/backup.test.ts.snap b/packages/core/src/storage/migration/__tests__/__snapshots__/backup.test.ts.snap index 04765793f5..6c949c78f3 100644 --- a/packages/core/src/storage/migration/__tests__/__snapshots__/backup.test.ts.snap +++ b/packages/core/src/storage/migration/__tests__/__snapshots__/backup.test.ts.snap @@ -1,9 +1,9 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`UpdateAssistant | Backup should create a backup 1`] = ` -Array [ - Object { - "_tags": Object { +[ + { + "_tags": { "connectionId": "0b6de73d-b376-430f-b2b4-f6e51407bb66", "state": "done", "threadId": "578e73da-c3be-43d4-949b-7aadfd5a6eae", @@ -11,27 +11,27 @@ Array [ "autoAcceptCredential": "contentApproved", "connectionId": "0b6de73d-b376-430f-b2b4-f6e51407bb66", "createdAt": "2022-03-21T22:50:20.522Z", - "credentialAttributes": Array [ - Object { + "credentialAttributes": [ + { "mime-type": "text/plain", "name": "name", "value": "Alice", }, - Object { + { "mime-type": "text/plain", "name": "age", "value": "25", }, - Object { + { "mime-type": "text/plain", "name": "dateOfBirth", "value": "2020-01-01", }, ], - "credentials": Array [], + "credentials": [], "id": "574b2a37-1db1-4af1-a3bf-35c6cb9e1d7a", - "metadata": Object { - "_internal/indyCredential": Object { + "metadata": { + "_internal/indyCredential": { "credentialDefinitionId": "TL1EaPFCZ8Si5aUrqScBDt:3:CL:681:default", "schemaId": "TL1EaPFCZ8Si5aUrqScBDt:2:schema-80f7eec5-8e5a-43ca-ad4d-3274fb9361b8:1.0", }, @@ -39,12 +39,13 @@ Array [ "protocolVersion": "v1", "state": "done", "threadId": "578e73da-c3be-43d4-949b-7aadfd5a6eae", + "updatedAt": "2022-03-21T22:50:20.522Z", }, - Object { - "_tags": Object { + { + "_tags": { "connectionId": "54b61a2c-59ae-4e63-a441-7f1286350132", "credentialId": "a77114e1-c812-4bff-a53c-3d5003fcc278", - "credentialIds": Array [ + "credentialIds": [ "a77114e1-c812-4bff-a53c-3d5003fcc278", ], "state": "done", @@ -53,37 +54,37 @@ Array [ "autoAcceptCredential": "contentApproved", "connectionId": "54b61a2c-59ae-4e63-a441-7f1286350132", "createdAt": "2022-03-21T22:50:20.535Z", - "credentialAttributes": Array [ - Object { + "credentialAttributes": [ + { "mime-type": "text/plain", "name": "name", "value": "Alice", }, - Object { + { "mime-type": "text/plain", "name": "age", "value": "25", }, - Object { + { "mime-type": "text/plain", "name": "dateOfBirth", "value": "2020-01-01", }, ], - "credentials": Array [ - Object { + "credentials": [ + { "credentialRecordId": "a77114e1-c812-4bff-a53c-3d5003fcc278", "credentialRecordType": "indy", }, ], "id": "5f2b7bc7-edfd-47e7-a1d4-aae050df2c4a", - "metadata": Object { - "_internal/indyCredential": Object { + "metadata": { + "_internal/indyCredential": { "credentialDefinitionId": "TL1EaPFCZ8Si5aUrqScBDt:3:CL:681:default", "schemaId": "TL1EaPFCZ8Si5aUrqScBDt:2:schema-80f7eec5-8e5a-43ca-ad4d-3274fb9361b8:1.0", }, - "_internal/indyRequest": Object { - "master_secret_blinding_data": Object { + "_internal/indyRequest": { + "master_secret_blinding_data": { "v_prime": "36456944381549782028917743247126995038265466209293312755125557271456380841610111892515020379470931691048072348420844231863825225515560265358581756565441268878364665494094789024845049226122885121039335781567964878826549149370097276812152226343824116049855825405977949749345353074025294938300401262824951638782220004732873597724698990420932910079362747837952520524827009393981876443737452031919055976088763615615890946142630576421462920865811255312740184209214306243871230276622595183415487741608569800898909023830922654063814555128779494528740438076748829436757078504882332589744263200806138145494157659396691564807976032319024007464003538934", "vr_prime": null, }, @@ -94,9 +95,10 @@ Array [ "protocolVersion": "v1", "state": "done", "threadId": "578e73da-c3be-43d4-949b-7aadfd5a6eae", + "updatedAt": "2022-03-21T22:50:20.522Z", }, - Object { - "_tags": Object { + { + "_tags": { "connectionId": "cd66cbf1-5721-449e-8724-f4d8dcef1bc4", "state": "done", "threadId": "e2c2194c-6ac6-4b27-9030-18887c79b5eb", @@ -104,27 +106,27 @@ Array [ "autoAcceptCredential": "contentApproved", "connectionId": "cd66cbf1-5721-449e-8724-f4d8dcef1bc4", "createdAt": "2022-03-21T22:50:20.740Z", - "credentialAttributes": Array [ - Object { + "credentialAttributes": [ + { "mime-type": "text/plain", "name": "name", "value": "Alice", }, - Object { + { "mime-type": "text/plain", "name": "age", "value": "25", }, - Object { + { "mime-type": "text/plain", "name": "dateOfBirth", "value": "2020-01-01", }, ], - "credentials": Array [], + "credentials": [], "id": "ad644d8a-48a2-4c55-b46d-7a7f1a9278c7", - "metadata": Object { - "_internal/indyCredential": Object { + "metadata": { + "_internal/indyCredential": { "credentialDefinitionId": "TL1EaPFCZ8Si5aUrqScBDt:3:CL:681:default", "schemaId": "TL1EaPFCZ8Si5aUrqScBDt:2:schema-80f7eec5-8e5a-43ca-ad4d-3274fb9361b8:1.0", }, @@ -132,12 +134,13 @@ Array [ "protocolVersion": "v1", "state": "done", "threadId": "e2c2194c-6ac6-4b27-9030-18887c79b5eb", + "updatedAt": "2022-03-21T22:50:20.522Z", }, - Object { - "_tags": Object { + { + "_tags": { "connectionId": "d8f23338-9e99-469a-bd57-1c9a26c0080f", "credentialId": "19c1f29f-d2df-486c-b8c6-950c403fa7d9", - "credentialIds": Array [ + "credentialIds": [ "19c1f29f-d2df-486c-b8c6-950c403fa7d9", ], "state": "done", @@ -146,37 +149,37 @@ Array [ "autoAcceptCredential": "contentApproved", "connectionId": "d8f23338-9e99-469a-bd57-1c9a26c0080f", "createdAt": "2022-03-21T22:50:20.746Z", - "credentialAttributes": Array [ - Object { + "credentialAttributes": [ + { "mime-type": "text/plain", "name": "name", "value": "Alice", }, - Object { + { "mime-type": "text/plain", "name": "age", "value": "25", }, - Object { + { "mime-type": "text/plain", "name": "dateOfBirth", "value": "2020-01-01", }, ], - "credentials": Array [ - Object { + "credentials": [ + { "credentialRecordId": "19c1f29f-d2df-486c-b8c6-950c403fa7d9", "credentialRecordType": "indy", }, ], "id": "c7e0a752-7f1c-41c0-b0ae-a68c2d97ca8c", - "metadata": Object { - "_internal/indyCredential": Object { + "metadata": { + "_internal/indyCredential": { "credentialDefinitionId": "TL1EaPFCZ8Si5aUrqScBDt:3:CL:681:default", "schemaId": "TL1EaPFCZ8Si5aUrqScBDt:2:schema-80f7eec5-8e5a-43ca-ad4d-3274fb9361b8:1.0", }, - "_internal/indyRequest": Object { - "master_secret_blinding_data": Object { + "_internal/indyRequest": { + "master_secret_blinding_data": { "v_prime": "24405223168730122709164916892481085040205443709643249329100687534344659826655374235392514476392517756663433844139774514430993889493707631169979521764390851593418941181409704266182779162417466204970949168472702858363964258641437554267668466400711344128132909691514606077477555576087059339291048485225394874964325220472232903203038212033940680060605090839733163438385288769519855418153181511119637865605476043416048121313638627002888436809192752657860306784733123742838413845299796745569824223645588826964796075250758249133953560017373025169692866449286962430731916293683231375510684692358406054381559324718715654332979447698704161714028193478", "vr_prime": null, }, @@ -187,6 +190,7 @@ Array [ "protocolVersion": "v1", "state": "done", "threadId": "e2c2194c-6ac6-4b27-9030-18887c79b5eb", + "updatedAt": "2022-03-21T22:50:20.522Z", }, ] `; diff --git a/packages/core/src/storage/migration/__tests__/backup-askar.test.ts b/packages/core/src/storage/migration/__tests__/backup-askar.test.ts new file mode 100644 index 0000000000..170584f2f1 --- /dev/null +++ b/packages/core/src/storage/migration/__tests__/backup-askar.test.ts @@ -0,0 +1,168 @@ +import type { FileSystem } from '../../FileSystem' +import type { StorageUpdateError } from '../error/StorageUpdateError' + +import { readFileSync, unlinkSync } from 'fs' +import path from 'path' + +import { describeRunInNodeVersion } from '../../../../../../tests/runInVersion' +import { AskarModule } from '../../../../../askar/src' +import { askarModuleConfig } from '../../../../../askar/tests/helpers' +import { getAgentOptions } from '../../../../tests/helpers' +import { Agent } from '../../../agent/Agent' +import { InjectionSymbols } from '../../../constants' +import { AriesFrameworkError } from '../../../error' +import { CredentialExchangeRecord, CredentialRepository } from '../../../modules/credentials' +import { JsonTransformer } from '../../../utils' +import { StorageUpdateService } from '../StorageUpdateService' +import { UpdateAssistant } from '../UpdateAssistant' + +const agentOptions = getAgentOptions( + 'UpdateAssistant | Backup | Aries Askar', + {}, + { + askar: new AskarModule(askarModuleConfig), + } +) + +const aliceCredentialRecordsString = readFileSync( + path.join(__dirname, '__fixtures__/alice-4-credentials-0.1.json'), + 'utf8' +) + +const backupDate = new Date('2022-03-22T22:50:20.522Z') +jest.useFakeTimers().setSystemTime(backupDate) +const backupIdentifier = backupDate.getTime() + +describeRunInNodeVersion([18], 'UpdateAssistant | Backup | Aries Askar', () => { + let updateAssistant: UpdateAssistant + let agent: Agent + let backupPath: string + + beforeEach(async () => { + agent = new Agent(agentOptions) + const fileSystem = agent.dependencyManager.resolve(InjectionSymbols.FileSystem) + backupPath = `${fileSystem.dataPath}/migration/backup/${backupIdentifier}` + + // If tests fail it's possible the cleanup has been skipped. So remove before running tests + const doesFileSystemExist = await fileSystem.exists(backupPath) + if (doesFileSystemExist) { + unlinkSync(backupPath) + } + const doesbackupFileSystemExist = await fileSystem.exists(`${backupPath}-error`) + if (doesbackupFileSystemExist) { + unlinkSync(`${backupPath}-error`) + } + + updateAssistant = new UpdateAssistant(agent, { + v0_1ToV0_2: { + mediationRoleUpdateStrategy: 'allMediator', + }, + }) + + await updateAssistant.initialize() + }) + + afterEach(async () => { + await agent.shutdown() + await agent.wallet.delete() + }) + + it('should create a backup', async () => { + const aliceCredentialRecordsJson = JSON.parse(aliceCredentialRecordsString) + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const aliceCredentialRecords = Object.values(aliceCredentialRecordsJson).map((data: any) => { + const record = JsonTransformer.fromJSON(data.value, CredentialExchangeRecord) + + record.setTags(data.tags) + return record + }) + + const credentialRepository = agent.dependencyManager.resolve(CredentialRepository) + const storageUpdateService = agent.dependencyManager.resolve(StorageUpdateService) + + // Add 0.1 data and set version to 0.1 + for (const credentialRecord of aliceCredentialRecords) { + await credentialRepository.save(agent.context, credentialRecord) + } + await storageUpdateService.setCurrentStorageVersion(agent.context, '0.1') + + // Expect an update is needed + expect(await updateAssistant.isUpToDate()).toBe(false) + + const fileSystem = agent.dependencyManager.resolve(InjectionSymbols.FileSystem) + // Backup should not exist before update + expect(await fileSystem.exists(backupPath)).toBe(false) + + const walletSpy = jest.spyOn(agent.wallet, 'export') + + // Create update + await updateAssistant.update() + + // A wallet export should have been initiated + expect(walletSpy).toHaveBeenCalledWith({ key: agent.wallet.walletConfig?.key, path: backupPath }) + + // Backup should be cleaned after update + expect(await fileSystem.exists(backupPath)).toBe(false) + + expect( + (await credentialRepository.getAll(agent.context)).sort((a, b) => a.id.localeCompare(b.id)) + ).toMatchSnapshot() + }) + + it('should restore the backup if an error occurs during the update', async () => { + const aliceCredentialRecordsJson = JSON.parse(aliceCredentialRecordsString) + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const aliceCredentialRecords = Object.values(aliceCredentialRecordsJson).map((data: any) => { + const record = JsonTransformer.fromJSON(data.value, CredentialExchangeRecord) + + record.setTags(data.tags) + return record + }) + + const credentialRepository = agent.dependencyManager.resolve(CredentialRepository) + const storageUpdateService = agent.dependencyManager.resolve(StorageUpdateService) + + // Add 0.1 data and set version to 0.1 + for (const credentialRecord of aliceCredentialRecords) { + await credentialRepository.save(agent.context, credentialRecord) + } + await storageUpdateService.setCurrentStorageVersion(agent.context, '0.1') + + // Expect an update is needed + expect(await updateAssistant.isUpToDate()).toBe(false) + jest.spyOn(updateAssistant, 'getNeededUpdates').mockResolvedValue([ + { + fromVersion: '0.1', + toVersion: '0.2', + doUpdate: async () => { + throw new AriesFrameworkError("Uh oh I'm broken") + }, + }, + ]) + + const fileSystem = agent.dependencyManager.resolve(InjectionSymbols.FileSystem) + // Backup should not exist before update + expect(await fileSystem.exists(backupPath)).toBe(false) + + let updateError: StorageUpdateError | undefined = undefined + + try { + await updateAssistant.update() + } catch (error) { + updateError = error + } + + expect(updateError?.cause?.message).toEqual("Uh oh I'm broken") + + // Only backup error should exist after update + expect(await fileSystem.exists(backupPath)).toBe(false) + expect(await fileSystem.exists(`${backupPath}-error`)).toBe(true) + + // Wallet should be same as when we started because of backup + expect((await credentialRepository.getAll(agent.context)).sort((a, b) => a.id.localeCompare(b.id))).toEqual( + aliceCredentialRecords.sort((a, b) => a.id.localeCompare(b.id)) + ) + }) +}) diff --git a/packages/core/src/storage/migration/__tests__/backup.test.ts b/packages/core/src/storage/migration/__tests__/backup.test.ts index b1efe9b580..e582263b57 100644 --- a/packages/core/src/storage/migration/__tests__/backup.test.ts +++ b/packages/core/src/storage/migration/__tests__/backup.test.ts @@ -4,6 +4,7 @@ import type { StorageUpdateError } from '../error/StorageUpdateError' import { readFileSync, unlinkSync } from 'fs' import path from 'path' +import { getIndySdkModules } from '../../../../../indy-sdk/tests/setupIndySdkModule' import { getAgentOptions } from '../../../../tests/helpers' import { Agent } from '../../../agent/Agent' import { InjectionSymbols } from '../../../constants' @@ -13,7 +14,7 @@ import { JsonTransformer } from '../../../utils' import { StorageUpdateService } from '../StorageUpdateService' import { UpdateAssistant } from '../UpdateAssistant' -const agentOptions = getAgentOptions('UpdateAssistant | Backup') +const agentOptions = getAgentOptions('UpdateAssistant | Backup', {}, getIndySdkModules()) const aliceCredentialRecordsString = readFileSync( path.join(__dirname, '__fixtures__/alice-4-credentials-0.1.json'), @@ -32,7 +33,7 @@ describe('UpdateAssistant | Backup', () => { beforeEach(async () => { agent = new Agent(agentOptions) const fileSystem = agent.dependencyManager.resolve(InjectionSymbols.FileSystem) - backupPath = `${fileSystem.basePath}/afj/migration/backup/${backupIdentifier}` + backupPath = `${fileSystem.dataPath}/migration/backup/${backupIdentifier}` // If tests fail it's possible the cleanup has been skipped. So remove before running tests const doesFileSystemExist = await fileSystem.exists(backupPath) @@ -81,15 +82,20 @@ describe('UpdateAssistant | Backup', () => { // Expect an update is needed expect(await updateAssistant.isUpToDate()).toBe(false) - const fileSystem = agent.injectionContainer.resolve(InjectionSymbols.FileSystem) + const fileSystem = agent.dependencyManager.resolve(InjectionSymbols.FileSystem) // Backup should not exist before update expect(await fileSystem.exists(backupPath)).toBe(false) + const walletSpy = jest.spyOn(agent.wallet, 'export') + // Create update await updateAssistant.update() - // Backup should exist after update - expect(await fileSystem.exists(backupPath)).toBe(true) + // A wallet export should have been initiated + expect(walletSpy).toHaveBeenCalledWith({ key: agent.wallet.walletConfig?.key, path: backupPath }) + + // Backup should be cleaned after update + expect(await fileSystem.exists(backupPath)).toBe(false) expect( (await credentialRepository.getAll(agent.context)).sort((a, b) => a.id.localeCompare(b.id)) @@ -128,7 +134,7 @@ describe('UpdateAssistant | Backup', () => { }, ]) - const fileSystem = agent.injectionContainer.resolve(InjectionSymbols.FileSystem) + const fileSystem = agent.dependencyManager.resolve(InjectionSymbols.FileSystem) // Backup should not exist before update expect(await fileSystem.exists(backupPath)).toBe(false) @@ -142,8 +148,8 @@ describe('UpdateAssistant | Backup', () => { expect(updateError?.cause?.message).toEqual("Uh oh I'm broken") - // Backup should exist after update - expect(await fileSystem.exists(backupPath)).toBe(true) + // Only backup error should exist after update + expect(await fileSystem.exists(backupPath)).toBe(false) expect(await fileSystem.exists(`${backupPath}-error`)).toBe(true) // Wallet should be same as when we started because of backup diff --git a/packages/core/src/storage/migration/index.ts b/packages/core/src/storage/migration/index.ts index c908e9655d..477cfc3df5 100644 --- a/packages/core/src/storage/migration/index.ts +++ b/packages/core/src/storage/migration/index.ts @@ -2,3 +2,4 @@ export * from './repository/StorageVersionRecord' export * from './repository/StorageVersionRepository' export * from './StorageUpdateService' export * from './UpdateAssistant' +export { Update } from './updates' diff --git a/packages/core/src/storage/migration/updates.ts b/packages/core/src/storage/migration/updates.ts index 08c890fdd0..4e1d09a898 100644 --- a/packages/core/src/storage/migration/updates.ts +++ b/packages/core/src/storage/migration/updates.ts @@ -1,10 +1,11 @@ +import type { V0_1ToV0_2UpdateConfig } from './updates/0.1-0.2' import type { BaseAgent } from '../../agent/BaseAgent' import type { VersionString } from '../../utils/version' -import type { V0_1ToV0_2UpdateConfig } from './updates/0.1-0.2' import { updateV0_1ToV0_2 } from './updates/0.1-0.2' import { updateV0_2ToV0_3 } from './updates/0.2-0.3' import { updateV0_3ToV0_3_1 } from './updates/0.3-0.3.1' +import { updateV0_3_1ToV0_4 } from './updates/0.3.1-0.4' export const INITIAL_STORAGE_VERSION = '0.1' @@ -40,6 +41,11 @@ export const supportedUpdates = [ toVersion: '0.3.1', doUpdate: updateV0_3ToV0_3_1, }, + { + fromVersion: '0.3.1', + toVersion: '0.4', + doUpdate: updateV0_3_1ToV0_4, + }, ] as const // Current version is last toVersion from the supported updates @@ -49,4 +55,4 @@ export const CURRENT_FRAMEWORK_STORAGE_VERSION = supportedUpdates[supportedUpdat // eslint-disable-next-line @typescript-eslint/no-unused-vars type LastItem = T extends readonly [...infer _, infer U] ? U : T[0] | undefined -export type UpdateToVersion = typeof supportedUpdates[number]['toVersion'] +export type UpdateToVersion = (typeof supportedUpdates)[number]['toVersion'] diff --git a/packages/core/src/storage/migration/updates/0.1-0.2/credential.ts b/packages/core/src/storage/migration/updates/0.1-0.2/credential.ts index 647f8e8a40..61c2ddb3af 100644 --- a/packages/core/src/storage/migration/updates/0.1-0.2/credential.ts +++ b/packages/core/src/storage/migration/updates/0.1-0.2/credential.ts @@ -1,9 +1,8 @@ import type { BaseAgent } from '../../../../agent/BaseAgent' -import type { CredentialMetadata, CredentialExchangeRecord } from '../../../../modules/credentials' +import type { CredentialExchangeRecord } from '../../../../modules/credentials' import type { JsonObject } from '../../../../types' import { CredentialState } from '../../../../modules/credentials/models/CredentialState' -import { CredentialMetadataKeys } from '../../../../modules/credentials/repository/CredentialMetadataTypes' import { CredentialRepository } from '../../../../modules/credentials/repository/CredentialRepository' import { Metadata } from '../../../Metadata' import { DidCommMessageRepository, DidCommMessageRecord, DidCommMessageRole } from '../../../didcomm' @@ -122,27 +121,25 @@ export async function updateIndyMetadata( agent.config.logger.debug(`Updating indy metadata to use the generic metadata api available to records.`) const { requestMetadata, schemaId, credentialDefinitionId, ...rest } = credentialRecord.metadata.data - const metadata = new Metadata(rest) + const metadata = new Metadata>(rest) + const indyRequestMetadataKey = '_internal/indyRequest' + const indyCredentialMetadataKey = '_internal/indyCredential' if (requestMetadata) { - agent.config.logger.trace( - `Found top-level 'requestMetadata' key, moving to '${CredentialMetadataKeys.IndyRequest}'` - ) - metadata.add(CredentialMetadataKeys.IndyRequest, { ...requestMetadata }) + agent.config.logger.trace(`Found top-level 'requestMetadata' key, moving to '${indyRequestMetadataKey}'`) + metadata.add(indyRequestMetadataKey, { ...requestMetadata }) } if (schemaId && typeof schemaId === 'string') { - agent.config.logger.trace( - `Found top-level 'schemaId' key, moving to '${CredentialMetadataKeys.IndyCredential}.schemaId'` - ) - metadata.add(CredentialMetadataKeys.IndyCredential, { schemaId }) + agent.config.logger.trace(`Found top-level 'schemaId' key, moving to '${indyCredentialMetadataKey}.schemaId'`) + metadata.add(indyCredentialMetadataKey, { schemaId }) } if (credentialDefinitionId && typeof credentialDefinitionId === 'string') { agent.config.logger.trace( - `Found top-level 'credentialDefinitionId' key, moving to '${CredentialMetadataKeys.IndyCredential}.credentialDefinitionId'` + `Found top-level 'credentialDefinitionId' key, moving to '${indyCredentialMetadataKey}.credentialDefinitionId'` ) - metadata.add(CredentialMetadataKeys.IndyCredential, { credentialDefinitionId }) + metadata.add(indyCredentialMetadataKey, { credentialDefinitionId }) } credentialRecord.metadata = metadata @@ -170,7 +167,7 @@ export async function updateIndyMetadata( * "credentials": [ * { * "credentialRecordId": "09e46da9-a575-4909-b016-040e96c3c539", - * "credentialRecordType": "indy", + * "credentialRecordType": "anoncreds" * } * ] * } diff --git a/packages/core/src/storage/migration/updates/0.1-0.2/mediation.ts b/packages/core/src/storage/migration/updates/0.1-0.2/mediation.ts index 7d41c3366d..c131646507 100644 --- a/packages/core/src/storage/migration/updates/0.1-0.2/mediation.ts +++ b/packages/core/src/storage/migration/updates/0.1-0.2/mediation.ts @@ -1,6 +1,6 @@ +import type { V0_1ToV0_2UpdateConfig } from './index' import type { BaseAgent } from '../../../../agent/BaseAgent' import type { MediationRecord } from '../../../../modules/routing' -import type { V0_1ToV0_2UpdateConfig } from './index' import { MediationRepository, MediationRole } from '../../../../modules/routing' diff --git a/packages/core/src/storage/migration/updates/0.3.1-0.4/__tests__/cache.test.ts b/packages/core/src/storage/migration/updates/0.3.1-0.4/__tests__/cache.test.ts new file mode 100644 index 0000000000..477cdb0ffa --- /dev/null +++ b/packages/core/src/storage/migration/updates/0.3.1-0.4/__tests__/cache.test.ts @@ -0,0 +1,53 @@ +import { getAgentConfig, getAgentContext, mockFunction } from '../../../../../../tests/helpers' +import { Agent } from '../../../../../agent/Agent' +import * as testModule from '../cache' + +const agentConfig = getAgentConfig('Migration Cache 0.3.1-0.4') +const agentContext = getAgentContext() + +const storageService = { + getAll: jest.fn(), + deleteById: jest.fn(), +} + +jest.mock('../../../../../agent/Agent', () => { + return { + Agent: jest.fn(() => ({ + config: agentConfig, + context: agentContext, + dependencyManager: { + resolve: jest.fn(() => storageService), + }, + })), + } +}) + +// Mock typed object +const AgentMock = Agent as jest.Mock + +describe('0.3.1-0.4 | Cache', () => { + let agent: Agent + + beforeEach(() => { + agent = new AgentMock() + }) + + describe('migrateCacheToV0_4()', () => { + it('should fetch all cache records and remove them ', async () => { + const records = [{ id: 'first' }, { id: 'second' }] + + mockFunction(storageService.getAll).mockResolvedValue(records) + + await testModule.migrateCacheToV0_4(agent) + + expect(storageService.getAll).toHaveBeenCalledTimes(1) + expect(storageService.getAll).toHaveBeenCalledWith(agent.context, expect.anything()) + expect(storageService.deleteById).toHaveBeenCalledTimes(2) + + const [, , firstId] = mockFunction(storageService.deleteById).mock.calls[0] + const [, , secondId] = mockFunction(storageService.deleteById).mock.calls[1] + expect(firstId).toEqual('first') + expect(secondId).toEqual('second') + }) + }) +}) diff --git a/packages/core/src/storage/migration/updates/0.3.1-0.4/__tests__/did.test.ts b/packages/core/src/storage/migration/updates/0.3.1-0.4/__tests__/did.test.ts new file mode 100644 index 0000000000..fce2f75fb3 --- /dev/null +++ b/packages/core/src/storage/migration/updates/0.3.1-0.4/__tests__/did.test.ts @@ -0,0 +1,81 @@ +import { getAgentConfig, getAgentContext, mockFunction } from '../../../../../../tests/helpers' +import { Agent } from '../../../../../agent/Agent' +import { DidDocumentRole, DidRecord } from '../../../../../modules/dids' +import { DidRepository } from '../../../../../modules/dids/repository/DidRepository' +import { JsonTransformer } from '../../../../../utils' +import { uuid } from '../../../../../utils/uuid' +import { Metadata } from '../../../../Metadata' +import * as testModule from '../did' + +const agentConfig = getAgentConfig('Migration DidRecord 0.3.1-0.4') +const agentContext = getAgentContext() + +jest.mock('../../../../../modules/dids/repository/DidRepository') +const DidRepositoryMock = DidRepository as jest.Mock +const didRepository = new DidRepositoryMock() + +jest.mock('../../../../../agent/Agent', () => { + return { + Agent: jest.fn(() => ({ + config: agentConfig, + context: agentContext, + dependencyManager: { + resolve: jest.fn(() => didRepository), + }, + })), + } +}) + +// Mock typed object +const AgentMock = Agent as jest.Mock + +describe('0.3.1-0.4 | Did', () => { + let agent: Agent + + beforeEach(() => { + agent = new AgentMock() + }) + + describe('migrateDidRecordToV0_4()', () => { + it('should fetch all records and apply the needed updates ', async () => { + const records: DidRecord[] = [getDid({ did: 'did:sov:123', qualifiedIndyDid: 'did:indy:local:123' })] + + mockFunction(didRepository.findByQuery).mockResolvedValue(records) + + await testModule.migrateDidRecordToV0_4(agent) + + expect(didRepository.findByQuery).toHaveBeenCalledTimes(1) + expect(didRepository.findByQuery).toHaveBeenCalledWith(agent.context, { + method: 'sov', + role: DidDocumentRole.Created, + }) + expect(didRepository.findByQuery).toHaveBeenCalledTimes(1) + + const [, didRecord] = mockFunction(didRepository.update).mock.calls[0] + expect(didRecord).toEqual({ + type: 'DidRecord', + id: expect.any(String), + did: 'did:indy:local:123', + metadata: expect.any(Metadata), + role: DidDocumentRole.Created, + _tags: { + qualifiedIndyDid: undefined, + }, + }) + }) + }) +}) + +function getDid({ did, qualifiedIndyDid }: { did: string; qualifiedIndyDid: string }) { + return JsonTransformer.fromJSON( + { + role: DidDocumentRole.Created, + id: uuid(), + did, + _tags: { + qualifiedIndyDid, + }, + }, + DidRecord + ) +} diff --git a/packages/core/src/storage/migration/updates/0.3.1-0.4/cache.ts b/packages/core/src/storage/migration/updates/0.3.1-0.4/cache.ts new file mode 100644 index 0000000000..5ee3174e3b --- /dev/null +++ b/packages/core/src/storage/migration/updates/0.3.1-0.4/cache.ts @@ -0,0 +1,32 @@ +import type { BaseAgent } from '../../../../agent/BaseAgent' +import type { StorageService } from '../../../StorageService' + +import { InjectionSymbols } from '../../../../constants' +import { BaseRecord } from '../../../BaseRecord' + +/** + * removes the all cache records as used in 0.3.0, as they have been updated to use the new cache interface. + */ +export async function migrateCacheToV0_4(agent: Agent) { + agent.config.logger.info('Removing 0.3 cache records from storage') + + const storageService = agent.dependencyManager.resolve>(InjectionSymbols.StorageService) + + agent.config.logger.debug(`Fetching all cache records`) + const records = await storageService.getAll(agent.context, CacheRecord) + + for (const record of records) { + agent.config.logger.debug(`Removing cache record with id ${record.id}`) + await storageService.deleteById(agent.context, CacheRecord, record.id) + agent.config.logger.debug(`Successfully removed cache record with id ${record.id}`) + } +} + +class CacheRecord extends BaseRecord { + public static readonly type = 'CacheRecord' + public readonly type = CacheRecord.type + + public getTags() { + return this._tags + } +} diff --git a/packages/core/src/storage/migration/updates/0.3.1-0.4/did.ts b/packages/core/src/storage/migration/updates/0.3.1-0.4/did.ts new file mode 100644 index 0000000000..f9844b8c1c --- /dev/null +++ b/packages/core/src/storage/migration/updates/0.3.1-0.4/did.ts @@ -0,0 +1,51 @@ +import type { BaseAgent } from '../../../../agent/BaseAgent' +import type { DidRecord } from '../../../../modules/dids' + +import { DidDocumentRole, DidRepository } from '../../../../modules/dids' + +/** + * Migrates the {@link DidRecord} to 0.4 compatible format. It fetches all did records from storage + * with method sov and applies the needed updates to the records. After a record has been transformed, it is updated + * in storage and the next record will be transformed. + * + * The following transformations are applied: + * - {@link migrateSovDidToIndyDid} + */ +export async function migrateDidRecordToV0_4(agent: Agent) { + agent.config.logger.info('Migrating did records to storage version 0.4') + const didRepository = agent.dependencyManager.resolve(DidRepository) + + agent.config.logger.debug(`Fetching all did records with did method did:sov from storage`) + const allSovDids = await didRepository.findByQuery(agent.context, { + method: 'sov', + role: DidDocumentRole.Created, + }) + + agent.config.logger.debug(`Found a total of ${allSovDids.length} did:sov did records to update.`) + for (const sovDidRecord of allSovDids) { + agent.config.logger.debug(`Migrating did:sov did record with id ${sovDidRecord.id} to storage version 0.4`) + + const oldDid = sovDidRecord.did + migrateSovDidToIndyDid(agent, sovDidRecord) + + // Save updated did record + await didRepository.update(agent.context, sovDidRecord) + + agent.config.logger.debug( + `Successfully migrated did:sov did record with old did ${oldDid} to new did ${sovDidRecord.did} for storage version 0.4` + ) + } +} + +export function migrateSovDidToIndyDid(agent: Agent, didRecord: DidRecord) { + agent.config.logger.debug( + `Migrating did record with id ${didRecord.id} and did ${didRecord.did} to indy did for version 0.4` + ) + + const qualifiedIndyDid = didRecord.getTag('qualifiedIndyDid') as string + + didRecord.did = qualifiedIndyDid + + // Unset qualifiedIndyDid tag + didRecord.setTag('qualifiedIndyDid', undefined) +} diff --git a/packages/core/src/storage/migration/updates/0.3.1-0.4/index.ts b/packages/core/src/storage/migration/updates/0.3.1-0.4/index.ts new file mode 100644 index 0000000000..d43b32a15f --- /dev/null +++ b/packages/core/src/storage/migration/updates/0.3.1-0.4/index.ts @@ -0,0 +1,9 @@ +import type { BaseAgent } from '../../../../agent/BaseAgent' + +import { migrateCacheToV0_4 } from './cache' +import { migrateDidRecordToV0_4 } from './did' + +export async function updateV0_3_1ToV0_4(agent: Agent): Promise { + await migrateDidRecordToV0_4(agent) + await migrateCacheToV0_4(agent) +} diff --git a/packages/core/src/transport/HttpOutboundTransport.ts b/packages/core/src/transport/HttpOutboundTransport.ts index ac3fe30212..8ff21d71da 100644 --- a/packages/core/src/transport/HttpOutboundTransport.ts +++ b/packages/core/src/transport/HttpOutboundTransport.ts @@ -1,8 +1,8 @@ +import type { OutboundTransport } from './OutboundTransport' import type { Agent } from '../agent/Agent' import type { AgentMessageReceivedEvent } from '../agent/Events' -import type { OutboundPackage } from '../didcomm/types' import type { Logger } from '../logger' -import type { OutboundTransport } from './OutboundTransport' +import type { OutboundPackage } from '../types' import type fetch from 'node-fetch' import { AbortController } from 'abort-controller' diff --git a/packages/core/src/transport/WsOutboundTransport.ts b/packages/core/src/transport/WsOutboundTransport.ts index aba28e753d..e99e555409 100644 --- a/packages/core/src/transport/WsOutboundTransport.ts +++ b/packages/core/src/transport/WsOutboundTransport.ts @@ -1,9 +1,9 @@ +import type { OutboundTransport } from './OutboundTransport' +import type { OutboundWebSocketClosedEvent, OutboundWebSocketOpenedEvent } from './TransportEventTypes' import type { Agent } from '../agent/Agent' import type { AgentMessageReceivedEvent } from '../agent/Events' import type { OutboundPackage } from '../didcomm/types' import type { Logger } from '../logger' -import type { OutboundTransport } from './OutboundTransport' -import type { OutboundWebSocketClosedEvent, OutboundWebSocketOpenedEvent } from './TransportEventTypes' import type WebSocket from 'ws' import { AgentEventTypes } from '../agent/Events' @@ -47,7 +47,7 @@ export class WsOutboundTransport implements OutboundTransport { throw new AriesFrameworkError("Missing connection or endpoint. I don't know how and where to send the message.") } - const isNewSocket = this.hasOpenSocket(endpoint) + const isNewSocket = !this.hasOpenSocket(endpoint) const socket = await this.resolveSocket({ socketId: endpoint, endpoint, connectionId }) socket.send(Buffer.from(JSON.stringify(payload))) @@ -75,7 +75,7 @@ export class WsOutboundTransport implements OutboundTransport { // If we already have a socket connection use it let socket = this.transportTable.get(socketId) - if (!socket) { + if (!socket || socket.readyState === this.WebSocketClass.CLOSING) { if (!endpoint) { throw new AriesFrameworkError(`Missing endpoint. I don't know how and where to send the message.`) } diff --git a/packages/core/src/types.ts b/packages/core/src/types.ts index 2781c1f951..25ab78aad3 100644 --- a/packages/core/src/types.ts +++ b/packages/core/src/types.ts @@ -1,8 +1,5 @@ +import type { EncryptedMessage } from './didcomm' import type { Logger } from './logger' -import type { AutoAcceptCredential } from './modules/credentials/models/CredentialAutoAcceptType' -import type { IndyPoolConfig } from './modules/ledger/IndyPool' -import type { AutoAcceptProof } from './modules/proofs' -import type { MediatorPickupStrategy } from './modules/routing' export enum KeyDerivationMethod { /** default value in indy-sdk. Will be used when no value is provided */ @@ -13,15 +10,16 @@ export enum KeyDerivationMethod { Raw = 'RAW', } +export interface WalletStorageConfig { + type: string + [key: string]: unknown +} + export interface WalletConfig { id: string key: string keyDerivationMethod?: KeyDerivationMethod - storage?: { - type: string - [key: string]: unknown - } - masterSecretId?: string + storage?: WalletStorageConfig } export interface WalletConfigRekey { @@ -45,111 +43,31 @@ export enum DidCommMimeType { export interface InitConfig { endpoints?: string[] label: string - publicDidSeed?: string walletConfig?: WalletConfig logger?: Logger didCommMimeType?: DidCommMimeType useDidKeyInProtocols?: boolean - useLegacyDidSovPrefix?: boolean + useDidSovPrefixWhereAllowed?: boolean connectionImageUrl?: string autoUpdateStorageOnStartup?: boolean - - /** - * @deprecated configure `autoAcceptConnections` on the `ConnectionsModule` class - * @note This setting will be ignored if the `ConnectionsModule` is manually configured as - * a module - */ - autoAcceptConnections?: boolean - - /** - * @deprecated configure `autoAcceptProofs` on the `ProofModule` class - * @note This setting will be ignored if the `ProofsModule` is manually configured as - * a module - */ - autoAcceptProofs?: AutoAcceptProof - - /** - * @deprecated configure `autoAcceptCredentials` on the `CredentialsModule` class - * @note This setting will be ignored if the `CredentialsModule` is manually configured as - * a module - */ - autoAcceptCredentials?: AutoAcceptCredential - - /** - * @deprecated configure `indyLedgers` on the `LedgerModule` class - * @note This setting will be ignored if the `LedgerModule` is manually configured as - * a module - */ - indyLedgers?: IndyPoolConfig[] - - /** - * @deprecated configure `connectToIndyLedgersOnStartup` on the `LedgerModule` class - * @note This setting will be ignored if the `LedgerModule` is manually configured as - * a module - */ - connectToIndyLedgersOnStartup?: boolean - - /** - * @deprecated configure `autoAcceptMediationRequests` on the `RecipientModule` class - * @note This setting will be ignored if the `RecipientModule` is manually configured as - * a module - */ - autoAcceptMediationRequests?: boolean - - /** - * @deprecated configure `mediatorConnectionsInvite` on the `RecipientModule` class - * @note This setting will be ignored if the `RecipientModule` is manually configured as - * a module - */ - mediatorConnectionsInvite?: string - - /** - * @deprecated you can use `RecipientApi.setDefaultMediator` to set the default mediator. - */ - defaultMediatorId?: string - - /** - * @deprecated you can set the `default` tag to `false` (or remove it completely) to clear the default mediator. - */ - clearDefaultMediator?: boolean - - /** - * @deprecated configure `mediatorPollingInterval` on the `RecipientModule` class - * @note This setting will be ignored if the `RecipientModule` is manually configured as - * a module - */ - mediatorPollingInterval?: number - - /** - * @deprecated configure `mediatorPickupStrategy` on the `RecipientModule` class - * @note This setting will be ignored if the `RecipientModule` is manually configured as - * a module - */ - mediatorPickupStrategy?: MediatorPickupStrategy - - /** - * @deprecated configure `maximumMessagePickup` on the `RecipientModule` class - * @note This setting will be ignored if the `RecipientModule` is manually configured as - * a module - */ - maximumMessagePickup?: number - - /** - * @deprecated configure `baseMediatorReconnectionIntervalMs` on the `RecipientModule` class - * @note This setting will be ignored if the `RecipientModule` is manually configured as - * a module - */ - baseMediatorReconnectionIntervalMs?: number - - /** - * @deprecated configure `maximumMediatorReconnectionIntervalMs` on the `RecipientModule` class - * @note This setting will be ignored if the `RecipientModule` is manually configured as - * a module - */ - maximumMediatorReconnectionIntervalMs?: number } export type ProtocolVersion = `${number}.${number}` +export interface PlaintextMessage { + '@type': string + '@id': string + '~thread'?: { + thid?: string + } + [key: string]: unknown +} + +export interface OutboundPackage { + payload: EncryptedMessage + responseRequested?: boolean + endpoint?: string + connectionId?: string +} export type JsonValue = string | number | boolean | null | JsonObject | JsonArray export type JsonArray = Array @@ -157,7 +75,6 @@ export interface JsonObject { [property: string]: JsonValue } -// Flatten an array of arrays /** * Flatten an array of arrays * @example diff --git a/packages/core/src/utils/TypedArrayEncoder.ts b/packages/core/src/utils/TypedArrayEncoder.ts index 685eac485c..b98a5350c2 100644 --- a/packages/core/src/utils/TypedArrayEncoder.ts +++ b/packages/core/src/utils/TypedArrayEncoder.ts @@ -17,7 +17,7 @@ export class TypedArrayEncoder { * * @param buffer the buffer to encode into base64url string */ - public static toBase64URL(buffer: Buffer) { + public static toBase64URL(buffer: Buffer | Uint8Array) { return base64ToBase64URL(TypedArrayEncoder.toBase64(buffer)) } @@ -48,6 +48,24 @@ export class TypedArrayEncoder { return Buffer.from(decodeFromBase58(base58)) } + /** + * Encode buffer into base64 string. + * + * @param buffer the buffer to encode into base64 string + */ + public static toHex(buffer: Buffer | Uint8Array) { + return Buffer.from(buffer).toString('hex') + } + + /** + * Decode hex string into buffer + * + * @param hex the hex string to decode into buffer format + */ + public static fromHex(hex: string) { + return Buffer.from(hex, 'hex') + } + /** * Decode string into buffer. * diff --git a/packages/core/src/utils/__tests__/did.test.ts b/packages/core/src/utils/__tests__/did.test.ts deleted file mode 100644 index 3d7aa07792..0000000000 --- a/packages/core/src/utils/__tests__/did.test.ts +++ /dev/null @@ -1,188 +0,0 @@ -import { - getIndyDidFromVerificationMethod, - isAbbreviatedVerkey, - isDid, - isDidIdentifier, - isFullVerkey, - isSelfCertifiedDid, - isVerkey, -} from '../did' - -const validAbbreviatedVerkeys = [ - '~PKAYz8Ev4yoQgr2LaMAWFx', - '~Soy1augaQrQYtNZRRHsikB', - '~BUF7uxYTxZ6qYdZ4G9e1Gi', - '~DbZ4gkBqhFRVsT5P7BJqyZ', - '~4zmNTdG78iYyMAQdEQLrf8', -] - -const invalidAbbreviatedVerkeys = [ - '6YnVN5Qdb6mqimTRQcQmSXrHXKdTEdRn5YHZReezUTvt', - '8jG2Bim1HNSybCTdKBRppP4PCQSSijx1pBnreqsdo8JG', - 'ABUF7uxYTxZ6qYdZ4G9e1Gi', - '~Db3IgkBqhFRVsT5P7BJqyZ', - '~4zmNTlG78iYyMAQdEQLrf8', -] - -const validFullVerkeys = [ - '6YnVN5Qdb6mqimTRQcQmSXrHXKdTEdRn5YHZReezUTvt', - '8jG2Bim1HNSybCTdKBRppP4PCQSSijx1pBnreqsdo8JG', - '9wMLhw9SSxtTUyosrndMbvWY4TtDbVvRnMtzG2NysniP', - '6m2XT39vivJ7tLSxNPM8siMnhYCZcdMxbkTcJDSzAQTu', - 'CAgL85iEecPNQMmxQ1hgbqczwq7SAerQ8RbWTRtC7SoK', - 'MqXmB7cTsTXqyxDPBbrgu5EPqw61kouK1qjMvnoPa96', -] - -const invalidFullVerkeys = [ - '~PKAYz8Ev4yoQgr2LaMAWFx', - '~Soy1augaQrQYtNZRRHsikB', - '6YnVN5Qdb6mqimTRQcQmSXrHXKdTEdRn5YHZReezUTvta', - '6m2XT39vIvJ7tLSxNPM8siMnhYCZcdMxbkTcJDSzAQTu', - 'CAgL85iEecPNQMlxQ1hgbqczwq7SAerQ8RbWTRtC7SoK', -] - -const invalidVerkeys = [ - '6YnVN5Qdb6mqimTIQcQmSXrHXKdTEdRn5YHZReezUTvta', - '6m2XT39vIvJ7tlSxNPM8siMnhYCZcdMxbkTcJDSzAQTu', - 'CAgL85iEecPNQMlxQ1hgbqczwq7SAerQ8RbWTRtC7SoK', - '6YnVN5Qdb6mqilTRQcQmSXrHXKdTEdRn5YHZReezUTvt', - '8jG2Bim1HNIybCTdKBRppP4PCQSSijx1pBnreqsdo8JG', - 'ABUF7uxYTxZ6qYdZ4G9e1Gi', - '~Db3IgkBqhFRVsT5P7BJqyZ', - '~4zmNTlG78IYyMAQdEQLrf8', - 'randomverkey', -] - -const validDids = [ - 'did:indy:BBPoJqRKatdcfLEAFL7exC', - 'did:sov:N8NQHLtCKfPmWMgCSdfa7h', - 'did:random:FBSegXg6AsF8J73kx22gjk', - 'did:sov:8u2b8ZH6sHeWfvphyQuHCL', - 'did:ethr:0xb9c5714089478a327f09197987f16f9e5d936e8a', - 'did:btcr:xyv2-xzpq-q9wa-p7t', -] - -const invalidDids = [ - '6YnVN5Qdb6mqimTIQcQmSXrHXKdTEdRn5YHZReezUTvta', - 'did:BBPoJqRKatdcfLEAFL7exC', - 'sov:N8NQHLtCKfPmWMgCSdfa7h', - '8kyt-fzzq-qpqq-ljsc-5l', - 'did:test1:N8NQHLtCKfPmWMgCSdfa7h', - 'deid:ethr:9noxi4nL4SiJAsFcMLp2U4', -] - -const validDidIdentifiers = [ - '8kyt-fzzq-qpqq-ljsc-5l', - 'fEMDp21GvaafC5hXLaLHf', - '9noxi4nL4SiJAsFcMLp2U4', - 'QdAJFDpbVoHYrUpNAMe3An', - 'B9Y3e8PUKrM1ShumWU36xW', - '0xf3beac30c498d9e26865f34fcaa57dbb935b0d74', -] - -const invalidDidIdentifiers = [ - '6YnVN5Qdb6mqimTIQcQmSXrHXKdTEdRn5YHZReezUTvt/a', - 'did:BBPoJqRKatdcfLEAFL7exC', - 'sov:N8NQHLtCKfPmWMgCSdfa7h', - 'did:test1:N8NQHLtCKfPmWMgCSdfa7h', - 'deid:ethr:9noxi4nL4SiJAsFcMLp2U4', -] - -const verificationMethod = { - id: 'did:key:z6MkewW1GB5V6PF4HA2rixWy3X9z6bRthrjVwXrZH74Xd7Tr#z6MkewW1GB5V6PF4HA2rixWy3X9z6bRthrjVwXrZH74Xd7Tr', - type: 'Ed25519VerificationKey2018', - controller: 'did:key:z6MkewW1GB5V6PF4HA2rixWy3X9z6bRthrjVwXrZH74Xd7Tr', - publicKeyBase58: 'VExfvq3kqkbAfCA3PZ8CRbzH2A3HyV9FWwdSq6WhtgU', -} - -const invalidVerificationMethod = [ - { - id: 'did:key:z6MkewW1GB5V6PF4HA2rixWy3X9z6bRthrjVwXrZH74Xd7Tr#z6MkewW1GB5V6PF4HA2rixWy3X9z6bRthrjVwXrZH74Xd7Tr', - type: 'Ed25519VerificationKey2018', - controller: 'did:key:z6MkewW1GB5V6PF4HA2rixWy3X9z6bRthrjVwXrZH74Xd7Tr', - publicKeyBase58: '', - }, -] - -const indyDid = 'tpF86Zd1cf9JdVmqKdMW2' - -describe('Utils | Did', () => { - describe('isSelfCertifiedDid()', () => { - test('returns true if the verkey is abbreviated', () => { - expect(isSelfCertifiedDid('PW8ZHpNupeWXbmpPWog6Ki', '~QQ5jiH1dgXPAnvHdJvazn9')).toBe(true) - }) - - test('returns true if the verkey is not abbreviated and the did is generated from the verkey', () => { - expect(isSelfCertifiedDid('Y8q4Aq6gRAcmB6jjKk3Z7t', 'HyEoPRNvC7q4jj5joUo8AWYtxbNccbEnTAeuMYkpmNS2')).toBe(true) - }) - - test('returns false if the verkey is not abbreviated and the did is not generated from the verkey', () => { - expect(isSelfCertifiedDid('Y8q4Aq6gRAcmB6jjKk3Z7t', 'AcU7DnRqoXGYATD6VqsRq4eHuz55gdM3uzFBEhFd6rGh')).toBe(false) - }) - }) - - describe('isAbbreviatedVerkey()', () => { - test.each(validAbbreviatedVerkeys)('returns true when valid abbreviated verkey "%s" is passed in', (verkey) => { - expect(isAbbreviatedVerkey(verkey)).toBe(true) - }) - - test.each(invalidAbbreviatedVerkeys)( - 'returns false when invalid abbreviated verkey "%s" is passed in', - (verkey) => { - expect(isAbbreviatedVerkey(verkey)).toBe(false) - } - ) - }) - - describe('isFullVerkey()', () => { - test.each(validFullVerkeys)('returns true when valid full verkey "%s" is passed in', (verkey) => { - expect(isFullVerkey(verkey)).toBe(true) - }) - - test.each(invalidFullVerkeys)('returns false when invalid full verkey "%s" is passed in', (verkey) => { - expect(isFullVerkey(verkey)).toBe(false) - }) - }) - - describe('isVerkey()', () => { - const validVerkeys = [...validAbbreviatedVerkeys, ...validFullVerkeys] - - test.each(validVerkeys)('returns true when valid verkey "%s" is passed in', (verkey) => { - expect(isVerkey(verkey)).toBe(true) - }) - - test.each(invalidVerkeys)('returns false when invalid verkey "%s" is passed in', (verkey) => { - expect(isVerkey(verkey)).toBe(false) - }) - }) - - describe('isDid()', () => { - test.each(validDids)('returns true when valid did "%s" is passed in', (did) => { - expect(isDid(did)).toBe(true) - }) - - test.each(invalidDids)('returns false when invalid did "%s" is passed in', (did) => { - expect(isDid(did)).toBe(false) - }) - }) - - describe('isDidIdentifier()', () => { - test.each(validDidIdentifiers)('returns true when valid did identifier "%s" is passed in', (didIdentifier) => { - expect(isDidIdentifier(didIdentifier)).toBe(true) - }) - - test.each(invalidDidIdentifiers)('returns false when invalid did identifier "%s" is passed in', (didIdentifier) => { - expect(isDidIdentifier(didIdentifier)).toBe(false) - }) - }) - - describe('getIndyDidFromVerificationMethod()', () => { - expect(getIndyDidFromVerificationMethod(verificationMethod)).toBe(indyDid) - - test.each(invalidVerificationMethod)('throw error when invalid public key in verification method', (method) => { - expect(() => { - getIndyDidFromVerificationMethod(method) - }).toThrow() - }) - }) -}) diff --git a/packages/core/src/utils/__tests__/indyError.test.ts b/packages/core/src/utils/__tests__/indyError.test.ts deleted file mode 100644 index 154b99146d..0000000000 --- a/packages/core/src/utils/__tests__/indyError.test.ts +++ /dev/null @@ -1,52 +0,0 @@ -import { isIndyError } from '../indyError' - -describe('isIndyError()', () => { - it('should return true when the name is "IndyError" and no errorName is passed', () => { - const error = { name: 'IndyError' } - - expect(isIndyError(error)).toBe(true) - }) - - it('should return false when the name is not "IndyError"', () => { - const error = { name: 'IndyError2' } - - expect(isIndyError(error)).toBe(false) - expect(isIndyError(error, 'WalletAlreadyExistsError')).toBe(false) - }) - - it('should return true when indyName matches the passed errorName', () => { - const error = { name: 'IndyError', indyName: 'WalletAlreadyExistsError' } - - expect(isIndyError(error, 'WalletAlreadyExistsError')).toBe(true) - }) - - it('should return false when the indyName does not match the passes errorName', () => { - const error = { name: 'IndyError', indyName: 'WalletAlreadyExistsError' } - - // @ts-expect-error not a valid error name - expect(isIndyError(error, 'DoesNotMatchError')).toBe(false) - }) - - // Below here are temporary until indy-sdk releases new version - it('should return true when the indyName is missing but the message contains a matching error code', () => { - const error = { name: 'IndyError', message: '212' } - - expect(isIndyError(error, 'WalletItemNotFound')).toBe(true) - }) - - it('should return false when the indyName is missing and the message contains a valid but not matching error code', () => { - const error = { name: 'IndyError', message: '212' } - - // @ts-expect-error not a valid error name - expect(isIndyError(error, 'DoesNotMatchError')).toBe(false) - }) - - it('should throw an error when the indyName is missing and the message contains an invalid error code', () => { - const error = { name: 'IndyError', message: '832882' } - - // @ts-expect-error not a valid error name - expect(() => isIndyError(error, 'SomeNewErrorWeDoNotHave')).toThrowError( - 'Could not determine errorName of indyError 832882' - ) - }) -}) diff --git a/packages/core/src/utils/__tests__/indyIdentifiers.test.ts b/packages/core/src/utils/__tests__/indyIdentifiers.test.ts deleted file mode 100644 index 8da274a789..0000000000 --- a/packages/core/src/utils/__tests__/indyIdentifiers.test.ts +++ /dev/null @@ -1,62 +0,0 @@ -import { - isQualifiedIndyIdentifier, - getQualifiedIndyCredentialDefinitionId, - getQualifiedIndySchemaId, - getLegacyCredentialDefinitionId, - getLegacySchemaId, -} from '../indyIdentifiers' - -const indyNamespace = 'some:staging' -const did = 'q7ATwTYbQDgiigVijUAej' -const qualifiedSchemaId = `did:indy:${indyNamespace}:${did}/anoncreds/v0/SCHEMA/awesomeSchema/4.2.0` -const qualifiedCredentialDefinitionId = `did:indy:${indyNamespace}:${did}/anoncreds/v0/CLAIM_DEF/99/sth` -const unqualifiedSchemaId = `${did}:2:awesomeSchema:4.2.0` -const unqualifiedCredentialDefinitionId = `${did}:3:CL:99:sth` - -describe('Mangle indy identifiers', () => { - test('is a qualified identifier', async () => { - expect(isQualifiedIndyIdentifier(qualifiedSchemaId)).toBe(true) - }) - - test('is NOT a qualified identifier', async () => { - expect(isQualifiedIndyIdentifier(did)).toBe(false) - }) - - describe('get the qualified identifier', () => { - it('should return the qualified identifier if the identifier is already qualified', () => { - expect(getQualifiedIndyCredentialDefinitionId(indyNamespace, qualifiedCredentialDefinitionId)).toBe( - qualifiedCredentialDefinitionId - ) - }) - - it('should return the qualified identifier for a credential definition', () => { - expect(getQualifiedIndyCredentialDefinitionId(indyNamespace, unqualifiedCredentialDefinitionId)).toBe( - qualifiedCredentialDefinitionId - ) - }) - - it('should return the qualified identifier for a schema', () => { - expect(getQualifiedIndySchemaId(indyNamespace, qualifiedSchemaId)).toBe(qualifiedSchemaId) - }) - - it('should return the qualified identifier for a schema', () => { - expect(getQualifiedIndySchemaId(indyNamespace, unqualifiedSchemaId)).toBe(qualifiedSchemaId) - }) - }) - - // generateSchemaId - it('Should return a valid schema ID given did name and version', () => { - const did = '12345', - name = 'backbench', - version = '420' - expect(getLegacySchemaId(did, name, version)).toEqual('12345:2:backbench:420') - }) - - // generateCredentialDefinitionId - it('Should return a valid schema ID given did name and version', () => { - const did = '12345', - seqNo = 420, - tag = 'someTag' - expect(getLegacyCredentialDefinitionId(did, seqNo, tag)).toEqual('12345:3:CL:420:someTag') - }) -}) diff --git a/packages/core/src/utils/__tests__/indyProofRequest.test.ts b/packages/core/src/utils/__tests__/indyProofRequest.test.ts deleted file mode 100644 index 54a275b524..0000000000 --- a/packages/core/src/utils/__tests__/indyProofRequest.test.ts +++ /dev/null @@ -1,80 +0,0 @@ -import { AriesFrameworkError } from '../../error' -import { - AttributeFilter, - PredicateType, - ProofAttributeInfo, - ProofPredicateInfo, - ProofRequest, -} from '../../modules/proofs' -import { checkProofRequestForDuplicates } from '../indyProofRequest' - -describe('Present Proof', () => { - const credDefId = '9vPXgSpQJPkJEALbLXueBp:3:CL:57753:tag1' - const nonce = 'testtesttest12345' - - test('attribute names match', () => { - const attributes = { - age1: new ProofAttributeInfo({ - name: 'age', - restrictions: [ - new AttributeFilter({ - credentialDefinitionId: credDefId, - }), - ], - }), - age2: new ProofAttributeInfo({ - name: 'age', - restrictions: [ - new AttributeFilter({ - credentialDefinitionId: credDefId, - }), - ], - }), - } - - const proofRequest = new ProofRequest({ - name: 'proof-request', - version: '1.0', - nonce, - requestedAttributes: attributes, - }) - - expect(() => checkProofRequestForDuplicates(proofRequest)).not.toThrow() - }) - - test('attribute names match with predicates name', () => { - const attributes = { - attrib: new ProofAttributeInfo({ - name: 'age', - restrictions: [ - new AttributeFilter({ - credentialDefinitionId: credDefId, - }), - ], - }), - } - - const predicates = { - predicate: new ProofPredicateInfo({ - name: 'age', - predicateType: PredicateType.GreaterThanOrEqualTo, - predicateValue: 50, - restrictions: [ - new AttributeFilter({ - credentialDefinitionId: credDefId, - }), - ], - }), - } - - const proofRequest = new ProofRequest({ - name: 'proof-request', - version: '1.0', - nonce, - requestedAttributes: attributes, - requestedPredicates: predicates, - }) - - expect(() => checkProofRequestForDuplicates(proofRequest)).toThrowError(AriesFrameworkError) - }) -}) diff --git a/packages/core/src/utils/__tests__/regex.test.ts b/packages/core/src/utils/__tests__/regex.test.ts deleted file mode 100644 index 93cbaa7ae8..0000000000 --- a/packages/core/src/utils/__tests__/regex.test.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { credDefIdRegex, indyDidRegex, schemaIdRegex, schemaVersionRegex } from '../regex' - -describe('Valid Regular Expression', () => { - const invalidTest = 'test' - - test('test for credDefIdRegex', async () => { - const test = 'q7ATwTYbQDgiigVijUAej:3:CL:160971:1.0.0' - expect(test).toMatch(credDefIdRegex) - expect(credDefIdRegex.test(invalidTest)).toBeFalsy() - }) - - test('test for indyDidRegex', async () => { - const test = 'did:sov:q7ATwTYbQDgiigVijUAej' - expect(test).toMatch(indyDidRegex) - expect(indyDidRegex.test(invalidTest)).toBeFalsy - }) - - test('test for schemaIdRegex', async () => { - const test = 'q7ATwTYbQDgiigVijUAej:2:test:1.0' - expect(test).toMatch(schemaIdRegex) - expect(schemaIdRegex.test(invalidTest)).toBeFalsy - }) - - test('test for schemaVersionRegex', async () => { - const test = '1.0.0' - expect(test).toMatch(schemaVersionRegex) - expect(schemaVersionRegex.test(invalidTest)).toBeFalsy - }) -}) diff --git a/packages/core/src/utils/attachment.ts b/packages/core/src/utils/attachment.ts index 0d34830af3..970b199ee5 100644 --- a/packages/core/src/utils/attachment.ts +++ b/packages/core/src/utils/attachment.ts @@ -1,5 +1,5 @@ -import type { V1Attachment } from '../decorators/attachment/V1Attachment' import type { BaseName } from './MultiBaseEncoder' +import type { V1Attachment } from '../decorators/attachment/V1Attachment' import { AriesFrameworkError } from '../error/AriesFrameworkError' diff --git a/packages/core/src/utils/did.ts b/packages/core/src/utils/did.ts index 74f346560d..d21405252d 100644 --- a/packages/core/src/utils/did.ts +++ b/packages/core/src/utils/did.ts @@ -1,53 +1,4 @@ -/** - * Based on DidUtils implementation in Aries Framework .NET - * @see: https://github.com/hyperledger/aries-framework-dotnet/blob/f90eaf9db8548f6fc831abea917e906201755763/src/Hyperledger.Aries/Utils/DidUtils.cs - * - * Some context about full verkeys versus abbreviated verkeys: - * A standard verkey is 32 bytes, and by default in Indy the DID is chosen as the first 16 bytes of that key, before base58 encoding. - * An abbreviated verkey replaces the first 16 bytes of the verkey with ~ when it matches the DID. - * - * When a full verkey is used to register on the ledger, this is stored as a full verkey on the ledger and also returned from the ledger as a full verkey. - * The same applies to an abbreviated verkey. If an abbreviated verkey is used to register on the ledger, this is stored as an abbreviated verkey on the ledger and also returned from the ledger as an abbreviated verkey. - * - * For this reason we need some methods to check whether verkeys are full or abbreviated, so we can align this with `indy.abbreviateVerkey` - * - * Aries Framework .NET also abbreviates verkey before sending to ledger: - * https://github.com/hyperledger/aries-framework-dotnet/blob/f90eaf9db8548f6fc831abea917e906201755763/src/Hyperledger.Aries/Ledger/DefaultLedgerService.cs#L139-L147 - */ - -import type { VerificationMethod } from './../modules/dids/domain/verificationMethod/VerificationMethod' - import { TypedArrayEncoder } from './TypedArrayEncoder' -import { Buffer } from './buffer' - -export const FULL_VERKEY_REGEX = /^[1-9A-HJ-NP-Za-km-z]{43,44}$/ -export const ABBREVIATED_VERKEY_REGEX = /^~[1-9A-HJ-NP-Za-km-z]{21,22}$/ -export const VERKEY_REGEX = new RegExp(`${FULL_VERKEY_REGEX.source}|${ABBREVIATED_VERKEY_REGEX.source}`) -export const DID_REGEX = /^did:([a-z]+):([a-zA-z\d]+)/ -export const DID_IDENTIFIER_REGEX = /^[a-zA-z\d-]+$/ - -/** - * Check whether the did is a self certifying did. If the verkey is abbreviated this method - * will always return true. Make sure that the verkey you pass in this method belongs to the - * did passed in - * - * @return Boolean indicating whether the did is self certifying - */ -export function isSelfCertifiedDid(did: string, verkey: string): boolean { - // If the verkey is Abbreviated, it means the full verkey - // is the did + the verkey - if (isAbbreviatedVerkey(verkey)) { - return true - } - - const didFromVerkey = indyDidFromPublicKeyBase58(verkey) - - if (didFromVerkey === did) { - return true - } - - return false -} export function indyDidFromPublicKeyBase58(publicKeyBase58: string): string { const buffer = TypedArrayEncoder.fromBase58(publicKeyBase58) @@ -56,108 +7,3 @@ export function indyDidFromPublicKeyBase58(publicKeyBase58: string): string { return did } - -export function getFullVerkey(did: string, verkey: string) { - if (isFullVerkey(verkey)) return verkey - - // Did could have did:xxx prefix, only take the last item after : - const id = did.split(':').pop() ?? did - // Verkey is prefixed with ~ if abbreviated - const verkeyWithoutTilde = verkey.slice(1) - - // Create base58 encoded public key (32 bytes) - return TypedArrayEncoder.toBase58( - Buffer.concat([ - // Take did identifier (16 bytes) - TypedArrayEncoder.fromBase58(id), - // Concat the abbreviated verkey (16 bytes) - TypedArrayEncoder.fromBase58(verkeyWithoutTilde), - ]) - ) -} - -/** - * Extract did from schema id - */ -export function didFromSchemaId(schemaId: string) { - const [did] = schemaId.split(':') - - return did -} - -/** - * Extract did from credential definition id - */ -export function didFromCredentialDefinitionId(credentialDefinitionId: string) { - const [did] = credentialDefinitionId.split(':') - - return did -} - -/** - * Extract did from revocation registry definition id - */ -export function didFromRevocationRegistryDefinitionId(revocationRegistryId: string) { - const [did] = revocationRegistryId.split(':') - - return did -} - -/** - * Check a base58 encoded string against a regex expression to determine if it is a full valid verkey - * @param verkey Base58 encoded string representation of a verkey - * @return Boolean indicating if the string is a valid verkey - */ -export function isFullVerkey(verkey: string): boolean { - return FULL_VERKEY_REGEX.test(verkey) -} - -/** - * Check a base58 encoded string against a regex expression to determine if it is a valid abbreviated verkey - * @param verkey Base58 encoded string representation of an abbreviated verkey - * @returns Boolean indicating if the string is a valid abbreviated verkey - */ -export function isAbbreviatedVerkey(verkey: string): boolean { - return ABBREVIATED_VERKEY_REGEX.test(verkey) -} - -/** - * Check a base58 encoded string to determine if it is a valid verkey - * @param verkey Base58 encoded string representation of a verkey - * @returns Boolean indicating if the string is a valid verkey - */ -export function isVerkey(verkey: string): boolean { - return VERKEY_REGEX.test(verkey) -} - -/** - * Check a string to determine if it is a valid did - * @param did - * @return Boolean indicating if the string is a valid did - */ -export function isDid(did: string): boolean { - return DID_REGEX.test(did) -} - -/** - * Check a string to determine if it is a valid did identifier. - * @param identifier Did identifier. This is a did without the did:method part - * @return Boolean indicating if the string is a valid did identifier - */ -export function isDidIdentifier(identifier: string): boolean { - return DID_IDENTIFIER_REGEX.test(identifier) -} - -/** - * Get indy did from verification method - * @param verificationMethod - * @returns indy did - */ -export function getIndyDidFromVerificationMethod(verificationMethod: VerificationMethod): string { - if (!verificationMethod?.publicKeyBase58) { - throw new Error(`Unable to get publicKeyBase58 from verification method`) - } - const buffer = TypedArrayEncoder.fromBase58(verificationMethod.publicKeyBase58) - const did = TypedArrayEncoder.toBase58(buffer.slice(0, 16)) - return did -} diff --git a/packages/core/src/utils/index.ts b/packages/core/src/utils/index.ts index c0029a7764..b913a878cb 100644 --- a/packages/core/src/utils/index.ts +++ b/packages/core/src/utils/index.ts @@ -6,12 +6,10 @@ export * from './buffer' export * from './MultiHashEncoder' export * from './JWS' export * from './JWE' -export * from './regex' -export * from './indyProofRequest' export * from './VarintEncoder' export * from './Hasher' export * from './validators' export * from './type' -export * from './indyIdentifiers' export * from './deepEquality' export * from './objectEquality' +export * from './MessageValidator' diff --git a/packages/core/src/utils/indyIdentifiers.ts b/packages/core/src/utils/indyIdentifiers.ts deleted file mode 100644 index 0d6343a3e7..0000000000 --- a/packages/core/src/utils/indyIdentifiers.ts +++ /dev/null @@ -1,53 +0,0 @@ -/** - * - * @see For the definitions below see also: https://hyperledger.github.io/indy-did-method/#indy-did-method-identifiers - * - */ -export type Did = 'did' -export type DidIndyMethod = 'indy' -// Maybe this can be typed more strictly than string. Choosing string for now as this can be eg just `sovrin` or eg `sovrin:staging` -export type DidIndyNamespace = string -// NOTE: because of the ambiguous nature - whether there is a colon or not within DidIndyNamespace this is the substring after the ***last*** colon -export type NamespaceIdentifier = string - -// TODO: This template literal type can possibly be improved. This version leaves the substrings as potentially undefined -export type IndyNamespace = `${Did}:${DidIndyMethod}:${DidIndyNamespace}:${NamespaceIdentifier}` - -export function isQualifiedIndyIdentifier(identifier: string | undefined): identifier is IndyNamespace { - if (!identifier || identifier === '') return false - return identifier.startsWith('did:indy:') -} - -export function getQualifiedIndyCredentialDefinitionId( - indyNamespace: string, - unqualifiedCredentialDefinitionId: string -): IndyNamespace { - if (isQualifiedIndyIdentifier(unqualifiedCredentialDefinitionId)) return unqualifiedCredentialDefinitionId - - // 5nDyJVP1NrcPAttP3xwMB9:3:CL:56495:npbd - const [did, , , seqNo, tag] = unqualifiedCredentialDefinitionId.split(':') - - return `did:indy:${indyNamespace}:${did}/anoncreds/v0/CLAIM_DEF/${seqNo}/${tag}` -} - -/** - * - * @see https://hyperledger.github.io/indy-did-method/#schema - * - */ -export function getQualifiedIndySchemaId(indyNamespace: string, schemaId: string): IndyNamespace { - if (isQualifiedIndyIdentifier(schemaId)) return schemaId - - // F72i3Y3Q4i466efjYJYCHM:2:npdb:4.3.4 - const [did, , schemaName, schemaVersion] = schemaId.split(':') - - return `did:indy:${indyNamespace}:${did}/anoncreds/v0/SCHEMA/${schemaName}/${schemaVersion}` -} - -export function getLegacySchemaId(unqualifiedDid: string, name: string, version: string) { - return `${unqualifiedDid}:2:${name}:${version}` -} - -export function getLegacyCredentialDefinitionId(unqualifiedDid: string, seqNo: number, tag: string) { - return `${unqualifiedDid}:3:CL:${seqNo}:${tag}` -} diff --git a/packages/core/src/utils/indyProofRequest.ts b/packages/core/src/utils/indyProofRequest.ts deleted file mode 100644 index 853bf4f742..0000000000 --- a/packages/core/src/utils/indyProofRequest.ts +++ /dev/null @@ -1,32 +0,0 @@ -import type { ProofRequest } from '../modules/proofs/formats/indy/models/ProofRequest' - -import { AriesFrameworkError } from '../error/AriesFrameworkError' - -function attributeNamesToArray(proofRequest: ProofRequest) { - // Attributes can contain either a `name` string value or an `names` string array. We reduce it to a single array - // containing all attribute names from the requested attributes. - return Array.from(proofRequest.requestedAttributes.values()).reduce( - (names, a) => [...names, ...(a.name ? [a.name] : a.names ? a.names : [])], - [] - ) -} - -function predicateNamesToArray(proofRequest: ProofRequest) { - return Array.from(new Set(Array.from(proofRequest.requestedPredicates.values()).map((a) => a.name))) -} - -function assertNoDuplicates(predicates: string[], attributeNames: string[]) { - const duplicates = predicates.filter((item) => attributeNames.indexOf(item) !== -1) - if (duplicates.length > 0) { - throw new AriesFrameworkError( - `The proof request contains duplicate predicates and attributes: ${duplicates.toString()}` - ) - } -} - -// TODO: This is still not ideal. The requested groups can specify different credentials using restrictions. -export function checkProofRequestForDuplicates(proofRequest: ProofRequest) { - const attributes = attributeNamesToArray(proofRequest) - const predicates = predicateNamesToArray(proofRequest) - assertNoDuplicates(predicates, attributes) -} diff --git a/packages/core/src/utils/messageType.ts b/packages/core/src/utils/messageType.ts index 7430f67365..84a3602e32 100644 --- a/packages/core/src/utils/messageType.ts +++ b/packages/core/src/utils/messageType.ts @@ -1,5 +1,5 @@ -import type { PlaintextMessage } from '../didcomm/types' import type { VersionString } from './version' +import type { PlaintextMessage } from '../didcomm/types' import type { ValidationOptions, ValidationArguments } from 'class-validator' import { ValidateBy, buildMessage } from 'class-validator' diff --git a/packages/core/src/utils/regex.ts b/packages/core/src/utils/regex.ts deleted file mode 100644 index 629be026df..0000000000 --- a/packages/core/src/utils/regex.ts +++ /dev/null @@ -1,4 +0,0 @@ -export const schemaIdRegex = /^[a-zA-Z0-9]{21,22}:2:.+:[0-9.]+$/ -export const schemaVersionRegex = /^(\d+\.)?(\d+\.)?(\*|\d+)$/ -export const credDefIdRegex = /^([a-zA-Z0-9]{21,22}):3:CL:(([1-9][0-9]*)|([a-zA-Z0-9]{21,22}:2:.+:[0-9.]+)):(.+)?$/ -export const indyDidRegex = /^(did:sov:)?[a-zA-Z0-9]{21,22}$/ diff --git a/packages/core/src/utils/transformers.ts b/packages/core/src/utils/transformers.ts index eb6dea844a..005f0065da 100644 --- a/packages/core/src/utils/transformers.ts +++ b/packages/core/src/utils/transformers.ts @@ -6,45 +6,6 @@ import { DateTime } from 'luxon' import { Metadata } from '../storage/Metadata' -import { JsonTransformer } from './JsonTransformer' - -/** - * Decorator that transforms json to and from corresponding record. - * - * @example - * class Example { - * RecordTransformer(Service) - * private services: Record; - * } - */ -// eslint-disable-next-line @typescript-eslint/no-explicit-any -export function RecordTransformer(Class: { new (...args: any[]): T }) { - return Transform(({ value, type }) => { - switch (type) { - case TransformationType.CLASS_TO_PLAIN: - return Object.entries(value).reduce( - (accumulator, [key, attribute]) => ({ - ...accumulator, - [key]: JsonTransformer.toJSON(attribute), - }), - {} - ) - - case TransformationType.PLAIN_TO_CLASS: - return Object.entries(value).reduce( - (accumulator, [key, attribute]) => ({ - ...accumulator, - [key]: JsonTransformer.fromJSON(attribute, Class), - }), - {} - ) - - default: - return value - } - }) -} - /* * Decorator that transforms to and from a metadata instance. */ diff --git a/packages/core/src/utils/type.ts b/packages/core/src/utils/type.ts index 2155975323..064ca0ce75 100644 --- a/packages/core/src/utils/type.ts +++ b/packages/core/src/utils/type.ts @@ -4,10 +4,6 @@ export type SingleOrArray = T | T[] export type Optional = Pick, K> & Omit -export const isString = (value: unknown): value is string => typeof value === 'string' -export const isNumber = (value: unknown): value is number => typeof value === 'number' -export const isBoolean = (value: unknown): value is boolean => typeof value === 'boolean' - export const isJsonObject = (value: unknown): value is JsonObject => { return value !== undefined && typeof value === 'object' && value !== null && !Array.isArray(value) } diff --git a/packages/core/src/utils/uuid.ts b/packages/core/src/utils/uuid.ts index 77a29fd525..a6dd97a7f2 100644 --- a/packages/core/src/utils/uuid.ts +++ b/packages/core/src/utils/uuid.ts @@ -1,5 +1,9 @@ -import { v4 } from 'uuid' +import { v4, validate } from 'uuid' export function uuid() { return v4() } + +export function isValidUuid(id: string) { + return validate(id) +} diff --git a/packages/core/src/utils/validators.ts b/packages/core/src/utils/validators.ts index 8e7240b5f2..57b1a1ec17 100644 --- a/packages/core/src/utils/validators.ts +++ b/packages/core/src/utils/validators.ts @@ -69,7 +69,7 @@ export const UriValidator = /\w+:(\/?\/?)[^\s]+/ export function IsUri(validationOptions?: ValidationOptions): PropertyDecorator { return ValidateBy( { - name: 'isInstanceOrArrayOfInstances', + name: 'isUri', validator: { validate: (value): boolean => { return UriValidator.test(value) diff --git a/packages/core/src/utils/version.ts b/packages/core/src/utils/version.ts index 82a9597909..241ccbd838 100644 --- a/packages/core/src/utils/version.ts +++ b/packages/core/src/utils/version.ts @@ -7,8 +7,8 @@ export function parseVersionString(version: VersionString): Version { export function isFirstVersionHigherThanSecond(first: Version, second: Version) { return ( first[0] > second[0] || - (first[0] == second[0] && first[1] > second[1]) || - (first[0] == second[0] && first[1] == second[1] && first[2] > second[2]) + (first[0] === second[0] && first[1] > second[1]) || + (first[0] === second[0] && first[1] === second[1] && first[2] > second[2]) ) } diff --git a/packages/core/src/wallet/IndyWallet.test.ts b/packages/core/src/wallet/IndyWallet.test.ts deleted file mode 100644 index db767ca25a..0000000000 --- a/packages/core/src/wallet/IndyWallet.test.ts +++ /dev/null @@ -1,125 +0,0 @@ -import type { WalletConfig } from '../types' - -import { SIGNATURE_LENGTH as ED25519_SIGNATURE_LENGTH } from '@stablelib/ed25519' - -import { agentDependencies } from '../../tests/helpers' -import testLogger from '../../tests/logger' -import { KeyType } from '../crypto' -import { KeyProviderRegistry } from '../crypto/key-provider' -import { KeyDerivationMethod } from '../types' -import { TypedArrayEncoder } from '../utils' - -import { IndyWallet } from './IndyWallet' -import { WalletError } from './error' - -// use raw key derivation method to speed up wallet creating / opening / closing between tests -const walletConfig: WalletConfig = { - id: 'Wallet: IndyWalletTest', - // generated using indy.generateWalletKey - key: 'CwNJroKHTSSj3XvE7ZAnuKiTn2C4QkFvxEqfm5rzhNrb', - keyDerivationMethod: KeyDerivationMethod.Raw, -} - -const walletConfigWithMasterSecretId: WalletConfig = { - id: 'Wallet: WalletTestWithMasterSecretId', - // generated using indy.generateWalletKey - key: 'CwNJroKHTSSj3XvE7ZAnuKiTn2C4QkFvxEqfm5rzhNrb', - keyDerivationMethod: KeyDerivationMethod.Raw, - masterSecretId: 'customMasterSecretId', -} - -describe('IndyWallet', () => { - let indyWallet: IndyWallet - - const seed = 'sample-seed' - const message = TypedArrayEncoder.fromString('sample-message') - - beforeEach(async () => { - indyWallet = new IndyWallet(agentDependencies, testLogger, new KeyProviderRegistry([])) - await indyWallet.createAndOpen(walletConfig) - }) - - afterEach(async () => { - await indyWallet.delete() - }) - - test('Get the public DID', async () => { - await indyWallet.initPublicDid({ seed: '000000000000000000000000Trustee9' }) - expect(indyWallet.publicDid).toMatchObject({ - did: expect.any(String), - verkey: expect.any(String), - }) - }) - - test('Get the Master Secret', () => { - expect(indyWallet.masterSecretId).toEqual('Wallet: IndyWalletTest') - }) - - test('Get the wallet handle', () => { - expect(indyWallet.handle).toEqual(expect.any(Number)) - }) - - test('Initializes a public did', async () => { - await indyWallet.initPublicDid({ seed: '00000000000000000000000Forward01' }) - - expect(indyWallet.publicDid).toEqual({ - did: 'DtWRdd6C5dN5vpcN6XRAvu', - verkey: '82RBSn3heLgXzZd74UsMC8Q8YRfEEhQoAM7LUqE6bevJ', - }) - }) - - test('Generate Nonce', async () => { - await expect(indyWallet.generateNonce()).resolves.toEqual(expect.any(String)) - }) - - test('Create ed25519 keypair', async () => { - await expect( - indyWallet.createKey({ seed: '2103de41b4ae37e8e28586d84a342b67', keyType: KeyType.Ed25519 }) - ).resolves.toMatchObject({ - keyType: KeyType.Ed25519, - }) - }) - - test('Fail to create x25519 keypair', async () => { - await expect(indyWallet.createKey({ seed, keyType: KeyType.X25519 })).rejects.toThrowError(WalletError) - }) - - test('Create a signature with a ed25519 keypair', async () => { - const ed25519Key = await indyWallet.createKey({ keyType: KeyType.Ed25519 }) - const signature = await indyWallet.sign({ - data: message, - key: ed25519Key, - }) - expect(signature.length).toStrictEqual(ED25519_SIGNATURE_LENGTH) - }) - - test('Verify a signed message with a ed25519 publicKey', async () => { - const ed25519Key = await indyWallet.createKey({ keyType: KeyType.Ed25519 }) - const signature = await indyWallet.sign({ - data: message, - key: ed25519Key, - }) - await expect(indyWallet.verify({ key: ed25519Key, data: message, signature })).resolves.toStrictEqual(true) - }) - - test('masterSecretId is equal to wallet ID by default', async () => { - expect(indyWallet.masterSecretId).toEqual(walletConfig.id) - }) -}) - -describe('IndyWallet with custom Master Secret Id', () => { - let indyWallet: IndyWallet - - beforeEach(async () => { - indyWallet = new IndyWallet(agentDependencies, testLogger, new KeyProviderRegistry([])) - await indyWallet.createAndOpen(walletConfigWithMasterSecretId) - }) - - afterEach(async () => { - await indyWallet.delete() - }) - - test('masterSecretId is set by config', async () => { - expect(indyWallet.masterSecretId).toEqual(walletConfigWithMasterSecretId.masterSecretId) - }) -}) diff --git a/packages/core/src/wallet/Wallet.ts b/packages/core/src/wallet/Wallet.ts index 2163c1ba1d..5d78c083df 100644 --- a/packages/core/src/wallet/Wallet.ts +++ b/packages/core/src/wallet/Wallet.ts @@ -1,18 +1,10 @@ -import type { Key, KeyType, KeyPair } from '../crypto' -import type { EncryptedMessage, PlaintextMessage } from '../didcomm/types' +import type { Key, KeyType } from '../crypto' +import type { EncryptedMessage, PlaintextMessage, EnvelopeType, DidCommMessageVersion } from '../didcomm/types' import type { Disposable } from '../plugins' import type { WalletConfig, WalletConfigRekey, WalletExportImportConfig } from '../types' import type { Buffer } from '../utils/buffer' export interface Wallet extends Disposable { - /** - * @deprecated The public did functionality of the wallet has been deprecated in favour of the DidsModule, which can be - * used to create and resolve dids. Currently the global agent public did functionality is still used by the `LedgerModule`, but - * will be removed once the `LedgerModule` has been deprecated. Do not use this property for new functionality, but rather - * use the `DidsModule`. - */ - publicDid: DidInfo | undefined - isInitialized: boolean isProvisioned: boolean @@ -22,36 +14,41 @@ export interface Wallet extends Disposable { rotateKey(walletConfig: WalletConfigRekey): Promise close(): Promise delete(): Promise + + /** + * Export the wallet to a file at the given path and encrypt it with the given key. + * + * @throws {WalletExportPathExistsError} When the export path already exists + */ export(exportConfig: WalletExportImportConfig): Promise import(walletConfig: WalletConfig, importConfig: WalletExportImportConfig): Promise + /** + * Create a key with an optional private key and keyType. + * + * @param options.privateKey Buffer Private key (formerly called 'seed') + * @param options.keyType KeyType the type of key that should be created + * + * @returns a `Key` instance + * + * @throws {WalletError} When an unsupported keytype is requested + * @throws {WalletError} When the key could not be created + * @throws {WalletKeyExistsError} When the key already exists in the wallet + */ createKey(options: WalletCreateKeyOptions): Promise sign(options: WalletSignOptions): Promise verify(options: WalletVerifyOptions): Promise - /** - * @deprecated The public did functionality of the wallet has been deprecated in favour of the DidsModule, which can be - * used to create and resolve dids. Currently the global agent public did functionality is still used by the `LedgerModule`, but - * will be removed once the `LedgerModule` has been deprecated. Do not use this property for new functionality, but rather - * use the `DidsModule`. - */ - initPublicDid(didConfig: DidConfig): Promise - - pack(payload: unknown, recipientKeys: string[], senderVerkey?: string): Promise + pack(payload: Record, params: WalletPackOptions): Promise unpack(encryptedMessage: EncryptedMessage): Promise generateNonce(): Promise generateWalletKey(): Promise - retrieveKeyPair(keyId: string): Promise -} - -export interface DidInfo { - did: string - verkey: string } export interface WalletCreateKeyOptions { keyType: KeyType - seed?: string + seed?: Buffer + privateKey?: Buffer } export interface WalletSignOptions { @@ -65,12 +62,16 @@ export interface WalletVerifyOptions { signature: Buffer } -export interface DidConfig { - seed?: string -} - export interface UnpackedMessageContext { + didCommVersion: DidCommMessageVersion plaintextMessage: PlaintextMessage - senderKey?: string - recipientKey?: string + senderKey?: Key + recipientKey?: Key +} + +export type WalletPackOptions = { + didCommVersion: DidCommMessageVersion + recipientKeys: Key[] + senderKey?: Key | null + envelopeType?: EnvelopeType } diff --git a/packages/core/src/wallet/WalletApi.ts b/packages/core/src/wallet/WalletApi.ts index 548df623cf..ac987b1c75 100644 --- a/packages/core/src/wallet/WalletApi.ts +++ b/packages/core/src/wallet/WalletApi.ts @@ -1,5 +1,5 @@ +import type { Wallet, WalletCreateKeyOptions } from './Wallet' import type { WalletConfig, WalletConfigRekey, WalletExportImportConfig } from '../types' -import type { Wallet } from './Wallet' import { AgentContext } from '../agent' import { InjectionSymbols } from '../constants' @@ -116,4 +116,21 @@ export class WalletApi { public async import(walletConfig: WalletConfig, importConfig: WalletExportImportConfig): Promise { await this.wallet.import(walletConfig, importConfig) } + + /** + * Create a key for and store it in the wallet. You can optionally provide a `privateKey` + * or `seed` for deterministic key generation. + * + * @param privateKey Buffer Private key (formerly called 'seed') + * @param seed Buffer (formerly called 'seed') + * @param keyType KeyType the type of key that should be created + * + * @returns a `Key` instance + * + * @throws {WalletError} When an unsupported `KeyType` is provided + * @throws {WalletError} When the key could not be created + */ + public async createKey(options: WalletCreateKeyOptions) { + return this.wallet.createKey(options) + } } diff --git a/packages/core/src/wallet/error/WalletDuplicateError.ts b/packages/core/src/wallet/error/WalletDuplicateError.ts index d7aa80c1c0..615b2563bb 100644 --- a/packages/core/src/wallet/error/WalletDuplicateError.ts +++ b/packages/core/src/wallet/error/WalletDuplicateError.ts @@ -1,6 +1,6 @@ -import { AriesFrameworkError } from '../../error/AriesFrameworkError' +import { WalletError } from './WalletError' -export class WalletDuplicateError extends AriesFrameworkError { +export class WalletDuplicateError extends WalletError { public constructor(message: string, { walletType, cause }: { walletType: string; cause?: Error }) { super(`${walletType}: ${message}`, { cause }) } diff --git a/packages/core/src/wallet/error/WalletExportPathExistsError.ts b/packages/core/src/wallet/error/WalletExportPathExistsError.ts new file mode 100644 index 0000000000..cf46e028e7 --- /dev/null +++ b/packages/core/src/wallet/error/WalletExportPathExistsError.ts @@ -0,0 +1,7 @@ +import { WalletError } from './WalletError' + +export class WalletExportPathExistsError extends WalletError { + public constructor(message: string, { cause }: { cause?: Error } = {}) { + super(message, { cause }) + } +} diff --git a/packages/core/src/wallet/error/WalletInvalidKeyError.ts b/packages/core/src/wallet/error/WalletInvalidKeyError.ts index d3562d9bce..b7a29de2d9 100644 --- a/packages/core/src/wallet/error/WalletInvalidKeyError.ts +++ b/packages/core/src/wallet/error/WalletInvalidKeyError.ts @@ -1,6 +1,6 @@ -import { AriesFrameworkError } from '../../error/AriesFrameworkError' +import { WalletError } from './WalletError' -export class WalletInvalidKeyError extends AriesFrameworkError { +export class WalletInvalidKeyError extends WalletError { public constructor(message: string, { walletType, cause }: { walletType: string; cause?: Error }) { super(`${walletType}: ${message}`, { cause }) } diff --git a/packages/core/src/modules/ledger/error/LedgerNotFoundError.ts b/packages/core/src/wallet/error/WalletKeyExistsError.ts similarity index 52% rename from packages/core/src/modules/ledger/error/LedgerNotFoundError.ts rename to packages/core/src/wallet/error/WalletKeyExistsError.ts index 09355964d6..3e0a19e7b4 100644 --- a/packages/core/src/modules/ledger/error/LedgerNotFoundError.ts +++ b/packages/core/src/wallet/error/WalletKeyExistsError.ts @@ -1,6 +1,6 @@ -import { LedgerError } from './LedgerError' +import { WalletError } from './WalletError' -export class LedgerNotFoundError extends LedgerError { +export class WalletKeyExistsError extends WalletError { public constructor(message: string, { cause }: { cause?: Error } = {}) { super(message, { cause }) } diff --git a/packages/core/src/wallet/error/WalletNotFoundError.ts b/packages/core/src/wallet/error/WalletNotFoundError.ts index b5cc194fd8..a2e8d32d45 100644 --- a/packages/core/src/wallet/error/WalletNotFoundError.ts +++ b/packages/core/src/wallet/error/WalletNotFoundError.ts @@ -1,6 +1,6 @@ -import { AriesFrameworkError } from '../../error/AriesFrameworkError' +import { WalletError } from './WalletError' -export class WalletNotFoundError extends AriesFrameworkError { +export class WalletNotFoundError extends WalletError { public constructor(message: string, { walletType, cause }: { walletType: string; cause?: Error }) { super(`${walletType}: ${message}`, { cause }) } diff --git a/packages/core/src/wallet/error/index.ts b/packages/core/src/wallet/error/index.ts index 222cb8a532..92040216f4 100644 --- a/packages/core/src/wallet/error/index.ts +++ b/packages/core/src/wallet/error/index.ts @@ -2,3 +2,5 @@ export { WalletDuplicateError } from './WalletDuplicateError' export { WalletNotFoundError } from './WalletNotFoundError' export { WalletInvalidKeyError } from './WalletInvalidKeyError' export { WalletError } from './WalletError' +export { WalletKeyExistsError } from './WalletKeyExistsError' +export { WalletExportPathExistsError } from './WalletExportPathExistsError' diff --git a/packages/core/src/wallet/index.ts b/packages/core/src/wallet/index.ts index 6e19fc5d3c..e60dcfdb68 100644 --- a/packages/core/src/wallet/index.ts +++ b/packages/core/src/wallet/index.ts @@ -1,4 +1,3 @@ export * from './Wallet' -export * from './IndyWallet' export * from './WalletApi' export * from './WalletModule' diff --git a/packages/core/src/wallet/util/assertIndyWallet.ts b/packages/core/src/wallet/util/assertIndyWallet.ts deleted file mode 100644 index a26c43f0fe..0000000000 --- a/packages/core/src/wallet/util/assertIndyWallet.ts +++ /dev/null @@ -1,12 +0,0 @@ -import type { Wallet } from '../Wallet' - -import { AriesFrameworkError } from '../../error' -import { IndyWallet } from '../IndyWallet' - -export function assertIndyWallet(wallet: Wallet): asserts wallet is IndyWallet { - if (!(wallet instanceof IndyWallet)) { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const walletClassName = (wallet as any).constructor?.name ?? 'unknown' - throw new AriesFrameworkError(`Expected wallet to be instance of IndyWallet, found ${walletClassName}`) - } -} diff --git a/packages/core/tests/TestMessage.ts b/packages/core/tests/TestMessage.ts index b40a652508..c9ca013bfb 100644 --- a/packages/core/tests/TestMessage.ts +++ b/packages/core/tests/TestMessage.ts @@ -1,4 +1,4 @@ -import { DidCommV1Message } from '../src/didcomm' +import { DidCommV1Message, DidCommV2Message } from '../src/didcomm' export class TestMessage extends DidCommV1Message { public constructor() { @@ -9,3 +9,14 @@ export class TestMessage extends DidCommV1Message { public type = 'https://didcomm.org/connections/1.0/invitation' } + +export class V2TestMessage extends DidCommV2Message { + public constructor() { + super() + + this.id = this.generateId() + this.body = {} + } + + public type = 'https://didcomm.org/connections/2.0/invitation' +} diff --git a/packages/core/tests/agents.test.ts b/packages/core/tests/agents.test.ts index 493e932368..5e3ca4d6fb 100644 --- a/packages/core/tests/agents.test.ts +++ b/packages/core/tests/agents.test.ts @@ -1,22 +1,27 @@ /* eslint-disable @typescript-eslint/no-non-null-assertion */ -import type { SubjectMessage } from '../../../tests/transport/SubjectInboundTransport' import type { ConnectionRecord } from '../src/modules/connections' -import { Subject } from 'rxjs' - -import { SubjectInboundTransport } from '../../../tests/transport/SubjectInboundTransport' -import { SubjectOutboundTransport } from '../../../tests/transport/SubjectOutboundTransport' +import { getIndySdkModules } from '../../indy-sdk/tests/setupIndySdkModule' import { Agent } from '../src/agent/Agent' import { HandshakeProtocol } from '../src/modules/connections' import { waitForBasicMessage, getAgentOptions } from './helpers' - -const aliceAgentOptions = getAgentOptions('Agents Alice', { - endpoints: ['rxjs:alice'], -}) -const bobAgentOptions = getAgentOptions('Agents Bob', { - endpoints: ['rxjs:bob'], -}) +import { setupSubjectTransports } from './transport' + +const aliceAgentOptions = getAgentOptions( + 'Agents Alice', + { + endpoints: ['rxjs:alice'], + }, + getIndySdkModules() +) +const bobAgentOptions = getAgentOptions( + 'Agents Bob', + { + endpoints: ['rxjs:bob'], + }, + getIndySdkModules() +) describe('agents', () => { let aliceAgent: Agent @@ -32,22 +37,12 @@ describe('agents', () => { }) test('make a connection between agents', async () => { - const aliceMessages = new Subject() - const bobMessages = new Subject() + aliceAgent = new Agent(aliceAgentOptions) + bobAgent = new Agent(bobAgentOptions) - const subjectMap = { - 'rxjs:alice': aliceMessages, - 'rxjs:bob': bobMessages, - } + setupSubjectTransports([aliceAgent, bobAgent]) - aliceAgent = new Agent(aliceAgentOptions) - aliceAgent.registerInboundTransport(new SubjectInboundTransport(aliceMessages)) - aliceAgent.registerOutboundTransport(new SubjectOutboundTransport(subjectMap)) await aliceAgent.initialize() - - bobAgent = new Agent(bobAgentOptions) - bobAgent.registerInboundTransport(new SubjectInboundTransport(bobMessages)) - bobAgent.registerOutboundTransport(new SubjectOutboundTransport(subjectMap)) await bobAgent.initialize() const aliceBobOutOfBandRecord = await aliceAgent.oob.createInvitation({ diff --git a/packages/core/tests/connections.test.ts b/packages/core/tests/connections.test.ts index d2222ba86d..9284b90be2 100644 --- a/packages/core/tests/connections.test.ts +++ b/packages/core/tests/connections.test.ts @@ -1,12 +1,11 @@ /* eslint-disable @typescript-eslint/no-non-null-assertion */ -import type { SubjectMessage } from '../../../tests/transport/SubjectInboundTransport' import type { AgentMessageProcessedEvent, KeylistUpdate } from '../src' -import { filter, firstValueFrom, map, Subject, timeout } from 'rxjs' +import { filter, firstValueFrom, map, timeout } from 'rxjs' -import { SubjectInboundTransport } from '../../../tests/transport/SubjectInboundTransport' -import { SubjectOutboundTransport } from '../../../tests/transport/SubjectOutboundTransport' +import { getIndySdkModules } from '../../indy-sdk/tests/setupIndySdkModule' import { + MediatorModule, Key, AgentEventTypes, KeylistUpdateMessage, @@ -18,7 +17,8 @@ import { Agent } from '../src/agent/Agent' import { didKeyToVerkey } from '../src/modules/dids/helpers' import { OutOfBandState } from '../src/modules/oob/domain/OutOfBandState' -import { getAgentOptions } from './helpers' +import { getAgentOptions, waitForTrustPingResponseReceivedEvent } from './helpers' +import { setupSubjectTransports } from './transport' describe('connections', () => { let faberAgent: Agent @@ -27,50 +27,50 @@ describe('connections', () => { let mediatorAgent: Agent beforeEach(async () => { - const faberAgentOptions = getAgentOptions('Faber Agent Connections', { - endpoints: ['rxjs:faber'], - }) - const aliceAgentOptions = getAgentOptions('Alice Agent Connections', { - endpoints: ['rxjs:alice'], - }) - const acmeAgentOptions = getAgentOptions('Acme Agent Connections', { - endpoints: ['rxjs:acme'], - }) - const mediatorAgentOptions = getAgentOptions('Mediator Agent Connections', { - endpoints: ['rxjs:mediator'], - autoAcceptMediationRequests: true, - }) + const faberAgentOptions = getAgentOptions( + 'Faber Agent Connections', + { + endpoints: ['rxjs:faber'], + }, + getIndySdkModules() + ) + const aliceAgentOptions = getAgentOptions( + 'Alice Agent Connections', + { + endpoints: ['rxjs:alice'], + }, + getIndySdkModules() + ) + const acmeAgentOptions = getAgentOptions( + 'Acme Agent Connections', + { + endpoints: ['rxjs:acme'], + }, + getIndySdkModules() + ) + const mediatorAgentOptions = getAgentOptions( + 'Mediator Agent Connections', + { + endpoints: ['rxjs:mediator'], + }, + { + ...getIndySdkModules(), + mediator: new MediatorModule({ + autoAcceptMediationRequests: true, + }), + } + ) - const faberMessages = new Subject() - const aliceMessages = new Subject() - const acmeMessages = new Subject() - const mediatorMessages = new Subject() + faberAgent = new Agent(faberAgentOptions) + aliceAgent = new Agent(aliceAgentOptions) + acmeAgent = new Agent(acmeAgentOptions) + mediatorAgent = new Agent(mediatorAgentOptions) - const subjectMap = { - 'rxjs:faber': faberMessages, - 'rxjs:alice': aliceMessages, - 'rxjs:acme': acmeMessages, - 'rxjs:mediator': mediatorMessages, - } + setupSubjectTransports([faberAgent, aliceAgent, acmeAgent, mediatorAgent]) - faberAgent = new Agent(faberAgentOptions) - faberAgent.registerInboundTransport(new SubjectInboundTransport(faberMessages)) - faberAgent.registerOutboundTransport(new SubjectOutboundTransport(subjectMap)) await faberAgent.initialize() - - aliceAgent = new Agent(aliceAgentOptions) - aliceAgent.registerInboundTransport(new SubjectInboundTransport(aliceMessages)) - aliceAgent.registerOutboundTransport(new SubjectOutboundTransport(subjectMap)) await aliceAgent.initialize() - - acmeAgent = new Agent(acmeAgentOptions) - acmeAgent.registerInboundTransport(new SubjectInboundTransport(acmeMessages)) - acmeAgent.registerOutboundTransport(new SubjectOutboundTransport(subjectMap)) await acmeAgent.initialize() - - mediatorAgent = new Agent(mediatorAgentOptions) - mediatorAgent.registerInboundTransport(new SubjectInboundTransport(mediatorMessages)) - mediatorAgent.registerOutboundTransport(new SubjectOutboundTransport(subjectMap)) await mediatorAgent.initialize() }) @@ -85,6 +85,25 @@ describe('connections', () => { await mediatorAgent.wallet.delete() }) + it('one agent should be able to send and receive a ping', async () => { + const faberOutOfBandRecord = await faberAgent.oob.createInvitation({ + handshakeProtocols: [HandshakeProtocol.Connections], + multiUseInvitation: true, + }) + + const invitation = faberOutOfBandRecord.getOutOfBandInvitation() + const invitationUrl = invitation.toUrl({ domain: 'https://example.com' }) + + // Receive invitation with alice agent + let { connectionRecord: aliceFaberConnection } = await aliceAgent.oob.receiveInvitationFromUrl(invitationUrl) + aliceFaberConnection = await aliceAgent.connections.returnWhenIsConnected(aliceFaberConnection!.id) + expect(aliceFaberConnection.state).toBe(DidExchangeState.Completed) + + const ping = await aliceAgent.connections.sendPing(aliceFaberConnection.id, {}) + + await waitForTrustPingResponseReceivedEvent(aliceAgent, { threadId: ping.threadId }) + }) + it('one should be able to make multiple connections using a multi use invite', async () => { const faberOutOfBandRecord = await faberAgent.oob.createInvitation({ handshakeProtocols: [HandshakeProtocol.Connections], diff --git a/packages/core/tests/events.ts b/packages/core/tests/events.ts new file mode 100644 index 0000000000..e48f689f1e --- /dev/null +++ b/packages/core/tests/events.ts @@ -0,0 +1,21 @@ +import type { Agent, BaseEvent } from '../src' + +import { ReplaySubject } from 'rxjs' + +export type EventReplaySubject = ReplaySubject + +export function setupEventReplaySubjects(agents: Agent[], eventTypes: string[]): ReplaySubject[] { + const replaySubjects: EventReplaySubject[] = [] + + for (const agent of agents) { + const replaySubject = new ReplaySubject() + + for (const eventType of eventTypes) { + agent.events.observable(eventType).subscribe(replaySubject) + } + + replaySubjects.push(replaySubject) + } + + return replaySubjects +} diff --git a/packages/core/tests/generic-records.test.ts b/packages/core/tests/generic-records.test.ts index efe7455e2f..3d37def0ed 100644 --- a/packages/core/tests/generic-records.test.ts +++ b/packages/core/tests/generic-records.test.ts @@ -1,13 +1,18 @@ import type { GenericRecord } from '../src/modules/generic-records/repository/GenericRecord' +import { getIndySdkModules } from '../../indy-sdk/tests/setupIndySdkModule' import { Agent } from '../src/agent/Agent' import { RecordNotFoundError } from '../src/error' import { getAgentOptions } from './helpers' -const aliceAgentOptions = getAgentOptions('Generic Records Alice', { - endpoints: ['rxjs:alice'], -}) +const aliceAgentOptions = getAgentOptions( + 'Generic Records Alice', + { + endpoints: ['rxjs:alice'], + }, + getIndySdkModules() +) describe('genericRecords', () => { let aliceAgent: Agent @@ -56,11 +61,11 @@ describe('genericRecords', () => { test('get generic-record specific record', async () => { //Create genericRecord message const savedRecords1 = await aliceAgent.genericRecords.findAllByQuery({ myTag: 'foobar1' }) - expect(savedRecords1?.length == 1).toBe(true) + expect(savedRecords1?.length === 1).toBe(true) expect(savedRecords1[0].content).toEqual({ foo: 42 }) const savedRecords2 = await aliceAgent.genericRecords.findAllByQuery({ myTag: 'foobar2' }) - expect(savedRecords2.length == 2).toBe(true) + expect(savedRecords2.length === 2).toBe(true) expect(savedRecords2[0].content).toEqual({ foo: 'Some data saved' }) }) diff --git a/packages/core/tests/helpers.ts b/packages/core/tests/helpers.ts index 47e07fbd80..e288843fca 100644 --- a/packages/core/tests/helpers.ts +++ b/packages/core/tests/helpers.ts @@ -1,77 +1,60 @@ /* eslint-disable @typescript-eslint/no-non-null-assertion */ -import type { SubjectMessage } from '../../../tests/transport/SubjectInboundTransport' import type { - AcceptCredentialOfferOptions, AgentDependencies, + BaseEvent, BasicMessage, BasicMessageStateChangedEvent, ConnectionRecordProps, - CredentialDefinitionTemplate, CredentialStateChangedEvent, InitConfig, InjectionToken, ProofStateChangedEvent, - SchemaTemplate, Wallet, + Agent, + CredentialState, + ConnectionStateChangedEvent, + Buffer, } from '../src' import type { AgentModulesInput, EmptyModuleMap } from '../src/agent/AgentModules' -import type { IndyOfferCredentialFormat } from '../src/modules/credentials/formats/indy/IndyCredentialFormat' -import type { ProofAttributeInfo, ProofPredicateInfo } from '../src/modules/proofs/formats/indy/models' -import type { AutoAcceptProof } from '../src/modules/proofs/models/ProofAutoAcceptType' -import type { Awaited } from '../src/types' -import type { CredDef, Schema } from 'indy-sdk' +import type { + TrustPingReceivedEvent, + TrustPingResponseReceivedEvent, +} from '../src/modules/connections/protocols/trust-ping/TrustPingEvents' +import type { ProofState } from '../src/modules/proofs/models/ProofState' +import type { WalletConfig } from '../src/types' import type { Observable } from 'rxjs' +import { readFileSync } from 'fs' import path from 'path' -import { firstValueFrom, ReplaySubject, Subject } from 'rxjs' -import { catchError, filter, map, timeout } from 'rxjs/operators' +import { lastValueFrom, firstValueFrom, ReplaySubject } from 'rxjs' +import { catchError, filter, map, take, timeout } from 'rxjs/operators' -import { SubjectInboundTransport } from '../../../tests/transport/SubjectInboundTransport' -import { SubjectOutboundTransport } from '../../../tests/transport/SubjectOutboundTransport' -import { BbsModule } from '../../bbs-signatures/src/BbsModule' -import { agentDependencies, WalletScheme } from '../../node/src' +import { agentDependencies, IndySdkPostgresWalletScheme } from '../../node/src' import { - CredentialsModule, - IndyCredentialFormatService, - JsonLdCredentialFormatService, - V1CredentialProtocol, - V2CredentialProtocol, - W3cVcModule, - Agent, + ConnectionsModule, + ConnectionEventTypes, + TypedArrayEncoder, AgentConfig, AgentContext, - AriesFrameworkError, BasicMessageEventTypes, ConnectionRecord, CredentialEventTypes, - CredentialState, DependencyManager, DidExchangeRole, DidExchangeState, HandshakeProtocol, InjectionSymbols, - LogLevel, ProofEventTypes, + TrustPingEventTypes, } from '../src' import { Key, KeyType } from '../src/crypto' -import { V1Attachment, V1AttachmentData } from '../src/decorators/attachment/V1Attachment' -import { AutoAcceptCredential } from '../src/modules/credentials/models/CredentialAutoAcceptType' -import { V1CredentialPreview } from '../src/modules/credentials/protocol/v1/messages/V1CredentialPreview' import { DidCommV1Service } from '../src/modules/dids' import { DidKey } from '../src/modules/dids/methods/key' import { OutOfBandRole } from '../src/modules/oob/domain/OutOfBandRole' import { OutOfBandState } from '../src/modules/oob/domain/OutOfBandState' -import { OutOfBandInvitation } from '../src/modules/oob/protocols/v1/messages' +import { OutOfBandInvitation } from '../src/modules/oob/protocols/v1/messages/OutOfBandInvitation' import { OutOfBandRecord } from '../src/modules/oob/repository' -import { PredicateType } from '../src/modules/proofs/formats/indy/models' -import { ProofState } from '../src/modules/proofs/models/ProofState' -import { - PresentationPreview, - PresentationPreviewAttribute, - PresentationPreviewPredicate, -} from '../src/modules/proofs/protocol/v1/models/V1PresentationPreview' -import { customDocumentLoader } from '../src/modules/vc/__tests__/documentLoader' -import { LinkedAttachment } from '../src/utils/LinkedAttachment' +import { KeyDerivationMethod } from '../src/types' import { uuid } from '../src/utils/uuid' import testLogger, { TestLogger } from './logger' @@ -80,54 +63,63 @@ export const genesisPath = process.env.GENESIS_TXN_PATH ? path.resolve(process.env.GENESIS_TXN_PATH) : path.join(__dirname, '../../../network/genesis/local-genesis.txn') +export const genesisTransactions = readFileSync(genesisPath).toString('utf-8') + export const publicDidSeed = process.env.TEST_AGENT_PUBLIC_DID_SEED ?? '000000000000000000000000Trustee9' -const taaVersion = (process.env.TEST_AGENT_TAA_VERSION ?? '1') as `${number}.${number}` | `${number}` -const taaAcceptanceMechanism = process.env.TEST_AGENT_TAA_ACCEPTANCE_MECHANISM ?? 'accept' +export const taaVersion = (process.env.TEST_AGENT_TAA_VERSION ?? '1') as `${number}.${number}` | `${number}` +export const taaAcceptanceMechanism = process.env.TEST_AGENT_TAA_ACCEPTANCE_MECHANISM ?? 'accept' export { agentDependencies } export function getAgentOptions( name: string, extraConfig: Partial = {}, - modules?: AgentModules + inputModules?: AgentModules ): { config: InitConfig; modules: AgentModules; dependencies: AgentDependencies } { + const random = uuid().slice(0, 4) const config: InitConfig = { - label: `Agent: ${name}`, + label: `Agent: ${name} - ${random}`, walletConfig: { - id: `Wallet: ${name}`, - key: `Key: ${name}`, + id: `Wallet: ${name} - ${random}`, + key: 'DZ9hPqFWTPxemcGea72C1X1nusqk5wFNLq6QPjwXGqAa', // generated using indy.generateWalletKey + keyDerivationMethod: KeyDerivationMethod.Raw, }, - publicDidSeed, - autoAcceptConnections: true, - connectToIndyLedgersOnStartup: false, - indyLedgers: [ - { - id: `pool-${name}`, - isProduction: false, - genesisPath, - indyNamespace: `pool:localtest`, - transactionAuthorAgreement: { version: taaVersion, acceptanceMechanism: taaAcceptanceMechanism }, - }, - ], // TODO: determine the log level based on an environment variable. This will make it // possible to run e.g. failed github actions in debug mode for extra logs - logger: new TestLogger(LogLevel.off, name), + logger: TestLogger.fromLogger(testLogger, name), ...extraConfig, } - return { config, modules: (modules ?? {}) as AgentModules, dependencies: agentDependencies } as const + const m = (inputModules ?? {}) as AgentModulesInput + const modules = { + ...m, + // Make sure connections module is always defined so we can set autoAcceptConnections + connections: + m.connections ?? + new ConnectionsModule({ + autoAcceptConnections: true, + }), + } + + return { config, modules: modules as AgentModules, dependencies: agentDependencies } as const } -export function getPostgresAgentOptions(name: string, extraConfig: Partial = {}) { +export function getPostgresAgentOptions( + name: string, + extraConfig: Partial = {}, + inputModules?: AgentModules +) { + const random = uuid().slice(0, 4) const config: InitConfig = { - label: `Agent: ${name}`, + label: `Agent: ${name} - ${random}`, walletConfig: { - id: `Wallet${name}`, + // NOTE: IndySDK Postgres database per wallet doesn't support special characters/spaces in the wallet name + id: `PostgresWallet${name}${random}`, key: `Key${name}`, storage: { type: 'postgres_storage', config: { url: 'localhost:5432', - wallet_scheme: WalletScheme.DatabasePerWallet, + wallet_scheme: IndySdkPostgresWalletScheme.DatabasePerWallet, }, credentials: { account: 'postgres', @@ -137,27 +129,44 @@ export function getPostgresAgentOptions(name: string, extraConfig: Partial = {}) { +export async function importExistingIndyDidFromPrivateKey(agent: Agent, privateKey: Buffer) { + const key = await agent.wallet.createKey({ + keyType: KeyType.Ed25519, + privateKey, + }) + + // did is first 16 bytes of public key encoded as base58 + const unqualifiedIndyDid = TypedArrayEncoder.toBase58(key.publicKey.slice(0, 16)) + + // import the did in the wallet so it can be used + await agent.dids.import({ did: `did:indy:pool:localtest:${unqualifiedIndyDid}` }) + + return unqualifiedIndyDid +} + +export function getAgentConfig( + name: string, + extraConfig: Partial = {} +): AgentConfig & { walletConfig: WalletConfig } { const { config, dependencies } = getAgentOptions(name, extraConfig) - return new AgentConfig(config, dependencies) + return new AgentConfig(config, dependencies) as AgentConfig & { walletConfig: WalletConfig } } export function getAgentContext({ @@ -201,25 +210,39 @@ export async function waitForProofExchangeRecord( return waitForProofExchangeRecordSubject(observable, options) } +const isProofStateChangedEvent = (e: BaseEvent): e is ProofStateChangedEvent => + e.type === ProofEventTypes.ProofStateChanged +const isCredentialStateChangedEvent = (e: BaseEvent): e is CredentialStateChangedEvent => + e.type === CredentialEventTypes.CredentialStateChanged +const isConnectionStateChangedEvent = (e: BaseEvent): e is ConnectionStateChangedEvent => + e.type === ConnectionEventTypes.ConnectionStateChanged +const isTrustPingReceivedEvent = (e: BaseEvent): e is TrustPingReceivedEvent => + e.type === TrustPingEventTypes.TrustPingReceivedEvent +const isTrustPingResponseReceivedEvent = (e: BaseEvent): e is TrustPingResponseReceivedEvent => + e.type === TrustPingEventTypes.TrustPingResponseReceivedEvent + export function waitForProofExchangeRecordSubject( - subject: ReplaySubject | Observable, + subject: ReplaySubject | Observable, { threadId, parentThreadId, state, previousState, timeoutMs = 10000, + count = 1, }: { threadId?: string parentThreadId?: string state?: ProofState previousState?: ProofState | null timeoutMs?: number + count?: number } ) { - const observable = subject instanceof ReplaySubject ? subject.asObservable() : subject - return firstValueFrom( + const observable: Observable = subject instanceof ReplaySubject ? subject.asObservable() : subject + return lastValueFrom( observable.pipe( + filter(isProofStateChangedEvent), filter((e) => previousState === undefined || e.payload.previousState === previousState), filter((e) => threadId === undefined || e.payload.proofRecord.threadId === threadId), filter((e) => parentThreadId === undefined || e.payload.proofRecord.parentThreadId === parentThreadId), @@ -227,21 +250,104 @@ export function waitForProofExchangeRecordSubject( timeout(timeoutMs), catchError(() => { throw new Error( - `ProofStateChangedEvent event not emitted within specified timeout: { - previousState: ${previousState}, + `ProofStateChangedEvent event not emitted within specified timeout: ${timeoutMs} + previousState: ${previousState}, + threadId: ${threadId}, + parentThreadId: ${parentThreadId}, + state: ${state} + }` + ) + }), + take(count), + map((e) => e.payload.proofRecord) + ) + ) +} + +export async function waitForTrustPingReceivedEvent( + agent: Agent, + options: { + threadId?: string + timeoutMs?: number + } +) { + const observable = agent.events.observable(TrustPingEventTypes.TrustPingReceivedEvent) + + return waitForTrustPingReceivedEventSubject(observable, options) +} + +export function waitForTrustPingReceivedEventSubject( + subject: ReplaySubject | Observable, + { + threadId, + timeoutMs = 10000, + }: { + threadId?: string + timeoutMs?: number + } +) { + const observable = subject instanceof ReplaySubject ? subject.asObservable() : subject + return firstValueFrom( + observable.pipe( + filter(isTrustPingReceivedEvent), + filter((e) => threadId === undefined || e.payload.message.threadId === threadId), + timeout(timeoutMs), + catchError(() => { + throw new Error( + `TrustPingReceivedEvent event not emitted within specified timeout: ${timeoutMs} threadId: ${threadId}, - parentThreadId: ${parentThreadId}, - state: ${state} }` ) }), - map((e) => e.payload.proofRecord) + map((e) => e.payload.message) + ) + ) +} + +export async function waitForTrustPingResponseReceivedEvent( + agent: Agent, + options: { + threadId?: string + timeoutMs?: number + } +) { + const observable = agent.events.observable( + TrustPingEventTypes.TrustPingResponseReceivedEvent + ) + + return waitForTrustPingResponseReceivedEventSubject(observable, options) +} + +export function waitForTrustPingResponseReceivedEventSubject( + subject: ReplaySubject | Observable, + { + threadId, + timeoutMs = 10000, + }: { + threadId?: string + timeoutMs?: number + } +) { + const observable = subject instanceof ReplaySubject ? subject.asObservable() : subject + return firstValueFrom( + observable.pipe( + filter(isTrustPingResponseReceivedEvent), + filter((e) => threadId === undefined || e.payload.message.threadId === threadId), + timeout(timeoutMs), + catchError(() => { + throw new Error( + `TrustPingResponseReceivedEvent event not emitted within specified timeout: ${timeoutMs} + threadId: ${threadId}, +}` + ) + }), + map((e) => e.payload.message) ) ) } export function waitForCredentialRecordSubject( - subject: ReplaySubject | Observable, + subject: ReplaySubject | Observable, { threadId, state, @@ -258,6 +364,7 @@ export function waitForCredentialRecordSubject( return firstValueFrom( observable.pipe( + filter(isCredentialStateChangedEvent), filter((e) => previousState === undefined || e.payload.previousState === previousState), filter((e) => threadId === undefined || e.payload.credentialRecord.threadId === threadId), filter((e) => state === undefined || e.payload.credentialRecord.state === state), @@ -287,6 +394,54 @@ export async function waitForCredentialRecord( return waitForCredentialRecordSubject(observable, options) } +export function waitForConnectionRecordSubject( + subject: ReplaySubject | Observable, + { + threadId, + state, + previousState, + timeoutMs = 15000, // sign and store credential in W3c credential protocols take several seconds + }: { + threadId?: string + state?: DidExchangeState + previousState?: DidExchangeState | null + timeoutMs?: number + } +) { + const observable = subject instanceof ReplaySubject ? subject.asObservable() : subject + + return firstValueFrom( + observable.pipe( + filter(isConnectionStateChangedEvent), + filter((e) => previousState === undefined || e.payload.previousState === previousState), + filter((e) => threadId === undefined || e.payload.connectionRecord.threadId === threadId), + filter((e) => state === undefined || e.payload.connectionRecord.state === state), + timeout(timeoutMs), + catchError(() => { + throw new Error(`ConnectionStateChanged event not emitted within specified timeout: { + previousState: ${previousState}, + threadId: ${threadId}, + state: ${state} +}`) + }), + map((e) => e.payload.connectionRecord) + ) + ) +} + +export async function waitForConnectionRecord( + agent: Agent, + options: { + threadId?: string + state?: DidExchangeState + previousState?: DidExchangeState | null + timeoutMs?: number + } +) { + const observable = agent.events.observable(ConnectionEventTypes.ConnectionStateChanged) + return waitForConnectionRecordSubject(observable, options) +} + export async function waitForBasicMessage(agent: Agent, { content }: { content?: string }): Promise { return new Promise((resolve) => { const listener = (event: BasicMessageStateChangedEvent) => { @@ -383,281 +538,17 @@ export async function makeConnection(agentA: Agent, agentB: Agent) { handshakeProtocols: [HandshakeProtocol.Connections], }) - let { connectionRecord: agentBConnection } = await agentB.oob.receiveInvitation(agentAOutOfBand.outOfBandInvitation!) + let { connectionRecord: agentBConnection } = await agentB.oob.receiveInvitation( + agentAOutOfBand.getOutOfBandInvitation() + ) agentBConnection = await agentB.connections.returnWhenIsConnected(agentBConnection!.id) - let [agentAConnection] = await agentA.connections.findAllByOutOfBandId(agentAOutOfBand!.id) + let [agentAConnection] = await agentA.connections.findAllByOutOfBandId(agentAOutOfBand.id) agentAConnection = await agentA.connections.returnWhenIsConnected(agentAConnection!.id) return [agentAConnection, agentBConnection] } -export async function registerSchema(agent: Agent, schemaTemplate: SchemaTemplate): Promise { - const schema = await agent.ledger.registerSchema(schemaTemplate) - testLogger.test(`created schema with id ${schema.id}`, schema) - return schema -} - -export async function registerDefinition( - agent: Agent, - definitionTemplate: CredentialDefinitionTemplate -): Promise { - const credentialDefinition = await agent.ledger.registerCredentialDefinition(definitionTemplate) - testLogger.test(`created credential definition with id ${credentialDefinition.id}`, credentialDefinition) - return credentialDefinition -} - -export async function prepareForIssuance(agent: Agent, attributes: string[]) { - const publicDid = agent.publicDid?.did - - if (!publicDid) { - throw new AriesFrameworkError('No public did') - } - - await ensurePublicDidIsOnLedger(agent, publicDid) - - const schema = await registerSchema(agent, { - attributes, - name: `schema-${uuid()}`, - version: '1.0', - }) - - const definition = await registerDefinition(agent, { - schema, - signatureType: 'CL', - supportRevocation: false, - tag: 'default', - }) - - return { - schema, - definition, - publicDid, - } -} - -export async function ensurePublicDidIsOnLedger(agent: Agent, publicDid: string) { - try { - testLogger.test(`Ensure test DID ${publicDid} is written to ledger`) - await agent.ledger.getPublicDid(publicDid) - // eslint-disable-next-line @typescript-eslint/no-explicit-any - } catch (error: any) { - // Unfortunately, this won't prevent from the test suite running because of Jest runner runs all tests - // regardless of thrown errors. We're more explicit about the problem with this error handling. - throw new Error(`Test DID ${publicDid} is not written on ledger or ledger is not available: ${error.message}`) - } -} - -/** - * Assumes that the autoAcceptCredential is set to {@link AutoAcceptCredential.ContentApproved} - */ -export async function issueCredential({ - issuerAgent, - issuerConnectionId, - holderAgent, - credentialTemplate, -}: { - issuerAgent: Agent - issuerConnectionId: string - holderAgent: Agent - credentialTemplate: IndyOfferCredentialFormat -}) { - const issuerReplay = new ReplaySubject() - const holderReplay = new ReplaySubject() - - issuerAgent.events - .observable(CredentialEventTypes.CredentialStateChanged) - .subscribe(issuerReplay) - holderAgent.events - .observable(CredentialEventTypes.CredentialStateChanged) - .subscribe(holderReplay) - - let issuerCredentialRecord = await issuerAgent.credentials.offerCredential({ - comment: 'some comment about credential', - connectionId: issuerConnectionId, - protocolVersion: 'v1', - credentialFormats: { - indy: { - attributes: credentialTemplate.attributes, - credentialDefinitionId: credentialTemplate.credentialDefinitionId, - linkedAttachments: credentialTemplate.linkedAttachments, - }, - }, - autoAcceptCredential: AutoAcceptCredential.ContentApproved, - }) - - let holderCredentialRecord = await waitForCredentialRecordSubject(holderReplay, { - threadId: issuerCredentialRecord.threadId, - state: CredentialState.OfferReceived, - }) - - const acceptOfferOptions: AcceptCredentialOfferOptions = { - credentialRecordId: holderCredentialRecord.id, - autoAcceptCredential: AutoAcceptCredential.ContentApproved, - } - - await holderAgent.credentials.acceptOffer(acceptOfferOptions) - - // Because we use auto-accept it can take a while to have the whole credential flow finished - // Both parties need to interact with the ledger and sign/verify the credential - holderCredentialRecord = await waitForCredentialRecordSubject(holderReplay, { - threadId: issuerCredentialRecord.threadId, - state: CredentialState.Done, - }) - issuerCredentialRecord = await waitForCredentialRecordSubject(issuerReplay, { - threadId: issuerCredentialRecord.threadId, - state: CredentialState.Done, - }) - - return { - issuerCredential: issuerCredentialRecord, - holderCredential: holderCredentialRecord, - } -} - -export async function issueConnectionLessCredential({ - issuerAgent, - holderAgent, - credentialTemplate, -}: { - issuerAgent: Agent - holderAgent: Agent - credentialTemplate: IndyOfferCredentialFormat -}) { - const issuerReplay = new ReplaySubject() - const holderReplay = new ReplaySubject() - - issuerAgent.events - .observable(CredentialEventTypes.CredentialStateChanged) - .subscribe(issuerReplay) - holderAgent.events - .observable(CredentialEventTypes.CredentialStateChanged) - .subscribe(holderReplay) - - // eslint-disable-next-line prefer-const - let { credentialRecord: issuerCredentialRecord, message } = await issuerAgent.credentials.createOffer({ - comment: 'V1 Out of Band offer', - protocolVersion: 'v1', - credentialFormats: { - indy: { - attributes: credentialTemplate.attributes, - credentialDefinitionId: credentialTemplate.credentialDefinitionId, - }, - }, - autoAcceptCredential: AutoAcceptCredential.ContentApproved, - }) - - const { message: offerMessage } = await issuerAgent.oob.createLegacyConnectionlessInvitation({ - recordId: issuerCredentialRecord.id, - domain: 'https://example.org', - message, - }) - - await holderAgent.receiveMessage(offerMessage.toJSON()) - - let holderCredentialRecord = await waitForCredentialRecordSubject(holderReplay, { - threadId: issuerCredentialRecord.threadId, - state: CredentialState.OfferReceived, - }) - const acceptOfferOptions: AcceptCredentialOfferOptions = { - credentialRecordId: holderCredentialRecord.id, - autoAcceptCredential: AutoAcceptCredential.ContentApproved, - } - - await holderAgent.credentials.acceptOffer(acceptOfferOptions) - - holderCredentialRecord = await waitForCredentialRecordSubject(holderReplay, { - threadId: issuerCredentialRecord.threadId, - state: CredentialState.Done, - }) - - issuerCredentialRecord = await waitForCredentialRecordSubject(issuerReplay, { - threadId: issuerCredentialRecord.threadId, - state: CredentialState.Done, - }) - - return { - issuerCredential: issuerCredentialRecord, - holderCredential: holderCredentialRecord, - } -} - -export async function presentProof({ - verifierAgent, - verifierConnectionId, - holderAgent, - presentationTemplate: { attributes, predicates }, -}: { - verifierAgent: Agent - verifierConnectionId: string - holderAgent: Agent - presentationTemplate: { - attributes?: Record - predicates?: Record - } -}) { - const verifierReplay = new ReplaySubject() - const holderReplay = new ReplaySubject() - - verifierAgent.events.observable(ProofEventTypes.ProofStateChanged).subscribe(verifierReplay) - holderAgent.events.observable(ProofEventTypes.ProofStateChanged).subscribe(holderReplay) - - let holderProofExchangeRecordPromise = waitForProofExchangeRecordSubject(holderReplay, { - state: ProofState.RequestReceived, - }) - - let verifierRecord = await verifierAgent.proofs.requestProof({ - connectionId: verifierConnectionId, - proofFormats: { - indy: { - name: 'test-proof-request', - requestedAttributes: attributes, - requestedPredicates: predicates, - version: '1.0', - nonce: '947121108704767252195123', - }, - }, - protocolVersion: 'v2', - }) - - let holderRecord = await holderProofExchangeRecordPromise - - const requestedCredentials = await holderAgent.proofs.autoSelectCredentialsForProofRequest({ - proofRecordId: holderRecord.id, - config: { - filterByPresentationPreview: true, - }, - }) - - const verifierProofExchangeRecordPromise = waitForProofExchangeRecordSubject(verifierReplay, { - threadId: holderRecord.threadId, - state: ProofState.PresentationReceived, - }) - - await holderAgent.proofs.acceptRequest({ - proofRecordId: holderRecord.id, - proofFormats: { indy: requestedCredentials.proofFormats.indy }, - }) - - verifierRecord = await verifierProofExchangeRecordPromise - - // assert presentation is valid - expect(verifierRecord.isVerified).toBe(true) - - holderProofExchangeRecordPromise = waitForProofExchangeRecordSubject(holderReplay, { - threadId: holderRecord.threadId, - state: ProofState.Done, - }) - - verifierRecord = await verifierAgent.proofs.acceptPresentation(verifierRecord.id) - holderRecord = await holderProofExchangeRecordPromise - - return { - verifierProof: verifierRecord, - holderProof: holderRecord, - } -} - /** * Returns mock of function with correct type annotations according to original function `fn`. * It can be used also for class methods. @@ -677,194 +568,3 @@ export function mockFunction any>(fn: T): jest.Moc export function mockProperty(object: T, property: K, value: T[K]) { Object.defineProperty(object, property, { get: () => value }) } - -// Helper type to get the type of the agents (with the custom modules) for the credential tests -export type CredentialTestsAgent = Awaited>['aliceAgent'] -export async function setupCredentialTests( - faberName: string, - aliceName: string, - autoAcceptCredentials?: AutoAcceptCredential -) { - const faberMessages = new Subject() - const aliceMessages = new Subject() - const subjectMap = { - 'rxjs:faber': faberMessages, - 'rxjs:alice': aliceMessages, - } - - const indyCredentialFormat = new IndyCredentialFormatService() - const jsonLdCredentialFormat = new JsonLdCredentialFormatService() - - // TODO remove the dependency on BbsModule - const modules = { - bbs: new BbsModule(), - - // Initialize custom credentials module (with jsonLdCredentialFormat enabled) - credentials: new CredentialsModule({ - autoAcceptCredentials, - credentialProtocols: [ - new V1CredentialProtocol({ indyCredentialFormat }), - new V2CredentialProtocol({ - credentialFormats: [indyCredentialFormat, jsonLdCredentialFormat], - }), - ], - }), - // Register custom w3cVc module so we can define the test document loader - w3cVc: new W3cVcModule({ - documentLoader: customDocumentLoader, - }), - } - const faberAgentOptions = getAgentOptions( - faberName, - { - endpoints: ['rxjs:faber'], - }, - modules - ) - - const aliceAgentOptions = getAgentOptions( - aliceName, - { - endpoints: ['rxjs:alice'], - }, - modules - ) - const faberAgent = new Agent(faberAgentOptions) - faberAgent.registerInboundTransport(new SubjectInboundTransport(faberMessages)) - faberAgent.registerOutboundTransport(new SubjectOutboundTransport(subjectMap)) - await faberAgent.initialize() - - const aliceAgent = new Agent(aliceAgentOptions) - aliceAgent.registerInboundTransport(new SubjectInboundTransport(aliceMessages)) - aliceAgent.registerOutboundTransport(new SubjectOutboundTransport(subjectMap)) - await aliceAgent.initialize() - - const { - schema, - definition: { id: credDefId }, - } = await prepareForIssuance(faberAgent, ['name', 'age', 'profile_picture', 'x-ray']) - - const [faberConnection, aliceConnection] = await makeConnection(faberAgent, aliceAgent) - - const faberReplay = new ReplaySubject() - const aliceReplay = new ReplaySubject() - - faberAgent.events - .observable(CredentialEventTypes.CredentialStateChanged) - .subscribe(faberReplay) - aliceAgent.events - .observable(CredentialEventTypes.CredentialStateChanged) - .subscribe(aliceReplay) - - return { faberAgent, aliceAgent, credDefId, schema, faberConnection, aliceConnection, faberReplay, aliceReplay } -} - -export async function setupProofsTest(faberName: string, aliceName: string, autoAcceptProofs?: AutoAcceptProof) { - const credentialPreview = V1CredentialPreview.fromRecord({ - name: 'John', - age: '99', - }) - - const unique = uuid().substring(0, 4) - - const faberAgentOptions = getAgentOptions(`${faberName}-${unique}`, { - autoAcceptProofs, - endpoints: ['rxjs:faber'], - }) - - const aliceAgentOptions = getAgentOptions(`${aliceName}-${unique}`, { - autoAcceptProofs, - endpoints: ['rxjs:alice'], - }) - - const faberMessages = new Subject() - const aliceMessages = new Subject() - - const subjectMap = { - 'rxjs:faber': faberMessages, - 'rxjs:alice': aliceMessages, - } - const faberAgent = new Agent(faberAgentOptions) - faberAgent.registerInboundTransport(new SubjectInboundTransport(faberMessages)) - faberAgent.registerOutboundTransport(new SubjectOutboundTransport(subjectMap)) - await faberAgent.initialize() - - const aliceAgent = new Agent(aliceAgentOptions) - aliceAgent.registerInboundTransport(new SubjectInboundTransport(aliceMessages)) - aliceAgent.registerOutboundTransport(new SubjectOutboundTransport(subjectMap)) - await aliceAgent.initialize() - - const { definition } = await prepareForIssuance(faberAgent, ['name', 'age', 'image_0', 'image_1']) - - const [agentAConnection, agentBConnection] = await makeConnection(faberAgent, aliceAgent) - expect(agentAConnection.isReady).toBe(true) - expect(agentBConnection.isReady).toBe(true) - - const faberConnection = agentAConnection - const aliceConnection = agentBConnection - - const presentationPreview = new PresentationPreview({ - attributes: [ - new PresentationPreviewAttribute({ - name: 'name', - credentialDefinitionId: definition.id, - referent: '0', - value: 'John', - }), - new PresentationPreviewAttribute({ - name: 'image_0', - credentialDefinitionId: definition.id, - }), - ], - predicates: [ - new PresentationPreviewPredicate({ - name: 'age', - credentialDefinitionId: definition.id, - predicate: PredicateType.GreaterThanOrEqualTo, - threshold: 50, - }), - ], - }) - - await issueCredential({ - issuerAgent: faberAgent, - issuerConnectionId: faberConnection.id, - holderAgent: aliceAgent, - credentialTemplate: { - credentialDefinitionId: definition.id, - attributes: credentialPreview.attributes, - linkedAttachments: [ - new LinkedAttachment({ - name: 'image_0', - attachment: new V1Attachment({ - filename: 'picture-of-a-cat.png', - data: new V1AttachmentData({ base64: 'cGljdHVyZSBvZiBhIGNhdA==' }), - }), - }), - new LinkedAttachment({ - name: 'image_1', - attachment: new V1Attachment({ - filename: 'picture-of-a-dog.png', - data: new V1AttachmentData({ base64: 'UGljdHVyZSBvZiBhIGRvZw==' }), - }), - }), - ], - }, - }) - const faberReplay = new ReplaySubject() - const aliceReplay = new ReplaySubject() - - faberAgent.events.observable(ProofEventTypes.ProofStateChanged).subscribe(faberReplay) - aliceAgent.events.observable(ProofEventTypes.ProofStateChanged).subscribe(aliceReplay) - - return { - faberAgent, - aliceAgent, - credDefId: definition.id, - faberConnection, - aliceConnection, - presentationPreview, - faberReplay, - aliceReplay, - } -} diff --git a/packages/core/tests/index.ts b/packages/core/tests/index.ts new file mode 100644 index 0000000000..2822fb23e1 --- /dev/null +++ b/packages/core/tests/index.ts @@ -0,0 +1,9 @@ +export * from './jsonld' +export * from './transport' +export * from './events' +export * from './helpers' +export * from './indySdk' + +import testLogger from './logger' + +export { testLogger } diff --git a/packages/core/tests/indySdk.ts b/packages/core/tests/indySdk.ts new file mode 100644 index 0000000000..b5e5a3075d --- /dev/null +++ b/packages/core/tests/indySdk.ts @@ -0,0 +1,3 @@ +import { indySdk } from '../../indy-sdk/tests/setupIndySdkModule' + +export { indySdk } diff --git a/packages/core/tests/jsonld.ts b/packages/core/tests/jsonld.ts new file mode 100644 index 0000000000..41ea8013b4 --- /dev/null +++ b/packages/core/tests/jsonld.ts @@ -0,0 +1,171 @@ +import type { EventReplaySubject } from './events' +import type { AutoAcceptCredential, AutoAcceptProof, ConnectionRecord } from '../src' + +import { BbsModule } from '../../bbs-signatures/src/BbsModule' +import { IndySdkModule } from '../../indy-sdk/src' +import { indySdk } from '../../indy-sdk/tests/setupIndySdkModule' +import { + CacheModule, + CredentialEventTypes, + InMemoryLruCache, + ProofEventTypes, + Agent, + ProofsModule, + CredentialsModule, + JsonLdCredentialFormatService, + V2CredentialProtocol, + W3cCredentialsModule, +} from '../src' +import { customDocumentLoader } from '../src/modules/vc/__tests__/documentLoader' + +import { setupEventReplaySubjects } from './events' +import { getAgentOptions, makeConnection } from './helpers' +import { setupSubjectTransports } from './transport' + +export type JsonLdTestsAgent = Agent> + +export const getJsonLdModules = ({ + autoAcceptCredentials, + autoAcceptProofs, +}: { autoAcceptCredentials?: AutoAcceptCredential; autoAcceptProofs?: AutoAcceptProof } = {}) => + ({ + credentials: new CredentialsModule({ + credentialProtocols: [new V2CredentialProtocol({ credentialFormats: [new JsonLdCredentialFormatService()] })], + autoAcceptCredentials, + }), + w3cCredentials: new W3cCredentialsModule({ + documentLoader: customDocumentLoader, + }), + proofs: new ProofsModule({ + autoAcceptProofs, + }), + cache: new CacheModule({ + cache: new InMemoryLruCache({ limit: 100 }), + }), + indySdk: new IndySdkModule({ + indySdk, + }), + bbs: new BbsModule(), + } as const) + +interface SetupJsonLdTestsReturn { + issuerAgent: JsonLdTestsAgent + issuerReplay: EventReplaySubject + + holderAgent: JsonLdTestsAgent + holderReplay: EventReplaySubject + + issuerHolderConnectionId: CreateConnections extends true ? string : undefined + holderIssuerConnectionId: CreateConnections extends true ? string : undefined + + verifierHolderConnectionId: CreateConnections extends true + ? VerifierName extends string + ? string + : undefined + : undefined + holderVerifierConnectionId: CreateConnections extends true + ? VerifierName extends string + ? string + : undefined + : undefined + + verifierAgent: VerifierName extends string ? JsonLdTestsAgent : undefined + verifierReplay: VerifierName extends string ? EventReplaySubject : undefined + + credentialDefinitionId: string +} + +export async function setupJsonLdTests< + VerifierName extends string | undefined = undefined, + CreateConnections extends boolean = true +>({ + issuerName, + holderName, + verifierName, + autoAcceptCredentials, + autoAcceptProofs, + createConnections, +}: { + issuerName: string + holderName: string + verifierName?: VerifierName + autoAcceptCredentials?: AutoAcceptCredential + autoAcceptProofs?: AutoAcceptProof + createConnections?: CreateConnections +}): Promise> { + const modules = getJsonLdModules({ + autoAcceptCredentials, + autoAcceptProofs, + }) + + const issuerAgent = new Agent( + getAgentOptions( + issuerName, + { + endpoints: ['rxjs:issuer'], + }, + modules + ) + ) + + const holderAgent = new Agent( + getAgentOptions( + holderName, + { + endpoints: ['rxjs:holder'], + }, + modules + ) + ) + + const verifierAgent = verifierName + ? new Agent( + getAgentOptions( + verifierName, + { + endpoints: ['rxjs:verifier'], + }, + modules + ) + ) + : undefined + + setupSubjectTransports(verifierAgent ? [issuerAgent, holderAgent, verifierAgent] : [issuerAgent, holderAgent]) + const [issuerReplay, holderReplay, verifierReplay] = setupEventReplaySubjects( + verifierAgent ? [issuerAgent, holderAgent, verifierAgent] : [issuerAgent, holderAgent], + [CredentialEventTypes.CredentialStateChanged, ProofEventTypes.ProofStateChanged] + ) + + await issuerAgent.initialize() + await holderAgent.initialize() + if (verifierAgent) await verifierAgent.initialize() + + let issuerHolderConnection: ConnectionRecord | undefined + let holderIssuerConnection: ConnectionRecord | undefined + let verifierHolderConnection: ConnectionRecord | undefined + let holderVerifierConnection: ConnectionRecord | undefined + + if (createConnections ?? true) { + ;[issuerHolderConnection, holderIssuerConnection] = await makeConnection(issuerAgent, holderAgent) + + if (verifierAgent) { + ;[holderVerifierConnection, verifierHolderConnection] = await makeConnection(holderAgent, verifierAgent) + } + } + + return { + issuerAgent, + issuerReplay, + + holderAgent, + holderReplay, + + verifierAgent: verifierName ? verifierAgent : undefined, + verifierReplay: verifierName ? verifierReplay : undefined, + + issuerHolderConnectionId: issuerHolderConnection?.id, + holderIssuerConnectionId: holderIssuerConnection?.id, + holderVerifierConnectionId: holderVerifierConnection?.id, + verifierHolderConnectionId: verifierHolderConnection?.id, + } as unknown as SetupJsonLdTestsReturn +} diff --git a/packages/core/tests/ledger.test.ts b/packages/core/tests/ledger.test.ts deleted file mode 100644 index 9d3411e54d..0000000000 --- a/packages/core/tests/ledger.test.ts +++ /dev/null @@ -1,170 +0,0 @@ -import { promises } from 'fs' -import * as indy from 'indy-sdk' - -import { KeyType } from '../src' -import { Agent } from '../src/agent/Agent' -import { - DID_IDENTIFIER_REGEX, - indyDidFromPublicKeyBase58, - isAbbreviatedVerkey, - isFullVerkey, - VERKEY_REGEX, -} from '../src/utils/did' -import { sleep } from '../src/utils/sleep' - -import { genesisPath, getAgentOptions } from './helpers' -import testLogger from './logger' - -describe('ledger', () => { - let faberAgent: Agent - let schemaId: indy.SchemaId - - beforeAll(async () => { - faberAgent = new Agent(getAgentOptions('Faber Ledger')) - await faberAgent.initialize() - }) - - afterAll(async () => { - await faberAgent.shutdown() - await faberAgent.wallet.delete() - }) - - test(`initialization of agent's public DID`, async () => { - const publicDid = faberAgent.publicDid - testLogger.test('faberAgentPublicDid', publicDid) - - expect(publicDid).toEqual( - expect.objectContaining({ - did: expect.stringMatching(DID_IDENTIFIER_REGEX), - verkey: expect.stringMatching(VERKEY_REGEX), - }) - ) - }) - - test('get public DID from ledger', async () => { - if (!faberAgent.publicDid) { - throw new Error('Agent does not have public did.') - } - - const result = await faberAgent.ledger.getPublicDid(faberAgent.publicDid.did) - - let { verkey } = faberAgent.publicDid - // Agent’s public did stored locally in Indy wallet and created from public did seed during - // its initialization always returns full verkey. Therefore we need to align that here. - if (isFullVerkey(verkey) && isAbbreviatedVerkey(result.verkey)) { - verkey = await indy.abbreviateVerkey(faberAgent.publicDid.did, verkey) - } - - expect(result).toEqual( - expect.objectContaining({ - did: faberAgent.publicDid.did, - verkey: verkey, - role: '0', - }) - ) - }) - - test('register public DID on ledger', async () => { - if (!faberAgent.publicDid) { - throw new Error('Agent does not have public did.') - } - - const faberWallet = faberAgent.context.wallet - const key = await faberWallet.createKey({ keyType: KeyType.Ed25519 }) - const did = indyDidFromPublicKeyBase58(key.publicKeyBase58) - - const result = await faberAgent.ledger.registerPublicDid(did, key.publicKeyBase58, 'alias', 'TRUST_ANCHOR') - - expect(result).toEqual(did) - }) - - test('register schema on ledger', async () => { - if (!faberAgent.publicDid) { - throw new Error('Agent does not have public did.') - } - - const schemaName = `test-schema-${Date.now()}` - const schemaTemplate = { - name: schemaName, - attributes: ['name', 'age'], - version: '1.0', - } - - const schema = await faberAgent.ledger.registerSchema(schemaTemplate) - schemaId = schema.id - - await sleep(2000) - - const ledgerSchema = await faberAgent.ledger.getSchema(schemaId) - - expect(schemaId).toBe(`${faberAgent.publicDid.did}:2:${schemaName}:1.0`) - - expect(ledgerSchema).toEqual( - expect.objectContaining({ - attrNames: expect.arrayContaining(schemaTemplate.attributes), - id: `${faberAgent.publicDid.did}:2:${schemaName}:1.0`, - name: schemaName, - seqNo: schema.seqNo, - ver: schemaTemplate.version, - version: schemaTemplate.version, - }) - ) - }) - - test('register definition on ledger', async () => { - if (!faberAgent.publicDid) { - throw new Error('Agent does not have public did.') - } - const schema = await faberAgent.ledger.getSchema(schemaId) - const credentialDefinitionTemplate = { - schema: schema, - tag: 'TAG', - signatureType: 'CL' as const, - supportRevocation: true, - } - - const credentialDefinition = await faberAgent.ledger.registerCredentialDefinition(credentialDefinitionTemplate) - - await sleep(2000) - - const ledgerCredDef = await faberAgent.ledger.getCredentialDefinition(credentialDefinition.id) - - const credDefIdRegExp = new RegExp(`${faberAgent.publicDid.did}:3:CL:[0-9]+:TAG`) - expect(ledgerCredDef).toEqual( - expect.objectContaining({ - id: expect.stringMatching(credDefIdRegExp), - schemaId: String(schema.seqNo), - type: credentialDefinitionTemplate.signatureType, - tag: credentialDefinitionTemplate.tag, - ver: '1.0', - value: expect.objectContaining({ - primary: expect.anything(), - revocation: expect.anything(), - }), - }) - ) - }) - - it('should correctly store the genesis file if genesis transactions is passed', async () => { - const genesisTransactions = await promises.readFile(genesisPath, { encoding: 'utf-8' }) - const agentOptions = getAgentOptions('Faber Ledger Genesis Transactions', { - indyLedgers: [ - { - id: 'pool-Faber Ledger Genesis Transactions', - indyNamespace: 'pool-faber-ledger-genesis-transactions', - isProduction: false, - genesisTransactions, - }, - ], - }) - const agent = new Agent(agentOptions) - await agent.initialize() - - if (!faberAgent.publicDid?.did) { - throw new Error('No public did') - } - - const did = await agent.ledger.getPublicDid(faberAgent.publicDid.did) - expect(did.did).toEqual(faberAgent.publicDid.did) - }) -}) diff --git a/packages/core/tests/logger.ts b/packages/core/tests/logger.ts index 46c7066acc..769143e938 100644 --- a/packages/core/tests/logger.ts +++ b/packages/core/tests/logger.ts @@ -1,6 +1,6 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ -import type { ILogObject } from 'tslog' +import type { ILogObj } from 'tslog' import { appendFileSync } from 'fs' import { Logger } from 'tslog' @@ -9,15 +9,15 @@ import { LogLevel } from '../src/logger' import { BaseLogger } from '../src/logger/BaseLogger' import { replaceError } from '../src/logger/replaceError' -function logToTransport(logObject: ILogObject) { +function logToTransport(logObject: ILogObj) { appendFileSync('logs.txt', JSON.stringify(logObject) + '\n') } export class TestLogger extends BaseLogger { - private logger: Logger + public readonly logger: Logger // Map our log levels to tslog levels - private tsLogLevelMap = { + private tsLogLevelStringMap = { [LogLevel.test]: 'silly', [LogLevel.trace]: 'trace', [LogLevel.debug]: 'debug', @@ -27,33 +27,40 @@ export class TestLogger extends BaseLogger { [LogLevel.fatal]: 'fatal', } as const - public constructor(logLevel: LogLevel, name?: string) { + // Map our log levels to tslog levels + private tsLogLevelNumgerMap = { + [LogLevel.test]: 0, + [LogLevel.trace]: 1, + [LogLevel.debug]: 2, + [LogLevel.info]: 3, + [LogLevel.warn]: 4, + [LogLevel.error]: 5, + [LogLevel.fatal]: 6, + } as const + + public static fromLogger(logger: TestLogger, name?: string) { + return new TestLogger(logger.logLevel, name, logger.logger) + } + + public constructor(logLevel: LogLevel, name?: string, logger?: Logger) { super(logLevel) - this.logger = new Logger({ - name, - minLevel: this.logLevel == LogLevel.off ? undefined : this.tsLogLevelMap[this.logLevel], - ignoreStackLevels: 5, - attachedTransports: [ - { - transportLogger: { - silly: logToTransport, - debug: logToTransport, - trace: logToTransport, - info: logToTransport, - warn: logToTransport, - error: logToTransport, - fatal: logToTransport, - }, - // always log to file - minLevel: 'silly', - }, - ], - }) + if (logger) { + this.logger = logger.getSubLogger({ + name, + minLevel: this.logLevel == LogLevel.off ? undefined : this.tsLogLevelNumgerMap[this.logLevel], + }) + } else { + this.logger = new Logger({ + name, + minLevel: this.logLevel == LogLevel.off ? undefined : this.tsLogLevelNumgerMap[this.logLevel], + attachedTransports: [logToTransport], + }) + } } private log(level: Exclude, message: string, data?: Record): void { - const tsLogLevel = this.tsLogLevelMap[level] + const tsLogLevel = this.tsLogLevelStringMap[level] if (this.logLevel === LogLevel.off) return @@ -93,6 +100,6 @@ export class TestLogger extends BaseLogger { } } -const testLogger = new TestLogger(LogLevel.error) +const testLogger = new TestLogger(LogLevel.off) export default testLogger diff --git a/packages/core/tests/migration.test.ts b/packages/core/tests/migration.test.ts index 0dbf6b02dc..ac898caa73 100644 --- a/packages/core/tests/migration.test.ts +++ b/packages/core/tests/migration.test.ts @@ -1,11 +1,12 @@ import type { VersionString } from '../src/utils/version' +import { getIndySdkModules } from '../../indy-sdk/tests/setupIndySdkModule' import { Agent } from '../src/agent/Agent' import { UpdateAssistant } from '../src/storage/migration/UpdateAssistant' import { getAgentOptions } from './helpers' -const agentOptions = getAgentOptions('Migration', { publicDidSeed: undefined, indyLedgers: [] }) +const agentOptions = getAgentOptions('Migration', {}, getIndySdkModules()) describe('migration', () => { test('manually initiating the update assistant to perform an update', async () => { diff --git a/packages/core/tests/mocks/MockWallet.ts b/packages/core/tests/mocks/MockWallet.ts index 20e4718305..5949aa1ff3 100644 --- a/packages/core/tests/mocks/MockWallet.ts +++ b/packages/core/tests/mocks/MockWallet.ts @@ -1,20 +1,17 @@ /* eslint-disable @typescript-eslint/no-unused-vars */ -import type { Wallet, KeyPair } from '../../src' +import type { Wallet, KeyPair, WalletPackOptions } from '../../src' import type { Key } from '../../src/crypto' import type { EncryptedMessage } from '../../src/didcomm/types' import type { WalletConfig, WalletExportImportConfig, WalletConfigRekey } from '../../src/types' import type { Buffer } from '../../src/utils/buffer' import type { - DidInfo, UnpackedMessageContext, - DidConfig, WalletCreateKeyOptions, WalletSignOptions, WalletVerifyOptions, } from '../../src/wallet' export class MockWallet implements Wallet { - public publicDid = undefined public isInitialized = true public isProvisioned = true @@ -42,14 +39,7 @@ export class MockWallet implements Wallet { public import(walletConfig: WalletConfig, importConfig: WalletExportImportConfig): Promise { throw new Error('Method not implemented.') } - public initPublicDid(didConfig: DidConfig): Promise { - throw new Error('Method not implemented.') - } - public pack( - payload: Record, - recipientKeys: string[], - senderVerkey?: string - ): Promise { + public pack(payload: Record, params: WalletPackOptions): Promise { throw new Error('Method not implemented.') } public unpack(encryptedMessage: EncryptedMessage): Promise { @@ -74,10 +64,6 @@ export class MockWallet implements Wallet { throw new Error('Method not implemented.') } - public retrieveKeyPair(keyId: string): Promise { - throw new Error('Method not implemented.') - } - public dispose() { // Nothing to do here } diff --git a/packages/core/tests/multi-protocol-version.test.ts b/packages/core/tests/multi-protocol-version.test.ts index c2c64675a3..fdb28cf00d 100644 --- a/packages/core/tests/multi-protocol-version.test.ts +++ b/packages/core/tests/multi-protocol-version.test.ts @@ -1,24 +1,31 @@ -import type { SubjectMessage } from '../../../tests/transport/SubjectInboundTransport' import type { AgentMessageProcessedEvent } from '../src/agent/Events' -import { filter, firstValueFrom, Subject, timeout } from 'rxjs' +import { filter, firstValueFrom, timeout } from 'rxjs' -import { SubjectInboundTransport } from '../../../tests/transport/SubjectInboundTransport' -import { SubjectOutboundTransport } from '../../../tests/transport/SubjectOutboundTransport' -import { parseMessageType, MessageSender, Dispatcher, IsValidMessageType } from '../src' +import { getIndySdkModules } from '../../indy-sdk/tests/setupIndySdkModule' +import { parseMessageType, MessageSender, IsValidMessageType } from '../src' import { Agent } from '../src/agent/Agent' import { AgentEventTypes } from '../src/agent/Events' import { OutboundMessageContext } from '../src/agent/models' import { DidCommV1Message } from '../src/didcomm/versions/v1' import { getAgentOptions } from './helpers' - -const aliceAgentOptions = getAgentOptions('Multi Protocol Versions - Alice', { - endpoints: ['rxjs:alice'], -}) -const bobAgentOptions = getAgentOptions('Multi Protocol Versions - Bob', { - endpoints: ['rxjs:bob'], -}) +import { setupSubjectTransports } from './transport' + +const aliceAgentOptions = getAgentOptions( + 'Multi Protocol Versions - Alice', + { + endpoints: ['rxjs:alice'], + }, + getIndySdkModules() +) +const bobAgentOptions = getAgentOptions( + 'Multi Protocol Versions - Bob', + { + endpoints: ['rxjs:bob'], + }, + getIndySdkModules() +) describe('multi version protocols', () => { let aliceAgent: Agent @@ -32,29 +39,15 @@ describe('multi version protocols', () => { }) test('should successfully handle a message with a lower minor version than the currently supported version', async () => { - const aliceMessages = new Subject() - const bobMessages = new Subject() - - const subjectMap = { - 'rxjs:alice': aliceMessages, - 'rxjs:bob': bobMessages, - } - - const mockHandle = jest.fn() - + bobAgent = new Agent(bobAgentOptions) aliceAgent = new Agent(aliceAgentOptions) - aliceAgent.registerInboundTransport(new SubjectInboundTransport(aliceMessages)) - aliceAgent.registerOutboundTransport(new SubjectOutboundTransport(subjectMap)) + setupSubjectTransports([aliceAgent, bobAgent]) // Register the test handler with the v1.3 version of the message - const dispatcher = aliceAgent.dependencyManager.resolve(Dispatcher) - dispatcher.registerMessageHandler({ supportedMessages: [TestMessageV13], handle: mockHandle }) + const mockHandle = jest.fn() + aliceAgent.dependencyManager.registerMessageHandlers([{ supportedMessages: [TestMessageV13], handle: mockHandle }]) await aliceAgent.initialize() - - bobAgent = new Agent(bobAgentOptions) - bobAgent.registerInboundTransport(new SubjectInboundTransport(bobMessages)) - bobAgent.registerOutboundTransport(new SubjectOutboundTransport(subjectMap)) await bobAgent.initialize() const { outOfBandInvitation, id } = await aliceAgent.oob.createInvitation() diff --git a/packages/core/tests/oob-mediation-provision.test.ts b/packages/core/tests/oob-mediation-provision.test.ts index c8af7fbd03..147c88cd0a 100644 --- a/packages/core/tests/oob-mediation-provision.test.ts +++ b/packages/core/tests/oob-mediation-provision.test.ts @@ -1,29 +1,48 @@ /* eslint-disable @typescript-eslint/no-non-null-assertion */ -import type { SubjectMessage } from '../../../tests/transport/SubjectInboundTransport' -import type { OutOfBandInvitation } from '../src/modules/oob/protocols/v1/messages' +import type { OutOfBandInvitation } from '../src/modules/oob/protocols/v1/messages/OutOfBandInvitation' import type { V2OutOfBandInvitation } from '../src/modules/oob/protocols/v2' -import { Subject } from 'rxjs' - -import { SubjectInboundTransport } from '../../../tests/transport/SubjectInboundTransport' -import { SubjectOutboundTransport } from '../../../tests/transport/SubjectOutboundTransport' +import { getIndySdkModules } from '../../indy-sdk/tests/setupIndySdkModule' import { Agent } from '../src/agent/Agent' import { DidExchangeState, HandshakeProtocol } from '../src/modules/connections' -import { MediationState, MediatorPickupStrategy } from '../src/modules/routing' +import { + MediationState, + MediatorModule, + MediatorPickupStrategy, + MediationRecipientModule, +} from '../src/modules/routing' import { getAgentOptions, waitForBasicMessage } from './helpers' - -const faberAgentOptions = getAgentOptions('OOB mediation provision - Faber Agent', { - endpoints: ['rxjs:faber'], -}) -const aliceAgentOptions = getAgentOptions('OOB mediation provision - Alice Recipient Agent', { - endpoints: ['rxjs:alice'], - mediatorPickupStrategy: MediatorPickupStrategy.PickUpV1, -}) -const mediatorAgentOptions = getAgentOptions('OOB mediation provision - Mediator Agent', { - endpoints: ['rxjs:mediator'], - autoAcceptMediationRequests: true, -}) +import { setupSubjectTransports } from './transport' + +const faberAgentOptions = getAgentOptions( + 'OOB mediation provision - Faber Agent', + { + endpoints: ['rxjs:faber'], + }, + getIndySdkModules() +) +const aliceAgentOptions = getAgentOptions( + 'OOB mediation provision - Alice Recipient Agent', + { + endpoints: ['rxjs:alice'], + }, + { + ...getIndySdkModules(), + mediationRecipient: new MediationRecipientModule({ + // FIXME: discover features returns that we support this protocol, but we don't support all roles + // we should return that we only support the mediator role so we don't have to explicitly declare this + mediatorPickupStrategy: MediatorPickupStrategy.PickUpV1, + }), + } +) +const mediatorAgentOptions = getAgentOptions( + 'OOB mediation provision - Mediator Agent', + { + endpoints: ['rxjs:mediator'], + }, + { ...getIndySdkModules(), mediator: new MediatorModule({ autoAcceptMediationRequests: true }) } +) describe('out of band with mediation set up with provision method', () => { const makeConnectionConfig = { @@ -41,32 +60,19 @@ describe('out of band with mediation set up with provision method', () => { let mediatorOutOfBandInvitation: OutOfBandInvitation | V2OutOfBandInvitation beforeAll(async () => { - const faberMessages = new Subject() - const aliceMessages = new Subject() - const mediatorMessages = new Subject() - const subjectMap = { - 'rxjs:faber': faberMessages, - 'rxjs:alice': aliceMessages, - 'rxjs:mediator': mediatorMessages, - } - mediatorAgent = new Agent(mediatorAgentOptions) - mediatorAgent.registerInboundTransport(new SubjectInboundTransport(mediatorMessages)) - mediatorAgent.registerOutboundTransport(new SubjectOutboundTransport(subjectMap)) - await mediatorAgent.initialize() - + aliceAgent = new Agent(aliceAgentOptions) faberAgent = new Agent(faberAgentOptions) - faberAgent.registerInboundTransport(new SubjectInboundTransport(faberMessages)) - faberAgent.registerOutboundTransport(new SubjectOutboundTransport(subjectMap)) - await faberAgent.initialize() - aliceAgent = new Agent(aliceAgentOptions) - aliceAgent.registerInboundTransport(new SubjectInboundTransport(aliceMessages)) - aliceAgent.registerOutboundTransport(new SubjectOutboundTransport(subjectMap)) - const mediationInvitationResult = await mediatorAgent.oob.createInvitation(makeConnectionConfig) - mediatorOutOfBandInvitation = mediationInvitationResult.outOfBandInvitation! + setupSubjectTransports([mediatorAgent, aliceAgent, faberAgent]) + await mediatorAgent.initialize() await aliceAgent.initialize() + await faberAgent.initialize() + + const mediationOutOfBandRecord = await mediatorAgent.oob.createInvitation(makeConnectionConfig) + mediatorOutOfBandInvitation = mediationOutOfBandRecord.getOutOfBandInvitation() + let { connectionRecord } = await aliceAgent.oob.receiveInvitation(mediatorOutOfBandInvitation) connectionRecord = await aliceAgent.connections.returnWhenIsConnected(connectionRecord!.id) await aliceAgent.mediationRecipient.provision(connectionRecord!) diff --git a/packages/core/tests/oob-mediation.test.ts b/packages/core/tests/oob-mediation.test.ts index 77ad5d162f..d5ca377e45 100644 --- a/packages/core/tests/oob-mediation.test.ts +++ b/packages/core/tests/oob-mediation.test.ts @@ -7,6 +7,7 @@ import { filter, firstValueFrom, map, Subject, timeout } from 'rxjs' import { SubjectInboundTransport } from '../../../tests/transport/SubjectInboundTransport' import { SubjectOutboundTransport } from '../../../tests/transport/SubjectOutboundTransport' +import { getIndySdkModules } from '../../indy-sdk/tests/setupIndySdkModule' import { Agent } from '../src/agent/Agent' import { AgentEventTypes } from '../src/agent/Events' import { DidExchangeState, HandshakeProtocol } from '../src/modules/connections' @@ -17,23 +18,40 @@ import { KeylistUpdateAction, MediationState, MediatorPickupStrategy, + MediationRecipientModule, + MediatorModule, } from '../src/modules/routing' import { getAgentOptions, waitForBasicMessage } from './helpers' -const faberAgentOptions = getAgentOptions('OOB mediation - Faber Agent', { - endpoints: ['rxjs:faber'], -}) -const aliceAgentOptions = getAgentOptions('OOB mediation - Alice Recipient Agent', { - endpoints: ['rxjs:alice'], - // FIXME: discover features returns that we support this protocol, but we don't support all roles - // we should return that we only support the mediator role so we don't have to explicitly declare this - mediatorPickupStrategy: MediatorPickupStrategy.PickUpV1, -}) -const mediatorAgentOptions = getAgentOptions('OOB mediation - Mediator Agent', { - endpoints: ['rxjs:mediator'], - autoAcceptMediationRequests: true, -}) +const faberAgentOptions = getAgentOptions( + 'OOB mediation - Faber Agent', + { + endpoints: ['rxjs:faber'], + }, + getIndySdkModules() +) +const aliceAgentOptions = getAgentOptions( + 'OOB mediation - Alice Recipient Agent', + { + endpoints: ['rxjs:alice'], + }, + { + ...getIndySdkModules(), + mediationRecipient: new MediationRecipientModule({ + // FIXME: discover features returns that we support this protocol, but we don't support all roles + // we should return that we only support the mediator role so we don't have to explicitly declare this + mediatorPickupStrategy: MediatorPickupStrategy.PickUpV1, + }), + } +) +const mediatorAgentOptions = getAgentOptions( + 'OOB mediation - Mediator Agent', + { + endpoints: ['rxjs:mediator'], + }, + { ...getIndySdkModules(), mediator: new MediatorModule({ autoAcceptMediationRequests: true }) } +) describe('out of band with mediation', () => { const makeConnectionConfig = { diff --git a/packages/core/tests/oob.test.ts b/packages/core/tests/oob.test.ts index 1dd540ffb5..eb64b27e51 100644 --- a/packages/core/tests/oob.test.ts +++ b/packages/core/tests/oob.test.ts @@ -1,19 +1,21 @@ /* eslint-disable @typescript-eslint/no-non-null-assertion */ import type { SubjectMessage } from '../../../tests/transport/SubjectInboundTransport' +import type { V1CredentialProtocol } from '../../anoncreds/src' import type { DidCommV1Message } from '../src/didcomm/versions/v1' -import type { CreateOfferOptions, V1CredentialProtocol } from '../src/modules/credentials' +import type { CreateCredentialOfferOptions } from '../src/modules/credentials' import type { AgentMessageReceivedEvent } from '@aries-framework/core' import { Subject } from 'rxjs' import { SubjectInboundTransport } from '../../../tests/transport/SubjectInboundTransport' import { SubjectOutboundTransport } from '../../../tests/transport/SubjectOutboundTransport' +import { getLegacyAnonCredsModules, prepareForAnonCredsIssuance } from '../../anoncreds/tests/legacyAnonCredsSetup' import { Agent } from '../src/agent/Agent' import { AgentEventTypes } from '../src/agent/Events' import { Key } from '../src/crypto' import { AriesFrameworkError } from '../src/error' import { DidExchangeState, HandshakeProtocol } from '../src/modules/connections' -import { AutoAcceptCredential, CredentialState, V1CredentialPreview } from '../src/modules/credentials' +import { AutoAcceptCredential, CredentialState } from '../src/modules/credentials' import { OutOfBandDidCommService } from '../src/modules/oob/domain/OutOfBandDidCommService' import { OutOfBandEventTypes } from '../src/modules/oob/domain/OutOfBandEvents' import { OutOfBandRole } from '../src/modules/oob/domain/OutOfBandRole' @@ -23,14 +25,26 @@ import { DidCommMessageRepository, DidCommMessageRole } from '../src/storage' import { JsonEncoder } from '../src/utils' import { TestMessage } from './TestMessage' -import { getAgentOptions, prepareForIssuance, waitForCredentialRecord } from './helpers' - -const faberAgentOptions = getAgentOptions('Faber Agent OOB', { - endpoints: ['rxjs:faber'], -}) -const aliceAgentOptions = getAgentOptions('Alice Agent OOB', { - endpoints: ['rxjs:alice'], -}) +import { getAgentOptions, waitForCredentialRecord } from './helpers' + +const faberAgentOptions = getAgentOptions( + 'Faber Agent OOB', + { + endpoints: ['rxjs:faber'], + }, + getLegacyAnonCredsModules({ + autoAcceptCredentials: AutoAcceptCredential.ContentApproved, + }) +) +const aliceAgentOptions = getAgentOptions( + 'Alice Agent OOB', + { + endpoints: ['rxjs:alice'], + }, + getLegacyAnonCredsModules({ + autoAcceptCredentials: AutoAcceptCredential.ContentApproved, + }) +) describe('out of band', () => { const makeConnectionConfig = { @@ -38,6 +52,7 @@ describe('out of band', () => { goalCode: 'p2p-messaging', label: 'Faber College', alias: `Faber's connection with Alice`, + imageUrl: 'http://faber.image.url', } const issueCredentialConfig = { @@ -51,9 +66,9 @@ describe('out of band', () => { autoAcceptConnection: false, } - let faberAgent: Agent - let aliceAgent: Agent - let credentialTemplate: CreateOfferOptions<[V1CredentialProtocol]> + let faberAgent: Agent> + let aliceAgent: Agent> + let credentialTemplate: CreateCredentialOfferOptions<[V1CredentialProtocol]> beforeAll(async () => { const faberMessages = new Subject() @@ -74,19 +89,33 @@ describe('out of band', () => { aliceAgent.registerOutboundTransport(new SubjectOutboundTransport(subjectMap)) await aliceAgent.initialize() - const { definition } = await prepareForIssuance(faberAgent, ['name', 'age', 'profile_picture', 'x-ray']) + const { credentialDefinition } = await prepareForAnonCredsIssuance(faberAgent, { + attributeNames: ['name', 'age', 'profile_picture', 'x-ray'], + }) credentialTemplate = { protocolVersion: 'v1', credentialFormats: { indy: { - attributes: V1CredentialPreview.fromRecord({ - name: 'name', - age: 'age', - profile_picture: 'profile_picture', - 'x-ray': 'x-ray', - }).attributes, - credentialDefinitionId: definition.id, + attributes: [ + { + name: 'name', + value: 'name', + }, + { + name: 'age', + value: 'age', + }, + { + name: 'profile_picture', + value: 'profile_picture', + }, + { + name: 'x-ray', + value: 'x-ray', + }, + ], + credentialDefinitionId: credentialDefinition.credentialDefinitionId, }, }, autoAcceptCredential: AutoAcceptCredential.Never, @@ -158,9 +187,12 @@ describe('out of band', () => { expect(outOfBandRecord.state).toBe(OutOfBandState.AwaitResponse) expect(outOfBandRecord.alias).toBe(makeConnectionConfig.alias) expect(outOfBandRecord.reusable).toBe(false) - expect(outOfBandRecord.outOfBandInvitation!.goal).toBe(makeConnectionConfig.goal) - expect(outOfBandRecord.outOfBandInvitation!.goalCode).toBe(makeConnectionConfig.goalCode) - expect(outOfBandRecord.outOfBandInvitation!.label).toBe(makeConnectionConfig.label) + + const outOfBandInvitation = outOfBandRecord.getOutOfBandInvitation() + expect(outOfBandInvitation.goal).toBe(makeConnectionConfig.goal) + expect(outOfBandInvitation.goalCode).toBe(makeConnectionConfig.goalCode) + expect(outOfBandInvitation.label).toBe(makeConnectionConfig.label) + expect(outOfBandInvitation.imageUrl).toBe(makeConnectionConfig.imageUrl) }) test('create OOB message only with handshake', async () => { @@ -301,6 +333,7 @@ describe('out of band', () => { expect(faberAliceConnection?.state).toBe(DidExchangeState.Completed) expect(aliceFaberConnection).toBeConnectedWith(faberAliceConnection) + expect(aliceFaberConnection.imageUrl).toBe(makeConnectionConfig.imageUrl) expect(faberAliceConnection).toBeConnectedWith(aliceFaberConnection) expect(faberAliceConnection.alias).toBe(makeConnectionConfig.alias) }) diff --git a/packages/core/tests/proofs-sub-protocol.test.ts b/packages/core/tests/proofs-sub-protocol.test.ts index 271bd089c3..244eacd496 100644 --- a/packages/core/tests/proofs-sub-protocol.test.ts +++ b/packages/core/tests/proofs-sub-protocol.test.ts @@ -1,33 +1,58 @@ -import type { Agent, ConnectionRecord, ProofExchangeRecord } from '../src' -import type { PresentationPreview } from '../src/modules/proofs/protocol/v1/models/V1PresentationPreview' -import type { CredDefId } from 'indy-sdk' - -import { - ProofAttributeInfo, - AttributeFilter, - ProofPredicateInfo, - PredicateType, -} from '../src/modules/proofs/formats/indy/models' +import type { EventReplaySubject } from './events' +import type { AnonCredsTestsAgent } from '../../anoncreds/tests/legacyAnonCredsSetup' + +import { issueLegacyAnonCredsCredential, setupAnonCredsTests } from '../../anoncreds/tests/legacyAnonCredsSetup' import { ProofState } from '../src/modules/proofs/models/ProofState' import { uuid } from '../src/utils/uuid' -import { setupProofsTest, waitForProofExchangeRecord } from './helpers' +import { waitForProofExchangeRecord } from './helpers' import testLogger from './logger' describe('Present Proof Subprotocol', () => { - let faberAgent: Agent - let aliceAgent: Agent - let credDefId: CredDefId - let faberConnection: ConnectionRecord - let aliceConnection: ConnectionRecord - let aliceProofExchangeRecord: ProofExchangeRecord - let presentationPreview: PresentationPreview + let faberAgent: AnonCredsTestsAgent + let faberReplay: EventReplaySubject + let aliceAgent: AnonCredsTestsAgent + let aliceReplay: EventReplaySubject + let credentialDefinitionId: string + let faberConnectionId: string + let aliceConnectionId: string beforeAll(async () => { testLogger.test('Initializing the agents') - ;({ faberAgent, aliceAgent, credDefId, faberConnection, aliceConnection, presentationPreview } = - await setupProofsTest('Faber agent', 'Alice agent')) - testLogger.test('Issuing second credential') + ;({ + issuerAgent: faberAgent, + issuerReplay: faberReplay, + holderAgent: aliceAgent, + holderReplay: aliceReplay, + credentialDefinitionId, + issuerHolderConnectionId: faberConnectionId, + holderIssuerConnectionId: aliceConnectionId, + } = await setupAnonCredsTests({ + issuerName: 'Faber agent', + holderName: 'Alice agent', + attributeNames: ['name', 'age'], + })) + + await issueLegacyAnonCredsCredential({ + issuerAgent: faberAgent, + issuerReplay: faberReplay, + holderAgent: aliceAgent, + holderReplay: aliceReplay, + issuerHolderConnectionId: faberConnectionId, + offer: { + attributes: [ + { + name: 'name', + value: 'Alice', + }, + { + name: 'age', + value: '50', + }, + ], + credentialDefinitionId, + }, + }) }) afterAll(async () => { @@ -49,17 +74,29 @@ describe('Present Proof Subprotocol', () => { state: ProofState.ProposalReceived, }) - aliceProofExchangeRecord = await aliceAgent.proofs.proposeProof({ - connectionId: aliceConnection.id, + const aliceProofExchangeRecord = await aliceAgent.proofs.proposeProof({ + connectionId: aliceConnectionId, protocolVersion: 'v1', parentThreadId, proofFormats: { indy: { name: 'abc', version: '1.0', - nonce: '947121108704767252195126', - attributes: presentationPreview.attributes, - predicates: presentationPreview.predicates, + attributes: [ + { + name: 'name', + credentialDefinitionId, + value: 'Alice', + }, + ], + predicates: [ + { + credentialDefinitionId, + name: 'age', + predicate: '>=', + threshold: 40, + }, + ], }, }, }) @@ -87,11 +124,8 @@ describe('Present Proof Subprotocol', () => { // Alice retrieves the requested credentials and accepts the presentation request testLogger.test('Alice accepts presentation request from Faber') - const requestedCredentials = await aliceAgent.proofs.autoSelectCredentialsForProofRequest({ + const requestedCredentials = await aliceAgent.proofs.selectCredentialsForRequest({ proofRecordId: aliceProofExchangeRecord.id, - config: { - filterByPresentationPreview: true, - }, }) await aliceAgent.proofs.acceptRequest({ proofRecordId: aliceProofExchangeRecord.id, @@ -107,7 +141,7 @@ describe('Present Proof Subprotocol', () => { // Faber accepts the presentation provided by Alice testLogger.test('Faber accepts the presentation provided by Alice') - await faberAgent.proofs.acceptPresentation(faberProofExchangeRecord.id) + await faberAgent.proofs.acceptPresentation({ proofRecordId: faberProofExchangeRecord.id }) // Alice waits until she received a presentation acknowledgement testLogger.test('Alice waits until she receives a presentation acknowledgement') @@ -122,30 +156,6 @@ describe('Present Proof Subprotocol', () => { const parentThreadId = uuid() testLogger.test('Faber sends presentation request to Alice') - const attributes = { - name: new ProofAttributeInfo({ - name: 'name', - restrictions: [ - new AttributeFilter({ - credentialDefinitionId: credDefId, - }), - ], - }), - } - - const predicates = { - age: new ProofPredicateInfo({ - name: 'age', - predicateType: PredicateType.GreaterThanOrEqualTo, - predicateValue: 50, - restrictions: [ - new AttributeFilter({ - credentialDefinitionId: credDefId, - }), - ], - }), - } - const aliceProofExchangeRecordPromise = waitForProofExchangeRecord(aliceAgent, { parentThreadId, state: ProofState.RequestReceived, @@ -154,16 +164,35 @@ describe('Present Proof Subprotocol', () => { // Faber sends a presentation request to Alice testLogger.test('Faber sends a presentation request to Alice') const faberProofExchangeRecord = await faberAgent.proofs.requestProof({ - connectionId: faberConnection.id, + connectionId: faberConnectionId, parentThreadId, protocolVersion: 'v1', proofFormats: { indy: { name: 'proof-request', version: '1.0', - nonce: '1298236324864', - requestedAttributes: attributes, - requestedPredicates: predicates, + requested_attributes: { + name: { + name: 'name', + restrictions: [ + { + cred_def_id: credentialDefinitionId, + }, + ], + }, + }, + requested_predicates: { + age: { + name: 'age', + p_type: '>=', + p_value: 50, + restrictions: [ + { + cred_def_id: credentialDefinitionId, + }, + ], + }, + }, }, }, }) @@ -181,11 +210,8 @@ describe('Present Proof Subprotocol', () => { // Alice retrieves the requested credentials and accepts the presentation request testLogger.test('Alice accepts presentation request from Faber') - const requestedCredentials = await aliceAgent.proofs.autoSelectCredentialsForProofRequest({ + const requestedCredentials = await aliceAgent.proofs.selectCredentialsForRequest({ proofRecordId: aliceProofExchangeRecord.id, - config: { - filterByPresentationPreview: true, - }, }) await aliceAgent.proofs.acceptRequest({ proofRecordId: aliceProofExchangeRecord.id, @@ -202,7 +228,7 @@ describe('Present Proof Subprotocol', () => { // Faber accepts the presentation testLogger.test('Faber accept the presentation from Alice') - await faberAgent.proofs.acceptPresentation(faberProofExchangeRecord.id) + await faberAgent.proofs.acceptPresentation({ proofRecordId: faberProofExchangeRecord.id }) // Alice waits until she receives a presentation acknowledgement testLogger.test('Alice waits for acceptance by Faber') @@ -224,17 +250,29 @@ describe('Present Proof Subprotocol', () => { state: ProofState.ProposalReceived, }) - aliceProofExchangeRecord = await aliceAgent.proofs.proposeProof({ - connectionId: aliceConnection.id, + const aliceProofExchangeRecord = await aliceAgent.proofs.proposeProof({ + connectionId: aliceConnectionId, protocolVersion: 'v2', parentThreadId, proofFormats: { indy: { name: 'abc', version: '1.0', - nonce: '947121108704767252195126', - attributes: presentationPreview.attributes, - predicates: presentationPreview.predicates, + attributes: [ + { + name: 'name', + credentialDefinitionId, + value: 'Alice', + }, + ], + predicates: [ + { + credentialDefinitionId, + name: 'age', + predicate: '>=', + threshold: 40, + }, + ], }, }, }) @@ -262,11 +300,8 @@ describe('Present Proof Subprotocol', () => { // Alice retrieves the requested credentials and accepts the presentation request testLogger.test('Alice accepts presentation request from Faber') - const requestedCredentials = await aliceAgent.proofs.autoSelectCredentialsForProofRequest({ + const requestedCredentials = await aliceAgent.proofs.selectCredentialsForRequest({ proofRecordId: aliceProofExchangeRecord.id, - config: { - filterByPresentationPreview: true, - }, }) await aliceAgent.proofs.acceptRequest({ proofRecordId: aliceProofExchangeRecord.id, @@ -282,7 +317,7 @@ describe('Present Proof Subprotocol', () => { // Faber accepts the presentation provided by Alice testLogger.test('Faber accepts the presentation provided by Alice') - await faberAgent.proofs.acceptPresentation(faberProofExchangeRecord.id) + await faberAgent.proofs.acceptPresentation({ proofRecordId: faberProofExchangeRecord.id }) // Alice waits until she received a presentation acknowledgement testLogger.test('Alice waits until she receives a presentation acknowledgement') @@ -297,30 +332,6 @@ describe('Present Proof Subprotocol', () => { const parentThreadId = uuid() testLogger.test('Faber sends presentation request to Alice') - const attributes = { - name: new ProofAttributeInfo({ - name: 'name', - restrictions: [ - new AttributeFilter({ - credentialDefinitionId: credDefId, - }), - ], - }), - } - - const predicates = { - age: new ProofPredicateInfo({ - name: 'age', - predicateType: PredicateType.GreaterThanOrEqualTo, - predicateValue: 50, - restrictions: [ - new AttributeFilter({ - credentialDefinitionId: credDefId, - }), - ], - }), - } - const aliceProofExchangeRecordPromise = waitForProofExchangeRecord(aliceAgent, { parentThreadId, state: ProofState.RequestReceived, @@ -329,16 +340,35 @@ describe('Present Proof Subprotocol', () => { // Faber sends a presentation request to Alice testLogger.test('Faber sends a presentation request to Alice') const faberProofExchangeRecord = await faberAgent.proofs.requestProof({ - connectionId: faberConnection.id, + connectionId: faberConnectionId, parentThreadId, protocolVersion: 'v2', proofFormats: { indy: { name: 'proof-request', version: '1.0', - nonce: '1298236324864', - requestedAttributes: attributes, - requestedPredicates: predicates, + requested_attributes: { + name: { + name: 'name', + restrictions: [ + { + cred_def_id: credentialDefinitionId, + }, + ], + }, + }, + requested_predicates: { + age: { + name: 'age', + p_type: '>=', + p_value: 50, + restrictions: [ + { + cred_def_id: credentialDefinitionId, + }, + ], + }, + }, }, }, }) @@ -356,11 +386,8 @@ describe('Present Proof Subprotocol', () => { // Alice retrieves the requested credentials and accepts the presentation request testLogger.test('Alice accepts presentation request from Faber') - const requestedCredentials = await aliceAgent.proofs.autoSelectCredentialsForProofRequest({ + const requestedCredentials = await aliceAgent.proofs.selectCredentialsForRequest({ proofRecordId: aliceProofExchangeRecord.id, - config: { - filterByPresentationPreview: true, - }, }) await aliceAgent.proofs.acceptRequest({ proofRecordId: aliceProofExchangeRecord.id, @@ -377,7 +404,7 @@ describe('Present Proof Subprotocol', () => { // Faber accepts the presentation testLogger.test('Faber accept the presentation from Alice') - await faberAgent.proofs.acceptPresentation(faberProofExchangeRecord.id) + await faberAgent.proofs.acceptPresentation({ proofRecordId: faberProofExchangeRecord.id }) // Alice waits until she receives a presentation acknowledgement testLogger.test('Alice waits for acceptance by Faber') diff --git a/packages/core/tests/transport.ts b/packages/core/tests/transport.ts new file mode 100644 index 0000000000..2577fdd428 --- /dev/null +++ b/packages/core/tests/transport.ts @@ -0,0 +1,18 @@ +import type { SubjectMessage } from '../../../tests/transport/SubjectInboundTransport' +import type { Agent } from '../src' + +import { Subject } from 'rxjs' + +import { SubjectInboundTransport } from '../../../tests/transport/SubjectInboundTransport' +import { SubjectOutboundTransport } from '../../../tests/transport/SubjectOutboundTransport' + +export function setupSubjectTransports(agents: Agent[]) { + const subjectMap: Record> = {} + + for (const agent of agents) { + const messages = new Subject() + subjectMap[agent.config.endpoints[0]] = messages + agent.registerInboundTransport(new SubjectInboundTransport(messages)) + agent.registerOutboundTransport(new SubjectOutboundTransport(subjectMap)) + } +} diff --git a/packages/core/tests/v1-connectionless-proofs.test.ts b/packages/core/tests/v1-connectionless-proofs.test.ts deleted file mode 100644 index 98ba142e8c..0000000000 --- a/packages/core/tests/v1-connectionless-proofs.test.ts +++ /dev/null @@ -1,392 +0,0 @@ -import type { SubjectMessage } from '../../../tests/transport/SubjectInboundTransport' -import type { ProofStateChangedEvent } from '../src/modules/proofs' - -import { Subject, ReplaySubject } from 'rxjs' - -import { SubjectInboundTransport } from '../../../tests/transport/SubjectInboundTransport' -import { SubjectOutboundTransport } from '../../../tests/transport/SubjectOutboundTransport' -import { Agent } from '../src/agent/Agent' -import { V1Attachment, V1AttachmentData } from '../src/decorators/attachment/V1Attachment' -import { HandshakeProtocol } from '../src/modules/connections' -import { V1CredentialPreview } from '../src/modules/credentials' -import { - PredicateType, - ProofState, - ProofAttributeInfo, - AttributeFilter, - ProofPredicateInfo, - AutoAcceptProof, - ProofEventTypes, -} from '../src/modules/proofs' -import { MediatorPickupStrategy } from '../src/modules/routing' -import { LinkedAttachment } from '../src/utils/LinkedAttachment' -import { uuid } from '../src/utils/uuid' - -import { - getAgentOptions, - issueCredential, - makeConnection, - prepareForIssuance, - setupProofsTest, - waitForProofExchangeRecordSubject, -} from './helpers' -import testLogger from './logger' - -describe('Present Proof', () => { - let agents: Agent[] - - afterEach(async () => { - for (const agent of agents) { - await agent.shutdown() - await agent.wallet.delete() - } - }) - - test('Faber starts with connection-less proof requests to Alice', async () => { - const { aliceAgent, faberAgent, aliceReplay, credDefId, faberReplay } = await setupProofsTest( - 'Faber connection-less Proofs', - 'Alice connection-less Proofs', - AutoAcceptProof.Never - ) - agents = [aliceAgent, faberAgent] - testLogger.test('Faber sends presentation request to Alice') - - const attributes = { - name: new ProofAttributeInfo({ - name: 'name', - restrictions: [ - new AttributeFilter({ - credentialDefinitionId: credDefId, - }), - ], - }), - } - - const predicates = { - age: new ProofPredicateInfo({ - name: 'age', - predicateType: PredicateType.GreaterThanOrEqualTo, - predicateValue: 50, - restrictions: [ - new AttributeFilter({ - credentialDefinitionId: credDefId, - }), - ], - }), - } - - let aliceProofExchangeRecordPromise = waitForProofExchangeRecordSubject(aliceReplay, { - state: ProofState.RequestReceived, - }) - - // eslint-disable-next-line prefer-const - let { proofRecord: faberProofExchangeRecord, message } = await faberAgent.proofs.createRequest({ - protocolVersion: 'v1', - proofFormats: { - indy: { - name: 'test-proof-request', - version: '1.0', - nonce: '12345678901', - requestedAttributes: attributes, - requestedPredicates: predicates, - }, - }, - }) - - const { message: requestMessage } = await faberAgent.oob.createLegacyConnectionlessInvitation({ - recordId: faberProofExchangeRecord.id, - message, - domain: 'https://a-domain.com', - }) - await aliceAgent.receiveMessage(requestMessage.toJSON()) - - testLogger.test('Alice waits for presentation request from Faber') - let aliceProofExchangeRecord = await aliceProofExchangeRecordPromise - - testLogger.test('Alice accepts presentation request from Faber') - const requestedCredentials = await aliceAgent.proofs.autoSelectCredentialsForProofRequest({ - proofRecordId: aliceProofExchangeRecord.id, - config: { - filterByPresentationPreview: true, - }, - }) - - const faberProofExchangeRecordPromise = waitForProofExchangeRecordSubject(faberReplay, { - threadId: aliceProofExchangeRecord.threadId, - state: ProofState.PresentationReceived, - }) - - await aliceAgent.proofs.acceptRequest({ - proofRecordId: aliceProofExchangeRecord.id, - proofFormats: { indy: requestedCredentials.proofFormats.indy }, - }) - - testLogger.test('Faber waits for presentation from Alice') - faberProofExchangeRecord = await faberProofExchangeRecordPromise - - // assert presentation is valid - expect(faberProofExchangeRecord.isVerified).toBe(true) - - aliceProofExchangeRecordPromise = waitForProofExchangeRecordSubject(aliceReplay, { - threadId: aliceProofExchangeRecord.threadId, - state: ProofState.Done, - }) - - // Faber accepts presentation - await faberAgent.proofs.acceptPresentation(faberProofExchangeRecord.id) - - // Alice waits till it receives presentation ack - aliceProofExchangeRecord = await aliceProofExchangeRecordPromise - }) - - test('Faber starts with connection-less proof requests to Alice with auto-accept enabled', async () => { - testLogger.test('Faber sends presentation request to Alice') - - const { aliceAgent, faberAgent, aliceReplay, credDefId, faberReplay } = await setupProofsTest( - 'Faber connection-less Proofs - Auto Accept', - 'Alice connection-less Proofs - Auto Accept', - AutoAcceptProof.Always - ) - - agents = [aliceAgent, faberAgent] - - const attributes = { - name: new ProofAttributeInfo({ - name: 'name', - restrictions: [ - new AttributeFilter({ - credentialDefinitionId: credDefId, - }), - ], - }), - } - - const predicates = { - age: new ProofPredicateInfo({ - name: 'age', - predicateType: PredicateType.GreaterThanOrEqualTo, - predicateValue: 50, - restrictions: [ - new AttributeFilter({ - credentialDefinitionId: credDefId, - }), - ], - }), - } - - const aliceProofExchangeRecordPromise = waitForProofExchangeRecordSubject(aliceReplay, { - state: ProofState.Done, - }) - - const faberProofExchangeRecordPromise = waitForProofExchangeRecordSubject(faberReplay, { - state: ProofState.Done, - }) - - // eslint-disable-next-line prefer-const - let { message, proofRecord: faberProofExchangeRecord } = await faberAgent.proofs.createRequest({ - protocolVersion: 'v1', - proofFormats: { - indy: { - name: 'test-proof-request', - version: '1.0', - nonce: '12345678901', - requestedAttributes: attributes, - requestedPredicates: predicates, - }, - }, - autoAcceptProof: AutoAcceptProof.ContentApproved, - }) - - const { message: requestMessage } = await faberAgent.oob.createLegacyConnectionlessInvitation({ - recordId: faberProofExchangeRecord.id, - message, - domain: 'https://a-domain.com', - }) - - await aliceAgent.receiveMessage(requestMessage.toJSON()) - - await aliceProofExchangeRecordPromise - - await faberProofExchangeRecordPromise - }) - - test('Faber starts with connection-less proof requests to Alice with auto-accept enabled and both agents having a mediator', async () => { - testLogger.test('Faber sends presentation request to Alice') - - const credentialPreview = V1CredentialPreview.fromRecord({ - name: 'John', - age: '99', - }) - - const unique = uuid().substring(0, 4) - - const mediatorAgentOptions = getAgentOptions(`Connectionless proofs with mediator Mediator-${unique}`, { - autoAcceptMediationRequests: true, - endpoints: ['rxjs:mediator'], - }) - - const faberMessages = new Subject() - const aliceMessages = new Subject() - const mediatorMessages = new Subject() - - const subjectMap = { - 'rxjs:mediator': mediatorMessages, - } - - // Initialize mediator - const mediatorAgent = new Agent(mediatorAgentOptions) - mediatorAgent.registerOutboundTransport(new SubjectOutboundTransport(subjectMap)) - mediatorAgent.registerInboundTransport(new SubjectInboundTransport(mediatorMessages)) - await mediatorAgent.initialize() - - const faberMediationOutOfBandRecord = await mediatorAgent.oob.createInvitation({ - label: 'faber invitation', - handshakeProtocols: [HandshakeProtocol.Connections], - }) - - const aliceMediationOutOfBandRecord = await mediatorAgent.oob.createInvitation({ - label: 'alice invitation', - handshakeProtocols: [HandshakeProtocol.Connections], - }) - - const faberAgentOptions = getAgentOptions(`Connectionless proofs with mediator Faber-${unique}`, { - autoAcceptProofs: AutoAcceptProof.Always, - mediatorConnectionsInvite: faberMediationOutOfBandRecord.outOfBandInvitation?.toUrl({ - domain: 'https://example.com', - }), - mediatorPickupStrategy: MediatorPickupStrategy.PickUpV1, - }) - - const aliceAgentOptions = getAgentOptions(`Connectionless proofs with mediator Alice-${unique}`, { - autoAcceptProofs: AutoAcceptProof.Always, - mediatorConnectionsInvite: aliceMediationOutOfBandRecord.outOfBandInvitation?.toUrl({ - domain: 'https://example.com', - }), - mediatorPickupStrategy: MediatorPickupStrategy.PickUpV1, - }) - - const faberAgent = new Agent(faberAgentOptions) - faberAgent.registerOutboundTransport(new SubjectOutboundTransport(subjectMap)) - faberAgent.registerInboundTransport(new SubjectInboundTransport(faberMessages)) - await faberAgent.initialize() - - const aliceAgent = new Agent(aliceAgentOptions) - aliceAgent.registerOutboundTransport(new SubjectOutboundTransport(subjectMap)) - aliceAgent.registerInboundTransport(new SubjectInboundTransport(aliceMessages)) - await aliceAgent.initialize() - - agents = [aliceAgent, faberAgent, mediatorAgent] - - const { definition } = await prepareForIssuance(faberAgent, ['name', 'age', 'image_0', 'image_1']) - - const [faberConnection, aliceConnection] = await makeConnection(faberAgent, aliceAgent) - expect(faberConnection.isReady).toBe(true) - expect(aliceConnection.isReady).toBe(true) - - await issueCredential({ - issuerAgent: faberAgent, - issuerConnectionId: faberConnection.id, - holderAgent: aliceAgent, - credentialTemplate: { - credentialDefinitionId: definition.id, - attributes: credentialPreview.attributes, - linkedAttachments: [ - new LinkedAttachment({ - name: 'image_0', - attachment: new V1Attachment({ - filename: 'picture-of-a-cat.png', - data: new V1AttachmentData({ base64: 'cGljdHVyZSBvZiBhIGNhdA==' }), - }), - }), - new LinkedAttachment({ - name: 'image_1', - attachment: new V1Attachment({ - filename: 'picture-of-a-dog.png', - data: new V1AttachmentData({ base64: 'UGljdHVyZSBvZiBhIGRvZw==' }), - }), - }), - ], - }, - }) - const faberReplay = new ReplaySubject() - const aliceReplay = new ReplaySubject() - - faberAgent.events.observable(ProofEventTypes.ProofStateChanged).subscribe(faberReplay) - aliceAgent.events.observable(ProofEventTypes.ProofStateChanged).subscribe(aliceReplay) - - const attributes = { - name: new ProofAttributeInfo({ - name: 'name', - restrictions: [ - new AttributeFilter({ - credentialDefinitionId: definition.id, - }), - ], - }), - } - - const predicates = { - age: new ProofPredicateInfo({ - name: 'age', - predicateType: PredicateType.GreaterThanOrEqualTo, - predicateValue: 50, - restrictions: [ - new AttributeFilter({ - credentialDefinitionId: definition.id, - }), - ], - }), - } - - const aliceProofExchangeRecordPromise = waitForProofExchangeRecordSubject(aliceReplay, { - state: ProofState.Done, - }) - - const faberProofExchangeRecordPromise = waitForProofExchangeRecordSubject(faberReplay, { - state: ProofState.Done, - }) - - // eslint-disable-next-line prefer-const - let { message, proofRecord: faberProofExchangeRecord } = await faberAgent.proofs.createRequest({ - protocolVersion: 'v1', - proofFormats: { - indy: { - name: 'test-proof-request', - version: '1.0', - nonce: '12345678901', - requestedAttributes: attributes, - requestedPredicates: predicates, - }, - }, - autoAcceptProof: AutoAcceptProof.ContentApproved, - }) - - const { message: requestMessage } = await faberAgent.oob.createLegacyConnectionlessInvitation({ - recordId: faberProofExchangeRecord.id, - message, - domain: 'https://a-domain.com', - }) - - const mediationRecord = await faberAgent.mediationRecipient.findDefaultMediator() - if (!mediationRecord) { - throw new Error('Faber agent has no default mediator') - } - - expect(requestMessage).toMatchObject({ - service: { - recipientKeys: [expect.any(String)], - routingKeys: mediationRecord.routingKeys, - serviceEndpoint: mediationRecord.endpoint, - }, - }) - - await aliceAgent.receiveMessage(requestMessage.toJSON()) - - await aliceProofExchangeRecordPromise - - await faberProofExchangeRecordPromise - - await aliceAgent.mediationRecipient.stopMessagePickup() - await faberAgent.mediationRecipient.stopMessagePickup() - }) -}) diff --git a/packages/core/tests/v1-proofs-auto-accept.test.ts b/packages/core/tests/v1-proofs-auto-accept.test.ts deleted file mode 100644 index 6c222e70e9..0000000000 --- a/packages/core/tests/v1-proofs-auto-accept.test.ts +++ /dev/null @@ -1,215 +0,0 @@ -import type { Agent, ConnectionRecord } from '../src' -import type { PresentationPreview } from '../src/modules/proofs/protocol/v1/models/V1PresentationPreview' - -import { - AutoAcceptProof, - ProofState, - ProofAttributeInfo, - AttributeFilter, - ProofPredicateInfo, - PredicateType, -} from '../src' - -import { setupProofsTest, waitForProofExchangeRecord } from './helpers' -import testLogger from './logger' - -describe('Auto accept present proof', () => { - let faberAgent: Agent - let aliceAgent: Agent - let credDefId: string - let faberConnection: ConnectionRecord - let aliceConnection: ConnectionRecord - let presentationPreview: PresentationPreview - - describe('Auto accept on `always`', () => { - beforeAll(async () => { - ;({ faberAgent, aliceAgent, credDefId, faberConnection, aliceConnection, presentationPreview } = - await setupProofsTest( - 'Faber Auto Accept Always Proofs', - 'Alice Auto Accept Always Proofs', - AutoAcceptProof.Always - )) - }) - afterAll(async () => { - await faberAgent.shutdown() - await faberAgent.wallet.delete() - await aliceAgent.shutdown() - await aliceAgent.wallet.delete() - }) - - test('Alice starts with proof proposal to Faber, both with autoAcceptProof on `always`', async () => { - testLogger.test('Alice sends presentation proposal to Faber') - - await aliceAgent.proofs.proposeProof({ - connectionId: aliceConnection.id, - protocolVersion: 'v1', - proofFormats: { - indy: { - nonce: '58d223e5-fc4d-4448-b74c-5eb11c6b558f', - name: 'abc', - version: '1.0', - attributes: presentationPreview.attributes, - predicates: presentationPreview.predicates, - }, - }, - }) - - testLogger.test('Faber waits for presentation from Alice') - testLogger.test('Alice waits till it receives presentation ack') - await Promise.all([ - waitForProofExchangeRecord(faberAgent, { state: ProofState.Done }), - waitForProofExchangeRecord(aliceAgent, { state: ProofState.Done }), - ]) - }) - - test('Faber starts with proof requests to Alice, both with autoAcceptProof on `always`', async () => { - testLogger.test('Faber sends presentation request to Alice') - const attributes = { - name: new ProofAttributeInfo({ - name: 'name', - restrictions: [ - new AttributeFilter({ - credentialDefinitionId: credDefId, - }), - ], - }), - } - const predicates = { - age: new ProofPredicateInfo({ - name: 'age', - predicateType: PredicateType.GreaterThanOrEqualTo, - predicateValue: 50, - restrictions: [ - new AttributeFilter({ - credentialDefinitionId: credDefId, - }), - ], - }), - } - - await faberAgent.proofs.requestProof({ - protocolVersion: 'v1', - connectionId: faberConnection.id, - proofFormats: { - indy: { - name: 'proof-request', - version: '1.0', - nonce: '1298236324864', - requestedAttributes: attributes, - requestedPredicates: predicates, - }, - }, - }) - - testLogger.test('Faber waits for presentation from Alice') - await Promise.all([ - waitForProofExchangeRecord(faberAgent, { state: ProofState.Done }), - waitForProofExchangeRecord(aliceAgent, { state: ProofState.Done }), - ]) - }) - }) - - describe('Auto accept on `contentApproved`', () => { - beforeAll(async () => { - testLogger.test('Initializing the agents') - ;({ faberAgent, aliceAgent, credDefId, faberConnection, aliceConnection, presentationPreview } = - await setupProofsTest( - 'Faber Auto Accept Content Approved Proofs', - 'Alice Auto Accept Content Approved Proofs', - AutoAcceptProof.ContentApproved - )) - }) - afterAll(async () => { - testLogger.test('Shutting down both agents') - await faberAgent.shutdown() - await faberAgent.wallet.delete() - await aliceAgent.shutdown() - await aliceAgent.wallet.delete() - }) - - test('Alice starts with proof proposal to Faber, both with autoacceptproof on `contentApproved`', async () => { - testLogger.test('Alice sends presentation proposal to Faber') - - const aliceProofExchangeRecord = await aliceAgent.proofs.proposeProof({ - connectionId: aliceConnection.id, - protocolVersion: 'v1', - proofFormats: { - indy: { - nonce: '1298236324864', - name: 'abc', - version: '1.0', - attributes: presentationPreview.attributes, - predicates: presentationPreview.predicates, - }, - }, - }) - - testLogger.test('Faber waits for presentation proposal from Alice') - const faberProofExchangeRecord = await waitForProofExchangeRecord(faberAgent, { - threadId: aliceProofExchangeRecord.threadId, - state: ProofState.ProposalReceived, - }) - - testLogger.test('Faber accepts presentation proposal from Alice') - await faberAgent.proofs.acceptProposal({ proofRecordId: faberProofExchangeRecord.id }) - - await Promise.all([ - waitForProofExchangeRecord(aliceAgent, { state: ProofState.Done }), - waitForProofExchangeRecord(faberAgent, { state: ProofState.Done }), - ]) - }) - - test('Faber starts with proof requests to Alice, both with autoacceptproof on `contentApproved`', async () => { - testLogger.test('Faber sends presentation request to Alice') - const attributes = { - name: new ProofAttributeInfo({ - name: 'name', - restrictions: [ - new AttributeFilter({ - credentialDefinitionId: credDefId, - }), - ], - }), - } - const predicates = { - age: new ProofPredicateInfo({ - name: 'age', - predicateType: PredicateType.GreaterThanOrEqualTo, - predicateValue: 50, - restrictions: [ - new AttributeFilter({ - credentialDefinitionId: credDefId, - }), - ], - }), - } - - await faberAgent.proofs.requestProof({ - protocolVersion: 'v1', - connectionId: faberConnection.id, - proofFormats: { - indy: { - name: 'proof-request', - version: '1.0', - nonce: '1298236324866', - requestedAttributes: attributes, - requestedPredicates: predicates, - }, - }, - }) - - testLogger.test('Alice waits for request from Faber') - const { id: proofRecordId } = await waitForProofExchangeRecord(aliceAgent, { - state: ProofState.RequestReceived, - }) - - const { proofFormats } = await aliceAgent.proofs.autoSelectCredentialsForProofRequest({ proofRecordId }) - await aliceAgent.proofs.acceptRequest({ proofRecordId, proofFormats }) - - await Promise.all([ - waitForProofExchangeRecord(aliceAgent, { state: ProofState.Done }), - waitForProofExchangeRecord(faberAgent, { state: ProofState.Done }), - ]) - }) - }) -}) diff --git a/packages/core/tests/v2-connectionless-proofs.test.ts b/packages/core/tests/v2-connectionless-proofs.test.ts deleted file mode 100644 index a3cbcd3879..0000000000 --- a/packages/core/tests/v2-connectionless-proofs.test.ts +++ /dev/null @@ -1,392 +0,0 @@ -import type { SubjectMessage } from '../../../tests/transport/SubjectInboundTransport' -import type { ProofStateChangedEvent } from '../src/modules/proofs' - -import { Subject, ReplaySubject } from 'rxjs' - -import { SubjectInboundTransport } from '../../../tests/transport/SubjectInboundTransport' -import { SubjectOutboundTransport } from '../../../tests/transport/SubjectOutboundTransport' -import { V1CredentialPreview } from '../src' -import { Agent } from '../src/agent/Agent' -import { V1Attachment, V1AttachmentData } from '../src/decorators/attachment/V1Attachment' -import { HandshakeProtocol } from '../src/modules/connections/models/HandshakeProtocol' -import { - PredicateType, - ProofState, - ProofAttributeInfo, - AttributeFilter, - ProofPredicateInfo, - AutoAcceptProof, - ProofEventTypes, -} from '../src/modules/proofs' -import { MediatorPickupStrategy } from '../src/modules/routing/MediatorPickupStrategy' -import { LinkedAttachment } from '../src/utils/LinkedAttachment' -import { uuid } from '../src/utils/uuid' - -import { - getAgentOptions, - issueCredential, - makeConnection, - prepareForIssuance, - setupProofsTest, - waitForProofExchangeRecordSubject, -} from './helpers' -import testLogger from './logger' - -describe('Present Proof', () => { - let agents: Agent[] - - afterEach(async () => { - for (const agent of agents) { - await agent.shutdown() - await agent.wallet.delete() - } - }) - - test('Faber starts with connection-less proof requests to Alice', async () => { - const { aliceAgent, faberAgent, aliceReplay, credDefId, faberReplay } = await setupProofsTest( - 'Faber connection-less Proofs', - 'Alice connection-less Proofs', - AutoAcceptProof.Never - ) - agents = [aliceAgent, faberAgent] - testLogger.test('Faber sends presentation request to Alice') - - const attributes = { - name: new ProofAttributeInfo({ - name: 'name', - restrictions: [ - new AttributeFilter({ - credentialDefinitionId: credDefId, - }), - ], - }), - } - - const predicates = { - age: new ProofPredicateInfo({ - name: 'age', - predicateType: PredicateType.GreaterThanOrEqualTo, - predicateValue: 50, - restrictions: [ - new AttributeFilter({ - credentialDefinitionId: credDefId, - }), - ], - }), - } - - let aliceProofExchangeRecordPromise = waitForProofExchangeRecordSubject(aliceReplay, { - state: ProofState.RequestReceived, - }) - - // eslint-disable-next-line prefer-const - let { proofRecord: faberProofExchangeRecord, message } = await faberAgent.proofs.createRequest({ - protocolVersion: 'v2', - proofFormats: { - indy: { - name: 'test-proof-request', - version: '1.0', - nonce: '12345678901', - requestedAttributes: attributes, - requestedPredicates: predicates, - }, - }, - }) - - const { message: requestMessage } = await faberAgent.oob.createLegacyConnectionlessInvitation({ - recordId: faberProofExchangeRecord.id, - message, - domain: 'https://a-domain.com', - }) - - await aliceAgent.receiveMessage(requestMessage.toJSON()) - - testLogger.test('Alice waits for presentation request from Faber') - let aliceProofExchangeRecord = await aliceProofExchangeRecordPromise - - testLogger.test('Alice accepts presentation request from Faber') - - const requestedCredentials = await aliceAgent.proofs.autoSelectCredentialsForProofRequest({ - proofRecordId: aliceProofExchangeRecord.id, - config: { - filterByPresentationPreview: true, - }, - }) - - const faberProofExchangeRecordPromise = waitForProofExchangeRecordSubject(faberReplay, { - threadId: aliceProofExchangeRecord.threadId, - state: ProofState.PresentationReceived, - }) - - await aliceAgent.proofs.acceptRequest({ - proofRecordId: aliceProofExchangeRecord.id, - proofFormats: { indy: requestedCredentials.proofFormats.indy }, - }) - - testLogger.test('Faber waits for presentation from Alice') - faberProofExchangeRecord = await faberProofExchangeRecordPromise - - // assert presentation is valid - expect(faberProofExchangeRecord.isVerified).toBe(true) - - aliceProofExchangeRecordPromise = waitForProofExchangeRecordSubject(aliceReplay, { - threadId: aliceProofExchangeRecord.threadId, - state: ProofState.Done, - }) - - // Faber accepts presentation - await faberAgent.proofs.acceptPresentation(faberProofExchangeRecord.id) - - // Alice waits till it receives presentation ack - aliceProofExchangeRecord = await aliceProofExchangeRecordPromise - }) - - test('Faber starts with connection-less proof requests to Alice with auto-accept enabled', async () => { - testLogger.test('Faber sends presentation request to Alice') - - const { aliceAgent, faberAgent, aliceReplay, credDefId, faberReplay } = await setupProofsTest( - 'Faber connection-less Proofs - Auto Accept', - 'Alice connection-less Proofs - Auto Accept', - AutoAcceptProof.Always - ) - - agents = [aliceAgent, faberAgent] - - const attributes = { - name: new ProofAttributeInfo({ - name: 'name', - restrictions: [ - new AttributeFilter({ - credentialDefinitionId: credDefId, - }), - ], - }), - } - - const predicates = { - age: new ProofPredicateInfo({ - name: 'age', - predicateType: PredicateType.GreaterThanOrEqualTo, - predicateValue: 50, - restrictions: [ - new AttributeFilter({ - credentialDefinitionId: credDefId, - }), - ], - }), - } - - const aliceProofExchangeRecordPromise = waitForProofExchangeRecordSubject(aliceReplay, { - state: ProofState.Done, - }) - - const faberProofExchangeRecordPromise = waitForProofExchangeRecordSubject(faberReplay, { - state: ProofState.Done, - }) - - // eslint-disable-next-line prefer-const - let { message, proofRecord: faberProofExchangeRecord } = await faberAgent.proofs.createRequest({ - protocolVersion: 'v2', - proofFormats: { - indy: { - name: 'test-proof-request', - version: '1.0', - nonce: '12345678901', - requestedAttributes: attributes, - requestedPredicates: predicates, - }, - }, - autoAcceptProof: AutoAcceptProof.ContentApproved, - }) - - const { message: requestMessage } = await faberAgent.oob.createLegacyConnectionlessInvitation({ - recordId: faberProofExchangeRecord.id, - message, - domain: 'https://a-domain.com', - }) - await aliceAgent.receiveMessage(requestMessage.toJSON()) - - await aliceProofExchangeRecordPromise - - await faberProofExchangeRecordPromise - }) - - test('Faber starts with connection-less proof requests to Alice with auto-accept enabled and both agents having a mediator', async () => { - testLogger.test('Faber sends presentation request to Alice') - - const credentialPreview = V1CredentialPreview.fromRecord({ - name: 'John', - age: '99', - }) - - const unique = uuid().substring(0, 4) - - const mediatorOptions = getAgentOptions(`Connectionless proofs with mediator Mediator-${unique}`, { - autoAcceptMediationRequests: true, - endpoints: ['rxjs:mediator'], - }) - - const faberMessages = new Subject() - const aliceMessages = new Subject() - const mediatorMessages = new Subject() - - const subjectMap = { - 'rxjs:mediator': mediatorMessages, - } - - // Initialize mediator - const mediatorAgent = new Agent(mediatorOptions) - mediatorAgent.registerOutboundTransport(new SubjectOutboundTransport(subjectMap)) - mediatorAgent.registerInboundTransport(new SubjectInboundTransport(mediatorMessages)) - await mediatorAgent.initialize() - - const faberMediationOutOfBandRecord = await mediatorAgent.oob.createInvitation({ - label: 'faber invitation', - handshakeProtocols: [HandshakeProtocol.Connections], - }) - - const aliceMediationOutOfBandRecord = await mediatorAgent.oob.createInvitation({ - label: 'alice invitation', - handshakeProtocols: [HandshakeProtocol.Connections], - }) - - const faberOptions = getAgentOptions(`Connectionless proofs with mediator Faber-${unique}`, { - autoAcceptProofs: AutoAcceptProof.Always, - mediatorConnectionsInvite: faberMediationOutOfBandRecord.outOfBandInvitation?.toUrl({ - domain: 'https://example.com', - }), - mediatorPickupStrategy: MediatorPickupStrategy.PickUpV1, - }) - - const aliceOptions = getAgentOptions(`Connectionless proofs with mediator Alice-${unique}`, { - autoAcceptProofs: AutoAcceptProof.Always, - // logger: new TestLogger(LogLevel.test), - mediatorConnectionsInvite: aliceMediationOutOfBandRecord.outOfBandInvitation?.toUrl({ - domain: 'https://example.com', - }), - mediatorPickupStrategy: MediatorPickupStrategy.PickUpV1, - }) - - const faberAgent = new Agent(faberOptions) - faberAgent.registerOutboundTransport(new SubjectOutboundTransport(subjectMap)) - faberAgent.registerInboundTransport(new SubjectInboundTransport(faberMessages)) - await faberAgent.initialize() - - const aliceAgent = new Agent(aliceOptions) - aliceAgent.registerOutboundTransport(new SubjectOutboundTransport(subjectMap)) - aliceAgent.registerInboundTransport(new SubjectInboundTransport(aliceMessages)) - await aliceAgent.initialize() - - agents = [aliceAgent, faberAgent, mediatorAgent] - - const { definition } = await prepareForIssuance(faberAgent, ['name', 'age', 'image_0', 'image_1']) - - const [faberConnection, aliceConnection] = await makeConnection(faberAgent, aliceAgent) - expect(faberConnection.isReady).toBe(true) - expect(aliceConnection.isReady).toBe(true) - - // issue credential with two linked attachments - await issueCredential({ - issuerAgent: faberAgent, - issuerConnectionId: faberConnection.id, - holderAgent: aliceAgent, - credentialTemplate: { - credentialDefinitionId: definition.id, - attributes: credentialPreview.attributes, - linkedAttachments: [ - new LinkedAttachment({ - name: 'image_0', - attachment: new V1Attachment({ - filename: 'picture-of-a-cat.png', - data: new V1AttachmentData({ base64: 'cGljdHVyZSBvZiBhIGNhdA==' }), - }), - }), - new LinkedAttachment({ - name: 'image_1', - attachment: new V1Attachment({ - filename: 'picture-of-a-dog.png', - data: new V1AttachmentData({ base64: 'UGljdHVyZSBvZiBhIGRvZw==' }), - }), - }), - ], - }, - }) - const faberReplay = new ReplaySubject() - const aliceReplay = new ReplaySubject() - - faberAgent.events.observable(ProofEventTypes.ProofStateChanged).subscribe(faberReplay) - aliceAgent.events.observable(ProofEventTypes.ProofStateChanged).subscribe(aliceReplay) - - const attributes = { - name: new ProofAttributeInfo({ - name: 'name', - restrictions: [ - new AttributeFilter({ - credentialDefinitionId: definition.id, - }), - ], - }), - } - - const predicates = { - age: new ProofPredicateInfo({ - name: 'age', - predicateType: PredicateType.GreaterThanOrEqualTo, - predicateValue: 50, - restrictions: [ - new AttributeFilter({ - credentialDefinitionId: definition.id, - }), - ], - }), - } - - const aliceProofExchangeRecordPromise = waitForProofExchangeRecordSubject(aliceReplay, { - state: ProofState.Done, - }) - - const faberProofExchangeRecordPromise = waitForProofExchangeRecordSubject(faberReplay, { - state: ProofState.Done, - }) - - // eslint-disable-next-line prefer-const - let { message, proofRecord: faberProofExchangeRecord } = await faberAgent.proofs.createRequest({ - protocolVersion: 'v2', - proofFormats: { - indy: { - name: 'test-proof-request', - version: '1.0', - nonce: '12345678901', - requestedAttributes: attributes, - requestedPredicates: predicates, - }, - }, - autoAcceptProof: AutoAcceptProof.ContentApproved, - }) - - const { message: requestMessage } = await faberAgent.oob.createLegacyConnectionlessInvitation({ - recordId: faberProofExchangeRecord.id, - message, - domain: 'https://a-domain.com', - }) - - const mediationRecord = await faberAgent.mediationRecipient.findDefaultMediator() - if (!mediationRecord) { - throw new Error('Faber agent has no default mediator') - } - - expect(requestMessage).toMatchObject({ - service: { - recipientKeys: [expect.any(String)], - routingKeys: mediationRecord.routingKeys, - serviceEndpoint: mediationRecord.endpoint, - }, - }) - - await aliceAgent.receiveMessage(requestMessage.toJSON()) - - await aliceProofExchangeRecordPromise - - await faberProofExchangeRecordPromise - }) -}) diff --git a/packages/core/tests/v2-proofs-auto-accept.test.ts b/packages/core/tests/v2-proofs-auto-accept.test.ts deleted file mode 100644 index 0ab3d81e13..0000000000 --- a/packages/core/tests/v2-proofs-auto-accept.test.ts +++ /dev/null @@ -1,213 +0,0 @@ -import type { Agent, ConnectionRecord } from '../src' -import type { PresentationPreview } from '../src/modules/proofs/protocol/v1/models/V1PresentationPreview' - -import { - AutoAcceptProof, - ProofState, - ProofAttributeInfo, - AttributeFilter, - ProofPredicateInfo, - PredicateType, -} from '../src' - -import { setupProofsTest, waitForProofExchangeRecord } from './helpers' -import testLogger from './logger' - -describe('Auto accept present proof', () => { - let faberAgent: Agent - let aliceAgent: Agent - let credDefId: string - let faberConnection: ConnectionRecord - let aliceConnection: ConnectionRecord - let presentationPreview: PresentationPreview - - describe('Auto accept on `always`', () => { - beforeAll(async () => { - ;({ faberAgent, aliceAgent, credDefId, faberConnection, aliceConnection, presentationPreview } = - await setupProofsTest( - 'Faber Auto Accept Always Proofs', - 'Alice Auto Accept Always Proofs', - AutoAcceptProof.Always - )) - }) - afterAll(async () => { - await faberAgent.shutdown() - await faberAgent.wallet.delete() - await aliceAgent.shutdown() - await aliceAgent.wallet.delete() - }) - - test('Alice starts with proof proposal to Faber, both with autoAcceptProof on `always`', async () => { - testLogger.test('Alice sends presentation proposal to Faber') - - await aliceAgent.proofs.proposeProof({ - connectionId: aliceConnection.id, - protocolVersion: 'v2', - proofFormats: { - indy: { - nonce: '1298236324864', - name: 'abc', - version: '1.0', - attributes: presentationPreview.attributes, - predicates: presentationPreview.predicates, - }, - }, - }) - - testLogger.test('Faber waits for presentation from Alice') - testLogger.test('Alice waits till it receives presentation ack') - await Promise.all([ - waitForProofExchangeRecord(faberAgent, { state: ProofState.Done }), - waitForProofExchangeRecord(aliceAgent, { state: ProofState.Done }), - ]) - }) - - test('Faber starts with proof requests to Alice, both with autoAcceptProof on `always`', async () => { - testLogger.test('Faber sends presentation request to Alice') - const attributes = { - name: new ProofAttributeInfo({ - name: 'name', - restrictions: [ - new AttributeFilter({ - credentialDefinitionId: credDefId, - }), - ], - }), - } - const predicates = { - age: new ProofPredicateInfo({ - name: 'age', - predicateType: PredicateType.GreaterThanOrEqualTo, - predicateValue: 50, - restrictions: [ - new AttributeFilter({ - credentialDefinitionId: credDefId, - }), - ], - }), - } - - await faberAgent.proofs.requestProof({ - protocolVersion: 'v2', - connectionId: faberConnection.id, - proofFormats: { - indy: { - name: 'proof-request', - version: '1.0', - nonce: '1298236324864', - requestedAttributes: attributes, - requestedPredicates: predicates, - }, - }, - }) - - testLogger.test('Alice waits for presentation from Faber') - testLogger.test('Faber waits till it receives presentation ack') - await Promise.all([ - waitForProofExchangeRecord(faberAgent, { state: ProofState.Done }), - waitForProofExchangeRecord(aliceAgent, { state: ProofState.Done }), - ]) - }) - }) - - describe('Auto accept on `contentApproved`', () => { - beforeAll(async () => { - testLogger.test('Initializing the agents') - ;({ faberAgent, aliceAgent, credDefId, faberConnection, aliceConnection, presentationPreview } = - await setupProofsTest( - 'Faber Auto Accept Content Approved Proofs', - 'Alice Auto Accept Content Approved Proofs', - AutoAcceptProof.ContentApproved - )) - }) - afterAll(async () => { - testLogger.test('Shutting down both agents') - await faberAgent.shutdown() - await faberAgent.wallet.delete() - await aliceAgent.shutdown() - await aliceAgent.wallet.delete() - }) - - test('Alice starts with proof proposal to Faber, both with autoAcceptProof on `contentApproved`', async () => { - testLogger.test('Alice sends presentation proposal to Faber') - - await aliceAgent.proofs.proposeProof({ - connectionId: aliceConnection.id, - protocolVersion: 'v2', - proofFormats: { - indy: { - nonce: '1298236324864', - attributes: presentationPreview.attributes, - predicates: presentationPreview.predicates, - name: 'abc', - version: '1.0', - }, - }, - }) - - const { id: proofRecordId } = await waitForProofExchangeRecord(faberAgent, { - state: ProofState.ProposalReceived, - }) - - testLogger.test('Faber accepts presentation proposal from Alice') - await faberAgent.proofs.acceptProposal({ proofRecordId }) - - await Promise.all([ - waitForProofExchangeRecord(aliceAgent, { state: ProofState.Done }), - waitForProofExchangeRecord(faberAgent, { state: ProofState.Done }), - ]) - }) - - test('Faber starts with proof requests to Alice, both with autoAcceptProof on `contentApproved`', async () => { - testLogger.test('Faber sends presentation request to Alice') - const attributes = { - name: new ProofAttributeInfo({ - name: 'name', - restrictions: [ - new AttributeFilter({ - credentialDefinitionId: credDefId, - }), - ], - }), - } - const predicates = { - age: new ProofPredicateInfo({ - name: 'age', - predicateType: PredicateType.GreaterThanOrEqualTo, - predicateValue: 50, - restrictions: [ - new AttributeFilter({ - credentialDefinitionId: credDefId, - }), - ], - }), - } - - await faberAgent.proofs.requestProof({ - protocolVersion: 'v2', - connectionId: faberConnection.id, - proofFormats: { - indy: { - name: 'proof-request', - version: '1.0', - nonce: '1298236324866', - requestedAttributes: attributes, - requestedPredicates: predicates, - }, - }, - }) - - testLogger.test('Alice waits for request from Faber') - const { id: proofRecordId } = await waitForProofExchangeRecord(aliceAgent, { - state: ProofState.RequestReceived, - }) - const { proofFormats } = await aliceAgent.proofs.autoSelectCredentialsForProofRequest({ proofRecordId }) - await aliceAgent.proofs.acceptRequest({ proofRecordId, proofFormats }) - - await Promise.all([ - waitForProofExchangeRecord(faberAgent, { state: ProofState.Done }), - waitForProofExchangeRecord(aliceAgent, { state: ProofState.Done }), - ]) - }) - }) -}) diff --git a/packages/core/tests/wallet.test.ts b/packages/core/tests/wallet.test.ts index 3362741146..78e089482a 100644 --- a/packages/core/tests/wallet.test.ts +++ b/packages/core/tests/wallet.test.ts @@ -1,6 +1,7 @@ import { tmpdir } from 'os' import path from 'path' +import { getIndySdkModules } from '../../indy-sdk/tests/setupIndySdkModule' import { Agent } from '../src/agent/Agent' import { BasicMessageRepository, BasicMessageRecord, BasicMessageRole } from '../src/modules/basic-messages' import { KeyDerivationMethod } from '../src/types' @@ -11,8 +12,8 @@ import { WalletNotFoundError } from '../src/wallet/error/WalletNotFoundError' import { getAgentOptions } from './helpers' -const aliceAgentOptions = getAgentOptions('wallet-tests-Alice') -const bobAgentOptions = getAgentOptions('wallet-tests-Bob') +const aliceAgentOptions = getAgentOptions('wallet-tests-Alice', {}, getIndySdkModules()) +const bobAgentOptions = getAgentOptions('wallet-tests-Bob', {}, getIndySdkModules()) describe('wallet', () => { let aliceAgent: Agent @@ -150,9 +151,14 @@ describe('wallet', () => { await bobAgent.wallet.initialize({ id: backupWalletName, key: backupWalletName }) // Expect same basic message record to exist in new wallet - expect(await bobBasicMessageRepository.getById(bobAgent.context, basicMessageRecord.id)).toMatchObject( - basicMessageRecord - ) + expect(await bobBasicMessageRepository.getById(bobAgent.context, basicMessageRecord.id)).toMatchObject({ + id: basicMessageRecord.id, + connectionId: basicMessageRecord.connectionId, + content: basicMessageRecord.content, + createdAt: basicMessageRecord.createdAt, + updatedAt: basicMessageRecord.updatedAt, + type: basicMessageRecord.type, + }) }) test('changing wallet key', async () => { diff --git a/packages/didcomm-v2/README.md b/packages/didcomm-v2/README.md deleted file mode 100644 index 2a0d099c27..0000000000 --- a/packages/didcomm-v2/README.md +++ /dev/null @@ -1,57 +0,0 @@ -

-
- Hyperledger Aries logo -

-

Aries Framework JavaScript - DidComm V2 Module

-

- License - typescript - @aries-framework/bbs-signatures version - -

-
- -Aries Framework JavaScript DidComm V2 Module provides an optional addon to Aries Framework JavaScript to support [DID Comm V2 messages](https://identity.foundation/didcomm-messaging/spec/). - -## Installation - -```sh -yarn add @aries-framework/didcomm-v2 -``` - -### Usage - -- NodeJS - [didcomm-node](https://www.npmjs.com/package/didcomm-node) package should be added into project dependencies and passed as a parameter into module constructor. - - ``` - import * as didcomm from 'didcomm-node' - - const didcommV2Module = new DidCommV2Module({ didcomm }) - didcommV2Module.register(this.agent.dependencyManager) - ``` - -- React Native - [@sicpa_open_source/didcomm-react-native](https://www.npmjs.com/package/@sicpa_open_source/didcomm-react-native) package should be added into project dependencies and passed as a parameter into module constructor. - - ``` - import * as didcomm from '@sicpa_open_source/didcomm-react-native' - - const didcommV2Module = new DidCommV2Module({ didcomm }) - didcommV2Module.register(this.agent.dependencyManager) - ``` diff --git a/packages/didcomm-v2/integration-notes.md b/packages/didcomm-v2/integration-notes.md deleted file mode 100644 index 05d648cdf9..0000000000 --- a/packages/didcomm-v2/integration-notes.md +++ /dev/null @@ -1,154 +0,0 @@ -## DIDComm V2 integration notes - -This document contains the plan (what's done, questions, issues) of DIDComm V2 messaging integration into Aries Framework Javascript. - -[DIDComm V2 Specification](https://identity.foundation/didcomm-messaging/spec/) - -### Proof of work - -**State:** Done - -Update [Alice/Faber demo](../../demo/README.md) scripts.\ -Faber can generate DidComm V2 based [Out-of-Band invitation](https://identity.foundation/didcomm-messaging/spec/#invitation) -Alice can accept it and `ping` Faber using DidComm V2 messaging.\ - -> Note: Issuance and Verification protocols are not adopted! So these options not working now. - -### DidComm V2 as independent optional package - -**State:** Done - -- AFJ Core package contains: - - - Definitions for general DIDComm V1/V2 messages. - - Service interfaces for DIDComm V1 / V2 massages encryption - - DIDComm V1 encryption service implementation which is based on indy wallet. - - For DIDComm V2 stub is registered into the dependency injection container. Agent initialization works fine without providing actual implementation. Error will be thrown if DidComm V2 methods are called and there has not been done registration of actual implementation for DidComm V2 encryption service. - - Common `EnvelopeService` injecting DIDComm V1 / V2 services and used by message sender and receiver. - -- `didcomm-v2` package: - - - DIDComm V2 encryption service implementation which is based on [Sicpa didcomm libraries](https://github.com/sicpa-dlab/didcomm-rust). - - [Node](https://www.npmjs.com/package/didcomm-node) or [React Native](https://www.npmjs.com/package/@sicpa_open_source/didcomm-react-native) package must be passed into `DidCommV2Module` module constructor as parameter. - - `didCommV2` modules should be passed into Agent constructor for proper registration in the dependency injection container. - - Example of agent initialization: - - ``` - import * as didcomm from 'didcomm-node' - - const didCommV2Module = new DidCommV2Module({ didcomm }) - const modules = { didCommV2: didCommV2Module } - - const agent = new Agent({ config, modules, dependencies: agentDependencies }) - ``` - -### Private Keys out of Wallet issue - -**State:** To discuss - -`didcomm-v2` package requires resolving of private keys and passing their values into native didcomm libraries. Right now, there is no way to bypass this. - -### Connections - -**State:** Done simple option. Need to discuss - -Right now, DIDComm V2 messaging specification does not provide any definition for DID Exchange protocol. There is only definition for single out-of-band message which should be used for sharing DIDs. -At the same time, all AFJ protocols (issuance, proofs, etc.) require existence of Connection state outOfBandRecord stored in the Wallet. - -Currently, there is implemented straight forward option: - -- Sender can create DidComm V2 specific out-of-band invitation -- Receiver can accept invitation - Connection state outOfBandRecord will be created in the `ready` state. - - > Note: Current Out-of-Band implementation is really trivial and limited. It should be evaluated in the future. - - ``` - // Inviter - const outOfBandRecord = await this.agent.oob.createInvitation({ version: 'v2' }) - const invitationUrl = outOfBandRecord.v2OutOfBandInvitation.toUrl({ domain: `http://localhost:${this.port}` }) - - // Receiver - const { connectionRecord } = await this.agent.oob.receiveInvitationFromUrl(invitationUrl) - // connectionRecord.isReady === true - ``` - -- Note: So that connection will be created only on one side! Inviter still will not be able to trigger protocols. But still will be able to reply on incoming messages. - -We need to discuss whether this approach is acceptable and provide better design on how to make connections if it is not. - -### Mediator - -**State:** There is independent [open sourced implementation of Cloud Mediator agent supporting DidComm V2 protocols](https://github.com/sicpa-dlab/didcomm-v2-mediator-ts). - -AFJ core modules related to Mediator functionality have not been updated to provide mediation for DidComm V2 edge agents. -This work should be done during the next steps. - -### Mediation Recipient - -**State:** Completed initial implementation - -[Repository](https://github.com/decentralized-identity/didcomm.org/tree/main/site/content/protocols) contacting protocols specifications. - -This repository contains protocols adoptions for DidComm V2 messaging: - -- [Mediator Coordination](https://github.com/decentralized-identity/didcomm.org/tree/main/site/content/protocols/mediator-coordination/2.0) -- [Pickup](https://github.com/decentralized-identity/didcomm.org/tree/main/site/content/protocols/pickup/3.0) -- [Routing](https://github.com/decentralized-identity/didcomm.org/tree/main/site/content/protocols/routing/2.0) - -The work devoted to the adoption of these protocols already has been done in the separate [didcommv2-contribution-routing](https://github.com/sicpa-dlab/aries-framework-javascript/tree/didcommv2-contribution-routing) branch. -This branch has been created from the main `didcommv2-contribution` branch but got significantly behind and need to be updated. - -We need to merge general DidComm V2 MR firstly. After that we will actualize the branch with routing support. - -### Peer DID package - -**State:** There is [peer-did-ts](https://www.npmjs.com/package/@sicpa_open_source/peer-did-ts) package providing implementation of the Peer DID method specification in Typescript. The source code for this package has been mainly taken from AFJ Core package and probably should be reworked. - -It will be good to extract sup-packages providing TypeScript implementations for `DidDocument` and `PeerDid` from AFJ Core. - -> Should be postponed and done later in a separate PR - -### Peer DID format - -**State:** Resolved. We found another issue with our new changes and rolled back peer DID changes. There is no other work that needs to be done here. Everything is good now. - -~~There is a contradiction between the two specifications:~~ - -~~- According to the [Peer DID spec](https://identity.foundation/peer-did-method-spec/#multi-key-creation) key ids doesn't have `z` prefix in `did-url` (fragment after #).~~ -~~- According to the [DIDComm spec](https://identity.foundation/didcomm-messaging/spec/#:~:text=%22id%22%3A%20%22did%3Aexample%3A123%23zC9ByQ8aJs8vrNXyDhPHHNNMSHPcaSgNpjjsBYpMMjsTdS%22%2C) key ids must have `z` prefix in `did-url` (fragment after #).~~ - -~~In our understanding, `z` prefix should always be used for multi-base key representation which is currently used there.\ -Sicpa `didcomm-rust` library requests keys from the Wallet by id containing additional `z` letter at the start of the did-url. -If we consider this behaviour wrong, we will need to change `didcomm-rust` libraries.~~ - -~~> Should be postponed if possible and changed later in a separate PR if current behaviour is wrong~~ - -### Protocols adoptions - -**State:** To be done - -- [Trust Ping](https://identity.foundation/didcomm-messaging/spec/#trust-ping-protocol-20) protocol. - - For the DidComm V2 testing and demonstration purpose was added public `sendPing` function to Connection API. Right now, this ping function does not depend on the Connection state outOfBandRecord and requires the passing of sender and receiver DIDs to send DidComm V2-based ping message. - -> Should be postponed and done later in a separate PR - -### Rework Message Sender / Receiver - -**State:** To discuss. - -Right now seems sensible to split Message Sender classes into two subclasses providing functionality for processing of corresponding DidComm message version (V1/V2). -So instead of having sendV1/sendV2 functions there will be call send method of the corresponding subclass. -BUT: right now we do not have full integration of Connection state objects for DidComm V2 protocols. This is one of main cause of adding new `sendV2` methods. In future this issue may go away once we get Connections for DidComm V2. -So it looks lite this kind of refactoring should be postponed. - -Regarding Message Receiver, there is no actually many specific v1/v2 methods. - -> Should be postponed and done later in a separate PR - -### Envelop unpack result - -**State:** To discuss - related to the [comment](https://github.com/hyperledger/aries-framework-javascript/pull/1096#discussion_r1023938917) - -The `DecryptedMessageContext` object returning as result of message unpacking contains keys while for DidComm V2 messaging it rather should be DIDs. For the current implementation we adopted V2 unpack function to return keys as well but in the future we probably need to revise this structure. - -> Should be postponed and done later in a separate PR diff --git a/packages/didcomm-v2/src/DidCommV2Module.ts b/packages/didcomm-v2/src/DidCommV2Module.ts deleted file mode 100644 index 6b73894d51..0000000000 --- a/packages/didcomm-v2/src/DidCommV2Module.ts +++ /dev/null @@ -1,34 +0,0 @@ -import type { DependencyManager, Module } from '@aries-framework/core' -import type { default as didcomm } from 'didcomm' - -import { DidCommV2EnvelopeServiceToken } from '@aries-framework/core' - -import { DidCommV2EnvelopeServiceImpl, DIDCommV2LibraryToken } from './services' -import { DidCommV2DidResolver } from './services/DidCommV2DidResolver' -import { DidCommV2SecretsResolver } from './services/DidCommV2SecretsResolver' - -export interface DidCommV2ModuleParams { - // Should be passed on of packages: - // - NodeJS: `didcomm-node` - https://www.npmjs.com/package/didcomm-node - // - React Native - `@sicpa_open_source/didcomm-react-native` - https://www.npmjs.com/package/@sicpa_open_source/didcomm-react-native - didcomm: typeof didcomm -} - -export class DidCommV2Module implements Module { - private didcomm: typeof didcomm - - /** - * Registers the dependencies of the didcomm-v2 module on the dependency manager. - */ - - public constructor(props: DidCommV2ModuleParams) { - this.didcomm = props.didcomm - } - - public register(dependencyManager: DependencyManager) { - dependencyManager.registerInstance(DIDCommV2LibraryToken, this.didcomm) - dependencyManager.registerSingleton(DidCommV2EnvelopeServiceToken, DidCommV2EnvelopeServiceImpl) - dependencyManager.registerContextScoped(DidCommV2DidResolver) - dependencyManager.registerContextScoped(DidCommV2SecretsResolver) - } -} diff --git a/packages/didcomm-v2/src/index.ts b/packages/didcomm-v2/src/index.ts deleted file mode 100644 index dab9f795a4..0000000000 --- a/packages/didcomm-v2/src/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from './DidCommV2Module' -export { DidCommV2EnvelopeServiceImpl, DIDCommV2LibraryToken } from './services/DidCommV2EnvelopeServiceImpl' diff --git a/packages/didcomm-v2/src/services/DidCommV2DidResolver.ts b/packages/didcomm-v2/src/services/DidCommV2DidResolver.ts deleted file mode 100644 index 24904ca6bd..0000000000 --- a/packages/didcomm-v2/src/services/DidCommV2DidResolver.ts +++ /dev/null @@ -1,141 +0,0 @@ -import type { VerificationMethod, DidDocumentService } from '@aries-framework/core' -import type { DIDDoc, DIDResolver } from 'didcomm' - -import { - injectable, - AgentContext, - DidResolverService, - DidCommV2Service, - DidCommV1Service, - IndyAgentService, -} from '@aries-framework/core' - -@injectable() -export class DidCommV2DidResolver implements DIDResolver { - private agentContext: AgentContext - private didResolverService: DidResolverService - - public constructor(agentContext: AgentContext, didResolverService: DidResolverService) { - this.agentContext = agentContext - this.didResolverService = didResolverService - } - - public async resolve(did: string): Promise { - const result = await this.didResolverService.resolve(this.agentContext, did) - if (!result.didDocument) { - return null - } - - const verificationMethods = result.didDocument.verificationMethod?.map((verificationMethod) => - DidCommV2DidResolver.mapVerificationMethod(verificationMethod) - ) - - const services = result.didDocument.service?.map((service) => DidCommV2DidResolver.mapService(service)) - - const didDod: DIDDoc = { - did: result.didDocument.id, - verification_methods: verificationMethods || [], - services: services || [], - key_agreements: [], - authentications: [], - } - - const keyAgreements = result.didDocument.keyAgreement || [] - for (const keyAgreement of keyAgreements) { - if (typeof keyAgreement === 'string') { - didDod.key_agreements.push(keyAgreement) - } else { - didDod.key_agreements.push(keyAgreement.id) - didDod.verification_methods.push(DidCommV2DidResolver.mapVerificationMethod(keyAgreement)) - } - } - - const authentications = result.didDocument.authentication || [] - for (const authentication of authentications) { - if (typeof authentication === 'string') { - didDod.authentications.push(authentication) - } else { - didDod.authentications.push(authentication.id) - didDod.verification_methods.push(DidCommV2DidResolver.mapVerificationMethod(authentication)) - } - } - - return didDod - } - - private static mapVerificationMethod(verificationMethod: VerificationMethod) { - return { - id: verificationMethod.id, - type: verificationMethod.type, - controller: verificationMethod.controller, - verification_material: verificationMethod.publicKeyBase58 - ? { format: 'Base58', value: verificationMethod.publicKeyBase58 } - : verificationMethod.publicKeyMultibase - ? { format: 'Multibase', value: verificationMethod.publicKeyMultibase } - : verificationMethod.publicKeyHex - ? { format: 'Hex', value: verificationMethod.publicKeyHex } - : verificationMethod.publicKeyJwk - ? { format: 'JWK', value: verificationMethod.publicKeyJwk } - : { - format: 'Other', - value: - verificationMethod.publicKeyPem || - verificationMethod.publicKeyBase64 || - verificationMethod.blockchainAccountId || - verificationMethod.ethereumAddress, - }, - } - } - - private static mapService(service: DidDocumentService) { - if (service instanceof DidCommV2Service) { - return { - id: service.id, - kind: { - DIDCommMessaging: { - service_endpoint: service.serviceEndpoint, - accept: service.accept ?? [], - routing_keys: service.routingKeys ?? [], - }, - }, - } - } else if (service instanceof DidCommV1Service) { - return { - id: service.id, - kind: { - Other: { - type: service.type, - serviceEndpoint: service.serviceEndpoint, - recipientKeys: service.recipientKeys || [], - routingKeys: service.routingKeys, - accept: service.accept, - priority: service.priority, - }, - }, - } - } else if (service instanceof IndyAgentService) { - return { - id: service.id, - kind: { - Other: { - type: service.type, - serviceEndpoint: service.serviceEndpoint, - recipientKeys: service.recipientKeys, - routingKeys: service.routingKeys, - priority: service.priority, - }, - }, - } - } else { - return { - id: service.id, - kind: { - Other: { - type: service.type, - serviceEndpoint: service.serviceEndpoint, - }, - }, - } - } - } -} diff --git a/packages/didcomm-v2/src/services/DidCommV2EnvelopeServiceImpl.ts b/packages/didcomm-v2/src/services/DidCommV2EnvelopeServiceImpl.ts deleted file mode 100644 index ca9ce4f490..0000000000 --- a/packages/didcomm-v2/src/services/DidCommV2EnvelopeServiceImpl.ts +++ /dev/null @@ -1,105 +0,0 @@ -import type { - DidCommV2EnvelopeService, - AgentContext, - DidCommV2Message, - V2PackMessageParams, - EncryptedMessage, - SignedMessage, - DecryptedMessageContext, -} from '@aries-framework/core' -import type { default as didcommLibrary, IMessage } from 'didcomm' - -import { - injectable, - EnvelopeType, - JsonEncoder, - AriesFrameworkError, - Key, - DidCommMessageVersion, - inject, -} from '@aries-framework/core' - -import { DidCommV2DidResolver } from './DidCommV2DidResolver' -import { DidCommV2SecretsResolver } from './DidCommV2SecretsResolver' - -export const DIDCommV2LibraryToken = Symbol('DIDCommV2LibraryToken') - -@injectable() -export class DidCommV2EnvelopeServiceImpl implements DidCommV2EnvelopeService { - private didcomm: typeof didcommLibrary - - public constructor(@inject(DIDCommV2LibraryToken) didcomm: typeof didcommLibrary) { - this.didcomm = didcomm - } - - public async packMessage( - agentContext: AgentContext, - payload: DidCommV2Message, - params: V2PackMessageParams - ): Promise { - const message = new this.didcomm.Message(payload.toJSON() as IMessage) - - const didResolver = agentContext.dependencyManager.resolve(DidCommV2DidResolver) - const secretsResolver = agentContext.dependencyManager.resolve(DidCommV2SecretsResolver) - - if (params.envelopeType === EnvelopeType.Signed && params.signByDid) { - const [encryptedMsg] = await message.pack_signed(params.signByDid, didResolver, secretsResolver) - return JsonEncoder.fromString(encryptedMsg) - } - if ((params.envelopeType === EnvelopeType.Encrypted || !params.envelopeType) && params.toDid) { - const [encryptedMsg] = await message.pack_encrypted( - params.toDid, - params.fromDid || null, - params.signByDid || null, - didResolver, - secretsResolver, - { - messaging_service: params.serviceId, - forward: params.wrapIntoForward, - } - ) - return JsonEncoder.fromString(encryptedMsg) - } - throw new AriesFrameworkError('Unexpected case') - } - - public async unpackMessage( - agentContext: AgentContext, - packedMessage: EncryptedMessage | SignedMessage - ): Promise { - const didResolver = agentContext.dependencyManager.resolve(DidCommV2DidResolver) - const secretsResolver = agentContext.dependencyManager.resolve(DidCommV2SecretsResolver) - - const [unpackedMsg, unpackMetadata] = await this.didcomm.Message.unpack( - JsonEncoder.toString(packedMessage), - didResolver, - secretsResolver, - {} - ) - - let senderKey: Key | undefined = undefined - let recipientKey: Key | undefined = undefined - - try { - // FIXME: DIDComm V2 returns `kid` instead of base58 key. - // We cannot simply create Key object as for DIDComm V1 from base58 representation - // So we use helper parsing kid - // TODO: Properly we should return either DecryptedV1MessageContext or DecryptedV2MessageContext depending on the unpacked message - senderKey = unpackMetadata.encrypted_from_kid ? Key.fromPublicKeyId(unpackMetadata.encrypted_from_kid) : undefined - - recipientKey = - unpackMetadata.encrypted_to_kids?.length && unpackMetadata.encrypted_to_kids[0] - ? Key.fromPublicKeyId(unpackMetadata.encrypted_to_kids[0]) - : undefined - } catch (e) { - // nothing - } - - return { - senderKey, - recipientKey, - plaintextMessage: unpackedMsg.as_value(), - didCommVersion: DidCommMessageVersion.V2, - } - } -} diff --git a/packages/didcomm-v2/src/services/DidCommV2SecretsResolver.ts b/packages/didcomm-v2/src/services/DidCommV2SecretsResolver.ts deleted file mode 100644 index 50f7f4ea07..0000000000 --- a/packages/didcomm-v2/src/services/DidCommV2SecretsResolver.ts +++ /dev/null @@ -1,39 +0,0 @@ -import type { Secret, SecretsResolver } from 'didcomm' - -import { InjectionSymbols, Wallet, inject, injectable, getKeyDidMappingByKeyType, Key } from '@aries-framework/core' - -@injectable() -export class DidCommV2SecretsResolver implements SecretsResolver { - private wallet: Wallet - - public constructor(@inject(InjectionSymbols.Wallet) wallet: Wallet) { - this.wallet = wallet - } - - public async find_secrets(secret_ids: Array): Promise> { - const secrets = [] - for (const secret_id of secret_ids) { - // Workaround: AFJ core stores keys in the Wallet by their base58 representation, so we need to parse kid to get it - const keyId = Key.fromPublicKeyId(secret_id).publicKeyBase58 - const secret = await this.wallet.retrieveKeyPair(keyId) - if (secret) { - secrets.push(secret_id) - } - } - return secrets - } - - public async get_secret(secret_id: string): Promise { - // Workaround: AFJ core stores keys in the Wallet by their base58 representation, so we need to parse kid to get it - const keyId = Key.fromPublicKeyId(secret_id).publicKeyBase58 - const key = await this.wallet.retrieveKeyPair(keyId) - if (!key) return null - - const { supportedVerificationMethodTypes } = getKeyDidMappingByKeyType(key.keyType) - return { - id: secret_id, - type: supportedVerificationMethodTypes[0], - secret_material: { format: 'Base58', value: key.privateKeyBase58 }, - } - } -} diff --git a/packages/didcomm-v2/src/services/index.ts b/packages/didcomm-v2/src/services/index.ts deleted file mode 100644 index d9fcd86b01..0000000000 --- a/packages/didcomm-v2/src/services/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { DIDCommV2LibraryToken, DidCommV2EnvelopeServiceImpl } from './DidCommV2EnvelopeServiceImpl' diff --git a/packages/didcomm-v2/tests/DidCommV2DidResolver.test.ts b/packages/didcomm-v2/tests/DidCommV2DidResolver.test.ts deleted file mode 100644 index 35c1413f72..0000000000 --- a/packages/didcomm-v2/tests/DidCommV2DidResolver.test.ts +++ /dev/null @@ -1,146 +0,0 @@ -import type { DIDDoc } from 'didcomm' - -import { - DidCommV2Service, - DidDocument, - DidDocumentService, - DidResolverService, - IndyAgentService, - VerificationMethod, -} from '@aries-framework/core' - -import { DidCommV2DidResolver } from '../src/services/DidCommV2DidResolver' - -import { getAgentContext } from './helpers' - -const didDocument = new DidDocument({ - id: 'did:example:alice', - keyAgreement: ['did:example:alice#key-x25519'], - verificationMethod: [ - new VerificationMethod({ - id: 'did:example:alice#key-x25519', - type: 'X25519KeyAgreementKey2019', - controller: 'did:example:alice', - publicKeyBase58: '9hFgmPVfmBZwRvFEyniQDBkz9LmV7gDEqytWyGZLmDXE', - }), - ], - service: [ - new DidDocumentService({ - id: 'did:example:alice#mediator', - type: 'Mediator', - serviceEndpoint: 'did:sov:Q4zqM7aXqm7gDQkUVLng9h', - }), - new DidDocumentService({ - id: 'did:example:alice#endpoint', - type: 'endpoint', - serviceEndpoint: 'https://agent.com', - }), - new IndyAgentService({ - id: 'did:example:alice#indy-agent', - serviceEndpoint: 'did:sov:LjgpST2rjsoxYegQDRm7EL', - recipientKeys: ['did:sov:WJz9mHyW9BZksioQnRsrAo#key-agreement-1'], - routingKeys: ['did:sov:mediator1#key-agreement-1', 'did:sov:mediator2#key-agreement-2'], - priority: 5, - }), - new DidCommV2Service({ - id: 'did:example:alice#did-comm-v2', - serviceEndpoint: 'https://agent.com/did-comm-v2', - routingKeys: ['did:example:mediator1#key-x25519', 'did:example:mediator2#key-x25519'], - accept: ['didcomm/v2', 'didcomm/aip2;env=rfc587'], - }), - ], -}) - -const resolveMock = jest.fn().mockResolvedValue({ - didResolutionMetadata: {}, - didDocument: didDocument, - didDocumentMetadata: {}, -}) - -jest.mock('@aries-framework/core', () => { - const original = jest.requireActual('@aries-framework/core') - return { - __esModule: true, - ...original, - DidResolverService: jest.fn().mockImplementation(() => { - return { resolve: resolveMock } - }), - } -}) - -const DidResolverServiceMock = DidResolverService as jest.Mock - -describe('DidCommV2DidResolver', () => { - const agentContext = getAgentContext() - const didResolverService = new DidResolverServiceMock() - - const didCommV2DidResolver = new DidCommV2DidResolver(agentContext, didResolverService) - - it('converts DidDocumentService of Mediator type into didcomm lib Service of Other kind', async () => { - const result = await didCommV2DidResolver.resolve('did:example:alice') - expect(result).not.toBeNull() - - const adaptedDidDocument = result as DIDDoc - expect(adaptedDidDocument.services).toHaveLength(4) - - expect(adaptedDidDocument.services[0].id).toBe('did:example:alice#mediator') - expect(adaptedDidDocument.services[0].kind).toStrictEqual({ - Other: { - type: 'Mediator', - serviceEndpoint: 'did:sov:Q4zqM7aXqm7gDQkUVLng9h', - }, - }) - }) - - it('converts DidDocumentService of endpoint type into didcomm lib Service of Other kind', async () => { - const result = await didCommV2DidResolver.resolve('did:example:alice') - expect(result).not.toBeNull() - - const adaptedDidDocument = result as DIDDoc - expect(adaptedDidDocument.services).toHaveLength(4) - - expect(adaptedDidDocument.services[1].id).toBe('did:example:alice#endpoint') - expect(adaptedDidDocument.services[1].kind).toStrictEqual({ - Other: { - type: 'endpoint', - serviceEndpoint: 'https://agent.com', - }, - }) - }) - - it('converts IndyAgentService into didcomm lib Service of Other kind', async () => { - const result = await didCommV2DidResolver.resolve('did:example:alice') - expect(result).not.toBeNull() - - const adaptedDidDocument = result as DIDDoc - expect(adaptedDidDocument.services).toHaveLength(4) - - expect(adaptedDidDocument.services[2].id).toBe('did:example:alice#indy-agent') - expect(adaptedDidDocument.services[2].kind).toStrictEqual({ - Other: { - type: 'IndyAgent', - serviceEndpoint: 'did:sov:LjgpST2rjsoxYegQDRm7EL', - recipientKeys: ['did:sov:WJz9mHyW9BZksioQnRsrAo#key-agreement-1'], - routingKeys: ['did:sov:mediator1#key-agreement-1', 'did:sov:mediator2#key-agreement-2'], - priority: 5, - }, - }) - }) - - it('converts DidCommV2Service into didcomm lib Service of DIDCommMessaging kind', async () => { - const result = await didCommV2DidResolver.resolve('did:example:alice') - expect(result).not.toBeNull() - - const adaptedDidDocument = result as DIDDoc - expect(adaptedDidDocument.services).toHaveLength(4) - - expect(adaptedDidDocument.services[3].id).toBe('did:example:alice#did-comm-v2') - expect(adaptedDidDocument.services[3].kind).toStrictEqual({ - DIDCommMessaging: { - service_endpoint: 'https://agent.com/did-comm-v2', - accept: ['didcomm/v2', 'didcomm/aip2;env=rfc587'], - routing_keys: ['did:example:mediator1#key-x25519', 'did:example:mediator2#key-x25519'], - }, - }) - }) -}) diff --git a/packages/didcomm-v2/tests/DidCommV2EnvelopeService.test.ts b/packages/didcomm-v2/tests/DidCommV2EnvelopeService.test.ts deleted file mode 100644 index 8bf1b9a2bd..0000000000 --- a/packages/didcomm-v2/tests/DidCommV2EnvelopeService.test.ts +++ /dev/null @@ -1,303 +0,0 @@ -import type { - DidCommV2MessageParams, - DidResolutionResult, - EncryptedMessage, - PlaintextMessage, - AgentContext, -} from '@aries-framework/core' -import type { Secret } from 'didcomm' - -import { - DidCommV2Message, - DidCommV2Service, - DidDocument, - DidResolverService, - isJsonObject, - IsValidMessageType, - parseMessageType, - VerificationMethod, -} from '@aries-framework/core' -import { default as didcomm } from 'didcomm-node' - -import { DidCommV2EnvelopeServiceImpl } from '../src/services' -import { DidCommV2DidResolver } from '../src/services/DidCommV2DidResolver' -import { DidCommV2SecretsResolver } from '../src/services/DidCommV2SecretsResolver' - -import { getAgentContext, mockFunction } from './helpers' - -jest.mock('@aries-framework/core', () => { - const original = jest.requireActual('@aries-framework/core') - return { - __esModule: true, - ...original, - DidResolverService: jest.fn().mockImplementation(() => { - return { resolve: jest.fn() } - }), - } -}) - -const DidResolverServiceMock = DidResolverService as jest.Mock - -jest.mock('../src/services/DidCommV2SecretsResolver') -const SecretsResolverMock = DidCommV2SecretsResolver as jest.Mock - -interface ForwardMessage { - next: string - forwardedMessage: EncryptedMessage -} - -function parseForward(plaintextMessage: PlaintextMessage): ForwardMessage { - expect(plaintextMessage.type).toBe('https://didcomm.org/routing/2.0/forward') - - expect(isJsonObject(plaintextMessage.body)).toBe(true) - const body = plaintextMessage.body as Record - - expect(typeof body.next).toBe('string') - const next = body.next as string - - expect(Array.isArray(plaintextMessage.attachments)).toBe(true) - const attachments = plaintextMessage.attachments as Array - - expect(isJsonObject(attachments[0])).toBe(true) - const attachment = attachments[0] as Record - - expect(isJsonObject(attachment.data)).toBe(true) - const attachmentData = attachment.data as Record - - expect(isJsonObject(attachmentData.json)).toBe(true) - const attachmentDataJson = attachmentData.json as Record - - expect(typeof attachmentDataJson.protected).toBe('string') - expect(typeof attachmentDataJson.iv).toBe('string') - expect(typeof attachmentDataJson.ciphertext).toBe('string') - expect(typeof attachmentDataJson.tag).toBe('string') - const forwardedMessage = attachmentDataJson as EncryptedMessage - - return { - next, - forwardedMessage, - } -} - -type TestMessageParams = DidCommV2MessageParams - -class TestMessage extends DidCommV2Message { - public constructor(options?: TestMessageParams) { - super(options) - } - - @IsValidMessageType(TestMessage.type) - public readonly type = TestMessage.type.messageTypeUri - public static readonly type = parseMessageType('https://didcomm.org/test/2.0/example') -} - -async function didResolutionSuccessResult(didDocument: DidDocument): Promise { - return Promise.resolve({ - didResolutionMetadata: { contentType: 'application/did+ld+json' }, - didDocument, - didDocumentMetadata: {}, - }) -} - -async function didResolutionFailureResult(): Promise { - return Promise.resolve({ - didResolutionMetadata: { - error: 'notFound', - message: `DIDDoc not found.`, - }, - didDocument: null, - didDocumentMetadata: {}, - }) -} - -function oneKeySecretsResolver(secret: Secret) { - const service = new SecretsResolverMock() - mockFunction(service.find_secrets).mockImplementation(async (secret_ids) => - Promise.resolve(secret_ids.filter((value) => value == secret.id)) - ) - mockFunction(service.get_secret).mockImplementation(async (secret_id) => - Promise.resolve(secret_id == secret.id ? secret : null) - ) - return service -} - -const bobDidDocument = new DidDocument({ - id: 'did:example:bob', - keyAgreement: ['did:example:bob#key-x25519'], - verificationMethod: [ - new VerificationMethod({ - id: 'did:example:bob#key-x25519', - type: 'JsonWebKey2020', - controller: 'did:example:bob', - publicKeyJwk: { - kty: 'OKP', - crv: 'X25519', - x: 'GDTrI66K0pFfO54tlCSvfjjNapIs44dzpneBgyx0S3E', - }, - }), - ], - service: [ - new DidCommV2Service({ - id: 'did:example:bob#did-comm-v2', - serviceEndpoint: 'https://agent.com/did-comm-v2', - routingKeys: ['did:example:mediator1', 'did:example:mediator2'], - accept: ['didcomm/v2', 'didcomm/aip2;env=rfc587'], - }), - ], -}) - -const mediator1DidDocument = new DidDocument({ - id: 'did:example:mediator1', - keyAgreement: ['did:example:mediator1#key-x25519'], - verificationMethod: [ - new VerificationMethod({ - id: 'did:example:mediator1#key-x25519', - type: 'JsonWebKey2020', - controller: 'did:example:mediator1', - publicKeyJwk: { - kty: 'OKP', - crv: 'X25519', - x: 'UT9S3F5ep16KSNBBShU2wh3qSfqYjlasZimn0mB8_VM', - }, - }), - ], -}) - -const mediator2DidDocument = new DidDocument({ - id: 'did:example:mediator2', - keyAgreement: ['did:example:mediator2#key-x25519'], - verificationMethod: [ - new VerificationMethod({ - id: 'did:example:mediator2#key-x25519', - type: 'JsonWebKey2020', - controller: 'did:example:mediator2', - publicKeyJwk: { - kty: 'OKP', - crv: 'X25519', - x: '82k2BTUiywKv49fKLZa-WwDi8RBf0tB0M8bvSAUQ3yY', - }, - }), - ], -}) - -const bobSecret = { - id: 'did:example:bob#key-x25519', - type: 'JsonWebKey2020', - secret_material: { - format: 'JWK', - value: { - kty: 'OKP', - crv: 'X25519', - x: 'GDTrI66K0pFfO54tlCSvfjjNapIs44dzpneBgyx0S3E', - d: 'b9NnuOCB0hm7YGNvaE9DMhwH_wjZA1-gWD6dA0JWdL0', - }, - }, -} - -const mediator1Secret = { - id: 'did:example:mediator1#key-x25519', - type: 'JsonWebKey2020', - secret_material: { - format: 'JWK', - value: { - kty: 'OKP', - crv: 'X25519', - x: 'UT9S3F5ep16KSNBBShU2wh3qSfqYjlasZimn0mB8_VM', - d: 'p-vteoF1gopny1HXywt76xz_uC83UUmrgszsI-ThBKk', - }, - }, -} - -const mediator2Secret = { - id: 'did:example:mediator2#key-x25519', - type: 'JsonWebKey2020', - secret_material: { - format: 'JWK', - value: { - kty: 'OKP', - crv: 'X25519', - x: '82k2BTUiywKv49fKLZa-WwDi8RBf0tB0M8bvSAUQ3yY', - d: 'f9WJeuQXEItkGM8shN4dqFr5fLQLBasHnWZ-8dPaSo0', - }, - }, -} - -describe('DIDCommV2EnvelopeService', () => { - const envelopeService = new DidCommV2EnvelopeServiceImpl(didcomm) - - const didResolverService = new DidResolverServiceMock() - - // eslint-disable-next-line @typescript-eslint/no-unused-vars - mockFunction(didResolverService.resolve).mockImplementation(async (agentContext, didUrl, _options) => { - switch (didUrl) { - case bobDidDocument.id: - return didResolutionSuccessResult(bobDidDocument) - case mediator1DidDocument.id: - return didResolutionSuccessResult(mediator1DidDocument) - case mediator2DidDocument.id: - return didResolutionSuccessResult(mediator2DidDocument) - default: - return didResolutionFailureResult() - } - }) - - const aliceAgentContext = getAgentContext() - const aliceDidResolver = new DidCommV2DidResolver(aliceAgentContext, didResolverService) - const aliceSecretsResolver = new SecretsResolverMock() - registerResolversInContext(aliceAgentContext, aliceDidResolver, aliceSecretsResolver) - - const bobAgentContext = getAgentContext() - const bobDidResolver = new DidCommV2DidResolver(bobAgentContext, didResolverService) - const bobSecretsResolver = oneKeySecretsResolver(bobSecret) - registerResolversInContext(bobAgentContext, bobDidResolver, bobSecretsResolver) - - const mediator1AgentContext = getAgentContext() - const mediator1DidResolver = new DidCommV2DidResolver(mediator1AgentContext, didResolverService) - const mediator1SecretsResolver = oneKeySecretsResolver(mediator1Secret) - registerResolversInContext(mediator1AgentContext, mediator1DidResolver, mediator1SecretsResolver) - - const mediator2AgentContext = getAgentContext() - const mediator2DidResolver = new DidCommV2DidResolver(mediator2AgentContext, didResolverService) - const mediator2SecretsResolver = oneKeySecretsResolver(mediator2Secret) - registerResolversInContext(mediator2AgentContext, mediator2DidResolver, mediator2SecretsResolver) - - test("packMessageEncrypted uses recipient's routing", async () => { - const message = new TestMessage({ - to: 'did:example:bob', - body: { - greeting: 'Hello, world!', - }, - }) as DidCommV2Message - - const packedForMediator1 = await envelopeService.packMessage(aliceAgentContext, message, { - toDid: 'did:example:bob', - }) - - const unpackedByMediator1 = await envelopeService.unpackMessage(mediator1AgentContext, packedForMediator1) - const forwardForMediator1 = parseForward(unpackedByMediator1.plaintextMessage) - expect(forwardForMediator1.next).toBe('did:example:mediator2') - const packedForMediator2 = forwardForMediator1.forwardedMessage - - const unpackedByMediator2 = await envelopeService.unpackMessage(mediator2AgentContext, packedForMediator2) - const forwardForMediator2 = parseForward(unpackedByMediator2.plaintextMessage) - expect(forwardForMediator2.next).toBe('did:example:bob') - const packedForBob = forwardForMediator2.forwardedMessage - - const unpackedByBob = await envelopeService.unpackMessage(bobAgentContext, packedForBob) - expect(unpackedByBob.plaintextMessage.type).toBe('https://didcomm.org/test/2.0/example') - - expect(isJsonObject(unpackedByBob.plaintextMessage.body)).toBe(true) - const unpackedMessageBody = unpackedByBob.plaintextMessage.body as Record - - expect(unpackedMessageBody.greeting).toBe('Hello, world!') - }) -}) - -function registerResolversInContext( - agentContext: AgentContext, - didResolver: DidCommV2DidResolver, - secretsResolver: DidCommV2SecretsResolver -) { - agentContext.dependencyManager.registerInstance(DidCommV2DidResolver, didResolver) - agentContext.dependencyManager.registerInstance(DidCommV2SecretsResolver, secretsResolver) -} diff --git a/packages/didcomm-v2/tests/DidCommV2Module.test.ts b/packages/didcomm-v2/tests/DidCommV2Module.test.ts deleted file mode 100644 index c553cb3735..0000000000 --- a/packages/didcomm-v2/tests/DidCommV2Module.test.ts +++ /dev/null @@ -1,28 +0,0 @@ -import type { DependencyManager } from '@aries-framework/core' - -import { DidCommV2EnvelopeServiceToken } from '@aries-framework/core' -import { default as didcomm } from 'didcomm-node' - -import { DIDCommV2LibraryToken, DidCommV2Module, DidCommV2EnvelopeServiceImpl } from '../src' - -const dependencyManager = { - registerInstance: jest.fn(), - registerSingleton: jest.fn(), - registerContextScoped: jest.fn(), -} as unknown as DependencyManager - -describe('DidCommV2Module', () => { - test('registers dependencies on the dependency manager', () => { - const didCommV2Module = new DidCommV2Module({ didcomm }) - didCommV2Module.register(dependencyManager) - - expect(dependencyManager.registerInstance).toHaveBeenCalledTimes(1) - expect(dependencyManager.registerInstance).toHaveBeenCalledWith(DIDCommV2LibraryToken, didcomm) - - expect(dependencyManager.registerSingleton).toHaveBeenCalledTimes(1) - expect(dependencyManager.registerSingleton).toHaveBeenCalledWith( - DidCommV2EnvelopeServiceToken, - DidCommV2EnvelopeServiceImpl - ) - }) -}) diff --git a/packages/didcomm-v2/tests/helpers.ts b/packages/didcomm-v2/tests/helpers.ts deleted file mode 100644 index fc45d856be..0000000000 --- a/packages/didcomm-v2/tests/helpers.ts +++ /dev/null @@ -1,23 +0,0 @@ -import type { Wallet } from '@aries-framework/core' - -import { AgentConfig, AgentContext, DependencyManager, InjectionSymbols } from '@aries-framework/core' - -export function mockFunction any>(fn: T): jest.MockedFunction { - return fn as jest.MockedFunction -} - -export function getAgentContext({ - dependencyManager = new DependencyManager(), - wallet, - agentConfig, - contextCorrelationId = 'mock', -}: { - dependencyManager?: DependencyManager - wallet?: Wallet - agentConfig?: AgentConfig - contextCorrelationId?: string -} = {}) { - if (wallet) dependencyManager.registerInstance(InjectionSymbols.Wallet, wallet) - if (agentConfig) dependencyManager.registerInstance(AgentConfig, agentConfig) - return new AgentContext({ dependencyManager, contextCorrelationId }) -} diff --git a/packages/didcomm-v2/tests/setup.ts b/packages/didcomm-v2/tests/setup.ts deleted file mode 100644 index 00b77cc0fe..0000000000 --- a/packages/didcomm-v2/tests/setup.ts +++ /dev/null @@ -1,3 +0,0 @@ -import 'reflect-metadata' - -jest.setTimeout(30000) diff --git a/packages/indy-sdk-to-askar-migration/README.md b/packages/indy-sdk-to-askar-migration/README.md new file mode 100644 index 0000000000..866e4180b0 --- /dev/null +++ b/packages/indy-sdk-to-askar-migration/README.md @@ -0,0 +1,31 @@ +

+
+ Hyperledger Aries logo +

+

Aries Framework JavaScript Indy SDK To Askar Migration Module

+

+ License + typescript + @aries-framework/indy-sdk-to-askar-migration version + +

+
+ +Indy SDK to Askar migration module for [Aries Framework JavaScript](https://github.com/hyperledger/aries-framework-javascript.git). diff --git a/packages/indy-sdk-to-askar-migration/jest.config.ts b/packages/indy-sdk-to-askar-migration/jest.config.ts new file mode 100644 index 0000000000..93c0197296 --- /dev/null +++ b/packages/indy-sdk-to-askar-migration/jest.config.ts @@ -0,0 +1,13 @@ +import type { Config } from '@jest/types' + +import base from '../../jest.config.base' + +import packageJson from './package.json' + +const config: Config.InitialOptions = { + ...base, + displayName: packageJson.name, + setupFilesAfterEnv: ['./tests/setup.ts'], +} + +export default config diff --git a/packages/indy-sdk-to-askar-migration/package.json b/packages/indy-sdk-to-askar-migration/package.json new file mode 100644 index 0000000000..5b8d30917d --- /dev/null +++ b/packages/indy-sdk-to-askar-migration/package.json @@ -0,0 +1,40 @@ +{ + "name": "@aries-framework/indy-sdk-to-askar-migration", + "main": "build/index", + "types": "build/index", + "version": "0.3.3", + "files": [ + "build" + ], + "license": "Apache-2.0", + "publishConfig": { + "access": "public" + }, + "homepage": "https://github.com/hyperledger/aries-framework-javascript/tree/main/packages/indy-sdk-to-askar-migration", + "repository": { + "type": "git", + "url": "https://github.com/hyperledger/aries-framework-javascript", + "directory": "packages/indy-sdk-to-askar-migration" + }, + "scripts": { + "build": "yarn run clean && yarn run compile", + "clean": "rimraf ./build", + "compile": "tsc -p tsconfig.build.json", + "prepublishOnly": "yarn run build", + "test": "jest" + }, + "dependencies": { + "@aries-framework/anoncreds": "0.3.3", + "@aries-framework/askar": "0.3.3", + "@aries-framework/core": "0.3.3", + "@aries-framework/node": "0.3.3", + "@hyperledger/aries-askar-shared": "^0.1.0-dev.8" + }, + "devDependencies": { + "@hyperledger/aries-askar-nodejs": "^0.1.0-dev.8", + "@aries-framework/indy-sdk": "0.3.3", + "indy-sdk": "^1.16.0-dev-1655", + "rimraf": "^4.4.0", + "typescript": "~4.9.5" + } +} diff --git a/packages/indy-sdk-to-askar-migration/src/IndySdkToAskarMigrationUpdater.ts b/packages/indy-sdk-to-askar-migration/src/IndySdkToAskarMigrationUpdater.ts new file mode 100644 index 0000000000..0af8f27508 --- /dev/null +++ b/packages/indy-sdk-to-askar-migration/src/IndySdkToAskarMigrationUpdater.ts @@ -0,0 +1,422 @@ +import type { AnonCredsCredentialValue } from '@aries-framework/anoncreds' +import type { Agent, FileSystem, WalletConfig } from '@aries-framework/core' +import type { EntryObject } from '@hyperledger/aries-askar-shared' + +import { AnonCredsCredentialRecord, AnonCredsLinkSecretRecord } from '@aries-framework/anoncreds' +import { AskarWallet } from '@aries-framework/askar' +import { InjectionSymbols, KeyDerivationMethod, JsonTransformer, TypedArrayEncoder } from '@aries-framework/core' +import { Migration, Key, KeyAlgs, Store } from '@hyperledger/aries-askar-shared' + +import { IndySdkToAskarMigrationError } from './errors/IndySdkToAskarMigrationError' +import { keyDerivationMethodToStoreKeyMethod, transformFromRecordTagValues } from './utils' + +/** + * + * Migration class to move a wallet form the indy-sdk structure to the new + * askar wallet structure. + * + * Right now, this is ONLY supported within React Native environments AND only sqlite. + * + * The reason it only works within React Native is that we ONLY update the + * keys, masterSecret and credentials for now. If you have an agent in Node.JS + * where it only contains these records, it may be used but we cannot + * guarantee a successful migration. + * + */ +export class IndySdkToAskarMigrationUpdater { + private store?: Store + private walletConfig: WalletConfig + private defaultLinkSecretId: string + private agent: Agent + private dbPath: string + private fs: FileSystem + + private constructor(walletConfig: WalletConfig, agent: Agent, dbPath: string, defaultLinkSecretId?: string) { + this.walletConfig = walletConfig + this.dbPath = dbPath + this.agent = agent + this.fs = this.agent.dependencyManager.resolve(InjectionSymbols.FileSystem) + this.defaultLinkSecretId = defaultLinkSecretId ?? walletConfig.id + } + + public static async initialize({ + dbPath, + agent, + defaultLinkSecretId, + }: { + dbPath: string + agent: Agent + defaultLinkSecretId?: string + }) { + const { + config: { walletConfig }, + } = agent + if (typeof process?.versions?.node !== 'undefined') { + agent.config.logger.warn( + 'Node.JS is not fully supported. Using this will likely leave the wallet in a half-migrated state' + ) + } + + if (!walletConfig) { + throw new IndySdkToAskarMigrationError('Wallet config is required for updating the wallet') + } + + if (walletConfig.storage && walletConfig.storage.type !== 'sqlite') { + throw new IndySdkToAskarMigrationError('Only sqlite wallets are supported, right now') + } + + if (agent.isInitialized) { + throw new IndySdkToAskarMigrationError('Wallet migration can not be done on an initialized agent') + } + + if (!(agent.dependencyManager.resolve(InjectionSymbols.Wallet) instanceof AskarWallet)) { + throw new IndySdkToAskarMigrationError("Wallet on the agent must be of instance 'AskarWallet'") + } + + return new IndySdkToAskarMigrationUpdater(walletConfig, agent, dbPath, defaultLinkSecretId) + } + + /** + * This function migrates the old database to the new structure. + * + * This doubles checks some fields as later it might be possible to run this function + */ + private async migrate() { + const specUri = this.backupFile + const kdfLevel = this.walletConfig.keyDerivationMethod ?? KeyDerivationMethod.Argon2IMod + const walletName = this.walletConfig.id + const walletKey = this.walletConfig.key + const storageType = this.walletConfig.storage?.type ?? 'sqlite' + + if (storageType !== 'sqlite') { + throw new IndySdkToAskarMigrationError("Storage type defined and not of type 'sqlite'") + } + + if (!walletKey) { + throw new IndySdkToAskarMigrationError('Wallet key is not defined in the wallet configuration') + } + + this.agent.config.logger.info('Migration indy-sdk database structure to askar') + await Migration.migrate({ specUri, walletKey, kdfLevel, walletName }) + } + + /* + * Checks whether the destination locations are already used. This might + * happen if you want to migrate a wallet when you already have a new wallet + * with the same id. + */ + private async assertDestinationsAreFree() { + const areAllDestinationsTaken = + (await this.fs.exists(this.backupFile)) || (await this.fs.exists(this.newWalletPath)) + + if (areAllDestinationsTaken) { + throw new IndySdkToAskarMigrationError( + `Files already exist at paths that will be used for backing up. Please remove them manually. Backup path: '${this.backupFile}' and new wallet path: ${this.newWalletPath} ` + ) + } + } + + /** + * Location of the new wallet + */ + private get newWalletPath() { + return `${this.fs.dataPath}/wallet/${this.walletConfig.id}/sqlite.db` + } + + /** + * Temporary backup location of the pre-migrated script + */ + private get backupFile() { + return `${this.fs.tempPath}/${this.walletConfig.id}.db` + } + + private async copyDatabaseWithOptionalWal(src: string, dest: string) { + // Copy the supplied database to the backup destination + await this.fs.copyFile(src, dest) + + // If a wal-file is included, also copy it (https://www.sqlite.org/wal.html) + if (await this.fs.exists(`${src}-wal`)) { + await this.fs.copyFile(`${src}-wal`, `${dest}-wal`) + } + } + + /** + * Backup the database file. This function makes sure that the the indy-sdk + * database file is backed up within our temporary directory path. If some + * error occurs, `this.revertDatabase()` will be called to revert the backup. + */ + private async backupDatabase() { + const src = this.dbPath + const dest = this.backupFile + this.agent.config.logger.trace(`Creating backup from '${src}' to '${dest}'`) + + // Create the directories for the backup + await this.fs.createDirectory(dest) + + // Copy the supplied database to the backup destination, with optional wal-file + await this.copyDatabaseWithOptionalWal(src, dest) + + if (!(await this.fs.exists(dest))) { + throw new IndySdkToAskarMigrationError('Could not locate the new backup file') + } + } + + // Delete the backup as `this.fs.copyFile` only copies and no deletion + // Since we use `tempPath` which is cleared when certain events happen, + // e.g. cron-job and system restart (depending on the os) we could omit + // this call `await this.fs.delete(this.backupFile)`. + private async cleanBackup() { + this.agent.config.logger.trace(`Deleting the backup file at '${this.backupFile}'`) + await this.fs.delete(this.backupFile) + + // Also delete wal-file if it exists + if (await this.fs.exists(`${this.backupFile}-wal`)) { + await this.fs.delete(`${this.backupFile}-wal`) + } + } + + /** + * Move the migrated and updated database file to the new location according + * to the `FileSystem.dataPath`. + */ + private async moveToNewLocation() { + const src = this.backupFile + // New path for the database + const dest = this.newWalletPath + + // create the wallet directory + await this.fs.createDirectory(dest) + + this.agent.config.logger.trace(`Moving upgraded database from ${src} to ${dest}`) + + // Copy the file from the database path to the new location, with optional wal-file + await this.copyDatabaseWithOptionalWal(src, dest) + } + + /** + * Function that updates the values from an indy-sdk structure to the new askar structure. + * + * > NOTE: It is very important that this script is ran before the 0.3.x to + * 0.4.x migration script. This can easily be done by calling this when you + * upgrade, before you initialize the agent with `autoUpdateStorageOnStartup: + * true`. + * + * - Assert that the paths that will be used are free + * - Create a backup of the database + * - Migrate the database to askar structure + * - Update the Keys + * - Update the Master Secret (Link Secret) + * - Update the credentials + * If any of those failed: + * - Revert the database + * - Clear the backup from the temporary directory + */ + public async update() { + await this.assertDestinationsAreFree() + + await this.backupDatabase() + try { + // Migrate the database + await this.migrate() + + const keyMethod = keyDerivationMethodToStoreKeyMethod( + this.walletConfig.keyDerivationMethod ?? KeyDerivationMethod.Argon2IMod + ) + this.store = await Store.open({ uri: `sqlite://${this.backupFile}`, passKey: this.walletConfig.key, keyMethod }) + + // Update the values to reflect the new structure + await this.updateKeys() + await this.updateCredentialDefinitions() + await this.updateMasterSecret() + await this.updateCredentials() + + // Move the migrated and updated file to the expected location for afj + await this.moveToNewLocation() + } catch (err) { + this.agent.config.logger.error(`Migration failed. Restoring state. ${err.message}`) + + throw new IndySdkToAskarMigrationError(`Migration failed. State has been restored. ${err.message}`, { + cause: err.cause, + }) + } finally { + await this.cleanBackup() + } + } + + private async updateKeys() { + if (!this.store) { + throw new IndySdkToAskarMigrationError('Update keys can not be called outside of the `update()` function') + } + + const category = 'Indy::Key' + + this.agent.config.logger.info(`Migrating category: ${category}`) + + let updateCount = 0 + const session = this.store.transaction() + for (;;) { + const txn = await session.open() + const keys = await txn.fetchAll({ category, limit: 50 }) + if (!keys || keys.length === 0) { + await txn.close() + break + } + + for (const row of keys) { + this.agent.config.logger.debug(`Migrating ${row.name} to the new askar format`) + const signKey: string = JSON.parse(row.value as string).signkey + const keySk = TypedArrayEncoder.fromBase58(signKey) + const key = Key.fromSecretBytes({ + algorithm: KeyAlgs.Ed25519, + secretKey: new Uint8Array(keySk.slice(0, 32)), + }) + await txn.insertKey({ name: row.name, key }) + + await txn.remove({ category, name: row.name }) + key.handle.free() + updateCount++ + } + await txn.commit() + } + + this.agent.config.logger.info(`Migrated ${updateCount} records of type ${category}`) + } + + private async updateCredentialDefinitions() { + if (!this.store) { + throw new IndySdkToAskarMigrationError('Update keys can not be called outside of the `update()` function') + } + + const category = 'Indy::CredentialDefinition' + + this.agent.config.logger.info(`Migrating category: ${category}`) + + const session = this.store.transaction() + for (;;) { + const txn = await session.open() + const keys = await txn.fetchAll({ category, limit: 50 }) + if (!keys || keys.length === 0) { + await txn.close() + break + } else { + // This will be entered if there are credential definitions in the wallet + await txn.close() + throw new IndySdkToAskarMigrationError('Migration of Credential Definitions is not yet supported') + } + } + } + + private async updateMasterSecret() { + if (!this.store) { + throw new IndySdkToAskarMigrationError( + 'Update master secret can not be called outside of the `update()` function' + ) + } + + const category = 'Indy::MasterSecret' + + this.agent.config.logger.info(`Migrating category: ${category}`) + + let updateCount = 0 + const session = this.store.transaction() + + for (;;) { + const txn = await session.open() + const masterSecrets = await txn.fetchAll({ category, limit: 50 }) + if (!masterSecrets || masterSecrets.length === 0) { + await txn.close() + break + } + + if (!masterSecrets.some((ms: EntryObject) => ms.name === this.defaultLinkSecretId)) { + throw new IndySdkToAskarMigrationError('defaultLinkSecretId can not be established.') + } + + this.agent.config.logger.info(`Default link secret id for migration is ${this.defaultLinkSecretId}`) + + for (const row of masterSecrets) { + this.agent.config.logger.debug(`Migrating ${row.name} to the new askar format`) + + const isDefault = masterSecrets.length === 0 || row.name === this.walletConfig.id + + const { + value: { ms }, + } = JSON.parse(row.value as string) as { value: { ms: string } } + + const record = new AnonCredsLinkSecretRecord({ linkSecretId: row.name, value: ms }) + record.setTag('isDefault', isDefault) + const value = JsonTransformer.serialize(record) + + const tags = transformFromRecordTagValues(record.getTags()) + + await txn.insert({ category: record.type, name: record.id, value, tags }) + + await txn.remove({ category, name: row.name }) + updateCount++ + } + await txn.commit() + } + + this.agent.config.logger.info(`Migrated ${updateCount} records of type ${category}`) + } + + private async updateCredentials() { + if (!this.store) { + throw new IndySdkToAskarMigrationError('Update credentials can not be called outside of the `update()` function') + } + + const category = 'Indy::Credential' + + this.agent.config.logger.info(`Migrating category: ${category}`) + + let updateCount = 0 + const session = this.store.transaction() + for (;;) { + const txn = await session.open() + const credentials = await txn.fetchAll({ category, limit: 50 }) + if (!credentials || credentials.length === 0) { + await txn.close() + break + } + + for (const row of credentials) { + this.agent.config.logger.debug(`Migrating ${row.name} to the new askar format`) + const data = JSON.parse(row.value as string) as { + schema_id: string + cred_def_id: string + rev_reg_id?: string + values: Record + signature: Record + signature_correctness_proof: Record + rev_reg?: Record + witness?: Record + } + const [issuerId] = data.cred_def_id.split(':') + const [schemaIssuerId, , schemaName, schemaVersion] = data.schema_id.split(':') + + const record = new AnonCredsCredentialRecord({ + credential: data, + issuerId, + schemaName, + schemaIssuerId, + schemaVersion, + credentialId: row.name, + linkSecretId: this.defaultLinkSecretId, + // Hardcode methodName to indy as all IndySDK credentials are indy credentials + methodName: 'indy', + }) + + const tags = transformFromRecordTagValues(record.getTags()) + const value = JsonTransformer.serialize(record) + + await txn.insert({ category: record.type, name: record.id, value, tags }) + + await txn.remove({ category, name: row.name }) + updateCount++ + } + await txn.commit() + } + + this.agent.config.logger.info(`Migrated ${updateCount} records of type ${category}`) + } +} diff --git a/packages/indy-sdk-to-askar-migration/src/errors/IndySdkToAskarMigrationError.ts b/packages/indy-sdk-to-askar-migration/src/errors/IndySdkToAskarMigrationError.ts new file mode 100644 index 0000000000..4621d3969c --- /dev/null +++ b/packages/indy-sdk-to-askar-migration/src/errors/IndySdkToAskarMigrationError.ts @@ -0,0 +1,6 @@ +import { AriesFrameworkError } from '@aries-framework/core' + +/** + * @internal + */ +export class IndySdkToAskarMigrationError extends AriesFrameworkError {} diff --git a/packages/indy-sdk-to-askar-migration/src/index.ts b/packages/indy-sdk-to-askar-migration/src/index.ts new file mode 100644 index 0000000000..daac1c7b49 --- /dev/null +++ b/packages/indy-sdk-to-askar-migration/src/index.ts @@ -0,0 +1 @@ +export { IndySdkToAskarMigrationUpdater } from './IndySdkToAskarMigrationUpdater' diff --git a/packages/indy-sdk-to-askar-migration/src/utils.ts b/packages/indy-sdk-to-askar-migration/src/utils.ts new file mode 100644 index 0000000000..86998ecb4a --- /dev/null +++ b/packages/indy-sdk-to-askar-migration/src/utils.ts @@ -0,0 +1,53 @@ +import type { TagsBase } from '@aries-framework/core' + +import { KeyDerivationMethod } from '@aries-framework/core' +import { KdfMethod, StoreKeyMethod } from '@hyperledger/aries-askar-shared' + +/** + * Adopted from `AskarStorageService` implementation and should be kept in sync. + */ +export const transformFromRecordTagValues = (tags: TagsBase): { [key: string]: string | undefined } => { + const transformedTags: { [key: string]: string | undefined } = {} + + for (const [key, value] of Object.entries(tags)) { + // If the value is of type null we use the value undefined + // Askar doesn't support null as a value + if (value === null) { + transformedTags[key] = undefined + } + // If the value is a boolean use the Askar + // '1' or '0' syntax + else if (typeof value === 'boolean') { + transformedTags[key] = value ? '1' : '0' + } + // If the value is 1 or 0, we need to add something to the value, otherwise + // the next time we deserialize the tag values it will be converted to boolean + else if (value === '1' || value === '0') { + transformedTags[key] = `n__${value}` + } + // If the value is an array we create a tag for each array + // item ("tagName:arrayItem" = "1") + else if (Array.isArray(value)) { + value.forEach((item) => { + const tagName = `${key}:${item}` + transformedTags[tagName] = '1' + }) + } + // Otherwise just use the value + else { + transformedTags[key] = value + } + } + + return transformedTags +} + +export const keyDerivationMethodToStoreKeyMethod = (keyDerivationMethod: KeyDerivationMethod) => { + const correspondenceTable = { + [KeyDerivationMethod.Raw]: KdfMethod.Raw, + [KeyDerivationMethod.Argon2IInt]: KdfMethod.Argon2IInt, + [KeyDerivationMethod.Argon2IMod]: KdfMethod.Argon2IMod, + } + + return new StoreKeyMethod(correspondenceTable[keyDerivationMethod]) +} diff --git a/packages/indy-sdk-to-askar-migration/tests/migrate.test.ts b/packages/indy-sdk-to-askar-migration/tests/migrate.test.ts new file mode 100644 index 0000000000..ba78a901ec --- /dev/null +++ b/packages/indy-sdk-to-askar-migration/tests/migrate.test.ts @@ -0,0 +1,123 @@ +import type { InitConfig } from '@aries-framework/core' + +import { AskarModule } from '@aries-framework/askar' +import { utils, KeyDerivationMethod, Agent } from '@aries-framework/core' +import { IndySdkModule } from '@aries-framework/indy-sdk' +import { agentDependencies } from '@aries-framework/node' +import { ariesAskar } from '@hyperledger/aries-askar-nodejs' +import { registerAriesAskar } from '@hyperledger/aries-askar-shared' +import indy from 'indy-sdk' +import { homedir } from 'os' + +import { describeRunInNodeVersion } from '../../../tests/runInVersion' +import { IndySdkToAskarMigrationUpdater } from '../src' +import { IndySdkToAskarMigrationError } from '../src/errors/IndySdkToAskarMigrationError' + +// FIXME: Re-include in tests when NodeJS wrapper performance is improved +describeRunInNodeVersion([18], 'Indy SDK To Askar Migration', () => { + beforeAll(() => { + registerAriesAskar({ askar: ariesAskar }) + }) + + test('indy-sdk sqlite to aries-askar sqlite successful migration', async () => { + const indySdkAndAskarConfig: InitConfig = { + label: `indy | indy-sdk sqlite to aries-askar sqlite successful migration | ${utils.uuid()}`, + walletConfig: { + id: `indy-sdk sqlite to aries-askar sqlite successful migration | ${utils.uuid()}`, + key: 'GfwU1DC7gEZNs3w41tjBiZYj7BNToDoFEqKY6wZXqs1A', + keyDerivationMethod: KeyDerivationMethod.Raw, + }, + } + + const indySdkAgent = new Agent({ + config: indySdkAndAskarConfig, + modules: { indySdk: new IndySdkModule({ indySdk: indy }) }, + dependencies: agentDependencies, + }) + + const indySdkAgentDbPath = `${homedir()}/.indy_client/wallet/${indySdkAndAskarConfig.walletConfig?.id}/sqlite.db` + + const genericRecordContent = { foo: 'bar' } + + await indySdkAgent.initialize() + + const record = await indySdkAgent.genericRecords.save({ content: genericRecordContent }) + + await indySdkAgent.shutdown() + + const askarAgent = new Agent({ + config: indySdkAndAskarConfig, + modules: { askar: new AskarModule({ ariesAskar }) }, + dependencies: agentDependencies, + }) + + const updater = await IndySdkToAskarMigrationUpdater.initialize({ dbPath: indySdkAgentDbPath, agent: askarAgent }) + await updater.update() + + await askarAgent.initialize() + + await expect(askarAgent.genericRecords.findById(record.id)).resolves.toMatchObject({ + content: genericRecordContent, + }) + + await askarAgent.shutdown() + }) + + /* + * - Initialize an agent + * - Save a generic record + * - try to migrate with invalid state (wrong key) + * - Migration will be attempted, fails, and restores + * - Check if the record can still be accessed + */ + test('indy-sdk sqlite to aries-askar sqlite fails and restores', async () => { + const indySdkAndAskarConfig: InitConfig = { + label: `indy | indy-sdk sqlite to aries-askar sqlite fails and restores | ${utils.uuid()}`, + walletConfig: { + id: `indy-sdk sqlite to aries-askar sqlite fails and restores | ${utils.uuid()}`, + key: 'GfwU1DC7gEZNs3w41tjBiZYj7BNToDoFEqKY6wZXqs1A', + keyDerivationMethod: KeyDerivationMethod.Raw, + }, + } + + const indySdkAgent = new Agent({ + config: indySdkAndAskarConfig, + modules: { indySdk: new IndySdkModule({ indySdk: indy }) }, + dependencies: agentDependencies, + }) + + const indySdkAgentDbPath = `${homedir()}/.indy_client/wallet/${indySdkAndAskarConfig.walletConfig?.id}/sqlite.db` + + const genericRecordContent = { foo: 'bar' } + + await indySdkAgent.initialize() + + const record = await indySdkAgent.genericRecords.save({ content: genericRecordContent }) + + await indySdkAgent.shutdown() + + const askarAgent = new Agent({ + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + config: { ...indySdkAndAskarConfig, walletConfig: { ...indySdkAndAskarConfig.walletConfig!, key: 'wrong-key' } }, + modules: { + askar: new AskarModule({ + ariesAskar, + }), + }, + dependencies: agentDependencies, + }) + + const updater = await IndySdkToAskarMigrationUpdater.initialize({ + dbPath: indySdkAgentDbPath, + agent: askarAgent, + }) + + await expect(updater.update()).rejects.toThrowError(IndySdkToAskarMigrationError) + + await indySdkAgent.initialize() + + await expect(indySdkAgent.genericRecords.findById(record.id)).resolves.toMatchObject({ + content: genericRecordContent, + }) + }) +}) diff --git a/packages/indy-sdk-to-askar-migration/tests/setup.ts b/packages/indy-sdk-to-askar-migration/tests/setup.ts new file mode 100644 index 0000000000..226f7031fa --- /dev/null +++ b/packages/indy-sdk-to-askar-migration/tests/setup.ts @@ -0,0 +1 @@ +jest.setTimeout(20000) diff --git a/packages/indy-sdk-to-askar-migration/tsconfig.build.json b/packages/indy-sdk-to-askar-migration/tsconfig.build.json new file mode 100644 index 0000000000..2b075bbd85 --- /dev/null +++ b/packages/indy-sdk-to-askar-migration/tsconfig.build.json @@ -0,0 +1,8 @@ +{ + "extends": "../../tsconfig.build.json", + "compilerOptions": { + "outDir": "./build", + "skipLibCheck": true + }, + "include": ["src/**/*"] +} diff --git a/packages/indy-sdk-to-askar-migration/tsconfig.json b/packages/indy-sdk-to-askar-migration/tsconfig.json new file mode 100644 index 0000000000..46efe6f721 --- /dev/null +++ b/packages/indy-sdk-to-askar-migration/tsconfig.json @@ -0,0 +1,6 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "types": ["jest"] + } +} diff --git a/packages/indy-sdk/README.md b/packages/indy-sdk/README.md new file mode 100644 index 0000000000..368d25db71 --- /dev/null +++ b/packages/indy-sdk/README.md @@ -0,0 +1,31 @@ +

+
+ Hyperledger Aries logo +

+

Aries Framework JavaScript IndySDK Module

+

+ License + typescript + @aries-framework/indy-sdk version + +

+
+ +IndySDK module for [Aries Framework JavaScript](https://github.com/hyperledger/aries-framework-javascript.git). diff --git a/packages/indy-sdk/jest.config.ts b/packages/indy-sdk/jest.config.ts new file mode 100644 index 0000000000..93c0197296 --- /dev/null +++ b/packages/indy-sdk/jest.config.ts @@ -0,0 +1,13 @@ +import type { Config } from '@jest/types' + +import base from '../../jest.config.base' + +import packageJson from './package.json' + +const config: Config.InitialOptions = { + ...base, + displayName: packageJson.name, + setupFilesAfterEnv: ['./tests/setup.ts'], +} + +export default config diff --git a/packages/indy-sdk/package.json b/packages/indy-sdk/package.json new file mode 100644 index 0000000000..27f6402457 --- /dev/null +++ b/packages/indy-sdk/package.json @@ -0,0 +1,40 @@ +{ + "name": "@aries-framework/indy-sdk", + "main": "build/index", + "types": "build/index", + "version": "0.3.3", + "files": [ + "build" + ], + "license": "Apache-2.0", + "publishConfig": { + "access": "public" + }, + "homepage": "https://github.com/hyperledger/aries-framework-javascript/tree/main/packages/indy-sdk", + "repository": { + "type": "git", + "url": "https://github.com/hyperledger/aries-framework-javascript", + "directory": "packages/indy-sdk" + }, + "scripts": { + "build": "yarn run clean && yarn run compile", + "clean": "rimraf ./build", + "compile": "tsc -p tsconfig.build.json", + "prepublishOnly": "yarn run build", + "test": "jest" + }, + "dependencies": { + "@aries-framework/anoncreds": "0.3.3", + "@aries-framework/core": "0.3.3", + "@types/indy-sdk": "1.16.26", + "@stablelib/ed25519": "^1.0.3", + "class-transformer": "0.5.1", + "class-validator": "0.14.0", + "rxjs": "^7.2.0", + "tsyringe": "^4.7.0" + }, + "devDependencies": { + "rimraf": "^4.4.0", + "typescript": "~4.9.5" + } +} diff --git a/packages/indy-sdk/src/IndySdkModule.ts b/packages/indy-sdk/src/IndySdkModule.ts new file mode 100644 index 0000000000..d099591543 --- /dev/null +++ b/packages/indy-sdk/src/IndySdkModule.ts @@ -0,0 +1,60 @@ +import type { IndySdkModuleConfigOptions } from './IndySdkModuleConfig' +import type { AgentContext, DependencyManager, Module } from '@aries-framework/core' + +import { + AnonCredsHolderServiceSymbol, + AnonCredsIssuerServiceSymbol, + AnonCredsVerifierServiceSymbol, +} from '@aries-framework/anoncreds' +import { AriesFrameworkError, InjectionSymbols } from '@aries-framework/core' + +import { IndySdkModuleConfig } from './IndySdkModuleConfig' +import { IndySdkHolderService, IndySdkIssuerService, IndySdkVerifierService } from './anoncreds' +import { IndySdkPoolService } from './ledger' +import { IndySdkStorageService } from './storage' +import { IndySdkSymbol } from './types' +import { IndySdkWallet } from './wallet' + +export class IndySdkModule implements Module { + public readonly config: IndySdkModuleConfig + + public constructor(config: IndySdkModuleConfigOptions) { + this.config = new IndySdkModuleConfig(config) + } + + public register(dependencyManager: DependencyManager) { + dependencyManager.registerInstance(IndySdkSymbol, this.config.indySdk) + + // Register config + dependencyManager.registerInstance(IndySdkModuleConfig, this.config) + + if (dependencyManager.isRegistered(InjectionSymbols.Wallet)) { + throw new AriesFrameworkError('There is an instance of Wallet already registered') + } else { + dependencyManager.registerContextScoped(InjectionSymbols.Wallet, IndySdkWallet) + } + + if (dependencyManager.isRegistered(InjectionSymbols.StorageService)) { + throw new AriesFrameworkError('There is an instance of StorageService already registered') + } else { + dependencyManager.registerSingleton(InjectionSymbols.StorageService, IndySdkStorageService) + } + + // NOTE: for now we are registering the needed indy services. We may want to make this + // more explicit and require the user to register the services they need on the specific modules. + dependencyManager.registerSingleton(IndySdkPoolService) + dependencyManager.registerSingleton(AnonCredsIssuerServiceSymbol, IndySdkIssuerService) + dependencyManager.registerSingleton(AnonCredsHolderServiceSymbol, IndySdkHolderService) + dependencyManager.registerSingleton(AnonCredsVerifierServiceSymbol, IndySdkVerifierService) + } + + public async initialize(agentContext: AgentContext): Promise { + const indySdkPoolService = agentContext.dependencyManager.resolve(IndySdkPoolService) + + for (const pool of indySdkPoolService.pools) { + if (pool.config.connectOnStartup) { + await pool.connect() + } + } + } +} diff --git a/packages/indy-sdk/src/IndySdkModuleConfig.ts b/packages/indy-sdk/src/IndySdkModuleConfig.ts new file mode 100644 index 0000000000..5bb066bebb --- /dev/null +++ b/packages/indy-sdk/src/IndySdkModuleConfig.ts @@ -0,0 +1,70 @@ +import type { IndySdkPoolConfig } from './ledger' +import type * as IndySdk from 'indy-sdk' + +/** + * IndySdkModuleConfigOptions defines the interface for the options of the IndySdkModuleConfig class. + */ +export interface IndySdkModuleConfigOptions { + /** + * Implementation of the IndySdk interface according to the @types/indy-sdk package. + * + * + * ## Node.JS + * + * ```ts + * import * as indySdk from 'indy-sdk' + * + * const indySdkModule = new IndySdkModule({ + * indySdk + * }) + * ``` + * + * ## React Native + * + * ```ts + * import indySdk from 'indy-sdk-react-native' + * + * const indySdkModule = new IndySdkModule({ + * indySdk + * }) + * ``` + */ + indySdk: typeof IndySdk + + /** + * Array of indy networks to connect to. Each item in the list must include either the `genesisPath` or `genesisTransactions` property. + * + * @default [] + * + * @example + * ``` + * { + * isProduction: false, + * genesisPath: '/path/to/genesis.txn', + * indyNamespace: 'localhost:test', + * transactionAuthorAgreement: { + * version: '1', + * acceptanceMechanism: 'accept' + * } + * } + * ``` + */ + networks?: IndySdkPoolConfig[] +} + +export class IndySdkModuleConfig { + private options: IndySdkModuleConfigOptions + + public constructor(options: IndySdkModuleConfigOptions) { + this.options = options + } + + /** See {@link IndySdkModuleConfigOptions.indySdk} */ + public get indySdk() { + return this.options.indySdk + } + + public get networks() { + return this.options.networks ?? [] + } +} diff --git a/packages/indy-sdk/src/anoncreds/index.ts b/packages/indy-sdk/src/anoncreds/index.ts new file mode 100644 index 0000000000..adba521ce0 --- /dev/null +++ b/packages/indy-sdk/src/anoncreds/index.ts @@ -0,0 +1,4 @@ +export { IndySdkAnonCredsRegistry } from './services/IndySdkAnonCredsRegistry' +export { IndySdkHolderService } from './services/IndySdkHolderService' +export { IndySdkIssuerService } from './services/IndySdkIssuerService' +export { IndySdkVerifierService } from './services/IndySdkVerifierService' diff --git a/packages/indy-sdk/src/anoncreds/services/IndySdkAnonCredsRegistry.ts b/packages/indy-sdk/src/anoncreds/services/IndySdkAnonCredsRegistry.ts new file mode 100644 index 0000000000..21ab95ab53 --- /dev/null +++ b/packages/indy-sdk/src/anoncreds/services/IndySdkAnonCredsRegistry.ts @@ -0,0 +1,623 @@ +import type { IndySdkPool } from '../../ledger' +import type { IndySdk } from '../../types' +import type { + AnonCredsRegistry, + GetCredentialDefinitionReturn, + GetRevocationStatusListReturn, + GetRevocationRegistryDefinitionReturn, + GetSchemaReturn, + RegisterCredentialDefinitionOptions, + RegisterCredentialDefinitionReturn, + RegisterSchemaOptions, + RegisterSchemaReturn, +} from '@aries-framework/anoncreds' +import type { AgentContext } from '@aries-framework/core' +import type { Schema as IndySdkSchema } from 'indy-sdk' + +import { + getUnqualifiedCredentialDefinitionId, + getUnqualifiedRevocationRegistryId, + getUnqualifiedSchemaId, + parseIndyCredentialDefinitionId, + parseIndyDid, + parseIndyRevocationRegistryId, + parseIndySchemaId, +} from '@aries-framework/anoncreds' + +import { verificationKeyForIndyDid } from '../../dids/didIndyUtil' +import { IndySdkError, isIndyError } from '../../error' +import { IndySdkPoolService } from '../../ledger' +import { IndySdkSymbol } from '../../types' +import { + getDidIndyCredentialDefinitionId, + getDidIndySchemaId, + indySdkAnonCredsRegistryIdentifierRegex, +} from '../utils/identifiers' +import { anonCredsRevocationStatusListFromIndySdk } from '../utils/transform' + +export class IndySdkAnonCredsRegistry implements AnonCredsRegistry { + public readonly methodName = 'indy' + + /** + * This class supports resolving and registering objects with did:indy as well as legacy indy identifiers. + * It needs to include support for the schema, credential definition, revocation registry as well + * as the issuer id (which is needed when registering objects). + */ + public readonly supportedIdentifier = indySdkAnonCredsRegistryIdentifierRegex + + public async getSchema(agentContext: AgentContext, schemaId: string): Promise { + try { + const indySdkPoolService = agentContext.dependencyManager.resolve(IndySdkPoolService) + const indySdk = agentContext.dependencyManager.resolve(IndySdkSymbol) + + // parse schema id (supports did:indy and legacy) + const { did, namespaceIdentifier, schemaName, schemaVersion } = parseIndySchemaId(schemaId) + const { pool } = await indySdkPoolService.getPoolForDid(agentContext, did) + agentContext.config.logger.debug(`Getting schema '${schemaId}' from ledger '${pool.didIndyNamespace}'`) + + // even though we support did:indy and legacy identifiers we always need to fetch using the legacy identifier + const legacySchemaId = getUnqualifiedSchemaId(namespaceIdentifier, schemaName, schemaVersion) + const request = await indySdk.buildGetSchemaRequest(null, legacySchemaId) + + agentContext.config.logger.trace( + `Submitting get schema request for schema '${schemaId}' to ledger '${pool.didIndyNamespace}'` + ) + const response = await indySdkPoolService.submitReadRequest(pool, request) + + agentContext.config.logger.trace(`Got un-parsed schema '${schemaId}' from ledger '${pool.didIndyNamespace}'`, { + response, + }) + + const [, schema] = await indySdk.parseGetSchemaResponse(response) + agentContext.config.logger.debug(`Got schema '${schemaId}' from ledger '${pool.didIndyNamespace}'`, { + schema, + }) + + return { + schema: { + attrNames: schema.attrNames, + name: schema.name, + version: schema.version, + issuerId: did, + }, + schemaId, + resolutionMetadata: {}, + schemaMetadata: { + didIndyNamespace: pool.didIndyNamespace, + // NOTE: the seqNo is required by the indy-sdk even though not present in AnonCreds v1. + // For this reason we return it in the metadata. + indyLedgerSeqNo: schema.seqNo, + }, + } + } catch (error) { + agentContext.config.logger.error(`Error retrieving schema '${schemaId}'`, { + error, + schemaId, + }) + + return { + schemaId, + resolutionMetadata: { + error: 'notFound', + message: `unable to resolve credential definition: ${error.message}`, + }, + schemaMetadata: {}, + } + } + } + + public async registerSchema( + agentContext: AgentContext, + options: RegisterSchemaOptions + ): Promise { + try { + // This will throw an error if trying to register a schema with a legacy indy identifier. We only support did:indy identifiers + // for registering, that will allow us to extract the namespace and means all stored records will use did:indy identifiers. + const { namespaceIdentifier, namespace } = parseIndyDid(options.schema.issuerId) + + const indySdkPoolService = agentContext.dependencyManager.resolve(IndySdkPoolService) + const indySdk = agentContext.dependencyManager.resolve(IndySdkSymbol) + + const pool = indySdkPoolService.getPoolForNamespace(namespace) + agentContext.config.logger.debug( + `Register schema on ledger '${pool.didIndyNamespace}' with did '${options.schema.issuerId}'`, + options.schema + ) + + const didIndySchemaId = getDidIndySchemaId( + namespace, + namespaceIdentifier, + options.schema.name, + options.schema.version + ) + const legacySchemaId = getUnqualifiedSchemaId(namespaceIdentifier, options.schema.name, options.schema.version) + + const schema = { + attrNames: options.schema.attrNames, + name: options.schema.name, + version: options.schema.version, + id: legacySchemaId, + ver: '1.0', + // Casted as because the type expect a seqNo, but that's not actually required for the input of + // buildSchemaRequest (seqNo is not yet known) + } as IndySdkSchema + + const request = await indySdk.buildSchemaRequest(namespaceIdentifier, schema) + const submitterKey = await verificationKeyForIndyDid(agentContext, options.schema.issuerId) + + const response = await indySdkPoolService.submitWriteRequest(agentContext, pool, request, submitterKey) + agentContext.config.logger.debug(`Registered schema '${schema.id}' on ledger '${pool.didIndyNamespace}'`, { + response, + schema, + }) + + return { + schemaState: { + state: 'finished', + schema: { + attrNames: schema.attrNames, + issuerId: options.schema.issuerId, + name: schema.name, + version: schema.version, + }, + schemaId: didIndySchemaId, + }, + registrationMetadata: {}, + schemaMetadata: { + // NOTE: the seqNo is required by the indy-sdk even though not present in AnonCreds v1. + // For this reason we return it in the metadata. + indyLedgerSeqNo: response.result.txnMetadata.seqNo, + }, + } + } catch (error) { + agentContext.config.logger.error(`Error registering schema for did '${options.schema.issuerId}'`, { + error, + did: options.schema.issuerId, + schema: options.schema, + }) + + return { + schemaMetadata: {}, + registrationMetadata: {}, + schemaState: { + state: 'failed', + schema: options.schema, + reason: `unknownError: ${error.message}`, + }, + } + } + } + + public async getCredentialDefinition( + agentContext: AgentContext, + credentialDefinitionId: string + ): Promise { + try { + const indySdkPoolService = agentContext.dependencyManager.resolve(IndySdkPoolService) + const indySdk = agentContext.dependencyManager.resolve(IndySdkSymbol) + + // we support did:indy and legacy identifiers + const { did, namespaceIdentifier, schemaSeqNo, tag } = parseIndyCredentialDefinitionId(credentialDefinitionId) + const { pool } = await indySdkPoolService.getPoolForDid(agentContext, did) + + agentContext.config.logger.debug( + `Using ledger '${pool.didIndyNamespace}' to retrieve credential definition '${credentialDefinitionId}'` + ) + + const legacyCredentialDefinitionId = getUnqualifiedCredentialDefinitionId(namespaceIdentifier, schemaSeqNo, tag) + const request = await indySdk.buildGetCredDefRequest(null, legacyCredentialDefinitionId) + + agentContext.config.logger.trace( + `Submitting get credential definition request for credential definition '${credentialDefinitionId}' to ledger '${pool.didIndyNamespace}'` + ) + + const response = await indySdkPoolService.submitReadRequest(pool, request) + agentContext.config.logger.trace( + `Got un-parsed credential definition '${credentialDefinitionId}' from ledger '${pool.didIndyNamespace}'`, + { + response, + } + ) + + const [, credentialDefinition] = await indySdk.parseGetCredDefResponse(response) + const { schema } = await this.fetchIndySchemaWithSeqNo(agentContext, pool, Number(credentialDefinition.schemaId)) + + if (credentialDefinition && schema) { + agentContext.config.logger.debug( + `Got credential definition '${credentialDefinitionId}' from ledger '${pool.didIndyNamespace}'`, + { + credentialDefinition, + } + ) + + // Format the schema id based on the type of the credential definition id + const schemaId = credentialDefinitionId.startsWith('did:indy') + ? getDidIndySchemaId(pool.didIndyNamespace, namespaceIdentifier, schema.name, schema.version) + : schema.schemaId + + return { + credentialDefinitionId, + credentialDefinition: { + issuerId: did, + schemaId, + tag: credentialDefinition.tag, + type: 'CL', + value: credentialDefinition.value, + }, + credentialDefinitionMetadata: { + didIndyNamespace: pool.didIndyNamespace, + }, + resolutionMetadata: {}, + } + } + + agentContext.config.logger.error(`Error retrieving credential definition '${credentialDefinitionId}'`, { + credentialDefinitionId, + }) + + return { + credentialDefinitionId, + credentialDefinitionMetadata: {}, + resolutionMetadata: { + error: 'notFound', + message: `unable to resolve credential definition`, + }, + } + } catch (error) { + agentContext.config.logger.error(`Error retrieving credential definition '${credentialDefinitionId}'`, { + error, + credentialDefinitionId, + }) + + return { + credentialDefinitionId, + credentialDefinitionMetadata: {}, + resolutionMetadata: { + error: 'notFound', + message: `unable to resolve credential definition: ${error.message}`, + }, + } + } + } + + public async registerCredentialDefinition( + agentContext: AgentContext, + options: RegisterCredentialDefinitionOptions + ): Promise { + try { + // This will throw an error if trying to register a credential defintion with a legacy indy identifier. We only support did:indy + // identifiers for registering, that will allow us to extract the namespace and means all stored records will use did:indy identifiers. + const { namespaceIdentifier, namespace } = parseIndyDid(options.credentialDefinition.issuerId) + + const indySdkPoolService = agentContext.dependencyManager.resolve(IndySdkPoolService) + const indySdk = agentContext.dependencyManager.resolve(IndySdkSymbol) + + const pool = indySdkPoolService.getPoolForNamespace(namespace) + agentContext.config.logger.debug( + `Registering credential definition on ledger '${pool.didIndyNamespace}' with did '${options.credentialDefinition.issuerId}'`, + options.credentialDefinition + ) + + // TODO: check structure of the schemaId + // TODO: this will bypass caching if done on a higher level. + const { schema, schemaMetadata, resolutionMetadata } = await this.getSchema( + agentContext, + options.credentialDefinition.schemaId + ) + + if (!schema || !schemaMetadata.indyLedgerSeqNo || typeof schemaMetadata.indyLedgerSeqNo !== 'number') { + return { + registrationMetadata: {}, + credentialDefinitionMetadata: { + didIndyNamespace: pool.didIndyNamespace, + }, + credentialDefinitionState: { + credentialDefinition: options.credentialDefinition, + state: 'failed', + reason: `error resolving schema with id ${options.credentialDefinition.schemaId}: ${resolutionMetadata.error} ${resolutionMetadata.message}`, + }, + } + } + + const legacyCredentialDefinitionId = getUnqualifiedCredentialDefinitionId( + namespaceIdentifier, + schemaMetadata.indyLedgerSeqNo, + options.credentialDefinition.tag + ) + const didIndyCredentialDefinitionId = getDidIndyCredentialDefinitionId( + namespace, + namespaceIdentifier, + schemaMetadata.indyLedgerSeqNo, + options.credentialDefinition.tag + ) + + const request = await indySdk.buildCredDefRequest(namespaceIdentifier, { + id: legacyCredentialDefinitionId, + // Indy ledger requires the credential schemaId to be a string of the schema seqNo. + schemaId: schemaMetadata.indyLedgerSeqNo.toString(), + tag: options.credentialDefinition.tag, + type: options.credentialDefinition.type, + value: options.credentialDefinition.value, + ver: '1.0', + }) + + const submitterKey = await verificationKeyForIndyDid(agentContext, options.credentialDefinition.issuerId) + const response = await indySdkPoolService.submitWriteRequest(agentContext, pool, request, submitterKey) + + agentContext.config.logger.debug( + `Registered credential definition '${didIndyCredentialDefinitionId}' on ledger '${pool.didIndyNamespace}'`, + { + response, + credentialDefinition: options.credentialDefinition, + } + ) + + return { + credentialDefinitionMetadata: {}, + credentialDefinitionState: { + credentialDefinition: options.credentialDefinition, + credentialDefinitionId: didIndyCredentialDefinitionId, + state: 'finished', + }, + registrationMetadata: {}, + } + } catch (error) { + agentContext.config.logger.error( + `Error registering credential definition for schema '${options.credentialDefinition.schemaId}'`, + { + error, + did: options.credentialDefinition.issuerId, + credentialDefinition: options.credentialDefinition, + } + ) + + throw isIndyError(error) ? new IndySdkError(error) : error + } + } + + public async getRevocationRegistryDefinition( + agentContext: AgentContext, + revocationRegistryDefinitionId: string + ): Promise { + try { + const indySdkPoolService = agentContext.dependencyManager.resolve(IndySdkPoolService) + const indySdk = agentContext.dependencyManager.resolve(IndySdkSymbol) + + const { did, namespaceIdentifier, credentialDefinitionTag, revocationRegistryTag, schemaSeqNo } = + parseIndyRevocationRegistryId(revocationRegistryDefinitionId) + const { pool } = await indySdkPoolService.getPoolForDid(agentContext, did) + + agentContext.config.logger.debug( + `Using ledger '${pool.didIndyNamespace}' to retrieve revocation registry definition '${revocationRegistryDefinitionId}'` + ) + + const legacyRevocationRegistryId = getUnqualifiedRevocationRegistryId( + namespaceIdentifier, + schemaSeqNo, + credentialDefinitionTag, + revocationRegistryTag + ) + const request = await indySdk.buildGetRevocRegDefRequest(null, legacyRevocationRegistryId) + + agentContext.config.logger.trace( + `Submitting get revocation registry definition request for revocation registry definition '${revocationRegistryDefinitionId}' to ledger` + ) + const response = await indySdkPoolService.submitReadRequest(pool, request) + agentContext.config.logger.trace( + `Got un-parsed revocation registry definition '${revocationRegistryDefinitionId}' from ledger '${pool.didIndyNamespace}'`, + { + response, + } + ) + + const [, revocationRegistryDefinition] = await indySdk.parseGetRevocRegDefResponse(response) + + agentContext.config.logger.debug( + `Got revocation registry definition '${revocationRegistryDefinitionId}' from ledger`, + { + revocationRegistryDefinition, + } + ) + + const credentialDefinitionId = revocationRegistryDefinitionId.startsWith('did:indy:') + ? getDidIndyCredentialDefinitionId( + pool.didIndyNamespace, + namespaceIdentifier, + schemaSeqNo, + credentialDefinitionTag + ) + : getUnqualifiedCredentialDefinitionId(namespaceIdentifier, schemaSeqNo, credentialDefinitionTag) + + return { + resolutionMetadata: {}, + revocationRegistryDefinition: { + issuerId: did, + credDefId: credentialDefinitionId, + value: { + maxCredNum: revocationRegistryDefinition.value.maxCredNum, + publicKeys: revocationRegistryDefinition.value.publicKeys, + tailsHash: revocationRegistryDefinition.value.tailsHash, + tailsLocation: revocationRegistryDefinition.value.tailsLocation, + }, + tag: revocationRegistryDefinition.tag, + revocDefType: 'CL_ACCUM', + }, + revocationRegistryDefinitionId, + revocationRegistryDefinitionMetadata: { + issuanceType: revocationRegistryDefinition.value.issuanceType, + didIndyNamespace: pool.didIndyNamespace, + }, + } + } catch (error) { + agentContext.config.logger.error( + `Error retrieving revocation registry definition '${revocationRegistryDefinitionId}' from ledger`, + { + error, + revocationRegistryDefinitionId: revocationRegistryDefinitionId, + } + ) + + return { + resolutionMetadata: { + error: 'notFound', + message: `unable to resolve revocation registry definition: ${error.message}`, + }, + revocationRegistryDefinitionId, + revocationRegistryDefinitionMetadata: {}, + } + } + } + + public async getRevocationStatusList( + agentContext: AgentContext, + revocationRegistryId: string, + timestamp: number + ): Promise { + try { + const indySdkPoolService = agentContext.dependencyManager.resolve(IndySdkPoolService) + const indySdk = agentContext.dependencyManager.resolve(IndySdkSymbol) + + const { did, namespaceIdentifier, schemaSeqNo, credentialDefinitionTag, revocationRegistryTag } = + parseIndyRevocationRegistryId(revocationRegistryId) + const { pool } = await indySdkPoolService.getPoolForDid(agentContext, did) + + agentContext.config.logger.debug( + `Using ledger '${pool.didIndyNamespace}' to retrieve revocation registry deltas with revocation registry definition id '${revocationRegistryId}' until ${timestamp}` + ) + + const legacyRevocationRegistryId = getUnqualifiedRevocationRegistryId( + namespaceIdentifier, + schemaSeqNo, + credentialDefinitionTag, + revocationRegistryTag + ) + + // TODO: implement caching for returned deltas + const request = await indySdk.buildGetRevocRegDeltaRequest(null, legacyRevocationRegistryId, 0, timestamp) + + agentContext.config.logger.trace( + `Submitting get revocation registry delta request for revocation registry '${revocationRegistryId}' to ledger` + ) + + const response = await indySdkPoolService.submitReadRequest(pool, request) + agentContext.config.logger.trace( + `Got revocation registry delta unparsed-response '${revocationRegistryId}' from ledger`, + { + response, + } + ) + + const [, revocationRegistryDelta, deltaTimestamp] = await indySdk.parseGetRevocRegDeltaResponse(response) + + agentContext.config.logger.debug( + `Got revocation registry deltas '${revocationRegistryId}' until timestamp ${timestamp} from ledger`, + { + revocationRegistryDelta, + deltaTimestamp, + } + ) + + const { resolutionMetadata, revocationRegistryDefinition, revocationRegistryDefinitionMetadata } = + await this.getRevocationRegistryDefinition(agentContext, revocationRegistryId) + + if ( + !revocationRegistryDefinition || + !revocationRegistryDefinitionMetadata.issuanceType || + typeof revocationRegistryDefinitionMetadata.issuanceType !== 'string' + ) { + return { + resolutionMetadata: { + error: `error resolving revocation registry definition with id ${revocationRegistryId}: ${resolutionMetadata.error} ${resolutionMetadata.message}`, + }, + revocationStatusListMetadata: { + didIndyNamespace: pool.didIndyNamespace, + }, + } + } + + const isIssuanceByDefault = revocationRegistryDefinitionMetadata.issuanceType === 'ISSUANCE_BY_DEFAULT' + + return { + resolutionMetadata: {}, + revocationStatusList: anonCredsRevocationStatusListFromIndySdk( + revocationRegistryId, + revocationRegistryDefinition, + revocationRegistryDelta, + deltaTimestamp, + isIssuanceByDefault + ), + revocationStatusListMetadata: { + didIndyNamespace: pool.didIndyNamespace, + }, + } + } catch (error) { + agentContext.config.logger.error( + `Error retrieving revocation registry delta '${revocationRegistryId}' from ledger, potentially revocation interval ends before revocation registry creation?"`, + { + error, + revocationRegistryId: revocationRegistryId, + } + ) + + return { + resolutionMetadata: { + error: 'notFound', + message: `Error retrieving revocation registry delta '${revocationRegistryId}' from ledger, potentially revocation interval ends before revocation registry creation: ${error.message}`, + }, + revocationStatusListMetadata: {}, + } + } + } + + private async fetchIndySchemaWithSeqNo(agentContext: AgentContext, pool: IndySdkPool, seqNo: number) { + const indySdkPoolService = agentContext.dependencyManager.resolve(IndySdkPoolService) + const indySdk = agentContext.dependencyManager.resolve(IndySdkSymbol) + + agentContext.config.logger.debug(`Getting transaction with seqNo '${seqNo}' from ledger '${pool.didIndyNamespace}'`) + + const request = await indySdk.buildGetTxnRequest(null, 'DOMAIN', seqNo) + + agentContext.config.logger.trace(`Submitting get transaction request to ledger '${pool.didIndyNamespace}'`) + const response = await indySdkPoolService.submitReadRequest(pool, request) + + const schema = response.result.data as SchemaType + + if (schema.txn.type !== '101') { + agentContext.config.logger.error(`Could not get schema from ledger for seq no ${seqNo}'`) + return {} + } + + return { + schema: { + // txnId is the schema id + schemaId: schema.txnMetadata.txnId, + attr_name: schema.txn.data.data.attr_names, + name: schema.txn.data.data.name, + version: schema.txn.data.data.version, + issuerId: schema.txn.metadata.from, + seqNo, + }, + indyNamespace: pool.didIndyNamespace, + } + } +} + +interface SchemaType { + txnMetadata: { + txnId: string + } + txn: { + metadata: { + from: string + } + data: { + data: { + attr_names: string[] + version: string + name: string + } + } + + type: string + } +} diff --git a/packages/indy-sdk/src/anoncreds/services/IndySdkHolderService.ts b/packages/indy-sdk/src/anoncreds/services/IndySdkHolderService.ts new file mode 100644 index 0000000000..ef44edb2e2 --- /dev/null +++ b/packages/indy-sdk/src/anoncreds/services/IndySdkHolderService.ts @@ -0,0 +1,456 @@ +import type { + AnonCredsHolderService, + AnonCredsProof, + CreateCredentialRequestOptions, + CreateCredentialRequestReturn, + CreateProofOptions, + AnonCredsCredentialInfo, + GetCredentialOptions, + StoreCredentialOptions, + GetCredentialsForProofRequestOptions, + GetCredentialsForProofRequestReturn, + AnonCredsSelectedCredentials, + CreateLinkSecretOptions, + CreateLinkSecretReturn, + GetCredentialsOptions, +} from '@aries-framework/anoncreds' +import type { AgentContext } from '@aries-framework/core' +import type { + CredentialDefs, + IndyRequestedCredentials, + RevStates, + Schemas, + IndyCredential as IndySdkCredential, + IndyProofRequest, +} from 'indy-sdk' + +import { + parseIndyCredentialDefinitionId, + AnonCredsLinkSecretRepository, + generateLegacyProverDidLikeString, +} from '@aries-framework/anoncreds' +import { AriesFrameworkError, injectable, inject, utils } from '@aries-framework/core' + +import { IndySdkError, isIndyError } from '../../error' +import { IndySdk, IndySdkSymbol } from '../../types' +import { assertIndySdkWallet } from '../../utils/assertIndySdkWallet' +import { + assertAllUnqualified, + assertUnqualifiedCredentialOffer, + assertUnqualifiedProofRequest, +} from '../utils/assertUnqualified' +import { + anonCredsCredentialRequestMetadataFromIndySdk, + indySdkCredentialDefinitionFromAnonCreds, + indySdkCredentialRequestMetadataFromAnonCreds, + indySdkRevocationRegistryDefinitionFromAnonCreds, + indySdkSchemaFromAnonCreds, +} from '../utils/transform' + +import { IndySdkRevocationService } from './IndySdkRevocationService' + +@injectable() +export class IndySdkHolderService implements AnonCredsHolderService { + private indySdk: IndySdk + private indyRevocationService: IndySdkRevocationService + + public constructor(indyRevocationService: IndySdkRevocationService, @inject(IndySdkSymbol) indySdk: IndySdk) { + this.indySdk = indySdk + this.indyRevocationService = indyRevocationService + } + + public async createLinkSecret( + agentContext: AgentContext, + options: CreateLinkSecretOptions + ): Promise { + assertIndySdkWallet(agentContext.wallet) + + const linkSecretId = options.linkSecretId ?? utils.uuid() + + try { + await this.indySdk.proverCreateMasterSecret(agentContext.wallet.handle, linkSecretId) + + // We don't have the value for the link secret when using the indy-sdk so we can't return it. + return { + linkSecretId, + } + } catch (error) { + agentContext.config.logger.error(`Error creating link secret`, { + error, + linkSecretId, + }) + + throw isIndyError(error) ? new IndySdkError(error) : error + } + } + + public async createProof(agentContext: AgentContext, options: CreateProofOptions): Promise { + const { credentialDefinitions, proofRequest, selectedCredentials, schemas } = options + + assertIndySdkWallet(agentContext.wallet) + + // Make sure all identifiers are unqualified + assertAllUnqualified({ + schemaIds: Object.keys(options.schemas), + credentialDefinitionIds: Object.keys(options.credentialDefinitions), + revocationRegistryIds: Object.keys(options.revocationRegistries), + }) + + const linkSecretRepository = agentContext.dependencyManager.resolve(AnonCredsLinkSecretRepository) + + try { + agentContext.config.logger.debug('Creating Indy Proof') + const indyRevocationStates: RevStates = await this.indyRevocationService.createRevocationState( + agentContext, + proofRequest, + selectedCredentials, + options.revocationRegistries + ) + + // The AnonCredsSchema doesn't contain the seqNo anymore. However, the indy credential definition id + // does contain the seqNo, so we can extract it from the credential definition id. + const seqNoMap: { [schemaId: string]: number } = {} + + // Convert AnonCreds credential definitions to Indy credential definitions + const indyCredentialDefinitions: CredentialDefs = {} + for (const credentialDefinitionId in credentialDefinitions) { + const credentialDefinition = credentialDefinitions[credentialDefinitionId] + indyCredentialDefinitions[credentialDefinitionId] = indySdkCredentialDefinitionFromAnonCreds( + credentialDefinitionId, + credentialDefinition + ) + + // Get the seqNo for the schemas so we can use it when transforming the schemas + const { schemaSeqNo } = parseIndyCredentialDefinitionId(credentialDefinitionId) + seqNoMap[credentialDefinition.schemaId] = Number(schemaSeqNo) + } + + // Convert AnonCreds schemas to Indy schemas + const indySchemas: Schemas = {} + for (const schemaId in schemas) { + const schema = schemas[schemaId] + indySchemas[schemaId] = indySdkSchemaFromAnonCreds(schemaId, schema, seqNoMap[schemaId]) + } + + const linkSecretRecord = await linkSecretRepository.findDefault(agentContext) + if (!linkSecretRecord) { + // No default link secret + throw new AriesFrameworkError( + 'No default link secret found. Indy SDK requires a default link secret to be created before creating a proof.' + ) + } + + const indyProof = await this.indySdk.proverCreateProof( + agentContext.wallet.handle, + proofRequest as IndyProofRequest, + this.parseSelectedCredentials(selectedCredentials), + linkSecretRecord.linkSecretId, + indySchemas, + indyCredentialDefinitions, + indyRevocationStates + ) + + agentContext.config.logger.trace('Created Indy Proof', { + indyProof, + }) + + return indyProof + } catch (error) { + agentContext.config.logger.error(`Error creating Indy Proof`, { + error, + proofRequest, + selectedCredentials, + }) + + throw isIndyError(error) ? new IndySdkError(error) : error + } + } + + public async storeCredential(agentContext: AgentContext, options: StoreCredentialOptions): Promise { + assertIndySdkWallet(agentContext.wallet) + assertAllUnqualified({ + schemaIds: [options.credentialDefinition.schemaId, options.credential.schema_id], + credentialDefinitionIds: [options.credentialDefinitionId, options.credential.cred_def_id], + revocationRegistryIds: [options.revocationRegistry?.id, options.credential.rev_reg_id], + }) + + const indyRevocationRegistryDefinition = options.revocationRegistry + ? indySdkRevocationRegistryDefinitionFromAnonCreds( + options.revocationRegistry.id, + options.revocationRegistry.definition + ) + : null + + try { + return await this.indySdk.proverStoreCredential( + agentContext.wallet.handle, + options.credentialId ?? null, + indySdkCredentialRequestMetadataFromAnonCreds(options.credentialRequestMetadata), + options.credential, + indySdkCredentialDefinitionFromAnonCreds(options.credentialDefinitionId, options.credentialDefinition), + indyRevocationRegistryDefinition + ) + } catch (error) { + agentContext.config.logger.error(`Error storing Indy Credential '${options.credentialId}'`, { + error, + }) + + throw isIndyError(error) ? new IndySdkError(error) : error + } + } + + public async getCredential( + agentContext: AgentContext, + options: GetCredentialOptions + ): Promise { + assertIndySdkWallet(agentContext.wallet) + + try { + const result = await this.indySdk.proverGetCredential(agentContext.wallet.handle, options.credentialId) + + return { + credentialDefinitionId: result.cred_def_id, + attributes: result.attrs, + credentialId: result.referent, + schemaId: result.schema_id, + credentialRevocationId: result.cred_rev_id, + revocationRegistryId: result.rev_reg_id, + methodName: 'indy', + } + } catch (error) { + agentContext.config.logger.error(`Error getting Indy Credential '${options.credentialId}'`, { + error, + }) + + throw isIndyError(error) ? new IndySdkError(error) : error + } + } + + public async getCredentials(agentContext: AgentContext, options: GetCredentialsOptions) { + assertIndySdkWallet(agentContext.wallet) + + // Indy SDK only supports indy credentials + if (options.methodName && options.methodName !== 'indy') { + return [] + } + + assertAllUnqualified({ + credentialDefinitionIds: [options.credentialDefinitionId], + schemaIds: [options.schemaId], + issuerIds: [options.issuerId, options.schemaIssuerId], + }) + + const credentials = await this.indySdk.proverGetCredentials(agentContext.wallet.handle, { + cred_def_id: options.credentialDefinitionId, + schema_id: options.schemaId, + schema_issuer_did: options.schemaIssuerId, + schema_name: options.schemaName, + schema_version: options.schemaVersion, + issuer_did: options.issuerId, + }) + + return credentials.map((credential) => ({ + credentialDefinitionId: credential.cred_def_id, + attributes: credential.attrs, + credentialId: credential.referent, + schemaId: credential.schema_id, + credentialRevocationId: credential.cred_rev_id, + revocationRegistryId: credential.rev_reg_id, + methodName: 'indy', + })) + } + + public async createCredentialRequest( + agentContext: AgentContext, + options: CreateCredentialRequestOptions + ): Promise { + assertIndySdkWallet(agentContext.wallet) + + assertUnqualifiedCredentialOffer(options.credentialOffer) + assertAllUnqualified({ + schemaIds: [options.credentialDefinition.schemaId], + issuerIds: [options.credentialDefinition.issuerId], + }) + + if (!options.useLegacyProverDid) { + throw new AriesFrameworkError('Indy SDK only supports legacy prover did for credential requests') + } + + const linkSecretRepository = agentContext.dependencyManager.resolve(AnonCredsLinkSecretRepository) + + // We just generate a prover did like string, as it's not used for anything and we don't need + // to prove ownership of the did. It's deprecated in AnonCreds v1, but kept for backwards compatibility + const proverDid = generateLegacyProverDidLikeString() + + // If a link secret is specified, use it. Otherwise, attempt to use default link secret + const linkSecretRecord = options.linkSecretId + ? await linkSecretRepository.getByLinkSecretId(agentContext, options.linkSecretId) + : await linkSecretRepository.findDefault(agentContext) + + if (!linkSecretRecord) { + // No default link secret + throw new AriesFrameworkError( + 'No link secret provided to createCredentialRequest and no default link secret has been found' + ) + } + + try { + const result = await this.indySdk.proverCreateCredentialReq( + agentContext.wallet.handle, + proverDid, + options.credentialOffer, + // NOTE: Is it safe to use the cred_def_id from the offer? I think so. You can't create a request + // for a cred def that is not in the offer + indySdkCredentialDefinitionFromAnonCreds(options.credentialOffer.cred_def_id, options.credentialDefinition), + linkSecretRecord.linkSecretId + ) + + return { + credentialRequest: result[0], + // The type is typed as a Record in the indy-sdk, but the anoncreds package contains the correct type + credentialRequestMetadata: anonCredsCredentialRequestMetadataFromIndySdk(result[1]), + } + } catch (error) { + agentContext.config.logger.error(`Error creating Indy Credential Request`, { + error, + credentialOffer: options.credentialOffer, + }) + + throw isIndyError(error) ? new IndySdkError(error) : error + } + } + + public async deleteCredential(agentContext: AgentContext, credentialId: string): Promise { + assertIndySdkWallet(agentContext.wallet) + + try { + return await this.indySdk.proverDeleteCredential(agentContext.wallet.handle, credentialId) + } catch (error) { + agentContext.config.logger.error(`Error deleting Indy Credential from Wallet`, { + error, + }) + + throw isIndyError(error) ? new IndySdkError(error) : error + } + } + + public async getCredentialsForProofRequest( + agentContext: AgentContext, + options: GetCredentialsForProofRequestOptions + ): Promise { + assertIndySdkWallet(agentContext.wallet) + assertUnqualifiedProofRequest(options.proofRequest) + + try { + // Open indy credential search + const searchHandle = await this.indySdk.proverSearchCredentialsForProofReq( + agentContext.wallet.handle, + options.proofRequest as IndyProofRequest, + options.extraQuery ?? null + ) + + const start = options.start ?? 0 + + try { + // Make sure database cursors start at 'start' (bit ugly, but no way around in indy) + if (start > 0) { + await this.fetchCredentialsForReferent(agentContext, searchHandle, options.attributeReferent, start) + } + + // Fetch the credentials + const credentials = await this.fetchCredentialsForReferent( + agentContext, + searchHandle, + options.attributeReferent, + options.limit + ) + + // TODO: sort the credentials (irrevocable first) + return credentials.map((credential) => ({ + credentialInfo: { + credentialDefinitionId: credential.cred_info.cred_def_id, + credentialId: credential.cred_info.referent, + attributes: credential.cred_info.attrs, + schemaId: credential.cred_info.schema_id, + revocationRegistryId: credential.cred_info.rev_reg_id, + credentialRevocationId: credential.cred_info.cred_rev_id, + methodName: 'indy', + }, + interval: credential.interval, + })) + } finally { + // Always close search + await this.indySdk.proverCloseCredentialsSearchForProofReq(searchHandle) + } + } catch (error) { + if (isIndyError(error)) { + throw new IndySdkError(error) + } + + throw error + } + } + + private async fetchCredentialsForReferent( + agentContext: AgentContext, + searchHandle: number, + referent: string, + limit?: number + ) { + try { + let credentials: IndySdkCredential[] = [] + + // Allow max of 256 per fetch operation + const chunk = limit ? Math.min(256, limit) : 256 + + // Loop while limit not reached (or no limit specified) + while (!limit || credentials.length < limit) { + // Retrieve credentials + const credentialsJson = await this.indySdk.proverFetchCredentialsForProofReq(searchHandle, referent, chunk) + credentials = [...credentials, ...credentialsJson] + + // If the number of credentials returned is less than chunk + // It means we reached the end of the iterator (no more credentials) + if (credentialsJson.length < chunk) { + return credentials + } + } + + return credentials + } catch (error) { + agentContext.config.logger.error(`Error Fetching Indy Credentials For Referent`, { + error, + }) + + throw isIndyError(error) ? new IndySdkError(error) : error + } + } + + /** + * Converts a public api form of {@link AnonCredsSelectedCredentials} interface into a format {@link Indy.IndyRequestedCredentials} that Indy SDK expects. + **/ + private parseSelectedCredentials(selectedCredentials: AnonCredsSelectedCredentials): IndyRequestedCredentials { + const indyRequestedCredentials: IndyRequestedCredentials = { + requested_attributes: {}, + requested_predicates: {}, + self_attested_attributes: {}, + } + + for (const groupName in selectedCredentials.attributes) { + indyRequestedCredentials.requested_attributes[groupName] = { + cred_id: selectedCredentials.attributes[groupName].credentialId, + revealed: selectedCredentials.attributes[groupName].revealed, + timestamp: selectedCredentials.attributes[groupName].timestamp, + } + } + + for (const groupName in selectedCredentials.predicates) { + indyRequestedCredentials.requested_predicates[groupName] = { + cred_id: selectedCredentials.predicates[groupName].credentialId, + timestamp: selectedCredentials.predicates[groupName].timestamp, + } + } + + return indyRequestedCredentials + } +} diff --git a/packages/indy-sdk/src/anoncreds/services/IndySdkIssuerService.ts b/packages/indy-sdk/src/anoncreds/services/IndySdkIssuerService.ts new file mode 100644 index 0000000000..01973d31dd --- /dev/null +++ b/packages/indy-sdk/src/anoncreds/services/IndySdkIssuerService.ts @@ -0,0 +1,157 @@ +import type { CreateCredentialDefinitionMetadata } from './IndySdkIssuerServiceMetadata' +import type { + AnonCredsIssuerService, + CreateCredentialDefinitionOptions, + CreateCredentialOfferOptions, + CreateCredentialOptions, + CreateCredentialReturn, + CreateSchemaOptions, + AnonCredsCredentialOffer, + AnonCredsSchema, + CreateCredentialDefinitionReturn, +} from '@aries-framework/anoncreds' +import type { AgentContext } from '@aries-framework/core' + +import { parseIndyDid, getUnqualifiedSchemaId, generateLegacyProverDidLikeString } from '@aries-framework/anoncreds' +import { injectable, AriesFrameworkError, inject } from '@aries-framework/core' + +import { IndySdkError, isIndyError } from '../../error' +import { IndySdk, IndySdkSymbol } from '../../types' +import { assertIndySdkWallet } from '../../utils/assertIndySdkWallet' +import { + assertUnqualifiedCredentialDefinitionId, + assertUnqualifiedCredentialOffer, + assertUnqualifiedCredentialRequest, + assertUnqualifiedRevocationRegistryId, +} from '../utils/assertUnqualified' +import { createTailsReader } from '../utils/tails' +import { indySdkSchemaFromAnonCreds } from '../utils/transform' + +@injectable() +export class IndySdkIssuerService implements AnonCredsIssuerService { + private indySdk: IndySdk + + public constructor(@inject(IndySdkSymbol) indySdk: IndySdk) { + this.indySdk = indySdk + } + + public async createSchema(agentContext: AgentContext, options: CreateSchemaOptions): Promise { + // We only support passing qualified did:indy issuer ids in the indy issuer service when creating objects + const { namespaceIdentifier } = parseIndyDid(options.issuerId) + + const { name, version, attrNames, issuerId } = options + assertIndySdkWallet(agentContext.wallet) + + try { + const [, schema] = await this.indySdk.issuerCreateSchema(namespaceIdentifier, name, version, attrNames) + + return { + issuerId, + attrNames: schema.attrNames, + name: schema.name, + version: schema.version, + } + } catch (error) { + throw isIndyError(error) ? new IndySdkError(error) : error + } + } + + public async createCredentialDefinition( + agentContext: AgentContext, + options: CreateCredentialDefinitionOptions, + metadata?: CreateCredentialDefinitionMetadata + ): Promise { + const { tag, supportRevocation, schema, issuerId, schemaId } = options + + // We only support passing qualified did:indy issuer ids in the indy issuer service when creating objects + const { namespaceIdentifier } = parseIndyDid(options.issuerId) + + // parse schema in a way that supports both unqualified and qualified identifiers + const legacySchemaId = getUnqualifiedSchemaId(namespaceIdentifier, schema.name, schema.version) + + if (!metadata) + throw new AriesFrameworkError('The metadata parameter is required when using Indy, but received undefined.') + + try { + assertIndySdkWallet(agentContext.wallet) + const [, credentialDefinition] = await this.indySdk.issuerCreateAndStoreCredentialDef( + agentContext.wallet.handle, + namespaceIdentifier, + indySdkSchemaFromAnonCreds(legacySchemaId, schema, metadata.indyLedgerSchemaSeqNo), + tag, + 'CL', + { + support_revocation: supportRevocation, + } + ) + + return { + credentialDefinition: { + issuerId, + tag: credentialDefinition.tag, + schemaId, + type: 'CL', + value: credentialDefinition.value, + }, + } + } catch (error) { + throw isIndyError(error) ? new IndySdkError(error) : error + } + } + + public async createCredentialOffer( + agentContext: AgentContext, + options: CreateCredentialOfferOptions + ): Promise { + assertIndySdkWallet(agentContext.wallet) + assertUnqualifiedCredentialDefinitionId(options.credentialDefinitionId) + + try { + return await this.indySdk.issuerCreateCredentialOffer(agentContext.wallet.handle, options.credentialDefinitionId) + } catch (error) { + throw isIndyError(error) ? new IndySdkError(error) : error + } + } + + public async createCredential( + agentContext: AgentContext, + options: CreateCredentialOptions + ): Promise { + const { tailsFilePath, credentialOffer, credentialRequest, credentialValues, revocationRegistryId } = options + + assertIndySdkWallet(agentContext.wallet) + assertUnqualifiedCredentialOffer(options.credentialOffer) + assertUnqualifiedCredentialRequest(options.credentialRequest) + if (options.revocationRegistryId) { + assertUnqualifiedRevocationRegistryId(options.revocationRegistryId) + } + + try { + // Indy SDK requires tailsReaderHandle. Use null if no tailsFilePath is present + const tailsReaderHandle = tailsFilePath ? await createTailsReader(agentContext, tailsFilePath) : 0 + + if (revocationRegistryId || tailsFilePath) { + throw new AriesFrameworkError('Revocation not supported yet') + } + + // prover_did is deprecated and thus if not provided we generate something on our side, as it's still required by the indy sdk + const proverDid = credentialRequest.prover_did ?? generateLegacyProverDidLikeString() + + const [credential, credentialRevocationId] = await this.indySdk.issuerCreateCredential( + agentContext.wallet.handle, + credentialOffer, + { ...credentialRequest, prover_did: proverDid }, + credentialValues, + revocationRegistryId ?? null, + tailsReaderHandle + ) + + return { + credential, + credentialRevocationId, + } + } catch (error) { + throw isIndyError(error) ? new IndySdkError(error) : error + } + } +} diff --git a/packages/indy-sdk/src/anoncreds/services/IndySdkIssuerServiceMetadata.ts b/packages/indy-sdk/src/anoncreds/services/IndySdkIssuerServiceMetadata.ts new file mode 100644 index 0000000000..bb02f17967 --- /dev/null +++ b/packages/indy-sdk/src/anoncreds/services/IndySdkIssuerServiceMetadata.ts @@ -0,0 +1,3 @@ +export type CreateCredentialDefinitionMetadata = { + indyLedgerSchemaSeqNo: number +} diff --git a/packages/indy-sdk/src/anoncreds/services/IndySdkRevocationService.ts b/packages/indy-sdk/src/anoncreds/services/IndySdkRevocationService.ts new file mode 100644 index 0000000000..6690fb6ab3 --- /dev/null +++ b/packages/indy-sdk/src/anoncreds/services/IndySdkRevocationService.ts @@ -0,0 +1,155 @@ +import type { + AnonCredsRevocationRegistryDefinition, + AnonCredsRevocationStatusList, + AnonCredsProofRequest, + AnonCredsSelectedCredentials, + AnonCredsCredentialInfo, + AnonCredsNonRevokedInterval, +} from '@aries-framework/anoncreds' +import type { AgentContext } from '@aries-framework/core' +import type { RevStates } from 'indy-sdk' + +import { assertBestPracticeRevocationInterval } from '@aries-framework/anoncreds' +import { AriesFrameworkError, inject, injectable } from '@aries-framework/core' + +import { IndySdkError, isIndyError } from '../../error' +import { IndySdk, IndySdkSymbol } from '../../types' +import { createTailsReader } from '../utils/tails' +import { + indySdkRevocationDeltaFromAnonCreds, + indySdkRevocationRegistryDefinitionFromAnonCreds, +} from '../utils/transform' + +enum RequestReferentType { + Attribute = 'attribute', + Predicate = 'predicate', + SelfAttestedAttribute = 'self-attested-attribute', +} + +/** + * Internal class that handles revocation related logic for the Indy SDK + * + * @internal + */ +@injectable() +export class IndySdkRevocationService { + private indySdk: IndySdk + + public constructor(@inject(IndySdkSymbol) indySdk: IndySdk) { + this.indySdk = indySdk + } + + /** + * Creates the revocation state for the requested credentials in a format that the Indy SDK expects. + */ + public async createRevocationState( + agentContext: AgentContext, + proofRequest: AnonCredsProofRequest, + selectedCredentials: AnonCredsSelectedCredentials, + revocationRegistries: { + [revocationRegistryDefinitionId: string]: { + // Tails is already downloaded + tailsFilePath: string + definition: AnonCredsRevocationRegistryDefinition + revocationStatusLists: { + [timestamp: string]: AnonCredsRevocationStatusList + } + } + } + ): Promise { + try { + agentContext.config.logger.debug(`Creating Revocation State(s) for proof request`, { + proofRequest, + selectedCredentials, + }) + const indyRevocationStates: RevStates = {} + const referentCredentials: Array<{ + type: RequestReferentType + referent: string + credentialInfo: AnonCredsCredentialInfo + referentRevocationInterval: AnonCredsNonRevokedInterval | undefined + timestamp: number | undefined + }> = [] + + //Retrieve information for referents and push to single array + for (const [referent, selectedCredential] of Object.entries(selectedCredentials.attributes ?? {})) { + referentCredentials.push({ + referent, + credentialInfo: selectedCredential.credentialInfo, + type: RequestReferentType.Attribute, + referentRevocationInterval: proofRequest.requested_attributes[referent].non_revoked, + timestamp: selectedCredential.timestamp, + }) + } + for (const [referent, selectedCredential] of Object.entries(selectedCredentials.predicates ?? {})) { + referentCredentials.push({ + referent, + credentialInfo: selectedCredential.credentialInfo, + type: RequestReferentType.Predicate, + referentRevocationInterval: proofRequest.requested_predicates[referent].non_revoked, + timestamp: selectedCredential.timestamp, + }) + } + + for (const { referent, credentialInfo, type, referentRevocationInterval, timestamp } of referentCredentials) { + // Prefer referent-specific revocation interval over global revocation interval + const requestRevocationInterval = referentRevocationInterval ?? proofRequest.non_revoked + const credentialRevocationId = credentialInfo.credentialRevocationId + const revocationRegistryId = credentialInfo.revocationRegistryId + + // If revocation interval is present and the credential is revocable then create revocation state + if (requestRevocationInterval && timestamp && credentialRevocationId && revocationRegistryId) { + agentContext.config.logger.trace( + `Presentation is requesting proof of non revocation for ${type} referent '${referent}', creating revocation state for credential`, + { + requestRevocationInterval, + credentialRevocationId, + revocationRegistryId, + } + ) + + assertBestPracticeRevocationInterval(requestRevocationInterval) + + const { definition, revocationStatusLists, tailsFilePath } = revocationRegistries[revocationRegistryId] + + // Extract revocation status list for the given timestamp + const revocationStatusList = revocationStatusLists[timestamp] + if (!revocationStatusList) { + throw new AriesFrameworkError( + `Revocation status list for revocation registry ${revocationRegistryId} and timestamp ${timestamp} not found in revocation status lists. All revocation status lists must be present.` + ) + } + + const tails = await createTailsReader(agentContext, tailsFilePath) + + const revocationState = await this.indySdk.createRevocationState( + tails, + indySdkRevocationRegistryDefinitionFromAnonCreds(revocationRegistryId, definition), + indySdkRevocationDeltaFromAnonCreds(revocationStatusList), + revocationStatusList.timestamp, + credentialRevocationId + ) + + if (!indyRevocationStates[revocationRegistryId]) { + indyRevocationStates[revocationRegistryId] = {} + } + indyRevocationStates[revocationRegistryId][timestamp] = revocationState + } + } + + agentContext.config.logger.debug(`Created Revocation States for Proof Request`, { + indyRevocationStates, + }) + + return indyRevocationStates + } catch (error) { + agentContext.config.logger.error(`Error creating Indy Revocation State for Proof Request`, { + error, + proofRequest, + selectedCredentials, + }) + + throw isIndyError(error) ? new IndySdkError(error) : error + } + } +} diff --git a/packages/indy-sdk/src/anoncreds/services/IndySdkVerifierService.ts b/packages/indy-sdk/src/anoncreds/services/IndySdkVerifierService.ts new file mode 100644 index 0000000000..5d03e7e18c --- /dev/null +++ b/packages/indy-sdk/src/anoncreds/services/IndySdkVerifierService.ts @@ -0,0 +1,95 @@ +import type { AnonCredsVerifierService, VerifyProofOptions } from '@aries-framework/anoncreds' +import type { AgentContext } from '@aries-framework/core' +import type { CredentialDefs, Schemas, RevocRegDefs, RevRegs, IndyProofRequest, IndyProof } from 'indy-sdk' + +import { parseIndyCredentialDefinitionId } from '@aries-framework/anoncreds' +import { inject, injectable } from '@aries-framework/core' + +import { IndySdkError, isIndyError } from '../../error' +import { IndySdk, IndySdkSymbol } from '../../types' +import { assertAllUnqualified } from '../utils/assertUnqualified' +import { + indySdkCredentialDefinitionFromAnonCreds, + indySdkRevocationRegistryDefinitionFromAnonCreds, + indySdkRevocationRegistryFromAnonCreds, + indySdkSchemaFromAnonCreds, +} from '../utils/transform' + +@injectable() +export class IndySdkVerifierService implements AnonCredsVerifierService { + private indySdk: IndySdk + + public constructor(@inject(IndySdkSymbol) indySdk: IndySdk) { + this.indySdk = indySdk + } + + public async verifyProof(agentContext: AgentContext, options: VerifyProofOptions): Promise { + assertAllUnqualified({ + credentialDefinitionIds: Object.keys(options.credentialDefinitions), + schemaIds: Object.keys(options.schemas), + revocationRegistryIds: Object.keys(options.revocationRegistries), + }) + + try { + // The AnonCredsSchema doesn't contain the seqNo anymore. However, the indy credential definition id + // does contain the seqNo, so we can extract it from the credential definition id. + const seqNoMap: { [schemaId: string]: number } = {} + + // Convert AnonCreds credential definitions to Indy credential definitions + const indyCredentialDefinitions: CredentialDefs = {} + for (const credentialDefinitionId in options.credentialDefinitions) { + const credentialDefinition = options.credentialDefinitions[credentialDefinitionId] + + indyCredentialDefinitions[credentialDefinitionId] = indySdkCredentialDefinitionFromAnonCreds( + credentialDefinitionId, + credentialDefinition + ) + + // Get the seqNo for the schemas so we can use it when transforming the schemas + const { schemaSeqNo } = parseIndyCredentialDefinitionId(credentialDefinitionId) + seqNoMap[credentialDefinition.schemaId] = Number(schemaSeqNo) + } + + // Convert AnonCreds schemas to Indy schemas + const indySchemas: Schemas = {} + for (const schemaId in options.schemas) { + const schema = options.schemas[schemaId] + indySchemas[schemaId] = indySdkSchemaFromAnonCreds(schemaId, schema, seqNoMap[schemaId]) + } + + // Convert AnonCreds revocation definitions to Indy revocation definitions + const indyRevocationDefinitions: RevocRegDefs = {} + const indyRevocationRegistries: RevRegs = {} + + for (const revocationRegistryDefinitionId in options.revocationRegistries) { + const { definition, revocationStatusLists } = options.revocationRegistries[revocationRegistryDefinitionId] + indyRevocationDefinitions[revocationRegistryDefinitionId] = indySdkRevocationRegistryDefinitionFromAnonCreds( + revocationRegistryDefinitionId, + definition + ) + + // Initialize empty object for this revocation registry + indyRevocationRegistries[revocationRegistryDefinitionId] = {} + + // Also transform the revocation lists for the specified timestamps into the revocation registry + // format Indy expects + for (const timestamp in revocationStatusLists) { + const revocationStatusList = revocationStatusLists[timestamp] + indyRevocationRegistries[revocationRegistryDefinitionId][timestamp] = + indySdkRevocationRegistryFromAnonCreds(revocationStatusList) + } + } + + return await this.indySdk.verifierVerifyProof( + options.proofRequest as IndyProofRequest, + options.proof as IndyProof, + indySchemas, + indyCredentialDefinitions, + indyRevocationDefinitions, + indyRevocationRegistries + ) + } catch (error) { + throw isIndyError(error) ? new IndySdkError(error) : error + } + } +} diff --git a/packages/indy-sdk/src/anoncreds/utils/__tests__/assertUnqualified.test.ts b/packages/indy-sdk/src/anoncreds/utils/__tests__/assertUnqualified.test.ts new file mode 100644 index 0000000000..3475cc48bc --- /dev/null +++ b/packages/indy-sdk/src/anoncreds/utils/__tests__/assertUnqualified.test.ts @@ -0,0 +1,152 @@ +import type { AnonCredsCredentialOffer, AnonCredsCredentialRequest } from '@aries-framework/anoncreds' + +import { + assertUnqualifiedCredentialDefinitionId, + assertUnqualifiedCredentialOffer, + assertUnqualifiedCredentialRequest, + assertUnqualifiedIssuerId, + assertUnqualifiedProofRequest, + assertUnqualifiedRevocationRegistryId, + assertUnqualifiedSchemaId, +} from '../assertUnqualified' + +describe('assertUnqualified', () => { + describe('assertUnqualifiedCredentialDefinitionId', () => { + test('throws when a non-unqualified credential definition id is passed', () => { + expect(() => + assertUnqualifiedCredentialDefinitionId( + 'did:indy:local:N7baRMcyvPwWc8v85CtZ6e/anoncreds/v0/CLAIM_DEF/100669/SCH Employee ID' + ) + ).toThrow() + }) + + test('does not throw when an unqualified credential definition id is passed', () => { + expect(() => + assertUnqualifiedCredentialDefinitionId('N7baRMcyvPwWc8v85CtZ6e:3:CL:100669:SCH Employee ID') + ).not.toThrow() + }) + }) + + describe('assertUnqualifiedSchemaId', () => { + test('throws when a non-unqualified schema id is passed', () => { + expect(() => + assertUnqualifiedSchemaId('did:indy:local:BQ42WeE24jFHeyGg8x9XAz/anoncreds/v0/SCHEMA/Medical Bill/1.0') + ).toThrowError('Schema id') + }) + + test('does not throw when an unqualified schema id is passed', () => { + expect(() => assertUnqualifiedSchemaId('BQ42WeE24jFHeyGg8x9XAz:2:Medical Bill:1.0')).not.toThrow() + }) + }) + + describe('assertUnqualifiedRevocationRegistryId', () => { + test('throws when a non-unqualified revocation registry id is passed', () => { + expect(() => + assertUnqualifiedRevocationRegistryId( + 'did:indy:local:N7baRMcyvPwWc8v85CtZ6e/anoncreds/v0/REV_REG_DEF/100669/SCH Employee ID/1-1024' + ) + ).toThrowError('Revocation registry id') + }) + + test('does not throw when an unqualified revocation registry id is passed', () => { + expect(() => + assertUnqualifiedRevocationRegistryId( + 'N7baRMcyvPwWc8v85CtZ6e:4:N7baRMcyvPwWc8v85CtZ6e:3:CL:100669:SCH Employee ID:CL_ACCUM:1-1024' + ) + ).not.toThrow() + }) + }) + + describe('assertUnqualifiedIssuerId', () => { + test('throws when a non-unqualified issuer id is passed', () => { + expect(() => assertUnqualifiedIssuerId('did:indy:sovrin:N7baRMcyvPwWc8v85CtZ6e')).toThrowError('Issuer id') + }) + + test('does not throw when an unqualified issuer id is passed', () => { + expect(() => assertUnqualifiedIssuerId('N7baRMcyvPwWc8v85CtZ6e')).not.toThrow() + }) + }) + + describe('assertUnqualifiedCredentialOffer', () => { + test('throws when non-unqualified identifiers are passed', () => { + expect(() => + assertUnqualifiedCredentialOffer({ + cred_def_id: 'did:indy:local:N7baRMcyvPwWc8v85CtZ6e/anoncreds/v0/CLAIM_DEF/100669/SCH Employee ID', + schema_id: 'BQ42WeE24jFHeyGg8x9XAz:2:Medical Bill:1.0', + } as AnonCredsCredentialOffer) + ).toThrowError('Credential definition id') + + expect(() => + assertUnqualifiedCredentialOffer({ + cred_def_id: 'N7baRMcyvPwWc8v85CtZ6e:3:CL:100669:SCH Employee ID', + schema_id: 'did:indy:local:BQ42WeE24jFHeyGg8x9XAz/anoncreds/v0/SCHEMA/Medical Bill/1.0', + } as AnonCredsCredentialOffer) + ).toThrowError('Schema id') + }) + + test('does not throw when only unqualified identifiers are passed', () => { + expect(() => + assertUnqualifiedCredentialOffer({ + cred_def_id: 'N7baRMcyvPwWc8v85CtZ6e:3:CL:100669:SCH Employee ID', + schema_id: 'BQ42WeE24jFHeyGg8x9XAz:2:Medical Bill:1.0', + } as AnonCredsCredentialOffer) + ).not.toThrow() + }) + }) + + describe('assertUnqualifiedCredentialRequest', () => { + test('throws when non-unqualified identifiers are passed', () => { + expect(() => + assertUnqualifiedCredentialRequest({ + cred_def_id: 'did:indy:local:N7baRMcyvPwWc8v85CtZ6e/anoncreds/v0/CLAIM_DEF/100669/SCH Employee ID', + } as AnonCredsCredentialRequest) + ).toThrowError('Credential definition id') + }) + + test('does not throw when only unqualified identifiers are passed', () => { + expect(() => + assertUnqualifiedCredentialRequest({ + cred_def_id: 'N7baRMcyvPwWc8v85CtZ6e:3:CL:100669:SCH Employee ID', + } as AnonCredsCredentialRequest) + ).not.toThrow() + }) + }) + + describe('assertUnqualifiedProofRequest', () => { + test('throws when non-unqualified identifiers are passed', () => { + expect(() => + assertUnqualifiedProofRequest({ + requested_attributes: { + a: { + restrictions: [ + { + cred_def_id: 'did:indy:local:N7baRMcyvPwWc8v85CtZ6e/anoncreds/v0/CLAIM_DEF/100669/SCH Employee ID', + }, + ], + }, + }, + requested_predicates: {}, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + } as any) + ).toThrowError('Credential definition id') + }) + + test('does not throw when only unqualified identifiers are passed', () => { + expect(() => + assertUnqualifiedProofRequest({ + requested_attributes: { + a: { + restrictions: [ + { + schema_id: 'BQ42WeE24jFHeyGg8x9XAz:2:Medical Bill:1.0', + }, + ], + }, + }, + requested_predicates: {}, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + } as any) + ).not.toThrow() + }) + }) +}) diff --git a/packages/indy-sdk/src/anoncreds/utils/__tests__/identifiers.test.ts b/packages/indy-sdk/src/anoncreds/utils/__tests__/identifiers.test.ts new file mode 100644 index 0000000000..9b9a54ba83 --- /dev/null +++ b/packages/indy-sdk/src/anoncreds/utils/__tests__/identifiers.test.ts @@ -0,0 +1,79 @@ +import { + getDidIndyCredentialDefinitionId, + getDidIndyRevocationRegistryId, + getDidIndySchemaId, + indySdkAnonCredsRegistryIdentifierRegex, +} from '../identifiers' + +describe('identifiers', () => { + describe('indySdkAnonCredsRegistryIdentifierRegex', () => { + test('matches against a legacy schema id, credential definition id and revocation registry id', () => { + const did = '7Tqg6BwSSWapxgUDm9KKgg' + const schemaId = 'BQ42WeE24jFHeyGg8x9XAz:2:Medical Bill:1.0' + const credentialDefinitionId = 'N7baRMcyvPwWc8v85CtZ6e:3:CL:100669:SCH Employee ID' + const revocationRegistryId = + 'N7baRMcyvPwWc8v85CtZ6e:4:N7baRMcyvPwWc8v85CtZ6e:3:CL:100669:SCH Employee ID:CL_ACCUM:1-1024' + + const anotherId = 'some:id' + + // unqualified issuerId not in regex on purpose. See note in implementation. + expect(indySdkAnonCredsRegistryIdentifierRegex.test(did)).toEqual(false) + + expect(indySdkAnonCredsRegistryIdentifierRegex.test(schemaId)).toEqual(true) + expect(indySdkAnonCredsRegistryIdentifierRegex.test(credentialDefinitionId)).toEqual(true) + expect(indySdkAnonCredsRegistryIdentifierRegex.test(revocationRegistryId)).toEqual(true) + expect(indySdkAnonCredsRegistryIdentifierRegex.test(anotherId)).toEqual(false) + }) + + test('matches against a did indy did, schema id, credential definition id and revocation registry id', () => { + const did = 'did:indy:local:7Tqg6BwSSWapxgUDm9KKgg' + const schemaId = 'did:indy:local:BQ42WeE24jFHeyGg8x9XAz/anoncreds/v0/SCHEMA/Medical Bill/1.0' + const credentialDefinitionId = + 'did:indy:local:N7baRMcyvPwWc8v85CtZ6e/anoncreds/v0/CLAIM_DEF/100669/SCH Employee ID' + const revocationRegistryId = + 'did:indy:local:N7baRMcyvPwWc8v85CtZ6e/anoncreds/v0/REV_REG_DEF/100669/SCH Employee ID/1-1024' + + const anotherId = 'did:indy:local:N7baRMcyvPwWc8v85CtZ6e/anoncreds/v0/SOME_DEF' + + expect(indySdkAnonCredsRegistryIdentifierRegex.test(did)).toEqual(true) + expect(indySdkAnonCredsRegistryIdentifierRegex.test(schemaId)).toEqual(true) + expect(indySdkAnonCredsRegistryIdentifierRegex.test(credentialDefinitionId)).toEqual(true) + expect(indySdkAnonCredsRegistryIdentifierRegex.test(revocationRegistryId)).toEqual(true) + expect(indySdkAnonCredsRegistryIdentifierRegex.test(anotherId)).toEqual(false) + }) + }) + + test('getDidIndySchemaId returns a valid schema id given a did, name, and version', () => { + const namespace = 'sovrin:test' + const did = '12345' + const name = 'backbench' + const version = '420' + + expect(getDidIndySchemaId(namespace, did, name, version)).toEqual( + 'did:indy:sovrin:test:12345/anoncreds/v0/SCHEMA/backbench/420' + ) + }) + + test('getDidIndyCredentialDefinitionId returns a valid credential definition id given a did, seqNo, and tag', () => { + const namespace = 'sovrin:test' + const did = '12345' + const seqNo = 420 + const tag = 'someTag' + + expect(getDidIndyCredentialDefinitionId(namespace, did, seqNo, tag)).toEqual( + 'did:indy:sovrin:test:12345/anoncreds/v0/CLAIM_DEF/420/someTag' + ) + }) + + test('getDidIndyRevocationRegistryId returns a valid credential definition id given a did, seqNo, and tag', () => { + const namespace = 'sovrin:test' + const did = '12345' + const seqNo = 420 + const credentialDefinitionTag = 'someTag' + const tag = 'anotherTag' + + expect(getDidIndyRevocationRegistryId(namespace, did, seqNo, credentialDefinitionTag, tag)).toEqual( + 'did:indy:sovrin:test:12345/anoncreds/v0/REV_REG_DEF/420/someTag/anotherTag' + ) + }) +}) diff --git a/packages/indy-sdk/src/anoncreds/utils/__tests__/transform.test.ts b/packages/indy-sdk/src/anoncreds/utils/__tests__/transform.test.ts new file mode 100644 index 0000000000..f94060d8fd --- /dev/null +++ b/packages/indy-sdk/src/anoncreds/utils/__tests__/transform.test.ts @@ -0,0 +1,114 @@ +import type { AnonCredsCredentialDefinition, AnonCredsSchema } from '../../../../../anoncreds/src' +import type { CredDef, Schema } from 'indy-sdk' + +import { + anonCredsCredentialDefinitionFromIndySdk, + anonCredsSchemaFromIndySdk, + indySdkCredentialDefinitionFromAnonCreds, + indySdkSchemaFromAnonCreds, +} from '../transform' + +describe('transform', () => { + it('anonCredsSchemaFromIndySdk should return a valid anoncreds schema', () => { + const schema: Schema = { + attrNames: ['hello'], + id: 'TL1EaPFCZ8Si5aUrqScBDt:2:Example Schema:1.0.0', + name: 'Example Schema', + seqNo: 150, + ver: '1.0', + version: '1.0.0', + } + + expect(anonCredsSchemaFromIndySdk(schema)).toEqual({ + attrNames: ['hello'], + issuerId: 'TL1EaPFCZ8Si5aUrqScBDt', + name: 'Example Schema', + version: '1.0.0', + }) + }) + + it('indySdkSchemaFromAnonCreds should return a valid indy sdk schema', () => { + const schemaId = 'TL1EaPFCZ8Si5aUrqScBDt:2:Example Schema:1.0.0' + const schema: AnonCredsSchema = { + attrNames: ['hello'], + issuerId: 'TL1EaPFCZ8Si5aUrqScBDt', + name: 'Example Schema', + version: '1.0.0', + } + + expect(indySdkSchemaFromAnonCreds(schemaId, schema, 150)).toEqual({ + attrNames: ['hello'], + id: 'TL1EaPFCZ8Si5aUrqScBDt:2:Example Schema:1.0.0', + name: 'Example Schema', + seqNo: 150, + ver: '1.0', + version: '1.0.0', + }) + }) + + it('anonCredsCredentialDefinitionFromIndySdk should return a valid anoncreds credential definition', () => { + const credDef: CredDef = { + id: 'TL1EaPFCZ8Si5aUrqScBDt:3:CL:420:someTag', + schemaId: '8910:2:Example Schema:1.0.0', + tag: 'someTag', + type: 'CL', + value: { + primary: { + something: 'string', + }, + }, + ver: '1.0', + } + + expect(anonCredsCredentialDefinitionFromIndySdk(credDef)).toEqual({ + issuerId: 'TL1EaPFCZ8Si5aUrqScBDt', + schemaId: '8910:2:Example Schema:1.0.0', + tag: 'someTag', + type: 'CL', + value: { + primary: { + something: 'string', + }, + }, + }) + }) + + it('indySdkCredentialDefinitionFromAnonCreds should return a valid indy sdk credential definition', () => { + const credentialDefinitionId = 'TL1EaPFCZ8Si5aUrqScBDt:3:CL:420:someTag' + const credentialDefinition: AnonCredsCredentialDefinition = { + issuerId: 'TL1EaPFCZ8Si5aUrqScBDt', + schemaId: '8910:2:Example Schema:1.0.0', + tag: 'someTag', + type: 'CL', + value: { + primary: { + something: 'string', + }, + }, + } + + expect(indySdkCredentialDefinitionFromAnonCreds(credentialDefinitionId, credentialDefinition)).toEqual({ + id: 'TL1EaPFCZ8Si5aUrqScBDt:3:CL:420:someTag', + schemaId: '8910:2:Example Schema:1.0.0', + tag: 'someTag', + type: 'CL', + value: { + primary: { + something: 'string', + }, + }, + ver: '1.0', + }) + }) + + // TODO: add tests for these models once finalized in the anoncreds spec + test.todo( + 'anonCredsRevocationRegistryDefinitionFromIndySdk should return a valid anoncreds revocation registry definition' + ) + test.todo( + 'indySdkRevocationRegistryDefinitionFromAnonCreds should return a valid indy sdk revocation registry definition' + ) + test.todo('anonCredsRevocationStatusListFromIndySdk should return a valid anoncreds revocation list') + test.todo('indySdkRevocationRegistryFromAnonCreds should return a valid indy sdk revocation registry') + test.todo('indySdkRevocationDeltaFromAnonCreds should return a valid indy sdk revocation delta') +}) diff --git a/packages/indy-sdk/src/anoncreds/utils/assertUnqualified.ts b/packages/indy-sdk/src/anoncreds/utils/assertUnqualified.ts new file mode 100644 index 0000000000..320fadcb6e --- /dev/null +++ b/packages/indy-sdk/src/anoncreds/utils/assertUnqualified.ts @@ -0,0 +1,133 @@ +import type { + AnonCredsCredentialOffer, + AnonCredsCredentialRequest, + AnonCredsProofRequest, +} from '@aries-framework/anoncreds' + +import { + unqualifiedRevocationRegistryIdRegex, + unqualifiedCredentialDefinitionIdRegex, + unqualifiedIndyDidRegex, + unqualifiedSchemaIdRegex, +} from '@aries-framework/anoncreds' +import { AriesFrameworkError } from '@aries-framework/core' + +/** + * Assert that a credential definition id is unqualified. + */ +export function assertUnqualifiedCredentialDefinitionId(credentialDefinitionId: string) { + if (!unqualifiedCredentialDefinitionIdRegex.test(credentialDefinitionId)) { + throw new AriesFrameworkError( + `Credential definition id '${credentialDefinitionId}' is not an unqualified credential definition id. Indy SDK only supports unqualified identifiers.` + ) + } +} + +/** + * Assert that a schema id is unqualified. + */ +export function assertUnqualifiedSchemaId(schemaId: string) { + if (!unqualifiedSchemaIdRegex.test(schemaId)) { + throw new AriesFrameworkError( + `Schema id '${schemaId}' is not an unqualified schema id. Indy SDK only supports unqualified identifiers.` + ) + } +} + +/** + * Assert that a revocation registry id is unqualified. + */ +export function assertUnqualifiedRevocationRegistryId(revocationRegistryId: string) { + if (!unqualifiedRevocationRegistryIdRegex.test(revocationRegistryId)) { + throw new AriesFrameworkError( + `Revocation registry id '${revocationRegistryId}' is not an unqualified revocation registry id. Indy SDK only supports unqualified identifiers.` + ) + } +} + +/** + * Assert that an issuer id is unqualified. + */ +export function assertUnqualifiedIssuerId(issuerId: string) { + if (!unqualifiedIndyDidRegex.test(issuerId)) { + throw new AriesFrameworkError( + `Issuer id '${issuerId}' is not an unqualified issuer id. Indy SDK only supports unqualified identifiers.` + ) + } +} + +/** + * Assert that a credential offer only contains unqualified identifiers. + */ +export function assertUnqualifiedCredentialOffer(credentialOffer: AnonCredsCredentialOffer) { + assertUnqualifiedCredentialDefinitionId(credentialOffer.cred_def_id) + assertUnqualifiedSchemaId(credentialOffer.schema_id) +} + +/** + * Assert that a credential request only contains unqualified identifiers. + */ +export function assertUnqualifiedCredentialRequest(credentialRequest: AnonCredsCredentialRequest) { + assertUnqualifiedCredentialDefinitionId(credentialRequest.cred_def_id) +} + +/** + * Assert that a proof request only contains unqualified identifiers. + */ +export function assertUnqualifiedProofRequest(proofRequest: AnonCredsProofRequest) { + const allRequested = [ + ...Object.values(proofRequest.requested_attributes), + ...Object.values(proofRequest.requested_predicates), + ] + + for (const requested of allRequested) { + for (const restriction of requested.restrictions ?? []) { + assertAllUnqualified({ + credentialDefinitionIds: [restriction.cred_def_id], + schemaIds: [restriction.schema_id], + revocationRegistryIds: [restriction.rev_reg_id], + issuerIds: [restriction.issuer_did, restriction.schema_issuer_did], + }) + } + } +} + +export function assertAllUnqualified({ + schemaIds = [], + credentialDefinitionIds = [], + revocationRegistryIds = [], + issuerIds = [], +}: { + schemaIds?: Array + credentialDefinitionIds?: Array + revocationRegistryIds?: Array + issuerIds?: Array +}) { + for (const schemaId of schemaIds) { + // We don't validate undefined values + if (!schemaId) continue + + assertUnqualifiedSchemaId(schemaId) + } + + for (const credentialDefinitionId of credentialDefinitionIds) { + // We don't validate undefined values + if (!credentialDefinitionId) continue + + assertUnqualifiedCredentialDefinitionId(credentialDefinitionId) + } + + for (const revocationRegistryId of revocationRegistryIds) { + // We don't validate undefined values + if (!revocationRegistryId) continue + + assertUnqualifiedRevocationRegistryId(revocationRegistryId) + } + + for (const issuerId of issuerIds) { + // We don't validate undefined values + if (!issuerId) continue + + assertUnqualifiedIssuerId(issuerId) + } +} diff --git a/packages/indy-sdk/src/anoncreds/utils/identifiers.ts b/packages/indy-sdk/src/anoncreds/utils/identifiers.ts new file mode 100644 index 0000000000..4cedb11ff4 --- /dev/null +++ b/packages/indy-sdk/src/anoncreds/utils/identifiers.ts @@ -0,0 +1,63 @@ +/** + * NOTE: this file is available in both the indy-sdk and indy-vdr packages. If making changes to + * this file, make sure to update both files if applicable. + */ + +import { + unqualifiedSchemaIdRegex, + unqualifiedCredentialDefinitionIdRegex, + unqualifiedRevocationRegistryIdRegex, + didIndyCredentialDefinitionIdRegex, + didIndyRevocationRegistryIdRegex, + didIndySchemaIdRegex, + didIndyRegex, +} from '@aries-framework/anoncreds' + +// combines both legacy and did:indy anoncreds identifiers and also the issuer id +const indySdkAnonCredsRegexes = [ + // NOTE: we only include the qualified issuer id here, as we don't support registering objects based on legacy issuer ids. + // you can still resolve using legacy issuer ids, but you need to use the full did:indy identifier when registering. + // As we find a matching anoncreds registry based on the issuerId only when creating an object, this will make sure + // it will throw an no registry found for identifier error. + // issuer id + didIndyRegex, + + // schema + didIndySchemaIdRegex, + unqualifiedSchemaIdRegex, + + // credential definition + didIndyCredentialDefinitionIdRegex, + unqualifiedCredentialDefinitionIdRegex, + + // revocation registry + unqualifiedRevocationRegistryIdRegex, + didIndyRevocationRegistryIdRegex, +] + +export const indySdkAnonCredsRegistryIdentifierRegex = new RegExp( + indySdkAnonCredsRegexes.map((r) => r.source).join('|') +) + +export function getDidIndySchemaId(namespace: string, unqualifiedDid: string, name: string, version: string) { + return `did:indy:${namespace}:${unqualifiedDid}/anoncreds/v0/SCHEMA/${name}/${version}` +} + +export function getDidIndyCredentialDefinitionId( + namespace: string, + unqualifiedDid: string, + seqNo: string | number, + tag: string +) { + return `did:indy:${namespace}:${unqualifiedDid}/anoncreds/v0/CLAIM_DEF/${seqNo}/${tag}` +} + +export function getDidIndyRevocationRegistryId( + namespace: string, + unqualifiedDid: string, + seqNo: string | number, + credentialDefinitionTag: string, + revocationRegistryTag: string +) { + return `did:indy:${namespace}:${unqualifiedDid}/anoncreds/v0/REV_REG_DEF/${seqNo}/${credentialDefinitionTag}/${revocationRegistryTag}` +} diff --git a/packages/indy-sdk/src/anoncreds/utils/tails.ts b/packages/indy-sdk/src/anoncreds/utils/tails.ts new file mode 100644 index 0000000000..787d757322 --- /dev/null +++ b/packages/indy-sdk/src/anoncreds/utils/tails.ts @@ -0,0 +1,45 @@ +import type { IndySdk } from '../../types' +import type { AgentContext, FileSystem } from '@aries-framework/core' + +import { AriesFrameworkError, getDirFromFilePath, InjectionSymbols } from '@aries-framework/core' + +import { IndySdkError, isIndyError } from '../../error' +import { IndySdkSymbol } from '../../types' + +/** + * Get a handler for the blob storage tails file reader. + * + * @param agentContext The agent context + * @param tailsFilePath The path of the tails file + * @returns The blob storage reader handle + */ +export async function createTailsReader(agentContext: AgentContext, tailsFilePath: string) { + const fileSystem = agentContext.dependencyManager.resolve(InjectionSymbols.FileSystem) + const indySdk = agentContext.dependencyManager.resolve(IndySdkSymbol) + + try { + agentContext.config.logger.debug(`Opening tails reader at path ${tailsFilePath}`) + const tailsFileExists = await fileSystem.exists(tailsFilePath) + + // Extract directory from path (should also work with windows paths) + const dirname = getDirFromFilePath(tailsFilePath) + + if (!tailsFileExists) { + throw new AriesFrameworkError(`Tails file does not exist at path ${tailsFilePath}`) + } + + const tailsReaderConfig = { + base_dir: dirname, + } + + const tailsReader = await indySdk.openBlobStorageReader('default', tailsReaderConfig) + agentContext.config.logger.debug(`Opened tails reader at path ${tailsFilePath}`) + return tailsReader + } catch (error) { + if (isIndyError(error)) { + throw new IndySdkError(error) + } + + throw error + } +} diff --git a/packages/indy-sdk/src/anoncreds/utils/transform.ts b/packages/indy-sdk/src/anoncreds/utils/transform.ts new file mode 100644 index 0000000000..73b5441c93 --- /dev/null +++ b/packages/indy-sdk/src/anoncreds/utils/transform.ts @@ -0,0 +1,161 @@ +import type { + AnonCredsCredentialDefinition, + AnonCredsRevocationStatusList, + AnonCredsRevocationRegistryDefinition, + AnonCredsSchema, + AnonCredsCredentialRequestMetadata, + AnonCredsLinkSecretBlindingData, +} from '@aries-framework/anoncreds' +import type { CredDef, CredReqMetadata, RevocReg, RevocRegDef, RevocRegDelta, Schema } from 'indy-sdk' + +import { parseIndyCredentialDefinitionId, parseIndySchemaId } from '@aries-framework/anoncreds' + +export function anonCredsSchemaFromIndySdk(schema: Schema): AnonCredsSchema { + const { did } = parseIndySchemaId(schema.id) + return { + issuerId: did, + name: schema.name, + version: schema.version, + attrNames: schema.attrNames, + } +} + +export function indySdkSchemaFromAnonCreds(schemaId: string, schema: AnonCredsSchema, indyLedgerSeqNo: number): Schema { + return { + id: schemaId, + attrNames: schema.attrNames, + name: schema.name, + version: schema.version, + ver: '1.0', + seqNo: indyLedgerSeqNo, + } +} + +export function anonCredsCredentialDefinitionFromIndySdk(credentialDefinition: CredDef): AnonCredsCredentialDefinition { + const { did } = parseIndyCredentialDefinitionId(credentialDefinition.id) + + return { + issuerId: did, + schemaId: credentialDefinition.schemaId, + tag: credentialDefinition.tag, + type: 'CL', + value: credentialDefinition.value, + } +} + +export function indySdkCredentialDefinitionFromAnonCreds( + credentialDefinitionId: string, + credentialDefinition: AnonCredsCredentialDefinition +): CredDef { + return { + id: credentialDefinitionId, + schemaId: credentialDefinition.schemaId, + tag: credentialDefinition.tag, + type: credentialDefinition.type, + value: credentialDefinition.value, + ver: '1.0', + } +} + +export function indySdkRevocationRegistryDefinitionFromAnonCreds( + revocationRegistryDefinitionId: string, + revocationRegistryDefinition: AnonCredsRevocationRegistryDefinition +): RevocRegDef { + return { + id: revocationRegistryDefinitionId, + credDefId: revocationRegistryDefinition.credDefId, + revocDefType: revocationRegistryDefinition.revocDefType, + tag: revocationRegistryDefinition.tag, + value: { + issuanceType: 'ISSUANCE_BY_DEFAULT', // NOTE: we always use ISSUANCE_BY_DEFAULT when passing to the indy-sdk. It doesn't matter, as we have the revocation List with the full state + maxCredNum: revocationRegistryDefinition.value.maxCredNum, + publicKeys: revocationRegistryDefinition.value.publicKeys, + tailsHash: revocationRegistryDefinition.value.tailsHash, + tailsLocation: revocationRegistryDefinition.value.tailsLocation, + }, + ver: '1.0', + } +} + +export function anonCredsRevocationStatusListFromIndySdk( + revocationRegistryDefinitionId: string, + revocationRegistryDefinition: AnonCredsRevocationRegistryDefinition, + delta: RevocRegDelta, + timestamp: number, + isIssuanceByDefault: boolean +): AnonCredsRevocationStatusList { + // 0 means unrevoked, 1 means revoked + const defaultState = isIssuanceByDefault ? 0 : 1 + + // Fill with default value + const revocationList = new Array(revocationRegistryDefinition.value.maxCredNum).fill(defaultState) + + // Set all `issuer` indexes to 0 (not revoked) + for (const issued of delta.value.issued ?? []) { + revocationList[issued] = 0 + } + + // Set all `revoked` indexes to 1 (revoked) + for (const revoked of delta.value.revoked ?? []) { + revocationList[revoked] = 1 + } + + return { + issuerId: revocationRegistryDefinition.issuerId, + currentAccumulator: delta.value.accum, + revRegDefId: revocationRegistryDefinitionId, + revocationList, + timestamp, + } +} + +export function indySdkRevocationRegistryFromAnonCreds(revocationStatusList: AnonCredsRevocationStatusList): RevocReg { + return { + ver: '1.0', + value: { + accum: revocationStatusList.currentAccumulator, + }, + } +} + +export function indySdkRevocationDeltaFromAnonCreds( + revocationStatusList: AnonCredsRevocationStatusList +): RevocRegDelta { + // Get all indices from the revocationStatusList that are revoked (so have value '1') + const revokedIndices = revocationStatusList.revocationList.reduce( + (revoked, current, index) => (current === 1 ? [...revoked, index] : revoked), + [] + ) + + return { + value: { + accum: revocationStatusList.currentAccumulator, + issued: [], + revoked: revokedIndices, + // NOTE: this must be a valid accumulator but it's not actually used. So we set it to the + // currentAccumulator as that should always be a valid accumulator. + prevAccum: revocationStatusList.currentAccumulator, + }, + ver: '1.0', + } +} + +export function anonCredsCredentialRequestMetadataFromIndySdk( + credentialRequestMetadata: CredReqMetadata +): AnonCredsCredentialRequestMetadata { + return { + link_secret_blinding_data: credentialRequestMetadata.master_secret_blinding_data as AnonCredsLinkSecretBlindingData, + link_secret_name: credentialRequestMetadata.master_secret_name as string, + nonce: credentialRequestMetadata.nonce as string, + } +} + +export function indySdkCredentialRequestMetadataFromAnonCreds( + credentialRequestMetadata: AnonCredsCredentialRequestMetadata +): CredReqMetadata { + return { + master_secret_blinding_data: credentialRequestMetadata.link_secret_blinding_data, + master_secret_name: credentialRequestMetadata.link_secret_name, + nonce: credentialRequestMetadata.nonce, + } +} diff --git a/packages/indy-sdk/src/dids/IndySdkIndyDidRegistrar.ts b/packages/indy-sdk/src/dids/IndySdkIndyDidRegistrar.ts new file mode 100644 index 0000000000..2a3c6c3097 --- /dev/null +++ b/packages/indy-sdk/src/dids/IndySdkIndyDidRegistrar.ts @@ -0,0 +1,328 @@ +import type { IndyEndpointAttrib } from './didSovUtil' +import type { IndySdkPool } from '../ledger' +import type { IndySdk } from '../types' +import type { + AgentContext, + Buffer, + DidCreateOptions, + DidCreateResult, + DidDeactivateResult, + DidRegistrar, + DidUpdateResult, +} from '@aries-framework/core' +import type { NymRole } from 'indy-sdk' + +import { parseIndyDid } from '@aries-framework/anoncreds' +import { DidDocumentRole, DidRecord, DidRepository, KeyType, Key } from '@aries-framework/core' + +import { IndySdkError } from '../error' +import { isIndyError } from '../error/indyError' +import { IndySdkPoolService } from '../ledger' +import { IndySdkSymbol } from '../types' +import { assertIndySdkWallet } from '../utils/assertIndySdkWallet' +import { isLegacySelfCertifiedDid, legacyIndyDidFromPublicKeyBase58 } from '../utils/did' + +import { createKeyAgreementKey, indyDidDocumentFromDid, verificationKeyForIndyDid } from './didIndyUtil' +import { addServicesFromEndpointsAttrib } from './didSovUtil' + +export class IndySdkIndyDidRegistrar implements DidRegistrar { + public readonly supportedMethods = ['indy'] + + public async create(agentContext: AgentContext, options: IndySdkIndyDidCreateOptions): Promise { + const indySdkPoolService = agentContext.dependencyManager.resolve(IndySdkPoolService) + const didRepository = agentContext.dependencyManager.resolve(DidRepository) + + const { alias, role, submitterDid, endpoints } = options.options + let did = options.did + let namespaceIdentifier: string + let verificationKey: Key + const privateKey = options.secret?.privateKey + + if (did && privateKey) { + return { + didDocumentMetadata: {}, + didRegistrationMetadata: {}, + didState: { + state: 'failed', + reason: `Only one of 'privateKey' or 'did' must be provided`, + }, + } + } + + try { + assertIndySdkWallet(agentContext.wallet) + + // Parse submitterDid and extract namespace based on the submitter did + const { namespace: submitterNamespace, namespaceIdentifier: submitterNamespaceIdentifier } = + parseIndyDid(submitterDid) + const submitterSigningKey = await verificationKeyForIndyDid(agentContext, submitterDid) + + // Only supports version 1 did identifier (which is same as did:sov) + if (did) { + if (!options.options.verkey) { + return { + didDocumentMetadata: {}, + didRegistrationMetadata: {}, + didState: { + state: 'failed', + reason: 'If a did is defined, a matching verkey must be provided', + }, + } + } + + const { namespace, namespaceIdentifier: _namespaceIdentifier } = parseIndyDid(did) + namespaceIdentifier = _namespaceIdentifier + + verificationKey = Key.fromPublicKeyBase58(options.options.verkey, KeyType.Ed25519) + + if (!isLegacySelfCertifiedDid(namespaceIdentifier, options.options.verkey)) { + return { + didDocumentMetadata: {}, + didRegistrationMetadata: {}, + didState: { + state: 'failed', + reason: `Did must be first 16 bytes of the the verkey base58 encoded.`, + }, + } + } + + if (submitterNamespace !== namespace) { + return { + didDocumentMetadata: {}, + didRegistrationMetadata: {}, + didState: { + state: 'failed', + reason: `The submitter did uses namespace ${submitterNamespace} and the did to register uses namespace ${namespace}. Namespaces must match.`, + }, + } + } + } else { + // Create a new key and calculate did according to the rules for indy did method + verificationKey = await agentContext.wallet.createKey({ privateKey, keyType: KeyType.Ed25519 }) + namespaceIdentifier = legacyIndyDidFromPublicKeyBase58(verificationKey.publicKeyBase58) + did = `did:indy:${submitterNamespace}:${namespaceIdentifier}` + } + + const pool = indySdkPoolService.getPoolForNamespace(submitterNamespace) + await this.registerPublicDid( + agentContext, + pool, + submitterNamespaceIdentifier, + submitterSigningKey, + namespaceIdentifier, + verificationKey, + alias, + role + ) + + // Create did document + const didDocumentBuilder = indyDidDocumentFromDid(did, verificationKey.publicKeyBase58) + + // Add services if endpoints object was passed. + if (endpoints) { + const keyAgreementId = `${did}#key-agreement-1` + + await this.setEndpointsForDid(agentContext, pool, namespaceIdentifier, verificationKey, endpoints) + + didDocumentBuilder + .addContext('https://w3id.org/security/suites/x25519-2019/v1') + .addVerificationMethod({ + controller: did, + id: keyAgreementId, + publicKeyBase58: createKeyAgreementKey(verificationKey.publicKeyBase58), + type: 'X25519KeyAgreementKey2019', + }) + .addKeyAgreement(keyAgreementId) + + // Process endpoint attrib following the same rules as for did:sov + addServicesFromEndpointsAttrib(didDocumentBuilder, did, endpoints, keyAgreementId) + } + + // Build did document. + const didDocument = didDocumentBuilder.build() + + // Save the did so we know we created it and can issue with it + const didRecord = new DidRecord({ + did, + role: DidDocumentRole.Created, + tags: { + recipientKeyFingerprints: didDocument.recipientKeys.map((key: Key) => key.fingerprint), + }, + }) + await didRepository.save(agentContext, didRecord) + + return { + didDocumentMetadata: {}, + didRegistrationMetadata: {}, + didState: { + state: 'finished', + did, + didDocument, + secret: { + // FIXME: the uni-registrar creates the seed in the registrar method + // if it doesn't exist so the seed can always be returned. Currently + // we can only return it if the seed was passed in by the user. Once + // we have a secure method for generating seeds we should use the same + // approach + privateKey: options.secret?.privateKey, + }, + }, + } + } catch (error) { + return { + didDocumentMetadata: {}, + didRegistrationMetadata: {}, + didState: { + state: 'failed', + reason: `unknownError: ${error.message}`, + }, + } + } + } + + public async update(): Promise { + return { + didDocumentMetadata: {}, + didRegistrationMetadata: {}, + didState: { + state: 'failed', + reason: `notImplemented: updating did:indy not implemented yet`, + }, + } + } + + public async deactivate(): Promise { + return { + didDocumentMetadata: {}, + didRegistrationMetadata: {}, + didState: { + state: 'failed', + reason: `notImplemented: deactivating did:indy not implemented yet`, + }, + } + } + + private async registerPublicDid( + agentContext: AgentContext, + pool: IndySdkPool, + unqualifiedSubmitterDid: string, + submitterSigningKey: Key, + unqualifiedDid: string, + signingKey: Key, + alias: string, + role?: NymRole + ) { + const indySdk = agentContext.dependencyManager.resolve(IndySdkSymbol) + const indySdkPoolService = agentContext.dependencyManager.resolve(IndySdkPoolService) + + try { + agentContext.config.logger.debug(`Register public did '${unqualifiedDid}' on ledger '${pool.didIndyNamespace}'`) + + const request = await indySdk.buildNymRequest( + unqualifiedSubmitterDid, + unqualifiedDid, + signingKey.publicKeyBase58, + alias, + role || null + ) + + const response = await indySdkPoolService.submitWriteRequest(agentContext, pool, request, submitterSigningKey) + + agentContext.config.logger.debug( + `Registered public did '${unqualifiedDid}' on ledger '${pool.didIndyNamespace}'`, + { + response, + } + ) + } catch (error) { + agentContext.config.logger.error( + `Error registering public did '${unqualifiedDid}' on ledger '${pool.didIndyNamespace}'`, + { + error, + unqualifiedSubmitterDid, + unqualifiedDid, + verkey: signingKey.publicKeyBase58, + alias, + role, + pool: pool.didIndyNamespace, + } + ) + + throw error + } + } + + private async setEndpointsForDid( + agentContext: AgentContext, + pool: IndySdkPool, + unqualifiedDid: string, + signingKey: Key, + endpoints: IndyEndpointAttrib + ): Promise { + const indySdk = agentContext.dependencyManager.resolve(IndySdkSymbol) + const indySdkPoolService = agentContext.dependencyManager.resolve(IndySdkPoolService) + + try { + agentContext.config.logger.debug( + `Set endpoints for did '${unqualifiedDid}' on ledger '${pool.didIndyNamespace}'`, + endpoints + ) + + const request = await indySdk.buildAttribRequest( + unqualifiedDid, + unqualifiedDid, + null, + { endpoint: endpoints }, + null + ) + + const response = await indySdkPoolService.submitWriteRequest(agentContext, pool, request, signingKey) + agentContext.config.logger.debug( + `Successfully set endpoints for did '${unqualifiedDid}' on ledger '${pool.didIndyNamespace}'`, + { + response, + endpoints, + } + ) + } catch (error) { + agentContext.config.logger.error( + `Error setting endpoints for did '${unqualifiedDid}' on ledger '${pool.didIndyNamespace}'`, + { + error, + unqualifiedDid, + endpoints, + } + ) + + throw isIndyError(error) ? new IndySdkError(error) : error + } + } +} + +interface IndySdkIndyDidCreateOptionsBase extends DidCreateOptions { + // The indy sdk can only publish a very limited did document (what is mostly known as a legacy did:sov did) and thus we require everything + // needed to construct the did document to be passed through the options object. + didDocument?: never + options: { + alias: string + role?: NymRole + verkey?: string + endpoints?: IndyEndpointAttrib + submitterDid: string + } + secret?: { + privateKey?: Buffer + } +} + +interface IndySdkIndyDidCreateOptionsWithDid extends IndySdkIndyDidCreateOptionsBase { + method?: never + did: string +} + +interface IndySdkIndyDidCreateOptionsWithoutDid extends IndySdkIndyDidCreateOptionsBase { + method: 'indy' + did?: never +} + +export type IndySdkIndyDidCreateOptions = IndySdkIndyDidCreateOptionsWithDid | IndySdkIndyDidCreateOptionsWithoutDid diff --git a/packages/indy-sdk/src/dids/IndySdkIndyDidResolver.ts b/packages/indy-sdk/src/dids/IndySdkIndyDidResolver.ts new file mode 100644 index 0000000000..1c486eb3aa --- /dev/null +++ b/packages/indy-sdk/src/dids/IndySdkIndyDidResolver.ts @@ -0,0 +1,124 @@ +import type { IndyEndpointAttrib } from './didSovUtil' +import type { IndySdkPool } from '../ledger' +import type { IndySdk } from '../types' +import type { DidResolutionResult, DidResolver, AgentContext } from '@aries-framework/core' + +import { parseIndyDid } from '@aries-framework/anoncreds' + +import { isIndyError, IndySdkError } from '../error' +import { IndySdkPoolService } from '../ledger/IndySdkPoolService' +import { IndySdkSymbol } from '../types' +import { getFullVerkey } from '../utils/did' + +import { createKeyAgreementKey, indyDidDocumentFromDid } from './didIndyUtil' +import { addServicesFromEndpointsAttrib } from './didSovUtil' + +export class IndySdkIndyDidResolver implements DidResolver { + public readonly supportedMethods = ['indy'] + + public async resolve(agentContext: AgentContext, did: string): Promise { + const didDocumentMetadata = {} + + try { + const { namespaceIdentifier, namespace } = parseIndyDid(did) + + const poolService = agentContext.dependencyManager.resolve(IndySdkPoolService) + const pool = poolService.getPoolForNamespace(namespace) + + const nym = await this.getPublicDid(agentContext, pool, namespaceIdentifier) + const endpoints = await this.getEndpointsForDid(agentContext, pool, namespaceIdentifier) + + // For modern did:indy DIDs, we assume that GET_NYM is always a full verkey in base58. + // For backwards compatibility, we accept a shortened verkey and convert it using previous convention + const verkey = getFullVerkey(did, nym.verkey) + const builder = indyDidDocumentFromDid(did, verkey) + + // NOTE: we don't support the `diddocContent` field in the GET_NYM response using the indy-sdk. So if the did would have the `diddocContent` field + // we will ignore it without knowing if it is present. We may be able to extract the diddocContent from the GET_NYM response in the future, but need + // some dids registered with diddocContent to test with. + if (endpoints) { + const keyAgreementId = `${did}#key-agreement-1` + + builder + .addContext('https://w3id.org/security/suites/x25519-2019/v1') + .addVerificationMethod({ + controller: did, + id: keyAgreementId, + publicKeyBase58: createKeyAgreementKey(verkey), + type: 'X25519KeyAgreementKey2019', + }) + .addKeyAgreement(keyAgreementId) + addServicesFromEndpointsAttrib(builder, did, endpoints, keyAgreementId) + } + + return { + didDocument: builder.build(), + didDocumentMetadata, + didResolutionMetadata: { contentType: 'application/did+ld+json' }, + } + } catch (error) { + return { + didDocument: null, + didDocumentMetadata, + didResolutionMetadata: { + error: 'notFound', + message: `resolver_error: Unable to resolve did '${did}': ${error}`, + }, + } + } + } + + private async getPublicDid(agentContext: AgentContext, pool: IndySdkPool, unqualifiedDid: string) { + const indySdkPoolService = agentContext.dependencyManager.resolve(IndySdkPoolService) + const indySdk = agentContext.dependencyManager.resolve(IndySdkSymbol) + + const request = await indySdk.buildGetNymRequest(null, unqualifiedDid) + const response = await indySdkPoolService.submitReadRequest(pool, request) + + return await indySdk.parseGetNymResponse(response) + } + + private async getEndpointsForDid(agentContext: AgentContext, pool: IndySdkPool, unqualifiedDid: string) { + const indySdk = agentContext.dependencyManager.resolve(IndySdkSymbol) + const indySdkPoolService = agentContext.dependencyManager.resolve(IndySdkPoolService) + + try { + agentContext.config.logger.debug( + `Get endpoints for did '${unqualifiedDid}' from ledger '${pool.didIndyNamespace}'` + ) + + const request = await indySdk.buildGetAttribRequest(null, unqualifiedDid, 'endpoint', null, null) + + agentContext.config.logger.debug( + `Submitting get endpoint ATTRIB request for did '${unqualifiedDid}' to ledger '${pool.didIndyNamespace}'` + ) + const response = await indySdkPoolService.submitReadRequest(pool, request) + + if (!response.result.data) { + return null + } + + const endpoints = JSON.parse(response.result.data as string)?.endpoint as IndyEndpointAttrib + agentContext.config.logger.debug( + `Got endpoints '${JSON.stringify(endpoints)}' for did '${unqualifiedDid}' from ledger '${ + pool.didIndyNamespace + }'`, + { + response, + endpoints, + } + ) + + return endpoints + } catch (error) { + agentContext.config.logger.error( + `Error retrieving endpoints for did '${unqualifiedDid}' from ledger '${pool.didIndyNamespace}'`, + { + error, + } + ) + + throw isIndyError(error) ? new IndySdkError(error) : error + } + } +} diff --git a/packages/indy-sdk/src/dids/IndySdkSovDidResolver.ts b/packages/indy-sdk/src/dids/IndySdkSovDidResolver.ts new file mode 100644 index 0000000000..ff6afc9571 --- /dev/null +++ b/packages/indy-sdk/src/dids/IndySdkSovDidResolver.ts @@ -0,0 +1,99 @@ +import type { IndyEndpointAttrib } from './didSovUtil' +import type { IndySdkPool } from '../ledger' +import type { IndySdk } from '../types' +import type { DidResolutionResult, ParsedDid, DidResolver, AgentContext } from '@aries-framework/core' + +import { isIndyError, IndySdkError } from '../error' +import { IndySdkPoolService } from '../ledger/IndySdkPoolService' +import { IndySdkSymbol } from '../types' + +import { addServicesFromEndpointsAttrib, sovDidDocumentFromDid } from './didSovUtil' + +export class IndySdkSovDidResolver implements DidResolver { + public readonly supportedMethods = ['sov'] + + public async resolve(agentContext: AgentContext, did: string, parsed: ParsedDid): Promise { + const didDocumentMetadata = {} + + try { + const poolService = agentContext.dependencyManager.resolve(IndySdkPoolService) + const { pool, nymResponse } = await poolService.getPoolForDid(agentContext, parsed.id) + const nym = nymResponse ?? (await this.getPublicDid(agentContext, pool, parsed.id)) + const endpoints = await this.getEndpointsForDid(agentContext, pool, parsed.id) + + const keyAgreementId = `${parsed.did}#key-agreement-1` + const builder = sovDidDocumentFromDid(parsed.did, nym.verkey) + + if (endpoints) { + addServicesFromEndpointsAttrib(builder, parsed.did, endpoints, keyAgreementId) + } + + return { + didDocument: builder.build(), + didDocumentMetadata, + didResolutionMetadata: { contentType: 'application/did+ld+json' }, + } + } catch (error) { + return { + didDocument: null, + didDocumentMetadata, + didResolutionMetadata: { + error: 'notFound', + message: `resolver_error: Unable to resolve did '${did}': ${error}`, + }, + } + } + } + + private async getPublicDid(agentContext: AgentContext, pool: IndySdkPool, did: string) { + const indySdkPoolService = agentContext.dependencyManager.resolve(IndySdkPoolService) + const indySdk = agentContext.dependencyManager.resolve(IndySdkSymbol) + + const request = await indySdk.buildGetNymRequest(null, did) + const response = await indySdkPoolService.submitReadRequest(pool, request) + + return await indySdk.parseGetNymResponse(response) + } + + private async getEndpointsForDid(agentContext: AgentContext, pool: IndySdkPool, unqualifiedDid: string) { + const indySdk = agentContext.dependencyManager.resolve(IndySdkSymbol) + const indySdkPoolService = agentContext.dependencyManager.resolve(IndySdkPoolService) + + try { + agentContext.config.logger.debug( + `Get endpoints for did '${unqualifiedDid}' from ledger '${pool.didIndyNamespace}'` + ) + + const request = await indySdk.buildGetAttribRequest(null, unqualifiedDid, 'endpoint', null, null) + + agentContext.config.logger.debug( + `Submitting get endpoint ATTRIB request for did '${unqualifiedDid}' to ledger '${pool.didIndyNamespace}'` + ) + const response = await indySdkPoolService.submitReadRequest(pool, request) + + if (!response.result.data) return null + + const endpoints = JSON.parse(response.result.data as string)?.endpoint as IndyEndpointAttrib + agentContext.config.logger.debug( + `Got endpoints '${JSON.stringify(endpoints)}' for did '${unqualifiedDid}' from ledger '${ + pool.didIndyNamespace + }'`, + { + response, + endpoints, + } + ) + + return endpoints ?? null + } catch (error) { + agentContext.config.logger.error( + `Error retrieving endpoints for did '${unqualifiedDid}' from ledger '${pool.didIndyNamespace}'`, + { + error, + } + ) + + throw isIndyError(error) ? new IndySdkError(error) : error + } + } +} diff --git a/packages/indy-sdk/src/dids/__tests__/IndySdkIndyDidRegistrar.test.ts b/packages/indy-sdk/src/dids/__tests__/IndySdkIndyDidRegistrar.test.ts new file mode 100644 index 0000000000..3bd27a3455 --- /dev/null +++ b/packages/indy-sdk/src/dids/__tests__/IndySdkIndyDidRegistrar.test.ts @@ -0,0 +1,501 @@ +/* eslint-disable @typescript-eslint/ban-ts-comment */ +import type { IndySdkPool } from '../../ledger/IndySdkPool' +import type { DidRecord, RecordSavedEvent } from '@aries-framework/core' + +import { + KeyProviderRegistry, + DidsApi, + DidDocument, + VerificationMethod, + KeyType, + Key, + TypedArrayEncoder, + DidRepository, + JsonTransformer, + DidDocumentRole, + EventEmitter, + RepositoryEventTypes, +} from '@aries-framework/core' +import { Subject } from 'rxjs' + +import { InMemoryStorageService } from '../../../../../tests/InMemoryStorageService' +import { mockFunction, getAgentConfig, getAgentContext, agentDependencies, indySdk } from '../../../../core/tests' +import { IndySdkPoolService } from '../../ledger/IndySdkPoolService' +import { IndySdkWallet } from '../../wallet' +import { IndySdkIndyDidRegistrar } from '../IndySdkIndyDidRegistrar' + +jest.mock('../../ledger/IndySdkPoolService') +const IndySdkPoolServiceMock = IndySdkPoolService as jest.Mock +const indySdkPoolServiceMock = new IndySdkPoolServiceMock() + +const pool = { + config: { indyNamespace: 'pool1' }, +} as IndySdkPool +mockFunction(indySdkPoolServiceMock.getPoolForNamespace).mockReturnValue(pool) + +const agentConfig = getAgentConfig('IndySdkIndyDidRegistrar') +const wallet = new IndySdkWallet(indySdk, agentConfig.logger, new KeyProviderRegistry([])) + +jest + .spyOn(wallet, 'createKey') + .mockResolvedValue(Key.fromPublicKeyBase58('E6D1m3eERqCueX4ZgMCY14B4NceAr6XP2HyVqt55gDhu', KeyType.Ed25519)) +const storageService = new InMemoryStorageService() +const eventEmitter = new EventEmitter(agentDependencies, new Subject()) +const didRepository = new DidRepository(storageService, eventEmitter) + +const agentContext = getAgentContext({ + wallet, + registerInstances: [ + [DidRepository, didRepository], + [IndySdkPoolService, indySdkPoolServiceMock], + [ + DidsApi, + { + resolve: jest.fn().mockResolvedValue({ + didDocument: new DidDocument({ + id: 'did:indy:pool1:BzCbsNYhMrjHiqZDTUASHg', + authentication: [ + new VerificationMethod({ + id: 'did:indy:pool1:BzCbsNYhMrjHiqZDTUASHg#verkey', + type: 'Ed25519VerificationKey2018', + controller: 'did:indy:pool1:BzCbsNYhMrjHiqZDTUASHg', + publicKeyBase58: 'E6D1m3eERqCueX4ZgMCY14B4NceAr6XP2HyVqt55gDhu', + }), + ], + }), + }), + }, + ], + ], + agentConfig, +}) + +const indySdkIndyDidRegistrar = new IndySdkIndyDidRegistrar() + +describe('IndySdkIndyDidRegistrar', () => { + afterEach(() => { + jest.clearAllMocks() + }) + + test('returns an error state if both did and privateKey are provided', async () => { + const result = await indySdkIndyDidRegistrar.create(agentContext, { + did: 'did:indy:pool1:did-value', + options: { + submitterDid: 'did:indy:pool1:BzCbsNYhMrjHiqZDTUASHg', + alias: 'Hello', + }, + secret: { + privateKey: TypedArrayEncoder.fromString('key'), + }, + }) + + expect(JsonTransformer.toJSON(result)).toMatchObject({ + didDocumentMetadata: {}, + didRegistrationMetadata: {}, + didState: { + state: 'failed', + reason: `Only one of 'privateKey' or 'did' must be provided`, + }, + }) + }) + + test('returns an error state if the submitter did is not a valid did:indy did', async () => { + const result = await indySdkIndyDidRegistrar.create(agentContext, { + method: 'indy', + options: { + submitterDid: 'BzCbsNYhMrjHiqZDTUASHg', + alias: 'Hello', + }, + }) + + expect(JsonTransformer.toJSON(result)).toMatchObject({ + didDocumentMetadata: {}, + didRegistrationMetadata: {}, + didState: { + state: 'failed', + reason: 'unknownError: BzCbsNYhMrjHiqZDTUASHg is not a valid did:indy did', + }, + }) + }) + + test('returns an error state if did is provided, but it is not a valid did:indy did', async () => { + const result = await indySdkIndyDidRegistrar.create(agentContext, { + did: 'BzCbsNYhMrjHiqZDTUASHg', + options: { + submitterDid: 'did:indy:pool1:BzCbsNYhMrjHiqZDTUASHg', + verkey: 'verkey', + alias: 'Hello', + }, + }) + + expect(JsonTransformer.toJSON(result)).toMatchObject({ + didDocumentMetadata: {}, + didRegistrationMetadata: {}, + didState: { + state: 'failed', + reason: 'unknownError: BzCbsNYhMrjHiqZDTUASHg is not a valid did:indy did', + }, + }) + }) + + test('returns an error state if did is provided, but no verkey', async () => { + const result = await indySdkIndyDidRegistrar.create(agentContext, { + did: 'BzCbsNYhMrjHiqZDTUASHg', + options: { + submitterDid: 'did:indy:pool1:BzCbsNYhMrjHiqZDTUASHg', + alias: 'Hello', + }, + }) + + expect(JsonTransformer.toJSON(result)).toMatchObject({ + didDocumentMetadata: {}, + didRegistrationMetadata: {}, + didState: { + state: 'failed', + reason: 'If a did is defined, a matching verkey must be provided', + }, + }) + }) + + test('returns an error state if did and verkey are provided, but the did is not self certifying', async () => { + const result = await indySdkIndyDidRegistrar.create(agentContext, { + did: 'did:indy:pool1:BzCbsNYhMrjHiqZDTUASHg', + options: { + submitterDid: 'did:indy:pool1:BzCbsNYhMrjHiqZDTUASHg', + verkey: 'verkey', + alias: 'Hello', + }, + }) + + expect(JsonTransformer.toJSON(result)).toMatchObject({ + didDocumentMetadata: {}, + didRegistrationMetadata: {}, + didState: { + state: 'failed', + reason: 'Did must be first 16 bytes of the the verkey base58 encoded.', + }, + }) + }) + + test('returns an error state if did is provided, but does not match with the namespace from the submitterDid', async () => { + const result = await indySdkIndyDidRegistrar.create(agentContext, { + did: 'did:indy:pool2:R1xKJw17sUoXhejEpugMYJ', + options: { + submitterDid: 'did:indy:pool1:BzCbsNYhMrjHiqZDTUASHg', + verkey: 'E6D1m3eERqCueX4ZgMCY14B4NceAr6XP2HyVqt55gDhu', + alias: 'Hello', + }, + }) + + expect(JsonTransformer.toJSON(result)).toMatchObject({ + didDocumentMetadata: {}, + didRegistrationMetadata: {}, + didState: { + state: 'failed', + reason: + 'The submitter did uses namespace pool1 and the did to register uses namespace pool2. Namespaces must match.', + }, + }) + }) + + test('creates a did:indy document without services', async () => { + const privateKey = TypedArrayEncoder.fromString('96213c3d7fc8d4d6754c712fd969598e') + + // @ts-ignore method is private + const registerPublicDidSpy = jest.spyOn(indySdkIndyDidRegistrar, 'registerPublicDid') + // @ts-ignore type check fails because method is private + registerPublicDidSpy.mockImplementationOnce(() => Promise.resolve()) + + const result = await indySdkIndyDidRegistrar.create(agentContext, { + method: 'indy', + options: { + alias: 'Hello', + submitterDid: 'did:indy:pool1:BzCbsNYhMrjHiqZDTUASHg', + role: 'STEWARD', + }, + secret: { + privateKey, + }, + }) + expect(registerPublicDidSpy).toHaveBeenCalledWith( + agentContext, + pool, + // Unqualified submitter did + 'BzCbsNYhMrjHiqZDTUASHg', + // submitter signing key, + expect.any(Key), + // Unqualified created indy did + 'R1xKJw17sUoXhejEpugMYJ', + // Verkey + expect.any(Key), + // Alias + 'Hello', + // Role + 'STEWARD' + ) + expect(JsonTransformer.toJSON(result)).toMatchObject({ + didDocumentMetadata: {}, + didRegistrationMetadata: {}, + didState: { + state: 'finished', + did: 'did:indy:pool1:R1xKJw17sUoXhejEpugMYJ', + didDocument: { + '@context': ['https://w3id.org/did/v1', 'https://w3id.org/security/suites/ed25519-2018/v1'], + id: 'did:indy:pool1:R1xKJw17sUoXhejEpugMYJ', + verificationMethod: [ + { + id: 'did:indy:pool1:R1xKJw17sUoXhejEpugMYJ#verkey', + type: 'Ed25519VerificationKey2018', + controller: 'did:indy:pool1:R1xKJw17sUoXhejEpugMYJ', + publicKeyBase58: 'E6D1m3eERqCueX4ZgMCY14B4NceAr6XP2HyVqt55gDhu', + }, + ], + authentication: ['did:indy:pool1:R1xKJw17sUoXhejEpugMYJ#verkey'], + assertionMethod: undefined, + keyAgreement: undefined, + }, + secret: { + privateKey, + }, + }, + }) + }) + + test('creates a did:indy document by passing did', async () => { + // @ts-ignore method is private + const registerPublicDidSpy = jest.spyOn(indySdkIndyDidRegistrar, 'registerPublicDid') + // @ts-ignore type check fails because method is private + registerPublicDidSpy.mockImplementationOnce(() => Promise.resolve()) + + const result = await indySdkIndyDidRegistrar.create(agentContext, { + did: 'did:indy:pool1:R1xKJw17sUoXhejEpugMYJ', + options: { + verkey: 'E6D1m3eERqCueX4ZgMCY14B4NceAr6XP2HyVqt55gDhu', + alias: 'Hello', + submitterDid: 'did:indy:pool1:BzCbsNYhMrjHiqZDTUASHg', + role: 'STEWARD', + }, + secret: {}, + }) + expect(registerPublicDidSpy).toHaveBeenCalledWith( + agentContext, + pool, + // Unqualified submitter did + 'BzCbsNYhMrjHiqZDTUASHg', + // submitter signing key, + expect.any(Key), + // Unqualified created indy did + 'R1xKJw17sUoXhejEpugMYJ', + // Verkey + expect.any(Key), + // Alias + 'Hello', + // Role + 'STEWARD' + ) + expect(JsonTransformer.toJSON(result)).toMatchObject({ + didDocumentMetadata: {}, + didRegistrationMetadata: {}, + didState: { + state: 'finished', + did: 'did:indy:pool1:R1xKJw17sUoXhejEpugMYJ', + didDocument: { + '@context': ['https://w3id.org/did/v1', 'https://w3id.org/security/suites/ed25519-2018/v1'], + id: 'did:indy:pool1:R1xKJw17sUoXhejEpugMYJ', + verificationMethod: [ + { + id: 'did:indy:pool1:R1xKJw17sUoXhejEpugMYJ#verkey', + type: 'Ed25519VerificationKey2018', + controller: 'did:indy:pool1:R1xKJw17sUoXhejEpugMYJ', + publicKeyBase58: 'E6D1m3eERqCueX4ZgMCY14B4NceAr6XP2HyVqt55gDhu', + }, + ], + authentication: ['did:indy:pool1:R1xKJw17sUoXhejEpugMYJ#verkey'], + assertionMethod: undefined, + keyAgreement: undefined, + }, + secret: {}, + }, + }) + }) + + test('creates a did:indy document with services', async () => { + const privateKey = TypedArrayEncoder.fromString('96213c3d7fc8d4d6754c712fd969598e') + + // @ts-ignore method is private + const registerPublicDidSpy = jest.spyOn(indySdkIndyDidRegistrar, 'registerPublicDid') + // @ts-ignore type check fails because method is private + registerPublicDidSpy.mockImplementationOnce(() => Promise.resolve()) + + // @ts-ignore method is private + const setEndpointsForDidSpy = jest.spyOn(indySdkIndyDidRegistrar, 'setEndpointsForDid') + // @ts-ignore type check fails because method is private + setEndpointsForDidSpy.mockImplementationOnce(() => Promise.resolve(undefined)) + + const result = await indySdkIndyDidRegistrar.create(agentContext, { + method: 'indy', + options: { + alias: 'Hello', + submitterDid: 'did:indy:pool1:BzCbsNYhMrjHiqZDTUASHg', + role: 'STEWARD', + endpoints: { + endpoint: 'https://example.com/endpoint', + routingKeys: ['key-1'], + types: ['DIDCommMessaging', 'did-communication', 'endpoint'], + }, + }, + secret: { + privateKey, + }, + }) + + expect(registerPublicDidSpy).toHaveBeenCalledWith( + agentContext, + pool, + // Unqualified submitter did + 'BzCbsNYhMrjHiqZDTUASHg', + // submitter signing key, + expect.any(Key), + // Unqualified created indy did + 'R1xKJw17sUoXhejEpugMYJ', + // Verkey + expect.any(Key), + // Alias + 'Hello', + // Role + 'STEWARD' + ) + expect(JsonTransformer.toJSON(result)).toMatchObject({ + didDocumentMetadata: {}, + didRegistrationMetadata: {}, + didState: { + state: 'finished', + did: 'did:indy:pool1:R1xKJw17sUoXhejEpugMYJ', + didDocument: { + '@context': [ + 'https://w3id.org/did/v1', + 'https://w3id.org/security/suites/ed25519-2018/v1', + 'https://w3id.org/security/suites/x25519-2019/v1', + 'https://didcomm.org/messaging/contexts/v2', + ], + id: 'did:indy:pool1:R1xKJw17sUoXhejEpugMYJ', + verificationMethod: [ + { + id: 'did:indy:pool1:R1xKJw17sUoXhejEpugMYJ#verkey', + type: 'Ed25519VerificationKey2018', + controller: 'did:indy:pool1:R1xKJw17sUoXhejEpugMYJ', + publicKeyBase58: 'E6D1m3eERqCueX4ZgMCY14B4NceAr6XP2HyVqt55gDhu', + }, + { + id: 'did:indy:pool1:R1xKJw17sUoXhejEpugMYJ#key-agreement-1', + type: 'X25519KeyAgreementKey2019', + controller: 'did:indy:pool1:R1xKJw17sUoXhejEpugMYJ', + publicKeyBase58: 'Fbv17ZbnUSbafsiUBJbdGeC62M8v8GEscVMMcE59mRPt', + }, + ], + service: [ + { + id: 'did:indy:pool1:R1xKJw17sUoXhejEpugMYJ#endpoint', + serviceEndpoint: 'https://example.com/endpoint', + type: 'endpoint', + }, + { + id: 'did:indy:pool1:R1xKJw17sUoXhejEpugMYJ#did-communication', + serviceEndpoint: 'https://example.com/endpoint', + type: 'did-communication', + priority: 0, + recipientKeys: ['did:indy:pool1:R1xKJw17sUoXhejEpugMYJ#key-agreement-1'], + routingKeys: ['key-1'], + accept: ['didcomm/aip2;env=rfc19'], + }, + { + id: 'did:indy:pool1:R1xKJw17sUoXhejEpugMYJ#didcomm-1', + serviceEndpoint: 'https://example.com/endpoint', + type: 'DIDCommMessaging', + routingKeys: ['key-1'], + accept: ['didcomm/v2'], + }, + ], + authentication: ['did:indy:pool1:R1xKJw17sUoXhejEpugMYJ#verkey'], + assertionMethod: undefined, + keyAgreement: ['did:indy:pool1:R1xKJw17sUoXhejEpugMYJ#key-agreement-1'], + }, + secret: { + privateKey, + }, + }, + }) + }) + + test('stores the did document', async () => { + const privateKey = TypedArrayEncoder.fromString('96213c3d7fc8d4d6754c712fd969598e') + + // @ts-ignore method is private + const registerPublicDidSpy = jest.spyOn(indySdkIndyDidRegistrar, 'registerPublicDid') + // @ts-ignore type check fails because method is private + registerPublicDidSpy.mockImplementationOnce(() => Promise.resolve()) + + // @ts-ignore method is private + const setEndpointsForDidSpy = jest.spyOn(indySdkIndyDidRegistrar, 'setEndpointsForDid') + // @ts-ignore type check fails because method is private + setEndpointsForDidSpy.mockImplementationOnce(() => Promise.resolve(undefined)) + + const saveCalled = jest.fn() + eventEmitter.on>(RepositoryEventTypes.RecordSaved, saveCalled) + + await indySdkIndyDidRegistrar.create(agentContext, { + method: 'indy', + options: { + alias: 'Hello', + submitterDid: 'did:indy:pool1:BzCbsNYhMrjHiqZDTUASHg', + role: 'STEWARD', + endpoints: { + endpoint: 'https://example.com/endpoint', + routingKeys: ['key-1'], + types: ['DIDCommMessaging', 'did-communication', 'endpoint'], + }, + }, + secret: { + privateKey, + }, + }) + + expect(saveCalled).toHaveBeenCalledTimes(1) + const [saveEvent] = saveCalled.mock.calls[0] + + expect(saveEvent.payload.record).toMatchObject({ + did: 'did:indy:pool1:R1xKJw17sUoXhejEpugMYJ', + role: DidDocumentRole.Created, + _tags: { + recipientKeyFingerprints: ['z6LSrH6AdsQeZuKKmG6Ehx7abEQZsVg2psR2VU536gigUoAe'], + }, + didDocument: undefined, + }) + }) + + test('returns an error state when calling update', async () => { + const result = await indySdkIndyDidRegistrar.update() + + expect(result).toEqual({ + didDocumentMetadata: {}, + didRegistrationMetadata: {}, + didState: { + state: 'failed', + reason: `notImplemented: updating did:indy not implemented yet`, + }, + }) + }) + + test('returns an error state when calling deactivate', async () => { + const result = await indySdkIndyDidRegistrar.deactivate() + + expect(result).toEqual({ + didDocumentMetadata: {}, + didRegistrationMetadata: {}, + didState: { + state: 'failed', + reason: `notImplemented: deactivating did:indy not implemented yet`, + }, + }) + }) +}) diff --git a/packages/indy-sdk/src/dids/__tests__/IndySdkIndyDidResolver.test.ts b/packages/indy-sdk/src/dids/__tests__/IndySdkIndyDidResolver.test.ts new file mode 100644 index 0000000000..765717013b --- /dev/null +++ b/packages/indy-sdk/src/dids/__tests__/IndySdkIndyDidResolver.test.ts @@ -0,0 +1,127 @@ +import type { IndySdkPool } from '../../ledger' +import type { IndyEndpointAttrib } from '../didSovUtil' +import type { GetNymResponse } from 'indy-sdk' + +import { KeyProviderRegistry, JsonTransformer } from '@aries-framework/core' +import indySdk from 'indy-sdk' + +import { mockFunction, getAgentConfig, getAgentContext } from '../../../../core/tests/helpers' +import { IndySdkPoolService } from '../../ledger/IndySdkPoolService' +import { IndySdkSymbol } from '../../types' +import { IndySdkWallet } from '../../wallet' +import { IndySdkIndyDidResolver } from '../IndySdkIndyDidResolver' + +import didIndyPool1R1xKJw17sUoXhejEpugMYJFixture from './__fixtures__/didIndyPool1R1xKJw17sUoXhejEpugMYJ.json' +import didIndyPool1WJz9mHyW9BZksioQnRsrAoFixture from './__fixtures__/didIndyPool1WJz9mHyW9BZksioQnRsrAo.json' + +jest.mock('../../ledger/IndySdkPoolService') +const IndySdkPoolServiceMock = IndySdkPoolService as jest.Mock +const indySdkPoolServiceMock = new IndySdkPoolServiceMock() + +mockFunction(indySdkPoolServiceMock.getPoolForNamespace).mockReturnValue({ + config: { indyNamespace: 'pool1' }, +} as IndySdkPool) + +const agentConfig = getAgentConfig('IndySdkIndyDidResolver') + +const wallet = new IndySdkWallet(indySdk, agentConfig.logger, new KeyProviderRegistry([])) + +const agentContext = getAgentContext({ + wallet, + agentConfig, + registerInstances: [ + [IndySdkPoolService, indySdkPoolServiceMock], + [IndySdkSymbol, indySdk], + ], +}) + +const indySdkSovDidResolver = new IndySdkIndyDidResolver() + +describe('IndySdkIndyDidResolver', () => { + it('should correctly resolve a did:indy document', async () => { + const did = 'did:indy:pool1:R1xKJw17sUoXhejEpugMYJ' + + const nymResponse: GetNymResponse = { + did: 'R1xKJw17sUoXhejEpugMYJ', + verkey: 'E6D1m3eERqCueX4ZgMCY14B4NceAr6XP2HyVqt55gDhu', + role: 'ENDORSER', + } + + const endpoints: IndyEndpointAttrib = { + endpoint: 'https://ssi.com', + profile: 'https://profile.com', + hub: 'https://hub.com', + } + + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + jest.spyOn(indySdkSovDidResolver, 'getPublicDid').mockResolvedValue(nymResponse) + + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + jest.spyOn(indySdkSovDidResolver, 'getEndpointsForDid').mockResolvedValue(endpoints) + + const result = await indySdkSovDidResolver.resolve(agentContext, did) + + expect(JsonTransformer.toJSON(result)).toMatchObject({ + didDocument: didIndyPool1R1xKJw17sUoXhejEpugMYJFixture, + didDocumentMetadata: {}, + didResolutionMetadata: { + contentType: 'application/did+ld+json', + }, + }) + }) + + it('should resolve a did:indy document with routingKeys and types entries in the attrib', async () => { + const did = 'did:indy:pool1:WJz9mHyW9BZksioQnRsrAo' + + const nymResponse: GetNymResponse = { + did: 'WJz9mHyW9BZksioQnRsrAo', + verkey: 'GyYtYWU1vjwd5PFJM4VSX5aUiSV3TyZMuLBJBTQvfdF8', + role: 'ENDORSER', + } + + const endpoints: IndyEndpointAttrib = { + endpoint: 'https://agent.com', + types: ['endpoint', 'did-communication', 'DIDCommMessaging'], + routingKeys: ['routingKey1', 'routingKey2'], + } + + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + jest.spyOn(indySdkSovDidResolver, 'getPublicDid').mockResolvedValue(nymResponse) + + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + jest.spyOn(indySdkSovDidResolver, 'getEndpointsForDid').mockResolvedValue(endpoints) + + const result = await indySdkSovDidResolver.resolve(agentContext, did) + + expect(JsonTransformer.toJSON(result)).toMatchObject({ + didDocument: didIndyPool1WJz9mHyW9BZksioQnRsrAoFixture, + didDocumentMetadata: {}, + didResolutionMetadata: { + contentType: 'application/did+ld+json', + }, + }) + }) + + it('should return did resolution metadata with error if the indy ledger service throws an error', async () => { + const did = 'did:indy:pool1:R1xKJw17sUoXhejEpugMYJ' + + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + jest.spyOn(indySdkSovDidResolver, 'getPublicDid').mockRejectedValue(new Error('Error retrieving did')) + + const result = await indySdkSovDidResolver.resolve(agentContext, did) + + expect(result).toMatchObject({ + didDocument: null, + didDocumentMetadata: {}, + didResolutionMetadata: { + error: 'notFound', + message: `resolver_error: Unable to resolve did 'did:indy:pool1:R1xKJw17sUoXhejEpugMYJ': Error: Error retrieving did`, + }, + }) + }) +}) diff --git a/packages/indy-sdk/src/dids/__tests__/IndySdkSovDidResolver.test.ts b/packages/indy-sdk/src/dids/__tests__/IndySdkSovDidResolver.test.ts new file mode 100644 index 0000000000..5b1d2f1146 --- /dev/null +++ b/packages/indy-sdk/src/dids/__tests__/IndySdkSovDidResolver.test.ts @@ -0,0 +1,132 @@ +import type { IndySdkPool } from '../../ledger' +import type { IndyEndpointAttrib } from '../didSovUtil' +import type { GetNymResponse } from 'indy-sdk' + +import { KeyProviderRegistry, JsonTransformer } from '@aries-framework/core' +import indySdk from 'indy-sdk' + +import { parseDid } from '../../../../core/src/modules/dids/domain/parse' +import { mockFunction, getAgentConfig, getAgentContext } from '../../../../core/tests/helpers' +import { IndySdkPoolService } from '../../ledger/IndySdkPoolService' +import { IndySdkSymbol } from '../../types' +import { IndySdkWallet } from '../../wallet' +import { IndySdkSovDidResolver } from '../IndySdkSovDidResolver' + +import didSovR1xKJw17sUoXhejEpugMYJFixture from './__fixtures__/didSovR1xKJw17sUoXhejEpugMYJ.json' +import didSovWJz9mHyW9BZksioQnRsrAoFixture from './__fixtures__/didSovWJz9mHyW9BZksioQnRsrAo.json' + +jest.mock('../../ledger/IndySdkPoolService') +const IndySdkPoolServiceMock = IndySdkPoolService as jest.Mock +const indySdkPoolServiceMock = new IndySdkPoolServiceMock() + +mockFunction(indySdkPoolServiceMock.getPoolForNamespace).mockReturnValue({ + config: { indyNamespace: 'pool1' }, +} as IndySdkPool) + +mockFunction(indySdkPoolServiceMock.getPoolForDid).mockResolvedValue({ + pool: { config: { indyNamespace: 'pool1' } } as IndySdkPool, +}) + +const agentConfig = getAgentConfig('IndySdkSovDidResolver') + +const wallet = new IndySdkWallet(indySdk, agentConfig.logger, new KeyProviderRegistry([])) + +const agentContext = getAgentContext({ + wallet, + agentConfig, + registerInstances: [ + [IndySdkPoolService, indySdkPoolServiceMock], + [IndySdkSymbol, indySdk], + ], +}) + +const indySdkSovDidResolver = new IndySdkSovDidResolver() + +describe('IndySdkSovDidResolver', () => { + it('should correctly resolve a did:sov document', async () => { + const did = 'did:sov:R1xKJw17sUoXhejEpugMYJ' + + const nymResponse: GetNymResponse = { + did: 'R1xKJw17sUoXhejEpugMYJ', + verkey: 'E6D1m3eERqCueX4ZgMCY14B4NceAr6XP2HyVqt55gDhu', + role: 'ENDORSER', + } + + const endpoints: IndyEndpointAttrib = { + endpoint: 'https://ssi.com', + profile: 'https://profile.com', + hub: 'https://hub.com', + } + + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + jest.spyOn(indySdkSovDidResolver, 'getPublicDid').mockResolvedValue(nymResponse) + + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + jest.spyOn(indySdkSovDidResolver, 'getEndpointsForDid').mockResolvedValue(endpoints) + + const result = await indySdkSovDidResolver.resolve(agentContext, did, parseDid(did)) + + expect(JsonTransformer.toJSON(result)).toMatchObject({ + didDocument: didSovR1xKJw17sUoXhejEpugMYJFixture, + didDocumentMetadata: {}, + didResolutionMetadata: { + contentType: 'application/did+ld+json', + }, + }) + }) + + it('should resolve a did:sov document with routingKeys and types entries in the attrib', async () => { + const did = 'did:sov:WJz9mHyW9BZksioQnRsrAo' + + const nymResponse: GetNymResponse = { + did: 'WJz9mHyW9BZksioQnRsrAo', + verkey: 'GyYtYWU1vjwd5PFJM4VSX5aUiSV3TyZMuLBJBTQvfdF8', + role: 'ENDORSER', + } + + const endpoints: IndyEndpointAttrib = { + endpoint: 'https://agent.com', + types: ['endpoint', 'did-communication', 'DIDCommMessaging'], + routingKeys: ['routingKey1', 'routingKey2'], + } + + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + jest.spyOn(indySdkSovDidResolver, 'getPublicDid').mockResolvedValue(nymResponse) + + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + jest.spyOn(indySdkSovDidResolver, 'getEndpointsForDid').mockResolvedValue(endpoints) + + const result = await indySdkSovDidResolver.resolve(agentContext, did, parseDid(did)) + + expect(JsonTransformer.toJSON(result)).toMatchObject({ + didDocument: didSovWJz9mHyW9BZksioQnRsrAoFixture, + didDocumentMetadata: {}, + didResolutionMetadata: { + contentType: 'application/did+ld+json', + }, + }) + }) + + it('should return did resolution metadata with error if the indy ledger service throws an error', async () => { + const did = 'did:sov:R1xKJw17sUoXhejEpugMYJ' + + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + jest.spyOn(indySdkSovDidResolver, 'getPublicDid').mockRejectedValue(new Error('Error retrieving did')) + + const result = await indySdkSovDidResolver.resolve(agentContext, did, parseDid(did)) + + expect(result).toMatchObject({ + didDocument: null, + didDocumentMetadata: {}, + didResolutionMetadata: { + error: 'notFound', + message: `resolver_error: Unable to resolve did 'did:sov:R1xKJw17sUoXhejEpugMYJ': Error: Error retrieving did`, + }, + }) + }) +}) diff --git a/packages/indy-sdk/src/dids/__tests__/__fixtures__/didIndyPool1R1xKJw17sUoXhejEpugMYJ.json b/packages/indy-sdk/src/dids/__tests__/__fixtures__/didIndyPool1R1xKJw17sUoXhejEpugMYJ.json new file mode 100644 index 0000000000..c0bd51aa30 --- /dev/null +++ b/packages/indy-sdk/src/dids/__tests__/__fixtures__/didIndyPool1R1xKJw17sUoXhejEpugMYJ.json @@ -0,0 +1,50 @@ +{ + "@context": [ + "https://w3id.org/did/v1", + "https://w3id.org/security/suites/ed25519-2018/v1", + "https://w3id.org/security/suites/x25519-2019/v1" + ], + "id": "did:indy:pool1:R1xKJw17sUoXhejEpugMYJ", + "verificationMethod": [ + { + "type": "Ed25519VerificationKey2018", + "controller": "did:indy:pool1:R1xKJw17sUoXhejEpugMYJ", + "id": "did:indy:pool1:R1xKJw17sUoXhejEpugMYJ#verkey", + "publicKeyBase58": "E6D1m3eERqCueX4ZgMCY14B4NceAr6XP2HyVqt55gDhu" + }, + { + "type": "X25519KeyAgreementKey2019", + "controller": "did:indy:pool1:R1xKJw17sUoXhejEpugMYJ", + "id": "did:indy:pool1:R1xKJw17sUoXhejEpugMYJ#key-agreement-1", + "publicKeyBase58": "Fbv17ZbnUSbafsiUBJbdGeC62M8v8GEscVMMcE59mRPt" + } + ], + "authentication": ["did:indy:pool1:R1xKJw17sUoXhejEpugMYJ#verkey"], + "keyAgreement": ["did:indy:pool1:R1xKJw17sUoXhejEpugMYJ#key-agreement-1"], + "service": [ + { + "id": "did:indy:pool1:R1xKJw17sUoXhejEpugMYJ#endpoint", + "type": "endpoint", + "serviceEndpoint": "https://ssi.com" + }, + { + "accept": ["didcomm/aip2;env=rfc19"], + "id": "did:indy:pool1:R1xKJw17sUoXhejEpugMYJ#did-communication", + "priority": 0, + "recipientKeys": ["did:indy:pool1:R1xKJw17sUoXhejEpugMYJ#key-agreement-1"], + "routingKeys": [], + "serviceEndpoint": "https://ssi.com", + "type": "did-communication" + }, + { + "id": "did:indy:pool1:R1xKJw17sUoXhejEpugMYJ#profile", + "serviceEndpoint": "https://profile.com", + "type": "profile" + }, + { + "id": "did:indy:pool1:R1xKJw17sUoXhejEpugMYJ#hub", + "serviceEndpoint": "https://hub.com", + "type": "hub" + } + ] +} diff --git a/packages/indy-sdk/src/dids/__tests__/__fixtures__/didIndyPool1WJz9mHyW9BZksioQnRsrAo.json b/packages/indy-sdk/src/dids/__tests__/__fixtures__/didIndyPool1WJz9mHyW9BZksioQnRsrAo.json new file mode 100644 index 0000000000..0216cdc6a0 --- /dev/null +++ b/packages/indy-sdk/src/dids/__tests__/__fixtures__/didIndyPool1WJz9mHyW9BZksioQnRsrAo.json @@ -0,0 +1,48 @@ +{ + "@context": [ + "https://w3id.org/did/v1", + "https://w3id.org/security/suites/ed25519-2018/v1", + "https://w3id.org/security/suites/x25519-2019/v1", + "https://didcomm.org/messaging/contexts/v2" + ], + "id": "did:indy:pool1:WJz9mHyW9BZksioQnRsrAo", + "verificationMethod": [ + { + "type": "Ed25519VerificationKey2018", + "controller": "did:indy:pool1:WJz9mHyW9BZksioQnRsrAo", + "id": "did:indy:pool1:WJz9mHyW9BZksioQnRsrAo#verkey", + "publicKeyBase58": "GyYtYWU1vjwd5PFJM4VSX5aUiSV3TyZMuLBJBTQvfdF8" + }, + { + "type": "X25519KeyAgreementKey2019", + "controller": "did:indy:pool1:WJz9mHyW9BZksioQnRsrAo", + "id": "did:indy:pool1:WJz9mHyW9BZksioQnRsrAo#key-agreement-1", + "publicKeyBase58": "S3AQEEKkGYrrszT9D55ozVVX2XixYp8uynqVm4okbud" + } + ], + "authentication": ["did:indy:pool1:WJz9mHyW9BZksioQnRsrAo#verkey"], + "keyAgreement": ["did:indy:pool1:WJz9mHyW9BZksioQnRsrAo#key-agreement-1"], + "service": [ + { + "id": "did:indy:pool1:WJz9mHyW9BZksioQnRsrAo#endpoint", + "type": "endpoint", + "serviceEndpoint": "https://agent.com" + }, + { + "id": "did:indy:pool1:WJz9mHyW9BZksioQnRsrAo#did-communication", + "type": "did-communication", + "priority": 0, + "recipientKeys": ["did:indy:pool1:WJz9mHyW9BZksioQnRsrAo#key-agreement-1"], + "routingKeys": ["routingKey1", "routingKey2"], + "accept": ["didcomm/aip2;env=rfc19"], + "serviceEndpoint": "https://agent.com" + }, + { + "id": "did:indy:pool1:WJz9mHyW9BZksioQnRsrAo#didcomm-1", + "type": "DIDCommMessaging", + "serviceEndpoint": "https://agent.com", + "accept": ["didcomm/v2"], + "routingKeys": ["routingKey1", "routingKey2"] + } + ] +} diff --git a/packages/core/src/modules/dids/__tests__/__fixtures__/didSovR1xKJw17sUoXhejEpugMYJ.json b/packages/indy-sdk/src/dids/__tests__/__fixtures__/didSovR1xKJw17sUoXhejEpugMYJ.json similarity index 100% rename from packages/core/src/modules/dids/__tests__/__fixtures__/didSovR1xKJw17sUoXhejEpugMYJ.json rename to packages/indy-sdk/src/dids/__tests__/__fixtures__/didSovR1xKJw17sUoXhejEpugMYJ.json diff --git a/packages/core/src/modules/dids/__tests__/__fixtures__/didSovWJz9mHyW9BZksioQnRsrAo.json b/packages/indy-sdk/src/dids/__tests__/__fixtures__/didSovWJz9mHyW9BZksioQnRsrAo.json similarity index 100% rename from packages/core/src/modules/dids/__tests__/__fixtures__/didSovWJz9mHyW9BZksioQnRsrAo.json rename to packages/indy-sdk/src/dids/__tests__/__fixtures__/didSovWJz9mHyW9BZksioQnRsrAo.json diff --git a/packages/indy-sdk/src/dids/didIndyUtil.ts b/packages/indy-sdk/src/dids/didIndyUtil.ts new file mode 100644 index 0000000000..7da10664b2 --- /dev/null +++ b/packages/indy-sdk/src/dids/didIndyUtil.ts @@ -0,0 +1,55 @@ +import type { AgentContext } from '@aries-framework/core' + +import { + getKeyFromVerificationMethod, + AriesFrameworkError, + convertPublicKeyToX25519, + DidDocumentBuilder, + DidsApi, + TypedArrayEncoder, +} from '@aries-framework/core' + +// Create a base DIDDoc template according to https://hyperledger.github.io/indy-did-method/#base-diddoc-template +export function indyDidDocumentFromDid(did: string, publicKeyBase58: string) { + const verificationMethodId = `${did}#verkey` + + const builder = new DidDocumentBuilder(did) + .addContext('https://w3id.org/security/suites/ed25519-2018/v1') + .addVerificationMethod({ + controller: did, + id: verificationMethodId, + publicKeyBase58, + type: 'Ed25519VerificationKey2018', + }) + .addAuthentication(verificationMethodId) + + return builder +} + +export function createKeyAgreementKey(verkey: string) { + return TypedArrayEncoder.toBase58(convertPublicKeyToX25519(TypedArrayEncoder.fromBase58(verkey))) +} + +/** + * Fetches the verification key for a given did:indy did and returns the key as a {@link Key} object. + * + * @throws {@link AriesFrameworkError} if the did could not be resolved or the key could not be extracted + */ +export async function verificationKeyForIndyDid(agentContext: AgentContext, did: string) { + // FIXME: we should store the didDocument in the DidRecord so we don't have to fetch our own did + // from the ledger to know which key is associated with the did + const didsApi = agentContext.dependencyManager.resolve(DidsApi) + const didResult = await didsApi.resolve(did) + + if (!didResult.didDocument) { + throw new AriesFrameworkError( + `Could not resolve did ${did}. ${didResult.didResolutionMetadata.error} ${didResult.didResolutionMetadata.message}` + ) + } + + // did:indy dids MUST have a verificationMethod with #verkey + const verificationMethod = didResult.didDocument.dereferenceKey(`${did}#verkey`) + const key = getKeyFromVerificationMethod(verificationMethod) + + return key +} diff --git a/packages/core/src/modules/dids/methods/sov/util.ts b/packages/indy-sdk/src/dids/didSovUtil.ts similarity index 78% rename from packages/core/src/modules/dids/methods/sov/util.ts rename to packages/indy-sdk/src/dids/didSovUtil.ts index 6dcc5c973d..2975fee9b0 100644 --- a/packages/core/src/modules/dids/methods/sov/util.ts +++ b/packages/indy-sdk/src/dids/didSovUtil.ts @@ -1,12 +1,27 @@ -import type { IndyEndpointAttrib } from '../../../ledger' +import { + TypedArrayEncoder, + DidDocumentService, + DidDocumentBuilder, + DidCommV1Service, + DidCommV2Service, + convertPublicKeyToX25519, +} from '@aries-framework/core' -import { TypedArrayEncoder } from '../../../../utils' -import { getFullVerkey } from '../../../../utils/did' -import { SECURITY_X25519_CONTEXT_URL } from '../../../vc/constants' -import { ED25519_SUITE_CONTEXT_URL_2018 } from '../../../vc/signature-suites/ed25519/constants' -import { DidDocumentService, DidDocumentBuilder, DidCommV1Service, DidCommV2Service } from '../../domain' -import { convertPublicKeyToX25519 } from '../../domain/key-type/ed25519' +import { getFullVerkey } from '../utils/did' +export type DidCommServicesEndpointType = 'endpoint' | 'did-communication' | 'DIDCommMessaging' + +export interface IndyEndpointAttrib { + endpoint?: string + types?: Array<'endpoint' | 'did-communication' | 'DIDCommMessaging'> + routingKeys?: string[] + [key: string]: unknown +} + +/** + * Get a base did:sov did document based on the provided did and verkey + * https://sovrin-foundation.github.io/sovrin/spec/did-method-spec-template.html#crud-operation-definitions + */ export function sovDidDocumentFromDid(fullDid: string, verkey: string) { const verificationMethodId = `${fullDid}#key-1` const keyAgreementId = `${fullDid}#key-agreement-1` @@ -17,8 +32,8 @@ export function sovDidDocumentFromDid(fullDid: string, verkey: string) { ) const builder = new DidDocumentBuilder(fullDid) - .addContext(ED25519_SUITE_CONTEXT_URL_2018) - .addContext(SECURITY_X25519_CONTEXT_URL) + .addContext('https://w3id.org/security/suites/ed25519-2018/v1') + .addContext('https://w3id.org/security/suites/x25519-2019/v1') .addVerificationMethod({ controller: fullDid, id: verificationMethodId, @@ -94,7 +109,7 @@ export function addServicesFromEndpointsAttrib( }) ) - // If 'DIDComm' included in types, add DIDComm v2 entry + // If 'DIDCommMessaging' included in types, add DIDComm v2 entry if (processedTypes.includes('DIDCommMessaging')) { builder .addService( diff --git a/packages/indy-sdk/src/dids/index.ts b/packages/indy-sdk/src/dids/index.ts new file mode 100644 index 0000000000..8017bf8749 --- /dev/null +++ b/packages/indy-sdk/src/dids/index.ts @@ -0,0 +1,3 @@ +export { IndySdkIndyDidRegistrar, IndySdkIndyDidCreateOptions } from './IndySdkIndyDidRegistrar' +export { IndySdkSovDidResolver } from './IndySdkSovDidResolver' +export { IndySdkIndyDidResolver } from './IndySdkIndyDidResolver' diff --git a/packages/core/src/error/IndySdkError.ts b/packages/indy-sdk/src/error/IndySdkError.ts similarity index 71% rename from packages/core/src/error/IndySdkError.ts rename to packages/indy-sdk/src/error/IndySdkError.ts index f67a0721f6..4b67802a9a 100644 --- a/packages/core/src/error/IndySdkError.ts +++ b/packages/indy-sdk/src/error/IndySdkError.ts @@ -1,6 +1,6 @@ -import type { IndyError } from '../utils/indyError' +import type { IndyError } from './indyError' -import { AriesFrameworkError } from './AriesFrameworkError' +import { AriesFrameworkError } from '@aries-framework/core' export class IndySdkError extends AriesFrameworkError { public constructor(indyError: IndyError, message?: string) { diff --git a/packages/indy-sdk/src/error/index.ts b/packages/indy-sdk/src/error/index.ts new file mode 100644 index 0000000000..5829a46d0a --- /dev/null +++ b/packages/indy-sdk/src/error/index.ts @@ -0,0 +1,2 @@ +export * from './IndySdkError' +export * from './indyError' diff --git a/packages/core/src/utils/indyError.ts b/packages/indy-sdk/src/error/indyError.ts similarity index 96% rename from packages/core/src/utils/indyError.ts rename to packages/indy-sdk/src/error/indyError.ts index c46ebef13e..c5d23f6093 100644 --- a/packages/core/src/utils/indyError.ts +++ b/packages/indy-sdk/src/error/indyError.ts @@ -1,4 +1,4 @@ -import { AriesFrameworkError } from '../error' +import { AriesFrameworkError } from '@aries-framework/core' export const indyErrors = { 100: 'CommonInvalidParam1', @@ -60,7 +60,7 @@ export const indyErrors = { 706: 'TransactionNotAllowedError', } as const -type IndyErrorValues = typeof indyErrors[keyof typeof indyErrors] +type IndyErrorValues = (typeof indyErrors)[keyof typeof indyErrors] export interface IndyError { name: 'IndyError' diff --git a/packages/indy-sdk/src/index.ts b/packages/indy-sdk/src/index.ts new file mode 100644 index 0000000000..5857f00da2 --- /dev/null +++ b/packages/indy-sdk/src/index.ts @@ -0,0 +1,23 @@ +// Dids +export * from './dids' + +// Ledger +export { IndySdkPoolConfig } from './ledger' + +// Wallet +export { IndySdkWallet } from './wallet' + +// Storage +export { IndySdkStorageService } from './storage' + +// AnonCreds +export { + IndySdkAnonCredsRegistry, + IndySdkHolderService, + IndySdkIssuerService, + IndySdkVerifierService, +} from './anoncreds' + +// Module +export { IndySdkModule } from './IndySdkModule' +export { IndySdkModuleConfig } from './IndySdkModuleConfig' diff --git a/packages/core/src/modules/ledger/IndyPool.ts b/packages/indy-sdk/src/ledger/IndySdkPool.ts similarity index 64% rename from packages/core/src/modules/ledger/IndyPool.ts rename to packages/indy-sdk/src/ledger/IndySdkPool.ts index c3d2249799..b784600416 100644 --- a/packages/core/src/modules/ledger/IndyPool.ts +++ b/packages/indy-sdk/src/ledger/IndySdkPool.ts @@ -1,47 +1,53 @@ -import type { AgentDependencies } from '../../agent/AgentDependencies' -import type { Logger } from '../../logger' -import type { FileSystem } from '../../storage/FileSystem' -import type { DidIndyNamespace } from '../../utils/indyIdentifiers' -import type * as Indy from 'indy-sdk' +import type { IndySdk } from '../types' +import type { FileSystem, Logger } from '@aries-framework/core' +import type { LedgerReadReplyResponse, LedgerRequest, LedgerWriteReplyResponse } from 'indy-sdk' import type { Subject } from 'rxjs' -import { AriesFrameworkError, IndySdkError } from '../../error' -import { isIndyError } from '../../utils/indyError' +import { AriesFrameworkError } from '@aries-framework/core' -import { LedgerError } from './error/LedgerError' -import { isLedgerRejectResponse, isLedgerReqnackResponse } from './ledgerUtil' +import { isIndyError, IndySdkError } from '../error' + +import { IndySdkPoolError } from './error' +import { isLedgerRejectResponse, isLedgerReqnackResponse } from './util' export interface TransactionAuthorAgreement { version: `${number}.${number}` | `${number}` acceptanceMechanism: string } -export interface IndyPoolConfig { +export interface IndySdkPoolConfig { + /** + * Optional id that influences the pool config that is created by the indy-sdk. + * Uses the indyNamespace as the pool identifier if not provided. + */ + id?: string + genesisPath?: string genesisTransactions?: string - id: string + isProduction: boolean - indyNamespace: DidIndyNamespace + indyNamespace: string transactionAuthorAgreement?: TransactionAuthorAgreement + connectOnStartup?: boolean } -export class IndyPool { - private indy: typeof Indy +export class IndySdkPool { + private indySdk: IndySdk private logger: Logger private fileSystem: FileSystem - private poolConfig: IndyPoolConfig + private poolConfig: IndySdkPoolConfig private _poolHandle?: number - private poolConnected?: Promise + private poolConnected?: Promise public authorAgreement?: AuthorAgreement | null public constructor( - poolConfig: IndyPoolConfig, - agentDependencies: AgentDependencies, + poolConfig: IndySdkPoolConfig, + indySdk: IndySdk, logger: Logger, stop$: Subject, fileSystem: FileSystem ) { - this.indy = agentDependencies.indy + this.indySdk = indySdk this.fileSystem = fileSystem this.poolConfig = poolConfig this.logger = logger @@ -55,7 +61,7 @@ export class IndyPool { } public get didIndyNamespace(): string { - return this.didIndyNamespace + return this.config.indyNamespace } public get id() { @@ -76,7 +82,7 @@ export class IndyPool { this._poolHandle = undefined this.poolConnected = undefined - await this.indy.closePoolLedger(poolHandle) + await this.indySdk.closePoolLedger(poolHandle) } public async delete() { @@ -85,7 +91,7 @@ export class IndyPool { await this.close() } - await this.indy.deletePoolLedgerConfig(this.poolConfig.id) + await this.indySdk.deletePoolLedgerConfig(this.poolConfig.indyNamespace) } public async connect() { @@ -104,7 +110,7 @@ export class IndyPool { } private async connectToLedger() { - const poolName = this.poolConfig.id + const poolName = this.poolConfig.id ?? this.poolConfig.indyNamespace const genesisPath = await this.getGenesisPath() if (!genesisPath) { @@ -112,11 +118,11 @@ export class IndyPool { } this.logger.debug(`Connecting to ledger pool '${poolName}'`, { genesisPath }) - await this.indy.setProtocolVersion(2) + await this.indySdk.setProtocolVersion(2) try { - this._poolHandle = await this.indy.openPoolLedger(poolName) - return this._poolHandle + this._poolHandle = await this.indySdk.openPoolLedger(poolName) + return } catch (error) { if (!isIndyError(error, 'PoolLedgerNotCreatedError')) { throw isIndyError(error) ? new IndySdkError(error) : error @@ -127,36 +133,40 @@ export class IndyPool { indyError: 'PoolLedgerNotCreatedError', }) try { - await this.indy.createPoolLedgerConfig(poolName, { genesis_txn: genesisPath }) - this._poolHandle = await this.indy.openPoolLedger(poolName) - return this._poolHandle + await this.indySdk.createPoolLedgerConfig(poolName, { genesis_txn: genesisPath }) + this._poolHandle = await this.indySdk.openPoolLedger(poolName) + return } catch (error) { throw isIndyError(error) ? new IndySdkError(error) : error } } - private async submitRequest(request: Indy.LedgerRequest) { - return this.indy.submitRequest(await this.getPoolHandle(), request) + private async submitRequest(request: LedgerRequest) { + return this.indySdk.submitRequest(await this.getPoolHandle(), request) } - public async submitReadRequest(request: Indy.LedgerRequest) { + public async submitReadRequest(request: LedgerRequest) { const response = await this.submitRequest(request) if (isLedgerRejectResponse(response) || isLedgerReqnackResponse(response)) { - throw new LedgerError(`Ledger '${this.id}' rejected read transaction request: ${response.reason}`) + throw new IndySdkPoolError( + `Ledger '${this.didIndyNamespace}' rejected read transaction request: ${response.reason}` + ) } - return response as Indy.LedgerReadReplyResponse + return response as LedgerReadReplyResponse } - public async submitWriteRequest(request: Indy.LedgerRequest) { + public async submitWriteRequest(request: LedgerRequest) { const response = await this.submitRequest(request) if (isLedgerRejectResponse(response) || isLedgerReqnackResponse(response)) { - throw new LedgerError(`Ledger '${this.id}' rejected write transaction request: ${response.reason}`) + throw new IndySdkPoolError( + `Ledger '${this.didIndyNamespace}' rejected write transaction request: ${response.reason}` + ) } - return response as Indy.LedgerWriteReplyResponse + return response as LedgerWriteReplyResponse } private async getPoolHandle() { @@ -169,9 +179,8 @@ export class IndyPool { } } - if (!this._poolHandle) { - return this.connect() - } + if (!this._poolHandle) await this.connect() + if (!this._poolHandle) throw new IndySdkPoolError('Pool handle not set after connection') return this._poolHandle } @@ -181,7 +190,7 @@ export class IndyPool { if (this.poolConfig.genesisPath) return this.poolConfig.genesisPath // Determine the genesisPath - const genesisPath = this.fileSystem.basePath + `/afj/genesis-${this.poolConfig.id}.txn` + const genesisPath = this.fileSystem.tempPath + `/genesis-${this.poolConfig.id ?? this.poolConfig.indyNamespace}.txn` // Store genesis data if provided if (this.poolConfig.genesisTransactions) { await this.fileSystem.write(genesisPath, this.poolConfig.genesisTransactions) diff --git a/packages/core/src/modules/ledger/services/IndyPoolService.ts b/packages/indy-sdk/src/ledger/IndySdkPoolService.ts similarity index 53% rename from packages/core/src/modules/ledger/services/IndyPoolService.ts rename to packages/indy-sdk/src/ledger/IndySdkPoolService.ts index dd5095b08d..628d603c0d 100644 --- a/packages/core/src/modules/ledger/services/IndyPoolService.ts +++ b/packages/indy-sdk/src/ledger/IndySdkPoolService.ts @@ -1,141 +1,138 @@ -import type { AgentContext } from '../../../agent' -import type { AcceptanceMechanisms, AuthorAgreement, IndyPoolConfig } from '../IndyPool' -import type { default as Indy, LedgerReadReplyResponse, LedgerRequest, LedgerWriteReplyResponse } from 'indy-sdk' - +import type { AcceptanceMechanisms, AuthorAgreement } from './IndySdkPool' +import type { IndySdk } from '../types' +import type { AgentContext, Key } from '@aries-framework/core' +import type { GetNymResponse, LedgerReadReplyResponse, LedgerRequest, LedgerWriteReplyResponse } from 'indy-sdk' + +import { didIndyRegex } from '@aries-framework/anoncreds' +import { + TypedArrayEncoder, + CacheModuleConfig, + InjectionSymbols, + Logger, + injectable, + inject, + FileSystem, +} from '@aries-framework/core' import { Subject } from 'rxjs' -import { AgentDependencies } from '../../../agent/AgentDependencies' -import { CacheRepository, PersistedLruCache } from '../../../cache' -import { InjectionSymbols } from '../../../constants' -import { IndySdkError } from '../../../error/IndySdkError' -import { Logger } from '../../../logger/Logger' -import { injectable, inject } from '../../../plugins' -import { FileSystem } from '../../../storage/FileSystem' -import { isSelfCertifiedDid } from '../../../utils/did' -import { isIndyError } from '../../../utils/indyError' -import { allSettled, onlyFulfilled, onlyRejected } from '../../../utils/promises' -import { assertIndyWallet } from '../../../wallet/util/assertIndyWallet' -import { IndyPool } from '../IndyPool' -import { LedgerError } from '../error/LedgerError' -import { LedgerNotConfiguredError } from '../error/LedgerNotConfiguredError' -import { LedgerNotFoundError } from '../error/LedgerNotFoundError' - -export const DID_POOL_CACHE_ID = 'DID_POOL_CACHE' -export const DID_POOL_CACHE_LIMIT = 500 +import { IndySdkModuleConfig } from '../IndySdkModuleConfig' +import { IndySdkError, isIndyError } from '../error' +import { assertIndySdkWallet } from '../utils/assertIndySdkWallet' +import { isLegacySelfCertifiedDid } from '../utils/did' +import { allSettled, onlyFulfilled, onlyRejected } from '../utils/promises' + +import { IndySdkPool } from './IndySdkPool' +import { IndySdkPoolError, IndySdkPoolNotConfiguredError, IndySdkPoolNotFoundError } from './error' +import { serializeRequestForSignature } from './serializeRequestForSignature' + export interface CachedDidResponse { - nymResponse: Indy.GetNymResponse - poolId: string + nymResponse: GetNymResponse + indyNamespace: string } + @injectable() -export class IndyPoolService { - public pools: IndyPool[] = [] +export class IndySdkPoolService { + public pools: IndySdkPool[] = [] private logger: Logger - private indy: typeof Indy - private agentDependencies: AgentDependencies + private indySdk: IndySdk private stop$: Subject private fileSystem: FileSystem - private didCache: PersistedLruCache + private indySdkModuleConfig: IndySdkModuleConfig public constructor( - cacheRepository: CacheRepository, - @inject(InjectionSymbols.AgentDependencies) agentDependencies: AgentDependencies, @inject(InjectionSymbols.Logger) logger: Logger, @inject(InjectionSymbols.Stop$) stop$: Subject, - @inject(InjectionSymbols.FileSystem) fileSystem: FileSystem + @inject(InjectionSymbols.FileSystem) fileSystem: FileSystem, + indySdkModuleConfig: IndySdkModuleConfig ) { this.logger = logger - this.indy = agentDependencies.indy - this.agentDependencies = agentDependencies + this.indySdk = indySdkModuleConfig.indySdk this.fileSystem = fileSystem this.stop$ = stop$ + this.indySdkModuleConfig = indySdkModuleConfig - this.didCache = new PersistedLruCache(DID_POOL_CACHE_ID, DID_POOL_CACHE_LIMIT, cacheRepository) - } - - public setPools(poolConfigs: IndyPoolConfig[]) { - this.pools = poolConfigs.map( - (poolConfig) => new IndyPool(poolConfig, this.agentDependencies, this.logger, this.stop$, this.fileSystem) + this.pools = this.indySdkModuleConfig.networks.map( + (network) => new IndySdkPool(network, this.indySdk, this.logger, this.stop$, this.fileSystem) ) } /** - * Create connections to all ledger pools + * Get the most appropriate pool for the given did. + * If the did is a qualified indy did, the pool will be determined based on the namespace. + * If it is a legacy unqualified indy did, the pool will be determined based on the algorithm as described in this document: + * https://docs.google.com/document/d/109C_eMsuZnTnYe2OAd02jAts1vC4axwEKIq7_4dnNVA/edit + * + * This method will optionally return a nym response when the did has been resolved to determine the ledger + * either now or in the past. The nymResponse can be used to prevent multiple ledger queries fetching the same + * did */ - public async connectToPools() { - const handleArray: number[] = [] - // Sequentially connect to pools so we don't use up too many resources connecting in parallel - for (const pool of this.pools) { - this.logger.debug(`Connecting to pool: ${pool.id}`) - const poolHandle = await pool.connect() - this.logger.debug(`Finished connection to pool: ${pool.id}`) - handleArray.push(poolHandle) - } - return handleArray - } + public async getPoolForDid( + agentContext: AgentContext, + did: string + ): Promise<{ pool: IndySdkPool; nymResponse?: GetNymResponse }> { + // Check if the did starts with did:indy + const match = did.match(didIndyRegex) - /** - * @deprecated use instead getPoolForNamespace - * Get the pool used for writing to the ledger. For now we always use the first pool - * as the pool that writes to the ledger - */ - public get ledgerWritePool() { - if (this.pools.length === 0) { - throw new LedgerNotConfiguredError( - "No indy ledgers configured. Provide at least one pool configuration in the 'indyLedgers' agent configuration" - ) - } + if (match) { + const [, namespace] = match + + const pool = this.getPoolForNamespace(namespace) - return this.pools[0] + if (pool) return { pool } + + throw new IndySdkPoolError(`Pool for indy namespace '${namespace}' not found`) + } else { + return await this.getPoolForLegacyDid(agentContext, did) + } } - /** - * Get the most appropriate pool for the given did. The algorithm is based on the approach as described in this document: - * https://docs.google.com/document/d/109C_eMsuZnTnYe2OAd02jAts1vC4axwEKIq7_4dnNVA/edit - */ - public async getPoolForDid( + private async getPoolForLegacyDid( agentContext: AgentContext, did: string - ): Promise<{ pool: IndyPool; did: Indy.GetNymResponse }> { + ): Promise<{ pool: IndySdkPool; nymResponse: GetNymResponse }> { const pools = this.pools if (pools.length === 0) { - throw new LedgerNotConfiguredError( - "No indy ledgers configured. Provide at least one pool configuration in the 'indyLedgers' agent configuration" + throw new IndySdkPoolNotConfiguredError( + 'No indy ledgers configured. Provide at least one pool configuration in IndySdkModuleConfigOptions.networks' ) } - const cachedNymResponse = await this.didCache.get(agentContext, did) - const pool = this.pools.find((pool) => pool.id === cachedNymResponse?.poolId) + const cache = agentContext.dependencyManager.resolve(CacheModuleConfig).cache + const cacheKey = `IndySdkPoolService:${did}` + + const cachedNymResponse = await cache.get(agentContext, cacheKey) + const pool = this.pools.find((pool) => pool.didIndyNamespace === cachedNymResponse?.indyNamespace) // If we have the nym response with associated pool in the cache, we'll use that if (cachedNymResponse && pool) { - this.logger.trace(`Found ledger id '${pool.id}' for did '${did}' in cache`) - return { did: cachedNymResponse.nymResponse, pool } + this.logger.trace(`Found ledger '${pool.didIndyNamespace}' for did '${did}' in cache`) + return { nymResponse: cachedNymResponse.nymResponse, pool } } const { successful, rejected } = await this.getSettledDidResponsesFromPools(did, pools) if (successful.length === 0) { - const allNotFound = rejected.every((e) => e.reason instanceof LedgerNotFoundError) - const rejectedOtherThanNotFound = rejected.filter((e) => !(e.reason instanceof LedgerNotFoundError)) + const allNotFound = rejected.every((e) => e.reason instanceof IndySdkPoolNotFoundError) + const rejectedOtherThanNotFound = rejected.filter((e) => !(e.reason instanceof IndySdkPoolNotFoundError)) // All ledgers returned response that the did was not found if (allNotFound) { - throw new LedgerNotFoundError(`Did '${did}' not found on any of the ledgers (total ${this.pools.length}).`) + throw new IndySdkPoolNotFoundError(`Did '${did}' not found on any of the ledgers (total ${this.pools.length}).`) } // one or more of the ledgers returned an unknown error - throw new LedgerError( - `Unknown error retrieving did '${did}' from '${rejectedOtherThanNotFound.length}' of '${pools.length}' ledgers`, + throw new IndySdkPoolError( + `Unknown error retrieving did '${did}' from '${rejectedOtherThanNotFound.length}' of '${pools.length}' ledgers. ${rejectedOtherThanNotFound[0].reason}`, { cause: rejectedOtherThanNotFound[0].reason } ) } // If there are self certified DIDs we always prefer it over non self certified DIDs // We take the first self certifying DID as we take the order in the - // indyLedgers config as the order of preference of ledgers + // IndySdkModuleConfigOptions.networks config as the order of preference of ledgers let value = successful.find((response) => - isSelfCertifiedDid(response.value.did.did, response.value.did.verkey) + isLegacySelfCertifiedDid(response.value.did.did, response.value.did.verkey) )?.value if (!value) { @@ -146,19 +143,20 @@ export class IndyPoolService { const nonProduction = successful.filter((s) => !s.value.pool.config.isProduction) const productionOrNonProduction = production.length >= 1 ? production : nonProduction - // We take the first value as we take the order in the indyLedgers config as - // the order of preference of ledgers + // We take the first value as we take the order in the IndySdkModuleConfigOptions.networks + // config as the order of preference of ledgers value = productionOrNonProduction[0].value } - await this.didCache.set(agentContext, did, { + await cache.set(agentContext, cacheKey, { nymResponse: value.did, - poolId: value.pool.id, + indyNamespace: value.pool.didIndyNamespace, }) - return { pool: value.pool, did: value.did } + + return { pool: value.pool, nymResponse: value.did } } - private async getSettledDidResponsesFromPools(did: string, pools: IndyPool[]) { + private async getSettledDidResponsesFromPools(did: string, pools: IndySdkPool[]) { this.logger.trace(`Retrieving did '${did}' from ${pools.length} ledgers`) const didResponses = await allSettled(pools.map((pool) => this.getDidFromPool(did, pool))) @@ -178,8 +176,8 @@ export class IndyPoolService { */ public getPoolForNamespace(indyNamespace?: string) { if (this.pools.length === 0) { - throw new LedgerNotConfiguredError( - "No indy ledgers configured. Provide at least one pool configuration in the 'indyLedgers' agent configuration" + throw new IndySdkPoolNotConfiguredError( + 'No indy ledgers configured. Provide at least one pool configuration in IndySdkModuleConfigOptions.networks' ) } @@ -191,7 +189,7 @@ export class IndyPoolService { const pool = this.pools.find((pool) => pool.didIndyNamespace === indyNamespace) if (!pool) { - throw new LedgerNotFoundError(`No ledgers found for IndyNamespace '${indyNamespace}'.`) + throw new IndySdkPoolNotFoundError(`No ledgers found for indy namespace '${indyNamespace}'.`) } return pool @@ -199,13 +197,13 @@ export class IndyPoolService { public async submitWriteRequest( agentContext: AgentContext, - pool: IndyPool, + pool: IndySdkPool, request: LedgerRequest, - signDid: string + signingKey: Key ): Promise { try { const requestWithTaa = await this.appendTaa(pool, request) - const signedRequestWithTaa = await this.signRequest(agentContext, signDid, requestWithTaa) + const signedRequestWithTaa = await this.signRequest(agentContext, signingKey, requestWithTaa) const response = await pool.submitWriteRequest(signedRequestWithTaa) @@ -215,7 +213,7 @@ export class IndyPoolService { } } - public async submitReadRequest(pool: IndyPool, request: LedgerRequest): Promise { + public async submitReadRequest(pool: IndySdkPool, request: LedgerRequest): Promise { try { const response = await pool.submitReadRequest(request) @@ -225,17 +223,24 @@ export class IndyPoolService { } } - private async signRequest(agentContext: AgentContext, did: string, request: LedgerRequest): Promise { - assertIndyWallet(agentContext.wallet) + private async signRequest(agentContext: AgentContext, key: Key, request: LedgerRequest): Promise { + assertIndySdkWallet(agentContext.wallet) try { - return this.indy.signRequest(agentContext.wallet.handle, did, request) + const signedPayload = await this.indySdk.cryptoSign( + agentContext.wallet.handle, + key.publicKeyBase58, + TypedArrayEncoder.fromString(serializeRequestForSignature(request)) + ) + + const signedRequest = { ...request, signature: TypedArrayEncoder.toBase58(signedPayload) } + return signedRequest } catch (error) { throw isIndyError(error) ? new IndySdkError(error) : error } } - private async appendTaa(pool: IndyPool, request: Indy.LedgerRequest) { + private async appendTaa(pool: IndySdkPool, request: LedgerRequest) { try { const authorAgreement = await this.getTransactionAuthorAgreement(pool) const taa = pool.config.transactionAuthorAgreement @@ -246,7 +251,7 @@ export class IndyPoolService { } // Ledger has taa but user has not specified which one to use if (!taa) { - throw new LedgerError( + throw new IndySdkPoolError( `Please, specify a transaction author agreement with version and acceptance mechanism. ${JSON.stringify( authorAgreement )}` @@ -264,10 +269,10 @@ export class IndyPoolService { )} and version ${JSON.stringify(taa.version)} in pool.\n Found ${JSON.stringify( Object.keys(authorAgreement.acceptanceMechanisms.aml) )} and version ${authorAgreement.version} in pool.` - throw new LedgerError(errMessage) + throw new IndySdkPoolError(errMessage) } - const requestWithTaa = await this.indy.appendTxnAuthorAgreementAcceptanceToRequest( + const requestWithTaa = await this.indySdk.appendTxnAuthorAgreementAcceptanceToRequest( request, authorAgreement.text, taa.version, @@ -284,16 +289,16 @@ export class IndyPoolService { } } - private async getTransactionAuthorAgreement(pool: IndyPool): Promise { + private async getTransactionAuthorAgreement(pool: IndySdkPool): Promise { try { // TODO Replace this condition with memoization if (pool.authorAgreement !== undefined) { return pool.authorAgreement } - const taaRequest = await this.indy.buildGetTxnAuthorAgreementRequest(null) + const taaRequest = await this.indySdk.buildGetTxnAuthorAgreementRequest(null) const taaResponse = await this.submitReadRequest(pool, taaRequest) - const acceptanceMechanismRequest = await this.indy.buildGetAcceptanceMechanismsRequest(null) + const acceptanceMechanismRequest = await this.indySdk.buildGetAcceptanceMechanismsRequest(null) const acceptanceMechanismResponse = await this.submitReadRequest(pool, acceptanceMechanismRequest) // TAA can be null @@ -315,16 +320,16 @@ export class IndyPoolService { } } - private async getDidFromPool(did: string, pool: IndyPool): Promise { + private async getDidFromPool(did: string, pool: IndySdkPool): Promise { try { - this.logger.trace(`Get public did '${did}' from ledger '${pool.id}'`) - const request = await this.indy.buildGetNymRequest(null, did) + this.logger.trace(`Get public did '${did}' from ledger '${pool.didIndyNamespace}'`) + const request = await this.indySdk.buildGetNymRequest(null, did) - this.logger.trace(`Submitting get did request for did '${did}' to ledger '${pool.id}'`) + this.logger.trace(`Submitting get did request for did '${did}' to ledger '${pool.didIndyNamespace}'`) const response = await pool.submitReadRequest(request) - const result = await this.indy.parseGetNymResponse(response) - this.logger.trace(`Retrieved did '${did}' from ledger '${pool.id}'`, result) + const result = await this.indySdk.parseGetNymResponse(response) + this.logger.trace(`Retrieved did '${did}' from ledger '${pool.didIndyNamespace}'`, result) return { did: result, @@ -332,12 +337,12 @@ export class IndyPoolService { response, } } catch (error) { - this.logger.trace(`Error retrieving did '${did}' from ledger '${pool.id}'`, { + this.logger.trace(`Error retrieving did '${did}' from ledger '${pool.didIndyNamespace}'`, { error, did, }) if (isIndyError(error, 'LedgerNotFound')) { - throw new LedgerNotFoundError(`Did '${did}' not found on ledger ${pool.id}`) + throw new IndySdkPoolNotFoundError(`Did '${did}' not found on ledger ${pool.didIndyNamespace}`) } else { throw isIndyError(error) ? new IndySdkError(error) : error } @@ -346,7 +351,7 @@ export class IndyPoolService { } export interface PublicDidRequest { - did: Indy.GetNymResponse - pool: IndyPool - response: Indy.LedgerReadReplyResponse + did: GetNymResponse + pool: IndySdkPool + response: LedgerReadReplyResponse } diff --git a/packages/core/src/modules/ledger/__tests__/IndyPoolService.test.ts b/packages/indy-sdk/src/ledger/__tests__/IndySdkPoolService.test.ts similarity index 66% rename from packages/core/src/modules/ledger/__tests__/IndyPoolService.test.ts rename to packages/indy-sdk/src/ledger/__tests__/IndySdkPoolService.test.ts index 6c0d8e4e3f..db61266ea3 100644 --- a/packages/core/src/modules/ledger/__tests__/IndyPoolService.test.ts +++ b/packages/indy-sdk/src/ledger/__tests__/IndySdkPoolService.test.ts @@ -1,57 +1,52 @@ -import type { AgentContext } from '../../../agent' -import type { IndyPoolConfig } from '../IndyPool' -import type { CachedDidResponse } from '../services/IndyPoolService' - +import type { IndySdkPoolConfig } from '../IndySdkPool' +import type { CachedDidResponse } from '../IndySdkPoolService' + +import { + CacheModuleConfig, + InMemoryLruCache, + KeyProviderRegistry, + AriesFrameworkError, + Key, + KeyType, +} from '@aries-framework/core' +import indySdk from 'indy-sdk' import { Subject } from 'rxjs' -import { NodeFileSystem } from '../../../../../node/src/NodeFileSystem' -import { agentDependencies, getAgentConfig, getAgentContext, mockFunction } from '../../../../tests/helpers' -import { CacheRecord } from '../../../cache' -import { CacheRepository } from '../../../cache/CacheRepository' -import { KeyProviderRegistry } from '../../../crypto/key-provider' -import { AriesFrameworkError } from '../../../error/AriesFrameworkError' -import { IndyWallet } from '../../../wallet/IndyWallet' -import { LedgerError } from '../error/LedgerError' -import { LedgerNotConfiguredError } from '../error/LedgerNotConfiguredError' -import { LedgerNotFoundError } from '../error/LedgerNotFoundError' -import { DID_POOL_CACHE_ID, IndyPoolService } from '../services/IndyPoolService' +import { getAgentConfig, getAgentContext } from '../../../../core/tests/helpers' +import { NodeFileSystem } from '../../../../node/src/NodeFileSystem' +import { IndySdkModuleConfig } from '../../IndySdkModuleConfig' +import { IndySdkWallet } from '../../wallet/IndySdkWallet' +import { IndySdkPoolService } from '../IndySdkPoolService' +import { IndySdkPoolError, IndySdkPoolNotConfiguredError, IndySdkPoolNotFoundError } from '../error' import { getDidResponsesForDid } from './didResponses' -jest.mock('../../../cache/CacheRepository') -const CacheRepositoryMock = CacheRepository as jest.Mock - -const pools: IndyPoolConfig[] = [ +const pools: IndySdkPoolConfig[] = [ { - id: 'sovrinMain', indyNamespace: 'sovrin', isProduction: true, genesisTransactions: 'xxx', transactionAuthorAgreement: { version: '1', acceptanceMechanism: 'accept' }, }, { - id: 'sovrinBuilder', indyNamespace: 'sovrin:builder', isProduction: false, genesisTransactions: 'xxx', transactionAuthorAgreement: { version: '1', acceptanceMechanism: 'accept' }, }, { - id: 'sovringStaging', indyNamespace: 'sovrin:staging', isProduction: false, genesisTransactions: 'xxx', transactionAuthorAgreement: { version: '1', acceptanceMechanism: 'accept' }, }, { - id: 'indicioMain', indyNamespace: 'indicio', isProduction: true, genesisTransactions: 'xxx', transactionAuthorAgreement: { version: '1', acceptanceMechanism: 'accept' }, }, { - id: 'bcovrinTest', indyNamespace: 'bcovrin:test', isProduction: false, genesisTransactions: 'xxx', @@ -59,18 +54,21 @@ const pools: IndyPoolConfig[] = [ }, ] -describe('IndyPoolService', () => { - const config = getAgentConfig('IndyPoolServiceTest', { - indyLedgers: pools, - }) - let agentContext: AgentContext - let wallet: IndyWallet - let poolService: IndyPoolService - let cacheRepository: CacheRepository +const config = getAgentConfig('IndySdkPoolServiceTest') +const cache = new InMemoryLruCache({ limit: 1 }) + +const indySdkModule = new IndySdkModuleConfig({ indySdk, networks: pools }) +const wallet = new IndySdkWallet(indySdk, config.logger, new KeyProviderRegistry([])) +const agentContext = getAgentContext({ + wallet, + registerInstances: [[CacheModuleConfig, new CacheModuleConfig({ cache })]], +}) + +const poolService = new IndySdkPoolService(config.logger, new Subject(), new NodeFileSystem(), indySdkModule) + +describe('IndySdkPoolService', () => { beforeAll(async () => { - wallet = new IndyWallet(config.agentDependencies, config.logger, new KeyProviderRegistry([])) - agentContext = getAgentContext() // eslint-disable-next-line @typescript-eslint/no-non-null-assertion await wallet.createAndOpen(config.walletConfig!) }) @@ -79,41 +77,21 @@ describe('IndyPoolService', () => { await wallet.delete() }) - beforeEach(async () => { - cacheRepository = new CacheRepositoryMock() - mockFunction(cacheRepository.findById).mockResolvedValue(null) - - poolService = new IndyPoolService( - cacheRepository, - agentDependencies, - config.logger, - new Subject(), - new NodeFileSystem() - ) - - poolService.setPools(pools) - }) - - describe('ledgerWritePool', () => { - it('should return the first pool', async () => { - expect(poolService.ledgerWritePool).toBe(poolService.pools[0]) - }) - - it('should throw a LedgerNotConfiguredError error if no pools are configured on the pool service', async () => { - poolService.setPools([]) - - expect(() => poolService.ledgerWritePool).toThrow(LedgerNotConfiguredError) - }) + afterEach(() => { + cache.clear() }) describe('getPoolForDid', () => { - it('should throw a LedgerNotConfiguredError error if no pools are configured on the pool service', async () => { - poolService.setPools([]) + it('should throw a IndySdkPoolNotConfiguredError error if no pools are configured on the pool service', async () => { + const oldPools = poolService.pools + poolService.pools = [] - expect(poolService.getPoolForDid(agentContext, 'some-did')).rejects.toThrow(LedgerNotConfiguredError) + expect(poolService.getPoolForDid(agentContext, 'some-did')).rejects.toThrow(IndySdkPoolNotConfiguredError) + + poolService.pools = oldPools }) - it('should throw a LedgerError if all ledger requests throw an error other than NotFoundError', async () => { + it('should throw a IndySdkPoolError if all ledger requests throw an error other than NotFoundError', async () => { const did = 'Y5bj4SjCiTM9PgeheKAiXx' poolService.pools.forEach((pool) => { @@ -121,10 +99,10 @@ describe('IndyPoolService', () => { spy.mockImplementationOnce(() => Promise.reject(new AriesFrameworkError('Something went wrong'))) }) - expect(poolService.getPoolForDid(agentContext, did)).rejects.toThrowError(LedgerError) + expect(poolService.getPoolForDid(agentContext, did)).rejects.toThrowError(IndySdkPoolError) }) - it('should throw a LedgerNotFoundError if all pools did not find the did on the ledger', async () => { + it('should throw a IndySdkPoolNotFoundError if all pools did not find the did on the ledger', async () => { const did = 'Y5bj4SjCiTM9PgeheKAiXx' // Not found on any of the ledgers const responses = getDidResponsesForDid(did, pools, {}) @@ -134,14 +112,20 @@ describe('IndyPoolService', () => { spy.mockImplementationOnce(responses[index]) }) - expect(poolService.getPoolForDid(agentContext, did)).rejects.toThrowError(LedgerNotFoundError) + expect(poolService.getPoolForDid(agentContext, did)).rejects.toThrowError(IndySdkPoolNotFoundError) + }) + + it('should return the pool based on namespace if did is a valid did:indy did', async () => { + const { pool } = await poolService.getPoolForDid(agentContext, 'did:indy:sovrin:Y5bj4SjCiTM9PgeheKAiXx') + + expect(pool.didIndyNamespace).toBe('sovrin') }) it('should return the pool if the did was only found on one ledger', async () => { const did = 'TL1EaPFCZ8Si5aUrqScBDt' // Only found on one ledger const responses = getDidResponsesForDid(did, pools, { - sovrinMain: '~43X4NhAFqREffK7eWdKgFH', + sovrin: '~43X4NhAFqREffK7eWdKgFH', }) poolService.pools.forEach((pool, index) => { @@ -151,16 +135,16 @@ describe('IndyPoolService', () => { const { pool } = await poolService.getPoolForDid(agentContext, did) - expect(pool.config.id).toBe('sovrinMain') + expect(pool.config.indyNamespace).toBe('sovrin') }) it('should return the first pool with a self certifying DID if at least one did is self certifying ', async () => { const did = 'did:sov:q7ATwTYbQDgiigVijUAej' // Found on one production and one non production ledger const responses = getDidResponsesForDid(did, pools, { - indicioMain: '~43X4NhAFqREffK7eWdKgFH', - bcovrinTest: '43X4NhAFqREffK7eWdKgFH43X4NhAFqREffK7eWdKgFH', - sovrinBuilder: '~43X4NhAFqREffK7eWdKgFH', + indicio: '~43X4NhAFqREffK7eWdKgFH', + 'bcovrin:test': '43X4NhAFqREffK7eWdKgFH43X4NhAFqREffK7eWdKgFH', + 'sovrin:builder': '~43X4NhAFqREffK7eWdKgFH', }) poolService.pools.forEach((pool, index) => { @@ -170,15 +154,15 @@ describe('IndyPoolService', () => { const { pool } = await poolService.getPoolForDid(agentContext, did) - expect(pool.config.id).toBe('sovrinBuilder') + expect(pool.config.indyNamespace).toBe('sovrin:builder') }) it('should return the production pool if the did was found on one production and one non production ledger and both DIDs are not self certifying', async () => { const did = 'V6ty6ttM3EjuCtosH6sGtW' // Found on one production and one non production ledger const responses = getDidResponsesForDid(did, pools, { - indicioMain: '43X4NhAFqREffK7eWdKgFH43X4NhAFqREffK7eWdKgFH', - sovrinBuilder: '43X4NhAFqREffK7eWdKgFH43X4NhAFqREffK7eWdKgFH', + indicio: '43X4NhAFqREffK7eWdKgFH43X4NhAFqREffK7eWdKgFH', + 'sovrin:builder': '43X4NhAFqREffK7eWdKgFH43X4NhAFqREffK7eWdKgFH', }) poolService.pools.forEach((pool, index) => { @@ -188,15 +172,15 @@ describe('IndyPoolService', () => { const { pool } = await poolService.getPoolForDid(agentContext, did) - expect(pool.config.id).toBe('indicioMain') + expect(pool.config.indyNamespace).toBe('indicio') }) it('should return the pool with the self certified did if the did was found on two production ledgers where one did is self certified', async () => { const did = 'VsKV7grR1BUE29mG2Fm2kX' // Found on two production ledgers. Sovrin is self certified const responses = getDidResponsesForDid(did, pools, { - sovrinMain: '~43X4NhAFqREffK7eWdKgFH', - indicioMain: 'kqa2HyagzfMAq42H5f9u3UMwnSBPQx2QfrSyXbUPxMn', + sovrin: '~43X4NhAFqREffK7eWdKgFH', + indicio: 'kqa2HyagzfMAq42H5f9u3UMwnSBPQx2QfrSyXbUPxMn', }) poolService.pools.forEach((pool, index) => { @@ -206,16 +190,16 @@ describe('IndyPoolService', () => { const { pool } = await poolService.getPoolForDid(agentContext, did) - expect(pool.config.id).toBe('sovrinMain') + expect(pool.config.indyNamespace).toBe('sovrin') }) it('should return the first pool with a self certified did if the did was found on three non production ledgers where two DIDs are self certified', async () => { const did = 'HEi9QViXNThGQaDsQ3ptcw' // Found on two non production ledgers. Sovrin is self certified const responses = getDidResponsesForDid(did, pools, { - sovrinBuilder: '~M9kv2Ez61cur7X39DXWh8W', - sovrinStaging: '~M9kv2Ez61cur7X39DXWh8W', - bcovrinTest: '3SeuRm3uYuQDYmHeuMLu1xNHozNTtzS3kbZRFMMCWrX4', + 'sovrin:builder': '~M9kv2Ez61cur7X39DXWh8W', + 'sovrin:staging': '~M9kv2Ez61cur7X39DXWh8W', + 'bcovrin:test': '3SeuRm3uYuQDYmHeuMLu1xNHozNTtzS3kbZRFMMCWrX4', }) poolService.pools.forEach((pool, index) => { @@ -225,7 +209,7 @@ describe('IndyPoolService', () => { const { pool } = await poolService.getPoolForDid(agentContext, did) - expect(pool.config.id).toBe('sovrinBuilder') + expect(pool.config.indyNamespace).toBe('sovrin:builder') }) it('should return the pool from the cache if the did was found in the cache', async () => { @@ -239,44 +223,22 @@ describe('IndyPoolService', () => { role: 'ENDORSER', verkey: '~M9kv2Ez61cur7X39DXWh8W', }, - poolId: expectedPool.id, + indyNamespace: expectedPool.indyNamespace, } - const cachedEntries = [ - { - key: did, - value: didResponse, - }, - ] - - mockFunction(cacheRepository.findById).mockResolvedValue( - new CacheRecord({ - id: DID_POOL_CACHE_ID, - entries: cachedEntries, - }) - ) - + await cache.set(agentContext, `IndySdkPoolService:${did}`, didResponse) const { pool } = await poolService.getPoolForDid(agentContext, did) - expect(pool.config.id).toBe(pool.id) + expect(pool.config.indyNamespace).toBe(pool.didIndyNamespace) }) - it('should set the poolId in the cache if the did was not found in the cache, but resolved later on', async () => { + it('should set the indyNamespace in the cache if the did was not found in the cache, but resolved later on', async () => { const did = 'HEi9QViXNThGQaDsQ3ptcw' // Found on one ledger const responses = getDidResponsesForDid(did, pools, { - sovrinBuilder: '~M9kv2Ez61cur7X39DXWh8W', + 'sovrin:builder': '~M9kv2Ez61cur7X39DXWh8W', }) - mockFunction(cacheRepository.findById).mockResolvedValue( - new CacheRecord({ - id: DID_POOL_CACHE_ID, - entries: [], - }) - ) - - const spy = mockFunction(cacheRepository.update).mockResolvedValue() - poolService.pools.forEach((pool, index) => { const spy = jest.spyOn(pool, 'submitReadRequest') spy.mockImplementationOnce(responses[index]) @@ -284,37 +246,37 @@ describe('IndyPoolService', () => { const { pool } = await poolService.getPoolForDid(agentContext, did) - expect(pool.config.id).toBe('sovrinBuilder') + expect(pool.config.indyNamespace).toBe('sovrin:builder') expect(pool.config.indyNamespace).toBe('sovrin:builder') - const cacheRecord = spy.mock.calls[0][1] - expect(cacheRecord.entries.length).toBe(1) - expect(cacheRecord.entries[0].key).toBe(did) - expect(cacheRecord.entries[0].value).toEqual({ + expect(await cache.get(agentContext, `IndySdkPoolService:${did}`)).toEqual({ nymResponse: { did, verkey: '~M9kv2Ez61cur7X39DXWh8W', role: '0', }, - poolId: 'sovrinBuilder', + indyNamespace: 'sovrin:builder', }) }) }) describe('getPoolForNamespace', () => { - it('should throw a LedgerNotConfiguredError error if no pools are configured on the pool service', async () => { - poolService.setPools([]) + it('should throw a IndySdkPoolNotConfiguredError error if no pools are configured on the pool service', async () => { + const oldPools = poolService.pools + poolService.pools = [] + + expect(() => poolService.getPoolForNamespace()).toThrow(IndySdkPoolNotConfiguredError) - expect(() => poolService.getPoolForNamespace()).toThrow(LedgerNotConfiguredError) + poolService.pools = oldPools }) it('should return the first pool if indyNamespace is not provided', async () => { const expectedPool = pools[0] - expect(poolService.getPoolForNamespace().id).toEqual(expectedPool.id) + expect(poolService.getPoolForNamespace().didIndyNamespace).toEqual(expectedPool.indyNamespace) }) - it('should throw a LedgerNotFoundError error if any of the pools did not have the provided indyNamespace', async () => { + it('should throw a IndySdkPoolNotFoundError error if any of the pools did not have the provided indyNamespace', async () => { const indyNameSpace = 'test' const responses = pools.map((pool) => pool.indyNamespace) @@ -323,7 +285,7 @@ describe('IndyPoolService', () => { spy.mockReturnValueOnce(responses[index]) }) - expect(() => poolService.getPoolForNamespace(indyNameSpace)).toThrow(LedgerNotFoundError) + expect(() => poolService.getPoolForNamespace(indyNameSpace)).toThrow(IndySdkPoolNotFoundError) }) it('should return the first pool that indyNamespace matches', async () => { @@ -338,7 +300,7 @@ describe('IndyPoolService', () => { const pool = poolService.getPoolForNamespace(indyNameSpace) - expect(pool.id).toEqual(expectedPool.id) + expect(pool.didIndyNamespace).toEqual(expectedPool.indyNamespace) }) }) @@ -374,7 +336,7 @@ describe('IndyPoolService', () => { }, protocolVersion: 2, }, - 'GAb4NUvpBcHVCvtP45vTVa5Bp74vFg3iXzdp1Gbd68Wf' + Key.fromPublicKeyBase58('GAb4NUvpBcHVCvtP45vTVa5Bp74vFg3iXzdp1Gbd68Wf', KeyType.Ed25519) ) ).rejects.toThrowError( 'Unable to satisfy matching TAA with mechanism "accept" and version "1" in pool.\n Found ["accept"] and version 2.0 in pool.' @@ -412,7 +374,7 @@ describe('IndyPoolService', () => { }, protocolVersion: 2, }, - 'GAb4NUvpBcHVCvtP45vTVa5Bp74vFg3iXzdp1Gbd68Wf' + Key.fromPublicKeyBase58('GAb4NUvpBcHVCvtP45vTVa5Bp74vFg3iXzdp1Gbd68Wf', KeyType.Ed25519) ) ).rejects.toThrowError( 'Unable to satisfy matching TAA with mechanism "accept" and version "1" in pool.\n Found ["decline"] and version 1.0 in pool.' @@ -452,7 +414,7 @@ describe('IndyPoolService', () => { }, protocolVersion: 2, }, - 'GAb4NUvpBcHVCvtP45vTVa5Bp74vFg3iXzdp1Gbd68Wf' + Key.fromPublicKeyBase58('GAb4NUvpBcHVCvtP45vTVa5Bp74vFg3iXzdp1Gbd68Wf', KeyType.Ed25519) ) ).rejects.toThrowError(/Please, specify a transaction author agreement with version and acceptance mechanism/) }) diff --git a/packages/core/src/modules/ledger/__tests__/didResponses.ts b/packages/indy-sdk/src/ledger/__tests__/didResponses.ts similarity index 94% rename from packages/core/src/modules/ledger/__tests__/didResponses.ts rename to packages/indy-sdk/src/ledger/__tests__/didResponses.ts index bde086e073..4d3dac6596 100644 --- a/packages/core/src/modules/ledger/__tests__/didResponses.ts +++ b/packages/indy-sdk/src/ledger/__tests__/didResponses.ts @@ -1,4 +1,4 @@ -import type { IndyPoolConfig } from '../IndyPool' +import type { IndySdkPoolConfig } from '../IndySdkPool' import type * as Indy from 'indy-sdk' // eslint-disable-next-line @typescript-eslint/ban-ts-comment @@ -42,11 +42,11 @@ export function getDidResponse({ did, verkey }: { did: string; verkey: string }) export function getDidResponsesForDid( did: string, - pools: IndyPoolConfig[], + pools: IndySdkPoolConfig[], responses: { [key: string]: string | undefined } ) { return pools.map((pool) => { - const verkey = responses[pool.id] + const verkey = responses[pool.indyNamespace] if (verkey) { return () => Promise.resolve(getDidResponse({ did, verkey })) diff --git a/packages/indy-sdk/src/ledger/__tests__/serializeRequestForSignature.test.ts b/packages/indy-sdk/src/ledger/__tests__/serializeRequestForSignature.test.ts new file mode 100644 index 0000000000..7bf0c64a67 --- /dev/null +++ b/packages/indy-sdk/src/ledger/__tests__/serializeRequestForSignature.test.ts @@ -0,0 +1,87 @@ +import { serializeRequestForSignature } from '../serializeRequestForSignature' + +describe('serializeRequestForSignature', () => { + it('Should correctly serialize the json for signature input', () => { + const request = { + name: 'John Doe', + age: 43, + operation: { + dest: 54, + }, + phones: ['1234567', '2345678', { rust: 5, age: 1 }, 3], + } + + const expectedResult = 'age:43|name:John Doe|operation:dest:54|phones:1234567,2345678,age:1|rust:5,3' + + expect(serializeRequestForSignature(request)).toEqual(expectedResult) + }) + + it('Should correctly serialize the json for signature with skipped fields', () => { + const request = { + name: 'John Doe', + age: 43, + operation: { + type: '100', + hash: 'cool hash', + dest: 54, + }, + fees: 'fees1', + signature: 'sign1', + signatures: 'sign-m', + phones: ['1234567', '2345678', { rust: 5, age: 1 }, 3], + } + + const expectedResult = + 'age:43|name:John Doe|operation:dest:54|hash:46aa0c92129b33ee72ee1478d2ae62fa6e756869dedc6c858af3214a6fcf1904|type:100|phones:1234567,2345678,age:1|rust:5,3' + + expect(serializeRequestForSignature(request)).toEqual(expectedResult) + }) + + it('Should correctly serialize the json for signature with raw hash for attrib related types', () => { + const request = { + name: 'John Doe', + age: 43, + operation: { + type: '100', + hash: 'cool hash', + dest: 54, + raw: 'string for hash', + }, + phones: ['1234567', '2345678', { rust: 5, age: 1 }, 3], + } + + const expectedResult = + 'age:43|name:John Doe|operation:dest:54|hash:46aa0c92129b33ee72ee1478d2ae62fa6e756869dedc6c858af3214a6fcf1904|raw:1dcd0759ce38f57049344a6b3c5fc18144fca1724713090c2ceeffa788c02711|type:100|phones:1234567,2345678,age:1|rust:5,3' + + expect(serializeRequestForSignature(request)).toEqual(expectedResult) + }) + + it('Should correctly serialize the json for signature with raw hash for non-attrib related types', () => { + const request = { + name: 'John Doe', + age: 43, + operation: { + type: '101', + hash: 'cool hash', + dest: 54, + raw: 'string for hash', + }, + phones: ['1234567', '2345678', { rust: 5, age: 1 }, 3], + } + + const expectedResult = + 'age:43|name:John Doe|operation:dest:54|hash:cool hash|raw:string for hash|type:101|phones:1234567,2345678,age:1|rust:5,3' + + expect(serializeRequestForSignature(request)).toEqual(expectedResult) + }) + + it('Should correctly serialize the json for signature with null signature', () => { + const request = { + signature: null, + } + + const expectedResult = '' + + expect(serializeRequestForSignature(request)).toEqual(expectedResult) + }) +}) diff --git a/packages/core/src/modules/ledger/__tests__/ledgerUtils.test.ts b/packages/indy-sdk/src/ledger/__tests__/util.test.ts similarity index 96% rename from packages/core/src/modules/ledger/__tests__/ledgerUtils.test.ts rename to packages/indy-sdk/src/ledger/__tests__/util.test.ts index ec33976a63..38976758ae 100644 --- a/packages/core/src/modules/ledger/__tests__/ledgerUtils.test.ts +++ b/packages/indy-sdk/src/ledger/__tests__/util.test.ts @@ -1,6 +1,6 @@ import type { LedgerRejectResponse, LedgerReqnackResponse } from 'indy-sdk' -import * as LedgerUtil from '../ledgerUtil' +import * as LedgerUtil from '../util' describe('LedgerUtils', () => { // IsLedgerRejectResponse diff --git a/packages/indy-sdk/src/ledger/error/IndySdkPoolError.ts b/packages/indy-sdk/src/ledger/error/IndySdkPoolError.ts new file mode 100644 index 0000000000..fa6679d789 --- /dev/null +++ b/packages/indy-sdk/src/ledger/error/IndySdkPoolError.ts @@ -0,0 +1,7 @@ +import { AriesFrameworkError } from '@aries-framework/core' + +export class IndySdkPoolError extends AriesFrameworkError { + public constructor(message: string, { cause }: { cause?: Error } = {}) { + super(message, { cause }) + } +} diff --git a/packages/indy-sdk/src/ledger/error/IndySdkPoolNotConfiguredError.ts b/packages/indy-sdk/src/ledger/error/IndySdkPoolNotConfiguredError.ts new file mode 100644 index 0000000000..91cd3c7199 --- /dev/null +++ b/packages/indy-sdk/src/ledger/error/IndySdkPoolNotConfiguredError.ts @@ -0,0 +1,7 @@ +import { IndySdkPoolError } from './IndySdkPoolError' + +export class IndySdkPoolNotConfiguredError extends IndySdkPoolError { + public constructor(message: string, { cause }: { cause?: Error } = {}) { + super(message, { cause }) + } +} diff --git a/packages/indy-sdk/src/ledger/error/IndySdkPoolNotFoundError.ts b/packages/indy-sdk/src/ledger/error/IndySdkPoolNotFoundError.ts new file mode 100644 index 0000000000..4977428cba --- /dev/null +++ b/packages/indy-sdk/src/ledger/error/IndySdkPoolNotFoundError.ts @@ -0,0 +1,7 @@ +import { IndySdkPoolError } from './IndySdkPoolError' + +export class IndySdkPoolNotFoundError extends IndySdkPoolError { + public constructor(message: string, { cause }: { cause?: Error } = {}) { + super(message, { cause }) + } +} diff --git a/packages/indy-sdk/src/ledger/error/index.ts b/packages/indy-sdk/src/ledger/error/index.ts new file mode 100644 index 0000000000..e2554abbdf --- /dev/null +++ b/packages/indy-sdk/src/ledger/error/index.ts @@ -0,0 +1,3 @@ +export * from './IndySdkPoolError' +export * from './IndySdkPoolNotConfiguredError' +export * from './IndySdkPoolNotFoundError' diff --git a/packages/indy-sdk/src/ledger/index.ts b/packages/indy-sdk/src/ledger/index.ts new file mode 100644 index 0000000000..fe016abcec --- /dev/null +++ b/packages/indy-sdk/src/ledger/index.ts @@ -0,0 +1,2 @@ +export * from './IndySdkPool' +export * from './IndySdkPoolService' diff --git a/packages/indy-sdk/src/ledger/serializeRequestForSignature.ts b/packages/indy-sdk/src/ledger/serializeRequestForSignature.ts new file mode 100644 index 0000000000..630dcbab2b --- /dev/null +++ b/packages/indy-sdk/src/ledger/serializeRequestForSignature.ts @@ -0,0 +1,61 @@ +import { Hasher, TypedArrayEncoder } from '@aries-framework/core' + +const ATTRIB_TYPE = '100' +const GET_ATTR_TYPE = '104' + +/// Generate the normalized form of a ledger transaction request for signing +export function serializeRequestForSignature(v: any): string { + const type = v?.operation?.type + + return _serializeRequestForSignature(v, true, type != undefined ? `${type}` : undefined) +} + +/** + * Serialize an indy ledger request object for signing input. Based on the rust code. Indy SDK requires ledger requests to be signed using + * a did, however in AFJ's the wallet only creates keys, and we create custom did records. This allows us to remove the legacy createDid and + * publicDidSeed properties from the wallet, as we create the request payload ourselves. + * + * @see https://github.com/hyperledger/indy-shared-rs/blob/6af1e939586d1f16341dc03b62970cf28b32d118/indy-utils/src/txn_signature.rs#L10 + */ +function _serializeRequestForSignature(v: any, isTopLevel: boolean, _type?: string): string { + const vType = typeof v + + if (vType === 'boolean') return v ? 'True' : 'False' + if (vType === 'number') return v.toString() + if (vType === 'string') return v + + if (vType === 'object') { + if (Array.isArray(v)) { + return v.map((element) => _serializeRequestForSignature(element, false, _type)).join(',') + } + + let result = '' + let inMiddle = false + + for (const vKey of Object.keys(v).sort()) { + // Skip signature field at top level as in python code + if (isTopLevel && (vKey == 'signature' || vKey == 'fees' || vKey == 'signatures')) { + continue + } + + if (inMiddle) { + result += '|' + } + + let value = v[vKey] + if ((_type == ATTRIB_TYPE || _type == GET_ATTR_TYPE) && (vKey == 'raw' || vKey == 'hash' || vKey == 'enc')) { + // do it only for attribute related request + if (typeof value !== 'string') throw new Error('Value must be a string for hash') + const hash = Hasher.hash(TypedArrayEncoder.fromString(value), 'sha2-256') + value = Buffer.from(hash).toString('hex') + } + + result = `${result}${vKey}:${_serializeRequestForSignature(value, false, _type)}` + inMiddle = true + } + + return result + } + + return '' +} diff --git a/packages/indy-sdk/src/ledger/util.ts b/packages/indy-sdk/src/ledger/util.ts new file mode 100644 index 0000000000..d7b5fc2076 --- /dev/null +++ b/packages/indy-sdk/src/ledger/util.ts @@ -0,0 +1,9 @@ +import type { LedgerResponse, LedgerRejectResponse, LedgerReqnackResponse } from 'indy-sdk' + +export function isLedgerRejectResponse(response: LedgerResponse): response is LedgerRejectResponse { + return response.op === 'REJECT' +} + +export function isLedgerReqnackResponse(response: LedgerResponse): response is LedgerReqnackResponse { + return response.op === 'REQNACK' +} diff --git a/packages/core/src/storage/IndyStorageService.ts b/packages/indy-sdk/src/storage/IndySdkStorageService.ts similarity index 77% rename from packages/core/src/storage/IndyStorageService.ts rename to packages/indy-sdk/src/storage/IndySdkStorageService.ts index a66cb579fd..bfcb740d79 100644 --- a/packages/core/src/storage/IndyStorageService.ts +++ b/packages/indy-sdk/src/storage/IndySdkStorageService.ts @@ -1,29 +1,31 @@ -import type { AgentContext } from '../agent' -import type { IndyWallet } from '../wallet/IndyWallet' -import type { BaseRecord, TagsBase } from './BaseRecord' -import type { BaseRecordConstructor, Query, StorageService } from './StorageService' -import type { default as Indy, WalletQuery, WalletRecord, WalletSearchOptions } from 'indy-sdk' - -import { AgentDependencies } from '../agent/AgentDependencies' -import { InjectionSymbols } from '../constants' -import { IndySdkError, RecordDuplicateError, RecordNotFoundError } from '../error' -import { injectable, inject } from '../plugins' -import { JsonTransformer } from '../utils/JsonTransformer' -import { isIndyError } from '../utils/indyError' -import { isBoolean } from '../utils/type' -import { assertIndyWallet } from '../wallet/util/assertIndyWallet' +import type { IndySdkWallet } from '../wallet/IndySdkWallet' +import type { + BaseRecordConstructor, + AgentContext, + BaseRecord, + TagsBase, + Query, + StorageService, +} from '@aries-framework/core' +import type { WalletQuery, WalletRecord, WalletSearchOptions } from 'indy-sdk' + +import { RecordDuplicateError, RecordNotFoundError, injectable, inject, JsonTransformer } from '@aries-framework/core' + +import { isIndyError, IndySdkError } from '../error' +import { IndySdk, IndySdkSymbol } from '../types' +import { assertIndySdkWallet } from '../utils/assertIndySdkWallet' @injectable() -export class IndyStorageService implements StorageService { - private indy: typeof Indy +export class IndySdkStorageService implements StorageService { + private indySdk: IndySdk private static DEFAULT_QUERY_OPTIONS = { retrieveType: true, retrieveTags: true, } - public constructor(@inject(InjectionSymbols.AgentDependencies) agentDependencies: AgentDependencies) { - this.indy = agentDependencies.indy + public constructor(@inject(IndySdkSymbol) indySdk: IndySdk) { + this.indySdk = indySdk } private transformToRecordTagValues(tags: { [key: number]: string | undefined }): TagsBase { @@ -72,7 +74,7 @@ export class IndyStorageService implements StorageService< } // If the value is a boolean use the indy // '1' or '0' syntax - else if (isBoolean(value)) { + else if (typeof value === 'boolean') { transformedTags[key] = value ? '1' : '0' } // If the value is 1 or 0, we need to add something to the value, otherwise @@ -135,13 +137,15 @@ export class IndyStorageService implements StorageService< /** @inheritDoc */ public async save(agentContext: AgentContext, record: T) { - assertIndyWallet(agentContext.wallet) + assertIndySdkWallet(agentContext.wallet) + + record.updatedAt = new Date() const value = JsonTransformer.serialize(record) const tags = this.transformFromRecordTagValues(record.getTags()) as Record try { - await this.indy.addWalletRecord(agentContext.wallet.handle, record.type, record.id, value, tags) + await this.indySdk.addWalletRecord(agentContext.wallet.handle, record.type, record.id, value, tags) } catch (error) { // Record already exists if (isIndyError(error, 'WalletItemAlreadyExists')) { @@ -154,14 +158,16 @@ export class IndyStorageService implements StorageService< /** @inheritDoc */ public async update(agentContext: AgentContext, record: T): Promise { - assertIndyWallet(agentContext.wallet) + assertIndySdkWallet(agentContext.wallet) + + record.updatedAt = new Date() const value = JsonTransformer.serialize(record) const tags = this.transformFromRecordTagValues(record.getTags()) as Record try { - await this.indy.updateWalletRecordValue(agentContext.wallet.handle, record.type, record.id, value) - await this.indy.updateWalletRecordTags(agentContext.wallet.handle, record.type, record.id, tags) + await this.indySdk.updateWalletRecordValue(agentContext.wallet.handle, record.type, record.id, value) + await this.indySdk.updateWalletRecordTags(agentContext.wallet.handle, record.type, record.id, tags) } catch (error) { // Record does not exist if (isIndyError(error, 'WalletItemNotFound')) { @@ -177,10 +183,10 @@ export class IndyStorageService implements StorageService< /** @inheritDoc */ public async delete(agentContext: AgentContext, record: T) { - assertIndyWallet(agentContext.wallet) + assertIndySdkWallet(agentContext.wallet) try { - await this.indy.deleteWalletRecord(agentContext.wallet.handle, record.type, record.id) + await this.indySdk.deleteWalletRecord(agentContext.wallet.handle, record.type, record.id) } catch (error) { // Record does not exist if (isIndyError(error, 'WalletItemNotFound')) { @@ -200,10 +206,10 @@ export class IndyStorageService implements StorageService< recordClass: BaseRecordConstructor, id: string ): Promise { - assertIndyWallet(agentContext.wallet) + assertIndySdkWallet(agentContext.wallet) try { - await this.indy.deleteWalletRecord(agentContext.wallet.handle, recordClass.type, id) + await this.indySdk.deleteWalletRecord(agentContext.wallet.handle, recordClass.type, id) } catch (error) { if (isIndyError(error, 'WalletItemNotFound')) { throw new RecordNotFoundError(`record with id ${id} not found.`, { @@ -218,14 +224,14 @@ export class IndyStorageService implements StorageService< /** @inheritDoc */ public async getById(agentContext: AgentContext, recordClass: BaseRecordConstructor, id: string): Promise { - assertIndyWallet(agentContext.wallet) + assertIndySdkWallet(agentContext.wallet) try { - const record = await this.indy.getWalletRecord( + const record = await this.indySdk.getWalletRecord( agentContext.wallet.handle, recordClass.type, id, - IndyStorageService.DEFAULT_QUERY_OPTIONS + IndySdkStorageService.DEFAULT_QUERY_OPTIONS ) return this.recordToInstance(record, recordClass) } catch (error) { @@ -242,13 +248,13 @@ export class IndyStorageService implements StorageService< /** @inheritDoc */ public async getAll(agentContext: AgentContext, recordClass: BaseRecordConstructor): Promise { - assertIndyWallet(agentContext.wallet) + assertIndySdkWallet(agentContext.wallet) const recordIterator = this.search( agentContext.wallet, recordClass.type, {}, - IndyStorageService.DEFAULT_QUERY_OPTIONS + IndySdkStorageService.DEFAULT_QUERY_OPTIONS ) const records = [] for await (const record of recordIterator) { @@ -263,7 +269,7 @@ export class IndyStorageService implements StorageService< recordClass: BaseRecordConstructor, query: Query ): Promise { - assertIndyWallet(agentContext.wallet) + assertIndySdkWallet(agentContext.wallet) const indyQuery = this.indyQueryFromSearchQuery(query) @@ -271,7 +277,7 @@ export class IndyStorageService implements StorageService< agentContext.wallet, recordClass.type, indyQuery, - IndyStorageService.DEFAULT_QUERY_OPTIONS + IndySdkStorageService.DEFAULT_QUERY_OPTIONS ) const records = [] for await (const record of recordIterator) { @@ -281,15 +287,15 @@ export class IndyStorageService implements StorageService< } private async *search( - wallet: IndyWallet, + wallet: IndySdkWallet, type: string, query: WalletQuery, { limit = Infinity, ...options }: WalletSearchOptions & { limit?: number } ) { try { - const searchHandle = await this.indy.openWalletSearch(wallet.handle, type, query, options) + const searchHandle = await this.indySdk.openWalletSearch(wallet.handle, type, query, options) - let records: Indy.WalletRecord[] = [] + let records: WalletRecord[] = [] // Allow max of 256 per fetch operation const chunk = limit ? Math.min(256, limit) : 256 @@ -297,7 +303,7 @@ export class IndyStorageService implements StorageService< // Loop while limit not reached (or no limit specified) while (!limit || records.length < limit) { // Retrieve records - const recordsJson = await this.indy.fetchWalletSearchNextRecords(wallet.handle, searchHandle, chunk) + const recordsJson = await this.indySdk.fetchWalletSearchNextRecords(wallet.handle, searchHandle, chunk) if (recordsJson.records) { records = [...records, ...recordsJson.records] @@ -310,7 +316,7 @@ export class IndyStorageService implements StorageService< // If the number of records returned is less than chunk // It means we reached the end of the iterator (no more records) if (!records.length || !recordsJson.records || recordsJson.records.length < chunk) { - await this.indy.closeWalletSearch(searchHandle) + await this.indySdk.closeWalletSearch(searchHandle) return } diff --git a/packages/core/src/storage/__tests__/IndyStorageService.test.ts b/packages/indy-sdk/src/storage/__tests__/IndySdkStorageService.test.ts similarity index 80% rename from packages/core/src/storage/__tests__/IndyStorageService.test.ts rename to packages/indy-sdk/src/storage/__tests__/IndySdkStorageService.test.ts index ad098576bf..155ae2d6dd 100644 --- a/packages/core/src/storage/__tests__/IndyStorageService.test.ts +++ b/packages/indy-sdk/src/storage/__tests__/IndySdkStorageService.test.ts @@ -1,32 +1,29 @@ -import type { AgentContext } from '../../agent' -import type { TagsBase } from '../BaseRecord' -import type * as Indy from 'indy-sdk' +import type { IndySdk } from '../../types' +import type { TagsBase } from '@aries-framework/core' -import { agentDependencies, getAgentConfig, getAgentContext } from '../../../tests/helpers' -import { KeyProviderRegistry } from '../../crypto/key-provider' -import { RecordDuplicateError, RecordNotFoundError } from '../../error' -import { IndyWallet } from '../../wallet/IndyWallet' -import { IndyStorageService } from '../IndyStorageService' +import { RecordDuplicateError, RecordNotFoundError, KeyProviderRegistry } from '@aries-framework/core' +import * as indySdk from 'indy-sdk' -import { TestRecord } from './TestRecord' +import { TestRecord } from '../../../../core/src/storage/__tests__/TestRecord' +import { getAgentConfig, getAgentContext } from '../../../../core/tests/helpers' +import { IndySdkWallet } from '../../wallet/IndySdkWallet' +import { IndySdkStorageService } from '../IndySdkStorageService' -describe('IndyStorageService', () => { - let wallet: IndyWallet - let indy: typeof Indy - let storageService: IndyStorageService - let agentContext: AgentContext +const agentConfig = getAgentConfig('IndySdkStorageServiceTest') +const wallet = new IndySdkWallet(indySdk, agentConfig.logger, new KeyProviderRegistry([])) +const agentContext = getAgentContext({ + wallet, + agentConfig, +}) + +const storageService = new IndySdkStorageService(indySdk) +const startDate = Date.now() + +describe('IndySdkStorageService', () => { beforeEach(async () => { - const agentConfig = getAgentConfig('IndyStorageServiceTest') - indy = agentConfig.agentDependencies.indy - wallet = new IndyWallet(agentConfig.agentDependencies, agentConfig.logger, new KeyProviderRegistry([])) - agentContext = getAgentContext({ - wallet, - agentConfig, - }) // eslint-disable-next-line @typescript-eslint/no-non-null-assertion await wallet.createAndOpen(agentConfig.walletConfig!) - storageService = new IndyStorageService(agentConfig.agentDependencies) }) afterEach(async () => { @@ -59,7 +56,7 @@ describe('IndyStorageService', () => { }, }) - const retrieveRecord = await indy.getWalletRecord(wallet.handle, record.type, record.id, { + const retrieveRecord = await indySdk.getWalletRecord(wallet.handle, record.type, record.id, { retrieveType: true, retrieveTags: true, }) @@ -76,7 +73,7 @@ describe('IndyStorageService', () => { }) it('should correctly transform tag values from string after retrieving', async () => { - await indy.addWalletRecord(wallet.handle, TestRecord.type, 'some-id', '{}', { + await indySdk.addWalletRecord(wallet.handle, TestRecord.type, 'some-id', '{}', { someBoolean: '1', someOtherBoolean: '0', someStringValue: 'string', @@ -113,6 +110,12 @@ describe('IndyStorageService', () => { expect(record).toEqual(found) }) + + it('After a save the record should have update the updatedAt property', async () => { + const time = startDate + const record = await insertRecord({ id: 'test-updatedAt' }) + expect(record.updatedAt?.getTime()).toBeGreaterThan(time) + }) }) describe('getById()', () => { @@ -151,6 +154,18 @@ describe('IndyStorageService', () => { const retrievedRecord = await storageService.getById(agentContext, TestRecord, record.id) expect(retrievedRecord).toEqual(record) }) + + it('After a record has been updated it should have updated the updatedAT property', async () => { + const time = startDate + const record = await insertRecord({ id: 'test-id' }) + + record.replaceTags({ ...record.getTags(), foo: 'bar' }) + record.foo = 'foobaz' + await storageService.update(agentContext, record) + + const retrievedRecord = await storageService.getById(agentContext, TestRecord, record.id) + expect(retrievedRecord.createdAt.getTime()).toBeGreaterThan(time) + }) }) describe('delete()', () => { @@ -239,16 +254,13 @@ describe('IndyStorageService', () => { it('correctly transforms an advanced query into a valid WQL query', async () => { const indySpy = jest.fn() - const storageServiceWithoutIndy = new IndyStorageService({ - ...agentDependencies, - indy: { - openWalletSearch: indySpy, - fetchWalletSearchNextRecords: jest.fn(() => ({ records: undefined })), - closeWalletSearch: jest.fn(), - } as unknown as typeof Indy, - }) + const storageServiceWithoutIndySdk = new IndySdkStorageService({ + openWalletSearch: indySpy, + fetchWalletSearchNextRecords: jest.fn(() => ({ records: undefined })), + closeWalletSearch: jest.fn(), + } as unknown as IndySdk) - await storageServiceWithoutIndy.findByQuery(agentContext, TestRecord, { + await storageServiceWithoutIndySdk.findByQuery(agentContext, TestRecord, { $and: [ { $or: [{ myTag: true }, { myTag: false }], diff --git a/packages/indy-sdk/src/storage/index.ts b/packages/indy-sdk/src/storage/index.ts new file mode 100644 index 0000000000..ff59756cfa --- /dev/null +++ b/packages/indy-sdk/src/storage/index.ts @@ -0,0 +1 @@ +export * from './IndySdkStorageService' diff --git a/packages/indy-sdk/src/types.ts b/packages/indy-sdk/src/types.ts new file mode 100644 index 0000000000..f6ac41c161 --- /dev/null +++ b/packages/indy-sdk/src/types.ts @@ -0,0 +1,6 @@ +import type { default as _IndySdk } from 'indy-sdk' + +type IndySdk = typeof _IndySdk + +export const IndySdkSymbol = Symbol('IndySdk') +export type { IndySdk } diff --git a/packages/indy-sdk/src/utils/__tests__/did.test.ts b/packages/indy-sdk/src/utils/__tests__/did.test.ts new file mode 100644 index 0000000000..222f9898fd --- /dev/null +++ b/packages/indy-sdk/src/utils/__tests__/did.test.ts @@ -0,0 +1,77 @@ +import { isAbbreviatedVerkey, isFullVerkey, isLegacySelfCertifiedDid } from '../did' + +const validAbbreviatedVerkeys = [ + '~PKAYz8Ev4yoQgr2LaMAWFx', + '~Soy1augaQrQYtNZRRHsikB', + '~BUF7uxYTxZ6qYdZ4G9e1Gi', + '~DbZ4gkBqhFRVsT5P7BJqyZ', + '~4zmNTdG78iYyMAQdEQLrf8', +] + +const invalidAbbreviatedVerkeys = [ + '6YnVN5Qdb6mqimTRQcQmSXrHXKdTEdRn5YHZReezUTvt', + '8jG2Bim1HNSybCTdKBRppP4PCQSSijx1pBnreqsdo8JG', + 'ABUF7uxYTxZ6qYdZ4G9e1Gi', + '~Db3IgkBqhFRVsT5P7BJqyZ', + '~4zmNTlG78iYyMAQdEQLrf8', +] + +const validFullVerkeys = [ + '6YnVN5Qdb6mqimTRQcQmSXrHXKdTEdRn5YHZReezUTvt', + '8jG2Bim1HNSybCTdKBRppP4PCQSSijx1pBnreqsdo8JG', + '9wMLhw9SSxtTUyosrndMbvWY4TtDbVvRnMtzG2NysniP', + '6m2XT39vivJ7tLSxNPM8siMnhYCZcdMxbkTcJDSzAQTu', + 'CAgL85iEecPNQMmxQ1hgbqczwq7SAerQ8RbWTRtC7SoK', + 'MqXmB7cTsTXqyxDPBbrgu5EPqw61kouK1qjMvnoPa96', +] + +const invalidFullVerkeys = [ + '~PKAYz8Ev4yoQgr2LaMAWFx', + '~Soy1augaQrQYtNZRRHsikB', + '6YnVN5Qdb6mqimTRQcQmSXrHXKdTEdRn5YHZReezUTvta', + '6m2XT39vIvJ7tLSxNPM8siMnhYCZcdMxbkTcJDSzAQTu', + 'CAgL85iEecPNQMlxQ1hgbqczwq7SAerQ8RbWTRtC7SoK', +] + +describe('Utils | Did', () => { + describe('isSelfCertifiedDid()', () => { + test('returns true if the verkey is abbreviated', () => { + expect(isLegacySelfCertifiedDid('PW8ZHpNupeWXbmpPWog6Ki', '~QQ5jiH1dgXPAnvHdJvazn9')).toBe(true) + }) + + test('returns true if the verkey is not abbreviated and the did is generated from the verkey', () => { + expect(isLegacySelfCertifiedDid('Y8q4Aq6gRAcmB6jjKk3Z7t', 'HyEoPRNvC7q4jj5joUo8AWYtxbNccbEnTAeuMYkpmNS2')).toBe( + true + ) + }) + + test('returns false if the verkey is not abbreviated and the did is not generated from the verkey', () => { + expect(isLegacySelfCertifiedDid('Y8q4Aq6gRAcmB6jjKk3Z7t', 'AcU7DnRqoXGYATD6VqsRq4eHuz55gdM3uzFBEhFd6rGh')).toBe( + false + ) + }) + }) + + describe('isAbbreviatedVerkey()', () => { + test.each(validAbbreviatedVerkeys)('returns true when valid abbreviated verkey "%s" is passed in', (verkey) => { + expect(isAbbreviatedVerkey(verkey)).toBe(true) + }) + + test.each(invalidAbbreviatedVerkeys)( + 'returns false when invalid abbreviated verkey "%s" is passed in', + (verkey) => { + expect(isAbbreviatedVerkey(verkey)).toBe(false) + } + ) + }) + + describe('isFullVerkey()', () => { + test.each(validFullVerkeys)('returns true when valid full verkey "%s" is passed in', (verkey) => { + expect(isFullVerkey(verkey)).toBe(true) + }) + + test.each(invalidFullVerkeys)('returns false when invalid full verkey "%s" is passed in', (verkey) => { + expect(isFullVerkey(verkey)).toBe(false) + }) + }) +}) diff --git a/packages/indy-sdk/src/utils/assertIndySdkWallet.ts b/packages/indy-sdk/src/utils/assertIndySdkWallet.ts new file mode 100644 index 0000000000..0b1914555f --- /dev/null +++ b/packages/indy-sdk/src/utils/assertIndySdkWallet.ts @@ -0,0 +1,13 @@ +import type { Wallet } from '@aries-framework/core' + +import { AriesFrameworkError } from '@aries-framework/core' + +import { IndySdkWallet } from '../wallet/IndySdkWallet' + +export function assertIndySdkWallet(wallet: Wallet): asserts wallet is IndySdkWallet { + if (!(wallet instanceof IndySdkWallet)) { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const walletClassName = (wallet as any).constructor?.name ?? 'unknown' + throw new AriesFrameworkError(`Expected wallet to be instance of IndySdkWallet, found ${walletClassName}`) + } +} diff --git a/packages/indy-sdk/src/utils/did.ts b/packages/indy-sdk/src/utils/did.ts new file mode 100644 index 0000000000..afb080696f --- /dev/null +++ b/packages/indy-sdk/src/utils/did.ts @@ -0,0 +1,89 @@ +/** + * Based on DidUtils implementation in Aries Framework .NET + * @see: https://github.com/hyperledger/aries-framework-dotnet/blob/f90eaf9db8548f6fc831abea917e906201755763/src/Hyperledger.Aries/Utils/DidUtils.cs + * + * Some context about full verkeys versus abbreviated verkeys: + * A standard verkey is 32 bytes, and by default in Indy the DID is chosen as the first 16 bytes of that key, before base58 encoding. + * An abbreviated verkey replaces the first 16 bytes of the verkey with ~ when it matches the DID. + * + * When a full verkey is used to register on the ledger, this is stored as a full verkey on the ledger and also returned from the ledger as a full verkey. + * The same applies to an abbreviated verkey. If an abbreviated verkey is used to register on the ledger, this is stored as an abbreviated verkey on the ledger and also returned from the ledger as an abbreviated verkey. + * + * For this reason we need some methods to check whether verkeys are full or abbreviated, so we can align this with `indy.abbreviateVerkey` + * + * Aries Framework .NET also abbreviates verkey before sending to ledger: + * https://github.com/hyperledger/aries-framework-dotnet/blob/f90eaf9db8548f6fc831abea917e906201755763/src/Hyperledger.Aries/Ledger/DefaultLedgerService.cs#L139-L147 + */ + +import { Buffer, TypedArrayEncoder } from '@aries-framework/core' + +export const FULL_VERKEY_REGEX = /^[1-9A-HJ-NP-Za-km-z]{43,44}$/ +export const ABBREVIATED_VERKEY_REGEX = /^~[1-9A-HJ-NP-Za-km-z]{21,22}$/ + +/** + * Check whether the did is a self certifying did. If the verkey is abbreviated this method + * will always return true. Make sure that the verkey you pass in this method belongs to the + * did passed in + * + * @return Boolean indicating whether the did is self certifying + */ +export function isLegacySelfCertifiedDid(did: string, verkey: string): boolean { + // If the verkey is Abbreviated, it means the full verkey + // is the did + the verkey + if (isAbbreviatedVerkey(verkey)) { + return true + } + + const didFromVerkey = legacyIndyDidFromPublicKeyBase58(verkey) + + if (didFromVerkey === did) { + return true + } + + return false +} + +export function legacyIndyDidFromPublicKeyBase58(publicKeyBase58: string): string { + const buffer = TypedArrayEncoder.fromBase58(publicKeyBase58) + + const did = TypedArrayEncoder.toBase58(buffer.slice(0, 16)) + + return did +} + +export function getFullVerkey(did: string, verkey: string) { + if (isFullVerkey(verkey)) return verkey + + // Did could have did:xxx prefix, only take the last item after : + const id = did.split(':').pop() ?? did + // Verkey is prefixed with ~ if abbreviated + const verkeyWithoutTilde = verkey.slice(1) + + // Create base58 encoded public key (32 bytes) + return TypedArrayEncoder.toBase58( + Buffer.concat([ + // Take did identifier (16 bytes) + TypedArrayEncoder.fromBase58(id), + // Concat the abbreviated verkey (16 bytes) + TypedArrayEncoder.fromBase58(verkeyWithoutTilde), + ]) + ) +} + +/** + * Check a base58 encoded string against a regex expression to determine if it is a full valid verkey + * @param verkey Base58 encoded string representation of a verkey + * @return Boolean indicating if the string is a valid verkey + */ +export function isFullVerkey(verkey: string): boolean { + return FULL_VERKEY_REGEX.test(verkey) +} + +/** + * Check a base58 encoded string against a regex expression to determine if it is a valid abbreviated verkey + * @param verkey Base58 encoded string representation of an abbreviated verkey + * @returns Boolean indicating if the string is a valid abbreviated verkey + */ +export function isAbbreviatedVerkey(verkey: string): boolean { + return ABBREVIATED_VERKEY_REGEX.test(verkey) +} diff --git a/packages/indy-sdk/src/utils/promises.ts b/packages/indy-sdk/src/utils/promises.ts new file mode 100644 index 0000000000..0e843d73b5 --- /dev/null +++ b/packages/indy-sdk/src/utils/promises.ts @@ -0,0 +1,44 @@ +// This file polyfills the allSettled method introduced in ESNext + +export type AllSettledFulfilled = { + status: 'fulfilled' + value: T +} + +export type AllSettledRejected = { + status: 'rejected' + // eslint-disable-next-line @typescript-eslint/no-explicit-any + reason: any +} + +export function allSettled(promises: Promise[]) { + return Promise.all( + promises.map((p) => + p + .then( + (value) => + ({ + status: 'fulfilled', + value, + } as AllSettledFulfilled) + ) + .catch( + (reason) => + ({ + status: 'rejected', + reason, + } as AllSettledRejected) + ) + ) + ) +} + +export function onlyFulfilled(entries: Array | AllSettledRejected>) { + // We filter for only the rejected values, so we can safely cast the type + return entries.filter((e) => e.status === 'fulfilled') as AllSettledFulfilled[] +} + +export function onlyRejected(entries: Array | AllSettledRejected>) { + // We filter for only the rejected values, so we can safely cast the type + return entries.filter((e) => e.status === 'rejected') as AllSettledRejected[] +} diff --git a/packages/core/src/wallet/IndyWallet.ts b/packages/indy-sdk/src/wallet/IndySdkWallet.ts similarity index 64% rename from packages/core/src/wallet/IndyWallet.ts rename to packages/indy-sdk/src/wallet/IndySdkWallet.ts index 1143f44a28..b740484f84 100644 --- a/packages/core/src/wallet/IndyWallet.ts +++ b/packages/indy-sdk/src/wallet/IndySdkWallet.ts @@ -1,53 +1,65 @@ -import type { KeyPair } from '../crypto/key-provider/KeyProvider' -import type { EncryptedMessage } from '../didcomm/types' -import type { KeyDerivationMethod, WalletConfig, WalletConfigRekey, WalletExportImportConfig } from '../types' -import type { Buffer } from '../utils/buffer' import type { + Buffer, + EncryptedMessage, + KeyDerivationMethod, + KeyPair, + UnpackedMessageContext, + Wallet, + WalletConfig, + WalletConfigRekey, WalletCreateKeyOptions, - DidConfig, - DidInfo, + WalletExportImportConfig, + WalletPackOptions, WalletSignOptions, - UnpackedMessageContext, WalletVerifyOptions, - Wallet, -} from './Wallet' -import type { default as Indy, WalletStorageConfig } from 'indy-sdk' - +} from '@aries-framework/core' +// eslint-disable-next-line import/order +import type { OpenWalletCredentials, WalletConfig as IndySdkWalletConfig, WalletStorageConfig } from 'indy-sdk' +import { + AriesFrameworkError, + DidCommMessageVersion, + InjectionSymbols, + isDidCommV1EncryptedEnvelope, + isValidPrivateKey, + isValidSeed, + JsonEncoder, + Key, + KeyProviderRegistry, + KeyType, + Logger, + RecordNotFoundError, + TypedArrayEncoder, + WalletDuplicateError, + WalletError, + WalletExportPathExistsError, + WalletInvalidKeyError, + WalletKeyExistsError, + WalletNotFoundError, +} from '@aries-framework/core' import { inject, injectable } from 'tsyringe' -import { AgentDependencies } from '../agent/AgentDependencies' -import { InjectionSymbols } from '../constants' -import { KeyType } from '../crypto' -import { Key } from '../crypto/Key' -import { KeyProviderRegistry } from '../crypto/key-provider/KeyProviderRegistry' -import { AriesFrameworkError, IndySdkError, RecordDuplicateError, RecordNotFoundError } from '../error' -import { Logger } from '../logger' -import { TypedArrayEncoder } from '../utils' -import { JsonEncoder } from '../utils/JsonEncoder' -import { isError } from '../utils/error' -import { isIndyError } from '../utils/indyError' - -import { WalletDuplicateError, WalletError, WalletNotFoundError } from './error' -import { WalletInvalidKeyError } from './error/WalletInvalidKeyError' +import { IndySdkError, isIndyError } from '../error' +import { IndySdk, IndySdkSymbol } from '../types' + +const isError = (error: unknown): error is Error => error instanceof Error @injectable() -export class IndyWallet implements Wallet { +export class IndySdkWallet implements Wallet { private walletConfig?: WalletConfig private walletHandle?: number private logger: Logger private keyProviderRegistry: KeyProviderRegistry - private publicDidInfo: DidInfo | undefined - private indy: typeof Indy + private indySdk: IndySdk public constructor( - @inject(InjectionSymbols.AgentDependencies) agentDependencies: AgentDependencies, + @inject(IndySdkSymbol) indySdk: IndySdk, @inject(InjectionSymbols.Logger) logger: Logger, keyProviderRegistry: KeyProviderRegistry ) { this.logger = logger this.keyProviderRegistry = keyProviderRegistry - this.indy = agentDependencies.indy + this.indySdk = indySdk } public get isProvisioned() { @@ -58,16 +70,6 @@ export class IndyWallet implements Wallet { return this.walletHandle !== undefined } - /** - * @deprecated The public did functionality of the wallet has been deprecated in favour of the DidsModule, which can be - * used to create and resolve dids. Currently the global agent public did functionality is still used by the `LedgerModule`, but - * will be removed once the `LedgerModule` has been deprecated. Do not use this property for new functionality, but rather - * use the `DidsModule`. - */ - public get publicDid() { - return this.publicDidInfo - } - public get handle() { if (!this.walletHandle) { throw new AriesFrameworkError( @@ -78,16 +80,6 @@ export class IndyWallet implements Wallet { return this.walletHandle } - public get masterSecretId() { - if (!this.isInitialized || !(this.walletConfig?.id || this.walletConfig?.masterSecretId)) { - throw new AriesFrameworkError( - 'Wallet has not been initialized yet. Make sure to await agent.initialize() before using the agent.' - ) - } - - return this.walletConfig?.masterSecretId ?? this.walletConfig.id - } - /** * Dispose method is called when an agent context is disposed. */ @@ -97,8 +89,8 @@ export class IndyWallet implements Wallet { } } - private walletStorageConfig(walletConfig: WalletConfig): Indy.WalletConfig { - const walletStorageConfig: Indy.WalletConfig = { + private walletStorageConfig(walletConfig: WalletConfig): IndySdkWalletConfig { + const walletStorageConfig: IndySdkWalletConfig = { id: walletConfig.id, storage_type: walletConfig.storage?.type, } @@ -114,8 +106,8 @@ export class IndyWallet implements Wallet { walletConfig: WalletConfig, rekey?: string, rekeyDerivation?: KeyDerivationMethod - ): Indy.OpenWalletCredentials { - const walletCredentials: Indy.OpenWalletCredentials = { + ): OpenWalletCredentials { + const walletCredentials: OpenWalletCredentials = { key: walletConfig.key, key_derivation_method: walletConfig.keyDerivationMethod, } @@ -149,29 +141,22 @@ export class IndyWallet implements Wallet { this.logger.debug(`Creating wallet '${walletConfig.id}' using SQLite storage`) try { - await this.indy.createWallet(this.walletStorageConfig(walletConfig), this.walletCredentials(walletConfig)) + await this.indySdk.createWallet(this.walletStorageConfig(walletConfig), this.walletCredentials(walletConfig)) this.walletConfig = walletConfig - // We usually want to create master secret only once, therefore, we can to do so when creating a wallet. await this.open(walletConfig) - - // We need to open wallet before creating master secret because we need wallet handle here. - await this.createMasterSecret(this.handle, this.masterSecretId) } catch (error) { - // If an error ocurred while creating the master secret, we should close the wallet - if (this.isInitialized) await this.close() - if (isIndyError(error, 'WalletAlreadyExistsError')) { const errorMessage = `Wallet '${walletConfig.id}' already exists` this.logger.debug(errorMessage) throw new WalletDuplicateError(errorMessage, { - walletType: 'IndyWallet', + walletType: 'IndySdkWallet', cause: error, }) } else { if (!isError(error)) { - throw new AriesFrameworkError('Attempted to throw error, but it was not of type Error') + throw new AriesFrameworkError('Attempted to throw error, but it was not of type Error', { cause: error }) } const errorMessage = `Error creating wallet '${walletConfig.id}'` this.logger.error(errorMessage, { @@ -229,7 +214,7 @@ export class IndyWallet implements Wallet { } try { - this.walletHandle = await this.indy.openWallet( + this.walletHandle = await this.indySdk.openWallet( this.walletStorageConfig(walletConfig), this.walletCredentials(walletConfig, rekey, rekeyDerivation) ) @@ -244,19 +229,19 @@ export class IndyWallet implements Wallet { this.logger.debug(errorMessage) throw new WalletNotFoundError(errorMessage, { - walletType: 'IndyWallet', + walletType: 'IndySdkWallet', cause: error, }) } else if (isIndyError(error, 'WalletAccessFailed')) { const errorMessage = `Incorrect key for wallet '${walletConfig.id}'` this.logger.debug(errorMessage) throw new WalletInvalidKeyError(errorMessage, { - walletType: 'IndyWallet', + walletType: 'IndySdkWallet', cause: error, }) } else { if (!isError(error)) { - throw new AriesFrameworkError('Attempted to throw error, but it was not of type Error') + throw new AriesFrameworkError('Attempted to throw error, but it was not of type Error', { cause: error }) } const errorMessage = `Error opening wallet '${walletConfig.id}': ${error.message}` this.logger.error(errorMessage, { @@ -289,7 +274,7 @@ export class IndyWallet implements Wallet { } try { - await this.indy.deleteWallet( + await this.indySdk.deleteWallet( this.walletStorageConfig(this.walletConfig), this.walletCredentials(this.walletConfig) ) @@ -299,12 +284,12 @@ export class IndyWallet implements Wallet { this.logger.debug(errorMessage) throw new WalletNotFoundError(errorMessage, { - walletType: 'IndyWallet', + walletType: 'IndySdkWallet', cause: error, }) } else { if (!isError(error)) { - throw new AriesFrameworkError('Attempted to throw error, but it was not of type Error') + throw new AriesFrameworkError('Attempted to throw error, but it was not of type Error', { cause: error }) } const errorMessage = `Error deleting wallet '${this.walletConfig.id}': ${error.message}` this.logger.error(errorMessage, { @@ -320,11 +305,20 @@ export class IndyWallet implements Wallet { public async export(exportConfig: WalletExportImportConfig) { try { this.logger.debug(`Exporting wallet ${this.walletConfig?.id} to path ${exportConfig.path}`) - await this.indy.exportWallet(this.handle, exportConfig) + await this.indySdk.exportWallet(this.handle, exportConfig) } catch (error) { if (!isError(error)) { - throw new AriesFrameworkError('Attempted to throw error, but it was not of type Error') + throw new AriesFrameworkError('Attempted to throw error, but it was not of type Error', { cause: error }) } + + // Export path already exists + if (isIndyError(error, 'CommonIOError')) { + throw new WalletExportPathExistsError( + `Unable to create export, wallet export at path '${exportConfig.path}' already exists`, + { cause: error } + ) + } + const errorMessage = `Error exporting wallet: ${error.message}` this.logger.error(errorMessage, { error, @@ -337,14 +331,14 @@ export class IndyWallet implements Wallet { public async import(walletConfig: WalletConfig, importConfig: WalletExportImportConfig) { try { this.logger.debug(`Importing wallet ${walletConfig.id} from path ${importConfig.path}`) - await this.indy.importWallet( + await this.indySdk.importWallet( { id: walletConfig.id }, { key: walletConfig.key, key_derivation_method: walletConfig.keyDerivationMethod }, importConfig ) } catch (error) { if (!isError(error)) { - throw new AriesFrameworkError('Attempted to throw error, but it was not of type Error') + throw new AriesFrameworkError('Attempted to throw error, but it was not of type Error', { cause: error }) } const errorMessage = `Error importing wallet': ${error.message}` this.logger.error(errorMessage, { @@ -365,9 +359,8 @@ export class IndyWallet implements Wallet { } try { - await this.indy.closeWallet(this.walletHandle) + await this.indySdk.closeWallet(this.walletHandle) this.walletHandle = undefined - this.publicDidInfo = undefined } catch (error) { if (isIndyError(error, 'WalletInvalidHandle')) { const errorMessage = `Error closing wallet: wallet already closed` @@ -378,7 +371,7 @@ export class IndyWallet implements Wallet { }) } else { if (!isError(error)) { - throw new AriesFrameworkError('Attempted to throw error, but it was not of type Error') + throw new AriesFrameworkError('Attempted to throw error, but it was not of type Error', { cause: error }) } const errorMessage = `Error closing wallet': ${error.message}` this.logger.error(errorMessage, { @@ -392,113 +385,74 @@ export class IndyWallet implements Wallet { } /** - * Create master secret with specified id in currently opened wallet. - * - * If a master secret by this id already exists in the current wallet, the method - * will return without doing anything. + * Create a key with an optional private key and keyType. + * The keypair is also automatically stored in the wallet afterwards * - * @throws {WalletError} if an error occurs + * Bls12381g1g2 and X25519 are not supported. */ - private async createMasterSecret(walletHandle: number, masterSecretId: string): Promise { - this.logger.debug(`Creating master secret with id '${masterSecretId}' in wallet with handle '${walletHandle}'`) - + public async createKey({ seed, privateKey, keyType }: WalletCreateKeyOptions): Promise { try { - await this.indy.proverCreateMasterSecret(walletHandle, masterSecretId) - - return masterSecretId - } catch (error) { - if (isIndyError(error, 'AnoncredsMasterSecretDuplicateNameError')) { - // master secret id is the same as the master secret id passed in the create function - // so if it already exists we can just assign it. - this.logger.debug( - `Master secret with id '${masterSecretId}' already exists in wallet with handle '${walletHandle}'`, - { - indyError: 'AnoncredsMasterSecretDuplicateNameError', - } - ) - - return masterSecretId - } else { - if (!isIndyError(error)) { - throw new AriesFrameworkError('Attempted to throw Indy error, but it was not an Indy error') - } - - this.logger.error(`Error creating master secret with id ${masterSecretId}`, { - indyError: error.indyName, - error, - }) - - throw new WalletError( - `Error creating master secret with id ${masterSecretId} in wallet with handle '${walletHandle}'`, - { cause: error } - ) + if (seed && privateKey) { + throw new WalletError('Only one of seed and privateKey can be set') } - } - } - - /** - * @deprecated The public did functionality of the wallet has been deprecated in favour of the DidsModule, which can be - * used to create and resolve dids. Currently the global agent public did functionality is still used by the `LedgerModule`, but - * will be removed once the `LedgerModule` has been deprecated. Do not use this property for new functionality, but rather - * use the `DidsModule`. - */ - public async initPublicDid(didConfig: DidConfig) { - // The Indy SDK cannot use a key to sign a request for the ledger. This is the only place where we need to call createDid - try { - const [did, verkey] = await this.indy.createAndStoreMyDid(this.handle, didConfig || {}) - this.publicDidInfo = { - did, - verkey, + if (seed && !isValidSeed(seed, keyType)) { + throw new WalletError('Invalid seed provided') } - } catch (error) { - if (!isError(error)) { - throw new AriesFrameworkError('Attempted to throw error, but it was not of type Error') + + if (privateKey && !isValidPrivateKey(privateKey, keyType)) { + throw new WalletError('Invalid private key provided') } - throw new WalletError('Error creating Did', { cause: error }) - } - } - /** - * Create a key with an optional seed and keyType. - * The keypair is also automatically stored in the wallet afterwards - * - * Bls12381g1g2 and X25519 are not supported. - * - * @param seed string The seed for creating a key - * @param keyType KeyType the type of key that should be created - * - * @returns a Key instance with a publicKeyBase58 - * - * @throws {WalletError} When an unsupported keytype is requested - * @throws {WalletError} When the key could not be created - */ - public async createKey({ seed, keyType }: WalletCreateKeyOptions): Promise { - try { // Ed25519 is supported natively in Indy wallet if (keyType === KeyType.Ed25519) { - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - //@ts-ignore - const verkey = await this.indy.createKey(this.handle, { seed, crypto_type: 'ed25519' }) - return Key.fromPublicKeyBase58(verkey, keyType) + if (seed) { + throw new WalletError( + 'IndySdkWallet does not support seed. You may rather want to specify a private key for deterministic ed25519 key generation' + ) + } + try { + const verkey = await this.indySdk.createKey(this.handle, { + seed: privateKey?.toString(), + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + //@ts-ignore + crypto_type: 'ed25519', + }) + + return Key.fromPublicKeyBase58(verkey, keyType) + } catch (error) { + // Handle case where key already exists + if (isIndyError(error, 'WalletItemAlreadyExists')) { + throw new WalletKeyExistsError('Key already exists') + } + + // Otherwise re-throw error + throw error + } } // Check if there is a signing key provider for the specified key type. if (this.keyProviderRegistry.hasProviderForKeyType(keyType)) { const signingKeyProvider = this.keyProviderRegistry.getProviderForKeyType(keyType) - const keyPair = await signingKeyProvider.createKeyPair({ seed }) + const keyPair = await signingKeyProvider.createKeyPair({ seed, privateKey }) await this.storeKeyPair(keyPair) return Key.fromPublicKeyBase58(keyPair.publicKeyBase58, keyType) } } catch (error) { + // If already instance of `WalletError`, re-throw + if (error instanceof WalletError) throw error + if (!isError(error)) { - throw new AriesFrameworkError('Attempted to throw error, but it was not of type Error') + throw new AriesFrameworkError(`Attempted to throw error, but it was not of type Error: ${error}`, { + cause: error, + }) } + throw new WalletError(`Error creating key with key type '${keyType}': ${error.message}`, { cause: error }) } - throw new WalletError(`Unsupported key type: '${keyType}' for wallet IndyWallet`) + throw new WalletError(`Unsupported key type: '${keyType}' for wallet IndySdkWallet`) } /** @@ -519,14 +473,15 @@ export class IndyWallet implements Wallet { if (!TypedArrayEncoder.isTypedArray(data)) { throw new WalletError(`${KeyType.Ed25519} does not support multiple singing of multiple messages`) } - return await this.indy.cryptoSign(this.handle, key.publicKeyBase58, data as Buffer) + return await this.indySdk.cryptoSign(this.handle, key.publicKeyBase58, data as Buffer) } // Check if there is a signing key provider for the specified key type. if (this.keyProviderRegistry.hasProviderForKeyType(key.keyType)) { - const keyProvider = this.keyProviderRegistry.getProviderForKeyType(key.keyType) + const signingKeyProvider = this.keyProviderRegistry.getProviderForKeyType(key.keyType) + const keyPair = await this.retrieveKeyPair(key.publicKeyBase58) - const signed = await keyProvider.sign({ + const signed = await signingKeyProvider.sign({ data, privateKeyBase58: keyPair.privateKeyBase58, publicKeyBase58: key.publicKeyBase58, @@ -536,7 +491,7 @@ export class IndyWallet implements Wallet { } } catch (error) { if (!isError(error)) { - throw new AriesFrameworkError('Attempted to throw error, but it was not of type Error') + throw new AriesFrameworkError('Attempted to throw error, but it was not of type Error', { cause: error }) } throw new WalletError(`Error signing data with verkey ${key.publicKeyBase58}`, { cause: error }) } @@ -565,13 +520,14 @@ export class IndyWallet implements Wallet { if (!TypedArrayEncoder.isTypedArray(data)) { throw new WalletError(`${KeyType.Ed25519} does not support multiple singing of multiple messages`) } - return await this.indy.cryptoVerify(key.publicKeyBase58, data as Buffer, signature) + return await this.indySdk.cryptoVerify(key.publicKeyBase58, data as Buffer, signature) } // Check if there is a signing key provider for the specified key type. if (this.keyProviderRegistry.hasProviderForKeyType(key.keyType)) { - const keyProvider = this.keyProviderRegistry.getProviderForKeyType(key.keyType) - const signed = await keyProvider.verify({ + const signingKeyProvider = this.keyProviderRegistry.getProviderForKeyType(key.keyType) + + const signed = await signingKeyProvider.verify({ data, signature, publicKeyBase58: key.publicKeyBase58, @@ -581,7 +537,7 @@ export class IndyWallet implements Wallet { } } catch (error) { if (!isError(error)) { - throw new AriesFrameworkError('Attempted to throw error, but it was not of type Error') + throw new AriesFrameworkError('Attempted to throw error, but it was not of type Error', { cause: error }) } throw new WalletError(`Error verifying signature of data signed with verkey ${key.publicKeyBase58}`, { cause: error, @@ -590,35 +546,54 @@ export class IndyWallet implements Wallet { throw new WalletError(`Unsupported keyType: ${key.keyType}`) } - public async pack( - payload: Record, - recipientKeys: string[], - senderVerkey?: string - ): Promise { + public async pack(payload: Record, params: WalletPackOptions): Promise { + if (params.didCommVersion === DidCommMessageVersion.V1) { + return this.packDidCommV1(payload, params) + } + if (params.didCommVersion === DidCommMessageVersion.V2) { + throw new AriesFrameworkError(`DidComm V2 message encryption is not supported for Indy wallet`) + } + throw new AriesFrameworkError(`Unsupported DidComm version: ${params.didCommVersion}`) + } + + private async packDidCommV1(payload: Record, params: WalletPackOptions): Promise { try { const messageRaw = JsonEncoder.toBuffer(payload) - const packedMessage = await this.indy.packMessage(this.handle, messageRaw, recipientKeys, senderVerkey ?? null) + const recipientKeys = params.recipientKeys.map((recipientKey) => recipientKey.publicKeyBase58) + const senderKey = params.senderKey?.publicKeyBase58 ?? null + const packedMessage = await this.indySdk.packMessage(this.handle, messageRaw, recipientKeys, senderKey) return JsonEncoder.fromBuffer(packedMessage) } catch (error) { if (!isError(error)) { - throw new AriesFrameworkError('Attempted to throw error, but it was not of type Error') + throw new AriesFrameworkError('Attempted to throw error, but it was not of type Error', { cause: error }) } throw new WalletError('Error packing message', { cause: error }) } } public async unpack(messagePackage: EncryptedMessage): Promise { + if (isDidCommV1EncryptedEnvelope(messagePackage)) { + return this.unpackDidCommV1(messagePackage) + } else { + throw new AriesFrameworkError(`DidComm V2 message encryption is not supported for Indy wallet`) + } + } + + private async unpackDidCommV1(messagePackage: EncryptedMessage): Promise { try { - const unpackedMessageBuffer = await this.indy.unpackMessage(this.handle, JsonEncoder.toBuffer(messagePackage)) + const unpackedMessageBuffer = await this.indySdk.unpackMessage(this.handle, JsonEncoder.toBuffer(messagePackage)) const unpackedMessage = JsonEncoder.fromBuffer(unpackedMessageBuffer) return { - senderKey: unpackedMessage.sender_verkey, - recipientKey: unpackedMessage.recipient_verkey, + didCommVersion: DidCommMessageVersion.V1, + senderKey: unpackedMessage.sender_verkey + ? Key.fromPublicKeyBase58(unpackedMessage.sender_verkey, KeyType.Ed25519) + : undefined, + recipientKey: Key.fromPublicKeyBase58(unpackedMessage.recipient_verkey, KeyType.Ed25519), plaintextMessage: JsonEncoder.fromString(unpackedMessage.message), } } catch (error) { if (!isError(error)) { - throw new AriesFrameworkError('Attempted to throw error, but it was not of type Error') + throw new AriesFrameworkError('Attempted to throw error, but it was not of type Error', { cause: error }) } throw new WalletError('Error unpacking message', { cause: error }) } @@ -626,27 +601,26 @@ export class IndyWallet implements Wallet { public async generateNonce(): Promise { try { - return await this.indy.generateNonce() + return await this.indySdk.generateNonce() } catch (error) { if (!isError(error)) { - throw new AriesFrameworkError('Attempted to throw error, but it was not of type Error') + throw new AriesFrameworkError('Attempted to throw error, but it was not of type Error', { cause: error }) } throw new WalletError('Error generating nonce', { cause: error }) } } - // FIXME: Think how to avoid making function it public? - public async retrieveKeyPair(keyId: string): Promise { + private async retrieveKeyPair(publicKeyBase58: string): Promise { try { - const { value } = await this.indy.getWalletRecord(this.handle, 'KeyPairRecord', `key-${keyId}`, {}) + const { value } = await this.indySdk.getWalletRecord(this.handle, 'KeyPairRecord', `key-${publicKeyBase58}`, {}) if (value) { return JsonEncoder.fromString(value) as KeyPair } else { - throw new WalletError(`No content found for record with public key: ${keyId}`) + throw new WalletError(`No content found for record with public key: ${publicKeyBase58}`) } } catch (error) { if (isIndyError(error, 'WalletItemNotFound')) { - throw new RecordNotFoundError(`KeyPairRecord not found for public key: ${keyId}.`, { + throw new RecordNotFoundError(`KeyPairRecord not found for public key: ${publicKeyBase58}.`, { recordType: 'KeyPairRecord', cause: error, }) @@ -657,7 +631,7 @@ export class IndyWallet implements Wallet { private async storeKeyPair(keyPair: KeyPair): Promise { try { - await this.indy.addWalletRecord( + await this.indySdk.addWalletRecord( this.handle, 'KeyPairRecord', `key-${keyPair.publicKeyBase58}`, @@ -668,7 +642,7 @@ export class IndyWallet implements Wallet { ) } catch (error) { if (isIndyError(error, 'WalletItemAlreadyExists')) { - throw new RecordDuplicateError(`Record already exists`, { recordType: 'KeyPairRecord' }) + throw new WalletKeyExistsError('Key already exists') } throw isIndyError(error) ? new IndySdkError(error) : error } @@ -676,7 +650,7 @@ export class IndyWallet implements Wallet { public async generateWalletKey() { try { - return await this.indy.generateWalletKey() + return await this.indySdk.generateWalletKey() } catch (error) { throw new WalletError('Error generating wallet key', { cause: error }) } diff --git a/packages/indy-sdk/src/wallet/__tests__/IndySdkWallet.test.ts b/packages/indy-sdk/src/wallet/__tests__/IndySdkWallet.test.ts new file mode 100644 index 0000000000..acb4487592 --- /dev/null +++ b/packages/indy-sdk/src/wallet/__tests__/IndySdkWallet.test.ts @@ -0,0 +1,109 @@ +import type { KeyProvider, WalletConfig } from '@aries-framework/core' + +import { + Key, + WalletKeyExistsError, + KeyType, + KeyProviderRegistry, + TypedArrayEncoder, + KeyDerivationMethod, +} from '@aries-framework/core' +import indySdk from 'indy-sdk' + +import testLogger from '../../../../core/tests/logger' +import { IndySdkWallet } from '../IndySdkWallet' + +// use raw key derivation method to speed up wallet creating / opening / closing between tests +const walletConfig: WalletConfig = { + id: 'Wallet: IndySdkWalletTest', + // generated using indy.generateWalletKey + key: 'CwNJroKHTSSj3XvE7ZAnuKiTn2C4QkFvxEqfm5rzhNrb', + keyDerivationMethod: KeyDerivationMethod.Raw, +} + +const signingProvider = { + keyType: KeyType.X25519, + createKeyPair: () => Promise.resolve({ keyType: KeyType.X25519, privateKeyBase58: 'b', publicKeyBase58: 'a' }), +} + +describe('IndySdkWallet', () => { + let indySdkWallet: IndySdkWallet + + const privateKey = TypedArrayEncoder.fromString('sample-seed') + const message = TypedArrayEncoder.fromString('sample-message') + + beforeEach(async () => { + indySdkWallet = new IndySdkWallet( + indySdk, + testLogger, + new KeyProviderRegistry([signingProvider as unknown as KeyProvider]) + ) + await indySdkWallet.createAndOpen(walletConfig) + }) + + afterEach(async () => { + await indySdkWallet.delete() + }) + + test('Get the wallet handle', () => { + expect(indySdkWallet.handle).toEqual(expect.any(Number)) + }) + + test('Generate Nonce', async () => { + await expect(indySdkWallet.generateNonce()).resolves.toEqual(expect.any(String)) + }) + + test('Create ed25519 keypair from private key', async () => { + await expect( + indySdkWallet.createKey({ + privateKey: TypedArrayEncoder.fromString('2103de41b4ae37e8e28586d84a342b67'), + keyType: KeyType.Ed25519, + }) + ).resolves.toMatchObject({ + keyType: KeyType.Ed25519, + }) + }) + + test('throws WalletKeyExistsError when a key already exists', async () => { + const privateKey = TypedArrayEncoder.fromString('2103de41b4ae37e8e28586d84a342b68') + await expect(indySdkWallet.createKey({ privateKey, keyType: KeyType.Ed25519 })).resolves.toEqual(expect.any(Key)) + await expect(indySdkWallet.createKey({ privateKey, keyType: KeyType.Ed25519 })).rejects.toThrowError( + WalletKeyExistsError + ) + + // This should result in the signign provider being called twice, resulting in the record + // being stored twice + await expect(indySdkWallet.createKey({ privateKey, keyType: KeyType.X25519 })).resolves.toEqual(expect.any(Key)) + await expect(indySdkWallet.createKey({ privateKey, keyType: KeyType.X25519 })).rejects.toThrowError( + WalletKeyExistsError + ) + }) + + test('Fail to create ed25519 keypair from invalid private key', async () => { + await expect(indySdkWallet.createKey({ privateKey, keyType: KeyType.Ed25519 })).rejects.toThrowError( + /Invalid private key provided/ + ) + }) + + test('Fail to create x25519 keypair', async () => { + await expect(indySdkWallet.createKey({ keyType: KeyType.Bls12381g1 })).rejects.toThrowError(/Unsupported key type/) + }) + + test('Create a signature with a ed25519 keypair', async () => { + const ed25519Key = await indySdkWallet.createKey({ keyType: KeyType.Ed25519 }) + const signature = await indySdkWallet.sign({ + data: message, + key: ed25519Key, + }) + expect(signature.length).toStrictEqual(64) + }) + + test('Verify a signed message with a ed25519 publicKey', async () => { + const ed25519Key = await indySdkWallet.createKey({ keyType: KeyType.Ed25519 }) + const signature = await indySdkWallet.sign({ + data: message, + key: ed25519Key, + }) + await expect(indySdkWallet.verify({ key: ed25519Key, data: message, signature })).resolves.toStrictEqual(true) + }) +}) diff --git a/packages/indy-sdk/src/wallet/index.ts b/packages/indy-sdk/src/wallet/index.ts new file mode 100644 index 0000000000..b327ed63bf --- /dev/null +++ b/packages/indy-sdk/src/wallet/index.ts @@ -0,0 +1 @@ +export { IndySdkWallet } from './IndySdkWallet' diff --git a/packages/indy-sdk/tests/__fixtures__/anoncreds.ts b/packages/indy-sdk/tests/__fixtures__/anoncreds.ts new file mode 100644 index 0000000000..eb978ec748 --- /dev/null +++ b/packages/indy-sdk/tests/__fixtures__/anoncreds.ts @@ -0,0 +1,30 @@ +export const credentialDefinitionValue = { + primary: { + n: '96517142458750088826087901549537285521906361834839650465292394026155791790248920518228426560592477800345470631128393537910767968076647428853737338120375137978526133371095345886547568849980095910835456337942570110635942227498396677781945046904040000347997661394155645138402989185582727368743644878567330299129483548946710969360956979880962101169330048328620192831242584775824654760726417810662811409929761424969870024291961980782988854217354212087291593903213167261779548063894662259300608395552269380441482047725811646638173390809967510159302372018819245039226007682154490256871635806558216146474297742733244470144481', + s: '20992997088800769394205042281221010730843336204635587269131066142238627416871294692123680065003125450990475247419429111144686875080339959479648984195457400282722471552678361441816569115316390063503704185107464429408708889920969284364549487320740759452356010336698287092961864738455949515401889999320804333605635972368885179914619910494573144273759358510644118555354521660927445864167887629319425342133470781407706668100509422240127902573158722086763638357241708157836231326104213948080124231104027985997092193458353052131052627451830345602820935886233072722689872803371231173593216542422645374438328309647440653637339', + r: { + master_secret: + '96243300745227716230048295249700256382424379142767068560156597061550615821183969840133023439359733351013932957841392861447122785423145599004240865527901625751619237368187131360686977600247815596986496835118582544022443932674638843143227258367859921648385998241629365673854479167826898057354386557912400420925145402535066400276579674049751639901555837852972622061540154688641944145082381483273814616102862399655638465723909813901943343059991047747289931252070264205125933226649905593045675877143065756794349492159868513288280364195700788501708587588090219665708038121636837649207584981238653023213330207384929738192210', + name: '73301750658973501389860306433954162777688414647250690792688553201037736559940890441467927863421690990807820789906540409252803697381653459639864945429958798104818241892796218340966964349674689564019059435289373607451125919476002261041343187491848656595845611576458601110066647002078334660251906541846222115184239401618625285703919125402959929850028352261117167621349930047514115676870868726855651130262227714591240534532398809967792128535084773798290351459391475237061458901325844643172504167457543287673202618731404966555015061917662865397763636445953946274068384614117513804834235388565249331682010365807270858083546', + }, + rctxt: + '37788128721284563440858950515231840450431543928224096081933216180465915572829884228780081835462293611329848268384962871736884632087015070623933628853658097637604059748079512999518737243304794110313829761155878287344472916564970806851294430356498883927870926898737394894892797927804721407643833828162246495645836390303263072281761384240973982733122383052566872688887552226083782030670443318152427129452272570595367287061688769394567289624972332234661767648489253220495098949161964171486245324730862072203259801377135500275012560207100571502032523912388082460843991502336467718632746396226650194750972544436894286230063', + z: '43785356695890052462955676926428400928903479009358861113206349419200366390858322895540291303484939601128045362682307382393826375825484851021601464391509750565285197155653613669680662395620338416776539485377195826876505126073018100680273457526216247879013350460071029101583221000647494610122617904515744711339846577920055655093367012508192004131719432915903924789974568341538556528133188398290594619318653419602058489178526243446782729272985727332736198326183868783570550373552407121582843992983431205917273352230155794805507408743590383242904107596623095433284330566906935063373759426916339149701872288610119965287995', + }, + revocation: { + g: '1 0A84C28144BC8B677839038FFFA824AB5ADE517F8DD4A89F092FAF9A3560C62D 1 00FD708E112EEA5D89AF9D0559795E6DBCF56D3B8CDF79EFF34A72EB741F896F 2 095E45DDF417D05FB10933FFC63D474548B7FFFF7888802F07FFFFFF7D07A8A8', + g_dash: + '1 201F3E23CC7E9284F3EFCF9500F1E2537C398EAB2E94D2EB801AECC7FBFBDC01 1 08132C7723CF9861D4CC24B56555EF1CBD9AE746C97B3ADFA36C669F2DCE09B6 1 1B2397FB2A1ADE704E2A1E4C242612F4677F9F1BD09E6B14C2E77E25EDA4C62E 1 00CDC2CF5F278D699D52223577AB032C150A3CB4C8E8AB07AB9D592772910E95 2 095E45DDF417D05FB10933FFC63D474548B7FFFF7888802F07FFFFFF7D07A8A8 1 0000000000000000000000000000000000000000000000000000000000000000', + h: '1 072E0A505004F2F32B4210E72FA18A2ADF17F31479BD2059B7A8C0BA58F2ACB3 1 05C70F039E60317003C41C319753ECACC629791FDB06D6ADC5B06DD94501B973 2 095E45DDF417D05FB10933FFC63D474548B7FFFF7888802F07FFFFFF7D07A8A8', + h0: '1 03CBE26D18118E9770D4A0B3E8607B3B3A8D3D3CA81FF8D41862430CC583156E 1 004A2A57E0A826AEFF007EDDAF89B02F054050843689167B10127FE9EDEEEDA9 2 095E45DDF417D05FB10933FFC63D474548B7FFFF7888802F07FFFFFF7D07A8A8', + h1: '1 10C9F9DE537994E4FEF2625AFA78342C8A096238A875F6899DD500230E6022E5 1 0C0A88F53D020557377B4ED9C3826E9B8F918DD03E23B0F8ECD922F8333359D3 2 095E45DDF417D05FB10933FFC63D474548B7FFFF7888802F07FFFFFF7D07A8A8', + h2: '1 017F748AEEC1DDE4E4C3FBAE771C041F0A6FAEAF34FD02AF773AC4B75025147B 1 1298DBD9A4BEE6AD54E060A57BCE932735B7738C30A9ADAEFE2F38E1858A0183 2 095E45DDF417D05FB10933FFC63D474548B7FFFF7888802F07FFFFFF7D07A8A8', + htilde: + '1 0C471F0451D6AC352E28B6ECDE8D7233B75530AE59276DF0F4B9A8B0C5C7E5DB 1 24CE4461910AA5D60C09C24EE0FE51E1B1600D8BA6E483E9050EF897CA3E3C8A 2 095E45DDF417D05FB10933FFC63D474548B7FFFF7888802F07FFFFFF7D07A8A8', + h_cap: + '1 225B2106DEBD353AABDFC4C7F7E8660D308FB514EA9DAE0533DDEB65CF796159 1 1F6093622F439FC22C64F157F4F35F7C592EC0169C6F0026BC44CD3E375974A7 1 142126FAC3657AD846D394E1F72FD01ECC15E84416713CD133980E324B24F4BC 1 0357995DBDCD4385E59E607761AB30AE8D9DDE005A777EE846EF51AE2816CD33 2 095E45DDF417D05FB10933FFC63D474548B7FFFF7888802F07FFFFFF7D07A8A8 1 0000000000000000000000000000000000000000000000000000000000000000', + u: '1 00D8DDC2EB6536CA320EE035D099937E59B11678162C1BFEB30C58FCA9F84650 1 1557A5B05A1A30D63322E187D323C9CA431BC5E811E68D4703933D9DDA26D299 1 10E8AB93AA87839B757521742EBA23C3B257C91F61A93D37AEC4C0A011B5F073 1 1DA65E40406A7875DA8CFCE9FD7F283145C166382A937B72819BDC335FE9A734 2 095E45DDF417D05FB10933FFC63D474548B7FFFF7888802F07FFFFFF7D07A8A8 1 0000000000000000000000000000000000000000000000000000000000000000', + pk: '1 1A7EBBE3E7F8ED50959851364B20997944FA8AE5E3FC0A2BB531BAA17179D320 1 02C55FE6F64A2A4FF49B37C513C39E56ECD565CFAD6CA46DC6D8095179351863 2 095E45DDF417D05FB10933FFC63D474548B7FFFF7888802F07FFFFFF7D07A8A8', + y: '1 1BF97F07270EC21A89E43BCA645D86A755F846B547238F1DA379E088CDD9B40D 1 146BB00F56FFC0DEF6541CEB484C718559B398DB1547B52850E46B23144161F1 1 079A1BEF8DFFA4E6352F701D476664340E7FBE5D3F46B897412BD2B5F10E33D7 1 02FDC508AEF90FB11961AF332BE4037973C76B954FFA48848F7E0588E93FCA8C 2 095E45DDF417D05FB10933FFC63D474548B7FFFF7888802F07FFFFFF7D07A8A8 1 0000000000000000000000000000000000000000000000000000000000000000', + }, +} diff --git a/packages/indy-sdk/tests/indy-did-registrar.e2e.test.ts b/packages/indy-sdk/tests/indy-did-registrar.e2e.test.ts new file mode 100644 index 0000000000..bcdd04e039 --- /dev/null +++ b/packages/indy-sdk/tests/indy-did-registrar.e2e.test.ts @@ -0,0 +1,125 @@ +import type { IndySdkIndyDidCreateOptions } from '../src' + +import { Agent, TypedArrayEncoder, convertPublicKeyToX25519, JsonTransformer } from '@aries-framework/core' +import { generateKeyPairFromSeed } from '@stablelib/ed25519' + +import { getAgentOptions, importExistingIndyDidFromPrivateKey, publicDidSeed } from '../../core/tests' +import { legacyIndyDidFromPublicKeyBase58 } from '../src/utils/did' + +import { getIndySdkModules } from './setupIndySdkModule' + +const agentOptions = getAgentOptions('Indy Sdk Indy Did Registrar', {}, getIndySdkModules()) +const agent = new Agent(agentOptions) + +describe('Indy SDK Indy Did Registrar', () => { + beforeAll(async () => { + await agent.initialize() + }) + + afterAll(async () => { + await agent.shutdown() + await agent.wallet.delete() + }) + + it('should create a did:indy did', async () => { + // Add existing endorser did to the wallet + const unqualifiedSubmitterDid = await importExistingIndyDidFromPrivateKey( + agent, + TypedArrayEncoder.fromString(publicDidSeed) + ) + + // Generate a seed and the indy did. This allows us to create a new did every time + // but still check if the created output document is as expected. + const privateKey = TypedArrayEncoder.fromString( + Array(32 + 1) + .join((Math.random().toString(36) + '00000000000000000').slice(2, 18)) + .slice(0, 32) + ) + + const publicKeyEd25519 = generateKeyPairFromSeed(privateKey).publicKey + const x25519PublicKeyBase58 = TypedArrayEncoder.toBase58(convertPublicKeyToX25519(publicKeyEd25519)) + const ed25519PublicKeyBase58 = TypedArrayEncoder.toBase58(publicKeyEd25519) + const unqualifiedDid = legacyIndyDidFromPublicKeyBase58(ed25519PublicKeyBase58) + + const did = await agent.dids.create({ + method: 'indy', + options: { + submitterDid: `did:indy:pool:localtest:${unqualifiedSubmitterDid}`, + alias: 'Alias', + endpoints: { + endpoint: 'https://example.com/endpoint', + types: ['DIDCommMessaging', 'did-communication', 'endpoint'], + routingKeys: ['a-routing-key'], + }, + }, + secret: { + privateKey, + }, + }) + + expect(JsonTransformer.toJSON(did)).toMatchObject({ + didDocumentMetadata: {}, + didRegistrationMetadata: {}, + didState: { + state: 'finished', + did: `did:indy:pool:localtest:${unqualifiedDid}`, + didDocument: { + '@context': [ + 'https://w3id.org/did/v1', + 'https://w3id.org/security/suites/ed25519-2018/v1', + 'https://w3id.org/security/suites/x25519-2019/v1', + 'https://didcomm.org/messaging/contexts/v2', + ], + alsoKnownAs: undefined, + controller: undefined, + verificationMethod: [ + { + id: `did:indy:pool:localtest:${unqualifiedDid}#verkey`, + type: 'Ed25519VerificationKey2018', + controller: `did:indy:pool:localtest:${unqualifiedDid}`, + publicKeyBase58: ed25519PublicKeyBase58, + }, + { + id: `did:indy:pool:localtest:${unqualifiedDid}#key-agreement-1`, + type: 'X25519KeyAgreementKey2019', + controller: `did:indy:pool:localtest:${unqualifiedDid}`, + publicKeyBase58: x25519PublicKeyBase58, + }, + ], + service: [ + { + id: `did:indy:pool:localtest:${unqualifiedDid}#endpoint`, + serviceEndpoint: 'https://example.com/endpoint', + type: 'endpoint', + }, + { + accept: ['didcomm/aip2;env=rfc19'], + id: `did:indy:pool:localtest:${unqualifiedDid}#did-communication`, + priority: 0, + recipientKeys: [`did:indy:pool:localtest:${unqualifiedDid}#key-agreement-1`], + routingKeys: ['a-routing-key'], + serviceEndpoint: 'https://example.com/endpoint', + type: 'did-communication', + }, + { + accept: ['didcomm/v2'], + id: `did:indy:pool:localtest:${unqualifiedDid}#didcomm-1`, + routingKeys: ['a-routing-key'], + serviceEndpoint: 'https://example.com/endpoint', + type: 'DIDCommMessaging', + }, + ], + authentication: [`did:indy:pool:localtest:${unqualifiedDid}#verkey`], + assertionMethod: undefined, + keyAgreement: [`did:indy:pool:localtest:${unqualifiedDid}#key-agreement-1`], + capabilityInvocation: undefined, + capabilityDelegation: undefined, + id: `did:indy:pool:localtest:${unqualifiedDid}`, + }, + secret: { + privateKey, + }, + }, + }) + }) +}) diff --git a/packages/indy-sdk/tests/indy-did-resolver.e2e.test.ts b/packages/indy-sdk/tests/indy-did-resolver.e2e.test.ts new file mode 100644 index 0000000000..839db5e4df --- /dev/null +++ b/packages/indy-sdk/tests/indy-did-resolver.e2e.test.ts @@ -0,0 +1,99 @@ +import type { IndySdkIndyDidCreateOptions } from '../src' + +import { Agent, AriesFrameworkError, JsonTransformer, TypedArrayEncoder } from '@aries-framework/core' + +import { getAgentOptions, importExistingIndyDidFromPrivateKey, publicDidSeed } from '../../core/tests/helpers' + +import { getIndySdkModules } from './setupIndySdkModule' + +const agent = new Agent(getAgentOptions('Indy SDK Indy DID resolver', {}, getIndySdkModules())) + +describe('Indy SDK Indy DID resolver', () => { + beforeAll(async () => { + await agent.initialize() + }) + + afterAll(async () => { + await agent.shutdown() + await agent.wallet.delete() + }) + + it('should resolve a did:indy did', async () => { + // Add existing endorser did to the wallet + const unqualifiedSubmitterDid = await importExistingIndyDidFromPrivateKey( + agent, + TypedArrayEncoder.fromString(publicDidSeed) + ) + + const createResult = await agent.dids.create({ + method: 'indy', + options: { + submitterDid: `did:indy:pool:localtest:${unqualifiedSubmitterDid}`, + alias: 'Alias', + role: 'TRUSTEE', + endpoints: { + endpoint: 'http://localhost:3000', + }, + }, + }) + + // Terrible, but the did can't be immediately resolved, so we need to wait a bit + await new Promise((res) => setTimeout(res, 1000)) + + if (!createResult.didState.did) throw new AriesFrameworkError('Unable to register did') + + const didResult = await agent.dids.resolve(createResult.didState.did) + + expect(JsonTransformer.toJSON(didResult)).toMatchObject({ + didDocument: { + '@context': [ + 'https://w3id.org/did/v1', + 'https://w3id.org/security/suites/ed25519-2018/v1', + 'https://w3id.org/security/suites/x25519-2019/v1', + ], + id: createResult.didState.did, + alsoKnownAs: undefined, + controller: undefined, + verificationMethod: [ + { + type: 'Ed25519VerificationKey2018', + controller: createResult.didState.did, + id: `${createResult.didState.did}#verkey`, + publicKeyBase58: expect.any(String), + }, + { + controller: createResult.didState.did, + type: 'X25519KeyAgreementKey2019', + id: `${createResult.didState.did}#key-agreement-1`, + publicKeyBase58: expect.any(String), + }, + ], + capabilityDelegation: undefined, + capabilityInvocation: undefined, + authentication: [`${createResult.didState.did}#verkey`], + assertionMethod: undefined, + keyAgreement: [`${createResult.didState.did}#key-agreement-1`], + service: [ + { + id: `${createResult.didState.did}#endpoint`, + serviceEndpoint: 'http://localhost:3000', + type: 'endpoint', + }, + { + id: `${createResult.didState.did}#did-communication`, + accept: ['didcomm/aip2;env=rfc19'], + priority: 0, + recipientKeys: [`${createResult.didState.did}#key-agreement-1`], + routingKeys: [], + serviceEndpoint: 'http://localhost:3000', + type: 'did-communication', + }, + ], + }, + didDocumentMetadata: {}, + didResolutionMetadata: { + contentType: 'application/did+ld+json', + }, + }) + }) +}) diff --git a/packages/indy-sdk/tests/indy-sdk-anoncreds-registry.e2e.test.ts b/packages/indy-sdk/tests/indy-sdk-anoncreds-registry.e2e.test.ts new file mode 100644 index 0000000000..0efa8d533d --- /dev/null +++ b/packages/indy-sdk/tests/indy-sdk-anoncreds-registry.e2e.test.ts @@ -0,0 +1,345 @@ +import { Agent, Key, KeyType, TypedArrayEncoder } from '@aries-framework/core' + +import { + agentDependencies, + getAgentConfig, + importExistingIndyDidFromPrivateKey, + publicDidSeed, +} from '../../core/tests/helpers' +import { IndySdkAnonCredsRegistry } from '../src/anoncreds/services/IndySdkAnonCredsRegistry' +import { IndySdkPoolService } from '../src/ledger' + +import { credentialDefinitionValue } from './__fixtures__/anoncreds' +import { getIndySdkModules, indySdk } from './setupIndySdkModule' + +const agentConfig = getAgentConfig('IndySdkAnonCredsRegistry') +const indySdkModules = getIndySdkModules() + +const agent = new Agent({ + config: agentConfig, + dependencies: agentDependencies, + modules: indySdkModules, +}) + +const indySdkAnonCredsRegistry = new IndySdkAnonCredsRegistry() +const indySdkPoolService = agent.dependencyManager.resolve(IndySdkPoolService) +const pool = indySdkPoolService.getPoolForNamespace('pool:localtest') + +describe('IndySdkAnonCredsRegistry', () => { + beforeAll(async () => { + await agent.initialize() + + // We need to import the endorser did/key into the wallet + await importExistingIndyDidFromPrivateKey(agent, TypedArrayEncoder.fromString(publicDidSeed)) + }) + + afterAll(async () => { + await agent.shutdown() + await agent.wallet.delete() + }) + + // TODO: use different issuer for schema and credential definition to catch possible bugs + // One test as the credential definition depends on the schema + test('register and resolve a schema and credential definition', async () => { + const dynamicVersion = `1.${Math.random() * 100}` + + const legacyIssuerId = 'TL1EaPFCZ8Si5aUrqScBDt' + const signingKey = Key.fromPublicKeyBase58('FMGcFuU3QwAQLywxvmEnSorQT3NwU9wgDMMTaDFtvswm', KeyType.Ed25519) + const didIndyIssuerId = 'did:indy:pool:localtest:TL1EaPFCZ8Si5aUrqScBDt' + + const legacySchemaId = `TL1EaPFCZ8Si5aUrqScBDt:2:test:${dynamicVersion}` + const didIndySchemaId = `did:indy:pool:localtest:TL1EaPFCZ8Si5aUrqScBDt/anoncreds/v0/SCHEMA/test/${dynamicVersion}` + + const schemaResult = await indySdkAnonCredsRegistry.registerSchema(agent.context, { + schema: { + attrNames: ['name'], + issuerId: didIndyIssuerId, + name: 'test', + version: dynamicVersion, + }, + options: {}, + }) + + expect(schemaResult).toMatchObject({ + schemaState: { + state: 'finished', + schema: { + attrNames: ['name'], + issuerId: didIndyIssuerId, + name: 'test', + version: dynamicVersion, + }, + schemaId: didIndySchemaId, + }, + registrationMetadata: {}, + schemaMetadata: { + indyLedgerSeqNo: expect.any(Number), + }, + }) + + // Wait some time before resolving credential definition object + await new Promise((res) => setTimeout(res, 1000)) + + // Resolve using legacy schema id + const legacySchema = await indySdkAnonCredsRegistry.getSchema(agent.context, legacySchemaId) + expect(legacySchema).toMatchObject({ + schema: { + attrNames: ['name'], + name: 'test', + version: dynamicVersion, + issuerId: 'TL1EaPFCZ8Si5aUrqScBDt', + }, + schemaId: `TL1EaPFCZ8Si5aUrqScBDt:2:test:${dynamicVersion}`, + resolutionMetadata: {}, + schemaMetadata: { + didIndyNamespace: 'pool:localtest', + indyLedgerSeqNo: expect.any(Number), + }, + }) + + // Resolve using did indy schema id + const didIndySchema = await indySdkAnonCredsRegistry.getSchema(agent.context, didIndySchemaId) + expect(didIndySchema).toMatchObject({ + schema: { + attrNames: ['name'], + name: 'test', + version: dynamicVersion, + issuerId: didIndyIssuerId, + }, + schemaId: didIndySchemaId, + resolutionMetadata: {}, + schemaMetadata: { + didIndyNamespace: 'pool:localtest', + indyLedgerSeqNo: expect.any(Number), + }, + }) + + const legacyCredentialDefinitionId = `TL1EaPFCZ8Si5aUrqScBDt:3:CL:${schemaResult.schemaMetadata.indyLedgerSeqNo}:TAG` + const didIndyCredentialDefinitionId = `did:indy:pool:localtest:TL1EaPFCZ8Si5aUrqScBDt/anoncreds/v0/CLAIM_DEF/${schemaResult.schemaMetadata.indyLedgerSeqNo}/TAG` + const credentialDefinitionResult = await indySdkAnonCredsRegistry.registerCredentialDefinition(agent.context, { + credentialDefinition: { + issuerId: didIndyIssuerId, + tag: 'TAG', + schemaId: didIndySchemaId, + type: 'CL', + value: credentialDefinitionValue, + }, + options: {}, + }) + + expect(credentialDefinitionResult).toMatchObject({ + credentialDefinitionMetadata: {}, + credentialDefinitionState: { + credentialDefinition: { + issuerId: didIndyIssuerId, + tag: 'TAG', + schemaId: didIndySchemaId, + type: 'CL', + value: credentialDefinitionValue, + }, + credentialDefinitionId: didIndyCredentialDefinitionId, + state: 'finished', + }, + registrationMetadata: {}, + }) + + // Wait some time before resolving credential definition object + await new Promise((res) => setTimeout(res, 1000)) + + // Resolve using legacy credential definition id + const legacyCredentialDefinition = await indySdkAnonCredsRegistry.getCredentialDefinition( + agent.context, + legacyCredentialDefinitionId + ) + expect(legacyCredentialDefinition).toMatchObject({ + credentialDefinitionId: legacyCredentialDefinitionId, + credentialDefinition: { + issuerId: legacyIssuerId, + schemaId: legacySchemaId, + tag: 'TAG', + type: 'CL', + value: credentialDefinitionValue, + }, + credentialDefinitionMetadata: { + didIndyNamespace: 'pool:localtest', + }, + resolutionMetadata: {}, + }) + + // resolve using did indy credential definition id + const didIndyCredentialDefinition = await indySdkAnonCredsRegistry.getCredentialDefinition( + agent.context, + didIndyCredentialDefinitionId + ) + + expect(didIndyCredentialDefinition).toMatchObject({ + credentialDefinitionId: didIndyCredentialDefinitionId, + credentialDefinition: { + issuerId: didIndyIssuerId, + schemaId: didIndySchemaId, + tag: 'TAG', + type: 'CL', + value: credentialDefinitionValue, + }, + credentialDefinitionMetadata: { + didIndyNamespace: 'pool:localtest', + }, + resolutionMetadata: {}, + }) + + // We don't support creating a revocation registry using AFJ yet, so we directly use indy-sdk to register the revocation registry + const legacyRevocationRegistryId = `TL1EaPFCZ8Si5aUrqScBDt:4:TL1EaPFCZ8Si5aUrqScBDt:3:CL:${schemaResult.schemaMetadata.indyLedgerSeqNo}:TAG:CL_ACCUM:tag` + const didIndyRevocationRegistryId = `did:indy:pool:localtest:TL1EaPFCZ8Si5aUrqScBDt/anoncreds/v0/REV_REG_DEF/${schemaResult.schemaMetadata.indyLedgerSeqNo}/TAG/tag` + const revocationRegistryRequest = await indySdk.buildRevocRegDefRequest('TL1EaPFCZ8Si5aUrqScBDt', { + id: legacyRevocationRegistryId, + credDefId: legacyCredentialDefinitionId, + revocDefType: 'CL_ACCUM', + tag: 'tag', + value: { + issuanceType: 'ISSUANCE_BY_DEFAULT', + maxCredNum: 100, + publicKeys: { + accumKey: { + z: '1 1812B206EB395D3AEBD4BBF53EBB0FFC3371D8BD6175316AB32C1C5F65452051 1 22A079D49C5351EFDC1410C81A1F6D8B2E3B79CFF20A30690C118FE2050F72CB 1 0FFC28B923A4654E261DB4CB5B9BABEFCB4DB189B20F52412B0CC9CCCBB8A3B2 1 1EE967C43EF1A3F487061D21B07076A26C126AAF7712E7B5CF5A53688DDD5CC0 1 009ED4D65879CA81DA8227D34CEA3B759B4627E1E2FFB273E9645CD4F3B10F19 1 1CF070212E1E213AEB472F56EDFC9D48009796C77B2D8CC16F2836E37B8715C2 1 04954F0B7B468781BAAE3291DD0E6FFA7F1AF66CAA4094D37B24363CC34606FB 1 115367CB755E9DB18781B3825CB1AEE2C334558B2C038E13DF57BB57CE1CF847 1 110D37EC05862EE2757A7DF39E814876FC97376FF8105D2D29619CB575537BDE 1 13C559A9563FCE083B3B39AE7E8FCA4099BEF3A4C8C6672E543D521F9DA88F96 1 137D87CC22ACC1B6B8C20EABE59F6ED456A58FE4CBEEFDFC4FA9B87E3EF32D17 1 00A2A9711737AAF0404F35AE502887AC6172B2B57D236BD4A40B45F659BFC696', + }, + }, + tailsHash: 'HLKresYcDSZYSKogq8wive4zyXNY84669MygftLFBG1i', + tailsLocation: '/var/folders/l3/xy8jzyvj4p5_d9g1123rt4bw0000gn/T/HLKresYcDSZYSKogq8wive4zyXNY84669MygftLFBG1i', + }, + ver: '1.0', + }) + + await indySdkPoolService.submitWriteRequest(agent.context, pool, revocationRegistryRequest, signingKey) + + // indySdk.buildRevRegEntry panics, so we just pass a custom request directly + const entryResponse = await indySdkPoolService.submitWriteRequest( + agent.context, + pool, + { + identifier: legacyIssuerId, + operation: { + revocDefType: 'CL_ACCUM', + revocRegDefId: legacyRevocationRegistryId, + type: '114', + value: { + accum: + '1 0000000000000000000000000000000000000000000000000000000000000000 1 0000000000000000000000000000000000000000000000000000000000000000 2 095E45DDF417D05FB10933FFC63D474548B7FFFF7888802F07FFFFFF7D07A8A8 1 0000000000000000000000000000000000000000000000000000000000000000 1 0000000000000000000000000000000000000000000000000000000000000000 1 0000000000000000000000000000000000000000000000000000000000000000', + }, + }, + protocolVersion: 2, + reqId: Math.floor(Math.random() * 1000000), + }, + signingKey + ) + + const legacyRevocationRegistryDefinition = await indySdkAnonCredsRegistry.getRevocationRegistryDefinition( + agent.context, + legacyRevocationRegistryId + ) + expect(legacyRevocationRegistryDefinition).toMatchObject({ + revocationRegistryDefinitionId: legacyRevocationRegistryId, + revocationRegistryDefinition: { + issuerId: legacyIssuerId, + revocDefType: 'CL_ACCUM', + value: { + maxCredNum: 100, + tailsHash: 'HLKresYcDSZYSKogq8wive4zyXNY84669MygftLFBG1i', + tailsLocation: + '/var/folders/l3/xy8jzyvj4p5_d9g1123rt4bw0000gn/T/HLKresYcDSZYSKogq8wive4zyXNY84669MygftLFBG1i', + publicKeys: { + accumKey: { + z: '1 1812B206EB395D3AEBD4BBF53EBB0FFC3371D8BD6175316AB32C1C5F65452051 1 22A079D49C5351EFDC1410C81A1F6D8B2E3B79CFF20A30690C118FE2050F72CB 1 0FFC28B923A4654E261DB4CB5B9BABEFCB4DB189B20F52412B0CC9CCCBB8A3B2 1 1EE967C43EF1A3F487061D21B07076A26C126AAF7712E7B5CF5A53688DDD5CC0 1 009ED4D65879CA81DA8227D34CEA3B759B4627E1E2FFB273E9645CD4F3B10F19 1 1CF070212E1E213AEB472F56EDFC9D48009796C77B2D8CC16F2836E37B8715C2 1 04954F0B7B468781BAAE3291DD0E6FFA7F1AF66CAA4094D37B24363CC34606FB 1 115367CB755E9DB18781B3825CB1AEE2C334558B2C038E13DF57BB57CE1CF847 1 110D37EC05862EE2757A7DF39E814876FC97376FF8105D2D29619CB575537BDE 1 13C559A9563FCE083B3B39AE7E8FCA4099BEF3A4C8C6672E543D521F9DA88F96 1 137D87CC22ACC1B6B8C20EABE59F6ED456A58FE4CBEEFDFC4FA9B87E3EF32D17 1 00A2A9711737AAF0404F35AE502887AC6172B2B57D236BD4A40B45F659BFC696', + }, + }, + }, + tag: 'tag', + credDefId: legacyCredentialDefinitionId, + }, + revocationRegistryDefinitionMetadata: { + issuanceType: 'ISSUANCE_BY_DEFAULT', + didIndyNamespace: 'pool:localtest', + }, + resolutionMetadata: {}, + }) + + const didIndyRevocationRegistryDefinition = await indySdkAnonCredsRegistry.getRevocationRegistryDefinition( + agent.context, + didIndyRevocationRegistryId + ) + expect(didIndyRevocationRegistryDefinition).toMatchObject({ + revocationRegistryDefinitionId: didIndyRevocationRegistryId, + revocationRegistryDefinition: { + issuerId: didIndyIssuerId, + revocDefType: 'CL_ACCUM', + value: { + maxCredNum: 100, + tailsHash: 'HLKresYcDSZYSKogq8wive4zyXNY84669MygftLFBG1i', + tailsLocation: + '/var/folders/l3/xy8jzyvj4p5_d9g1123rt4bw0000gn/T/HLKresYcDSZYSKogq8wive4zyXNY84669MygftLFBG1i', + publicKeys: { + accumKey: { + z: '1 1812B206EB395D3AEBD4BBF53EBB0FFC3371D8BD6175316AB32C1C5F65452051 1 22A079D49C5351EFDC1410C81A1F6D8B2E3B79CFF20A30690C118FE2050F72CB 1 0FFC28B923A4654E261DB4CB5B9BABEFCB4DB189B20F52412B0CC9CCCBB8A3B2 1 1EE967C43EF1A3F487061D21B07076A26C126AAF7712E7B5CF5A53688DDD5CC0 1 009ED4D65879CA81DA8227D34CEA3B759B4627E1E2FFB273E9645CD4F3B10F19 1 1CF070212E1E213AEB472F56EDFC9D48009796C77B2D8CC16F2836E37B8715C2 1 04954F0B7B468781BAAE3291DD0E6FFA7F1AF66CAA4094D37B24363CC34606FB 1 115367CB755E9DB18781B3825CB1AEE2C334558B2C038E13DF57BB57CE1CF847 1 110D37EC05862EE2757A7DF39E814876FC97376FF8105D2D29619CB575537BDE 1 13C559A9563FCE083B3B39AE7E8FCA4099BEF3A4C8C6672E543D521F9DA88F96 1 137D87CC22ACC1B6B8C20EABE59F6ED456A58FE4CBEEFDFC4FA9B87E3EF32D17 1 00A2A9711737AAF0404F35AE502887AC6172B2B57D236BD4A40B45F659BFC696', + }, + }, + }, + tag: 'tag', + credDefId: didIndyCredentialDefinitionId, + }, + revocationRegistryDefinitionMetadata: { + issuanceType: 'ISSUANCE_BY_DEFAULT', + didIndyNamespace: 'pool:localtest', + }, + resolutionMetadata: {}, + }) + + const legacyRevocationStatusList = await indySdkAnonCredsRegistry.getRevocationStatusList( + agent.context, + legacyRevocationRegistryId, + entryResponse.result.txnMetadata.txnTime + ) + + expect(legacyRevocationStatusList).toMatchObject({ + resolutionMetadata: {}, + revocationStatusList: { + issuerId: legacyIssuerId, + currentAccumulator: + '1 0000000000000000000000000000000000000000000000000000000000000000 1 0000000000000000000000000000000000000000000000000000000000000000 2 095E45DDF417D05FB10933FFC63D474548B7FFFF7888802F07FFFFFF7D07A8A8 1 0000000000000000000000000000000000000000000000000000000000000000 1 0000000000000000000000000000000000000000000000000000000000000000 1 0000000000000000000000000000000000000000000000000000000000000000', + revRegDefId: legacyRevocationRegistryId, + revocationList: [ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + ], + timestamp: entryResponse.result.txnMetadata.txnTime, + }, + revocationStatusListMetadata: { + didIndyNamespace: 'pool:localtest', + }, + }) + + const didIndyRevocationStatusList = await indySdkAnonCredsRegistry.getRevocationStatusList( + agent.context, + didIndyRevocationRegistryId, + entryResponse.result.txnMetadata.txnTime + ) + + expect(didIndyRevocationStatusList).toMatchObject({ + resolutionMetadata: {}, + revocationStatusList: { + issuerId: didIndyIssuerId, + currentAccumulator: + '1 0000000000000000000000000000000000000000000000000000000000000000 1 0000000000000000000000000000000000000000000000000000000000000000 2 095E45DDF417D05FB10933FFC63D474548B7FFFF7888802F07FFFFFF7D07A8A8 1 0000000000000000000000000000000000000000000000000000000000000000 1 0000000000000000000000000000000000000000000000000000000000000000 1 0000000000000000000000000000000000000000000000000000000000000000', + revRegDefId: didIndyRevocationRegistryId, + revocationList: [ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + ], + timestamp: entryResponse.result.txnMetadata.txnTime, + }, + revocationStatusListMetadata: { + didIndyNamespace: 'pool:localtest', + }, + }) + }) +}) diff --git a/packages/core/tests/postgres.e2e.test.ts b/packages/indy-sdk/tests/postgres.e2e.test.ts similarity index 73% rename from packages/core/tests/postgres.e2e.test.ts rename to packages/indy-sdk/tests/postgres.e2e.test.ts index 2eb89abd1d..68755ead92 100644 --- a/packages/core/tests/postgres.e2e.test.ts +++ b/packages/indy-sdk/tests/postgres.e2e.test.ts @@ -1,30 +1,39 @@ /* eslint-disable @typescript-eslint/no-non-null-assertion */ import type { SubjectMessage } from '../../../tests/transport/SubjectInboundTransport' -import type { IndyPostgresStorageConfig } from '../../node/src' -import type { ConnectionRecord } from '../src/modules/connections' +import type { ConnectionRecord } from '../../core/src/modules/connections' +import type { IndySdkPostgresStorageConfig } from '../../node/src' import { Subject } from 'rxjs' import { SubjectInboundTransport } from '../../../tests/transport/SubjectInboundTransport' import { SubjectOutboundTransport } from '../../../tests/transport/SubjectOutboundTransport' -import { loadPostgresPlugin, WalletScheme } from '../../node/src' -import { Agent } from '../src/agent/Agent' -import { HandshakeProtocol } from '../src/modules/connections' - -import { waitForBasicMessage, getPostgresAgentOptions } from './helpers' - -const alicePostgresAgentOptions = getPostgresAgentOptions('AgentsAlice', { - endpoints: ['rxjs:alice'], -}) -const bobPostgresAgentOptions = getPostgresAgentOptions('AgentsBob', { - endpoints: ['rxjs:bob'], -}) +import { Agent } from '../../core/src/agent/Agent' +import { HandshakeProtocol } from '../../core/src/modules/connections' +import { waitForBasicMessage, getPostgresAgentOptions } from '../../core/tests/helpers' +import { loadIndySdkPostgresPlugin, IndySdkPostgresWalletScheme } from '../../node/src' + +import { getIndySdkModules } from './setupIndySdkModule' + +const alicePostgresAgentOptions = getPostgresAgentOptions( + 'AgentsAlice', + { + endpoints: ['rxjs:alice'], + }, + getIndySdkModules() +) + +const bobPostgresAgentOptions = getPostgresAgentOptions( + 'AgentsBob', + { + endpoints: ['rxjs:bob'], + }, + getIndySdkModules() +) describe('postgres agents', () => { let aliceAgent: Agent let bobAgent: Agent let aliceConnection: ConnectionRecord - let bobConnection: ConnectionRecord afterAll(async () => { await bobAgent.shutdown() @@ -42,11 +51,11 @@ describe('postgres agents', () => { 'rxjs:bob': bobMessages, } - const storageConfig: IndyPostgresStorageConfig = { + const storageConfig: IndySdkPostgresStorageConfig = { type: 'postgres_storage', config: { url: 'localhost:5432', - wallet_scheme: WalletScheme.DatabasePerWallet, + wallet_scheme: IndySdkPostgresWalletScheme.DatabasePerWallet, }, credentials: { account: 'postgres', @@ -57,7 +66,7 @@ describe('postgres agents', () => { } // loading the postgres wallet plugin - loadPostgresPlugin(storageConfig.config, storageConfig.credentials) + loadIndySdkPostgresPlugin(storageConfig.config, storageConfig.credentials) aliceAgent = new Agent(alicePostgresAgentOptions) aliceAgent.registerInboundTransport(new SubjectInboundTransport(aliceMessages)) @@ -76,13 +85,10 @@ describe('postgres agents', () => { const { connectionRecord: bobConnectionAtBobAlice } = await bobAgent.oob.receiveInvitation( aliceBobOutOfBandRecord.outOfBandInvitation! ) - bobConnection = await bobAgent.connections.returnWhenIsConnected(bobConnectionAtBobAlice!.id) + await bobAgent.connections.returnWhenIsConnected(bobConnectionAtBobAlice!.id) const [aliceConnectionAtAliceBob] = await aliceAgent.connections.findAllByOutOfBandId(aliceBobOutOfBandRecord!.id) aliceConnection = await aliceAgent.connections.returnWhenIsConnected(aliceConnectionAtAliceBob!.id) - - expect(aliceConnection).toBeConnectedWith(bobConnection) - expect(bobConnection).toBeConnectedWith(aliceConnection) }) test('send a message to connection', async () => { diff --git a/packages/indy-sdk/tests/setup.ts b/packages/indy-sdk/tests/setup.ts new file mode 100644 index 0000000000..34e38c9705 --- /dev/null +++ b/packages/indy-sdk/tests/setup.ts @@ -0,0 +1 @@ +jest.setTimeout(120000) diff --git a/packages/indy-sdk/tests/setupIndySdkModule.ts b/packages/indy-sdk/tests/setupIndySdkModule.ts new file mode 100644 index 0000000000..b4a30f799a --- /dev/null +++ b/packages/indy-sdk/tests/setupIndySdkModule.ts @@ -0,0 +1,35 @@ +import { DidsModule, KeyDidRegistrar, KeyDidResolver, utils } from '@aries-framework/core' +import indySdk from 'indy-sdk' + +import { genesisPath, taaVersion, taaAcceptanceMechanism } from '../../core/tests/helpers' +import { + IndySdkModule, + IndySdkModuleConfig, + IndySdkIndyDidRegistrar, + IndySdkSovDidResolver, + IndySdkIndyDidResolver, +} from '../src' + +export { indySdk } + +export const getIndySdkModuleConfig = () => + new IndySdkModuleConfig({ + indySdk, + networks: [ + { + id: `localhost-${utils.uuid()}`, + isProduction: false, + genesisPath, + indyNamespace: 'pool:localtest', + transactionAuthorAgreement: { version: taaVersion, acceptanceMechanism: taaAcceptanceMechanism }, + }, + ], + }) + +export const getIndySdkModules = () => ({ + indySdk: new IndySdkModule(getIndySdkModuleConfig()), + dids: new DidsModule({ + registrars: [new IndySdkIndyDidRegistrar(), new KeyDidRegistrar()], + resolvers: [new IndySdkSovDidResolver(), new IndySdkIndyDidResolver(), new KeyDidResolver()], + }), +}) diff --git a/packages/indy-sdk/tests/sov-did-resolver.e2e.test.ts b/packages/indy-sdk/tests/sov-did-resolver.e2e.test.ts new file mode 100644 index 0000000000..d4c2af8e38 --- /dev/null +++ b/packages/indy-sdk/tests/sov-did-resolver.e2e.test.ts @@ -0,0 +1,102 @@ +import type { IndySdkIndyDidCreateOptions } from '../src' + +import { parseIndyDid } from '@aries-framework/anoncreds' +import { Agent, AriesFrameworkError, JsonTransformer, TypedArrayEncoder } from '@aries-framework/core' + +import { getAgentOptions, importExistingIndyDidFromPrivateKey, publicDidSeed } from '../../core/tests/helpers' + +import { getIndySdkModules } from './setupIndySdkModule' + +const agent = new Agent(getAgentOptions('Indy SDK Sov DID resolver', {}, getIndySdkModules())) + +describe('Indy SDK Sov DID resolver', () => { + beforeAll(async () => { + await agent.initialize() + }) + + afterAll(async () => { + await agent.shutdown() + await agent.wallet.delete() + }) + + test('resolve a did:sov did', async () => { + // Add existing endorser did to the wallet + const unqualifiedSubmitterDid = await importExistingIndyDidFromPrivateKey( + agent, + TypedArrayEncoder.fromString(publicDidSeed) + ) + + const createResult = await agent.dids.create({ + method: 'indy', + options: { + submitterDid: `did:indy:pool:localtest:${unqualifiedSubmitterDid}`, + alias: 'Alias', + role: 'TRUSTEE', + endpoints: { + endpoint: 'http://localhost:3000', + }, + }, + }) + + // Terrible, but the did can't be immediately resolved, so we need to wait a bit + await new Promise((res) => setTimeout(res, 1000)) + + if (!createResult.didState.did) throw new AriesFrameworkError('Unable to register did') + + const { namespaceIdentifier } = parseIndyDid(createResult.didState.did) + const sovDid = `did:sov:${namespaceIdentifier}` + const didResult = await agent.dids.resolve(sovDid) + + expect(JsonTransformer.toJSON(didResult)).toMatchObject({ + didDocument: { + '@context': [ + 'https://w3id.org/did/v1', + 'https://w3id.org/security/suites/ed25519-2018/v1', + 'https://w3id.org/security/suites/x25519-2019/v1', + ], + id: sovDid, + alsoKnownAs: undefined, + controller: undefined, + verificationMethod: [ + { + type: 'Ed25519VerificationKey2018', + controller: sovDid, + id: `${sovDid}#key-1`, + publicKeyBase58: expect.any(String), + }, + { + controller: sovDid, + type: 'X25519KeyAgreementKey2019', + id: `${sovDid}#key-agreement-1`, + publicKeyBase58: expect.any(String), + }, + ], + capabilityDelegation: undefined, + capabilityInvocation: undefined, + authentication: [`${sovDid}#key-1`], + assertionMethod: [`${sovDid}#key-1`], + keyAgreement: [`${sovDid}#key-agreement-1`], + service: [ + { + id: `${sovDid}#endpoint`, + serviceEndpoint: 'http://localhost:3000', + type: 'endpoint', + }, + { + id: `${sovDid}#did-communication`, + accept: ['didcomm/aip2;env=rfc19'], + priority: 0, + recipientKeys: [`${sovDid}#key-agreement-1`], + routingKeys: [], + serviceEndpoint: 'http://localhost:3000', + type: 'did-communication', + }, + ], + }, + didDocumentMetadata: {}, + didResolutionMetadata: { + contentType: 'application/did+ld+json', + }, + }) + }) +}) diff --git a/packages/indy-sdk/tsconfig.build.json b/packages/indy-sdk/tsconfig.build.json new file mode 100644 index 0000000000..2b75d0adab --- /dev/null +++ b/packages/indy-sdk/tsconfig.build.json @@ -0,0 +1,7 @@ +{ + "extends": "../../tsconfig.build.json", + "compilerOptions": { + "outDir": "./build" + }, + "include": ["src/**/*"] +} diff --git a/packages/indy-sdk/tsconfig.json b/packages/indy-sdk/tsconfig.json new file mode 100644 index 0000000000..46efe6f721 --- /dev/null +++ b/packages/indy-sdk/tsconfig.json @@ -0,0 +1,6 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "types": ["jest"] + } +} diff --git a/packages/indy-vdr/README.md b/packages/indy-vdr/README.md new file mode 100644 index 0000000000..310b38a4f9 --- /dev/null +++ b/packages/indy-vdr/README.md @@ -0,0 +1,35 @@ +

+
+ Hyperledger Aries logo +

+

Aries Framework JavaScript - Indy Verifiable Data Registry (Indy-Vdr)

+

+ License + typescript + @aries-framework/anoncreds version + +

+
+ +### Installation + +### Quick start + +### Example of usage diff --git a/packages/indy-vdr/jest.config.ts b/packages/indy-vdr/jest.config.ts new file mode 100644 index 0000000000..93c0197296 --- /dev/null +++ b/packages/indy-vdr/jest.config.ts @@ -0,0 +1,13 @@ +import type { Config } from '@jest/types' + +import base from '../../jest.config.base' + +import packageJson from './package.json' + +const config: Config.InitialOptions = { + ...base, + displayName: packageJson.name, + setupFilesAfterEnv: ['./tests/setup.ts'], +} + +export default config diff --git a/packages/didcomm-v2/package.json b/packages/indy-vdr/package.json similarity index 54% rename from packages/didcomm-v2/package.json rename to packages/indy-vdr/package.json index a4426b63f7..86239fd7e7 100644 --- a/packages/didcomm-v2/package.json +++ b/packages/indy-vdr/package.json @@ -1,9 +1,8 @@ { - "name": "@aries-framework/didcomm-v2", + "name": "@aries-framework/indy-vdr", "main": "build/index", "types": "build/index", - "version": "0.3.1", - "private": true, + "version": "0.3.3", "files": [ "build" ], @@ -11,31 +10,29 @@ "publishConfig": { "access": "public" }, - "homepage": "https://github.com/hyperledger/aries-framework-javascript/tree/main/packages/didcomm-v2", + "homepage": "https://github.com/hyperledger/aries-framework-javascript/tree/main/packages/indy-vdr", "repository": { "type": "git", "url": "https://github.com/hyperledger/aries-framework-javascript", - "directory": "packages/didcomm-v2" + "directory": "packages/indy-vdr" }, "scripts": { "build": "yarn run clean && yarn run compile", - "clean": "rimraf -rf ./build", + "clean": "rimraf ./build", "compile": "tsc -p tsconfig.build.json", "prepublishOnly": "yarn run build", "test": "jest" }, "dependencies": { - "didcomm": "0.3.4" - }, - "peerDependencies": { - "@aries-framework/core": "0.3.2" + "@aries-framework/anoncreds": "0.3.3", + "@aries-framework/core": "0.3.3", + "@hyperledger/indy-vdr-shared": "^0.1.0-dev.14" }, "devDependencies": { - "@aries-framework/core": "0.3.2", - "@aries-framework/node": "0.3.2", - "didcomm-node": "0.3.4", - "reflect-metadata": "^0.1.13", - "rimraf": "~3.0.2", - "typescript": "~4.3.0" + "@hyperledger/indy-vdr-nodejs": "^0.1.0-dev.14", + "@stablelib/ed25519": "^1.0.2", + "rimraf": "^4.4.0", + "rxjs": "^7.2.0", + "typescript": "~4.9.5" } } diff --git a/packages/indy-vdr/src/IndyVdrApi.ts b/packages/indy-vdr/src/IndyVdrApi.ts new file mode 100644 index 0000000000..4cd0ff3154 --- /dev/null +++ b/packages/indy-vdr/src/IndyVdrApi.ts @@ -0,0 +1,79 @@ +import type { Key } from '@aries-framework/core' +import type { IndyVdrRequest } from '@hyperledger/indy-vdr-shared' + +import { parseIndyDid } from '@aries-framework/anoncreds' +import { AgentContext, injectable, TypedArrayEncoder } from '@aries-framework/core' +import { CustomRequest } from '@hyperledger/indy-vdr-shared' + +import { verificationKeyForIndyDid } from './dids/didIndyUtil' +import { IndyVdrError } from './error' +import { IndyVdrPoolService } from './pool' + +@injectable() +export class IndyVdrApi { + private agentContext: AgentContext + private indyVdrPoolService: IndyVdrPoolService + + public constructor(agentContext: AgentContext, indyVdrPoolService: IndyVdrPoolService) { + this.agentContext = agentContext + this.indyVdrPoolService = indyVdrPoolService + } + + private async multiSignRequest( + request: Request, + signingKey: Key, + identifier: string + ) { + const signature = await this.agentContext.wallet.sign({ + data: TypedArrayEncoder.fromString(request.signatureInput), + key: signingKey, + }) + + request.setMultiSignature({ + signature, + identifier, + }) + + return request + } + + private async signRequest(request: Request, submitterDid: string) { + const signingKey = await verificationKeyForIndyDid(this.agentContext, submitterDid) + + const { pool } = await this.indyVdrPoolService.getPoolForDid(this.agentContext, submitterDid) + const signedRequest = await pool.prepareWriteRequest(this.agentContext, request, signingKey) + + return signedRequest + } + + /** + * This method endorses a transaction. The transaction can be either a string or a JSON object. + * If the transaction has a signature, it means the transaction was created by another author and will be endorsed. + * This requires the `endorser` on the transaction to be equal to the unqualified variant of the `endorserDid`. + * + * If the transaction is not signed, we have a special case where the endorser will author the transaction. + * This is required when a new did is created, as the author and the endorser did must already exist on the ledger. + * In this case, the author did (`identifier`) must be equal to the unqualified identifier of the `endorserDid`. + * @param transaction the transaction body to be endorsed + * @param endorserDid the did of the endorser + * @returns An endorsed transaction + */ + public async endorseTransaction(transaction: string | Record, endorserDid: string) { + const endorserSigningKey = await verificationKeyForIndyDid(this.agentContext, endorserDid) + const { namespaceIdentifier } = parseIndyDid(endorserDid) + + const request = new CustomRequest({ customRequest: transaction }) + let endorsedTransaction: CustomRequest + + // the request is not parsed correctly due to too large numbers. The reqId overflows. + const txBody = typeof transaction === 'string' ? JSON.parse(transaction) : transaction + if (txBody.signature) { + if (txBody.endorser !== namespaceIdentifier) throw new IndyVdrError('Submitter does not match Endorser') + endorsedTransaction = await this.multiSignRequest(request, endorserSigningKey, namespaceIdentifier) + } else { + if (txBody.identifier !== namespaceIdentifier) throw new IndyVdrError('Submitter does not match identifier') + endorsedTransaction = await this.signRequest(request, endorserDid) + } + return endorsedTransaction.body + } +} diff --git a/packages/indy-vdr/src/IndyVdrModule.ts b/packages/indy-vdr/src/IndyVdrModule.ts new file mode 100644 index 0000000000..ee337fdcaf --- /dev/null +++ b/packages/indy-vdr/src/IndyVdrModule.ts @@ -0,0 +1,39 @@ +import type { IndyVdrModuleConfigOptions } from './IndyVdrModuleConfig' +import type { AgentContext, DependencyManager, Module } from '@aries-framework/core' + +import { IndyVdrApi } from './IndyVdrApi' +import { IndyVdrModuleConfig } from './IndyVdrModuleConfig' +import { IndyVdrPoolService } from './pool/IndyVdrPoolService' + +/** + * @public + * */ +export class IndyVdrModule implements Module { + public readonly config: IndyVdrModuleConfig + public readonly api = IndyVdrApi + + public constructor(config: IndyVdrModuleConfigOptions) { + this.config = new IndyVdrModuleConfig(config) + } + + public register(dependencyManager: DependencyManager) { + // Config + dependencyManager.registerInstance(IndyVdrModuleConfig, this.config) + + // Services + dependencyManager.registerSingleton(IndyVdrPoolService) + + // Api + dependencyManager.registerContextScoped(IndyVdrApi) + } + + public async initialize(agentContext: AgentContext): Promise { + const indyVdrPoolService = agentContext.dependencyManager.resolve(IndyVdrPoolService) + + for (const pool of indyVdrPoolService.pools) { + if (pool.config.connectOnStartup) { + await pool.connect() + } + } + } +} diff --git a/packages/indy-vdr/src/IndyVdrModuleConfig.ts b/packages/indy-vdr/src/IndyVdrModuleConfig.ts new file mode 100644 index 0000000000..086846ddf7 --- /dev/null +++ b/packages/indy-vdr/src/IndyVdrModuleConfig.ts @@ -0,0 +1,78 @@ +import type { IndyVdrPoolConfig } from './pool' +import type { IndyVdr } from '@hyperledger/indy-vdr-shared' + +export interface IndyVdrModuleConfigOptions { + /** + * + * ## Node.JS + * + * ```ts + * import { indyVdr } from '@hyperledger/indy-vdr-nodejs'; + * + * const agent = new Agent({ + * config: {}, + * dependencies: agentDependencies, + * modules: { + * indyVdr: new IndyVdrModule({ + * indyVdr, + * }) + * } + * }) + * ``` + * + * ## React Native + * + * ```ts + * import { indyVdr } from '@hyperledger/indy-vdr-react-native'; + * + * const agent = new Agent({ + * config: {}, + * dependencies: agentDependencies, + * modules: { + * indyVdr: new IndyVdrModule({ + * indyVdr, + * }) + * } + * }) + * ``` + */ + indyVdr: IndyVdr + + /** + * Array of indy networks to connect to. + * + * @default [] + * + * @example + * ``` + * { + * isProduction: false, + * genesisTransactions: 'xxx', + * indyNamespace: 'localhost:test', + * transactionAuthorAgreement: { + * version: '1', + * acceptanceMechanism: 'accept' + * } + * } + * ``` + */ + networks: [IndyVdrPoolConfig, ...IndyVdrPoolConfig[]] +} + +export class IndyVdrModuleConfig { + private options: IndyVdrModuleConfigOptions + + public constructor(options: IndyVdrModuleConfigOptions) { + this.options = options + } + + /** See {@link IndyVdrModuleConfigOptions.networks} */ + public get networks() { + return this.options.networks + } + + /** See {@link IndyVdrModuleConfigOptions.indyVdr} */ + public get indyVdr() { + return this.options.indyVdr + } +} diff --git a/packages/indy-vdr/src/__tests__/IndyVdrModule.test.ts b/packages/indy-vdr/src/__tests__/IndyVdrModule.test.ts new file mode 100644 index 0000000000..ecc5c5d14f --- /dev/null +++ b/packages/indy-vdr/src/__tests__/IndyVdrModule.test.ts @@ -0,0 +1,40 @@ +import type { DependencyManager } from '@aries-framework/core' + +import { indyVdr } from '@hyperledger/indy-vdr-nodejs' + +import { IndyVdrModule } from '../IndyVdrModule' +import { IndyVdrModuleConfig } from '../IndyVdrModuleConfig' +import { IndyVdrPoolService } from '../pool' + +const dependencyManager = { + registerInstance: jest.fn(), + registerSingleton: jest.fn(), + registerContextScoped: jest.fn(), +} as unknown as DependencyManager + +describe('IndyVdrModule', () => { + test('registers dependencies on the dependency manager', () => { + const indyVdrModule = new IndyVdrModule({ + indyVdr, + networks: [ + { + isProduction: false, + genesisTransactions: 'xxx', + indyNamespace: 'localhost:test', + transactionAuthorAgreement: { + version: '1', + acceptanceMechanism: 'accept', + }, + }, + ], + }) + + indyVdrModule.register(dependencyManager) + + expect(dependencyManager.registerSingleton).toHaveBeenCalledTimes(1) + expect(dependencyManager.registerSingleton).toHaveBeenCalledWith(IndyVdrPoolService) + + expect(dependencyManager.registerInstance).toHaveBeenCalledTimes(1) + expect(dependencyManager.registerInstance).toHaveBeenCalledWith(IndyVdrModuleConfig, indyVdrModule.config) + }) +}) diff --git a/packages/indy-vdr/src/__tests__/IndyVdrModuleConfig.test.ts b/packages/indy-vdr/src/__tests__/IndyVdrModuleConfig.test.ts new file mode 100644 index 0000000000..b1abb77784 --- /dev/null +++ b/packages/indy-vdr/src/__tests__/IndyVdrModuleConfig.test.ts @@ -0,0 +1,18 @@ +import type { IndyVdrPoolConfig } from '../pool' + +import { indyVdr } from '@hyperledger/indy-vdr-nodejs' + +import { IndyVdrModuleConfig } from '../IndyVdrModuleConfig' + +describe('IndyVdrModuleConfig', () => { + test('sets values', () => { + const networkConfig = {} as IndyVdrPoolConfig + + const config = new IndyVdrModuleConfig({ + indyVdr, + networks: [networkConfig], + }) + + expect(config.networks).toEqual([networkConfig]) + }) +}) diff --git a/packages/indy-vdr/src/anoncreds/IndyVdrAnonCredsRegistry.ts b/packages/indy-vdr/src/anoncreds/IndyVdrAnonCredsRegistry.ts new file mode 100644 index 0000000000..412d7f91c0 --- /dev/null +++ b/packages/indy-vdr/src/anoncreds/IndyVdrAnonCredsRegistry.ts @@ -0,0 +1,633 @@ +import type { + AnonCredsRegistry, + GetCredentialDefinitionReturn, + GetSchemaReturn, + RegisterSchemaOptions, + RegisterCredentialDefinitionOptions, + RegisterSchemaReturn, + RegisterCredentialDefinitionReturn, + GetRevocationStatusListReturn, + GetRevocationRegistryDefinitionReturn, + AnonCredsRevocationRegistryDefinition, +} from '@aries-framework/anoncreds' +import type { AgentContext } from '@aries-framework/core' + +import { + getUnqualifiedCredentialDefinitionId, + getUnqualifiedRevocationRegistryId, + getUnqualifiedSchemaId, + parseIndyCredentialDefinitionId, + parseIndyDid, + parseIndyRevocationRegistryId, + parseIndySchemaId, +} from '@aries-framework/anoncreds' +import { + GetSchemaRequest, + SchemaRequest, + GetCredentialDefinitionRequest, + CredentialDefinitionRequest, + GetTransactionRequest, + GetRevocationRegistryDeltaRequest, + GetRevocationRegistryDefinitionRequest, +} from '@hyperledger/indy-vdr-shared' + +import { verificationKeyForIndyDid } from '../dids/didIndyUtil' +import { IndyVdrPoolService } from '../pool' + +import { + indyVdrAnonCredsRegistryIdentifierRegex, + getDidIndySchemaId, + getDidIndyCredentialDefinitionId, +} from './utils/identifiers' +import { anonCredsRevocationStatusListFromIndyVdr } from './utils/transform' + +export class IndyVdrAnonCredsRegistry implements AnonCredsRegistry { + public readonly methodName = 'indy' + + public readonly supportedIdentifier = indyVdrAnonCredsRegistryIdentifierRegex + + public async getSchema(agentContext: AgentContext, schemaId: string): Promise { + try { + const indyVdrPoolService = agentContext.dependencyManager.resolve(IndyVdrPoolService) + + // parse schema id (supports did:indy and legacy) + const { did, namespaceIdentifier, schemaName, schemaVersion } = parseIndySchemaId(schemaId) + const { pool } = await indyVdrPoolService.getPoolForDid(agentContext, did) + agentContext.config.logger.debug(`Getting schema '${schemaId}' from ledger '${pool.indyNamespace}'`) + + // even though we support did:indy and legacy identifiers we always need to fetch using the legacy identifier + const legacySchemaId = getUnqualifiedSchemaId(namespaceIdentifier, schemaName, schemaVersion) + const request = new GetSchemaRequest({ schemaId: legacySchemaId }) + + agentContext.config.logger.trace( + `Submitting get schema request for schema '${schemaId}' to ledger '${pool.indyNamespace}'` + ) + const response = await pool.submitRequest(request) + + agentContext.config.logger.trace(`Got un-parsed schema '${schemaId}' from ledger '${pool.indyNamespace}'`, { + response, + }) + + if (!('attr_names' in response.result.data)) { + agentContext.config.logger.error(`Error retrieving schema '${schemaId}'`) + + return { + schemaId, + resolutionMetadata: { + error: 'notFound', + message: `unable to find schema with id ${schemaId}`, + }, + schemaMetadata: {}, + } + } + + return { + schema: { + attrNames: response.result.data.attr_names, + name: response.result.data.name, + version: response.result.data.version, + issuerId: did, + }, + schemaId, + resolutionMetadata: {}, + schemaMetadata: { + didIndyNamespace: pool.indyNamespace, + // NOTE: the seqNo is required by the indy-sdk even though not present in AnonCreds v1. + // For this reason we return it in the metadata. + indyLedgerSeqNo: response.result.seqNo, + }, + } + } catch (error) { + agentContext.config.logger.error(`Error retrieving schema '${schemaId}'`, { + error, + schemaId, + }) + + return { + schemaId, + resolutionMetadata: { + error: 'notFound', + }, + schemaMetadata: {}, + } + } + } + + public async registerSchema( + agentContext: AgentContext, + options: RegisterSchemaOptions + ): Promise { + try { + // This will throw an error if trying to register a schema with a legacy indy identifier. We only support did:indy identifiers + // for registering, that will allow us to extract the namespace and means all stored records will use did:indy identifiers. + const { namespaceIdentifier, namespace } = parseIndyDid(options.schema.issuerId) + + const indyVdrPoolService = agentContext.dependencyManager.resolve(IndyVdrPoolService) + + const pool = indyVdrPoolService.getPoolForNamespace(namespace) + agentContext.config.logger.debug( + `Register schema on ledger '${pool.indyNamespace}' with did '${options.schema.issuerId}'`, + options.schema + ) + + const didIndySchemaId = getDidIndySchemaId( + namespace, + namespaceIdentifier, + options.schema.name, + options.schema.version + ) + const legacySchemaId = getUnqualifiedSchemaId(namespaceIdentifier, options.schema.name, options.schema.version) + + const schemaRequest = new SchemaRequest({ + submitterDid: namespaceIdentifier, + schema: { + id: legacySchemaId, + name: options.schema.name, + ver: '1.0', + version: options.schema.version, + attrNames: options.schema.attrNames, + }, + }) + + const submitterKey = await verificationKeyForIndyDid(agentContext, options.schema.issuerId) + const writeRequest = await pool.prepareWriteRequest(agentContext, schemaRequest, submitterKey) + const response = await pool.submitRequest(writeRequest) + + agentContext.config.logger.debug(`Registered schema '${didIndySchemaId}' on ledger '${pool.indyNamespace}'`, { + response, + schemaRequest, + }) + + return { + schemaState: { + state: 'finished', + schema: { + attrNames: options.schema.attrNames, + issuerId: options.schema.issuerId, + name: options.schema.name, + version: options.schema.version, + }, + schemaId: didIndySchemaId, + }, + registrationMetadata: {}, + schemaMetadata: { + // NOTE: the seqNo is required by the indy-sdk even though not present in AnonCreds v1. + // For this reason we return it in the metadata. + indyLedgerSeqNo: response.result.txnMetadata.seqNo, + }, + } + } catch (error) { + agentContext.config.logger.error(`Error registering schema for did '${options.schema.issuerId}'`, { + error, + did: options.schema.issuerId, + schema: options.schema, + }) + + return { + schemaMetadata: {}, + registrationMetadata: {}, + schemaState: { + state: 'failed', + schema: options.schema, + reason: `unknownError: ${error.message}`, + }, + } + } + } + + public async getCredentialDefinition( + agentContext: AgentContext, + credentialDefinitionId: string + ): Promise { + try { + const indyVdrPoolService = agentContext.dependencyManager.resolve(IndyVdrPoolService) + + // we support did:indy and legacy identifiers + const { did, namespaceIdentifier, schemaSeqNo, tag } = parseIndyCredentialDefinitionId(credentialDefinitionId) + const { pool } = await indyVdrPoolService.getPoolForDid(agentContext, did) + + agentContext.config.logger.debug( + `Getting credential definition '${credentialDefinitionId}' from ledger '${pool.indyNamespace}'` + ) + + const legacyCredentialDefinitionId = getUnqualifiedCredentialDefinitionId(namespaceIdentifier, schemaSeqNo, tag) + const request = new GetCredentialDefinitionRequest({ + credentialDefinitionId: legacyCredentialDefinitionId, + }) + + agentContext.config.logger.trace( + `Submitting get credential definition request for credential definition '${credentialDefinitionId}' to ledger '${pool.indyNamespace}'` + ) + const response = await pool.submitRequest(request) + + // We need to fetch the schema to determine the schemaId (we only have the seqNo) + const schema = await this.fetchIndySchemaWithSeqNo(agentContext, response.result.ref, namespaceIdentifier) + + if (!schema || !response.result.data) { + agentContext.config.logger.error(`Error retrieving credential definition '${credentialDefinitionId}'`) + + return { + credentialDefinitionId, + credentialDefinitionMetadata: {}, + resolutionMetadata: { + error: 'notFound', + message: `unable to resolve credential definition with id ${credentialDefinitionId}`, + }, + } + } + + // Format the schema id based on the type of the credential definition id + const schemaId = credentialDefinitionId.startsWith('did:indy') + ? getDidIndySchemaId(pool.indyNamespace, namespaceIdentifier, schema.schema.name, schema.schema.version) + : schema.schema.schemaId + + return { + credentialDefinitionId: credentialDefinitionId, + credentialDefinition: { + issuerId: did, + schemaId, + tag: response.result.tag, + type: 'CL', + value: response.result.data, + }, + credentialDefinitionMetadata: { + didIndyNamespace: pool.indyNamespace, + }, + resolutionMetadata: {}, + } + } catch (error) { + agentContext.config.logger.error(`Error retrieving credential definition '${credentialDefinitionId}'`, { + error, + credentialDefinitionId, + }) + + return { + credentialDefinitionId, + credentialDefinitionMetadata: {}, + resolutionMetadata: { + error: 'notFound', + message: `unable to resolve credential definition: ${error.message}`, + }, + } + } + } + + public async registerCredentialDefinition( + agentContext: AgentContext, + options: RegisterCredentialDefinitionOptions + ): Promise { + try { + // This will throw an error if trying to register a credential defintion with a legacy indy identifier. We only support did:indy + // identifiers for registering, that will allow us to extract the namespace and means all stored records will use did:indy identifiers. + const { namespaceIdentifier, namespace } = parseIndyDid(options.credentialDefinition.issuerId) + + const indyVdrPoolService = agentContext.dependencyManager.resolve(IndyVdrPoolService) + + const pool = indyVdrPoolService.getPoolForNamespace(namespace) + agentContext.config.logger.debug( + `Registering credential definition on ledger '${pool.indyNamespace}' with did '${options.credentialDefinition.issuerId}'`, + options.credentialDefinition + ) + + // TODO: this will bypass caching if done on a higher level. + const { schema, schemaMetadata, resolutionMetadata } = await this.getSchema( + agentContext, + options.credentialDefinition.schemaId + ) + + if (!schema || !schemaMetadata.indyLedgerSeqNo || typeof schemaMetadata.indyLedgerSeqNo !== 'number') { + return { + registrationMetadata: {}, + credentialDefinitionMetadata: { + didIndyNamespace: pool.indyNamespace, + }, + credentialDefinitionState: { + credentialDefinition: options.credentialDefinition, + state: 'failed', + reason: `error resolving schema with id ${options.credentialDefinition.schemaId}: ${resolutionMetadata.error} ${resolutionMetadata.message}`, + }, + } + } + + const legacyCredentialDefinitionId = getUnqualifiedCredentialDefinitionId( + options.credentialDefinition.issuerId, + schemaMetadata.indyLedgerSeqNo, + options.credentialDefinition.tag + ) + const didIndyCredentialDefinitionId = getDidIndyCredentialDefinitionId( + namespace, + namespaceIdentifier, + schemaMetadata.indyLedgerSeqNo, + options.credentialDefinition.tag + ) + + const credentialDefinitionRequest = new CredentialDefinitionRequest({ + submitterDid: namespaceIdentifier, + credentialDefinition: { + ver: '1.0', + id: legacyCredentialDefinitionId, + schemaId: `${schemaMetadata.indyLedgerSeqNo}`, + type: 'CL', + tag: options.credentialDefinition.tag, + value: options.credentialDefinition.value, + }, + }) + + const submitterKey = await verificationKeyForIndyDid(agentContext, options.credentialDefinition.issuerId) + const writeRequest = await pool.prepareWriteRequest(agentContext, credentialDefinitionRequest, submitterKey) + const response = await pool.submitRequest(writeRequest) + agentContext.config.logger.debug( + `Registered credential definition '${didIndyCredentialDefinitionId}' on ledger '${pool.indyNamespace}'`, + { + response, + credentialDefinition: options.credentialDefinition, + } + ) + + return { + credentialDefinitionMetadata: {}, + credentialDefinitionState: { + credentialDefinition: options.credentialDefinition, + credentialDefinitionId: didIndyCredentialDefinitionId, + state: 'finished', + }, + registrationMetadata: {}, + } + } catch (error) { + agentContext.config.logger.error( + `Error registering credential definition for schema '${options.credentialDefinition.schemaId}'`, + { + error, + did: options.credentialDefinition.issuerId, + credentialDefinition: options.credentialDefinition, + } + ) + + return { + credentialDefinitionMetadata: {}, + registrationMetadata: {}, + credentialDefinitionState: { + credentialDefinition: options.credentialDefinition, + state: 'failed', + reason: `unknownError: ${error.message}`, + }, + } + } + } + + public async getRevocationRegistryDefinition( + agentContext: AgentContext, + revocationRegistryDefinitionId: string + ): Promise { + try { + const indySdkPoolService = agentContext.dependencyManager.resolve(IndyVdrPoolService) + + const { did, namespaceIdentifier, credentialDefinitionTag, revocationRegistryTag, schemaSeqNo } = + parseIndyRevocationRegistryId(revocationRegistryDefinitionId) + const { pool } = await indySdkPoolService.getPoolForDid(agentContext, did) + + agentContext.config.logger.debug( + `Using ledger '${pool.indyNamespace}' to retrieve revocation registry definition '${revocationRegistryDefinitionId}'` + ) + + const legacyRevocationRegistryId = getUnqualifiedRevocationRegistryId( + namespaceIdentifier, + schemaSeqNo, + credentialDefinitionTag, + revocationRegistryTag + ) + const request = new GetRevocationRegistryDefinitionRequest({ + revocationRegistryId: legacyRevocationRegistryId, + }) + + agentContext.config.logger.trace( + `Submitting get revocation registry definition request for revocation registry definition '${revocationRegistryDefinitionId}' to ledger` + ) + const response = await pool.submitRequest(request) + + if (!response.result.data) { + agentContext.config.logger.error( + `Error retrieving revocation registry definition '${revocationRegistryDefinitionId}' from ledger`, + { + revocationRegistryDefinitionId, + } + ) + + return { + resolutionMetadata: { + error: 'notFound', + message: `unable to resolve revocation registry definition`, + }, + revocationRegistryDefinitionId, + revocationRegistryDefinitionMetadata: {}, + } + } + + agentContext.config.logger.trace( + `Got revocation registry definition '${revocationRegistryDefinitionId}' from ledger '${pool.indyNamespace}'`, + { + response, + } + ) + + const credentialDefinitionId = revocationRegistryDefinitionId.startsWith('did:indy:') + ? getDidIndyCredentialDefinitionId( + pool.indyNamespace, + namespaceIdentifier, + schemaSeqNo, + credentialDefinitionTag + ) + : getUnqualifiedCredentialDefinitionId(namespaceIdentifier, schemaSeqNo, credentialDefinitionTag) + + const revocationRegistryDefinition = { + issuerId: did, + revocDefType: response.result.data.revocDefType, + value: { + maxCredNum: response.result.data.value.maxCredNum, + tailsHash: response.result.data.value.tailsHash, + tailsLocation: response.result.data.value.tailsLocation, + publicKeys: { + accumKey: { + z: response.result.data.value.publicKeys.accumKey.z, + }, + }, + }, + tag: response.result.data.tag, + credDefId: credentialDefinitionId, + } satisfies AnonCredsRevocationRegistryDefinition + + return { + revocationRegistryDefinitionId, + revocationRegistryDefinition, + revocationRegistryDefinitionMetadata: { + issuanceType: response.result.data.value.issuanceType, + didIndyNamespace: pool.indyNamespace, + }, + resolutionMetadata: {}, + } + } catch (error) { + agentContext.config.logger.error( + `Error retrieving revocation registry definition '${revocationRegistryDefinitionId}' from ledger`, + { + error, + revocationRegistryDefinitionId, + } + ) + + return { + resolutionMetadata: { + error: 'notFound', + message: `unable to resolve revocation registry definition: ${error.message}`, + }, + revocationRegistryDefinitionId, + revocationRegistryDefinitionMetadata: {}, + } + } + } + + public async getRevocationStatusList( + agentContext: AgentContext, + revocationRegistryId: string, + timestamp: number + ): Promise { + try { + const indySdkPoolService = agentContext.dependencyManager.resolve(IndyVdrPoolService) + + const { did, namespaceIdentifier, schemaSeqNo, credentialDefinitionTag, revocationRegistryTag } = + parseIndyRevocationRegistryId(revocationRegistryId) + const { pool } = await indySdkPoolService.getPoolForDid(agentContext, did) + + agentContext.config.logger.debug( + `Using ledger '${pool.indyNamespace}' to retrieve revocation registry deltas with revocation registry definition id '${revocationRegistryId}' until ${timestamp}` + ) + + const legacyRevocationRegistryId = getUnqualifiedRevocationRegistryId( + namespaceIdentifier, + schemaSeqNo, + credentialDefinitionTag, + revocationRegistryTag + ) + const request = new GetRevocationRegistryDeltaRequest({ + revocationRegistryId: legacyRevocationRegistryId, + toTs: timestamp, + }) + + agentContext.config.logger.trace( + `Submitting get revocation registry delta request for revocation registry '${revocationRegistryId}' to ledger` + ) + const response = await pool.submitRequest(request) + + agentContext.config.logger.debug( + `Got revocation registry deltas '${revocationRegistryId}' until timestamp ${timestamp} from ledger` + ) + + const { revocationRegistryDefinition, resolutionMetadata, revocationRegistryDefinitionMetadata } = + await this.getRevocationRegistryDefinition(agentContext, revocationRegistryId) + + if ( + !revocationRegistryDefinition || + !revocationRegistryDefinitionMetadata.issuanceType || + typeof revocationRegistryDefinitionMetadata.issuanceType !== 'string' + ) { + return { + resolutionMetadata: { + error: `error resolving revocation registry definition with id ${revocationRegistryId}: ${resolutionMetadata.error} ${resolutionMetadata.message}`, + }, + revocationStatusListMetadata: { + didIndyNamespace: pool.indyNamespace, + }, + } + } + + const isIssuanceByDefault = revocationRegistryDefinitionMetadata.issuanceType === 'ISSUANCE_BY_DEFAULT' + + if (!response.result.data) { + return { + resolutionMetadata: { + error: 'notFound', + message: `Error retrieving revocation registry delta '${revocationRegistryId}' from ledger, potentially revocation interval ends before revocation registry creation`, + }, + revocationStatusListMetadata: {}, + } + } + + const revocationRegistryDelta = { + accum: response.result.data.value.accum_to.value.accum, + issued: response.result.data.value.issued, + revoked: response.result.data.value.revoked, + } + + return { + resolutionMetadata: {}, + revocationStatusList: anonCredsRevocationStatusListFromIndyVdr( + revocationRegistryId, + revocationRegistryDefinition, + revocationRegistryDelta, + response.result.data.value.accum_to.txnTime, + isIssuanceByDefault + ), + revocationStatusListMetadata: { + didIndyNamespace: pool.indyNamespace, + }, + } + } catch (error) { + agentContext.config.logger.error( + `Error retrieving revocation registry delta '${revocationRegistryId}' from ledger, potentially revocation interval ends before revocation registry creation?"`, + { + error, + revocationRegistryId: revocationRegistryId, + } + ) + + return { + resolutionMetadata: { + error: 'notFound', + message: `Error retrieving revocation registry delta '${revocationRegistryId}' from ledger, potentially revocation interval ends before revocation registry creation: ${error.message}`, + }, + revocationStatusListMetadata: {}, + } + } + } + + private async fetchIndySchemaWithSeqNo(agentContext: AgentContext, seqNo: number, did: string) { + const indyVdrPoolService = agentContext.dependencyManager.resolve(IndyVdrPoolService) + + const { pool } = await indyVdrPoolService.getPoolForDid(agentContext, did) + + agentContext.config.logger.debug(`Getting transaction with seqNo '${seqNo}' from ledger '${pool.indyNamespace}'`) + // ledgerType 1 is domain ledger + const request = new GetTransactionRequest({ ledgerType: 1, seqNo }) + + agentContext.config.logger.trace(`Submitting get transaction request to ledger '${pool.indyNamespace}'`) + const response = await pool.submitRequest(request) + + if (response.result.data?.txn.type !== '101') { + agentContext.config.logger.error(`Could not get schema from ledger for seq no ${seqNo}'`) + return null + } + + const schema = response.result.data?.txn.data as SchemaType + + const schemaId = getUnqualifiedSchemaId(did, schema.data.name, schema.data.version) + + return { + schema: { + schemaId, + attr_name: schema.data.attr_names, + name: schema.data.name, + version: schema.data.version, + issuerId: did, + seqNo, + }, + indyNamespace: pool.indyNamespace, + } + } +} + +interface SchemaType { + data: { + attr_names: string[] + version: string + name: string + } +} diff --git a/packages/indy-vdr/src/anoncreds/index.ts b/packages/indy-vdr/src/anoncreds/index.ts new file mode 100644 index 0000000000..c1e469b307 --- /dev/null +++ b/packages/indy-vdr/src/anoncreds/index.ts @@ -0,0 +1 @@ +export * from './IndyVdrAnonCredsRegistry' diff --git a/packages/indy-vdr/src/anoncreds/utils/__tests__/identifiers.test.ts b/packages/indy-vdr/src/anoncreds/utils/__tests__/identifiers.test.ts new file mode 100644 index 0000000000..b96720611b --- /dev/null +++ b/packages/indy-vdr/src/anoncreds/utils/__tests__/identifiers.test.ts @@ -0,0 +1,79 @@ +import { + getDidIndyCredentialDefinitionId, + getDidIndyRevocationRegistryId, + getDidIndySchemaId, + indyVdrAnonCredsRegistryIdentifierRegex, +} from '../identifiers' + +describe('identifiers', () => { + describe('indyVdrAnonCredsRegistryIdentifierRegex', () => { + test('matches against a legacy schema id, credential definition id and revocation registry id', () => { + const did = '7Tqg6BwSSWapxgUDm9KKgg' + const schemaId = 'BQ42WeE24jFHeyGg8x9XAz:2:Medical Bill:1.0' + const credentialDefinitionId = 'N7baRMcyvPwWc8v85CtZ6e:3:CL:100669:SCH Employee ID' + const revocationRegistryId = + 'N7baRMcyvPwWc8v85CtZ6e:4:N7baRMcyvPwWc8v85CtZ6e:3:CL:100669:SCH Employee ID:CL_ACCUM:1-1024' + + const anotherId = 'some:id' + + // unqualified issuerId not in regex on purpose. See note in implementation. + expect(indyVdrAnonCredsRegistryIdentifierRegex.test(did)).toEqual(false) + + expect(indyVdrAnonCredsRegistryIdentifierRegex.test(schemaId)).toEqual(true) + expect(indyVdrAnonCredsRegistryIdentifierRegex.test(credentialDefinitionId)).toEqual(true) + expect(indyVdrAnonCredsRegistryIdentifierRegex.test(revocationRegistryId)).toEqual(true) + expect(indyVdrAnonCredsRegistryIdentifierRegex.test(anotherId)).toEqual(false) + }) + + test('matches against a did indy did, schema id, credential definition id and revocation registry id', () => { + const did = 'did:indy:local:7Tqg6BwSSWapxgUDm9KKgg' + const schemaId = 'did:indy:local:BQ42WeE24jFHeyGg8x9XAz/anoncreds/v0/SCHEMA/Medical Bill/1.0' + const credentialDefinitionId = + 'did:indy:local:N7baRMcyvPwWc8v85CtZ6e/anoncreds/v0/CLAIM_DEF/100669/SCH Employee ID' + const revocationRegistryId = + 'did:indy:local:N7baRMcyvPwWc8v85CtZ6e/anoncreds/v0/REV_REG_DEF/100669/SCH Employee ID/1-1024' + + const anotherId = 'did:indy:local:N7baRMcyvPwWc8v85CtZ6e/anoncreds/v0/SOME_DEF' + + expect(indyVdrAnonCredsRegistryIdentifierRegex.test(did)).toEqual(true) + expect(indyVdrAnonCredsRegistryIdentifierRegex.test(schemaId)).toEqual(true) + expect(indyVdrAnonCredsRegistryIdentifierRegex.test(credentialDefinitionId)).toEqual(true) + expect(indyVdrAnonCredsRegistryIdentifierRegex.test(revocationRegistryId)).toEqual(true) + expect(indyVdrAnonCredsRegistryIdentifierRegex.test(anotherId)).toEqual(false) + }) + }) + + test('getDidIndySchemaId returns a valid schema id given a did, name, and version', () => { + const namespace = 'sovrin:test' + const did = '12345' + const name = 'backbench' + const version = '420' + + expect(getDidIndySchemaId(namespace, did, name, version)).toEqual( + 'did:indy:sovrin:test:12345/anoncreds/v0/SCHEMA/backbench/420' + ) + }) + + test('getDidIndyCredentialDefinitionId returns a valid credential definition id given a did, seqNo, and tag', () => { + const namespace = 'sovrin:test' + const did = '12345' + const seqNo = 420 + const tag = 'someTag' + + expect(getDidIndyCredentialDefinitionId(namespace, did, seqNo, tag)).toEqual( + 'did:indy:sovrin:test:12345/anoncreds/v0/CLAIM_DEF/420/someTag' + ) + }) + + test('getDidIndyRevocationRegistryId returns a valid credential definition id given a did, seqNo, and tag', () => { + const namespace = 'sovrin:test' + const did = '12345' + const seqNo = 420 + const credentialDefinitionTag = 'someTag' + const tag = 'anotherTag' + + expect(getDidIndyRevocationRegistryId(namespace, did, seqNo, credentialDefinitionTag, tag)).toEqual( + 'did:indy:sovrin:test:12345/anoncreds/v0/REV_REG_DEF/420/someTag/anotherTag' + ) + }) +}) diff --git a/packages/indy-vdr/src/anoncreds/utils/identifiers.ts b/packages/indy-vdr/src/anoncreds/utils/identifiers.ts new file mode 100644 index 0000000000..1e9d6a8fd3 --- /dev/null +++ b/packages/indy-vdr/src/anoncreds/utils/identifiers.ts @@ -0,0 +1,63 @@ +/** + * NOTE: this file is availalbe in both the indy-sdk and indy-vdr packages. If making changes to + * this file, make sure to update both files if applicable. + */ + +import { + unqualifiedSchemaIdRegex, + unqualifiedCredentialDefinitionIdRegex, + unqualifiedRevocationRegistryIdRegex, + didIndyCredentialDefinitionIdRegex, + didIndyRevocationRegistryIdRegex, + didIndySchemaIdRegex, + didIndyRegex, +} from '@aries-framework/anoncreds' + +// combines both legacy and did:indy anoncreds identifiers and also the issuer id +const indyVdrAnonCredsRegexes = [ + // NOTE: we only include the qualified issuer id here, as we don't support registering objects based on legacy issuer ids. + // you can still resolve using legacy issuer ids, but you need to use the full did:indy identifier when registering. + // As we find a matching anoncreds registry based on the issuerId only when creating an object, this will make sure + // it will throw an no registry found for identifier error. + // issuer id + didIndyRegex, + + // schema + didIndySchemaIdRegex, + unqualifiedSchemaIdRegex, + + // credential definition + didIndyCredentialDefinitionIdRegex, + unqualifiedCredentialDefinitionIdRegex, + + // revocation registry + unqualifiedRevocationRegistryIdRegex, + didIndyRevocationRegistryIdRegex, +] + +export const indyVdrAnonCredsRegistryIdentifierRegex = new RegExp( + indyVdrAnonCredsRegexes.map((r) => r.source).join('|') +) + +export function getDidIndySchemaId(namespace: string, unqualifiedDid: string, name: string, version: string) { + return `did:indy:${namespace}:${unqualifiedDid}/anoncreds/v0/SCHEMA/${name}/${version}` +} + +export function getDidIndyCredentialDefinitionId( + namespace: string, + unqualifiedDid: string, + seqNo: string | number, + tag: string +) { + return `did:indy:${namespace}:${unqualifiedDid}/anoncreds/v0/CLAIM_DEF/${seqNo}/${tag}` +} + +export function getDidIndyRevocationRegistryId( + namespace: string, + unqualifiedDid: string, + seqNo: string | number, + credentialDefinitionTag: string, + revocationRegistryTag: string +) { + return `did:indy:${namespace}:${unqualifiedDid}/anoncreds/v0/REV_REG_DEF/${seqNo}/${credentialDefinitionTag}/${revocationRegistryTag}` +} diff --git a/packages/indy-vdr/src/anoncreds/utils/transform.ts b/packages/indy-vdr/src/anoncreds/utils/transform.ts new file mode 100644 index 0000000000..36f4628bb4 --- /dev/null +++ b/packages/indy-vdr/src/anoncreds/utils/transform.ts @@ -0,0 +1,39 @@ +import type { AnonCredsRevocationStatusList, AnonCredsRevocationRegistryDefinition } from '@aries-framework/anoncreds' + +export function anonCredsRevocationStatusListFromIndyVdr( + revocationRegistryDefinitionId: string, + revocationRegistryDefinition: AnonCredsRevocationRegistryDefinition, + delta: RevocRegDelta, + timestamp: number, + isIssuanceByDefault: boolean +): AnonCredsRevocationStatusList { + // 0 means unrevoked, 1 means revoked + const defaultState = isIssuanceByDefault ? 0 : 1 + + // Fill with default value + const revocationList = new Array(revocationRegistryDefinition.value.maxCredNum).fill(defaultState) + + // Set all `issuer` indexes to 0 (not revoked) + for (const issued of delta.issued ?? []) { + revocationList[issued] = 0 + } + + // Set all `revoked` indexes to 1 (revoked) + for (const revoked of delta.revoked ?? []) { + revocationList[revoked] = 1 + } + + return { + issuerId: revocationRegistryDefinition.issuerId, + currentAccumulator: delta.accum, + revRegDefId: revocationRegistryDefinitionId, + revocationList, + timestamp, + } +} + +interface RevocRegDelta { + accum: string + issued: number[] + revoked: number[] +} diff --git a/packages/indy-vdr/src/dids/IndyVdrIndyDidRegistrar.ts b/packages/indy-vdr/src/dids/IndyVdrIndyDidRegistrar.ts new file mode 100644 index 0000000000..7f998e247d --- /dev/null +++ b/packages/indy-vdr/src/dids/IndyVdrIndyDidRegistrar.ts @@ -0,0 +1,568 @@ +import type { IndyEndpointAttrib } from './didSovUtil' +import type { IndyVdrPool } from '../pool' +import type { + AgentContext, + Buffer, + DidCreateOptions, + DidCreateResult, + DidDeactivateResult, + DidDocument, + DidDocumentService, + DidOperationStateActionBase, + DidRegistrar, + DidUpdateResult, +} from '@aries-framework/core' +import type { IndyVdrRequest } from '@hyperledger/indy-vdr-shared' + +import { parseIndyDid } from '@aries-framework/anoncreds' +import { + DidCommV1Service, + DidCommV2Service, + DidDocumentRole, + DidRecord, + DidRepository, + Hasher, + IndyAgentService, + Key, + KeyType, + TypedArrayEncoder, +} from '@aries-framework/core' +import { AttribRequest, CustomRequest, NymRequest } from '@hyperledger/indy-vdr-shared' + +import { IndyVdrError } from '../error' +import { IndyVdrPoolService } from '../pool/IndyVdrPoolService' + +import { + buildDidDocument, + createKeyAgreementKey, + didDocDiff, + indyDidDocumentFromDid, + isSelfCertifiedIndyDid, + verificationKeyForIndyDid, +} from './didIndyUtil' +import { endpointsAttribFromServices } from './didSovUtil' + +export class IndyVdrIndyDidRegistrar implements DidRegistrar { + public readonly supportedMethods = ['indy'] + + private didCreateActionResult({ + namespace, + didAction, + did, + }: { + namespace: string + didAction: EndorseDidTxAction + did: string + }): IndyVdrDidCreateResult { + return { + jobId: did, + didDocumentMetadata: {}, + didRegistrationMetadata: { + didIndyNamespace: namespace, + }, + didState: didAction, + } + } + + private didCreateFailedResult({ reason }: { reason: string }): IndyVdrDidCreateResult { + return { + didDocumentMetadata: {}, + didRegistrationMetadata: {}, + didState: { + state: 'failed', + reason: reason, + }, + } + } + + private didCreateFinishedResult({ + seed, + privateKey, + did, + didDocument, + namespace, + }: { + seed: Buffer | undefined + privateKey: Buffer | undefined + did: string + didDocument: DidDocument + namespace: string + }): IndyVdrDidCreateResult { + return { + didDocumentMetadata: {}, + didRegistrationMetadata: { + didIndyNamespace: namespace, + }, + didState: { + state: 'finished', + did, + didDocument, + secret: { + // FIXME: the uni-registrar creates the seed in the registrar method + // if it doesn't exist so the seed can always be returned. Currently + // we can only return it if the seed was passed in by the user. Once + // we have a secure method for generating seeds we should use the same + // approach + seed: seed, + privateKey: privateKey, + }, + }, + } + } + + public async parseInput(agentContext: AgentContext, options: IndyVdrDidCreateOptions): Promise { + let did = options.did + let namespaceIdentifier: string + let verificationKey: Key + const seed = options.secret?.seed + const privateKey = options.secret?.privateKey + + if (options.options.endorsedTransaction) { + const _did = did as string + const { namespace } = parseIndyDid(_did) + // endorser did from the transaction + const endorserNamespaceIdentifier = JSON.parse(options.options.endorsedTransaction.nymRequest).identifier + + return { + status: 'ok', + did: _did, + namespace: namespace, + namespaceIdentifier: parseIndyDid(_did).namespaceIdentifier, + endorserNamespaceIdentifier, + seed, + privateKey, + } + } + + const endorserDid = options.options.endorserDid + const { namespace: endorserNamespace, namespaceIdentifier: endorserNamespaceIdentifier } = parseIndyDid(endorserDid) + + const allowOne = [privateKey, seed, did].filter((e) => e !== undefined) + if (allowOne.length > 1) { + return { + status: 'error', + reason: `Only one of 'seed', 'privateKey' and 'did' must be provided`, + } + } + + if (did) { + if (!options.options.verkey) { + return { + status: 'error', + reason: 'If a did is defined, a matching verkey must be provided', + } + } + + const { namespace: didNamespace, namespaceIdentifier: didNamespaceIdentifier } = parseIndyDid(did) + namespaceIdentifier = didNamespaceIdentifier + verificationKey = Key.fromPublicKeyBase58(options.options.verkey, KeyType.Ed25519) + + if (!isSelfCertifiedIndyDid(did, options.options.verkey)) { + return { + status: 'error', + reason: `Initial verkey ${options.options.verkey} does not match did ${did}`, + } + } + + if (didNamespace !== endorserNamespace) { + return { + status: 'error', + reason: `The endorser did uses namespace: '${endorserNamespace}' and the did to register uses namespace: '${didNamespace}'. Namespaces must match.`, + } + } + } else { + // Create a new key and calculate did according to the rules for indy did method + verificationKey = await agentContext.wallet.createKey({ privateKey, seed, keyType: KeyType.Ed25519 }) + const buffer = Hasher.hash(verificationKey.publicKey, 'sha2-256') + + namespaceIdentifier = TypedArrayEncoder.toBase58(buffer.slice(0, 16)) + did = `did:indy:${endorserNamespace}:${namespaceIdentifier}` + } + + return { + status: 'ok', + did, + verificationKey, + namespaceIdentifier, + namespace: endorserNamespace, + endorserNamespaceIdentifier, + seed, + privateKey, + } + } + + public async saveDidRecord(agentContext: AgentContext, did: string, didDocument: DidDocument): Promise { + // Save the did so we know we created it and can issue with it + const didRecord = new DidRecord({ + did, + role: DidDocumentRole.Created, + tags: { + recipientKeyFingerprints: didDocument.recipientKeys.map((key: Key) => key.fingerprint), + }, + }) + + const didRepository = agentContext.dependencyManager.resolve(DidRepository) + await didRepository.save(agentContext, didRecord) + } + + private createDidDocument( + did: string, + verificationKey: Key, + services: DidDocumentService[] | undefined, + useEndpointAttrib: boolean | undefined + ) { + // Create base did document + const didDocumentBuilder = indyDidDocumentFromDid(did, verificationKey.publicKeyBase58) + let diddocContent + + // Add services if object was passed + if (services) { + services.forEach((item) => { + const prependDidIfNotPresent = (id: string) => { + return id.startsWith('#') ? `${did}${id}` : id + } + + // Prepend the did to the service id if it is not already there + item.id = prependDidIfNotPresent(item.id) + + // TODO: should we also prepend the did to routingKeys? + if (item instanceof DidCommV1Service) { + item.recipientKeys = item.recipientKeys.map(prependDidIfNotPresent) + } + + didDocumentBuilder.addService(item) + }) + + const commTypes = [IndyAgentService.type, DidCommV1Service.type, DidCommV2Service.type] + const serviceTypes = new Set(services.map((item) => item.type)) + + const keyAgreementId = `${did}#key-agreement-1` + + // If there is at least a communication service, add the key agreement key + if (commTypes.some((type) => serviceTypes.has(type))) { + didDocumentBuilder + .addContext('https://w3id.org/security/suites/x25519-2019/v1') + .addVerificationMethod({ + controller: did, + id: keyAgreementId, + publicKeyBase58: createKeyAgreementKey(verificationKey.publicKeyBase58), + type: 'X25519KeyAgreementKey2019', + }) + .addKeyAgreement(keyAgreementId) + } + + // If there is a DIDComm V2 service, add context + if (serviceTypes.has(DidCommV2Service.type)) { + didDocumentBuilder.addContext('https://didcomm.org/messaging/contexts/v2') + } + + if (!useEndpointAttrib) { + // create diddocContent parameter based on the diff between the base and the resulting DID Document + diddocContent = didDocDiff( + didDocumentBuilder.build().toJSON(), + indyDidDocumentFromDid(did, verificationKey.publicKeyBase58).build().toJSON() + ) + } + } + + // Build did document + const didDocument = didDocumentBuilder.build() + + return { + diddocContent, + didDocument, + } + } + + public async create(agentContext: AgentContext, options: IndyVdrDidCreateOptions): Promise { + try { + const res = await this.parseInput(agentContext, options) + if (res.status === 'error') return this.didCreateFailedResult({ reason: res.reason }) + + const { did, namespaceIdentifier, endorserNamespaceIdentifier, verificationKey, namespace, seed, privateKey } = + res + + const pool = agentContext.dependencyManager.resolve(IndyVdrPoolService).getPoolForNamespace(namespace) + + let nymRequest: NymRequest | CustomRequest + let didDocument: DidDocument | undefined + let attribRequest: AttribRequest | CustomRequest | undefined + let alias: string | undefined + + if (options.options.endorsedTransaction) { + const { nymRequest: _nymRequest, attribRequest: _attribRequest } = options.options.endorsedTransaction + nymRequest = new CustomRequest({ customRequest: _nymRequest }) + attribRequest = _attribRequest ? new CustomRequest({ customRequest: _attribRequest }) : undefined + } else { + const { services, useEndpointAttrib } = options.options + alias = options.options.alias + if (!verificationKey) throw new Error('VerificationKey not defined') + + const { didDocument: _didDocument, diddocContent } = this.createDidDocument( + did, + verificationKey, + services, + useEndpointAttrib + ) + didDocument = _didDocument + + let didRegisterSigningKey: Key | undefined = undefined + if (options.options.endorserMode === 'internal') + didRegisterSigningKey = await verificationKeyForIndyDid(agentContext, options.options.endorserDid) + + nymRequest = await this.createRegisterDidWriteRequest({ + agentContext, + pool, + signingKey: didRegisterSigningKey, + submitterNamespaceIdentifier: endorserNamespaceIdentifier, + namespaceIdentifier, + verificationKey, + alias, + diddocContent, + }) + + if (services && useEndpointAttrib) { + const endpoints = endpointsAttribFromServices(services) + attribRequest = await this.createSetDidEndpointsRequest({ + agentContext, + pool, + signingKey: verificationKey, + endorserDid: options.options.endorserMode === 'external' ? options.options.endorserDid : undefined, + unqualifiedDid: namespaceIdentifier, + endpoints, + }) + } + + if (options.options.endorserMode === 'external') { + const didAction: EndorseDidTxAction = { + state: 'action', + action: 'endorseIndyTransaction', + endorserDid: options.options.endorserDid, + nymRequest: nymRequest.body, + attribRequest: attribRequest?.body, + did: did, + secret: { seed, privateKey }, + } + + return this.didCreateActionResult({ namespace, didAction, did }) + } + } + await this.registerPublicDid(agentContext, pool, nymRequest) + if (attribRequest) await this.setEndpointsForDid(agentContext, pool, attribRequest) + didDocument = didDocument ?? (await buildDidDocument(agentContext, pool, did)) + await this.saveDidRecord(agentContext, did, didDocument) + return this.didCreateFinishedResult({ did, didDocument, namespace, seed, privateKey }) + } catch (error) { + return this.didCreateFailedResult({ reason: `unknownError: ${error.message}` }) + } + } + + public async update(): Promise { + return { + didDocumentMetadata: {}, + didRegistrationMetadata: {}, + didState: { + state: 'failed', + reason: `notImplemented: updating did:indy not implemented yet`, + }, + } + } + + public async deactivate(): Promise { + return { + didDocumentMetadata: {}, + didRegistrationMetadata: {}, + didState: { + state: 'failed', + reason: `notImplemented: deactivating did:indy not implemented yet`, + }, + } + } + + private async createRegisterDidWriteRequest(options: { + agentContext: AgentContext + pool: IndyVdrPool + submitterNamespaceIdentifier: string + namespaceIdentifier: string + verificationKey: Key + signingKey?: Key + alias: string | undefined + diddocContent?: Record + }) { + const { + agentContext, + pool, + submitterNamespaceIdentifier, + namespaceIdentifier, + verificationKey, + alias, + signingKey, + } = options + + // FIXME: Add diddocContent when supported by indy-vdr + if (options.diddocContent) { + throw new IndyVdrError('diddocContent is not yet supported') + } + + const request = new NymRequest({ + submitterDid: submitterNamespaceIdentifier, + dest: namespaceIdentifier, + verkey: verificationKey.publicKeyBase58, + alias: alias, + }) + + if (!signingKey) return request + const writeRequest = await pool.prepareWriteRequest(agentContext, request, signingKey, undefined) + return writeRequest + } + + private async registerPublicDid( + agentContext: AgentContext, + pool: IndyVdrPool, + writeRequest: Request + ) { + const body = writeRequest.body + try { + const response = await pool.submitRequest(writeRequest) + + agentContext.config.logger.debug(`Register public did on ledger '${pool.indyNamespace}'\nRequest: ${body}}`, { + response, + }) + + return + } catch (error) { + agentContext.config.logger.error( + `Error Registering public did on ledger '${pool.indyNamespace}'\nRequest: ${body}}` + ) + + throw error + } + } + + private async createSetDidEndpointsRequest(options: { + agentContext: AgentContext + pool: IndyVdrPool + signingKey: Key + endorserDid?: string + unqualifiedDid: string + endpoints: IndyEndpointAttrib + }): Promise { + const { agentContext, pool, endpoints, unqualifiedDid, signingKey, endorserDid } = options + const request = new AttribRequest({ + submitterDid: unqualifiedDid, + targetDid: unqualifiedDid, + raw: JSON.stringify({ endpoint: endpoints }), + }) + + const writeRequest = await pool.prepareWriteRequest(agentContext, request, signingKey, endorserDid) + return writeRequest + } + + private async setEndpointsForDid( + agentContext: AgentContext, + pool: IndyVdrPool, + writeRequest: Request + ): Promise { + const body = writeRequest.body + try { + const response = await pool.submitRequest(writeRequest) + + agentContext.config.logger.debug( + `Successfully set endpoints for did on ledger '${pool.indyNamespace}'.\nRequest: ${body}}`, + { + response, + } + ) + } catch (error) { + agentContext.config.logger.error( + `Error setting endpoints for did on ledger '${pool.indyNamespace}'.\nRequest: ${body}}` + ) + + throw new IndyVdrError(error) + } + } +} + +interface IndyVdrDidCreateOptionsBase extends DidCreateOptions { + didDocument?: never // Not yet supported + options: { + alias?: string + role?: string + services?: DidDocumentService[] + useEndpointAttrib?: boolean + verkey?: string + + // endorserDid is always required. We just have internal or external mode + endorserDid: string + // if endorserMode is 'internal', the endorserDid MUST be present in the wallet + // if endorserMode is 'external', the endorserDid doesn't have to be present in the wallet + endorserMode: 'internal' | 'external' + endorsedTransaction?: never + } + secret?: { + seed?: Buffer + privateKey?: Buffer + } +} + +interface IndyVdrDidCreateOptionsWithDid extends IndyVdrDidCreateOptionsBase { + method?: never + did: string +} + +interface IndyVdrDidCreateOptionsWithoutDid extends IndyVdrDidCreateOptionsBase { + method: 'indy' + did?: never +} + +// When transactions have been endorsed. Only supported for external mode +// this is a separate interface so we can remove all the properties we don't need anymore. +interface IndyVdrDidCreateOptionsForSubmission extends DidCreateOptions { + didDocument?: never + did: string // for submission MUST always have a did, so we know which did we're submitting the transaction for. We MUST check whether the did passed here, matches with the + method?: never + options: { + endorserMode: 'external' + + // provide the endorsed transactions. If these are provided + // we will submit the transactions to the ledger + endorsedTransaction: { + nymRequest: string + attribRequest?: string + } + } + secret?: { + seed?: Buffer + privateKey?: Buffer + } +} + +export type IndyVdrDidCreateOptions = + | IndyVdrDidCreateOptionsWithDid + | IndyVdrDidCreateOptionsWithoutDid + | IndyVdrDidCreateOptionsForSubmission + +type ParseInputOk = { + status: 'ok' + did: string + verificationKey?: Key + namespaceIdentifier: string + namespace: string + endorserNamespaceIdentifier: string + seed: Buffer | undefined + privateKey: Buffer | undefined +} + +type parseInputError = { status: 'error'; reason: string } + +type ParseInputResult = ParseInputOk | parseInputError + +export interface EndorseDidTxAction extends DidOperationStateActionBase { + action: 'endorseIndyTransaction' + endorserDid: string + nymRequest: string + attribRequest?: string + did: string +} + +export type IndyVdrDidCreateResult = DidCreateResult diff --git a/packages/indy-vdr/src/dids/IndyVdrIndyDidResolver.ts b/packages/indy-vdr/src/dids/IndyVdrIndyDidResolver.ts new file mode 100644 index 0000000000..89271f5fdd --- /dev/null +++ b/packages/indy-vdr/src/dids/IndyVdrIndyDidResolver.ts @@ -0,0 +1,37 @@ +import type { AgentContext, DidResolutionResult, DidResolver } from '@aries-framework/core' + +import { parseIndyDid } from '@aries-framework/anoncreds' + +import { IndyVdrPoolService } from '../pool' + +import { buildDidDocument } from './didIndyUtil' + +export class IndyVdrIndyDidResolver implements DidResolver { + public readonly supportedMethods = ['indy'] + + public async resolve(agentContext: AgentContext, did: string): Promise { + const didDocumentMetadata = {} + try { + const poolService = agentContext.dependencyManager.resolve(IndyVdrPoolService) + const pool = poolService.getPoolForNamespace(parseIndyDid(did).namespace) + + // Get DID Document from Get NYM response + const didDocument = await buildDidDocument(agentContext, pool, did) + + return { + didDocument, + didDocumentMetadata, + didResolutionMetadata: { contentType: 'application/did+ld+json' }, + } + } catch (error) { + return { + didDocument: null, + didDocumentMetadata, + didResolutionMetadata: { + error: 'notFound', + message: `resolver_error: Unable to resolve did '${did}': ${error}`, + }, + } + } + } +} diff --git a/packages/indy-vdr/src/dids/IndyVdrSovDidResolver.ts b/packages/indy-vdr/src/dids/IndyVdrSovDidResolver.ts new file mode 100644 index 0000000000..4484586078 --- /dev/null +++ b/packages/indy-vdr/src/dids/IndyVdrSovDidResolver.ts @@ -0,0 +1,98 @@ +import type { GetNymResponseData, IndyEndpointAttrib } from './didSovUtil' +import type { IndyVdrPool } from '../pool' +import type { DidResolutionResult, ParsedDid, DidResolver, AgentContext } from '@aries-framework/core' + +import { GetAttribRequest, GetNymRequest } from '@hyperledger/indy-vdr-shared' + +import { IndyVdrError, IndyVdrNotFoundError } from '../error' +import { IndyVdrPoolService } from '../pool/IndyVdrPoolService' + +import { addServicesFromEndpointsAttrib, sovDidDocumentFromDid } from './didSovUtil' + +export class IndyVdrSovDidResolver implements DidResolver { + public readonly supportedMethods = ['sov'] + + public async resolve(agentContext: AgentContext, did: string, parsed: ParsedDid): Promise { + const didDocumentMetadata = {} + + try { + const indyVdrPoolService = agentContext.dependencyManager.resolve(IndyVdrPoolService) + + // FIXME: this actually fetches the did twice (if not cached), once for the pool and once for the nym + // we do not store the diddocContent in the pool cache currently so we need to fetch it again + // The logic is mostly to determine which pool to use for a did + const { pool } = await indyVdrPoolService.getPoolForDid(agentContext, parsed.id) + const nym = await this.getPublicDid(pool, parsed.id) + const endpoints = await this.getEndpointsForDid(agentContext, pool, parsed.id) + + const keyAgreementId = `${parsed.did}#key-agreement-1` + const builder = sovDidDocumentFromDid(parsed.did, nym.verkey) + + if (endpoints) { + addServicesFromEndpointsAttrib(builder, parsed.did, endpoints, keyAgreementId) + } + + return { + didDocument: builder.build(), + didDocumentMetadata, + didResolutionMetadata: { contentType: 'application/did+ld+json' }, + } + } catch (error) { + return { + didDocument: null, + didDocumentMetadata, + didResolutionMetadata: { + error: 'notFound', + message: `resolver_error: Unable to resolve did '${did}': ${error}`, + }, + } + } + } + + private async getPublicDid(pool: IndyVdrPool, unqualifiedDid: string) { + const request = new GetNymRequest({ dest: unqualifiedDid }) + const didResponse = await pool.submitRequest(request) + + if (!didResponse.result.data) { + throw new IndyVdrNotFoundError(`DID ${unqualifiedDid} not found`) + } + return JSON.parse(didResponse.result.data) as GetNymResponseData + } + + private async getEndpointsForDid(agentContext: AgentContext, pool: IndyVdrPool, did: string) { + try { + agentContext.config.logger.debug(`Get endpoints for did '${did}' from ledger '${pool.indyNamespace}'`) + + const request = new GetAttribRequest({ targetDid: did, raw: 'endpoint' }) + + agentContext.config.logger.debug( + `Submitting get endpoint ATTRIB request for did '${did}' to ledger '${pool.indyNamespace}'` + ) + const response = await pool.submitRequest(request) + + if (!response.result.data) { + return null + } + + const endpoints = JSON.parse(response.result.data as string)?.endpoint as IndyEndpointAttrib + agentContext.config.logger.debug( + `Got endpoints '${JSON.stringify(endpoints)}' for did '${did}' from ledger '${pool.indyNamespace}'`, + { + response, + endpoints, + } + ) + + return endpoints ?? null + } catch (error) { + agentContext.config.logger.error( + `Error retrieving endpoints for did '${did}' from ledger '${pool.indyNamespace}'`, + { + error, + } + ) + + throw new IndyVdrError(error) + } + } +} diff --git a/packages/indy-vdr/src/dids/__tests__/IndyVdrIndyDidRegistrar.test.ts b/packages/indy-vdr/src/dids/__tests__/IndyVdrIndyDidRegistrar.test.ts new file mode 100644 index 0000000000..d040cf0667 --- /dev/null +++ b/packages/indy-vdr/src/dids/__tests__/IndyVdrIndyDidRegistrar.test.ts @@ -0,0 +1,766 @@ +/* eslint-disable @typescript-eslint/ban-ts-comment */ +import type { DidRecord, RecordSavedEvent } from '@aries-framework/core' + +import { + DidCommV1Service, + DidCommV2Service, + DidDocumentService, + DidDocument, + DidDocumentRole, + DidRepository, + DidsApi, + EventEmitter, + JsonTransformer, + Key, + KeyType, + RepositoryEventTypes, + KeyProviderRegistry, + TypedArrayEncoder, + VerificationMethod, +} from '@aries-framework/core' +import { Subject } from 'rxjs' + +import { InMemoryStorageService } from '../../../../../tests/InMemoryStorageService' +import { agentDependencies, getAgentConfig, getAgentContext, indySdk, mockProperty } from '../../../../core/tests' +import { IndySdkWallet } from '../../../../indy-sdk/src' +import { IndyVdrPool, IndyVdrPoolService } from '../../pool' +import { IndyVdrIndyDidRegistrar } from '../IndyVdrIndyDidRegistrar' + +jest.mock('../../pool/IndyVdrPool') +const IndyVdrPoolMock = IndyVdrPool as jest.Mock +const poolMock = new IndyVdrPoolMock() +mockProperty(poolMock, 'indyNamespace', 'ns1') + +const agentConfig = getAgentConfig('IndyVdrIndyDidRegistrar') + +const wallet = new IndySdkWallet(indySdk, agentConfig.logger, new KeyProviderRegistry([])) + +jest + .spyOn(wallet, 'createKey') + .mockResolvedValue(Key.fromPublicKeyBase58('E6D1m3eERqCueX4ZgMCY14B4NceAr6XP2HyVqt55gDhu', KeyType.Ed25519)) +const storageService = new InMemoryStorageService() +const eventEmitter = new EventEmitter(agentDependencies, new Subject()) +const didRepository = new DidRepository(storageService, eventEmitter) + +const agentContext = getAgentContext({ + wallet, + registerInstances: [ + [DidRepository, didRepository], + [IndyVdrPoolService, { getPoolForNamespace: jest.fn().mockReturnValue(poolMock) }], + [ + DidsApi, + { + resolve: jest.fn().mockResolvedValue({ + didDocument: new DidDocument({ + id: 'did:indy:pool1:BzCbsNYhMrjHiqZDTUASHg', + authentication: [ + new VerificationMethod({ + id: 'did:indy:pool1:BzCbsNYhMrjHiqZDTUASHg#verkey', + type: 'Ed25519VerificationKey2018', + controller: 'did:indy:pool1:BzCbsNYhMrjHiqZDTUASHg', + publicKeyBase58: 'E6D1m3eERqCueX4ZgMCY14B4NceAr6XP2HyVqt55gDhu', + }), + ], + }), + }), + }, + ], + ], + agentConfig, +}) + +const indyVdrIndyDidRegistrar = new IndyVdrIndyDidRegistrar() + +describe('IndyVdrIndyDidRegistrar', () => { + afterEach(() => { + jest.clearAllMocks() + }) + + test('returns an error state if both did and privateKey are provided', async () => { + const result = await indyVdrIndyDidRegistrar.create(agentContext, { + did: 'did:indy:pool1:did-value', + options: { + alias: 'Hello', + endorserMode: 'internal', + endorserDid: 'did:indy:pool1:BzCbsNYhMrjHiqZDTUASHg', + }, + secret: { + privateKey: TypedArrayEncoder.fromString('key'), + }, + }) + + expect(JsonTransformer.toJSON(result)).toMatchObject({ + didDocumentMetadata: {}, + didRegistrationMetadata: {}, + didState: { + state: 'failed', + reason: `Only one of 'seed', 'privateKey' and 'did' must be provided`, + }, + }) + }) + + test('returns an error state if the endorser did is not a valid did:indy did', async () => { + const result = await indyVdrIndyDidRegistrar.create(agentContext, { + method: 'indy', + options: { + endorserMode: 'internal', + endorserDid: 'BzCbsNYhMrjHiqZDTUASHg', + alias: 'Hello', + }, + }) + + expect(JsonTransformer.toJSON(result)).toMatchObject({ + didDocumentMetadata: {}, + didRegistrationMetadata: {}, + didState: { + state: 'failed', + reason: 'unknownError: BzCbsNYhMrjHiqZDTUASHg is not a valid did:indy did', + }, + }) + }) + + test('returns an error state if did is provided, but it is not a valid did:indy did', async () => { + const result = await indyVdrIndyDidRegistrar.create(agentContext, { + did: 'BzCbsNYhMrjHiqZDTUASHg', + options: { + endorserMode: 'internal', + endorserDid: 'did:indy:pool1:BzCbsNYhMrjHiqZDTUASHg', + verkey: 'verkey', + alias: 'Hello', + }, + }) + + expect(JsonTransformer.toJSON(result)).toMatchObject({ + didDocumentMetadata: {}, + didRegistrationMetadata: {}, + didState: { + state: 'failed', + reason: 'unknownError: BzCbsNYhMrjHiqZDTUASHg is not a valid did:indy did', + }, + }) + }) + + test('returns an error state if did is provided, but no verkey', async () => { + const result = await indyVdrIndyDidRegistrar.create(agentContext, { + did: 'BzCbsNYhMrjHiqZDTUASHg', + options: { + endorserMode: 'internal', + endorserDid: 'did:indy:pool1:BzCbsNYhMrjHiqZDTUASHg', + alias: 'Hello', + }, + }) + + expect(JsonTransformer.toJSON(result)).toMatchObject({ + didDocumentMetadata: {}, + didRegistrationMetadata: {}, + didState: { + state: 'failed', + reason: 'If a did is defined, a matching verkey must be provided', + }, + }) + }) + + test('returns an error state if did and verkey are provided, but the did is not self certifying', async () => { + const result = await indyVdrIndyDidRegistrar.create(agentContext, { + did: 'did:indy:pool1:BzCbsNYhMrjHiqZDTUASHg', + options: { + endorserMode: 'internal', + endorserDid: 'did:indy:pool1:BzCbsNYhMrjHiqZDTUASHg', + verkey: 'verkey', + alias: 'Hello', + }, + }) + + expect(JsonTransformer.toJSON(result)).toMatchObject({ + didDocumentMetadata: {}, + didRegistrationMetadata: {}, + didState: { + state: 'failed', + reason: 'Initial verkey verkey does not match did did:indy:pool1:BzCbsNYhMrjHiqZDTUASHg', + }, + }) + }) + + test('returns an error state if did is provided, but does not match with the namespace from the endorserDid', async () => { + const result = await indyVdrIndyDidRegistrar.create(agentContext, { + did: 'did:indy:pool2:B6xaJg1c2xU3D9ppCtt1CZ', + options: { + endorserMode: 'internal', + endorserDid: 'did:indy:pool1:BzCbsNYhMrjHiqZDTUASHg', + verkey: 'E6D1m3eERqCueX4ZgMCY14B4NceAr6XP2HyVqt55gDhu', + alias: 'Hello', + }, + }) + + expect(JsonTransformer.toJSON(result)).toMatchObject({ + didDocumentMetadata: {}, + didRegistrationMetadata: {}, + didState: { + state: 'failed', + reason: + "The endorser did uses namespace: 'pool1' and the did to register uses namespace: 'pool2'. Namespaces must match.", + }, + }) + }) + + test('creates a did:indy document without services', async () => { + const privateKey = TypedArrayEncoder.fromString('96213c3d7fc8d4d6754c712fd969598e') + + // @ts-ignore - method is private + const createRegisterDidWriteRequest = jest.spyOn( + indyVdrIndyDidRegistrar, + 'createRegisterDidWriteRequest' + ) + // @ts-ignore type check fails because method is private + createRegisterDidWriteRequest.mockImplementationOnce(() => Promise.resolve()) + + // @ts-ignore - method is private + const registerPublicDidSpy = jest.spyOn(indyVdrIndyDidRegistrar, 'registerPublicDid') + // @ts-ignore type check fails because method is private + registerPublicDidSpy.mockImplementationOnce(() => Promise.resolve()) + + const result = await indyVdrIndyDidRegistrar.create(agentContext, { + method: 'indy', + options: { + alias: 'Hello', + endorserMode: 'internal', + endorserDid: 'did:indy:pool1:BzCbsNYhMrjHiqZDTUASHg', + role: 'STEWARD', + }, + secret: { + privateKey, + }, + }) + + expect(createRegisterDidWriteRequest).toHaveBeenCalledWith({ + agentContext, + pool: poolMock, + signingKey: expect.any(Key), + submitterNamespaceIdentifier: 'BzCbsNYhMrjHiqZDTUASHg', + namespaceIdentifier: 'B6xaJg1c2xU3D9ppCtt1CZ', + verificationKey: expect.any(Key), + alias: 'Hello', + diddocContent: undefined, + }) + + expect(registerPublicDidSpy).toHaveBeenCalledWith(agentContext, poolMock, undefined) + expect(JsonTransformer.toJSON(result)).toMatchObject({ + didDocumentMetadata: {}, + didRegistrationMetadata: {}, + didState: { + state: 'finished', + did: 'did:indy:pool1:B6xaJg1c2xU3D9ppCtt1CZ', + didDocument: { + '@context': ['https://w3id.org/did/v1', 'https://w3id.org/security/suites/ed25519-2018/v1'], + id: 'did:indy:pool1:B6xaJg1c2xU3D9ppCtt1CZ', + verificationMethod: [ + { + id: 'did:indy:pool1:B6xaJg1c2xU3D9ppCtt1CZ#verkey', + type: 'Ed25519VerificationKey2018', + controller: 'did:indy:pool1:B6xaJg1c2xU3D9ppCtt1CZ', + publicKeyBase58: 'E6D1m3eERqCueX4ZgMCY14B4NceAr6XP2HyVqt55gDhu', + }, + ], + authentication: ['did:indy:pool1:B6xaJg1c2xU3D9ppCtt1CZ#verkey'], + assertionMethod: undefined, + keyAgreement: undefined, + }, + secret: { + privateKey, + }, + }, + }) + }) + + test('creates a did:indy document by passing did', async () => { + // @ts-ignore - method is private + const createRegisterDidWriteRequest = jest.spyOn( + indyVdrIndyDidRegistrar, + 'createRegisterDidWriteRequest' + ) + // @ts-ignore type check fails because method is private + createRegisterDidWriteRequest.mockImplementationOnce(() => Promise.resolve()) + + // @ts-ignore - method is private + const registerPublicDidSpy = jest.spyOn(indyVdrIndyDidRegistrar, 'registerPublicDid') + // @ts-ignore type check fails because method is private + registerPublicDidSpy.mockImplementationOnce(() => Promise.resolve()) + + const result = await indyVdrIndyDidRegistrar.create(agentContext, { + did: 'did:indy:pool1:B6xaJg1c2xU3D9ppCtt1CZ', + options: { + verkey: 'E6D1m3eERqCueX4ZgMCY14B4NceAr6XP2HyVqt55gDhu', + alias: 'Hello', + endorserMode: 'internal', + endorserDid: 'did:indy:pool1:BzCbsNYhMrjHiqZDTUASHg', + role: 'STEWARD', + }, + secret: {}, + }) + + expect(createRegisterDidWriteRequest).toHaveBeenCalledWith({ + agentContext, + pool: poolMock, + signingKey: expect.any(Key), + submitterNamespaceIdentifier: 'BzCbsNYhMrjHiqZDTUASHg', + namespaceIdentifier: 'B6xaJg1c2xU3D9ppCtt1CZ', + verificationKey: expect.any(Key), + alias: 'Hello', + diddocContent: undefined, + }) + + expect(registerPublicDidSpy).toHaveBeenCalledWith( + agentContext, + poolMock, + // writeRequest + undefined + ) + expect(JsonTransformer.toJSON(result)).toMatchObject({ + didDocumentMetadata: {}, + didRegistrationMetadata: {}, + didState: { + state: 'finished', + did: 'did:indy:pool1:B6xaJg1c2xU3D9ppCtt1CZ', + didDocument: { + '@context': ['https://w3id.org/did/v1', 'https://w3id.org/security/suites/ed25519-2018/v1'], + id: 'did:indy:pool1:B6xaJg1c2xU3D9ppCtt1CZ', + verificationMethod: [ + { + id: 'did:indy:pool1:B6xaJg1c2xU3D9ppCtt1CZ#verkey', + type: 'Ed25519VerificationKey2018', + controller: 'did:indy:pool1:B6xaJg1c2xU3D9ppCtt1CZ', + publicKeyBase58: 'E6D1m3eERqCueX4ZgMCY14B4NceAr6XP2HyVqt55gDhu', + }, + ], + authentication: ['did:indy:pool1:B6xaJg1c2xU3D9ppCtt1CZ#verkey'], + assertionMethod: undefined, + keyAgreement: undefined, + }, + secret: {}, + }, + }) + }) + + test('creates a did:indy document with services using diddocContent', async () => { + const privateKey = TypedArrayEncoder.fromString('96213c3d7fc8d4d6754c712fd969598e') + + // @ts-ignore - method is private + const createRegisterDidWriteRequestSpy = jest.spyOn( + indyVdrIndyDidRegistrar, + 'createRegisterDidWriteRequest' + ) + // @ts-ignore type check fails because method is private + createRegisterDidWriteRequestSpy.mockImplementationOnce(() => Promise.resolve()) + + // @ts-ignore - method is private + const registerPublicDidSpy = jest.spyOn(indyVdrIndyDidRegistrar, 'registerPublicDid') + // @ts-ignore type check fails because method is private + registerPublicDidSpy.mockImplementationOnce(() => Promise.resolve()) + + // @ts-ignore - method is private + const setEndpointsForDidSpy = jest.spyOn(indyVdrIndyDidRegistrar, 'setEndpointsForDid') + + const result = await indyVdrIndyDidRegistrar.create(agentContext, { + method: 'indy', + options: { + alias: 'Hello', + endorserMode: 'internal', + endorserDid: 'did:indy:pool1:BzCbsNYhMrjHiqZDTUASHg', + role: 'STEWARD', + services: [ + new DidDocumentService({ + id: `#endpoint`, + serviceEndpoint: 'https://example.com/endpoint', + type: 'endpoint', + }), + new DidCommV1Service({ + id: `#did-communication`, + priority: 0, + recipientKeys: [`#key-agreement-1`], + routingKeys: ['key-1'], + serviceEndpoint: 'https://example.com/endpoint', + accept: ['didcomm/aip2;env=rfc19'], + }), + new DidCommV2Service({ + accept: ['didcomm/v2'], + id: `#didcomm-1`, + routingKeys: ['key-1'], + serviceEndpoint: 'https://example.com/endpoint', + }), + ], + }, + secret: { + privateKey, + }, + }) + + expect(createRegisterDidWriteRequestSpy).toHaveBeenCalledWith({ + agentContext, + pool: poolMock, + signingKey: expect.any(Key), + submitterNamespaceIdentifier: 'BzCbsNYhMrjHiqZDTUASHg', + namespaceIdentifier: 'B6xaJg1c2xU3D9ppCtt1CZ', + verificationKey: expect.any(Key), + alias: 'Hello', + diddocContent: { + '@context': [], + authentication: [], + id: 'did:indy:pool1:B6xaJg1c2xU3D9ppCtt1CZ', + keyAgreement: ['did:indy:pool1:B6xaJg1c2xU3D9ppCtt1CZ#key-agreement-1'], + service: [ + { + id: 'did:indy:pool1:B6xaJg1c2xU3D9ppCtt1CZ#endpoint', + serviceEndpoint: 'https://example.com/endpoint', + type: 'endpoint', + }, + { + accept: ['didcomm/aip2;env=rfc19'], + id: 'did:indy:pool1:B6xaJg1c2xU3D9ppCtt1CZ#did-communication', + priority: 0, + recipientKeys: ['did:indy:pool1:B6xaJg1c2xU3D9ppCtt1CZ#key-agreement-1'], + routingKeys: ['key-1'], + serviceEndpoint: 'https://example.com/endpoint', + type: 'did-communication', + }, + { + accept: ['didcomm/v2'], + id: 'did:indy:pool1:B6xaJg1c2xU3D9ppCtt1CZ#didcomm-1', + routingKeys: ['key-1'], + serviceEndpoint: 'https://example.com/endpoint', + type: 'DIDCommMessaging', + }, + ], + verificationMethod: [ + { + controller: 'did:indy:pool1:B6xaJg1c2xU3D9ppCtt1CZ', + id: 'did:indy:pool1:B6xaJg1c2xU3D9ppCtt1CZ#key-agreement-1', + publicKeyBase58: 'Fbv17ZbnUSbafsiUBJbdGeC62M8v8GEscVMMcE59mRPt', + type: 'X25519KeyAgreementKey2019', + }, + ], + }, + }) + + expect(registerPublicDidSpy).toHaveBeenCalledWith( + agentContext, + poolMock, + // writeRequest + undefined + ) + expect(setEndpointsForDidSpy).not.toHaveBeenCalled() + expect(JsonTransformer.toJSON(result)).toMatchObject({ + didDocumentMetadata: {}, + didRegistrationMetadata: {}, + didState: { + state: 'finished', + did: 'did:indy:pool1:B6xaJg1c2xU3D9ppCtt1CZ', + didDocument: { + '@context': [ + 'https://w3id.org/did/v1', + 'https://w3id.org/security/suites/ed25519-2018/v1', + 'https://w3id.org/security/suites/x25519-2019/v1', + 'https://didcomm.org/messaging/contexts/v2', + ], + id: 'did:indy:pool1:B6xaJg1c2xU3D9ppCtt1CZ', + verificationMethod: [ + { + id: 'did:indy:pool1:B6xaJg1c2xU3D9ppCtt1CZ#verkey', + type: 'Ed25519VerificationKey2018', + controller: 'did:indy:pool1:B6xaJg1c2xU3D9ppCtt1CZ', + publicKeyBase58: 'E6D1m3eERqCueX4ZgMCY14B4NceAr6XP2HyVqt55gDhu', + }, + { + id: 'did:indy:pool1:B6xaJg1c2xU3D9ppCtt1CZ#key-agreement-1', + type: 'X25519KeyAgreementKey2019', + controller: 'did:indy:pool1:B6xaJg1c2xU3D9ppCtt1CZ', + publicKeyBase58: 'Fbv17ZbnUSbafsiUBJbdGeC62M8v8GEscVMMcE59mRPt', + }, + ], + service: [ + { + id: 'did:indy:pool1:B6xaJg1c2xU3D9ppCtt1CZ#endpoint', + serviceEndpoint: 'https://example.com/endpoint', + type: 'endpoint', + }, + { + id: 'did:indy:pool1:B6xaJg1c2xU3D9ppCtt1CZ#did-communication', + serviceEndpoint: 'https://example.com/endpoint', + type: 'did-communication', + priority: 0, + recipientKeys: ['did:indy:pool1:B6xaJg1c2xU3D9ppCtt1CZ#key-agreement-1'], + routingKeys: ['key-1'], + accept: ['didcomm/aip2;env=rfc19'], + }, + { + id: 'did:indy:pool1:B6xaJg1c2xU3D9ppCtt1CZ#didcomm-1', + serviceEndpoint: 'https://example.com/endpoint', + type: 'DIDCommMessaging', + routingKeys: ['key-1'], + accept: ['didcomm/v2'], + }, + ], + authentication: ['did:indy:pool1:B6xaJg1c2xU3D9ppCtt1CZ#verkey'], + assertionMethod: undefined, + keyAgreement: ['did:indy:pool1:B6xaJg1c2xU3D9ppCtt1CZ#key-agreement-1'], + }, + secret: { + privateKey, + }, + }, + }) + }) + + test('creates a did:indy document with services using attrib', async () => { + const privateKey = TypedArrayEncoder.fromString('96213c3d7fc8d4d6754c712fd969598e') + + // @ts-ignore - method is private + const createRegisterDidWriteRequestSpy = jest.spyOn( + indyVdrIndyDidRegistrar, + 'createRegisterDidWriteRequest' + ) + // @ts-ignore type check fails because method is private + createRegisterDidWriteRequestSpy.mockImplementationOnce(() => Promise.resolve()) + + // @ts-ignore - method is private + const registerPublicDidSpy = jest.spyOn(indyVdrIndyDidRegistrar, 'registerPublicDid') + // @ts-ignore type check fails because method is private + registerPublicDidSpy.mockImplementationOnce(() => Promise.resolve()) + + // @ts-ignore - method is private + const createSetDidEndpointsRequestSpy = jest.spyOn( + indyVdrIndyDidRegistrar, + 'createSetDidEndpointsRequest' + ) + // @ts-ignore type check fails because method is private + createSetDidEndpointsRequestSpy.mockImplementationOnce(() => Promise.resolve(undefined)) + + // @ts-ignore - method is private + const setEndpointsForDidSpy = jest.spyOn(indyVdrIndyDidRegistrar, 'setEndpointsForDid') + // @ts-ignore type check fails because method is private + setEndpointsForDidSpy.mockImplementationOnce(() => Promise.resolve(undefined)) + + const result = await indyVdrIndyDidRegistrar.create(agentContext, { + method: 'indy', + options: { + alias: 'Hello', + endorserMode: 'internal', + endorserDid: 'did:indy:pool1:BzCbsNYhMrjHiqZDTUASHg', + role: 'STEWARD', + useEndpointAttrib: true, + services: [ + new DidDocumentService({ + id: `#endpoint`, + serviceEndpoint: 'https://example.com/endpoint', + type: 'endpoint', + }), + new DidCommV1Service({ + id: `#did-communication`, + priority: 0, + recipientKeys: [`#key-agreement-1`], + routingKeys: ['key-1'], + serviceEndpoint: 'https://example.com/endpoint', + accept: ['didcomm/aip2;env=rfc19'], + }), + new DidCommV2Service({ + accept: ['didcomm/v2'], + id: `#didcomm-1`, + routingKeys: ['key-1'], + serviceEndpoint: 'https://example.com/endpoint', + }), + ], + }, + secret: { + privateKey, + }, + }) + + expect(createRegisterDidWriteRequestSpy).toHaveBeenCalledWith({ + agentContext, + pool: poolMock, + signingKey: expect.any(Key), + submitterNamespaceIdentifier: 'BzCbsNYhMrjHiqZDTUASHg', + namespaceIdentifier: 'B6xaJg1c2xU3D9ppCtt1CZ', + verificationKey: expect.any(Key), + alias: 'Hello', + diddocContent: undefined, + }) + + expect(registerPublicDidSpy).toHaveBeenCalledWith( + agentContext, + poolMock, + // writeRequest + undefined + ) + expect(createSetDidEndpointsRequestSpy).toHaveBeenCalledWith({ + agentContext, + pool: poolMock, + signingKey: expect.any(Key), + endorserDid: undefined, + // Unqualified created indy did + unqualifiedDid: 'B6xaJg1c2xU3D9ppCtt1CZ', + endpoints: { + endpoint: 'https://example.com/endpoint', + routingKeys: ['key-1'], + types: ['endpoint', 'did-communication', 'DIDCommMessaging'], + }, + }) + expect(setEndpointsForDidSpy).not.toHaveBeenCalled() + expect(JsonTransformer.toJSON(result)).toMatchObject({ + didDocumentMetadata: {}, + didRegistrationMetadata: {}, + didState: { + state: 'finished', + did: 'did:indy:pool1:B6xaJg1c2xU3D9ppCtt1CZ', + didDocument: { + '@context': [ + 'https://w3id.org/did/v1', + 'https://w3id.org/security/suites/ed25519-2018/v1', + 'https://w3id.org/security/suites/x25519-2019/v1', + 'https://didcomm.org/messaging/contexts/v2', + ], + id: 'did:indy:pool1:B6xaJg1c2xU3D9ppCtt1CZ', + verificationMethod: [ + { + id: 'did:indy:pool1:B6xaJg1c2xU3D9ppCtt1CZ#verkey', + type: 'Ed25519VerificationKey2018', + controller: 'did:indy:pool1:B6xaJg1c2xU3D9ppCtt1CZ', + publicKeyBase58: 'E6D1m3eERqCueX4ZgMCY14B4NceAr6XP2HyVqt55gDhu', + }, + { + id: 'did:indy:pool1:B6xaJg1c2xU3D9ppCtt1CZ#key-agreement-1', + type: 'X25519KeyAgreementKey2019', + controller: 'did:indy:pool1:B6xaJg1c2xU3D9ppCtt1CZ', + publicKeyBase58: 'Fbv17ZbnUSbafsiUBJbdGeC62M8v8GEscVMMcE59mRPt', + }, + ], + service: [ + { + id: 'did:indy:pool1:B6xaJg1c2xU3D9ppCtt1CZ#endpoint', + serviceEndpoint: 'https://example.com/endpoint', + type: 'endpoint', + }, + { + id: 'did:indy:pool1:B6xaJg1c2xU3D9ppCtt1CZ#did-communication', + serviceEndpoint: 'https://example.com/endpoint', + type: 'did-communication', + priority: 0, + recipientKeys: ['did:indy:pool1:B6xaJg1c2xU3D9ppCtt1CZ#key-agreement-1'], + routingKeys: ['key-1'], + accept: ['didcomm/aip2;env=rfc19'], + }, + { + id: 'did:indy:pool1:B6xaJg1c2xU3D9ppCtt1CZ#didcomm-1', + serviceEndpoint: 'https://example.com/endpoint', + type: 'DIDCommMessaging', + routingKeys: ['key-1'], + accept: ['didcomm/v2'], + }, + ], + authentication: ['did:indy:pool1:B6xaJg1c2xU3D9ppCtt1CZ#verkey'], + assertionMethod: undefined, + keyAgreement: ['did:indy:pool1:B6xaJg1c2xU3D9ppCtt1CZ#key-agreement-1'], + }, + secret: { + privateKey, + }, + }, + }) + }) + + test('stores the did document', async () => { + const privateKey = TypedArrayEncoder.fromString('96213c3d7fc8d4d6754c712fd969598e') + + // @ts-ignore - method is private + const createRegisterDidWriteRequestSpy = jest.spyOn( + indyVdrIndyDidRegistrar, + 'createRegisterDidWriteRequest' + ) + // @ts-ignore type check fails because method is private + createRegisterDidWriteRequestSpy.mockImplementationOnce(() => Promise.resolve()) + + // @ts-ignore - method is private + const registerPublicDidSpy = jest.spyOn(indyVdrIndyDidRegistrar, 'registerPublicDid') + // @ts-ignore type check fails because method is private + registerPublicDidSpy.mockImplementationOnce(() => Promise.resolve()) + + // @ts-ignore - method is private + const setEndpointsForDidSpy = jest.spyOn(indyVdrIndyDidRegistrar, 'setEndpointsForDid') + // @ts-ignore type check fails because method is private + setEndpointsForDidSpy.mockImplementationOnce(() => Promise.resolve(undefined)) + + const saveCalled = jest.fn() + eventEmitter.on>(RepositoryEventTypes.RecordSaved, saveCalled) + + await indyVdrIndyDidRegistrar.create(agentContext, { + method: 'indy', + options: { + alias: 'Hello', + endorserMode: 'internal', + endorserDid: 'did:indy:pool1:BzCbsNYhMrjHiqZDTUASHg', + role: 'STEWARD', + services: [ + new DidDocumentService({ + id: `#endpoint`, + serviceEndpoint: 'https://example.com/endpoint', + type: 'endpoint', + }), + new DidCommV1Service({ + id: `#did-communication`, + priority: 0, + recipientKeys: [`#key-agreement-1`], + routingKeys: ['key-1'], + serviceEndpoint: 'https://example.com/endpoint', + accept: ['didcomm/aip2;env=rfc19'], + }), + new DidCommV2Service({ + accept: ['didcomm/v2'], + id: `#didcomm-1`, + routingKeys: ['key-1'], + serviceEndpoint: 'https://example.com/endpoint', + }), + ], + }, + secret: { + privateKey, + }, + }) + + expect(saveCalled).toHaveBeenCalledTimes(1) + const [saveEvent] = saveCalled.mock.calls[0] + + expect(saveEvent.payload.record).toMatchObject({ + did: 'did:indy:pool1:B6xaJg1c2xU3D9ppCtt1CZ', + role: DidDocumentRole.Created, + _tags: { + recipientKeyFingerprints: ['z6LSrH6AdsQeZuKKmG6Ehx7abEQZsVg2psR2VU536gigUoAe'], + }, + didDocument: undefined, + }) + }) + + test('returns an error state when calling update', async () => { + const result = await indyVdrIndyDidRegistrar.update() + + expect(result).toEqual({ + didDocumentMetadata: {}, + didRegistrationMetadata: {}, + didState: { + state: 'failed', + reason: `notImplemented: updating did:indy not implemented yet`, + }, + }) + }) + + test('returns an error state when calling deactivate', async () => { + const result = await indyVdrIndyDidRegistrar.deactivate() + + expect(result).toEqual({ + didDocumentMetadata: {}, + didRegistrationMetadata: {}, + didState: { + state: 'failed', + reason: `notImplemented: deactivating did:indy not implemented yet`, + }, + }) + }) +}) diff --git a/packages/indy-vdr/src/dids/__tests__/IndyVdrIndyDidResolver.test.ts b/packages/indy-vdr/src/dids/__tests__/IndyVdrIndyDidResolver.test.ts new file mode 100644 index 0000000000..f1221726b7 --- /dev/null +++ b/packages/indy-vdr/src/dids/__tests__/IndyVdrIndyDidResolver.test.ts @@ -0,0 +1,148 @@ +import { JsonTransformer } from '@aries-framework/core' + +import { getAgentConfig, getAgentContext, mockProperty } from '../../../../core/tests/helpers' +import { IndyVdrPool, IndyVdrPoolService } from '../../pool' +import { IndyVdrIndyDidResolver } from '../IndyVdrIndyDidResolver' + +import didIndyLjgpST2rjsoxYegQDRm7EL from './__fixtures__/didIndyLjgpST2rjsoxYegQDRm7EL.json' +import didIndyLjgpST2rjsoxYegQDRm7ELdiddocContent from './__fixtures__/didIndyLjgpST2rjsoxYegQDRm7ELdiddocContent.json' +import didIndyR1xKJw17sUoXhejEpugMYJFixture from './__fixtures__/didIndyR1xKJw17sUoXhejEpugMYJ.json' +import didIndyWJz9mHyW9BZksioQnRsrAoFixture from './__fixtures__/didIndyWJz9mHyW9BZksioQnRsrAo.json' + +jest.mock('../../pool/IndyVdrPool') +const IndyVdrPoolMock = IndyVdrPool as jest.Mock +const poolMock = new IndyVdrPoolMock() +mockProperty(poolMock, 'indyNamespace', 'ns1') + +const agentConfig = getAgentConfig('IndyVdrIndyDidResolver') + +const agentContext = getAgentContext({ + agentConfig, + registerInstances: [[IndyVdrPoolService, { getPoolForNamespace: jest.fn().mockReturnValue(poolMock) }]], +}) + +const resolver = new IndyVdrIndyDidResolver() + +describe('IndyVdrIndyDidResolver', () => { + describe('NYMs with diddocContent', () => { + it('should correctly resolve a did:indy document with arbitrary diddocContent', async () => { + const did = 'did:indy:ns2:LjgpST2rjsoxYegQDRm7EL' + + const nymResponse = { + result: { + data: JSON.stringify({ + did: 'LjgpST2rjsoxYegQDRm7EL', + verkey: 'E6D1m3eERqCueX4ZgMCY14B4NceAr6XP2HyVqt55gDhu', + role: 'ENDORSER', + diddocContent: didIndyLjgpST2rjsoxYegQDRm7ELdiddocContent, + }), + }, + } + + const poolMockSubmitRequest = jest.spyOn(poolMock, 'submitRequest') + poolMockSubmitRequest.mockResolvedValueOnce(nymResponse) + + const result = await resolver.resolve(agentContext, did) + + expect(poolMockSubmitRequest).toHaveBeenCalledTimes(1) + expect(JsonTransformer.toJSON(result)).toMatchObject({ + didDocument: didIndyLjgpST2rjsoxYegQDRm7EL, + didDocumentMetadata: {}, + didResolutionMetadata: { + contentType: 'application/did+ld+json', + }, + }) + }) + }) + + describe('NYMs without diddocContent', () => { + it('should correctly resolve a did:indy document without endpoint attrib', async () => { + const did = 'did:indy:ns1:R1xKJw17sUoXhejEpugMYJ' + + const nymResponse = { + result: { + data: JSON.stringify({ + did: 'R1xKJw17sUoXhejEpugMYJ', + verkey: 'E6D1m3eERqCueX4ZgMCY14B4NceAr6XP2HyVqt55gDhu', + role: 'ENDORSER', + }), + }, + } + + const attribResponse = { + result: { + data: null, + }, + } + + jest.spyOn(poolMock, 'submitRequest').mockResolvedValueOnce(nymResponse) + jest.spyOn(poolMock, 'submitRequest').mockResolvedValueOnce(attribResponse) + + const result = await resolver.resolve(agentContext, did) + + expect(JsonTransformer.toJSON(result)).toMatchObject({ + didDocument: didIndyR1xKJw17sUoXhejEpugMYJFixture, + didDocumentMetadata: {}, + didResolutionMetadata: { + contentType: 'application/did+ld+json', + }, + }) + }) + + it('should correctly resolve a did:indy document with endpoint attrib', async () => { + const did = 'did:indy:ns1:WJz9mHyW9BZksioQnRsrAo' + + const nymResponse = { + result: { + data: JSON.stringify({ + did: 'WJz9mHyW9BZksioQnRsrAo', + verkey: 'GyYtYWU1vjwd5PFJM4VSX5aUiSV3TyZMuLBJBTQvfdF8', + role: 'ENDORSER', + }), + }, + } + + const attribResponse = { + result: { + data: JSON.stringify({ + endpoint: { + endpoint: 'https://agent.com', + types: ['endpoint', 'did-communication', 'DIDCommMessaging'], + routingKeys: ['routingKey1', 'routingKey2'], + }, + }), + }, + } + + jest.spyOn(poolMock, 'submitRequest').mockResolvedValueOnce(nymResponse) + jest.spyOn(poolMock, 'submitRequest').mockResolvedValueOnce(attribResponse) + + const result = await resolver.resolve(agentContext, did) + + expect(JsonTransformer.toJSON(result)).toMatchObject({ + didDocument: didIndyWJz9mHyW9BZksioQnRsrAoFixture, + didDocumentMetadata: {}, + didResolutionMetadata: { + contentType: 'application/did+ld+json', + }, + }) + }) + + it('should return did resolution metadata with error if the indy ledger service throws an error', async () => { + const did = 'did:indy:ns1:R1xKJw17sUoXhejEpugMYJ' + + jest.spyOn(poolMock, 'submitRequest').mockRejectedValue(new Error('Error submitting read request')) + + const result = await resolver.resolve(agentContext, did) + + expect(result).toMatchObject({ + didDocument: null, + didDocumentMetadata: {}, + didResolutionMetadata: { + error: 'notFound', + message: `resolver_error: Unable to resolve did 'did:indy:ns1:R1xKJw17sUoXhejEpugMYJ': Error: Error submitting read request`, + }, + }) + }) + }) +}) diff --git a/packages/indy-vdr/src/dids/__tests__/IndyVdrSovDidResolver.test.ts b/packages/indy-vdr/src/dids/__tests__/IndyVdrSovDidResolver.test.ts new file mode 100644 index 0000000000..cfe09fa3f1 --- /dev/null +++ b/packages/indy-vdr/src/dids/__tests__/IndyVdrSovDidResolver.test.ts @@ -0,0 +1,123 @@ +import { JsonTransformer } from '@aries-framework/core' + +import { parseDid } from '../../../../core/src/modules/dids/domain/parse' +import { getAgentConfig, getAgentContext, mockProperty } from '../../../../core/tests/helpers' +import { IndyVdrPool } from '../../pool/IndyVdrPool' +import { IndyVdrPoolService } from '../../pool/IndyVdrPoolService' +import { IndyVdrSovDidResolver } from '../IndyVdrSovDidResolver' + +import didSovR1xKJw17sUoXhejEpugMYJFixture from './__fixtures__/didSovR1xKJw17sUoXhejEpugMYJ.json' +import didSovWJz9mHyW9BZksioQnRsrAoFixture from './__fixtures__/didSovWJz9mHyW9BZksioQnRsrAo.json' + +jest.mock('../../pool/IndyVdrPool') +const IndyVdrPoolMock = IndyVdrPool as jest.Mock +const poolMock = new IndyVdrPoolMock() +mockProperty(poolMock, 'indyNamespace', 'local') + +const agentConfig = getAgentConfig('IndyVdrSovDidResolver') + +const agentContext = getAgentContext({ + agentConfig, + registerInstances: [[IndyVdrPoolService, { getPoolForDid: jest.fn().mockReturnValue({ pool: poolMock }) }]], +}) + +const resolver = new IndyVdrSovDidResolver() + +describe('DidResolver', () => { + describe('IndyVdrSovDidResolver', () => { + it('should correctly resolve a did:sov document', async () => { + const did = 'did:sov:R1xKJw17sUoXhejEpugMYJ' + + const nymResponse = { + result: { + data: JSON.stringify({ + did: 'R1xKJw17sUoXhejEpugMYJ', + verkey: 'E6D1m3eERqCueX4ZgMCY14B4NceAr6XP2HyVqt55gDhu', + role: 'ENDORSER', + }), + }, + } + + const attribResponse = { + result: { + data: JSON.stringify({ + endpoint: { + endpoint: 'https://ssi.com', + profile: 'https://profile.com', + hub: 'https://hub.com', + }, + }), + }, + } + + jest.spyOn(poolMock, 'submitRequest').mockResolvedValueOnce(nymResponse) + jest.spyOn(poolMock, 'submitRequest').mockResolvedValueOnce(attribResponse) + + const result = await resolver.resolve(agentContext, did, parseDid(did)) + + expect(JsonTransformer.toJSON(result)).toMatchObject({ + didDocument: didSovR1xKJw17sUoXhejEpugMYJFixture, + didDocumentMetadata: {}, + didResolutionMetadata: { + contentType: 'application/did+ld+json', + }, + }) + }) + + it('should resolve a did:sov document with routingKeys and types entries in the attrib', async () => { + const did = 'did:sov:WJz9mHyW9BZksioQnRsrAo' + + const nymResponse = { + result: { + data: JSON.stringify({ + did: 'WJz9mHyW9BZksioQnRsrAo', + verkey: 'GyYtYWU1vjwd5PFJM4VSX5aUiSV3TyZMuLBJBTQvfdF8', + role: 'ENDORSER', + }), + }, + } + + const attribResponse = { + result: { + data: JSON.stringify({ + endpoint: { + endpoint: 'https://agent.com', + types: ['endpoint', 'did-communication', 'DIDCommMessaging'], + routingKeys: ['routingKey1', 'routingKey2'], + }, + }), + }, + } + + jest.spyOn(poolMock, 'submitRequest').mockResolvedValueOnce(nymResponse) + jest.spyOn(poolMock, 'submitRequest').mockResolvedValueOnce(attribResponse) + + const result = await resolver.resolve(agentContext, did, parseDid(did)) + + expect(JsonTransformer.toJSON(result)).toMatchObject({ + didDocument: didSovWJz9mHyW9BZksioQnRsrAoFixture, + didDocumentMetadata: {}, + didResolutionMetadata: { + contentType: 'application/did+ld+json', + }, + }) + }) + + it('should return did resolution metadata with error if the indy ledger service throws an error', async () => { + const did = 'did:sov:R1xKJw17sUoXhejEpugMYJ' + + jest.spyOn(poolMock, 'submitRequest').mockRejectedValue(new Error('Error submitting read request')) + + const result = await resolver.resolve(agentContext, did, parseDid(did)) + + expect(result).toMatchObject({ + didDocument: null, + didDocumentMetadata: {}, + didResolutionMetadata: { + error: 'notFound', + message: `resolver_error: Unable to resolve did 'did:sov:R1xKJw17sUoXhejEpugMYJ': Error: Error submitting read request`, + }, + }) + }) + }) +}) diff --git a/packages/indy-vdr/src/dids/__tests__/__fixtures__/didExample123.json b/packages/indy-vdr/src/dids/__tests__/__fixtures__/didExample123.json new file mode 100644 index 0000000000..c26715ddc1 --- /dev/null +++ b/packages/indy-vdr/src/dids/__tests__/__fixtures__/didExample123.json @@ -0,0 +1,100 @@ +{ + "@context": ["https://w3id.org/did/v1"], + "id": "did:example:123", + "alsoKnownAs": ["did:example:456"], + "controller": ["did:example:456"], + "verificationMethod": [ + { + "id": "did:example:123#verkey", + "type": "RsaVerificationKey2018", + "controller": "did:sov:LjgpST2rjsoxYegQDRm7EL", + "publicKeyPem": "-----BEGIN PUBLIC X..." + }, + { + "id": "did:example:123#key-2", + "type": "Ed25519VerificationKey2018", + "controller": "did:sov:LjgpST2rjsoxYegQDRm7EL", + "publicKeyBase58": "-----BEGIN PUBLIC 9..." + }, + { + "id": "did:example:123#key-3", + "type": "Secp256k1VerificationKey2018", + "controller": "did:sov:LjgpST2rjsoxYegQDRm7EL", + "publicKeyHex": "-----BEGIN PUBLIC A..." + } + ], + "service": [ + { + "id": "did:example:123#service-1", + "type": "Mediator", + "serviceEndpoint": "did:sov:Q4zqM7aXqm7gDQkUVLng9h" + }, + { + "id": "did:example:123#service-2", + "type": "IndyAgent", + "serviceEndpoint": "did:sov:Q4zqM7aXqm7gDQkUVLng9h", + "recipientKeys": ["Q4zqM7aXqm7gDQkUVLng9h"], + "routingKeys": ["Q4zqM7aXqm7gDQkUVLng9h"], + "priority": 5 + }, + { + "id": "did:example:123#service-3", + "type": "did-communication", + "serviceEndpoint": "https://agent.com/did-comm", + "recipientKeys": ["DADEajsDSaksLng9h"], + "routingKeys": ["DADEajsDSaksLng9h"], + "priority": 10 + } + ], + "authentication": [ + "did:example:123#verkey", + { + "id": "did:example:123#authentication-1", + "type": "RsaVerificationKey2018", + "controller": "did:sov:LjgpST2rjsoxYegQDRm7EL", + "publicKeyPem": "-----BEGIN PUBLIC A..." + } + ], + "assertionMethod": [ + "did:example:123#verkey", + { + "id": "did:example:123#assertionMethod-1", + "type": "RsaVerificationKey2018", + "controller": "did:sov:LjgpST2rjsoxYegQDRm7EL", + "publicKeyPem": "-----BEGIN PUBLIC A..." + } + ], + "capabilityDelegation": [ + "did:example:123#verkey", + { + "id": "did:example:123#capabilityDelegation-1", + "type": "RsaVerificationKey2018", + "controller": "did:sov:LjgpST2rjsoxYegQDRm7EL", + "publicKeyPem": "-----BEGIN PUBLIC A..." + } + ], + "capabilityInvocation": [ + "did:example:123#verkey", + { + "id": "did:example:123#capabilityInvocation-1", + "type": "RsaVerificationKey2018", + "controller": "did:sov:LjgpST2rjsoxYegQDRm7EL", + "publicKeyPem": "-----BEGIN PUBLIC A..." + } + ], + "keyAgreement": [ + "did:example:123#verkey", + { + "id": "did:example:123#keyAgreement-1", + "type": "RsaVerificationKey2018", + "controller": "did:sov:LjgpST2rjsoxYegQDRm7EL", + "publicKeyPem": "-----BEGIN PUBLIC A..." + }, + { + "id": "did:example:123#keyAgreement-1", + "type": "Ed25519VerificationKey2018", + "controller": "did:sov:LjgpST2rjsoxYegQDRm7EL", + "publicKeyPem": "-----BEGIN PUBLIC A..." + } + ] +} diff --git a/packages/indy-vdr/src/dids/__tests__/__fixtures__/didExample123base.json b/packages/indy-vdr/src/dids/__tests__/__fixtures__/didExample123base.json new file mode 100644 index 0000000000..ce8b62392f --- /dev/null +++ b/packages/indy-vdr/src/dids/__tests__/__fixtures__/didExample123base.json @@ -0,0 +1,12 @@ +{ + "id": "did:example:123", + "verificationMethod": [ + { + "id": "did:example:123#verkey", + "type": "RsaVerificationKey2018", + "controller": "did:sov:LjgpST2rjsoxYegQDRm7EL", + "publicKeyPem": "-----BEGIN PUBLIC X..." + } + ], + "authentication": ["did:example:123#verkey"] +} diff --git a/packages/indy-vdr/src/dids/__tests__/__fixtures__/didExample123extracontent.json b/packages/indy-vdr/src/dids/__tests__/__fixtures__/didExample123extracontent.json new file mode 100644 index 0000000000..81397021cd --- /dev/null +++ b/packages/indy-vdr/src/dids/__tests__/__fixtures__/didExample123extracontent.json @@ -0,0 +1,92 @@ +{ + "@context": ["https://w3id.org/did/v1"], + "alsoKnownAs": ["did:example:456"], + "controller": ["did:example:456"], + "verificationMethod": [ + { + "id": "did:example:123#key-2", + "type": "Ed25519VerificationKey2018", + "controller": "did:sov:LjgpST2rjsoxYegQDRm7EL", + "publicKeyBase58": "-----BEGIN PUBLIC 9..." + }, + { + "id": "did:example:123#key-3", + "type": "Secp256k1VerificationKey2018", + "controller": "did:sov:LjgpST2rjsoxYegQDRm7EL", + "publicKeyHex": "-----BEGIN PUBLIC A..." + } + ], + "service": [ + { + "id": "did:example:123#service-1", + "type": "Mediator", + "serviceEndpoint": "did:sov:Q4zqM7aXqm7gDQkUVLng9h" + }, + { + "id": "did:example:123#service-2", + "type": "IndyAgent", + "serviceEndpoint": "did:sov:Q4zqM7aXqm7gDQkUVLng9h", + "recipientKeys": ["Q4zqM7aXqm7gDQkUVLng9h"], + "routingKeys": ["Q4zqM7aXqm7gDQkUVLng9h"], + "priority": 5 + }, + { + "id": "did:example:123#service-3", + "type": "did-communication", + "serviceEndpoint": "https://agent.com/did-comm", + "recipientKeys": ["DADEajsDSaksLng9h"], + "routingKeys": ["DADEajsDSaksLng9h"], + "priority": 10 + } + ], + "authentication": [ + { + "id": "did:example:123#authentication-1", + "type": "RsaVerificationKey2018", + "controller": "did:sov:LjgpST2rjsoxYegQDRm7EL", + "publicKeyPem": "-----BEGIN PUBLIC A..." + } + ], + "assertionMethod": [ + "did:example:123#verkey", + { + "id": "did:example:123#assertionMethod-1", + "type": "RsaVerificationKey2018", + "controller": "did:sov:LjgpST2rjsoxYegQDRm7EL", + "publicKeyPem": "-----BEGIN PUBLIC A..." + } + ], + "capabilityDelegation": [ + "did:example:123#verkey", + { + "id": "did:example:123#capabilityDelegation-1", + "type": "RsaVerificationKey2018", + "controller": "did:sov:LjgpST2rjsoxYegQDRm7EL", + "publicKeyPem": "-----BEGIN PUBLIC A..." + } + ], + "capabilityInvocation": [ + "did:example:123#verkey", + { + "id": "did:example:123#capabilityInvocation-1", + "type": "RsaVerificationKey2018", + "controller": "did:sov:LjgpST2rjsoxYegQDRm7EL", + "publicKeyPem": "-----BEGIN PUBLIC A..." + } + ], + "keyAgreement": [ + "did:example:123#verkey", + { + "id": "did:example:123#keyAgreement-1", + "type": "RsaVerificationKey2018", + "controller": "did:sov:LjgpST2rjsoxYegQDRm7EL", + "publicKeyPem": "-----BEGIN PUBLIC A..." + }, + { + "id": "did:example:123#keyAgreement-1", + "type": "Ed25519VerificationKey2018", + "controller": "did:sov:LjgpST2rjsoxYegQDRm7EL", + "publicKeyPem": "-----BEGIN PUBLIC A..." + } + ] +} diff --git a/packages/indy-vdr/src/dids/__tests__/__fixtures__/didIndyLjgpST2rjsoxYegQDRm7EL.json b/packages/indy-vdr/src/dids/__tests__/__fixtures__/didIndyLjgpST2rjsoxYegQDRm7EL.json new file mode 100644 index 0000000000..49e43fa742 --- /dev/null +++ b/packages/indy-vdr/src/dids/__tests__/__fixtures__/didIndyLjgpST2rjsoxYegQDRm7EL.json @@ -0,0 +1,110 @@ +{ + "@context": [ + "https://w3id.org/did/v1", + "https://w3id.org/security/suites/ed25519-2018/v1", + "https://w3id.org/security/suites/x25519-2019/v1" + ], + "id": "did:indy:ns2:LjgpST2rjsoxYegQDRm7EL", + "alsoKnownAs": ["did:indy:ns2:R1xKJw17sUoXhejEpugMYJ"], + "controller": ["did:indy:ns2:R1xKJw17sUoXhejEpugMYJ"], + "verificationMethod": [ + { + "type": "Ed25519VerificationKey2018", + "controller": "did:indy:ns2:LjgpST2rjsoxYegQDRm7EL", + "id": "did:indy:ns2:LjgpST2rjsoxYegQDRm7EL#verkey", + "publicKeyBase58": "E6D1m3eERqCueX4ZgMCY14B4NceAr6XP2HyVqt55gDhu" + }, + { + "id": "did:indy:ns2:LjgpST2rjsoxYegQDRm7EL#key-2", + "type": "RsaVerificationKey2018", + "controller": "did:indy:ns2:WJz9mHyW9BZksioQnRsrAo", + "publicKeyPem": "-----BEGIN PUBLIC X..." + }, + { + "id": "did:indy:ns2:LjgpST2rjsoxYegQDRm7EL#key-3", + "type": "Ed25519VerificationKey2018", + "controller": "did:indy:ns2:WJz9mHyW9BZksioQnRsrAo", + "publicKeyBase58": "-----BEGIN PUBLIC 9..." + }, + { + "id": "did:indy:ns2:LjgpST2rjsoxYegQDRm7EL#key-4", + "type": "Secp256k1VerificationKey2018", + "controller": "did:indy:ns2:WJz9mHyW9BZksioQnRsrAo", + "publicKeyHex": "-----BEGIN PUBLIC A..." + } + ], + "service": [ + { + "id": "did:indy:ns2:LjgpST2rjsoxYegQDRm7EL#service-1", + "type": "Mediator", + "serviceEndpoint": "did:sov:Q4zqM7aXqm7gDQkUVLng9h" + }, + { + "id": "did:indy:ns2:LjgpST2rjsoxYegQDRm7EL#service-2", + "type": "IndyAgent", + "serviceEndpoint": "did:sov:Q4zqM7aXqm7gDQkUVLng9h", + "recipientKeys": ["Q4zqM7aXqm7gDQkUVLng9h"], + "routingKeys": ["Q4zqM7aXqm7gDQkUVLng9h"], + "priority": 5 + }, + { + "id": "did:indy:ns2:LjgpST2rjsoxYegQDRm7EL#service-3", + "type": "did-communication", + "serviceEndpoint": "https://agent.com/did-comm", + "recipientKeys": ["DADEajsDSaksLng9h"], + "routingKeys": ["DADEajsDSaksLng9h"], + "priority": 10 + } + ], + "authentication": [ + "did:indy:ns2:LjgpST2rjsoxYegQDRm7EL#verkey", + { + "id": "did:indy:ns2:LjgpST2rjsoxYegQDRm7EL#authentication-1", + "type": "RsaVerificationKey2018", + "controller": "did:indy:ns2:WJz9mHyW9BZksioQnRsrAo", + "publicKeyPem": "-----BEGIN PUBLIC A..." + } + ], + "assertionMethod": [ + "did:indy:ns2:LjgpST2rjsoxYegQDRm7EL#key-1", + { + "id": "did:indy:ns2:LjgpST2rjsoxYegQDRm7EL#assertionMethod-1", + "type": "RsaVerificationKey2018", + "controller": "did:indy:ns2:WJz9mHyW9BZksioQnRsrAo", + "publicKeyPem": "-----BEGIN PUBLIC A..." + } + ], + "capabilityDelegation": [ + "did:indy:ns2:LjgpST2rjsoxYegQDRm7EL#key-1", + { + "id": "did:indy:ns2:LjgpST2rjsoxYegQDRm7EL#capabilityDelegation-1", + "type": "RsaVerificationKey2018", + "controller": "did:indy:ns2:WJz9mHyW9BZksioQnRsrAo", + "publicKeyPem": "-----BEGIN PUBLIC A..." + } + ], + "capabilityInvocation": [ + "did:indy:ns2:LjgpST2rjsoxYegQDRm7EL#key-1", + { + "id": "did:indy:ns2:LjgpST2rjsoxYegQDRm7EL#capabilityInvocation-1", + "type": "RsaVerificationKey2018", + "controller": "did:indy:ns2:WJz9mHyW9BZksioQnRsrAo", + "publicKeyPem": "-----BEGIN PUBLIC A..." + } + ], + "keyAgreement": [ + "did:indy:ns2:LjgpST2rjsoxYegQDRm7EL#key-1", + { + "id": "did:indy:ns2:LjgpST2rjsoxYegQDRm7EL#keyAgreement-1", + "type": "RsaVerificationKey2018", + "controller": "did:indy:ns2:WJz9mHyW9BZksioQnRsrAo", + "publicKeyPem": "-----BEGIN PUBLIC A..." + }, + { + "id": "did:indy:ns2:LjgpST2rjsoxYegQDRm7EL#keyAgreement-1", + "type": "Ed25519VerificationKey2018", + "controller": "did:indy:ns2:WJz9mHyW9BZksioQnRsrAo", + "publicKeyPem": "-----BEGIN PUBLIC A..." + } + ] +} diff --git a/packages/indy-vdr/src/dids/__tests__/__fixtures__/didIndyLjgpST2rjsoxYegQDRm7ELdiddocContent.json b/packages/indy-vdr/src/dids/__tests__/__fixtures__/didIndyLjgpST2rjsoxYegQDRm7ELdiddocContent.json new file mode 100644 index 0000000000..94bfaed219 --- /dev/null +++ b/packages/indy-vdr/src/dids/__tests__/__fixtures__/didIndyLjgpST2rjsoxYegQDRm7ELdiddocContent.json @@ -0,0 +1,98 @@ +{ + "@context": ["https://w3id.org/security/suites/ed25519-2018/v1", "https://w3id.org/security/suites/x25519-2019/v1"], + "alsoKnownAs": ["did:indy:ns2:R1xKJw17sUoXhejEpugMYJ"], + "controller": ["did:indy:ns2:R1xKJw17sUoXhejEpugMYJ"], + "verificationMethod": [ + { + "id": "did:indy:ns2:LjgpST2rjsoxYegQDRm7EL#key-2", + "type": "RsaVerificationKey2018", + "controller": "did:indy:ns2:WJz9mHyW9BZksioQnRsrAo", + "publicKeyPem": "-----BEGIN PUBLIC X..." + }, + { + "id": "did:indy:ns2:LjgpST2rjsoxYegQDRm7EL#key-3", + "type": "Ed25519VerificationKey2018", + "controller": "did:indy:ns2:WJz9mHyW9BZksioQnRsrAo", + "publicKeyBase58": "-----BEGIN PUBLIC 9..." + }, + { + "id": "did:indy:ns2:LjgpST2rjsoxYegQDRm7EL#key-4", + "type": "Secp256k1VerificationKey2018", + "controller": "did:indy:ns2:WJz9mHyW9BZksioQnRsrAo", + "publicKeyHex": "-----BEGIN PUBLIC A..." + } + ], + "service": [ + { + "id": "did:indy:ns2:LjgpST2rjsoxYegQDRm7EL#service-1", + "type": "Mediator", + "serviceEndpoint": "did:sov:Q4zqM7aXqm7gDQkUVLng9h" + }, + { + "id": "did:indy:ns2:LjgpST2rjsoxYegQDRm7EL#service-2", + "type": "IndyAgent", + "serviceEndpoint": "did:sov:Q4zqM7aXqm7gDQkUVLng9h", + "recipientKeys": ["Q4zqM7aXqm7gDQkUVLng9h"], + "routingKeys": ["Q4zqM7aXqm7gDQkUVLng9h"], + "priority": 5 + }, + { + "id": "did:indy:ns2:LjgpST2rjsoxYegQDRm7EL#service-3", + "type": "did-communication", + "serviceEndpoint": "https://agent.com/did-comm", + "recipientKeys": ["DADEajsDSaksLng9h"], + "routingKeys": ["DADEajsDSaksLng9h"], + "priority": 10 + } + ], + "authentication": [ + { + "id": "did:indy:ns2:LjgpST2rjsoxYegQDRm7EL#authentication-1", + "type": "RsaVerificationKey2018", + "controller": "did:indy:ns2:WJz9mHyW9BZksioQnRsrAo", + "publicKeyPem": "-----BEGIN PUBLIC A..." + } + ], + "assertionMethod": [ + "did:indy:ns2:LjgpST2rjsoxYegQDRm7EL#key-1", + { + "id": "did:indy:ns2:LjgpST2rjsoxYegQDRm7EL#assertionMethod-1", + "type": "RsaVerificationKey2018", + "controller": "did:indy:ns2:WJz9mHyW9BZksioQnRsrAo", + "publicKeyPem": "-----BEGIN PUBLIC A..." + } + ], + "capabilityDelegation": [ + "did:indy:ns2:LjgpST2rjsoxYegQDRm7EL#key-1", + { + "id": "did:indy:ns2:LjgpST2rjsoxYegQDRm7EL#capabilityDelegation-1", + "type": "RsaVerificationKey2018", + "controller": "did:indy:ns2:WJz9mHyW9BZksioQnRsrAo", + "publicKeyPem": "-----BEGIN PUBLIC A..." + } + ], + "capabilityInvocation": [ + "did:indy:ns2:LjgpST2rjsoxYegQDRm7EL#key-1", + { + "id": "did:indy:ns2:LjgpST2rjsoxYegQDRm7EL#capabilityInvocation-1", + "type": "RsaVerificationKey2018", + "controller": "did:indy:ns2:WJz9mHyW9BZksioQnRsrAo", + "publicKeyPem": "-----BEGIN PUBLIC A..." + } + ], + "keyAgreement": [ + "did:indy:ns2:LjgpST2rjsoxYegQDRm7EL#key-1", + { + "id": "did:indy:ns2:LjgpST2rjsoxYegQDRm7EL#keyAgreement-1", + "type": "RsaVerificationKey2018", + "controller": "did:indy:ns2:WJz9mHyW9BZksioQnRsrAo", + "publicKeyPem": "-----BEGIN PUBLIC A..." + }, + { + "id": "did:indy:ns2:LjgpST2rjsoxYegQDRm7EL#keyAgreement-1", + "type": "Ed25519VerificationKey2018", + "controller": "did:indy:ns2:WJz9mHyW9BZksioQnRsrAo", + "publicKeyPem": "-----BEGIN PUBLIC A..." + } + ] +} diff --git a/packages/indy-vdr/src/dids/__tests__/__fixtures__/didIndyR1xKJw17sUoXhejEpugMYJ.json b/packages/indy-vdr/src/dids/__tests__/__fixtures__/didIndyR1xKJw17sUoXhejEpugMYJ.json new file mode 100644 index 0000000000..68874b6fc2 --- /dev/null +++ b/packages/indy-vdr/src/dids/__tests__/__fixtures__/didIndyR1xKJw17sUoXhejEpugMYJ.json @@ -0,0 +1,13 @@ +{ + "@context": ["https://w3id.org/did/v1", "https://w3id.org/security/suites/ed25519-2018/v1"], + "id": "did:indy:ns1:R1xKJw17sUoXhejEpugMYJ", + "verificationMethod": [ + { + "type": "Ed25519VerificationKey2018", + "controller": "did:indy:ns1:R1xKJw17sUoXhejEpugMYJ", + "id": "did:indy:ns1:R1xKJw17sUoXhejEpugMYJ#verkey", + "publicKeyBase58": "E6D1m3eERqCueX4ZgMCY14B4NceAr6XP2HyVqt55gDhu" + } + ], + "authentication": ["did:indy:ns1:R1xKJw17sUoXhejEpugMYJ#verkey"] +} diff --git a/packages/indy-vdr/src/dids/__tests__/__fixtures__/didIndyWJz9mHyW9BZksioQnRsrAo.json b/packages/indy-vdr/src/dids/__tests__/__fixtures__/didIndyWJz9mHyW9BZksioQnRsrAo.json new file mode 100644 index 0000000000..b73953aae8 --- /dev/null +++ b/packages/indy-vdr/src/dids/__tests__/__fixtures__/didIndyWJz9mHyW9BZksioQnRsrAo.json @@ -0,0 +1,48 @@ +{ + "@context": [ + "https://w3id.org/did/v1", + "https://w3id.org/security/suites/ed25519-2018/v1", + "https://w3id.org/security/suites/x25519-2019/v1", + "https://didcomm.org/messaging/contexts/v2" + ], + "id": "did:indy:ns1:WJz9mHyW9BZksioQnRsrAo", + "verificationMethod": [ + { + "type": "Ed25519VerificationKey2018", + "controller": "did:indy:ns1:WJz9mHyW9BZksioQnRsrAo", + "id": "did:indy:ns1:WJz9mHyW9BZksioQnRsrAo#verkey", + "publicKeyBase58": "GyYtYWU1vjwd5PFJM4VSX5aUiSV3TyZMuLBJBTQvfdF8" + }, + { + "type": "X25519KeyAgreementKey2019", + "controller": "did:indy:ns1:WJz9mHyW9BZksioQnRsrAo", + "id": "did:indy:ns1:WJz9mHyW9BZksioQnRsrAo#key-agreement-1", + "publicKeyBase58": "S3AQEEKkGYrrszT9D55ozVVX2XixYp8uynqVm4okbud" + } + ], + "authentication": ["did:indy:ns1:WJz9mHyW9BZksioQnRsrAo#verkey"], + "keyAgreement": ["did:indy:ns1:WJz9mHyW9BZksioQnRsrAo#key-agreement-1"], + "service": [ + { + "id": "did:indy:ns1:WJz9mHyW9BZksioQnRsrAo#endpoint", + "type": "endpoint", + "serviceEndpoint": "https://agent.com" + }, + { + "id": "did:indy:ns1:WJz9mHyW9BZksioQnRsrAo#did-communication", + "type": "did-communication", + "priority": 0, + "recipientKeys": ["did:indy:ns1:WJz9mHyW9BZksioQnRsrAo#key-agreement-1"], + "routingKeys": ["routingKey1", "routingKey2"], + "accept": ["didcomm/aip2;env=rfc19"], + "serviceEndpoint": "https://agent.com" + }, + { + "id": "did:indy:ns1:WJz9mHyW9BZksioQnRsrAo#didcomm-1", + "type": "DIDCommMessaging", + "serviceEndpoint": "https://agent.com", + "accept": ["didcomm/v2"], + "routingKeys": ["routingKey1", "routingKey2"] + } + ] +} diff --git a/packages/indy-vdr/src/dids/__tests__/__fixtures__/didSovR1xKJw17sUoXhejEpugMYJ.json b/packages/indy-vdr/src/dids/__tests__/__fixtures__/didSovR1xKJw17sUoXhejEpugMYJ.json new file mode 100644 index 0000000000..6a6e4ed706 --- /dev/null +++ b/packages/indy-vdr/src/dids/__tests__/__fixtures__/didSovR1xKJw17sUoXhejEpugMYJ.json @@ -0,0 +1,51 @@ +{ + "@context": [ + "https://w3id.org/did/v1", + "https://w3id.org/security/suites/ed25519-2018/v1", + "https://w3id.org/security/suites/x25519-2019/v1" + ], + "id": "did:sov:R1xKJw17sUoXhejEpugMYJ", + "verificationMethod": [ + { + "type": "Ed25519VerificationKey2018", + "controller": "did:sov:R1xKJw17sUoXhejEpugMYJ", + "id": "did:sov:R1xKJw17sUoXhejEpugMYJ#key-1", + "publicKeyBase58": "E6D1m3eERqCueX4ZgMCY14B4NceAr6XP2HyVqt55gDhu" + }, + { + "type": "X25519KeyAgreementKey2019", + "controller": "did:sov:R1xKJw17sUoXhejEpugMYJ", + "id": "did:sov:R1xKJw17sUoXhejEpugMYJ#key-agreement-1", + "publicKeyBase58": "Fbv17ZbnUSbafsiUBJbdGeC62M8v8GEscVMMcE59mRPt" + } + ], + "authentication": ["did:sov:R1xKJw17sUoXhejEpugMYJ#key-1"], + "assertionMethod": ["did:sov:R1xKJw17sUoXhejEpugMYJ#key-1"], + "keyAgreement": ["did:sov:R1xKJw17sUoXhejEpugMYJ#key-agreement-1"], + "service": [ + { + "id": "did:sov:R1xKJw17sUoXhejEpugMYJ#endpoint", + "type": "endpoint", + "serviceEndpoint": "https://ssi.com" + }, + { + "accept": ["didcomm/aip2;env=rfc19"], + "id": "did:sov:R1xKJw17sUoXhejEpugMYJ#did-communication", + "priority": 0, + "recipientKeys": ["did:sov:R1xKJw17sUoXhejEpugMYJ#key-agreement-1"], + "routingKeys": [], + "serviceEndpoint": "https://ssi.com", + "type": "did-communication" + }, + { + "id": "did:sov:R1xKJw17sUoXhejEpugMYJ#profile", + "serviceEndpoint": "https://profile.com", + "type": "profile" + }, + { + "id": "did:sov:R1xKJw17sUoXhejEpugMYJ#hub", + "serviceEndpoint": "https://hub.com", + "type": "hub" + } + ] +} diff --git a/packages/indy-vdr/src/dids/__tests__/__fixtures__/didSovWJz9mHyW9BZksioQnRsrAo.json b/packages/indy-vdr/src/dids/__tests__/__fixtures__/didSovWJz9mHyW9BZksioQnRsrAo.json new file mode 100644 index 0000000000..81bbc35e06 --- /dev/null +++ b/packages/indy-vdr/src/dids/__tests__/__fixtures__/didSovWJz9mHyW9BZksioQnRsrAo.json @@ -0,0 +1,49 @@ +{ + "@context": [ + "https://w3id.org/did/v1", + "https://w3id.org/security/suites/ed25519-2018/v1", + "https://w3id.org/security/suites/x25519-2019/v1", + "https://didcomm.org/messaging/contexts/v2" + ], + "id": "did:sov:WJz9mHyW9BZksioQnRsrAo", + "verificationMethod": [ + { + "type": "Ed25519VerificationKey2018", + "controller": "did:sov:WJz9mHyW9BZksioQnRsrAo", + "id": "did:sov:WJz9mHyW9BZksioQnRsrAo#key-1", + "publicKeyBase58": "GyYtYWU1vjwd5PFJM4VSX5aUiSV3TyZMuLBJBTQvfdF8" + }, + { + "type": "X25519KeyAgreementKey2019", + "controller": "did:sov:WJz9mHyW9BZksioQnRsrAo", + "id": "did:sov:WJz9mHyW9BZksioQnRsrAo#key-agreement-1", + "publicKeyBase58": "S3AQEEKkGYrrszT9D55ozVVX2XixYp8uynqVm4okbud" + } + ], + "authentication": ["did:sov:WJz9mHyW9BZksioQnRsrAo#key-1"], + "assertionMethod": ["did:sov:WJz9mHyW9BZksioQnRsrAo#key-1"], + "keyAgreement": ["did:sov:WJz9mHyW9BZksioQnRsrAo#key-agreement-1"], + "service": [ + { + "id": "did:sov:WJz9mHyW9BZksioQnRsrAo#endpoint", + "type": "endpoint", + "serviceEndpoint": "https://agent.com" + }, + { + "id": "did:sov:WJz9mHyW9BZksioQnRsrAo#did-communication", + "type": "did-communication", + "priority": 0, + "recipientKeys": ["did:sov:WJz9mHyW9BZksioQnRsrAo#key-agreement-1"], + "routingKeys": ["routingKey1", "routingKey2"], + "accept": ["didcomm/aip2;env=rfc19"], + "serviceEndpoint": "https://agent.com" + }, + { + "id": "did:sov:WJz9mHyW9BZksioQnRsrAo#didcomm-1", + "type": "DIDCommMessaging", + "serviceEndpoint": "https://agent.com", + "accept": ["didcomm/v2"], + "routingKeys": ["routingKey1", "routingKey2"] + } + ] +} diff --git a/packages/indy-vdr/src/dids/__tests__/didIndyUtil.test.ts b/packages/indy-vdr/src/dids/__tests__/didIndyUtil.test.ts new file mode 100644 index 0000000000..81c8218274 --- /dev/null +++ b/packages/indy-vdr/src/dids/__tests__/didIndyUtil.test.ts @@ -0,0 +1,23 @@ +import { DidDocument, JsonTransformer } from '@aries-framework/core' + +import { combineDidDocumentWithJson, didDocDiff } from '../didIndyUtil' + +import didExample123Fixture from './__fixtures__/didExample123.json' +import didExample123Base from './__fixtures__/didExample123base.json' +import didExample123Extra from './__fixtures__/didExample123extracontent.json' + +describe('didIndyUtil', () => { + describe('combineDidDocumentWithJson', () => { + it('should correctly combine a base DIDDoc with extra contents from a JSON object', async () => { + const didDocument = JsonTransformer.fromJSON(didExample123Base, DidDocument) + + expect(combineDidDocumentWithJson(didDocument, didExample123Extra).toJSON()).toEqual(didExample123Fixture) + }) + }) + + describe('deepObjectDiff', () => { + it('should correctly show the diff between a base DidDocument and a full DidDocument', async () => { + expect(didDocDiff(didExample123Fixture, didExample123Base)).toMatchObject(didExample123Extra) + }) + }) +}) diff --git a/packages/indy-vdr/src/dids/__tests__/didSovUtil.test.ts b/packages/indy-vdr/src/dids/__tests__/didSovUtil.test.ts new file mode 100644 index 0000000000..f09f8060bc --- /dev/null +++ b/packages/indy-vdr/src/dids/__tests__/didSovUtil.test.ts @@ -0,0 +1,5 @@ +describe('didSovUtil', () => { + describe('endpointsAttribFromServices', () => { + it.todo('should correctly transform DidDocumentService instances to endpoint Attrib') + }) +}) diff --git a/packages/indy-vdr/src/dids/didIndyUtil.ts b/packages/indy-vdr/src/dids/didIndyUtil.ts new file mode 100644 index 0000000000..8fd8fcdf9a --- /dev/null +++ b/packages/indy-vdr/src/dids/didIndyUtil.ts @@ -0,0 +1,277 @@ +import type { GetNymResponseData, IndyEndpointAttrib } from './didSovUtil' +import type { IndyVdrPool } from '../pool' +import type { AgentContext } from '@aries-framework/core' + +import { parseIndyDid } from '@aries-framework/anoncreds' +import { + AriesFrameworkError, + DidDocument, + DidDocumentBuilder, + DidsApi, + Hasher, + JsonTransformer, + Key, + KeyType, + TypedArrayEncoder, + convertPublicKeyToX25519, + getKeyFromVerificationMethod, +} from '@aries-framework/core' +import { GetAttribRequest, GetNymRequest } from '@hyperledger/indy-vdr-shared' + +import { IndyVdrError, IndyVdrNotFoundError } from '../error' + +import { addServicesFromEndpointsAttrib, getFullVerkey } from './didSovUtil' + +// Create a base DIDDoc template according to https://hyperledger.github.io/indy-did-method/#base-diddoc-template +export function indyDidDocumentFromDid(did: string, verKeyBase58: string) { + const verificationMethodId = `${did}#verkey` + + const publicKeyBase58 = verKeyBase58 + + const builder = new DidDocumentBuilder(did) + .addContext('https://w3id.org/security/suites/ed25519-2018/v1') + .addVerificationMethod({ + controller: did, + id: verificationMethodId, + publicKeyBase58, + type: 'Ed25519VerificationKey2018', + }) + .addAuthentication(verificationMethodId) + + return builder +} + +export function createKeyAgreementKey(verkey: string) { + return TypedArrayEncoder.toBase58(convertPublicKeyToX25519(TypedArrayEncoder.fromBase58(verkey))) +} + +const deepMerge = (a: Record, b: Record) => { + const output: Record = {} + + ;[...new Set([...Object.keys(a), ...Object.keys(b)])].forEach((key) => { + // Only an object includes a given key: just output it + if (a[key] && !b[key]) { + output[key] = a[key] + } else if (!a[key] && b[key]) { + output[key] = b[key] + } else { + // Both objects do include the key + // Some or both are arrays + if (Array.isArray(a[key])) { + if (Array.isArray(b[key])) { + const element = new Set() + ;(a[key] as Array).forEach((item: unknown) => element.add(item)) + ;(b[key] as Array).forEach((item: unknown) => element.add(item)) + output[key] = Array.from(element) + } else { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const arr = a[key] as Array + output[key] = Array.from(new Set(...arr, b[key])) + } + } else if (Array.isArray(b[key])) { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const arr = b[key] as Array + output[key] = Array.from(new Set(...arr, a[key])) + // Both elements are objects: recursive merge + } else if (typeof a[key] == 'object' && typeof b[key] == 'object') { + output[key] = deepMerge(a, b) + } + } + }) + return output +} + +/** + * Combine a JSON content with the contents of a DidDocument + * @param didDoc object containing original DIDDocument + * @param json object containing extra DIDDoc contents + * + * @returns a DidDocument object resulting from the combination of both + */ +export function combineDidDocumentWithJson(didDoc: DidDocument, json: Record) { + const didDocJson = didDoc.toJSON() + const combinedJson = deepMerge(didDocJson, json) + return JsonTransformer.fromJSON(combinedJson, DidDocument) +} + +/** + * Processes the difference between a base DidDocument and a complete DidDocument + * + * Note: it does deep comparison based only on "id" field to determine whether is + * the same object or is a different one + * + * @param extra complete DidDocument + * @param base base DidDocument + * @returns diff object + */ +export function didDocDiff(extra: Record, base: Record) { + const output: Record = {} + for (const key in extra) { + if (!(key in base)) { + output[key] = extra[key] + } else { + // They are arrays: compare elements + if (Array.isArray(extra[key]) && Array.isArray(base[key])) { + // Different types: return the extra + output[key] = [] + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const baseAsArray = base[key] as Array + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const extraAsArray = extra[key] as Array + for (const element of extraAsArray) { + if (!baseAsArray.find((item) => item.id === element.id)) { + ;(output[key] as Array).push(element) + } + } + } // They are both objects: do recursive diff + else if (typeof extra[key] == 'object' && typeof base[key] == 'object') { + output[key] = didDocDiff(extra[key] as Record, base[key] as Record) + } else { + output[key] = extra[key] + } + } + } + return output +} + +/** + * Check whether the did is a self certifying did. If the verkey is abbreviated this method + * will always return true. Make sure that the verkey you pass in this method belongs to the + * did passed in + * + * @return Boolean indicating whether the did is self certifying + */ +export function isSelfCertifiedIndyDid(did: string, verkey: string): boolean { + const { namespace } = parseIndyDid(did) + const { did: didFromVerkey } = indyDidFromNamespaceAndInitialKey( + namespace, + Key.fromPublicKeyBase58(verkey, KeyType.Ed25519) + ) + + if (didFromVerkey === did) { + return true + } + + return false +} + +export function indyDidFromNamespaceAndInitialKey(namespace: string, initialKey: Key) { + const buffer = Hasher.hash(initialKey.publicKey, 'sha2-256') + + const id = TypedArrayEncoder.toBase58(buffer.slice(0, 16)) + const verkey = initialKey.publicKeyBase58 + const did = `did:indy:${namespace}:${id}` + + return { did, id, verkey } +} + +/** + * Fetches the verification key for a given did:indy did and returns the key as a {@link Key} object. + * + * @throws {@link AriesFrameworkError} if the did could not be resolved or the key could not be extracted + */ +export async function verificationKeyForIndyDid(agentContext: AgentContext, did: string) { + // FIXME: we should store the didDocument in the DidRecord so we don't have to fetch our own did + // from the ledger to know which key is associated with the did + const didsApi = agentContext.dependencyManager.resolve(DidsApi) + const didResult = await didsApi.resolve(did) + + if (!didResult.didDocument) { + throw new AriesFrameworkError( + `Could not resolve did ${did}. ${didResult.didResolutionMetadata.error} ${didResult.didResolutionMetadata.message}` + ) + } + + // did:indy dids MUST have a verificationMethod with #verkey + const verificationMethod = didResult.didDocument.dereferenceKey(`${did}#verkey`) + const key = getKeyFromVerificationMethod(verificationMethod) + + return key +} + +export async function getPublicDid(pool: IndyVdrPool, unqualifiedDid: string) { + const request = new GetNymRequest({ dest: unqualifiedDid }) + + const didResponse = await pool.submitRequest(request) + + if (!didResponse.result.data) { + throw new IndyVdrNotFoundError(`DID ${unqualifiedDid} not found in indy namespace ${pool.indyNamespace}`) + } + return JSON.parse(didResponse.result.data) as GetNymResponseData +} + +export async function getEndpointsForDid(agentContext: AgentContext, pool: IndyVdrPool, unqualifiedDid: string) { + try { + agentContext.config.logger.debug(`Get endpoints for did '${unqualifiedDid}' from ledger '${pool.indyNamespace}'`) + + const request = new GetAttribRequest({ targetDid: unqualifiedDid, raw: 'endpoint' }) + + agentContext.config.logger.debug( + `Submitting get endpoint ATTRIB request for did '${unqualifiedDid}' to ledger '${pool.indyNamespace}'` + ) + const response = await pool.submitRequest(request) + + if (!response.result.data) { + return null + } + + const endpoints = JSON.parse(response.result.data as string)?.endpoint as IndyEndpointAttrib + agentContext.config.logger.debug( + `Got endpoints '${JSON.stringify(endpoints)}' for did '${unqualifiedDid}' from ledger '${pool.indyNamespace}'`, + { + response, + endpoints, + } + ) + + return endpoints + } catch (error) { + agentContext.config.logger.error( + `Error retrieving endpoints for did '${unqualifiedDid}' from ledger '${pool.indyNamespace}'`, + { + error, + } + ) + + throw new IndyVdrError(error) + } +} + +export async function buildDidDocument(agentContext: AgentContext, pool: IndyVdrPool, did: string) { + const { namespaceIdentifier } = parseIndyDid(did) + + const nym = await getPublicDid(pool, namespaceIdentifier) + + // Create base Did Document + + // For modern did:indy DIDs, we assume that GET_NYM is always a full verkey in base58. + // For backwards compatibility, we accept a shortened verkey and convert it using previous convention + const verkey = getFullVerkey(namespaceIdentifier, nym.verkey) + + const builder = indyDidDocumentFromDid(did, verkey) + + // If GET_NYM does not return any diddocContent, fallback to legacy GET_ATTRIB endpoint + if (!nym.diddocContent) { + const keyAgreementId = `${did}#key-agreement-1` + const endpoints = await getEndpointsForDid(agentContext, pool, namespaceIdentifier) + + if (endpoints) { + builder + .addContext('https://w3id.org/security/suites/x25519-2019/v1') + .addVerificationMethod({ + controller: did, + id: keyAgreementId, + publicKeyBase58: createKeyAgreementKey(verkey), + type: 'X25519KeyAgreementKey2019', + }) + .addKeyAgreement(keyAgreementId) + + // Process endpoint attrib following the same rules as for did:sov + addServicesFromEndpointsAttrib(builder, did, endpoints, keyAgreementId) + } + return builder.build() + } else { + // Combine it with didDoc (TODO: Check if diddocContent is returned as a JSON object or a string) + return combineDidDocumentWithJson(builder.build(), nym.diddocContent) + } +} diff --git a/packages/indy-vdr/src/dids/didSovUtil.ts b/packages/indy-vdr/src/dids/didSovUtil.ts new file mode 100644 index 0000000000..0520e75532 --- /dev/null +++ b/packages/indy-vdr/src/dids/didSovUtil.ts @@ -0,0 +1,203 @@ +import { + TypedArrayEncoder, + DidDocumentService, + DidDocumentBuilder, + DidCommV1Service, + DidCommV2Service, + convertPublicKeyToX25519, + AriesFrameworkError, +} from '@aries-framework/core' + +export type CommEndpointType = 'endpoint' | 'did-communication' | 'DIDCommMessaging' + +export interface IndyEndpointAttrib { + endpoint?: string + types?: Array + routingKeys?: string[] + [key: string]: unknown +} + +export interface GetNymResponseData { + did: string + verkey: string + role: string + alias?: string + diddocContent?: Record +} + +export const FULL_VERKEY_REGEX = /^[1-9A-HJ-NP-Za-km-z]{43,44}$/ + +/** + * Check a base58 encoded string against a regex expression to determine if it is a full valid verkey + * @param verkey Base58 encoded string representation of a verkey + * @return Boolean indicating if the string is a valid verkey + */ +export function isFullVerkey(verkey: string): boolean { + return FULL_VERKEY_REGEX.test(verkey) +} + +export function getFullVerkey(did: string, verkey: string) { + if (isFullVerkey(verkey)) return verkey + + // Did could have did:xxx prefix, only take the last item after : + const id = did.split(':').pop() ?? did + // Verkey is prefixed with ~ if abbreviated + const verkeyWithoutTilde = verkey.slice(1) + + // Create base58 encoded public key (32 bytes) + return TypedArrayEncoder.toBase58( + Buffer.concat([ + // Take did identifier (16 bytes) + TypedArrayEncoder.fromBase58(id), + // Concat the abbreviated verkey (16 bytes) + TypedArrayEncoder.fromBase58(verkeyWithoutTilde), + ]) + ) +} + +export function sovDidDocumentFromDid(fullDid: string, verkey: string) { + const verificationMethodId = `${fullDid}#key-1` + const keyAgreementId = `${fullDid}#key-agreement-1` + + const publicKeyBase58 = getFullVerkey(fullDid, verkey) + const publicKeyX25519 = TypedArrayEncoder.toBase58( + convertPublicKeyToX25519(TypedArrayEncoder.fromBase58(publicKeyBase58)) + ) + + const builder = new DidDocumentBuilder(fullDid) + .addContext('https://w3id.org/security/suites/ed25519-2018/v1') + .addContext('https://w3id.org/security/suites/x25519-2019/v1') + .addVerificationMethod({ + controller: fullDid, + id: verificationMethodId, + publicKeyBase58: publicKeyBase58, + type: 'Ed25519VerificationKey2018', + }) + .addVerificationMethod({ + controller: fullDid, + id: keyAgreementId, + publicKeyBase58: publicKeyX25519, + type: 'X25519KeyAgreementKey2019', + }) + .addAuthentication(verificationMethodId) + .addAssertionMethod(verificationMethodId) + .addKeyAgreement(keyAgreementId) + + return builder +} + +// Process Indy Attrib Endpoint Types according to: https://sovrin-foundation.github.io/sovrin/spec/did-method-spec-template.html > Read (Resolve) > DID Service Endpoint +function processEndpointTypes(types?: string[]) { + const expectedTypes = ['endpoint', 'did-communication', 'DIDCommMessaging'] + const defaultTypes = ['endpoint', 'did-communication'] + + // Return default types if types "is NOT present [or] empty" + if (!types || types.length <= 0) { + return defaultTypes + } + + // Return default types if types "contain any other values" + for (const type of types) { + if (!expectedTypes.includes(type)) { + return defaultTypes + } + } + + // Return provided types + return types +} + +export function endpointsAttribFromServices(services: DidDocumentService[]): IndyEndpointAttrib { + const commTypes: CommEndpointType[] = ['endpoint', 'did-communication', 'DIDCommMessaging'] + const commServices = services.filter((item) => commTypes.includes(item.type as CommEndpointType)) + + // Check that all services use the same endpoint, as only one is accepted + if (!commServices.every((item) => item.serviceEndpoint === services[0].serviceEndpoint)) { + throw new AriesFrameworkError('serviceEndpoint for all services must match') + } + + const types: CommEndpointType[] = [] + const routingKeys = new Set() + + for (const commService of commServices) { + const commServiceType = commService.type as CommEndpointType + if (types.includes(commServiceType)) { + throw new AriesFrameworkError('Only a single communication service per type is supported') + } + + types.push(commServiceType) + + if ( + (commService instanceof DidCommV1Service || commService instanceof DidCommV2Service) && + commService.routingKeys + ) { + commService.routingKeys.forEach((item) => routingKeys.add(item)) + } + } + + return { endpoint: services[0].serviceEndpoint, types, routingKeys: Array.from(routingKeys) } +} + +export function addServicesFromEndpointsAttrib( + builder: DidDocumentBuilder, + did: string, + endpoints: IndyEndpointAttrib, + keyAgreementId: string +) { + const { endpoint, routingKeys, types, ...otherEndpoints } = endpoints + + if (endpoint) { + const processedTypes = processEndpointTypes(types) + + // If 'endpoint' included in types, add id to the services array + if (processedTypes.includes('endpoint')) { + builder.addService( + new DidDocumentService({ + id: `${did}#endpoint`, + serviceEndpoint: endpoint, + type: 'endpoint', + }) + ) + } + + // If 'did-communication' included in types, add DIDComm v1 entry + if (processedTypes.includes('did-communication')) { + builder.addService( + new DidCommV1Service({ + id: `${did}#did-communication`, + serviceEndpoint: endpoint, + priority: 0, + routingKeys: routingKeys ?? [], + recipientKeys: [keyAgreementId], + accept: ['didcomm/aip2;env=rfc19'], + }) + ) + + // If 'DIDCommMessaging' included in types, add DIDComm v2 entry + // TODO: should it be DIDComm or DIDCommMessaging? (see https://github.com/sovrin-foundation/sovrin/issues/343) + if (processedTypes.includes('DIDCommMessaging')) { + builder + .addService( + new DidCommV2Service({ + id: `${did}#didcomm-1`, + serviceEndpoint: endpoint, + routingKeys: routingKeys ?? [], + accept: ['didcomm/v2'], + }) + ) + .addContext('https://didcomm.org/messaging/contexts/v2') + } + } + } + + // Add other endpoint types + for (const [type, endpoint] of Object.entries(otherEndpoints)) { + builder.addService( + new DidDocumentService({ + id: `${did}#${type}`, + serviceEndpoint: endpoint as string, + type, + }) + ) + } +} diff --git a/packages/indy-vdr/src/dids/index.ts b/packages/indy-vdr/src/dids/index.ts new file mode 100644 index 0000000000..24a7a4ae9f --- /dev/null +++ b/packages/indy-vdr/src/dids/index.ts @@ -0,0 +1,3 @@ +export { IndyVdrIndyDidRegistrar, IndyVdrDidCreateResult } from './IndyVdrIndyDidRegistrar' +export { IndyVdrIndyDidResolver } from './IndyVdrIndyDidResolver' +export { IndyVdrSovDidResolver } from './IndyVdrSovDidResolver' diff --git a/packages/indy-vdr/src/error/IndyVdrError.ts b/packages/indy-vdr/src/error/IndyVdrError.ts new file mode 100644 index 0000000000..501f428640 --- /dev/null +++ b/packages/indy-vdr/src/error/IndyVdrError.ts @@ -0,0 +1,7 @@ +import { AriesFrameworkError } from '@aries-framework/core' + +export class IndyVdrError extends AriesFrameworkError { + public constructor(message: string, { cause }: { cause?: Error } = {}) { + super(message, { cause }) + } +} diff --git a/packages/indy-vdr/src/error/IndyVdrNotConfiguredError.ts b/packages/indy-vdr/src/error/IndyVdrNotConfiguredError.ts new file mode 100644 index 0000000000..75cf40c9f6 --- /dev/null +++ b/packages/indy-vdr/src/error/IndyVdrNotConfiguredError.ts @@ -0,0 +1,7 @@ +import { IndyVdrError } from './IndyVdrError' + +export class IndyVdrNotConfiguredError extends IndyVdrError { + public constructor(message: string, { cause }: { cause?: Error } = {}) { + super(message, { cause }) + } +} diff --git a/packages/core/src/modules/ledger/error/LedgerNotConfiguredError.ts b/packages/indy-vdr/src/error/IndyVdrNotFound.ts similarity index 51% rename from packages/core/src/modules/ledger/error/LedgerNotConfiguredError.ts rename to packages/indy-vdr/src/error/IndyVdrNotFound.ts index 0cee3914dc..00b1b94c47 100644 --- a/packages/core/src/modules/ledger/error/LedgerNotConfiguredError.ts +++ b/packages/indy-vdr/src/error/IndyVdrNotFound.ts @@ -1,6 +1,6 @@ -import { LedgerError } from './LedgerError' +import { IndyVdrError } from './IndyVdrError' -export class LedgerNotConfiguredError extends LedgerError { +export class IndyVdrNotFoundError extends IndyVdrError { public constructor(message: string, { cause }: { cause?: Error } = {}) { super(message, { cause }) } diff --git a/packages/indy-vdr/src/error/index.ts b/packages/indy-vdr/src/error/index.ts new file mode 100644 index 0000000000..f062bfbed0 --- /dev/null +++ b/packages/indy-vdr/src/error/index.ts @@ -0,0 +1,3 @@ +export * from './IndyVdrError' +export * from './IndyVdrNotFound' +export * from './IndyVdrNotConfiguredError' diff --git a/packages/indy-vdr/src/index.ts b/packages/indy-vdr/src/index.ts new file mode 100644 index 0000000000..19fdf8dad2 --- /dev/null +++ b/packages/indy-vdr/src/index.ts @@ -0,0 +1,5 @@ +export { IndyVdrIndyDidRegistrar, IndyVdrIndyDidResolver, IndyVdrSovDidResolver, IndyVdrDidCreateResult } from './dids' +export { IndyVdrPoolConfig } from './pool' +export * from './IndyVdrModule' +export * from './IndyVdrModuleConfig' +export * from './anoncreds' diff --git a/packages/indy-vdr/src/pool/IndyVdrPool.ts b/packages/indy-vdr/src/pool/IndyVdrPool.ts new file mode 100644 index 0000000000..db3959f070 --- /dev/null +++ b/packages/indy-vdr/src/pool/IndyVdrPool.ts @@ -0,0 +1,200 @@ +import type { AgentContext, Key } from '@aries-framework/core' +import type { IndyVdrRequest, RequestResponseType, IndyVdrPool as indyVdrPool } from '@hyperledger/indy-vdr-shared' + +import { parseIndyDid } from '@aries-framework/anoncreds' +import { TypedArrayEncoder } from '@aries-framework/core' +import { + GetTransactionAuthorAgreementRequest, + GetAcceptanceMechanismsRequest, + PoolCreate, + indyVdr, +} from '@hyperledger/indy-vdr-shared' + +import { IndyVdrError } from '../error' + +export interface TransactionAuthorAgreement { + version?: `${number}.${number}` | `${number}` + acceptanceMechanism: string +} + +export interface AuthorAgreement { + digest: string + version: string + text: string + ratification_ts: number + acceptanceMechanisms: AcceptanceMechanisms +} + +export interface AcceptanceMechanisms { + aml: Record + amlContext: string + version: string +} + +export interface IndyVdrPoolConfig { + genesisTransactions: string + isProduction: boolean + indyNamespace: string + transactionAuthorAgreement?: TransactionAuthorAgreement + connectOnStartup?: boolean +} + +export class IndyVdrPool { + private _pool?: indyVdrPool + private poolConfig: IndyVdrPoolConfig + public authorAgreement?: AuthorAgreement | null + + public constructor(poolConfig: IndyVdrPoolConfig) { + this.poolConfig = poolConfig + } + + public get indyNamespace(): string { + return this.poolConfig.indyNamespace + } + + public get config() { + return this.poolConfig + } + + public connect() { + if (this._pool) { + throw new IndyVdrError('Cannot connect to pool, already connected.') + } + + this._pool = new PoolCreate({ + parameters: { + transactions: this.config.genesisTransactions, + }, + }) + } + + private get pool(): indyVdrPool { + if (!this._pool) this.connect() + if (!this._pool) throw new IndyVdrError('Pool is not connected.') + + return this._pool + } + + public close() { + if (!this._pool) { + throw new IndyVdrError("Can't close pool. Pool is not connected") + } + + // FIXME: this method doesn't work?? + // this.pool.close() + } + + public async prepareWriteRequest( + agentContext: AgentContext, + request: Request, + signingKey: Key, + endorserDid?: string + ) { + await this.appendTaa(request) + + if (endorserDid) { + request.setEndorser({ endorser: parseIndyDid(endorserDid).namespaceIdentifier }) + } + + const signature = await agentContext.wallet.sign({ + data: TypedArrayEncoder.fromString(request.signatureInput), + key: signingKey, + }) + + request.setSignature({ + signature, + }) + + return request + } + + /** + * This method submits a request to the ledger. + * It does only submit the request. It does not modify it in any way. + * To create the request, use the `prepareWriteRequest` method. + * @param writeRequest + */ + + public async submitRequest( + writeRequest: Request + ): Promise> { + return await this.pool.submitRequest(writeRequest) + } + + private async appendTaa(request: IndyVdrRequest) { + const authorAgreement = await this.getTransactionAuthorAgreement() + const poolTaa = this.config.transactionAuthorAgreement + + // If ledger does not have TAA, we can just send request + if (authorAgreement == null) { + return request + } + + // Ledger has taa but user has not specified which one to use + if (!poolTaa) { + throw new IndyVdrError( + `Please, specify a transaction author agreement with version and acceptance mechanism. ${JSON.stringify( + authorAgreement + )}` + ) + } + + // Throw an error if the pool doesn't have the specified version and acceptance mechanism + if ( + authorAgreement.version !== poolTaa.version || + !authorAgreement.acceptanceMechanisms.aml[poolTaa.acceptanceMechanism] + ) { + // Throw an error with a helpful message + const errMessage = `Unable to satisfy matching TAA with mechanism ${JSON.stringify( + poolTaa.acceptanceMechanism + )} and version ${poolTaa.version} in pool.\n Found ${JSON.stringify( + authorAgreement.acceptanceMechanisms.aml + )} and version ${authorAgreement.version} in pool.` + throw new IndyVdrError(errMessage) + } + + const acceptance = indyVdr.prepareTxnAuthorAgreementAcceptance({ + text: authorAgreement.text, + version: authorAgreement.version, + taaDigest: authorAgreement.digest, + time: Math.floor(new Date().getTime() / 1000), + acceptanceMechanismType: poolTaa.acceptanceMechanism, + }) + + request.setTransactionAuthorAgreementAcceptance({ + acceptance: JSON.parse(acceptance), + }) + } + + private async getTransactionAuthorAgreement(): Promise { + // TODO Replace this condition with memoization + if (this.authorAgreement !== undefined) { + return this.authorAgreement + } + + const taaRequest = new GetTransactionAuthorAgreementRequest({}) + const taaResponse = await this.submitRequest(taaRequest) + + const acceptanceMechanismRequest = new GetAcceptanceMechanismsRequest({}) + const acceptanceMechanismResponse = await this.submitRequest(acceptanceMechanismRequest) + + const taaData = taaResponse.result.data + + // TAA can be null + if (taaData == null) { + this.authorAgreement = null + return null + } + + // If TAA is not null, we can be sure AcceptanceMechanisms is also not null + const authorAgreement = taaData as Omit + + const acceptanceMechanisms = acceptanceMechanismResponse.result.data as AcceptanceMechanisms + this.authorAgreement = { + ...authorAgreement, + acceptanceMechanisms, + } + + return this.authorAgreement + } +} diff --git a/packages/indy-vdr/src/pool/IndyVdrPoolService.ts b/packages/indy-vdr/src/pool/IndyVdrPoolService.ts new file mode 100644 index 0000000000..aa0e15b518 --- /dev/null +++ b/packages/indy-vdr/src/pool/IndyVdrPoolService.ts @@ -0,0 +1,206 @@ +import type { AgentContext } from '@aries-framework/core' +import type { GetNymResponse } from '@hyperledger/indy-vdr-shared' + +import { didIndyRegex } from '@aries-framework/anoncreds' +import { Logger, InjectionSymbols, injectable, inject, CacheModuleConfig } from '@aries-framework/core' +import { GetNymRequest } from '@hyperledger/indy-vdr-shared' + +import { IndyVdrModuleConfig } from '../IndyVdrModuleConfig' +import { IndyVdrError, IndyVdrNotFoundError, IndyVdrNotConfiguredError } from '../error' +import { isSelfCertifiedDid } from '../utils/did' +import { allSettled, onlyFulfilled, onlyRejected } from '../utils/promises' + +import { IndyVdrPool } from './IndyVdrPool' + +export interface CachedDidResponse { + nymResponse: { + did: string + verkey: string + } + indyNamespace: string +} +@injectable() +export class IndyVdrPoolService { + public pools: IndyVdrPool[] = [] + private logger: Logger + private indyVdrModuleConfig: IndyVdrModuleConfig + + public constructor(@inject(InjectionSymbols.Logger) logger: Logger, indyVdrModuleConfig: IndyVdrModuleConfig) { + this.logger = logger + this.indyVdrModuleConfig = indyVdrModuleConfig + + this.pools = this.indyVdrModuleConfig.networks.map((poolConfig) => new IndyVdrPool(poolConfig)) + } + + /** + * Get the most appropriate pool for the given did. + * If the did is a qualified indy did, the pool will be determined based on the namespace. + * If it is a legacy unqualified indy did, the pool will be determined based on the algorithm as described in this document: + * https://docs.google.com/document/d/109C_eMsuZnTnYe2OAd02jAts1vC4axwEKIq7_4dnNVA/edit + * + * This method will optionally return a nym response when the did has been resolved to determine the ledger + * either now or in the past. The nymResponse can be used to prevent multiple ledger quries fetching the same + * did + */ + public async getPoolForDid( + agentContext: AgentContext, + did: string + ): Promise<{ pool: IndyVdrPool; nymResponse?: CachedDidResponse['nymResponse'] }> { + // Check if the did starts with did:indy + const match = did.match(didIndyRegex) + + if (match) { + const [, namespace] = match + + const pool = this.getPoolForNamespace(namespace) + + if (pool) return { pool } + + throw new IndyVdrError(`Pool for indy namespace '${namespace}' not found`) + } else { + return await this.getPoolForLegacyDid(agentContext, did) + } + } + + private async getPoolForLegacyDid( + agentContext: AgentContext, + did: string + ): Promise<{ pool: IndyVdrPool; nymResponse?: CachedDidResponse['nymResponse'] }> { + const pools = this.pools + + if (pools.length === 0) { + throw new IndyVdrNotConfiguredError( + 'No indy ledgers configured. Provide at least one pool configuration in IndyVdrModuleConfigOptions.networks' + ) + } + + const cache = agentContext.dependencyManager.resolve(CacheModuleConfig).cache + const cacheKey = `IndyVdrPoolService:${did}` + + const cachedNymResponse = await cache.get(agentContext, cacheKey) + const pool = this.pools.find((pool) => pool.indyNamespace === cachedNymResponse?.indyNamespace) + + // If we have the nym response with associated pool in the cache, we'll use that + if (cachedNymResponse && pool) { + this.logger.trace(`Found ledger id '${pool.indyNamespace}' for did '${did}' in cache`) + return { pool, nymResponse: cachedNymResponse.nymResponse } + } + + const { successful, rejected } = await this.getSettledDidResponsesFromPools(did, pools) + + if (successful.length === 0) { + const allNotFound = rejected.every((e) => e.reason instanceof IndyVdrNotFoundError) + const rejectedOtherThanNotFound = rejected.filter((e) => !(e.reason instanceof IndyVdrNotFoundError)) + + // All ledgers returned response that the did was not found + if (allNotFound) { + throw new IndyVdrNotFoundError(`Did '${did}' not found on any of the ledgers (total ${this.pools.length}).`) + } + + // one or more of the ledgers returned an unknown error + throw new IndyVdrError( + `Unknown error retrieving did '${did}' from '${rejectedOtherThanNotFound.length}' of '${pools.length}' ledgers. ${rejectedOtherThanNotFound[0].reason}`, + { cause: rejectedOtherThanNotFound[0].reason } + ) + } + + // If there are self certified DIDs we always prefer it over non self certified DIDs + // We take the first self certifying DID as we take the order in the + // IndyVdrModuleConfigOptions.networks config as the order of preference of ledgers + let value = successful.find((response) => + isSelfCertifiedDid(response.value.did.nymResponse.did, response.value.did.nymResponse.verkey) + )?.value + + if (!value) { + // Split between production and nonProduction ledgers. If there is at least one + // successful response from a production ledger, only keep production ledgers + // otherwise we only keep the non production ledgers. + const production = successful.filter((s) => s.value.pool.config.isProduction) + const nonProduction = successful.filter((s) => !s.value.pool.config.isProduction) + const productionOrNonProduction = production.length >= 1 ? production : nonProduction + + // We take the first value as we take the order in the IndyVdrModuleConfigOptions.networks + // config as the order of preference of ledgers + value = productionOrNonProduction[0].value + } + + await cache.set(agentContext, cacheKey, { + nymResponse: { + did: value.did.nymResponse.did, + verkey: value.did.nymResponse.verkey, + }, + indyNamespace: value.did.indyNamespace, + }) + return { pool: value.pool, nymResponse: value.did.nymResponse } + } + + private async getSettledDidResponsesFromPools(did: string, pools: IndyVdrPool[]) { + this.logger.trace(`Retrieving did '${did}' from ${pools.length} ledgers`) + const didResponses = await allSettled(pools.map((pool) => this.getDidFromPool(did, pool))) + + const successful = onlyFulfilled(didResponses) + this.logger.trace(`Retrieved ${successful.length} responses from ledgers for did '${did}'`) + + const rejected = onlyRejected(didResponses) + + return { + rejected, + successful, + } + } + + /** + * Get the most appropriate pool for the given indyNamespace + */ + public getPoolForNamespace(indyNamespace: string) { + if (this.pools.length === 0) { + throw new IndyVdrNotConfiguredError( + 'No indy ledgers configured. Provide at least one pool configuration in IndyVdrModuleConfigOptions.networks' + ) + } + + const pool = this.pools.find((pool) => pool.indyNamespace === indyNamespace) + + if (!pool) { + throw new IndyVdrError(`No ledgers found for indy namespace '${indyNamespace}'.`) + } + + return pool + } + + private async getDidFromPool(did: string, pool: IndyVdrPool): Promise { + try { + this.logger.trace(`Get public did '${did}' from ledger '${pool.indyNamespace}'`) + const request = new GetNymRequest({ dest: did }) + + this.logger.trace(`Submitting get did request for did '${did}' to ledger '${pool.indyNamespace}'`) + const response = await pool.submitRequest(request) + + if (!response.result.data) { + throw new IndyVdrNotFoundError(`Did ${did} not found on indy pool with namespace ${pool.indyNamespace}`) + } + + const result = JSON.parse(response.result.data) + + this.logger.trace(`Retrieved did '${did}' from ledger '${pool.indyNamespace}'`, result) + + return { + did: { nymResponse: { did: result.dest, verkey: result.verkey }, indyNamespace: pool.indyNamespace }, + pool, + response, + } + } catch (error) { + this.logger.trace(`Error retrieving did '${did}' from ledger '${pool.indyNamespace}'`, { + error, + did, + }) + throw error + } + } +} + +export interface PublicDidRequest { + did: CachedDidResponse + pool: IndyVdrPool + response: GetNymResponse +} diff --git a/packages/indy-vdr/src/pool/index.ts b/packages/indy-vdr/src/pool/index.ts new file mode 100644 index 0000000000..ec4bc06677 --- /dev/null +++ b/packages/indy-vdr/src/pool/index.ts @@ -0,0 +1,2 @@ +export * from './IndyVdrPool' +export * from './IndyVdrPoolService' diff --git a/packages/indy-vdr/src/utils/did.ts b/packages/indy-vdr/src/utils/did.ts new file mode 100644 index 0000000000..f3f346f070 --- /dev/null +++ b/packages/indy-vdr/src/utils/did.ts @@ -0,0 +1,60 @@ +/** + * Based on DidUtils implementation in Aries Framework .NET + * @see: https://github.com/hyperledger/aries-framework-dotnet/blob/f90eaf9db8548f6fc831abea917e906201755763/src/Hyperledger.Aries/Utils/DidUtils.cs + * + * Some context about full verkeys versus abbreviated verkeys: + * A standard verkey is 32 bytes, and by default in Indy the DID is chosen as the first 16 bytes of that key, before base58 encoding. + * An abbreviated verkey replaces the first 16 bytes of the verkey with ~ when it matches the DID. + * + * When a full verkey is used to register on the ledger, this is stored as a full verkey on the ledger and also returned from the ledger as a full verkey. + * The same applies to an abbreviated verkey. If an abbreviated verkey is used to register on the ledger, this is stored as an abbreviated verkey on the ledger and also returned from the ledger as an abbreviated verkey. + * + * For this reason we need some methods to check whether verkeys are full or abbreviated, so we can align this with `indy.abbreviateVerkey` + * + * Aries Framework .NET also abbreviates verkey before sending to ledger: + * https://github.com/hyperledger/aries-framework-dotnet/blob/f90eaf9db8548f6fc831abea917e906201755763/src/Hyperledger.Aries/Ledger/DefaultLedgerService.cs#L139-L147 + */ + +import { TypedArrayEncoder } from '@aries-framework/core' + +export const ABBREVIATED_VERKEY_REGEX = /^~[1-9A-HJ-NP-Za-km-z]{21,22}$/ + +/** + * Check whether the did is a self certifying did. If the verkey is abbreviated this method + * will always return true. Make sure that the verkey you pass in this method belongs to the + * did passed in + * + * @return Boolean indicating whether the did is self certifying + */ +export function isSelfCertifiedDid(did: string, verkey: string): boolean { + // If the verkey is Abbreviated, it means the full verkey + // is the did + the verkey + if (isAbbreviatedVerkey(verkey)) { + return true + } + + const didFromVerkey = indyDidFromPublicKeyBase58(verkey) + + if (didFromVerkey === did) { + return true + } + + return false +} + +export function indyDidFromPublicKeyBase58(publicKeyBase58: string): string { + const buffer = TypedArrayEncoder.fromBase58(publicKeyBase58) + + const did = TypedArrayEncoder.toBase58(buffer.slice(0, 16)) + + return did +} + +/** + * Check a base58 encoded string against a regex expression to determine if it is a valid abbreviated verkey + * @param verkey Base58 encoded string representation of an abbreviated verkey + * @returns Boolean indicating if the string is a valid abbreviated verkey + */ +export function isAbbreviatedVerkey(verkey: string): boolean { + return ABBREVIATED_VERKEY_REGEX.test(verkey) +} diff --git a/packages/indy-vdr/src/utils/promises.ts b/packages/indy-vdr/src/utils/promises.ts new file mode 100644 index 0000000000..0e843d73b5 --- /dev/null +++ b/packages/indy-vdr/src/utils/promises.ts @@ -0,0 +1,44 @@ +// This file polyfills the allSettled method introduced in ESNext + +export type AllSettledFulfilled = { + status: 'fulfilled' + value: T +} + +export type AllSettledRejected = { + status: 'rejected' + // eslint-disable-next-line @typescript-eslint/no-explicit-any + reason: any +} + +export function allSettled(promises: Promise[]) { + return Promise.all( + promises.map((p) => + p + .then( + (value) => + ({ + status: 'fulfilled', + value, + } as AllSettledFulfilled) + ) + .catch( + (reason) => + ({ + status: 'rejected', + reason, + } as AllSettledRejected) + ) + ) + ) +} + +export function onlyFulfilled(entries: Array | AllSettledRejected>) { + // We filter for only the rejected values, so we can safely cast the type + return entries.filter((e) => e.status === 'fulfilled') as AllSettledFulfilled[] +} + +export function onlyRejected(entries: Array | AllSettledRejected>) { + // We filter for only the rejected values, so we can safely cast the type + return entries.filter((e) => e.status === 'rejected') as AllSettledRejected[] +} diff --git a/packages/indy-vdr/tests/__fixtures__/anoncreds.ts b/packages/indy-vdr/tests/__fixtures__/anoncreds.ts new file mode 100644 index 0000000000..fea36d5fcb --- /dev/null +++ b/packages/indy-vdr/tests/__fixtures__/anoncreds.ts @@ -0,0 +1,30 @@ +export const credentialDefinitionValue = { + primary: { + n: '96517142458750088826087901549537285521906361834839650465292394026155791790248920518228426560592477800345470631128393537910767968076647428853737338120375137978526133371095345886547568849980095910835456337942570110635942227498396677781945046904040000347997661394155645138402989185582727368743644878567330299129483548946710969360956979880962101169330048328620192831242584775824654760726417810662811409929761424969870024291961980782988854217354212087291593903213167261779548063894662259300608395552269380441482047725811646638173390809967510159302372018819245039226007682154490256871635806558216146474297742733244470144481', + s: '20992997088800769394205042281221010730843336204635587269131066142238627416871294692123680065003125450990475247419429111144686875080339959479648984195457400282722471552678361441816569115316390063503704185107464429408708889920969284364549487320740759452356010336698287092961864738455949515401889999320804333605635972368885179914619910494573144273759358510644118555354521660927445864167887629319425342133470781407706668100509422240127902573158722086763638357241708157836231326104213948080124231104027985997092193458353052131052627451830345602820935886233072722689872803371231173593216542422645374438328309647440653637339', + r: { + master_secret: + '96243300745227716230048295249700256382424379142767068560156597061550615821183969840133023439359733351013932957841392861447122785423145599004240865527901625751619237368187131360686977600247815596986496835118582544022443932674638843143227258367859921648385998241629365673854479167826898057354386557912400420925145402535066400276579674049751639901555837852972622061540154688641944145082381483273814616102862399655638465723909813901943343059991047747289931252070264205125933226649905593045675877143065756794349492159868513288280364195700788501708587588090219665708038121636837649207584981238653023213330207384929738192210', + age: '73301750658973501389860306433954162777688414647250690792688553201037736559940890441467927863421690990807820789906540409252803697381653459639864945429958798104818241892796218340966964349674689564019059435289373607451125919476002261041343187491848656595845611576458601110066647002078334660251906541846222115184239401618625285703919125402959929850028352261117167621349930047514115676870868726855651130262227714591240534532398809967792128535084773798290351459391475237061458901325844643172504167457543287673202618731404966555015061917662865397763636445953946274068384614117513804834235388565249331682010365807270858083546', + }, + rctxt: + '37788128721284563440858950515231840450431543928224096081933216180465915572829884228780081835462293611329848268384962871736884632087015070623933628853658097637604059748079512999518737243304794110313829761155878287344472916564970806851294430356498883927870926898737394894892797927804721407643833828162246495645836390303263072281761384240973982733122383052566872688887552226083782030670443318152427129452272570595367287061688769394567289624972332234661767648489253220495098949161964171486245324730862072203259801377135500275012560207100571502032523912388082460843991502336467718632746396226650194750972544436894286230063', + z: '43785356695890052462955676926428400928903479009358861113206349419200366390858322895540291303484939601128045362682307382393826375825484851021601464391509750565285197155653613669680662395620338416776539485377195826876505126073018100680273457526216247879013350460071029101583221000647494610122617904515744711339846577920055655093367012508192004131719432915903924789974568341538556528133188398290594619318653419602058489178526243446782729272985727332736198326183868783570550373552407121582843992983431205917273352230155794805507408743590383242904107596623095433284330566906935063373759426916339149701872288610119965287995', + }, + revocation: { + g: '1 0A84C28144BC8B677839038FFFA824AB5ADE517F8DD4A89F092FAF9A3560C62D 1 00FD708E112EEA5D89AF9D0559795E6DBCF56D3B8CDF79EFF34A72EB741F896F 2 095E45DDF417D05FB10933FFC63D474548B7FFFF7888802F07FFFFFF7D07A8A8', + g_dash: + '1 201F3E23CC7E9284F3EFCF9500F1E2537C398EAB2E94D2EB801AECC7FBFBDC01 1 08132C7723CF9861D4CC24B56555EF1CBD9AE746C97B3ADFA36C669F2DCE09B6 1 1B2397FB2A1ADE704E2A1E4C242612F4677F9F1BD09E6B14C2E77E25EDA4C62E 1 00CDC2CF5F278D699D52223577AB032C150A3CB4C8E8AB07AB9D592772910E95 2 095E45DDF417D05FB10933FFC63D474548B7FFFF7888802F07FFFFFF7D07A8A8 1 0000000000000000000000000000000000000000000000000000000000000000', + h: '1 072E0A505004F2F32B4210E72FA18A2ADF17F31479BD2059B7A8C0BA58F2ACB3 1 05C70F039E60317003C41C319753ECACC629791FDB06D6ADC5B06DD94501B973 2 095E45DDF417D05FB10933FFC63D474548B7FFFF7888802F07FFFFFF7D07A8A8', + h0: '1 03CBE26D18118E9770D4A0B3E8607B3B3A8D3D3CA81FF8D41862430CC583156E 1 004A2A57E0A826AEFF007EDDAF89B02F054050843689167B10127FE9EDEEEDA9 2 095E45DDF417D05FB10933FFC63D474548B7FFFF7888802F07FFFFFF7D07A8A8', + h1: '1 10C9F9DE537994E4FEF2625AFA78342C8A096238A875F6899DD500230E6022E5 1 0C0A88F53D020557377B4ED9C3826E9B8F918DD03E23B0F8ECD922F8333359D3 2 095E45DDF417D05FB10933FFC63D474548B7FFFF7888802F07FFFFFF7D07A8A8', + h2: '1 017F748AEEC1DDE4E4C3FBAE771C041F0A6FAEAF34FD02AF773AC4B75025147B 1 1298DBD9A4BEE6AD54E060A57BCE932735B7738C30A9ADAEFE2F38E1858A0183 2 095E45DDF417D05FB10933FFC63D474548B7FFFF7888802F07FFFFFF7D07A8A8', + htilde: + '1 0C471F0451D6AC352E28B6ECDE8D7233B75530AE59276DF0F4B9A8B0C5C7E5DB 1 24CE4461910AA5D60C09C24EE0FE51E1B1600D8BA6E483E9050EF897CA3E3C8A 2 095E45DDF417D05FB10933FFC63D474548B7FFFF7888802F07FFFFFF7D07A8A8', + h_cap: + '1 225B2106DEBD353AABDFC4C7F7E8660D308FB514EA9DAE0533DDEB65CF796159 1 1F6093622F439FC22C64F157F4F35F7C592EC0169C6F0026BC44CD3E375974A7 1 142126FAC3657AD846D394E1F72FD01ECC15E84416713CD133980E324B24F4BC 1 0357995DBDCD4385E59E607761AB30AE8D9DDE005A777EE846EF51AE2816CD33 2 095E45DDF417D05FB10933FFC63D474548B7FFFF7888802F07FFFFFF7D07A8A8 1 0000000000000000000000000000000000000000000000000000000000000000', + u: '1 00D8DDC2EB6536CA320EE035D099937E59B11678162C1BFEB30C58FCA9F84650 1 1557A5B05A1A30D63322E187D323C9CA431BC5E811E68D4703933D9DDA26D299 1 10E8AB93AA87839B757521742EBA23C3B257C91F61A93D37AEC4C0A011B5F073 1 1DA65E40406A7875DA8CFCE9FD7F283145C166382A937B72819BDC335FE9A734 2 095E45DDF417D05FB10933FFC63D474548B7FFFF7888802F07FFFFFF7D07A8A8 1 0000000000000000000000000000000000000000000000000000000000000000', + pk: '1 1A7EBBE3E7F8ED50959851364B20997944FA8AE5E3FC0A2BB531BAA17179D320 1 02C55FE6F64A2A4FF49B37C513C39E56ECD565CFAD6CA46DC6D8095179351863 2 095E45DDF417D05FB10933FFC63D474548B7FFFF7888802F07FFFFFF7D07A8A8', + y: '1 1BF97F07270EC21A89E43BCA645D86A755F846B547238F1DA379E088CDD9B40D 1 146BB00F56FFC0DEF6541CEB484C718559B398DB1547B52850E46B23144161F1 1 079A1BEF8DFFA4E6352F701D476664340E7FBE5D3F46B897412BD2B5F10E33D7 1 02FDC508AEF90FB11961AF332BE4037973C76B954FFA48848F7E0588E93FCA8C 2 095E45DDF417D05FB10933FFC63D474548B7FFFF7888802F07FFFFFF7D07A8A8 1 0000000000000000000000000000000000000000000000000000000000000000', + }, +} diff --git a/packages/indy-vdr/tests/helpers.ts b/packages/indy-vdr/tests/helpers.ts new file mode 100644 index 0000000000..eb9a94b8cf --- /dev/null +++ b/packages/indy-vdr/tests/helpers.ts @@ -0,0 +1,69 @@ +import type { IndyVdrDidCreateOptions } from '../src/dids/IndyVdrIndyDidRegistrar' +import type { Agent } from '@aries-framework/core' + +import { DidCommV1Service, DidCommV2Service, DidDocumentService, KeyType } from '@aries-framework/core' +import { indyVdr } from '@hyperledger/indy-vdr-nodejs' + +import { sleep } from '../../core/src/utils/sleep' +import { genesisTransactions } from '../../core/tests/helpers' +import { IndyVdrModuleConfig } from '../src/IndyVdrModuleConfig' + +export const indyVdrModuleConfig = new IndyVdrModuleConfig({ + indyVdr, + networks: [ + { + genesisTransactions, + indyNamespace: 'pool:localtest', + isProduction: false, + transactionAuthorAgreement: { version: '1', acceptanceMechanism: 'accept' }, + }, + ], +}) + +export async function createDidOnLedger(agent: Agent, endorserDid: string) { + const key = await agent.wallet.createKey({ keyType: KeyType.Ed25519 }) + + const createResult = await agent.dids.create({ + method: 'indy', + options: { + endorserMode: 'internal', + endorserDid: endorserDid, + alias: 'Alias', + role: 'TRUSTEE', + verkey: key.publicKeyBase58, + useEndpointAttrib: true, + services: [ + new DidDocumentService({ + id: `#endpoint`, + serviceEndpoint: 'http://localhost:3000', + type: 'endpoint', + }), + new DidCommV1Service({ + id: `#did-communication`, + priority: 0, + recipientKeys: [`#key-agreement-1`], + routingKeys: ['a-routing-key'], + serviceEndpoint: 'http://localhost:3000', + accept: ['didcomm/aip2;env=rfc19'], + }), + new DidCommV2Service({ + accept: ['didcomm/v2'], + id: `#didcomm-1`, + routingKeys: ['a-routing-key'], + serviceEndpoint: 'http://localhost:3000', + }), + ], + }, + }) + + if (!createResult.didState.did) { + throw new Error( + `Did was not created. ${createResult.didState.state === 'failed' ? createResult.didState.reason : 'Not finished'}` + ) + } + + // Wait some time pass to let ledger settle the object + await sleep(1000) + + return { did: createResult.didState.did, key } +} diff --git a/packages/indy-vdr/tests/indy-vdr-anoncreds-registry.e2e.test.ts b/packages/indy-vdr/tests/indy-vdr-anoncreds-registry.e2e.test.ts new file mode 100644 index 0000000000..f71251b948 --- /dev/null +++ b/packages/indy-vdr/tests/indy-vdr-anoncreds-registry.e2e.test.ts @@ -0,0 +1,367 @@ +import { Agent, DidsModule, Key, KeyType, TypedArrayEncoder } from '@aries-framework/core' +import { indyVdr } from '@hyperledger/indy-vdr-nodejs' +import { RevocationRegistryDefinitionRequest, RevocationRegistryEntryRequest } from '@hyperledger/indy-vdr-shared' + +import { + agentDependencies, + getAgentConfig, + importExistingIndyDidFromPrivateKey, + publicDidSeed, +} from '../../core/tests/helpers' +import { IndySdkModule } from '../../indy-sdk/src' +import { indySdk } from '../../indy-sdk/tests/setupIndySdkModule' +import { IndyVdrIndyDidResolver, IndyVdrModule, IndyVdrSovDidResolver } from '../src' +import { IndyVdrAnonCredsRegistry } from '../src/anoncreds/IndyVdrAnonCredsRegistry' +import { IndyVdrPoolService } from '../src/pool' + +import { credentialDefinitionValue } from './__fixtures__/anoncreds' +import { indyVdrModuleConfig } from './helpers' + +const agentConfig = getAgentConfig('IndyVdrAnonCredsRegistry') + +const indyVdrAnonCredsRegistry = new IndyVdrAnonCredsRegistry() + +const agent = new Agent({ + config: agentConfig, + dependencies: agentDependencies, + modules: { + indyVdr: new IndyVdrModule({ + indyVdr, + networks: indyVdrModuleConfig.networks, + }), + indySdk: new IndySdkModule({ + indySdk, + }), + dids: new DidsModule({ + resolvers: [new IndyVdrSovDidResolver(), new IndyVdrIndyDidResolver()], + }), + }, +}) + +const indyVdrPoolService = agent.dependencyManager.resolve(IndyVdrPoolService) +const pool = indyVdrPoolService.getPoolForNamespace('pool:localtest') + +describe('IndyVdrAnonCredsRegistry', () => { + beforeAll(async () => { + await agent.initialize() + + await importExistingIndyDidFromPrivateKey(agent, TypedArrayEncoder.fromString(publicDidSeed)) + }) + + afterAll(async () => { + for (const pool of indyVdrPoolService.pools) { + pool.close() + } + + await agent.shutdown() + await agent.wallet.delete() + }) + + // One test as the credential definition depends on the schema + test('register and resolve a schema and credential definition', async () => { + const dynamicVersion = `1.${Math.random() * 100}` + + const legacyIssuerId = 'TL1EaPFCZ8Si5aUrqScBDt' + const signingKey = Key.fromPublicKeyBase58('FMGcFuU3QwAQLywxvmEnSorQT3NwU9wgDMMTaDFtvswm', KeyType.Ed25519) + const didIndyIssuerId = 'did:indy:pool:localtest:TL1EaPFCZ8Si5aUrqScBDt' + + const legacySchemaId = `TL1EaPFCZ8Si5aUrqScBDt:2:test:${dynamicVersion}` + const didIndySchemaId = `did:indy:pool:localtest:TL1EaPFCZ8Si5aUrqScBDt/anoncreds/v0/SCHEMA/test/${dynamicVersion}` + + const schemaResult = await indyVdrAnonCredsRegistry.registerSchema(agent.context, { + options: {}, + schema: { + attrNames: ['age'], + issuerId: didIndyIssuerId, + name: 'test', + version: dynamicVersion, + }, + }) + + expect(schemaResult).toMatchObject({ + schemaState: { + state: 'finished', + schema: { + attrNames: ['age'], + issuerId: didIndyIssuerId, + name: 'test', + version: dynamicVersion, + }, + schemaId: didIndySchemaId, + }, + registrationMetadata: {}, + schemaMetadata: { + indyLedgerSeqNo: expect.any(Number), + }, + }) + + // Wait some time before resolving credential definition object + await new Promise((res) => setTimeout(res, 1000)) + + const legacySchema = await indyVdrAnonCredsRegistry.getSchema(agent.context, legacySchemaId) + expect(legacySchema).toMatchObject({ + schema: { + attrNames: ['age'], + name: 'test', + version: dynamicVersion, + issuerId: legacyIssuerId, + }, + schemaId: legacySchemaId, + resolutionMetadata: {}, + schemaMetadata: { + didIndyNamespace: 'pool:localtest', + indyLedgerSeqNo: expect.any(Number), + }, + }) + + // Resolve using did indy schema id + const didIndySchema = await indyVdrAnonCredsRegistry.getSchema(agent.context, didIndySchemaId) + expect(didIndySchema).toMatchObject({ + schema: { + attrNames: ['age'], + name: 'test', + version: dynamicVersion, + issuerId: didIndyIssuerId, + }, + schemaId: didIndySchemaId, + resolutionMetadata: {}, + schemaMetadata: { + didIndyNamespace: 'pool:localtest', + indyLedgerSeqNo: expect.any(Number), + }, + }) + + const legacyCredentialDefinitionId = `TL1EaPFCZ8Si5aUrqScBDt:3:CL:${schemaResult.schemaMetadata.indyLedgerSeqNo}:TAG` + const didIndyCredentialDefinitionId = `did:indy:pool:localtest:TL1EaPFCZ8Si5aUrqScBDt/anoncreds/v0/CLAIM_DEF/${schemaResult.schemaMetadata.indyLedgerSeqNo}/TAG` + const credentialDefinitionResult = await indyVdrAnonCredsRegistry.registerCredentialDefinition(agent.context, { + credentialDefinition: { + issuerId: didIndyIssuerId, + tag: 'TAG', + schemaId: didIndySchemaId, + type: 'CL', + value: credentialDefinitionValue, + }, + options: {}, + }) + + expect(credentialDefinitionResult).toMatchObject({ + credentialDefinitionMetadata: {}, + credentialDefinitionState: { + credentialDefinition: { + issuerId: didIndyIssuerId, + tag: 'TAG', + schemaId: didIndySchemaId, + type: 'CL', + value: credentialDefinitionValue, + }, + credentialDefinitionId: didIndyCredentialDefinitionId, + state: 'finished', + }, + registrationMetadata: {}, + }) + + // Wait some time before resolving credential definition object + await new Promise((res) => setTimeout(res, 1000)) + + const legacyCredentialDefinition = await indyVdrAnonCredsRegistry.getCredentialDefinition( + agent.context, + legacyCredentialDefinitionId + ) + + expect(legacyCredentialDefinition).toMatchObject({ + credentialDefinitionId: legacyCredentialDefinitionId, + credentialDefinition: { + issuerId: legacyIssuerId, + schemaId: legacySchemaId, + tag: 'TAG', + type: 'CL', + value: credentialDefinitionValue, + }, + credentialDefinitionMetadata: { + didIndyNamespace: 'pool:localtest', + }, + resolutionMetadata: {}, + }) + + // resolve using did indy credential definition id + const didIndyCredentialDefinition = await indyVdrAnonCredsRegistry.getCredentialDefinition( + agent.context, + didIndyCredentialDefinitionId + ) + + expect(didIndyCredentialDefinition).toMatchObject({ + credentialDefinitionId: didIndyCredentialDefinitionId, + credentialDefinition: { + issuerId: didIndyIssuerId, + schemaId: didIndySchemaId, + tag: 'TAG', + type: 'CL', + value: credentialDefinitionValue, + }, + credentialDefinitionMetadata: { + didIndyNamespace: 'pool:localtest', + }, + resolutionMetadata: {}, + }) + + // We don't support creating a revocation registry using AFJ yet, so we directly use indy-vdr to create the revocation registry + const legacyRevocationRegistryId = `TL1EaPFCZ8Si5aUrqScBDt:4:TL1EaPFCZ8Si5aUrqScBDt:3:CL:${schemaResult.schemaMetadata.indyLedgerSeqNo}:TAG:CL_ACCUM:tag` + const didIndyRevocationRegistryId = `did:indy:pool:localtest:TL1EaPFCZ8Si5aUrqScBDt/anoncreds/v0/REV_REG_DEF/${schemaResult.schemaMetadata.indyLedgerSeqNo}/TAG/tag` + const revocationRegistryRequest = new RevocationRegistryDefinitionRequest({ + submitterDid: 'TL1EaPFCZ8Si5aUrqScBDt', + revocationRegistryDefinitionV1: { + credDefId: legacyCredentialDefinitionId, + id: legacyRevocationRegistryId, + revocDefType: 'CL_ACCUM', + tag: 'tag', + value: { + issuanceType: 'ISSUANCE_BY_DEFAULT', + maxCredNum: 100, + publicKeys: { + accumKey: { + z: '1 1812B206EB395D3AEBD4BBF53EBB0FFC3371D8BD6175316AB32C1C5F65452051 1 22A079D49C5351EFDC1410C81A1F6D8B2E3B79CFF20A30690C118FE2050F72CB 1 0FFC28B923A4654E261DB4CB5B9BABEFCB4DB189B20F52412B0CC9CCCBB8A3B2 1 1EE967C43EF1A3F487061D21B07076A26C126AAF7712E7B5CF5A53688DDD5CC0 1 009ED4D65879CA81DA8227D34CEA3B759B4627E1E2FFB273E9645CD4F3B10F19 1 1CF070212E1E213AEB472F56EDFC9D48009796C77B2D8CC16F2836E37B8715C2 1 04954F0B7B468781BAAE3291DD0E6FFA7F1AF66CAA4094D37B24363CC34606FB 1 115367CB755E9DB18781B3825CB1AEE2C334558B2C038E13DF57BB57CE1CF847 1 110D37EC05862EE2757A7DF39E814876FC97376FF8105D2D29619CB575537BDE 1 13C559A9563FCE083B3B39AE7E8FCA4099BEF3A4C8C6672E543D521F9DA88F96 1 137D87CC22ACC1B6B8C20EABE59F6ED456A58FE4CBEEFDFC4FA9B87E3EF32D17 1 00A2A9711737AAF0404F35AE502887AC6172B2B57D236BD4A40B45F659BFC696', + }, + }, + tailsHash: 'HLKresYcDSZYSKogq8wive4zyXNY84669MygftLFBG1i', + tailsLocation: + '/var/folders/l3/xy8jzyvj4p5_d9g1123rt4bw0000gn/T/HLKresYcDSZYSKogq8wive4zyXNY84669MygftLFBG1i', + }, + ver: '1.0', + }, + }) + + // After this call, the revocation registry should now be resolvable + const writeRequest = await pool.prepareWriteRequest(agent.context, revocationRegistryRequest, signingKey) + await pool.submitRequest(writeRequest) + + // Also create a revocation registry entry + const revocationEntryRequest = new RevocationRegistryEntryRequest({ + revocationRegistryDefinitionId: legacyRevocationRegistryId, + revocationRegistryDefinitionType: 'CL_ACCUM', + revocationRegistryEntry: { + ver: '1.0', + value: { + accum: '1', + }, + }, + submitterDid: legacyIssuerId, + }) + + // After this call we can query the revocation registry entries (using timestamp now) + + const revocationEntryWriteRequest = await pool.prepareWriteRequest( + agent.context, + revocationEntryRequest, + signingKey + ) + const entryResponse = await pool.submitRequest(revocationEntryWriteRequest) + + const legacyRevocationRegistryDefinition = await indyVdrAnonCredsRegistry.getRevocationRegistryDefinition( + agent.context, + legacyRevocationRegistryId + ) + expect(legacyRevocationRegistryDefinition).toMatchObject({ + revocationRegistryDefinitionId: legacyRevocationRegistryId, + revocationRegistryDefinition: { + issuerId: legacyIssuerId, + revocDefType: 'CL_ACCUM', + value: { + maxCredNum: 100, + tailsHash: 'HLKresYcDSZYSKogq8wive4zyXNY84669MygftLFBG1i', + tailsLocation: + '/var/folders/l3/xy8jzyvj4p5_d9g1123rt4bw0000gn/T/HLKresYcDSZYSKogq8wive4zyXNY84669MygftLFBG1i', + publicKeys: { + accumKey: { + z: '1 1812B206EB395D3AEBD4BBF53EBB0FFC3371D8BD6175316AB32C1C5F65452051 1 22A079D49C5351EFDC1410C81A1F6D8B2E3B79CFF20A30690C118FE2050F72CB 1 0FFC28B923A4654E261DB4CB5B9BABEFCB4DB189B20F52412B0CC9CCCBB8A3B2 1 1EE967C43EF1A3F487061D21B07076A26C126AAF7712E7B5CF5A53688DDD5CC0 1 009ED4D65879CA81DA8227D34CEA3B759B4627E1E2FFB273E9645CD4F3B10F19 1 1CF070212E1E213AEB472F56EDFC9D48009796C77B2D8CC16F2836E37B8715C2 1 04954F0B7B468781BAAE3291DD0E6FFA7F1AF66CAA4094D37B24363CC34606FB 1 115367CB755E9DB18781B3825CB1AEE2C334558B2C038E13DF57BB57CE1CF847 1 110D37EC05862EE2757A7DF39E814876FC97376FF8105D2D29619CB575537BDE 1 13C559A9563FCE083B3B39AE7E8FCA4099BEF3A4C8C6672E543D521F9DA88F96 1 137D87CC22ACC1B6B8C20EABE59F6ED456A58FE4CBEEFDFC4FA9B87E3EF32D17 1 00A2A9711737AAF0404F35AE502887AC6172B2B57D236BD4A40B45F659BFC696', + }, + }, + }, + tag: 'tag', + credDefId: legacyCredentialDefinitionId, + }, + revocationRegistryDefinitionMetadata: { + issuanceType: 'ISSUANCE_BY_DEFAULT', + didIndyNamespace: 'pool:localtest', + }, + resolutionMetadata: {}, + }) + + const didIndyRevocationRegistryDefinition = await indyVdrAnonCredsRegistry.getRevocationRegistryDefinition( + agent.context, + didIndyRevocationRegistryId + ) + expect(didIndyRevocationRegistryDefinition).toMatchObject({ + revocationRegistryDefinitionId: didIndyRevocationRegistryId, + revocationRegistryDefinition: { + issuerId: didIndyIssuerId, + revocDefType: 'CL_ACCUM', + value: { + maxCredNum: 100, + tailsHash: 'HLKresYcDSZYSKogq8wive4zyXNY84669MygftLFBG1i', + tailsLocation: + '/var/folders/l3/xy8jzyvj4p5_d9g1123rt4bw0000gn/T/HLKresYcDSZYSKogq8wive4zyXNY84669MygftLFBG1i', + publicKeys: { + accumKey: { + z: '1 1812B206EB395D3AEBD4BBF53EBB0FFC3371D8BD6175316AB32C1C5F65452051 1 22A079D49C5351EFDC1410C81A1F6D8B2E3B79CFF20A30690C118FE2050F72CB 1 0FFC28B923A4654E261DB4CB5B9BABEFCB4DB189B20F52412B0CC9CCCBB8A3B2 1 1EE967C43EF1A3F487061D21B07076A26C126AAF7712E7B5CF5A53688DDD5CC0 1 009ED4D65879CA81DA8227D34CEA3B759B4627E1E2FFB273E9645CD4F3B10F19 1 1CF070212E1E213AEB472F56EDFC9D48009796C77B2D8CC16F2836E37B8715C2 1 04954F0B7B468781BAAE3291DD0E6FFA7F1AF66CAA4094D37B24363CC34606FB 1 115367CB755E9DB18781B3825CB1AEE2C334558B2C038E13DF57BB57CE1CF847 1 110D37EC05862EE2757A7DF39E814876FC97376FF8105D2D29619CB575537BDE 1 13C559A9563FCE083B3B39AE7E8FCA4099BEF3A4C8C6672E543D521F9DA88F96 1 137D87CC22ACC1B6B8C20EABE59F6ED456A58FE4CBEEFDFC4FA9B87E3EF32D17 1 00A2A9711737AAF0404F35AE502887AC6172B2B57D236BD4A40B45F659BFC696', + }, + }, + }, + tag: 'tag', + credDefId: didIndyCredentialDefinitionId, + }, + revocationRegistryDefinitionMetadata: { + issuanceType: 'ISSUANCE_BY_DEFAULT', + didIndyNamespace: 'pool:localtest', + }, + resolutionMetadata: {}, + }) + + const legacyRevocationStatusList = await indyVdrAnonCredsRegistry.getRevocationStatusList( + agent.context, + legacyRevocationRegistryId, + entryResponse.result.txnMetadata.txnTime + ) + + expect(legacyRevocationStatusList).toMatchObject({ + resolutionMetadata: {}, + revocationStatusList: { + issuerId: legacyIssuerId, + currentAccumulator: '1', + revRegDefId: legacyRevocationRegistryId, + revocationList: [ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + ], + timestamp: entryResponse.result.txnMetadata.txnTime, + }, + revocationStatusListMetadata: { + didIndyNamespace: 'pool:localtest', + }, + }) + + const didIndyRevocationStatusList = await indyVdrAnonCredsRegistry.getRevocationStatusList( + agent.context, + didIndyRevocationRegistryId, + entryResponse.result.txnMetadata.txnTime + ) + + expect(didIndyRevocationStatusList).toMatchObject({ + resolutionMetadata: {}, + revocationStatusList: { + issuerId: didIndyIssuerId, + currentAccumulator: '1', + revRegDefId: didIndyRevocationRegistryId, + revocationList: [ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + ], + timestamp: entryResponse.result.txnMetadata.txnTime, + }, + revocationStatusListMetadata: { + didIndyNamespace: 'pool:localtest', + }, + }) + }) +}) diff --git a/packages/indy-vdr/tests/indy-vdr-did-registrar.e2e.test.ts b/packages/indy-vdr/tests/indy-vdr-did-registrar.e2e.test.ts new file mode 100644 index 0000000000..c74c522057 --- /dev/null +++ b/packages/indy-vdr/tests/indy-vdr-did-registrar.e2e.test.ts @@ -0,0 +1,696 @@ +import type { IndyVdrDidCreateOptions, IndyVdrDidCreateResult } from '../src/dids/IndyVdrIndyDidRegistrar' + +import { didIndyRegex } from '@aries-framework/anoncreds' +import { + Key, + JsonTransformer, + KeyType, + TypedArrayEncoder, + DidCommV1Service, + DidCommV2Service, + DidDocumentService, + Agent, + DidsModule, +} from '@aries-framework/core' +import { indyVdr } from '@hyperledger/indy-vdr-nodejs' +import { convertPublicKeyToX25519, generateKeyPairFromSeed } from '@stablelib/ed25519' + +import { sleep } from '../../core/src/utils/sleep' +import { getAgentOptions, importExistingIndyDidFromPrivateKey } from '../../core/tests/helpers' +import { IndySdkModule } from '../../indy-sdk/src' +import { indySdk } from '../../indy-sdk/tests/setupIndySdkModule' +import { IndyVdrModule, IndyVdrSovDidResolver } from '../src' +import { IndyVdrIndyDidRegistrar } from '../src/dids/IndyVdrIndyDidRegistrar' +import { IndyVdrIndyDidResolver } from '../src/dids/IndyVdrIndyDidResolver' +import { indyDidFromNamespaceAndInitialKey } from '../src/dids/didIndyUtil' + +import { indyVdrModuleConfig } from './helpers' + +const endorser = new Agent( + getAgentOptions( + 'Indy VDR Indy DID Registrar', + {}, + { + indyVdr: new IndyVdrModule({ + networks: indyVdrModuleConfig.networks, + indyVdr, + }), + indySdk: new IndySdkModule({ + indySdk, + }), + dids: new DidsModule({ + registrars: [new IndyVdrIndyDidRegistrar()], + resolvers: [new IndyVdrIndyDidResolver(), new IndyVdrSovDidResolver()], + }), + } + ) +) +const agent = new Agent( + getAgentOptions( + 'Indy VDR Indy DID Registrar', + {}, + { + indyVdr: new IndyVdrModule({ + indyVdr, + networks: indyVdrModuleConfig.networks, + }), + indySdk: new IndySdkModule({ + indySdk, + }), + dids: new DidsModule({ + registrars: [new IndyVdrIndyDidRegistrar()], + resolvers: [new IndyVdrIndyDidResolver(), new IndyVdrSovDidResolver()], + }), + } + ) +) + +describe('Indy VDR Indy Did Registrar', () => { + let endorserDid: string + + beforeAll(async () => { + await endorser.initialize() + const unqualifiedSubmitterDid = await importExistingIndyDidFromPrivateKey( + endorser, + TypedArrayEncoder.fromString('00000000000000000000000Endorser9') + ) + endorserDid = `did:indy:pool:localtest:${unqualifiedSubmitterDid}` + + await agent.initialize() + }) + + afterAll(async () => { + await endorser.shutdown() + await endorser.wallet.delete() + await agent.shutdown() + await agent.wallet.delete() + }) + + test('can register a did:indy without services', async () => { + const didRegistrationResult = await endorser.dids.create({ + method: 'indy', + options: { + endorserDid, + endorserMode: 'internal', + }, + }) + + expect(JsonTransformer.toJSON(didRegistrationResult)).toMatchObject({ + didDocumentMetadata: {}, + didRegistrationMetadata: {}, + didState: { + state: 'finished', + did: expect.stringMatching(didIndyRegex), + didDocument: { + '@context': ['https://w3id.org/did/v1', 'https://w3id.org/security/suites/ed25519-2018/v1'], + id: expect.stringMatching(didIndyRegex), + alsoKnownAs: undefined, + controller: undefined, + verificationMethod: [ + { + type: 'Ed25519VerificationKey2018', + controller: expect.stringMatching(didIndyRegex), + id: expect.stringContaining('#verkey'), + publicKeyBase58: expect.any(String), + }, + ], + capabilityDelegation: undefined, + capabilityInvocation: undefined, + authentication: [expect.stringContaining('#verkey')], + service: undefined, + }, + }, + }) + + const did = didRegistrationResult.didState.did + if (!did) throw Error('did not defined') + + // Wait some time pass to let ledger settle the object + await sleep(1000) + + const didResolutionResult = await endorser.dids.resolve(did) + expect(JsonTransformer.toJSON(didResolutionResult)).toMatchObject({ + didDocument: { + '@context': ['https://w3id.org/did/v1', 'https://w3id.org/security/suites/ed25519-2018/v1'], + id: did, + alsoKnownAs: undefined, + controller: undefined, + verificationMethod: [ + { + type: 'Ed25519VerificationKey2018', + controller: did, + id: `${did}#verkey`, + publicKeyBase58: expect.any(String), + }, + ], + capabilityDelegation: undefined, + capabilityInvocation: undefined, + authentication: [`${did}#verkey`], + service: undefined, + }, + didDocumentMetadata: {}, + didResolutionMetadata: { + contentType: 'application/did+ld+json', + }, + }) + }) + + test('can register a did:indy without services with endorser', async () => { + const didCreateTobeEndorsedResult = (await agent.dids.create({ + method: 'indy', + options: { + endorserMode: 'external', + endorserDid, + }, + })) as IndyVdrDidCreateResult + + const didState = didCreateTobeEndorsedResult.didState + if (didState.state !== 'action' || didState.action !== 'endorseIndyTransaction') throw Error('unexpected did state') + + const signedNymRequest = await endorser.modules.indyVdr.endorseTransaction( + didState.nymRequest, + didState.endorserDid + ) + const didCreateSubmitResult = await agent.dids.create({ + did: didState.did, + options: { + endorserMode: 'external', + endorsedTransaction: { + nymRequest: signedNymRequest, + }, + }, + secret: didState.secret, + }) + + expect(JsonTransformer.toJSON(didCreateSubmitResult)).toMatchObject({ + didDocumentMetadata: {}, + didRegistrationMetadata: {}, + didState: { + state: 'finished', + did: expect.stringMatching(didIndyRegex), + didDocument: { + '@context': ['https://w3id.org/did/v1', 'https://w3id.org/security/suites/ed25519-2018/v1'], + id: expect.stringMatching(didIndyRegex), + alsoKnownAs: undefined, + controller: undefined, + verificationMethod: [ + { + type: 'Ed25519VerificationKey2018', + controller: expect.stringMatching(didIndyRegex), + id: expect.stringContaining('#verkey'), + publicKeyBase58: expect.any(String), + }, + ], + capabilityDelegation: undefined, + capabilityInvocation: undefined, + authentication: [expect.stringContaining('#verkey')], + service: undefined, + }, + }, + }) + + const did = didCreateSubmitResult.didState.did + if (!did) throw Error('did not defined') + + // Wait some time pass to let ledger settle the object + await sleep(1000) + + const didResolutionResult = await endorser.dids.resolve(did) + expect(JsonTransformer.toJSON(didResolutionResult)).toMatchObject({ + didDocument: { + '@context': ['https://w3id.org/did/v1', 'https://w3id.org/security/suites/ed25519-2018/v1'], + id: did, + alsoKnownAs: undefined, + controller: undefined, + verificationMethod: [ + { + type: 'Ed25519VerificationKey2018', + controller: did, + id: `${did}#verkey`, + publicKeyBase58: expect.any(String), + }, + ], + capabilityDelegation: undefined, + capabilityInvocation: undefined, + authentication: [`${did}#verkey`], + service: undefined, + }, + didDocumentMetadata: {}, + didResolutionMetadata: { + contentType: 'application/did+ld+json', + }, + }) + }) + + test('can register an endorsed did:indy without services - did and verkey specified', async () => { + // Generate a seed and the indy did. This allows us to create a new did every time + // but still check if the created output document is as expected. + const seed = Array(32 + 1) + .join((Math.random().toString(36) + '00000000000000000').slice(2, 18)) + .slice(0, 32) + + const keyPair = generateKeyPairFromSeed(TypedArrayEncoder.fromString(seed)) + const ed25519PublicKeyBase58 = TypedArrayEncoder.toBase58(keyPair.publicKey) + + const { did, verkey } = indyDidFromNamespaceAndInitialKey( + 'pool:localtest', + Key.fromPublicKey(keyPair.publicKey, KeyType.Ed25519) + ) + + const didCreateTobeEndorsedResult = (await agent.dids.create({ + did, + options: { + endorserDid, + endorserMode: 'external', + verkey, + }, + })) as IndyVdrDidCreateResult + + const didState = didCreateTobeEndorsedResult.didState + if (didState.state !== 'action' || didState.action !== 'endorseIndyTransaction') throw Error('unexpected did state') + + const signedNymRequest = await endorser.modules.indyVdr.endorseTransaction( + didState.nymRequest, + didState.endorserDid + ) + const didCreateSubmitResult = await agent.dids.create({ + did: didState.did, + options: { + endorserMode: 'external', + endorsedTransaction: { + nymRequest: signedNymRequest, + }, + }, + secret: didState.secret, + }) + + expect(JsonTransformer.toJSON(didCreateSubmitResult)).toMatchObject({ + didDocumentMetadata: {}, + didRegistrationMetadata: {}, + didState: { + state: 'finished', + did, + didDocument: { + '@context': ['https://w3id.org/did/v1', 'https://w3id.org/security/suites/ed25519-2018/v1'], + id: did, + alsoKnownAs: undefined, + controller: undefined, + verificationMethod: [ + { + type: 'Ed25519VerificationKey2018', + controller: did, + id: `${did}#verkey`, + publicKeyBase58: ed25519PublicKeyBase58, + }, + ], + capabilityDelegation: undefined, + capabilityInvocation: undefined, + authentication: [`${did}#verkey`], + service: undefined, + }, + }, + }) + + // Wait some time pass to let ledger settle the object + await sleep(1000) + + const didResult = await endorser.dids.resolve(did) + expect(JsonTransformer.toJSON(didResult)).toMatchObject({ + didDocument: { + '@context': ['https://w3id.org/did/v1', 'https://w3id.org/security/suites/ed25519-2018/v1'], + id: did, + alsoKnownAs: undefined, + controller: undefined, + verificationMethod: [ + { + type: 'Ed25519VerificationKey2018', + controller: did, + id: `${did}#verkey`, + publicKeyBase58: ed25519PublicKeyBase58, + }, + ], + capabilityDelegation: undefined, + capabilityInvocation: undefined, + authentication: [`${did}#verkey`], + service: undefined, + }, + didDocumentMetadata: {}, + didResolutionMetadata: { + contentType: 'application/did+ld+json', + }, + }) + }) + + test('can register a did:indy without services - did and verkey specified', async () => { + // Generate a seed and the indy did. This allows us to create a new did every time + // but still check if the created output document is as expected. + const seed = Array(32 + 1) + .join((Math.random().toString(36) + '00000000000000000').slice(2, 18)) + .slice(0, 32) + + const keyPair = generateKeyPairFromSeed(TypedArrayEncoder.fromString(seed)) + const ed25519PublicKeyBase58 = TypedArrayEncoder.toBase58(keyPair.publicKey) + + const { did, verkey } = indyDidFromNamespaceAndInitialKey( + 'pool:localtest', + Key.fromPublicKey(keyPair.publicKey, KeyType.Ed25519) + ) + const didRegistrationResult = await endorser.dids.create({ + did, + options: { + endorserDid, + endorserMode: 'internal', + verkey, + }, + }) + + expect(JsonTransformer.toJSON(didRegistrationResult)).toMatchObject({ + didDocumentMetadata: {}, + didRegistrationMetadata: {}, + didState: { + state: 'finished', + did, + didDocument: { + '@context': ['https://w3id.org/did/v1', 'https://w3id.org/security/suites/ed25519-2018/v1'], + id: did, + alsoKnownAs: undefined, + controller: undefined, + verificationMethod: [ + { + type: 'Ed25519VerificationKey2018', + controller: did, + id: `${did}#verkey`, + publicKeyBase58: ed25519PublicKeyBase58, + }, + ], + capabilityDelegation: undefined, + capabilityInvocation: undefined, + authentication: [`${did}#verkey`], + service: undefined, + }, + }, + }) + + // Wait some time pass to let ledger settle the object + await sleep(1000) + + const didResult = await endorser.dids.resolve(did) + expect(JsonTransformer.toJSON(didResult)).toMatchObject({ + didDocument: { + '@context': ['https://w3id.org/did/v1', 'https://w3id.org/security/suites/ed25519-2018/v1'], + id: did, + alsoKnownAs: undefined, + controller: undefined, + verificationMethod: [ + { + type: 'Ed25519VerificationKey2018', + controller: did, + id: `${did}#verkey`, + publicKeyBase58: ed25519PublicKeyBase58, + }, + ], + capabilityDelegation: undefined, + capabilityInvocation: undefined, + authentication: [`${did}#verkey`], + service: undefined, + }, + didDocumentMetadata: {}, + didResolutionMetadata: { + contentType: 'application/did+ld+json', + }, + }) + }) + + test('can register a did:indy with services - did and verkey specified - using attrib endpoint', async () => { + // Generate a private key and the indy did. This allows us to create a new did every time + // but still check if the created output document is as expected. + const privateKey = TypedArrayEncoder.fromString( + Array(32 + 1) + .join((Math.random().toString(36) + '00000000000000000').slice(2, 18)) + .slice(0, 32) + ) + + const key = await endorser.wallet.createKey({ privateKey, keyType: KeyType.Ed25519 }) + const x25519PublicKeyBase58 = TypedArrayEncoder.toBase58(convertPublicKeyToX25519(key.publicKey)) + const ed25519PublicKeyBase58 = TypedArrayEncoder.toBase58(key.publicKey) + + const { did, verkey } = indyDidFromNamespaceAndInitialKey( + 'pool:localtest', + Key.fromPublicKey(key.publicKey, KeyType.Ed25519) + ) + + const didRegistrationResult = await endorser.dids.create({ + did, + options: { + endorserDid, + endorserMode: 'internal', + useEndpointAttrib: true, + verkey, + services: [ + new DidDocumentService({ + id: `${did}#endpoint`, + serviceEndpoint: 'https://example.com/endpoint', + type: 'endpoint', + }), + new DidCommV1Service({ + id: `${did}#did-communication`, + priority: 0, + recipientKeys: [`${did}#key-agreement-1`], + routingKeys: ['a-routing-key'], + serviceEndpoint: 'https://example.com/endpoint', + accept: ['didcomm/aip2;env=rfc19'], + }), + new DidCommV2Service({ + accept: ['didcomm/v2'], + id: `${did}#didcomm-1`, + routingKeys: ['a-routing-key'], + serviceEndpoint: 'https://example.com/endpoint', + }), + ], + }, + }) + + const expectedDidDocument = { + '@context': [ + 'https://w3id.org/did/v1', + 'https://w3id.org/security/suites/ed25519-2018/v1', + 'https://w3id.org/security/suites/x25519-2019/v1', + 'https://didcomm.org/messaging/contexts/v2', + ], + id: did, + alsoKnownAs: undefined, + controller: undefined, + verificationMethod: [ + { + type: 'Ed25519VerificationKey2018', + controller: did, + id: `${did}#verkey`, + publicKeyBase58: ed25519PublicKeyBase58, + }, + { + type: 'X25519KeyAgreementKey2019', + controller: did, + id: `${did}#key-agreement-1`, + publicKeyBase58: x25519PublicKeyBase58, + }, + ], + capabilityDelegation: undefined, + capabilityInvocation: undefined, + authentication: [`${did}#verkey`], + service: [ + { + id: `${did}#endpoint`, + serviceEndpoint: 'https://example.com/endpoint', + type: 'endpoint', + }, + { + accept: ['didcomm/aip2;env=rfc19'], + id: `${did}#did-communication`, + priority: 0, + recipientKeys: [`${did}#key-agreement-1`], + routingKeys: ['a-routing-key'], + serviceEndpoint: 'https://example.com/endpoint', + type: 'did-communication', + }, + { + accept: ['didcomm/v2'], + id: `${did}#didcomm-1`, + routingKeys: ['a-routing-key'], + serviceEndpoint: 'https://example.com/endpoint', + type: 'DIDCommMessaging', + }, + ], + } + + expect(JsonTransformer.toJSON(didRegistrationResult)).toMatchObject({ + didDocumentMetadata: {}, + didRegistrationMetadata: {}, + didState: { + state: 'finished', + did, + didDocument: expectedDidDocument, + }, + }) + + // Wait some time pass to let ledger settle the object + await sleep(1000) + + const didResult = await endorser.dids.resolve(did) + expect(JsonTransformer.toJSON(didResult)).toMatchObject({ + didDocument: expectedDidDocument, + didDocumentMetadata: {}, + didResolutionMetadata: { + contentType: 'application/did+ld+json', + }, + }) + }) + + test('can register an endorsed did:indy with services - did and verkey specified - using attrib endpoint', async () => { + // Generate a private key and the indy did. This allows us to create a new did every time + // but still check if the created output document is as expected. + const privateKey = TypedArrayEncoder.fromString( + Array(32 + 1) + .join((Math.random().toString(36) + '00000000000000000').slice(2, 18)) + .slice(0, 32) + ) + + const key = await endorser.wallet.createKey({ privateKey, keyType: KeyType.Ed25519 }) + const x25519PublicKeyBase58 = TypedArrayEncoder.toBase58(convertPublicKeyToX25519(key.publicKey)) + const ed25519PublicKeyBase58 = TypedArrayEncoder.toBase58(key.publicKey) + + const { did, verkey } = indyDidFromNamespaceAndInitialKey( + 'pool:localtest', + Key.fromPublicKey(key.publicKey, KeyType.Ed25519) + ) + + const didCreateTobeEndorsedResult = (await endorser.dids.create({ + did, + options: { + endorserMode: 'external', + endorserDid: endorserDid, + useEndpointAttrib: true, + verkey, + services: [ + new DidDocumentService({ + id: `${did}#endpoint`, + serviceEndpoint: 'https://example.com/endpoint', + type: 'endpoint', + }), + new DidCommV1Service({ + id: `${did}#did-communication`, + priority: 0, + recipientKeys: [`${did}#key-agreement-1`], + routingKeys: ['a-routing-key'], + serviceEndpoint: 'https://example.com/endpoint', + accept: ['didcomm/aip2;env=rfc19'], + }), + new DidCommV2Service({ + accept: ['didcomm/v2'], + id: `${did}#didcomm-1`, + routingKeys: ['a-routing-key'], + serviceEndpoint: 'https://example.com/endpoint', + }), + ], + }, + })) as IndyVdrDidCreateResult + + const didState = didCreateTobeEndorsedResult.didState + if (didState.state !== 'action' || didState.action !== 'endorseIndyTransaction') throw Error('unexpected did state') + + const signedNymRequest = await endorser.modules.indyVdr.endorseTransaction( + didState.nymRequest, + didState.endorserDid + ) + + if (!didState.attribRequest) throw Error('attrib request not found') + const endorsedAttribRequest = await endorser.modules.indyVdr.endorseTransaction( + didState.attribRequest, + didState.endorserDid + ) + + const didCreateSubmitResult = await agent.dids.create({ + did: didState.did, + options: { + endorserMode: 'external', + endorsedTransaction: { + nymRequest: signedNymRequest, + attribRequest: endorsedAttribRequest, + }, + }, + secret: didState.secret, + }) + + const expectedDidDocument = { + '@context': [ + 'https://w3id.org/did/v1', + 'https://w3id.org/security/suites/ed25519-2018/v1', + 'https://w3id.org/security/suites/x25519-2019/v1', + 'https://didcomm.org/messaging/contexts/v2', + ], + id: did, + alsoKnownAs: undefined, + controller: undefined, + verificationMethod: [ + { + type: 'Ed25519VerificationKey2018', + controller: did, + id: `${did}#verkey`, + publicKeyBase58: ed25519PublicKeyBase58, + }, + { + type: 'X25519KeyAgreementKey2019', + controller: did, + id: `${did}#key-agreement-1`, + publicKeyBase58: x25519PublicKeyBase58, + }, + ], + capabilityDelegation: undefined, + capabilityInvocation: undefined, + authentication: [`${did}#verkey`], + service: [ + { + id: `${did}#endpoint`, + serviceEndpoint: 'https://example.com/endpoint', + type: 'endpoint', + }, + { + accept: ['didcomm/aip2;env=rfc19'], + id: `${did}#did-communication`, + priority: 0, + recipientKeys: [`${did}#key-agreement-1`], + routingKeys: ['a-routing-key'], + serviceEndpoint: 'https://example.com/endpoint', + type: 'did-communication', + }, + { + accept: ['didcomm/v2'], + id: `${did}#didcomm-1`, + routingKeys: ['a-routing-key'], + serviceEndpoint: 'https://example.com/endpoint', + type: 'DIDCommMessaging', + }, + ], + } + + expect(JsonTransformer.toJSON(didCreateSubmitResult)).toMatchObject({ + didDocumentMetadata: {}, + didRegistrationMetadata: {}, + didState: { + state: 'finished', + did, + didDocument: expectedDidDocument, + }, + }) + // Wait some time pass to let ledger settle the object + await sleep(1000) + + const didResult = await endorser.dids.resolve(did) + expect(JsonTransformer.toJSON(didResult)).toMatchObject({ + didDocument: expectedDidDocument, + didDocumentMetadata: {}, + didResolutionMetadata: { + contentType: 'application/did+ld+json', + }, + }) + }) +}) diff --git a/packages/indy-vdr/tests/indy-vdr-indy-did-resolver.e2e.test.ts b/packages/indy-vdr/tests/indy-vdr-indy-did-resolver.e2e.test.ts new file mode 100644 index 0000000000..c642684212 --- /dev/null +++ b/packages/indy-vdr/tests/indy-vdr-indy-did-resolver.e2e.test.ts @@ -0,0 +1,145 @@ +import { DidsModule, Agent, TypedArrayEncoder, JsonTransformer } from '@aries-framework/core' +import { indyVdr } from '@hyperledger/indy-vdr-nodejs' + +import { getAgentOptions, importExistingIndyDidFromPrivateKey } from '../../core/tests/helpers' +import { IndySdkModule } from '../../indy-sdk/src' +import { indySdk } from '../../indy-sdk/tests/setupIndySdkModule' +import { IndyVdrModule } from '../src' +import { IndyVdrIndyDidRegistrar, IndyVdrIndyDidResolver, IndyVdrSovDidResolver } from '../src/dids' + +import { createDidOnLedger, indyVdrModuleConfig } from './helpers' + +const agent = new Agent( + getAgentOptions( + 'Indy VDR Indy DID resolver', + {}, + { + indyVdr: new IndyVdrModule({ + indyVdr, + networks: indyVdrModuleConfig.networks, + }), + indySdk: new IndySdkModule({ + indySdk, + }), + dids: new DidsModule({ + registrars: [new IndyVdrIndyDidRegistrar()], + resolvers: [new IndyVdrIndyDidResolver(), new IndyVdrSovDidResolver()], + }), + } + ) +) + +describe('indy-vdr DID Resolver E2E', () => { + beforeAll(async () => { + await agent.initialize() + }) + + afterAll(async () => { + await agent.shutdown() + await agent.wallet.delete() + }) + + test('resolve a did:indy did', async () => { + const did = 'did:indy:pool:localtest:TL1EaPFCZ8Si5aUrqScBDt' + const didResult = await agent.dids.resolve(did) + + expect(JsonTransformer.toJSON(didResult)).toMatchObject({ + didDocument: { + '@context': ['https://w3id.org/did/v1', 'https://w3id.org/security/suites/ed25519-2018/v1'], + id: did, + alsoKnownAs: undefined, + controller: undefined, + verificationMethod: [ + { + type: 'Ed25519VerificationKey2018', + controller: did, + id: `${did}#verkey`, + publicKeyBase58: expect.any(String), + }, + ], + capabilityDelegation: undefined, + capabilityInvocation: undefined, + authentication: [`${did}#verkey`], + assertionMethod: undefined, + keyAgreement: undefined, + service: undefined, + }, + didDocumentMetadata: {}, + didResolutionMetadata: { + contentType: 'application/did+ld+json', + }, + }) + }) + + test('resolve a did with endpoints', async () => { + const unqualifiedSubmitterDid = await importExistingIndyDidFromPrivateKey( + agent, + TypedArrayEncoder.fromString('000000000000000000000000Trustee9') + ) + + // First we need to create a new DID and add ATTRIB endpoint to it + const { did } = await createDidOnLedger(agent, `did:indy:pool:localtest:${unqualifiedSubmitterDid}`) + + // DID created. Now resolve it + const didResult = await agent.dids.resolve(did) + expect(JsonTransformer.toJSON(didResult)).toMatchObject({ + didDocument: { + '@context': [ + 'https://w3id.org/did/v1', + 'https://w3id.org/security/suites/ed25519-2018/v1', + 'https://w3id.org/security/suites/x25519-2019/v1', + 'https://didcomm.org/messaging/contexts/v2', + ], + id: did, + alsoKnownAs: undefined, + controller: undefined, + verificationMethod: [ + { + type: 'Ed25519VerificationKey2018', + controller: did, + id: `${did}#verkey`, + publicKeyBase58: expect.any(String), + }, + { + controller: did, + type: 'X25519KeyAgreementKey2019', + id: `${did}#key-agreement-1`, + publicKeyBase58: expect.any(String), + }, + ], + capabilityDelegation: undefined, + capabilityInvocation: undefined, + authentication: [`${did}#verkey`], + assertionMethod: undefined, + keyAgreement: [`${did}#key-agreement-1`], + service: [ + { + id: `${did}#endpoint`, + serviceEndpoint: 'http://localhost:3000', + type: 'endpoint', + }, + { + id: `${did}#did-communication`, + accept: ['didcomm/aip2;env=rfc19'], + priority: 0, + recipientKeys: [`${did}#key-agreement-1`], + routingKeys: ['a-routing-key'], + serviceEndpoint: 'http://localhost:3000', + type: 'did-communication', + }, + { + id: `${did}#didcomm-1`, + accept: ['didcomm/v2'], + routingKeys: ['a-routing-key'], + serviceEndpoint: 'http://localhost:3000', + type: 'DIDCommMessaging', + }, + ], + }, + didDocumentMetadata: {}, + didResolutionMetadata: { + contentType: 'application/did+ld+json', + }, + }) + }) +}) diff --git a/packages/indy-vdr/tests/indy-vdr-pool.e2e.test.ts b/packages/indy-vdr/tests/indy-vdr-pool.e2e.test.ts new file mode 100644 index 0000000000..95e1ffa41c --- /dev/null +++ b/packages/indy-vdr/tests/indy-vdr-pool.e2e.test.ts @@ -0,0 +1,225 @@ +import type { Key } from '@aries-framework/core' + +import { TypedArrayEncoder, KeyType, KeyProviderRegistry } from '@aries-framework/core' +import { GetNymRequest, NymRequest, SchemaRequest, CredentialDefinitionRequest } from '@hyperledger/indy-vdr-shared' + +import { genesisTransactions, getAgentConfig, getAgentContext } from '../../core/tests/helpers' +import testLogger from '../../core/tests/logger' +import { IndySdkWallet } from '../../indy-sdk/src' +import { indySdk } from '../../indy-sdk/tests/setupIndySdkModule' +import { IndyVdrPool } from '../src/pool' +import { IndyVdrPoolService } from '../src/pool/IndyVdrPoolService' +import { indyDidFromPublicKeyBase58 } from '../src/utils/did' + +import { indyVdrModuleConfig } from './helpers' + +const indyVdrPoolService = new IndyVdrPoolService(testLogger, indyVdrModuleConfig) +const wallet = new IndySdkWallet(indySdk, testLogger, new KeyProviderRegistry([])) +const agentConfig = getAgentConfig('IndyVdrPoolService') +const agentContext = getAgentContext({ wallet, agentConfig }) + +const config = { + isProduction: false, + genesisTransactions, + indyNamespace: `pool:localtest`, + transactionAuthorAgreement: { version: '1', acceptanceMechanism: 'accept' }, +} as const + +let signerKey: Key + +describe('IndyVdrPoolService', () => { + beforeAll(async () => { + await wallet.createAndOpen(agentConfig.walletConfig) + + signerKey = await wallet.createKey({ + privateKey: TypedArrayEncoder.fromString('000000000000000000000000Trustee9'), + keyType: KeyType.Ed25519, + }) + }) + + afterAll(async () => { + for (const pool of indyVdrPoolService.pools) { + pool.close() + } + + await wallet.delete() + }) + + describe('DIDs', () => { + test('can get a pool based on the namespace', async () => { + const pool = indyVdrPoolService.getPoolForNamespace('pool:localtest') + expect(pool).toBeInstanceOf(IndyVdrPool) + expect(pool.config).toEqual(config) + }) + + test('can resolve a did using the pool', async () => { + const pool = indyVdrPoolService.getPoolForNamespace('pool:localtest') + + const request = new GetNymRequest({ + dest: 'TL1EaPFCZ8Si5aUrqScBDt', + }) + + const response = await pool.submitRequest(request) + + expect(response).toMatchObject({ + op: 'REPLY', + result: { + dest: 'TL1EaPFCZ8Si5aUrqScBDt', + type: '105', + data: expect.any(String), + identifier: 'LibindyDid111111111111', + reqId: expect.any(Number), + seqNo: expect.any(Number), + txnTime: expect.any(Number), + state_proof: expect.any(Object), + }, + }) + + expect(JSON.parse(response.result.data as string)).toMatchObject({ + dest: 'TL1EaPFCZ8Si5aUrqScBDt', + identifier: 'V4SGRU86Z58d6TV7PBUe6f', + role: '0', + seqNo: expect.any(Number), + txnTime: expect.any(Number), + verkey: '~43X4NhAFqREffK7eWdKgFH', + }) + }) + + test('can write a did using the pool', async () => { + const pool = indyVdrPoolService.getPoolForNamespace('pool:localtest') + + // prepare the DID we are going to write to the ledger + const key = await wallet.createKey({ keyType: KeyType.Ed25519 }) + const did = indyDidFromPublicKeyBase58(key.publicKeyBase58) + + const request = new NymRequest({ + dest: did, + submitterDid: 'TL1EaPFCZ8Si5aUrqScBDt', + verkey: key.publicKeyBase58, + }) + + const writeRequest = await pool.prepareWriteRequest(agentContext, request, signerKey) + const response = await pool.submitRequest(writeRequest) + + expect(response).toMatchObject({ + op: 'REPLY', + result: { + txn: { + protocolVersion: 2, + metadata: expect.any(Object), + data: expect.any(Object), + type: '1', + }, + ver: '1', + rootHash: expect.any(String), + txnMetadata: expect.any(Object), + }, + }) + }) + }) + + test('can write a schema and credential definition using the pool', async () => { + const pool = indyVdrPoolService.getPoolForNamespace('pool:localtest') + + const dynamicVersion = `1.${Math.random() * 100}` + + const schemaRequest = new SchemaRequest({ + submitterDid: 'TL1EaPFCZ8Si5aUrqScBDt', + schema: { + id: 'test-schema-id', + name: 'test-schema', + ver: '1.0', + version: dynamicVersion, + attrNames: ['first_name', 'last_name', 'age'], + }, + }) + + const writeRequest = await pool.prepareWriteRequest(agentContext, schemaRequest, signerKey) + const schemaResponse = await pool.submitRequest(writeRequest) + + expect(schemaResponse).toMatchObject({ + op: 'REPLY', + result: { + ver: '1', + txn: { + metadata: expect.any(Object), + type: '101', + data: { + data: { + attr_names: expect.arrayContaining(['age', 'last_name', 'first_name']), + name: 'test-schema', + version: dynamicVersion, + }, + }, + }, + }, + }) + + const credentialDefinitionRequest = new CredentialDefinitionRequest({ + submitterDid: 'TL1EaPFCZ8Si5aUrqScBDt', + credentialDefinition: { + ver: '1.0', + id: `TL1EaPFCZ8Si5aUrqScBDt:3:CL:${schemaResponse.result.txnMetadata.seqNo}:TAG`, + // must be string version of the schema seqNo + schemaId: `${schemaResponse.result.txnMetadata.seqNo}`, + type: 'CL', + tag: 'TAG', + value: { + primary: { + n: '95671911213029889766246243339609567053285242961853979532076192834533577534909796042025401129640348836502648821408485216223269830089771714177855160978214805993386076928594836829216646288195127289421136294309746871614765411402917891972999085287429566166932354413679994469616357622976775651506242447852304853465380257226445481515631782793575184420720296120464167257703633829902427169144462981949944348928086406211627174233811365419264314148304536534528344413738913277713548403058098093453580992173145127632199215550027527631259565822872315784889212327945030315062879193999012349220118290071491899498795367403447663354833', + s: '1573939820553851804028472930351082111827449763317396231059458630252708273163050576299697385049087601314071156646675105028237105229428440185022593174121924731226634356276616495327358864865629675802738680754755949997611920669823449540027707876555408118172529688443208301403297680159171306000341239398135896274940688268460793682007115152428685521865921925309154307574955324973580144009271977076586453011938089159885164705002797196738438392179082905738155386545935208094240038135576042886730802817809757582039362798495805441520744154270346780731494125065136433163757697326955962282840631850597919384092584727207908978907', + r: { + master_secret: + '51468326064458249697956272807708948542001661888325200180968238787091473418947480867518174106588127385097619219536294589148765074804124925845579871788369264160902401097166484002617399484700234182426993061977152961670486891123188739266793651668791365808983166555735631354925174224786218771453042042304773095663181121735652667614424198057134974727791329623974680096491276337756445057223988781749506082654194307092164895251308088903000573135447235553684949564809677864522417041639512933806794232354223826262154508950271949764583849083972967642587778197779127063591201123312548182885603427440981731822883101260509710567731', + last_name: + '35864556460959997092903171610228165251001245539613587319116151716453114432309327039517115215674024166920383445379522674504803469517283236033110568676156285676664363558333716898161685255450536856645604857714925836474250821415182026707218622134953915013803750771185050002646661004119778318524426368842019753903741998256374803456282688037624993010626333853831264356355867746685055670790915539230702546586615988121383960277550317876816983602795121749533628953449405383896799464872758725899520173321672584180060465965090049734285011738428381648013150818429882628144544132356242262467090140003979917439514443707537952643217', + first_name: + '26405366527417391838431479783966663952336302347775179063968690502492620867161212873635806190080000833725932174641667734138216137047349915190546601368424742647800764149890590518336588437317392528514313749533980651547425554257026971104775208127915118918084350210726664749850578299247705298976657301433446491575776774836993110356033664644761593799921221474617858131678955318702706530853801195330271860527250931569815553226145458665481867408279941785848264018364216087471931232367137301987457054918438087686484522112532447779498424748261678616461026788516567300969886029412198319909977473167405879110243445062391837349387', + age: '19865805272519696320755573045337531955436490760876870776207490804137339344112305203631892390827288264857621916650098902064979838987400911652887344763586495880167030031364467726355103327059673023946234460960685398768709062405377107912774045508870580108596597470880834205563197111550140867466625683117333370595295321833757429488192170551320637065066368716366317421169802474954914904380304190861641082310805418122837214965865969459724848071006870574514215255412289237027267424055400593307112849859757094597401668252862525566316402695830217450073667487951799749275437192883439584518905943435472478496028380016245355151988', + }, + rctxt: + '17146114573198643698878017247599007910707723139165264508694101989891626297408755744139587708989465136799243292477223763665064840330721616213638280284119891715514951989022398510785960099708705561761504012512387129498731093386014964896897751536856287377064154297370092339714578039195258061017640952790913108285519632654466006255438773382930416822756630391947263044087385305540191237328903426888518439803354213792647775798033294505898635058814132665832000734168261793545453678083703704122695006541391598116359796491845268631009298069826949515604008666680160398698425061157356267086946953480945396595351944425658076127674', + z: '57056568014385132434061065334124327103768023932445648883765905576432733866307137325457775876741578717650388638737098805750938053855430851133826479968450532729423746605371536096355616166421996729493639634413002114547787617999178137950004782677177313856876420539744625174205603354705595789330008560775613287118432593300023801651460885523314713996258581986238928077688246511704050386525431448517516821261983193275502089060128363906909778842476516981025598807378338053788433033754999771876361716562378445777250912525673660842724168260417083076824975992327559199634032439358787956784395443246565622469187082767614421691234', + }, + }, + }, + }) + + const credDefWriteRequest = await pool.prepareWriteRequest(agentContext, credentialDefinitionRequest, signerKey) + const response = await pool.submitRequest(credDefWriteRequest) + + expect(response).toMatchObject({ + op: 'REPLY', + result: { + ver: '1', + txn: { + metadata: expect.any(Object), + type: '102', + data: { + data: { + primary: { + r: { + last_name: + '35864556460959997092903171610228165251001245539613587319116151716453114432309327039517115215674024166920383445379522674504803469517283236033110568676156285676664363558333716898161685255450536856645604857714925836474250821415182026707218622134953915013803750771185050002646661004119778318524426368842019753903741998256374803456282688037624993010626333853831264356355867746685055670790915539230702546586615988121383960277550317876816983602795121749533628953449405383896799464872758725899520173321672584180060465965090049734285011738428381648013150818429882628144544132356242262467090140003979917439514443707537952643217', + first_name: + '26405366527417391838431479783966663952336302347775179063968690502492620867161212873635806190080000833725932174641667734138216137047349915190546601368424742647800764149890590518336588437317392528514313749533980651547425554257026971104775208127915118918084350210726664749850578299247705298976657301433446491575776774836993110356033664644761593799921221474617858131678955318702706530853801195330271860527250931569815553226145458665481867408279941785848264018364216087471931232367137301987457054918438087686484522112532447779498424748261678616461026788516567300969886029412198319909977473167405879110243445062391837349387', + age: '19865805272519696320755573045337531955436490760876870776207490804137339344112305203631892390827288264857621916650098902064979838987400911652887344763586495880167030031364467726355103327059673023946234460960685398768709062405377107912774045508870580108596597470880834205563197111550140867466625683117333370595295321833757429488192170551320637065066368716366317421169802474954914904380304190861641082310805418122837214965865969459724848071006870574514215255412289237027267424055400593307112849859757094597401668252862525566316402695830217450073667487951799749275437192883439584518905943435472478496028380016245355151988', + master_secret: + '51468326064458249697956272807708948542001661888325200180968238787091473418947480867518174106588127385097619219536294589148765074804124925845579871788369264160902401097166484002617399484700234182426993061977152961670486891123188739266793651668791365808983166555735631354925174224786218771453042042304773095663181121735652667614424198057134974727791329623974680096491276337756445057223988781749506082654194307092164895251308088903000573135447235553684949564809677864522417041639512933806794232354223826262154508950271949764583849083972967642587778197779127063591201123312548182885603427440981731822883101260509710567731', + }, + z: '57056568014385132434061065334124327103768023932445648883765905576432733866307137325457775876741578717650388638737098805750938053855430851133826479968450532729423746605371536096355616166421996729493639634413002114547787617999178137950004782677177313856876420539744625174205603354705595789330008560775613287118432593300023801651460885523314713996258581986238928077688246511704050386525431448517516821261983193275502089060128363906909778842476516981025598807378338053788433033754999771876361716562378445777250912525673660842724168260417083076824975992327559199634032439358787956784395443246565622469187082767614421691234', + rctxt: + '17146114573198643698878017247599007910707723139165264508694101989891626297408755744139587708989465136799243292477223763665064840330721616213638280284119891715514951989022398510785960099708705561761504012512387129498731093386014964896897751536856287377064154297370092339714578039195258061017640952790913108285519632654466006255438773382930416822756630391947263044087385305540191237328903426888518439803354213792647775798033294505898635058814132665832000734168261793545453678083703704122695006541391598116359796491845268631009298069826949515604008666680160398698425061157356267086946953480945396595351944425658076127674', + n: '95671911213029889766246243339609567053285242961853979532076192834533577534909796042025401129640348836502648821408485216223269830089771714177855160978214805993386076928594836829216646288195127289421136294309746871614765411402917891972999085287429566166932354413679994469616357622976775651506242447852304853465380257226445481515631782793575184420720296120464167257703633829902427169144462981949944348928086406211627174233811365419264314148304536534528344413738913277713548403058098093453580992173145127632199215550027527631259565822872315784889212327945030315062879193999012349220118290071491899498795367403447663354833', + s: '1573939820553851804028472930351082111827449763317396231059458630252708273163050576299697385049087601314071156646675105028237105229428440185022593174121924731226634356276616495327358864865629675802738680754755949997611920669823449540027707876555408118172529688443208301403297680159171306000341239398135896274940688268460793682007115152428685521865921925309154307574955324973580144009271977076586453011938089159885164705002797196738438392179082905738155386545935208094240038135576042886730802817809757582039362798495805441520744154270346780731494125065136433163757697326955962282840631850597919384092584727207908978907', + }, + }, + signature_type: 'CL', + ref: schemaResponse.result.txnMetadata.seqNo, + tag: 'TAG', + }, + }, + }, + }) + }) +}) diff --git a/packages/indy-vdr/tests/indy-vdr-sov-did-resolver.e2e.test.ts b/packages/indy-vdr/tests/indy-vdr-sov-did-resolver.e2e.test.ts new file mode 100644 index 0000000000..705838f71f --- /dev/null +++ b/packages/indy-vdr/tests/indy-vdr-sov-did-resolver.e2e.test.ts @@ -0,0 +1,159 @@ +import { parseIndyDid } from '@aries-framework/anoncreds' +import { DidsModule, Agent, TypedArrayEncoder, JsonTransformer } from '@aries-framework/core' +import { indyVdr } from '@hyperledger/indy-vdr-nodejs' + +import { getAgentOptions, importExistingIndyDidFromPrivateKey } from '../../core/tests/helpers' +import { IndySdkModule } from '../../indy-sdk/src' +import { indySdk } from '../../indy-sdk/tests/setupIndySdkModule' +import { IndyVdrModule } from '../src' +import { IndyVdrIndyDidRegistrar, IndyVdrIndyDidResolver, IndyVdrSovDidResolver } from '../src/dids' + +import { createDidOnLedger, indyVdrModuleConfig } from './helpers' + +const agent = new Agent( + getAgentOptions( + 'Indy VDR Sov DID resolver', + {}, + { + indyVdr: new IndyVdrModule({ + indyVdr, + networks: indyVdrModuleConfig.networks, + }), + indySdk: new IndySdkModule({ + indySdk, + }), + dids: new DidsModule({ + registrars: [new IndyVdrIndyDidRegistrar()], + resolvers: [new IndyVdrIndyDidResolver(), new IndyVdrSovDidResolver()], + }), + } + ) +) + +describe('Indy VDR Sov DID Resolver', () => { + beforeAll(async () => { + await agent.initialize() + }) + + afterAll(async () => { + await agent.shutdown() + await agent.wallet.delete() + }) + + test('resolve a did:sov did', async () => { + const did = 'did:sov:TL1EaPFCZ8Si5aUrqScBDt' + const didResult = await agent.dids.resolve(did) + + expect(JsonTransformer.toJSON(didResult)).toMatchObject({ + didDocument: { + '@context': [ + 'https://w3id.org/did/v1', + 'https://w3id.org/security/suites/ed25519-2018/v1', + 'https://w3id.org/security/suites/x25519-2019/v1', + ], + id: did, + alsoKnownAs: undefined, + controller: undefined, + verificationMethod: [ + { + type: 'Ed25519VerificationKey2018', + controller: did, + id: `${did}#key-1`, + publicKeyBase58: expect.any(String), + }, + { + controller: did, + type: 'X25519KeyAgreementKey2019', + id: `${did}#key-agreement-1`, + publicKeyBase58: expect.any(String), + }, + ], + capabilityDelegation: undefined, + capabilityInvocation: undefined, + authentication: [`${did}#key-1`], + assertionMethod: [`${did}#key-1`], + keyAgreement: [`${did}#key-agreement-1`], + service: undefined, + }, + didDocumentMetadata: {}, + didResolutionMetadata: { + contentType: 'application/did+ld+json', + }, + }) + }) + + test('resolve a did with endpoints', async () => { + const unqualifiedSubmitterDid = await importExistingIndyDidFromPrivateKey( + agent, + TypedArrayEncoder.fromString('000000000000000000000000Trustee9') + ) + + // First we need to create a new DID and add ATTRIB endpoint to it + const { did } = await createDidOnLedger(agent, `did:indy:pool:localtest:${unqualifiedSubmitterDid}`) + const { namespaceIdentifier } = parseIndyDid(did) + const sovDid = `did:sov:${namespaceIdentifier}` + + // DID created. Now resolve it + const didResult = await agent.dids.resolve(sovDid) + + expect(JsonTransformer.toJSON(didResult)).toMatchObject({ + didDocument: { + '@context': [ + 'https://w3id.org/did/v1', + 'https://w3id.org/security/suites/ed25519-2018/v1', + 'https://w3id.org/security/suites/x25519-2019/v1', + 'https://didcomm.org/messaging/contexts/v2', + ], + id: sovDid, + alsoKnownAs: undefined, + controller: undefined, + verificationMethod: [ + { + type: 'Ed25519VerificationKey2018', + controller: sovDid, + id: `${sovDid}#key-1`, + publicKeyBase58: expect.any(String), + }, + { + controller: sovDid, + type: 'X25519KeyAgreementKey2019', + id: `${sovDid}#key-agreement-1`, + publicKeyBase58: expect.any(String), + }, + ], + capabilityDelegation: undefined, + capabilityInvocation: undefined, + authentication: [`${sovDid}#key-1`], + assertionMethod: [`${sovDid}#key-1`], + keyAgreement: [`${sovDid}#key-agreement-1`], + service: [ + { + id: `${sovDid}#endpoint`, + serviceEndpoint: 'http://localhost:3000', + type: 'endpoint', + }, + { + id: `${sovDid}#did-communication`, + accept: ['didcomm/aip2;env=rfc19'], + priority: 0, + recipientKeys: [`${sovDid}#key-agreement-1`], + routingKeys: ['a-routing-key'], + serviceEndpoint: 'http://localhost:3000', + type: 'did-communication', + }, + { + id: `${sovDid}#didcomm-1`, + accept: ['didcomm/v2'], + routingKeys: ['a-routing-key'], + serviceEndpoint: 'http://localhost:3000', + type: 'DIDCommMessaging', + }, + ], + }, + didDocumentMetadata: {}, + didResolutionMetadata: { + contentType: 'application/did+ld+json', + }, + }) + }) +}) diff --git a/packages/indy-vdr/tests/setup.ts b/packages/indy-vdr/tests/setup.ts new file mode 100644 index 0000000000..a5714a641c --- /dev/null +++ b/packages/indy-vdr/tests/setup.ts @@ -0,0 +1,3 @@ +import '@hyperledger/indy-vdr-nodejs' + +jest.setTimeout(120000) diff --git a/packages/indy-vdr/tsconfig.build.json b/packages/indy-vdr/tsconfig.build.json new file mode 100644 index 0000000000..2b75d0adab --- /dev/null +++ b/packages/indy-vdr/tsconfig.build.json @@ -0,0 +1,7 @@ +{ + "extends": "../../tsconfig.build.json", + "compilerOptions": { + "outDir": "./build" + }, + "include": ["src/**/*"] +} diff --git a/packages/indy-vdr/tsconfig.json b/packages/indy-vdr/tsconfig.json new file mode 100644 index 0000000000..46efe6f721 --- /dev/null +++ b/packages/indy-vdr/tsconfig.json @@ -0,0 +1,6 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "types": ["jest"] + } +} diff --git a/packages/node/CHANGELOG.md b/packages/node/CHANGELOG.md index a3f6e278ce..32ecc62fb0 100644 --- a/packages/node/CHANGELOG.md +++ b/packages/node/CHANGELOG.md @@ -3,6 +3,12 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [0.3.3](https://github.com/hyperledger/aries-framework-javascript/compare/v0.3.2...v0.3.3) (2023-01-18) + +### Bug Fixes + +- fix typing issues with typescript 4.9 ([#1214](https://github.com/hyperledger/aries-framework-javascript/issues/1214)) ([087980f](https://github.com/hyperledger/aries-framework-javascript/commit/087980f1adf3ee0bc434ca9782243a62c6124444)) + ## [0.3.2](https://github.com/hyperledger/aries-framework-javascript/compare/v0.3.1...v0.3.2) (2023-01-04) **Note:** Version bump only for package @aries-framework/node diff --git a/packages/node/bin/is-indy-installed.js b/packages/node/bin/is-indy-installed.js deleted file mode 100755 index 59704d4de7..0000000000 --- a/packages/node/bin/is-indy-installed.js +++ /dev/null @@ -1,17 +0,0 @@ -#!/usr/bin/env node -/* eslint-disable no-console, @typescript-eslint/no-var-requires, no-undef */ - -const { createWallet, deleteWallet } = require('indy-sdk') - -const uuid = Math.random() * 10000 -const id = `test-wallet-id-${uuid}` - -createWallet({ id }, { key: id }) - .then(() => deleteWallet({ id }, { key: id })) - .then(() => { - console.log('Libindy was installed correctly') - }) - .catch((e) => { - console.log('Libindy was installed correctly, but an error did occur') - console.error(e) - }) diff --git a/packages/node/jest.config.ts b/packages/node/jest.config.ts index ce53584ebf..2556d19c61 100644 --- a/packages/node/jest.config.ts +++ b/packages/node/jest.config.ts @@ -6,7 +6,6 @@ import packageJson from './package.json' const config: Config.InitialOptions = { ...base, - name: packageJson.name, displayName: packageJson.name, } diff --git a/packages/node/package.json b/packages/node/package.json index 540489b8b6..751b312f00 100644 --- a/packages/node/package.json +++ b/packages/node/package.json @@ -2,10 +2,9 @@ "name": "@aries-framework/node", "main": "build/index", "types": "build/index", - "version": "0.3.2", + "version": "0.3.3", "files": [ - "build", - "bin" + "build" ], "license": "Apache-2.0", "publishConfig": { @@ -17,33 +16,30 @@ "url": "https://github.com/hyperledger/aries-framework-javascript", "directory": "packages/node" }, - "bin": { - "is-indy-installed": "bin/is-indy-installed.js" - }, "scripts": { "build": "yarn run clean && yarn run compile", - "clean": "rimraf -rf ./build", + "clean": "rimraf ./build", "compile": "tsc -p tsconfig.build.json", "prepublishOnly": "yarn run build", "test": "jest" }, "dependencies": { - "@aries-framework/core": "0.3.2", + "@aries-framework/core": "0.3.3", + "@types/express": "^4.17.15", "express": "^4.17.1", "ffi-napi": "^4.0.3", - "indy-sdk": "^1.16.0-dev-1636", "node-fetch": "^2.6.1", "ref-napi": "^3.0.3", - "ws": "^7.5.3" + "ws": "^8.13.0" }, "devDependencies": { - "@types/express": "^4.17.13", "@types/ffi-napi": "^4.0.5", - "@types/node": "^15.14.4", - "@types/node-fetch": "^2.5.10", + "@types/node": "^16.11.7", + "@types/node-fetch": "2.6.2", "@types/ref-napi": "^3.0.4", - "@types/ws": "^7.4.6", - "rimraf": "~3.0.2", - "typescript": "~4.3.0" + "@types/ws": "^8.5.4", + "nock": "^13.3.0", + "rimraf": "^4.4.0", + "typescript": "~4.9.5" } } diff --git a/packages/node/src/NodeFileSystem.ts b/packages/node/src/NodeFileSystem.ts index f739c40814..33af5391d6 100644 --- a/packages/node/src/NodeFileSystem.ts +++ b/packages/node/src/NodeFileSystem.ts @@ -1,23 +1,34 @@ -import type { FileSystem } from '@aries-framework/core' +import type { DownloadToFileOptions, FileSystem } from '@aries-framework/core' +import { AriesFrameworkError, TypedArrayEncoder } from '@aries-framework/core' +import { createHash } from 'crypto' import fs, { promises } from 'fs' import http from 'http' import https from 'https' -import { tmpdir } from 'os' +import { tmpdir, homedir } from 'os' import { dirname } from 'path' -const { access, readFile, writeFile } = promises +const { access, readFile, writeFile, mkdir, rm, unlink, copyFile } = promises export class NodeFileSystem implements FileSystem { - public readonly basePath + public readonly dataPath + public readonly cachePath + public readonly tempPath /** * Create new NodeFileSystem class instance. * - * @param basePath The base path to use for reading and writing files. process.cwd() if not specified + * @param baseDataPath The base path to use for reading and writing data files used within the framework. + * Files will be created under baseDataPath/.afj directory. If not specified, it will be set to homedir() + * @param baseCachePath The base path to use for reading and writing cache files used within the framework. + * Files will be created under baseCachePath/.afj directory. If not specified, it will be set to homedir() + * @param baseTempPath The base path to use for reading and writing temporary files within the framework. + * Files will be created under baseTempPath/.afj directory. If not specified, it will be set to tmpdir() */ - public constructor(basePath?: string) { - this.basePath = basePath ?? tmpdir() + public constructor(options?: { baseDataPath?: string; baseCachePath?: string; baseTempPath?: string }) { + this.dataPath = options?.baseDataPath ? `${options?.baseDataPath}/.afj` : `${homedir()}/.afj/data` + this.cachePath = options?.baseCachePath ? `${options?.baseCachePath}/.afj` : `${homedir()}/.afj/cache` + this.tempPath = `${options?.baseTempPath ?? tmpdir()}/.afj` } public async exists(path: string) { @@ -29,9 +40,17 @@ export class NodeFileSystem implements FileSystem { } } + public async createDirectory(path: string): Promise { + await mkdir(dirname(path), { recursive: true }) + } + + public async copyFile(sourcePath: string, destinationPath: string): Promise { + await copyFile(sourcePath, destinationPath) + } + public async write(path: string, data: string): Promise { // Make sure parent directories exist - await promises.mkdir(dirname(path), { recursive: true }) + await mkdir(dirname(path), { recursive: true }) return writeFile(path, data, { encoding: 'utf-8' }) } @@ -40,13 +59,18 @@ export class NodeFileSystem implements FileSystem { return readFile(path, { encoding: 'utf-8' }) } - public async downloadToFile(url: string, path: string) { + public async delete(path: string): Promise { + await rm(path, { recursive: true, force: true }) + } + + public async downloadToFile(url: string, path: string, options: DownloadToFileOptions) { const httpMethod = url.startsWith('https') ? https : http // Make sure parent directories exist - await promises.mkdir(dirname(path), { recursive: true }) + await mkdir(dirname(path), { recursive: true }) const file = fs.createWriteStream(path) + const hash = options.verifyHash ? createHash('sha256') : undefined return new Promise((resolve, reject) => { httpMethod @@ -56,15 +80,32 @@ export class NodeFileSystem implements FileSystem { reject(`Unable to download file from url: ${url}. Response status was ${response.statusCode}`) } + hash && response.pipe(hash) response.pipe(file) - file.on('finish', () => { + file.on('finish', async () => { file.close() + + if (hash && options.verifyHash?.hash) { + hash.end() + const digest = hash.digest() + if (digest.compare(options.verifyHash.hash) !== 0) { + await fs.promises.unlink(path) + + reject( + new AriesFrameworkError( + `Hash of downloaded file does not match expected hash. Expected: ${ + options.verifyHash.hash + }, Actual: ${TypedArrayEncoder.toUtf8String(digest)})}` + ) + ) + } + } resolve() }) }) .on('error', async (error) => { // Handle errors - await fs.promises.unlink(path) // Delete the file async. (But we don't check the result) + await unlink(path) // Delete the file async. (But we don't check the result) reject(`Unable to download file from url: ${url}. ${error.message}`) }) }) diff --git a/packages/node/src/PostgresPlugin.ts b/packages/node/src/PostgresPlugin.ts index 4ad7dc5f37..2bcac4aae2 100644 --- a/packages/node/src/PostgresPlugin.ts +++ b/packages/node/src/PostgresPlugin.ts @@ -70,32 +70,35 @@ type NativeIndyPostgres = { let indyPostgresStorage: NativeIndyPostgres | undefined -export interface WalletStorageConfig { +export interface IndySdkPostgresWalletStorageConfig { url: string - wallet_scheme: WalletScheme + wallet_scheme: IndySdkPostgresWalletScheme path?: string } -export interface WalletStorageCredentials { +export interface IndySdkPostgresWalletStorageCredentials { account: string password: string admin_account: string admin_password: string } -export enum WalletScheme { +export enum IndySdkPostgresWalletScheme { DatabasePerWallet = 'DatabasePerWallet', MultiWalletSingleTable = 'MultiWalletSingleTable', MultiWalletSingleTableSharedPool = 'MultiWalletSingleTableSharedPool', } -export interface IndyPostgresStorageConfig { +export interface IndySdkPostgresStorageConfig { type: 'postgres_storage' - config: WalletStorageConfig - credentials: WalletStorageCredentials + config: IndySdkPostgresWalletStorageConfig + credentials: IndySdkPostgresWalletStorageCredentials } -export function loadPostgresPlugin(config: WalletStorageConfig, credentials: WalletStorageCredentials) { +export function loadIndySdkPostgresPlugin( + config: IndySdkPostgresWalletStorageConfig, + credentials: IndySdkPostgresWalletStorageCredentials +) { if (!indyPostgresStorage) { indyPostgresStorage = getLibrary() } diff --git a/packages/node/src/index.ts b/packages/node/src/index.ts index 5e58035b32..fbe7ed0452 100644 --- a/packages/node/src/index.ts +++ b/packages/node/src/index.ts @@ -1,12 +1,11 @@ import type { AgentDependencies } from '@aries-framework/core' import { EventEmitter } from 'events' -import * as indy from 'indy-sdk' import fetch from 'node-fetch' import WebSocket from 'ws' import { NodeFileSystem } from './NodeFileSystem' -import { IndyPostgresStorageConfig, loadPostgresPlugin, WalletScheme } from './PostgresPlugin' +import { IndySdkPostgresStorageConfig, loadIndySdkPostgresPlugin, IndySdkPostgresWalletScheme } from './PostgresPlugin' import { HttpInboundTransport } from './transport/HttpInboundTransport' import { WsInboundTransport } from './transport/WsInboundTransport' @@ -15,14 +14,13 @@ const agentDependencies: AgentDependencies = { fetch, EventEmitterClass: EventEmitter, WebSocketClass: WebSocket, - indy, } export { agentDependencies, HttpInboundTransport, WsInboundTransport, - loadPostgresPlugin, - IndyPostgresStorageConfig, - WalletScheme, + loadIndySdkPostgresPlugin, + IndySdkPostgresStorageConfig, + IndySdkPostgresWalletScheme, } diff --git a/packages/node/src/transport/HttpInboundTransport.ts b/packages/node/src/transport/HttpInboundTransport.ts index 2bc1161954..891ad4145e 100644 --- a/packages/node/src/transport/HttpInboundTransport.ts +++ b/packages/node/src/transport/HttpInboundTransport.ts @@ -5,6 +5,8 @@ import type { Server } from 'http' import { DidCommMimeType, AriesFrameworkError, TransportService, utils, MessageReceiver } from '@aries-framework/core' import express, { text } from 'express' +const supportedContentTypes: string[] = [DidCommMimeType.V0, DidCommMimeType.V1] + export class HttpInboundTransport implements InboundTransport { public readonly app: Express private port: number @@ -22,12 +24,19 @@ export class HttpInboundTransport implements InboundTransport { this.app = app ?? express() this.path = path ?? '/' - this.app.use( - text({ - type: [DidCommMimeType.V0, DidCommMimeType.V1], - limit: '5mb', - }) - ) + this.app.use((req, res, next) => { + const contentType = req.headers['content-type'] + + if (!contentType || !supportedContentTypes.includes(contentType)) { + return res + .status(415) + .send('Unsupported content-type. Supported content-types are: ' + supportedContentTypes.join(', ')) + } + + return next() + }) + + this.app.use(text({ type: supportedContentTypes, limit: '5mb' })) } public async start(agent: Agent) { diff --git a/packages/node/src/transport/WsInboundTransport.ts b/packages/node/src/transport/WsInboundTransport.ts index 2fa23c6168..0ccda783ba 100644 --- a/packages/node/src/transport/WsInboundTransport.ts +++ b/packages/node/src/transport/WsInboundTransport.ts @@ -58,7 +58,7 @@ export class WsInboundTransport implements InboundTransport { } private listenOnWebSocketMessages(agent: Agent, socket: WebSocket, session: TransportSession) { - const messageReceiver = agent.injectionContainer.resolve(MessageReceiver) + const messageReceiver = agent.dependencyManager.resolve(MessageReceiver) // eslint-disable-next-line @typescript-eslint/no-explicit-any socket.addEventListener('message', async (event: any) => { diff --git a/packages/node/tests/NodeFileSystem.test.ts b/packages/node/tests/NodeFileSystem.test.ts index e242b43cdd..f62f4d8e00 100644 --- a/packages/node/tests/NodeFileSystem.test.ts +++ b/packages/node/tests/NodeFileSystem.test.ts @@ -1,13 +1,42 @@ +import { TypedArrayEncoder } from '@aries-framework/core' +import nock, { cleanAll, enableNetConnect } from 'nock' +import path from 'path' + import { NodeFileSystem } from '../src/NodeFileSystem' describe('@aries-framework/file-system-node', () => { describe('NodeFileSystem', () => { const fileSystem = new NodeFileSystem() + afterAll(() => { + cleanAll() + enableNetConnect() + }) + describe('exists()', () => { it('should return false if the pash does not exist', () => { return expect(fileSystem.exists('some-random-path')).resolves.toBe(false) }) }) + + describe('downloadToFile()', () => { + test('should verify the hash', async () => { + // Mock tails file + nock('https://tails.prod.absa.africa') + .get('/api/public/tails/4B1NxYuGxwYMe5BAyP9NXkUmbEkDATo4oGZCgjXQ3y1p') + .replyWithFile(200, path.join(__dirname, '__fixtures__/tailsFile')) + + await fileSystem.downloadToFile( + 'https://tails.prod.absa.africa/api/public/tails/4B1NxYuGxwYMe5BAyP9NXkUmbEkDATo4oGZCgjXQ3y1p', + `${fileSystem.dataPath}/tails/4B1NxYuGxwYMe5BAyP9NXkUmbEkDATo4oGZCgjXQ3y1p`, + { + verifyHash: { + algorithm: 'sha256', + hash: TypedArrayEncoder.fromBase58('4B1NxYuGxwYMe5BAyP9NXkUmbEkDATo4oGZCgjXQ3y1p'), + }, + } + ) + }) + }) }) }) diff --git a/packages/node/tests/__fixtures__/tailsFile b/packages/node/tests/__fixtures__/tailsFile new file mode 100644 index 0000000000..73f0471860 Binary files /dev/null and b/packages/node/tests/__fixtures__/tailsFile differ diff --git a/packages/openid4vc-client/README.md b/packages/openid4vc-client/README.md new file mode 100644 index 0000000000..540339fef7 --- /dev/null +++ b/packages/openid4vc-client/README.md @@ -0,0 +1,167 @@ +

+
+ Hyperledger Aries logo +

+

Aries Framework JavaScript Open ID Connect For Verifiable Credentials Client Module

+

+ License + typescript + @aries-framework/openid4vc-client version + +

+
+ +Open ID Connect For Verifiable Credentials Client Module for [Aries Framework JavaScript](https://github.com/hyperledger/aries-framework-javascript). + +### Installation + +Make sure you have set up the correct version of Aries Framework JavaScript according to the AFJ repository. + +```sh +yarn add @aries-framework/openid4vc-client +``` + +### Quick start + +#### Requirements + +Before a credential can be requested, you need the issuer URI. This URI starts with `openid-initiate-issuance://` and is provided by the issuer. The issuer URI is commonly acquired by scanning a QR code. + +#### Module registration + +In order to get this module to work, we need to inject it into the agent. This makes the module's functionality accessible through the agent's `modules` api. + +```ts +import { OpenId4VcClientModule } from '@aries-framework/openid4vc-client' + +const agent = new Agent({ + config: { + /* config */ + }, + dependencies: agentDependencies, + modules: { + openId4VcClient: new OpenId4VcClientModule(), + /* other custom modules */ + }, +}) + +await agent.initialize() +``` + +How the module is injected and the agent has been initialized, you can access the module's functionality through `agent.modules.openId4VcClient`. + +#### Preparing a DID + +In order to request a credential, you'll need to provide a DID that the issuer will use for setting the credential subject. In the following snippet we create one for the sake of the example, but this can be any DID that has a _authentication verification method_ with key type `Ed25519`. + +```ts +// first we create the DID +const did = await agent.dids.create({ + method: 'key', + options: { + keyType: KeyType.Ed25519, + }, +}) + +// next we do some assertions and extract the key identifier (kid) + +if ( + !did.didState.didDocument || + !did.didState.didDocument.authentication || + did.didState.didDocument.authentication.length === 0 +) { + throw new Error("Error creating did document, or did document has no 'authentication' verificationMethods") +} + +const [verificationMethod] = did.didState.didDocument.authentication +const kid = typeof verificationMethod === 'string' ? verificationMethod : verificationMethod.id +``` + +#### Requesting the credential (Pre-Authorized) + +Now a credential issuance can be requested as follows. + +```ts +const w3cCredentialRecord = await agent.modules.openId4VcClient.requestCredentialPreAuthorized({ + issuerUri, + kid, + checkRevocationState: false, +}) + +console.log(w3cCredentialRecord) +``` + +#### Full example + +```ts +import { OpenId4VcClientModule } from '@aries-framework/openid4vc-client' +import { agentDependencies } from '@aries-framework/node' // use @aries-framework/react-native for React Native +import { Agent, KeyDidCreateOptions } from '@aries-framework/core' + +const run = async () => { + const issuerUri = '' // The obtained issuer URI + + // Create the Agent + const agent = new Agent({ + config: { + /* config */ + }, + dependencies: agentDependencies, + modules: { + openId4VcClient: new OpenId4VcClientModule(), + /* other custom modules */ + }, + }) + + // Initialize the Agent + await agent.initialize() + + // Create a DID + const did = await agent.dids.create({ + method: 'key', + options: { + keyType: KeyType.Ed25519, + }, + }) + + // Assert DIDDocument is valid + if ( + !did.didState.didDocument || + !did.didState.didDocument.authentication || + did.didState.didDocument.authentication.length === 0 + ) { + throw new Error("Error creating did document, or did document has no 'authentication' verificationMethods") + } + + // Extract key identified (kid) for authentication verification method + const [verificationMethod] = did.didState.didDocument.authentication + const kid = typeof verificationMethod === 'string' ? verificationMethod : verificationMethod.id + + // Request the credential + const w3cCredentialRecord = await agent.modules.openId4VcClient.requestCredentialPreAuthorized({ + issuerUri, + kid, + checkRevocationState: false, + }) + + // Log the received credential + console.log(w3cCredentialRecord) +} +``` diff --git a/packages/openid4vc-client/jest.config.ts b/packages/openid4vc-client/jest.config.ts new file mode 100644 index 0000000000..93c0197296 --- /dev/null +++ b/packages/openid4vc-client/jest.config.ts @@ -0,0 +1,13 @@ +import type { Config } from '@jest/types' + +import base from '../../jest.config.base' + +import packageJson from './package.json' + +const config: Config.InitialOptions = { + ...base, + displayName: packageJson.name, + setupFilesAfterEnv: ['./tests/setup.ts'], +} + +export default config diff --git a/packages/openid4vc-client/package.json b/packages/openid4vc-client/package.json new file mode 100644 index 0000000000..d9d642171d --- /dev/null +++ b/packages/openid4vc-client/package.json @@ -0,0 +1,37 @@ +{ + "name": "@aries-framework/openid4vc-client", + "main": "build/index", + "types": "build/index", + "version": "0.3.3", + "files": [ + "build" + ], + "license": "Apache-2.0", + "publishConfig": { + "access": "public" + }, + "homepage": "https://github.com/hyperledger/aries-framework-javascript/tree/main/packages/openid4vc-client", + "repository": { + "type": "git", + "url": "https://github.com/hyperledger/aries-framework-javascript", + "directory": "packages/openid4vc-client" + }, + "scripts": { + "build": "yarn run clean && yarn run compile", + "clean": "rimraf ./build", + "compile": "tsc -p tsconfig.build.json", + "prepublishOnly": "yarn run build", + "test": "jest" + }, + "dependencies": { + "@aries-framework/core": "0.3.3", + "@sphereon/openid4vci-client": "^0.4.0", + "@stablelib/random": "^1.0.2" + }, + "devDependencies": { + "@aries-framework/node": "0.3.3", + "nock": "^13.3.0", + "rimraf": "^4.4.0", + "typescript": "~4.9.5" + } +} diff --git a/packages/openid4vc-client/src/OpenId4VcClientApi.ts b/packages/openid4vc-client/src/OpenId4VcClientApi.ts new file mode 100644 index 0000000000..d927903c4c --- /dev/null +++ b/packages/openid4vc-client/src/OpenId4VcClientApi.ts @@ -0,0 +1,50 @@ +import type { + GenerateAuthorizationUrlOptions, + PreAuthCodeFlowOptions, + AuthCodeFlowOptions, +} from './OpenId4VcClientService' +import type { W3cCredentialRecord } from '@aries-framework/core' + +import { AgentContext, injectable } from '@aries-framework/core' + +import { AuthFlowType, OpenId4VcClientService } from './OpenId4VcClientService' + +/** + * @public + */ +@injectable() +export class OpenId4VcClientApi { + private agentContext: AgentContext + private openId4VcClientService: OpenId4VcClientService + + public constructor(agentContext: AgentContext, openId4VcClientService: OpenId4VcClientService) { + this.agentContext = agentContext + this.openId4VcClientService = openId4VcClientService + } + + public async requestCredentialUsingPreAuthorizedCode(options: PreAuthCodeFlowOptions): Promise { + // set defaults + const verifyRevocationState = options.verifyRevocationState ?? true + + return this.openId4VcClientService.requestCredential(this.agentContext, { + ...options, + verifyRevocationState: verifyRevocationState, + flowType: AuthFlowType.PreAuthorizedCodeFlow, + }) + } + + public async requestCredentialUsingAuthorizationCode(options: AuthCodeFlowOptions): Promise { + // set defaults + const checkRevocationState = options.verifyRevocationState ?? true + + return this.openId4VcClientService.requestCredential(this.agentContext, { + ...options, + verifyRevocationState: checkRevocationState, + flowType: AuthFlowType.AuthorizationCodeFlow, + }) + } + + public async generateAuthorizationUrl(options: GenerateAuthorizationUrlOptions) { + return this.openId4VcClientService.generateAuthorizationUrl(options) + } +} diff --git a/packages/openid4vc-client/src/OpenId4VcClientModule.ts b/packages/openid4vc-client/src/OpenId4VcClientModule.ts new file mode 100644 index 0000000000..fe941949cf --- /dev/null +++ b/packages/openid4vc-client/src/OpenId4VcClientModule.ts @@ -0,0 +1,22 @@ +import type { DependencyManager, Module } from '@aries-framework/core' + +import { OpenId4VcClientApi } from './OpenId4VcClientApi' +import { OpenId4VcClientService } from './OpenId4VcClientService' + +/** + * @public + */ +export class OpenId4VcClientModule implements Module { + public readonly api = OpenId4VcClientApi + + /** + * Registers the dependencies of the question answer module on the dependency manager. + */ + public register(dependencyManager: DependencyManager) { + // Api + dependencyManager.registerContextScoped(OpenId4VcClientApi) + + // Services + dependencyManager.registerSingleton(OpenId4VcClientService) + } +} diff --git a/packages/openid4vc-client/src/OpenId4VcClientService.ts b/packages/openid4vc-client/src/OpenId4VcClientService.ts new file mode 100644 index 0000000000..629e1d589f --- /dev/null +++ b/packages/openid4vc-client/src/OpenId4VcClientService.ts @@ -0,0 +1,320 @@ +import type { AgentContext } from '@aries-framework/core' +import type { AccessTokenResponse, EndpointMetadata, Jwt } from '@sphereon/openid4vci-client' + +import { + AriesFrameworkError, + DidsApi, + getKeyFromVerificationMethod, + Hasher, + inject, + injectable, + InjectionSymbols, + isJwtAlgorithm, + JsonEncoder, + JsonTransformer, + JwsService, + jwtKeyAlgMapping, + Logger, + TypedArrayEncoder, + W3cCredentialService, + W3cVerifiableCredential, +} from '@aries-framework/core' +import { + Alg, + AuthzFlowType, + CodeChallengeMethod, + CredentialRequestClientBuilder, + OpenID4VCIClient, + ProofOfPossessionBuilder, +} from '@sphereon/openid4vci-client' +import { randomStringForEntropy } from '@stablelib/random' + +export interface PreAuthCodeFlowOptions { + issuerUri: string + kid: string + verifyRevocationState: boolean +} + +export interface AuthCodeFlowOptions extends PreAuthCodeFlowOptions { + clientId: string + authorizationCode: string + codeVerifier: string + redirectUri: string +} + +export enum AuthFlowType { + AuthorizationCodeFlow, + PreAuthorizedCodeFlow, +} + +export type RequestCredentialOptions = { flowType: AuthFlowType } & PreAuthCodeFlowOptions & + Partial + +// The code_challenge_method is omitted here +// because we assume it will always be SHA256 +// as clear text code_challenges are unsafe +export interface GenerateAuthorizationUrlOptions { + initiationUri: string + clientId: string + redirectUri: string + scope?: string[] +} + +@injectable() +export class OpenId4VcClientService { + private logger: Logger + private w3cCredentialService: W3cCredentialService + private jwsService: JwsService + + public constructor( + @inject(InjectionSymbols.Logger) logger: Logger, + w3cCredentialService: W3cCredentialService, + jwsService: JwsService + ) { + this.w3cCredentialService = w3cCredentialService + this.jwsService = jwsService + this.logger = logger + } + + private signCallback(agentContext: AgentContext) { + return async (jwt: Jwt, kid: string) => { + if (!jwt.header) { + throw new AriesFrameworkError('No header present on JWT') + } + + if (!jwt.payload) { + throw new AriesFrameworkError('No payload present on JWT') + } + if (!kid.startsWith('did:')) { + throw new AriesFrameworkError(`kid '${kid}' is not a valid did. Only dids are supported as kid.`) + } + + if (!kid.includes('#')) { + throw new AriesFrameworkError( + `kid '${kid}' does not include a reference to the verificationMethod. The kid must specify a specific verificationMethod within the did document .` + ) + } + + const did = kid.split('#')[0] + + const didsApi = agentContext.dependencyManager.resolve(DidsApi) + const [didRecord] = await didsApi.getCreatedDids({ did }) + + if (!didRecord) { + throw new AriesFrameworkError(`No did record found for did ${did}. Is the did created by this agent?`) + } + + const didResult = await didsApi.resolve(did) + + if (!didResult.didDocument) { + throw new AriesFrameworkError( + `No did document found for did ${did}. ${didResult.didResolutionMetadata.error} - ${didResult.didResolutionMetadata.message}` + ) + } + + // TODO: which purposes are allowed? + const verificationMethod = didResult.didDocument.dereferenceKey(kid, ['authentication']) + const key = getKeyFromVerificationMethod(verificationMethod) + + const payload = JsonEncoder.toBuffer(jwt.payload) + + if (!isJwtAlgorithm(jwt.header.alg)) { + throw new AriesFrameworkError(`Unknown JWT algorithm: ${jwt.header.alg}`) + } + + if (jwtKeyAlgMapping[jwt.header.alg].includes(key.keyType)) { + throw new AriesFrameworkError( + `The retreived key's type does't match the JWT algorithm. Key type: ${key.keyType}, JWT algorithm: ${jwt.header.alg}` + ) + } + + const jws = await this.jwsService.createJwsCompact(agentContext, { + key, + payload, + protectedHeaderOptions: { + alg: jwt.header.alg, + kid: jwt.header.kid, + }, + }) + + return jws + } + } + + private getSignCallback(agentContext: AgentContext) { + return { + signCallback: this.signCallback(agentContext), + } + } + + private assertCredentialHasFormat(format: string, scope: string, metadata: EndpointMetadata) { + if (!metadata.openid4vci_metadata) { + throw new AriesFrameworkError( + `Server metadata doesn't include OpenID4VCI metadata. Unable to verify if the issuer supports the requested credential format: ${format}` + ) + } + + const supportedFomats = Object.keys(metadata.openid4vci_metadata?.credentials_supported[scope].formats) + + if (!supportedFomats.includes(format)) { + throw new AriesFrameworkError( + `Issuer doesn't support the requested credential format '${format}'' for requested credential type '${scope}'. Supported formats are: ${supportedFomats}` + ) + } + } + + private generateCodeVerifier(): string { + return randomStringForEntropy(256) + } + + public async generateAuthorizationUrl(options: GenerateAuthorizationUrlOptions) { + this.logger.debug('Generating authorization url') + + if (!options.scope || options.scope.length === 0) { + throw new AriesFrameworkError( + 'Only scoped based authorization requests are supported at this time. Please provide at least one scope' + ) + } + + const client = await OpenID4VCIClient.initiateFromURI({ + issuanceInitiationURI: options.initiationUri, + flowType: AuthzFlowType.AUTHORIZATION_CODE_FLOW, + }) + const codeVerifier = this.generateCodeVerifier() + const codeVerifierSha256 = Hasher.hash(TypedArrayEncoder.fromString(codeVerifier), 'sha2-256') + const base64Url = TypedArrayEncoder.toBase64URL(codeVerifierSha256) + + this.logger.debug('Converted code_verifier to code_challenge', { + codeVerifier: codeVerifier, + sha256: codeVerifierSha256.toString(), + base64Url: base64Url, + }) + + const authorizationUrl = client.createAuthorizationRequestUrl({ + clientId: options.clientId, + codeChallengeMethod: CodeChallengeMethod.SHA256, + codeChallenge: base64Url, + redirectUri: options.redirectUri, + scope: options.scope?.join(' '), + }) + + return { + authorizationUrl, + codeVerifier, + } + } + + public async requestCredential(agentContext: AgentContext, options: RequestCredentialOptions) { + const credentialFormat = 'ldp_vc' + + let flowType: AuthzFlowType + if (options.flowType === AuthFlowType.AuthorizationCodeFlow) { + flowType = AuthzFlowType.AUTHORIZATION_CODE_FLOW + } else if (options.flowType === AuthFlowType.PreAuthorizedCodeFlow) { + flowType = AuthzFlowType.PRE_AUTHORIZED_CODE_FLOW + } else { + throw new AriesFrameworkError( + `Unsupported flowType ${options.flowType}. Valid values are ${Object.values(AuthFlowType)}` + ) + } + + const client = await OpenID4VCIClient.initiateFromURI({ + issuanceInitiationURI: options.issuerUri, + flowType, + kid: options.kid, + alg: Alg.EdDSA, + }) + + let accessToken: AccessTokenResponse + + if (options.flowType === AuthFlowType.AuthorizationCodeFlow) { + if (!options.authorizationCode) + throw new AriesFrameworkError( + `The 'authorizationCode' parameter is required when 'flowType' is ${options.flowType}` + ) + if (!options.codeVerifier) + throw new AriesFrameworkError(`The 'codeVerifier' parameter is required when 'flowType' is ${options.flowType}`) + if (!options.redirectUri) + throw new AriesFrameworkError(`The 'redirectUri' parameter is required when 'flowType' is ${options.flowType}`) + + accessToken = await client.acquireAccessToken({ + clientId: options.clientId, + code: options.authorizationCode, + codeVerifier: options.codeVerifier, + redirectUri: options.redirectUri, + }) + } else { + accessToken = await client.acquireAccessToken({}) + } + + const serverMetadata = await client.retrieveServerMetadata() + + this.logger.info('Fetched server metadata', { + issuer: serverMetadata.issuer, + credentialEndpoint: serverMetadata.credential_endpoint, + tokenEndpoint: serverMetadata.token_endpoint, + }) + + this.logger.debug('Full server metadata', serverMetadata) + + if (accessToken.scope) { + for (const credentialType of accessToken.scope.split(' ')) { + this.assertCredentialHasFormat(credentialFormat, credentialType, serverMetadata) + } + } + + // proof of possession + const callbacks = this.getSignCallback(agentContext) + + const proofInput = await ProofOfPossessionBuilder.fromAccessTokenResponse({ + accessTokenResponse: accessToken, + callbacks: callbacks, + }) + .withEndpointMetadata(serverMetadata) + .withAlg(Alg.EdDSA) + .withKid(options.kid) + .build() + + this.logger.debug('Generated JWS', proofInput) + + const credentialRequestClient = CredentialRequestClientBuilder.fromIssuanceInitiationURI({ + uri: options.issuerUri, + metadata: serverMetadata, + }) + .withTokenFromResponse(accessToken) + .build() + + const credentialResponse = await credentialRequestClient.acquireCredentialsUsingProof({ + proofInput, + credentialType: accessToken.scope, + format: credentialFormat, + }) + + this.logger.debug('Credential request response', credentialResponse) + + if (!credentialResponse.successBody) { + throw new AriesFrameworkError('Did not receive a successful credential response') + } + + const credential = JsonTransformer.fromJSON(credentialResponse.successBody.credential, W3cVerifiableCredential) + + // verify the signature + const result = await this.w3cCredentialService.verifyCredential(agentContext, { + credential, + verifyRevocationState: options.verifyRevocationState, + }) + + if (result && !result.verified) { + throw new AriesFrameworkError(`Failed to validate credential, error = ${result.error}`) + } + + const storedCredential = await this.w3cCredentialService.storeCredential(agentContext, { + credential, + }) + + this.logger.info(`Stored credential with id: ${storedCredential.id}`) + this.logger.debug('Full credential', storedCredential) + + return storedCredential + } +} diff --git a/packages/openid4vc-client/src/__tests__/OpenId4VcClientModule.test.ts b/packages/openid4vc-client/src/__tests__/OpenId4VcClientModule.test.ts new file mode 100644 index 0000000000..b02fa08ffc --- /dev/null +++ b/packages/openid4vc-client/src/__tests__/OpenId4VcClientModule.test.ts @@ -0,0 +1,24 @@ +import type { DependencyManager } from '@aries-framework/core' + +import { OpenId4VcClientApi } from '../OpenId4VcClientApi' +import { OpenId4VcClientModule } from '../OpenId4VcClientModule' +import { OpenId4VcClientService } from '../OpenId4VcClientService' + +const dependencyManager = { + registerInstance: jest.fn(), + registerSingleton: jest.fn(), + registerContextScoped: jest.fn(), +} as unknown as DependencyManager + +describe('OpenId4VcClientModule', () => { + test('registers dependencies on the dependency manager', () => { + const openId4VcClientModule = new OpenId4VcClientModule() + openId4VcClientModule.register(dependencyManager) + + expect(dependencyManager.registerContextScoped).toHaveBeenCalledTimes(1) + expect(dependencyManager.registerContextScoped).toHaveBeenCalledWith(OpenId4VcClientApi) + + expect(dependencyManager.registerSingleton).toHaveBeenCalledTimes(1) + expect(dependencyManager.registerSingleton).toHaveBeenCalledWith(OpenId4VcClientService) + }) +}) diff --git a/packages/openid4vc-client/src/index.ts b/packages/openid4vc-client/src/index.ts new file mode 100644 index 0000000000..3200b8fa6d --- /dev/null +++ b/packages/openid4vc-client/src/index.ts @@ -0,0 +1,3 @@ +export * from './OpenId4VcClientModule' +export * from './OpenId4VcClientApi' +export * from './OpenId4VcClientService' diff --git a/packages/openid4vc-client/tests/fixtures.ts b/packages/openid4vc-client/tests/fixtures.ts new file mode 100644 index 0000000000..e739859415 --- /dev/null +++ b/packages/openid4vc-client/tests/fixtures.ts @@ -0,0 +1,134 @@ +export const getMetadataResponse = { + authorization_endpoint: 'https://launchpad.vii.electron.mattrlabs.io/oidc/v1/auth/authorize', + token_endpoint: 'https://launchpad.vii.electron.mattrlabs.io/oidc/v1/auth/token', + jwks_uri: 'https://launchpad.vii.electron.mattrlabs.io/oidc/v1/auth/jwks', + token_endpoint_auth_methods_supported: [ + 'none', + 'client_secret_basic', + 'client_secret_jwt', + 'client_secret_post', + 'private_key_jwt', + ], + code_challenge_methods_supported: ['S256'], + grant_types_supported: ['authorization_code', 'urn:ietf:params:oauth:grant-type:pre-authorized_code'], + response_modes_supported: ['form_post', 'fragment', 'query'], + response_types_supported: ['code id_token', 'code', 'id_token', 'none'], + scopes_supported: ['OpenBadgeCredential', 'AcademicAward', 'LearnerProfile', 'PermanentResidentCard'], + token_endpoint_auth_signing_alg_values_supported: ['HS256', 'RS256', 'PS256', 'ES256', 'EdDSA'], + credential_endpoint: 'https://launchpad.vii.electron.mattrlabs.io/oidc/v1/auth/credential', + credentials_supported: { + OpenBadgeCredential: { + formats: { + ldp_vc: { + name: 'JFF x vc-edu PlugFest 2', + description: "MATTR's submission for JFF Plugfest 2", + types: ['OpenBadgeCredential'], + binding_methods_supported: ['did'], + cryptographic_suites_supported: ['Ed25519Signature2018'], + }, + }, + }, + AcademicAward: { + formats: { + ldp_vc: { + name: 'Example Academic Award', + description: 'Microcredential from the MyCreds Network.', + types: ['AcademicAward'], + binding_methods_supported: ['did'], + cryptographic_suites_supported: ['Ed25519Signature2018'], + }, + }, + }, + LearnerProfile: { + formats: { + ldp_vc: { + name: 'Digitary Learner Profile', + description: 'Example', + types: ['LearnerProfile'], + binding_methods_supported: ['did'], + cryptographic_suites_supported: ['Ed25519Signature2018'], + }, + }, + }, + PermanentResidentCard: { + formats: { + ldp_vc: { + name: 'Permanent Resident Card', + description: 'Government of Kakapo', + types: ['PermanentResidentCard'], + binding_methods_supported: ['did'], + cryptographic_suites_supported: ['Ed25519Signature2018'], + }, + }, + }, + }, +} + +export const acquireAccessTokenResponse = { + access_token: '7nikUotMQefxn7oRX56R7MDNE7KJTGfwGjOkHzGaUIG', + expires_in: 3600, + scope: 'OpenBadgeCredential', + token_type: 'Bearer', +} + +export const credentialRequestResponse = { + format: 'w3cvc-jsonld', + credential: { + type: ['VerifiableCredential', 'VerifiableCredentialExtension', 'OpenBadgeCredential'], + issuer: { + id: 'did:web:launchpad.vii.electron.mattrlabs.io', + name: 'Jobs for the Future (JFF)', + iconUrl: 'https://w3c-ccg.github.io/vc-ed/plugfest-1-2022/images/JFF_LogoLockup.png', + image: 'https://w3c-ccg.github.io/vc-ed/plugfest-1-2022/images/JFF_LogoLockup.png', + }, + name: 'JFF x vc-edu PlugFest 2', + description: "MATTR's submission for JFF Plugfest 2", + credentialBranding: { + backgroundColor: '#464c49', + }, + issuanceDate: '2023-01-25T16:58:06.292Z', + credentialSubject: { + id: 'did:key:z6MkpGR4gs4Rc3Zph4vj8wRnjnAxgAPSxcR8MAVKutWspQzc', + type: ['AchievementSubject'], + achievement: { + id: 'urn:uuid:bd6d9316-f7ae-4073-a1e5-2f7f5bd22922', + name: 'JFF x vc-edu PlugFest 2 Interoperability', + type: ['Achievement'], + image: { + id: 'https://w3c-ccg.github.io/vc-ed/plugfest-2-2022/images/JFF-VC-EDU-PLUGFEST2-badge-image.png', + type: 'Image', + }, + criteria: { + type: 'Criteria', + narrative: + 'Solutions providers earned this badge by demonstrating interoperability between multiple providers based on the OBv3 candidate final standard, with some additional required fields. Credential issuers earning this badge successfully issued a credential into at least two wallets. Wallet implementers earning this badge successfully displayed credentials issued by at least two different credential issuers.', + }, + description: + 'This credential solution supports the use of OBv3 and w3c Verifiable Credentials and is interoperable with at least two other solutions. This was demonstrated successfully during JFF x vc-edu PlugFest 2.', + }, + }, + '@context': [ + 'https://www.w3.org/2018/credentials/v1', + { + '@vocab': 'https://w3id.org/security/undefinedTerm#', + }, + 'https://mattr.global/contexts/vc-extensions/v1', + 'https://purl.imsglobal.org/spec/ob/v3p0/context.json', + 'https://w3c-ccg.github.io/vc-status-rl-2020/contexts/vc-revocation-list-2020/v1.jsonld', + ], + credentialStatus: { + id: 'https://launchpad.vii.electron.mattrlabs.io/core/v1/revocation-lists/b4aa46a0-5539-4a6b-aa03-8f6791c22ce3#49', + type: 'RevocationList2020Status', + revocationListIndex: '49', + revocationListCredential: + 'https://launchpad.vii.electron.mattrlabs.io/core/v1/revocation-lists/b4aa46a0-5539-4a6b-aa03-8f6791c22ce3', + }, + proof: { + type: 'Ed25519Signature2018', + created: '2023-01-25T16:58:07Z', + jws: 'eyJhbGciOiJFZERTQSIsImI2NCI6ZmFsc2UsImNyaXQiOlsiYjY0Il19..PrpRKt60yXOzMNiQY5bELX40F6Svwm-FyQ-Jv02VJDfTTH8GPPByjtOb_n3YfWidQVgySfGQ_H7VmCGjvsU6Aw', + proofPurpose: 'assertionMethod', + verificationMethod: 'did:web:launchpad.vii.electron.mattrlabs.io#6BhFMCGTJg', + }, + }, +} diff --git a/packages/openid4vc-client/tests/openid4vc-client.e2e.test.ts b/packages/openid4vc-client/tests/openid4vc-client.e2e.test.ts new file mode 100644 index 0000000000..58716ab557 --- /dev/null +++ b/packages/openid4vc-client/tests/openid4vc-client.e2e.test.ts @@ -0,0 +1,243 @@ +import type { KeyDidCreateOptions } from '@aries-framework/core' + +import { Agent, KeyType, TypedArrayEncoder, W3cCredentialRecord, W3cCredentialsModule } from '@aries-framework/core' +import nock, { cleanAll, enableNetConnect } from 'nock' + +import { didKeyToInstanceOfKey } from '../../core/src/modules/dids/helpers' +import { customDocumentLoader } from '../../core/src/modules/vc/__tests__/documentLoader' +import { getAgentOptions, indySdk } from '../../core/tests' +import { IndySdkModule } from '../../indy-sdk/src' + +import { acquireAccessTokenResponse, credentialRequestResponse, getMetadataResponse } from './fixtures' + +import { OpenId4VcClientModule } from '@aries-framework/openid4vc-client' + +const modules = { + openId4VcClient: new OpenId4VcClientModule(), + w3cCredentials: new W3cCredentialsModule({ + documentLoader: customDocumentLoader, + }), + indySdk: new IndySdkModule({ + indySdk, + }), +} + +describe('OpenId4VcClient', () => { + let agent: Agent + + beforeEach(async () => { + const agentOptions = getAgentOptions('OpenId4VcClient Agent', {}, modules) + + agent = new Agent(agentOptions) + + await agent.initialize() + }) + + afterEach(async () => { + await agent.shutdown() + await agent.wallet.delete() + }) + + describe('Pre-authorized flow', () => { + const issuerUri = + 'openid-initiate-issuance://?issuer=https://launchpad.mattrlabs.com&credential_type=OpenBadgeCredential&pre-authorized_code=krBcsBIlye2T-G4-rHHnRZUCah9uzDKwohJK6ABNvL-' + beforeAll(async () => { + /** + * Below we're setting up some mock HTTP responses. + * These responses are based on the openid-initiate-issuance URI above + * */ + + // setup temporary redirect mock + nock('https://launchpad.mattrlabs.com').get('/.well-known/openid-credential-issuer').reply(307, undefined, { + Location: 'https://launchpad.vii.electron.mattrlabs.io/.well-known/openid-credential-issuer', + }) + + // setup server metadata response + const httpMock = nock('https://launchpad.vii.electron.mattrlabs.io') + .get('/.well-known/openid-credential-issuer') + .reply(200, getMetadataResponse) + + // setup access token response + httpMock.post('/oidc/v1/auth/token').reply(200, acquireAccessTokenResponse) + + // setup credential request response + httpMock.post('/oidc/v1/auth/credential').reply(200, credentialRequestResponse) + }) + + afterAll(async () => { + cleanAll() + enableNetConnect() + }) + + it('Should successfully execute the pre-authorized flow', async () => { + const did = await agent.dids.create({ + method: 'key', + options: { + keyType: KeyType.Ed25519, + }, + secret: { + privateKey: TypedArrayEncoder.fromString('96213c3d7fc8d4d6754c7a0fd969598e'), + }, + }) + + const keyInstance = didKeyToInstanceOfKey(did.didState.did as string) + + const kid = `${did.didState.did as string}#${keyInstance.fingerprint as string}` + + const w3cCredentialRecord = await agent.modules.openId4VcClient.requestCredentialUsingPreAuthorizedCode({ + issuerUri, + kid, + verifyRevocationState: false, + }) + + expect(w3cCredentialRecord).toBeInstanceOf(W3cCredentialRecord) + + expect(w3cCredentialRecord.credential.type).toEqual([ + 'VerifiableCredential', + 'VerifiableCredentialExtension', + 'OpenBadgeCredential', + ]) + + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + expect(w3cCredentialRecord.credential.credentialSubject.id).toEqual(did.didState.did) + }) + }) + describe('Authorization flow', () => { + beforeAll(async () => { + /** + * Below we're setting up some mock HTTP responses. + * These responses are based on the openid-initiate-issuance URI above + * */ + + // setup temporary redirect mock + nock('https://launchpad.mattrlabs.com').get('/.well-known/openid-credential-issuer').reply(307, undefined, { + Location: 'https://launchpad.vii.electron.mattrlabs.io/.well-known/openid-credential-issuer', + }) + + // setup server metadata response + const httpMock = nock('https://launchpad.vii.electron.mattrlabs.io') + .get('/.well-known/openid-credential-issuer') + .reply(200, getMetadataResponse) + + // setup access token response + httpMock.post('/oidc/v1/auth/token').reply(200, acquireAccessTokenResponse) + + // setup credential request response + httpMock.post('/oidc/v1/auth/credential').reply(200, credentialRequestResponse) + }) + + afterAll(async () => { + cleanAll() + enableNetConnect() + }) + + it('should generate a valid authorization url', async () => { + const clientId = 'test-client' + + const redirectUri = 'https://example.com/cb' + const scope = ['TestCredential'] + const initiationUri = + 'openid-initiate-issuance://?issuer=https://launchpad.mattrlabs.com&credential_type=OpenBadgeCredential' + const { authorizationUrl } = await agent.modules.openId4VcClient.generateAuthorizationUrl({ + clientId, + redirectUri, + scope, + initiationUri, + }) + + const parsedUrl = new URL(authorizationUrl) + expect(authorizationUrl.startsWith('https://launchpad.vii.electron.mattrlabs.io/oidc/v1/auth/authorize')).toBe( + true + ) + expect(parsedUrl.searchParams.get('response_type')).toBe('code') + expect(parsedUrl.searchParams.get('client_id')).toBe(clientId) + expect(parsedUrl.searchParams.get('code_challenge_method')).toBe('S256') + expect(parsedUrl.searchParams.get('redirect_uri')).toBe(redirectUri) + }) + it('should throw if no scope is provided', async () => { + // setup temporary redirect mock + nock('https://launchpad.mattrlabs.com').get('/.well-known/openid-credential-issuer').reply(307, undefined, { + Location: 'https://launchpad.vii.electron.mattrlabs.io/.well-known/openid-credential-issuer', + }) + + // setup server metadata response + nock('https://launchpad.vii.electron.mattrlabs.io') + .get('/.well-known/openid-credential-issuer') + .reply(200, getMetadataResponse) + + const clientId = 'test-client' + const redirectUri = 'https://example.com/cb' + const initiationUri = + 'openid-initiate-issuance://?issuer=https://launchpad.mattrlabs.com&credential_type=OpenBadgeCredential' + expect( + agent.modules.openId4VcClient.generateAuthorizationUrl({ + clientId, + redirectUri, + scope: [], + initiationUri, + }) + ).rejects.toThrow() + }) + it('should successfully execute request a credential', async () => { + // setup temporary redirect mock + nock('https://launchpad.mattrlabs.com').get('/.well-known/openid-credential-issuer').reply(307, undefined, { + Location: 'https://launchpad.vii.electron.mattrlabs.io/.well-known/openid-credential-issuer', + }) + + // setup server metadata response + nock('https://launchpad.vii.electron.mattrlabs.io') + .get('/.well-known/openid-credential-issuer') + .reply(200, getMetadataResponse) + + const did = await agent.dids.create({ + method: 'key', + options: { + keyType: KeyType.Ed25519, + }, + secret: { + privateKey: TypedArrayEncoder.fromString('96213c3d7fc8d4d6754c7a0fd969598e'), + }, + }) + + const keyInstance = didKeyToInstanceOfKey(did.didState.did as string) + + const kid = `${did.didState.did as string}#${keyInstance.fingerprint as string}` + + const clientId = 'test-client' + + const redirectUri = 'https://example.com/cb' + const initiationUri = + 'openid-initiate-issuance://?issuer=https://launchpad.mattrlabs.com&credential_type=OpenBadgeCredential' + + const scope = ['TestCredential'] + const { codeVerifier } = await agent.modules.openId4VcClient.generateAuthorizationUrl({ + clientId, + redirectUri, + scope, + initiationUri, + }) + const w3cCredentialRecord = await agent.modules.openId4VcClient.requestCredentialUsingAuthorizationCode({ + clientId: clientId, + authorizationCode: 'test-code', + codeVerifier: codeVerifier, + verifyRevocationState: false, + kid: kid, + issuerUri: initiationUri, + redirectUri: redirectUri, + }) + + expect(w3cCredentialRecord).toBeInstanceOf(W3cCredentialRecord) + + expect(w3cCredentialRecord.credential.type).toEqual([ + 'VerifiableCredential', + 'VerifiableCredentialExtension', + 'OpenBadgeCredential', + ]) + + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + expect(w3cCredentialRecord.credential.credentialSubject.id).toEqual(did.didState.did) + }) + }) +}) diff --git a/packages/openid4vc-client/tests/setup.ts b/packages/openid4vc-client/tests/setup.ts new file mode 100644 index 0000000000..34e38c9705 --- /dev/null +++ b/packages/openid4vc-client/tests/setup.ts @@ -0,0 +1 @@ +jest.setTimeout(120000) diff --git a/packages/openid4vc-client/tsconfig.build.json b/packages/openid4vc-client/tsconfig.build.json new file mode 100644 index 0000000000..2b075bbd85 --- /dev/null +++ b/packages/openid4vc-client/tsconfig.build.json @@ -0,0 +1,8 @@ +{ + "extends": "../../tsconfig.build.json", + "compilerOptions": { + "outDir": "./build", + "skipLibCheck": true + }, + "include": ["src/**/*"] +} diff --git a/packages/openid4vc-client/tsconfig.json b/packages/openid4vc-client/tsconfig.json new file mode 100644 index 0000000000..c1aca0e050 --- /dev/null +++ b/packages/openid4vc-client/tsconfig.json @@ -0,0 +1,7 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "types": ["jest"], + "skipLibCheck": true + } +} diff --git a/packages/question-answer/CHANGELOG.md b/packages/question-answer/CHANGELOG.md index 216388c0ea..dc3f65cf84 100644 --- a/packages/question-answer/CHANGELOG.md +++ b/packages/question-answer/CHANGELOG.md @@ -3,6 +3,16 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [0.3.3](https://github.com/hyperledger/aries-framework-javascript/compare/v0.3.2...v0.3.3) (2023-01-18) + +### Bug Fixes + +- fix typing issues with typescript 4.9 ([#1214](https://github.com/hyperledger/aries-framework-javascript/issues/1214)) ([087980f](https://github.com/hyperledger/aries-framework-javascript/commit/087980f1adf3ee0bc434ca9782243a62c6124444)) + +### Features + +- **indy-sdk:** add indy-sdk package ([#1200](https://github.com/hyperledger/aries-framework-javascript/issues/1200)) ([9933b35](https://github.com/hyperledger/aries-framework-javascript/commit/9933b35a6aa4524caef8a885e71b742cd0d7186b)) + ## [0.3.2](https://github.com/hyperledger/aries-framework-javascript/compare/v0.3.1...v0.3.2) (2023-01-04) **Note:** Version bump only for package @aries-framework/question-answer diff --git a/packages/question-answer/README.md b/packages/question-answer/README.md index 00ebca6637..33d1b17750 100644 --- a/packages/question-answer/README.md +++ b/packages/question-answer/README.md @@ -6,7 +6,7 @@ height="250px" />

-

Aries Framework JavaScript Question Answer Plugin

+

Aries Framework JavaScript Question Answer Module


-Question Answer plugin for [Aries Framework JavaScript](https://github.com/hyperledger/aries-framework-javascript.git). Implements [Aries RFC 0113](https://github.com/hyperledger/aries-rfcs/blob/1795d5c2d36f664f88f5e8045042ace8e573808c/features/0113-question-answer/README.md). +Question Answer module for [Aries Framework JavaScript](https://github.com/hyperledger/aries-framework-javascript.git). Implements [Aries RFC 0113](https://github.com/hyperledger/aries-rfcs/blob/1795d5c2d36f664f88f5e8045042ace8e573808c/features/0113-question-answer/README.md). ### Installation @@ -38,7 +38,7 @@ Make sure you have set up the correct version of Aries Framework JavaScript acco npm info "@aries-framework/question-answer" peerDependencies ``` -Then add the question-answer plugin to your project. +Then add the question-answer module to your project. ```sh yarn add @aries-framework/question-answer @@ -46,7 +46,7 @@ yarn add @aries-framework/question-answer ### Quick start -In order for this plugin to work, we have to inject it into the agent to access agent functionality. See the example for more information. +In order for this module to work, we have to inject it into the agent to access agent functionality. See the example for more information. ### Example of usage diff --git a/packages/question-answer/jest.config.ts b/packages/question-answer/jest.config.ts index 55c67d70a6..93c0197296 100644 --- a/packages/question-answer/jest.config.ts +++ b/packages/question-answer/jest.config.ts @@ -6,7 +6,6 @@ import packageJson from './package.json' const config: Config.InitialOptions = { ...base, - name: packageJson.name, displayName: packageJson.name, setupFilesAfterEnv: ['./tests/setup.ts'], } diff --git a/packages/question-answer/package.json b/packages/question-answer/package.json index 623417af88..9c00199122 100644 --- a/packages/question-answer/package.json +++ b/packages/question-answer/package.json @@ -2,7 +2,7 @@ "name": "@aries-framework/question-answer", "main": "build/index", "types": "build/index", - "version": "0.3.2", + "version": "0.3.3", "files": [ "build" ], @@ -18,24 +18,21 @@ }, "scripts": { "build": "yarn run clean && yarn run compile", - "clean": "rimraf -rf ./build", + "clean": "rimraf ./build", "compile": "tsc -p tsconfig.build.json", "prepublishOnly": "yarn run build", "test": "jest" }, "dependencies": { + "@aries-framework/core": "0.3.3", "class-transformer": "0.5.1", - "class-validator": "0.13.1", + "class-validator": "0.14.0", "rxjs": "^7.2.0" }, - "peerDependencies": { - "@aries-framework/core": "0.2.5" - }, "devDependencies": { - "@aries-framework/core": "0.3.2", - "@aries-framework/node": "0.3.2", + "@aries-framework/node": "0.3.3", "reflect-metadata": "^0.1.13", - "rimraf": "~3.0.2", - "typescript": "~4.3.0" + "rimraf": "^4.4.0", + "typescript": "~4.9.5" } } diff --git a/packages/question-answer/src/QuestionAnswerApi.ts b/packages/question-answer/src/QuestionAnswerApi.ts index 6fed567575..97ea98c143 100644 --- a/packages/question-answer/src/QuestionAnswerApi.ts +++ b/packages/question-answer/src/QuestionAnswerApi.ts @@ -5,7 +5,6 @@ import { AgentContext, ConnectionService, OutboundMessageContext, - Dispatcher, injectable, MessageSender, } from '@aries-framework/core' @@ -22,7 +21,6 @@ export class QuestionAnswerApi { private agentContext: AgentContext public constructor( - dispatcher: Dispatcher, questionAnswerService: QuestionAnswerService, messageSender: MessageSender, connectionService: ConnectionService, @@ -32,7 +30,11 @@ export class QuestionAnswerApi { this.messageSender = messageSender this.connectionService = connectionService this.agentContext = agentContext - this.registerMessageHandlers(dispatcher) + + this.agentContext.dependencyManager.registerMessageHandlers([ + new QuestionMessageHandler(this.questionAnswerService), + new AnswerMessageHandler(this.questionAnswerService), + ]) } /** @@ -131,9 +133,4 @@ export class QuestionAnswerApi { public findById(questionAnswerId: string) { return this.questionAnswerService.findById(this.agentContext, questionAnswerId) } - - private registerMessageHandlers(dispatcher: Dispatcher) { - dispatcher.registerMessageHandler(new QuestionMessageHandler(this.questionAnswerService)) - dispatcher.registerMessageHandler(new AnswerMessageHandler(this.questionAnswerService)) - } } diff --git a/packages/question-answer/src/__tests__/QuestionAnswerService.test.ts b/packages/question-answer/src/__tests__/QuestionAnswerService.test.ts index 479b9bd290..74f80ec4ff 100644 --- a/packages/question-answer/src/__tests__/QuestionAnswerService.test.ts +++ b/packages/question-answer/src/__tests__/QuestionAnswerService.test.ts @@ -1,17 +1,13 @@ -import type { AgentConfig, AgentContext, Repository } from '@aries-framework/core' +import type { AgentConfig, AgentContext, Repository, Wallet } from '@aries-framework/core' import type { QuestionAnswerStateChangedEvent, ValidResponse } from '@aries-framework/question-answer' -import { - EventEmitter, - IndyWallet, - KeyProviderRegistry, - InboundMessageContext, - DidExchangeState, -} from '@aries-framework/core' +import { EventEmitter, InboundMessageContext, DidExchangeState, KeyProviderRegistry } from '@aries-framework/core' import { agentDependencies } from '@aries-framework/node' import { Subject } from 'rxjs' import { getAgentConfig, getAgentContext, getMockConnection, mockFunction } from '../../../core/tests/helpers' +import { IndySdkWallet } from '../../../indy-sdk/src' +import { indySdk } from '../../../indy-sdk/tests/setupIndySdkModule' import { QuestionAnswerRecord, @@ -34,7 +30,7 @@ describe('QuestionAnswerService', () => { state: DidExchangeState.Completed, }) - let wallet: IndyWallet + let wallet: Wallet let agentConfig: AgentConfig let questionAnswerRepository: Repository let questionAnswerService: QuestionAnswerService @@ -65,7 +61,7 @@ describe('QuestionAnswerService', () => { beforeAll(async () => { agentConfig = getAgentConfig('QuestionAnswerServiceTest') - wallet = new IndyWallet(agentConfig.agentDependencies, agentConfig.logger, new KeyProviderRegistry([])) + wallet = new IndySdkWallet(indySdk, agentConfig.logger, new KeyProviderRegistry([])) agentContext = getAgentContext() // eslint-disable-next-line @typescript-eslint/no-non-null-assertion await wallet.createAndOpen(agentConfig.walletConfig!) diff --git a/packages/question-answer/src/services/QuestionAnswerService.ts b/packages/question-answer/src/services/QuestionAnswerService.ts index c471ca3cca..5223ab520f 100644 --- a/packages/question-answer/src/services/QuestionAnswerService.ts +++ b/packages/question-answer/src/services/QuestionAnswerService.ts @@ -53,7 +53,7 @@ export class QuestionAnswerService { const questionAnswerRecord = await this.createRecord({ questionText: questionMessage.questionText, questionDetail: questionMessage.questionDetail, - threadId: questionMessage.id, + threadId: questionMessage.threadId, connectionId: connectionId, role: QuestionAnswerRole.Questioner, signatureRequired: false, @@ -97,7 +97,7 @@ export class QuestionAnswerService { questionText: questionMessage.questionText, questionDetail: questionMessage.questionDetail, connectionId: connection?.id, - threadId: questionMessage.id, + threadId: questionMessage.threadId, role: QuestionAnswerRole.Responder, signatureRequired: false, state: QuestionAnswerState.QuestionReceived, diff --git a/packages/question-answer/tests/question-answer.e2e.test.ts b/packages/question-answer/tests/question-answer.e2e.test.ts index e8d6d5a0c3..af3373de88 100644 --- a/packages/question-answer/tests/question-answer.e2e.test.ts +++ b/packages/question-answer/tests/question-answer.e2e.test.ts @@ -1,26 +1,27 @@ -import type { SubjectMessage } from '../../../tests/transport/SubjectInboundTransport' import type { ConnectionRecord } from '@aries-framework/core' import { Agent } from '@aries-framework/core' -import { Subject } from 'rxjs' -import { SubjectInboundTransport } from '../../../tests/transport/SubjectInboundTransport' -import { SubjectOutboundTransport } from '../../../tests/transport/SubjectOutboundTransport' -import { getAgentOptions, makeConnection } from '../../core/tests/helpers' -import testLogger from '../../core/tests/logger' +import { indySdk, setupSubjectTransports, testLogger, getAgentOptions, makeConnection } from '../../core/tests' +import { IndySdkModule } from '../../indy-sdk/src' import { waitForQuestionAnswerRecord } from './helpers' import { QuestionAnswerModule, QuestionAnswerRole, QuestionAnswerState } from '@aries-framework/question-answer' +const modules = { + questionAnswer: new QuestionAnswerModule(), + indySdk: new IndySdkModule({ + indySdk, + }), +} + const bobAgentOptions = getAgentOptions( 'Bob Question Answer', { endpoints: ['rxjs:bob'], }, - { - questionAnswer: new QuestionAnswerModule(), - } + modules ) const aliceAgentOptions = getAgentOptions( @@ -28,37 +29,20 @@ const aliceAgentOptions = getAgentOptions( { endpoints: ['rxjs:alice'], }, - { - questionAnswer: new QuestionAnswerModule(), - } + modules ) describe('Question Answer', () => { - let bobAgent: Agent<{ - questionAnswer: QuestionAnswerModule - }> - let aliceAgent: Agent<{ - questionAnswer: QuestionAnswerModule - }> + let bobAgent: Agent + let aliceAgent: Agent let aliceConnection: ConnectionRecord beforeEach(async () => { - const bobMessages = new Subject() - const aliceMessages = new Subject() - const subjectMap = { - 'rxjs:bob': bobMessages, - 'rxjs:alice': aliceMessages, - } - bobAgent = new Agent(bobAgentOptions) - bobAgent.registerInboundTransport(new SubjectInboundTransport(bobMessages)) - bobAgent.registerOutboundTransport(new SubjectOutboundTransport(subjectMap)) - await bobAgent.initialize() - aliceAgent = new Agent(aliceAgentOptions) + setupSubjectTransports([bobAgent, aliceAgent]) - aliceAgent.registerInboundTransport(new SubjectInboundTransport(aliceMessages)) - aliceAgent.registerOutboundTransport(new SubjectOutboundTransport(subjectMap)) + await bobAgent.initialize() await aliceAgent.initialize() ;[aliceConnection] = await makeConnection(aliceAgent, bobAgent) }) diff --git a/packages/question-answer/tests/setup.ts b/packages/question-answer/tests/setup.ts index 4955aeb601..78143033f2 100644 --- a/packages/question-answer/tests/setup.ts +++ b/packages/question-answer/tests/setup.ts @@ -1,3 +1,3 @@ import 'reflect-metadata' -jest.setTimeout(20000) +jest.setTimeout(120000) diff --git a/packages/react-native/CHANGELOG.md b/packages/react-native/CHANGELOG.md index 092f793c6d..c0ff907f14 100644 --- a/packages/react-native/CHANGELOG.md +++ b/packages/react-native/CHANGELOG.md @@ -3,6 +3,16 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [0.3.3](https://github.com/hyperledger/aries-framework-javascript/compare/v0.3.2...v0.3.3) (2023-01-18) + +### Bug Fixes + +- fix typing issues with typescript 4.9 ([#1214](https://github.com/hyperledger/aries-framework-javascript/issues/1214)) ([087980f](https://github.com/hyperledger/aries-framework-javascript/commit/087980f1adf3ee0bc434ca9782243a62c6124444)) + +### Features + +- **indy-sdk:** add indy-sdk package ([#1200](https://github.com/hyperledger/aries-framework-javascript/issues/1200)) ([9933b35](https://github.com/hyperledger/aries-framework-javascript/commit/9933b35a6aa4524caef8a885e71b742cd0d7186b)) + ## [0.3.2](https://github.com/hyperledger/aries-framework-javascript/compare/v0.3.1...v0.3.2) (2023-01-04) **Note:** Version bump only for package @aries-framework/react-native diff --git a/packages/react-native/jest.config.ts b/packages/react-native/jest.config.ts index 0e512f33f8..6426c5d8b8 100644 --- a/packages/react-native/jest.config.ts +++ b/packages/react-native/jest.config.ts @@ -6,7 +6,6 @@ import packageJson from './package.json' const config: Config.InitialOptions = { ...base, - name: packageJson.name, displayName: packageJson.name, moduleNameMapper: { ...base.moduleNameMapper, diff --git a/packages/react-native/package.json b/packages/react-native/package.json index dfef41e5d2..edc4b21683 100644 --- a/packages/react-native/package.json +++ b/packages/react-native/package.json @@ -2,7 +2,7 @@ "name": "@aries-framework/react-native", "main": "build/index", "types": "build/index", - "version": "0.3.2", + "version": "0.3.3", "files": [ "build" ], @@ -18,30 +18,26 @@ }, "scripts": { "build": "yarn run clean && yarn run compile", - "clean": "rimraf -rf ./build", + "clean": "rimraf ./build", "compile": "tsc -p tsconfig.build.json", "prepublishOnly": "yarn run build", "test": "jest" }, "dependencies": { - "@aries-framework/core": "0.3.2", - "@azure/core-asynciterator-polyfill": "^1.0.0", + "@aries-framework/core": "0.3.3", + "@azure/core-asynciterator-polyfill": "^1.0.2", "events": "^3.3.0" }, "devDependencies": { - "@types/indy-sdk-react-native": "npm:@types/indy-sdk@^1.16.21", - "@types/react-native": "^0.64.10", - "indy-sdk-react-native": "^0.3.0", - "react": "17.0.1", - "react-native": "0.64.2", - "react-native-fs": "^2.18.0", - "react-native-get-random-values": "^1.7.0", - "rimraf": "~3.0.2", - "typescript": "~4.3.0" + "react-native": "^0.71.4", + "react-native-fs": "^2.20.0", + "react-native-get-random-values": "^1.8.0", + "rimraf": "^4.4.0", + "typescript": "~4.9.5" }, "peerDependencies": { - "indy-sdk-react-native": "^0.3.0", - "react-native-fs": "^2.18.0", - "react-native-get-random-values": "^1.7.0" + "react-native": "^0.71.4", + "react-native-fs": "^2.20.0", + "react-native-get-random-values": "^1.8.0" } } diff --git a/packages/react-native/src/ReactNativeFileSystem.ts b/packages/react-native/src/ReactNativeFileSystem.ts index 331fa11a54..a14ba4bd40 100644 --- a/packages/react-native/src/ReactNativeFileSystem.ts +++ b/packages/react-native/src/ReactNativeFileSystem.ts @@ -1,26 +1,52 @@ -import type { FileSystem } from '@aries-framework/core' +import type { FileSystem, DownloadToFileOptions } from '@aries-framework/core' -import { getDirFromFilePath } from '@aries-framework/core' +import { TypedArrayEncoder, AriesFrameworkError, getDirFromFilePath, Buffer } from '@aries-framework/core' +import { Platform } from 'react-native' import * as RNFS from 'react-native-fs' export class ReactNativeFileSystem implements FileSystem { - public readonly basePath + public readonly dataPath + public readonly cachePath + public readonly tempPath /** * Create new ReactNativeFileSystem class instance. * - * @param basePath The base path to use for reading and writing files. RNFS.TemporaryDirectoryPath if not specified + * @param baseDataPath The base path to use for reading and writing data files used within the framework. + * Files will be created under baseDataPath/.afj directory. If not specified, it will be set to + * RNFS.DocumentDirectoryPath + * @param baseCachePath The base path to use for reading and writing cache files used within the framework. + * Files will be created under baseCachePath/.afj directory. If not specified, it will be set to + * RNFS.CachesDirectoryPath + * @param baseTempPath The base path to use for reading and writing temporary files within the framework. + * Files will be created under baseTempPath/.afj directory. If not specified, it will be set to + * RNFS.TemporaryDirectoryPath * * @see https://github.com/itinance/react-native-fs#constants */ - public constructor(basePath?: string) { - this.basePath = basePath ?? RNFS.TemporaryDirectoryPath + public constructor(options?: { baseDataPath?: string; baseCachePath?: string; baseTempPath?: string }) { + this.dataPath = `${options?.baseDataPath ?? RNFS.DocumentDirectoryPath}/.afj` + // In Android, TemporaryDirectoryPath falls back to CachesDirectoryPath + this.cachePath = options?.baseCachePath + ? `${options?.baseCachePath}/.afj` + : `${RNFS.CachesDirectoryPath}/.afj${Platform.OS === 'android' ? '/cache' : ''}` + this.tempPath = options?.baseTempPath + ? `${options?.baseTempPath}/.afj` + : `${RNFS.TemporaryDirectoryPath}/.afj${Platform.OS === 'android' ? '/temp' : ''}` } public async exists(path: string): Promise { return RNFS.exists(path) } + public async createDirectory(path: string): Promise { + await RNFS.mkdir(getDirFromFilePath(path)) + } + + public async copyFile(sourcePath: string, destinationPath: string): Promise { + await RNFS.copyFile(sourcePath, destinationPath) + } + public async write(path: string, data: string): Promise { // Make sure parent directories exist await RNFS.mkdir(getDirFromFilePath(path)) @@ -32,7 +58,11 @@ export class ReactNativeFileSystem implements FileSystem { return RNFS.readFile(path, 'utf8') } - public async downloadToFile(url: string, path: string) { + public async delete(path: string): Promise { + await RNFS.unlink(path) + } + + public async downloadToFile(url: string, path: string, options?: DownloadToFileOptions) { // Make sure parent directories exist await RNFS.mkdir(getDirFromFilePath(path)) @@ -42,5 +72,21 @@ export class ReactNativeFileSystem implements FileSystem { }) await promise + + if (options?.verifyHash) { + // RNFS returns hash as HEX + const fileHash = await RNFS.hash(path, options.verifyHash.algorithm) + const fileHashBuffer = Buffer.from(fileHash, 'hex') + + // If hash doesn't match, remove file and throw error + if (fileHashBuffer.compare(options.verifyHash.hash) !== 0) { + await RNFS.unlink(path) + throw new AriesFrameworkError( + `Hash of downloaded file does not match expected hash. Expected: ${TypedArrayEncoder.toBase58( + options.verifyHash.hash + )}, Actual: ${TypedArrayEncoder.toBase58(fileHashBuffer)}` + ) + } + } } } diff --git a/packages/react-native/src/index.ts b/packages/react-native/src/index.ts index 4b7e0a3eb9..ea76cafbe0 100644 --- a/packages/react-native/src/index.ts +++ b/packages/react-native/src/index.ts @@ -4,10 +4,6 @@ import '@azure/core-asynciterator-polyfill' import type { AgentDependencies } from '@aries-framework/core' import { EventEmitter } from 'events' -// Eslint complains indy-sdk-react-native has no default export -// But that's not true -// eslint-disable-next-line import/default -import indy from 'indy-sdk-react-native' import { ReactNativeFileSystem } from './ReactNativeFileSystem' @@ -19,7 +15,6 @@ const agentDependencies: AgentDependencies = { fetch, EventEmitterClass: EventEmitter, WebSocketClass: WebSocket, - indy, } export { agentDependencies } diff --git a/packages/tenants/CHANGELOG.md b/packages/tenants/CHANGELOG.md index a73651ba84..d3f33ffa92 100644 --- a/packages/tenants/CHANGELOG.md +++ b/packages/tenants/CHANGELOG.md @@ -3,6 +3,12 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [0.3.3](https://github.com/hyperledger/aries-framework-javascript/compare/v0.3.2...v0.3.3) (2023-01-18) + +### Bug Fixes + +- fix typing issues with typescript 4.9 ([#1214](https://github.com/hyperledger/aries-framework-javascript/issues/1214)) ([087980f](https://github.com/hyperledger/aries-framework-javascript/commit/087980f1adf3ee0bc434ca9782243a62c6124444)) + ## [0.3.2](https://github.com/hyperledger/aries-framework-javascript/compare/v0.3.1...v0.3.2) (2023-01-04) ### Bug Fixes diff --git a/packages/tenants/jest.config.ts b/packages/tenants/jest.config.ts index 55c67d70a6..93c0197296 100644 --- a/packages/tenants/jest.config.ts +++ b/packages/tenants/jest.config.ts @@ -6,7 +6,6 @@ import packageJson from './package.json' const config: Config.InitialOptions = { ...base, - name: packageJson.name, displayName: packageJson.name, setupFilesAfterEnv: ['./tests/setup.ts'], } diff --git a/packages/tenants/package.json b/packages/tenants/package.json index 191baf900f..24f8a859ab 100644 --- a/packages/tenants/package.json +++ b/packages/tenants/package.json @@ -2,7 +2,7 @@ "name": "@aries-framework/tenants", "main": "build/index", "types": "build/index", - "version": "0.3.2", + "version": "0.3.3", "files": [ "build" ], @@ -18,19 +18,19 @@ }, "scripts": { "build": "yarn run clean && yarn run compile", - "clean": "rimraf -rf ./build", + "clean": "rimraf ./build", "compile": "tsc -p tsconfig.build.json", "prepublishOnly": "yarn run build", "test": "jest" }, "dependencies": { - "@aries-framework/core": "0.3.2", - "async-mutex": "^0.3.2" + "@aries-framework/core": "0.3.3", + "async-mutex": "^0.4.0" }, "devDependencies": { - "@aries-framework/node": "0.3.2", + "@aries-framework/node": "0.3.3", "reflect-metadata": "^0.1.13", - "rimraf": "~3.0.2", - "typescript": "~4.3.0" + "rimraf": "^4.4.0", + "typescript": "~4.9.5" } } diff --git a/packages/tenants/src/__tests__/TenantAgent.test.ts b/packages/tenants/src/__tests__/TenantAgent.test.ts index ce599ef4bf..1c3bb05cc3 100644 --- a/packages/tenants/src/__tests__/TenantAgent.test.ts +++ b/packages/tenants/src/__tests__/TenantAgent.test.ts @@ -1,6 +1,8 @@ import { Agent, AgentContext } from '@aries-framework/core' +import { indySdk } from '../../../core/tests' import { agentDependencies, getAgentConfig, getAgentContext } from '../../../core/tests/helpers' +import { IndySdkModule } from '../../../indy-sdk/src' import { TenantAgent } from '../TenantAgent' describe('TenantAgent', () => { @@ -14,6 +16,9 @@ describe('TenantAgent', () => { }, }, dependencies: agentDependencies, + modules: { + indySdk: new IndySdkModule({ indySdk }), + }, }) const tenantDependencyManager = agent.dependencyManager.createChild() diff --git a/packages/tenants/src/__tests__/TenantsApi.test.ts b/packages/tenants/src/__tests__/TenantsApi.test.ts index e2c5c28fed..213e0cfda3 100644 --- a/packages/tenants/src/__tests__/TenantsApi.test.ts +++ b/packages/tenants/src/__tests__/TenantsApi.test.ts @@ -1,6 +1,7 @@ import { Agent, AgentContext, InjectionSymbols } from '@aries-framework/core' -import { getAgentContext, getAgentOptions, mockFunction } from '../../../core/tests/helpers' +import { indySdk, getAgentContext, getAgentOptions, mockFunction } from '../../../core/tests' +import { IndySdkModule } from '../../../indy-sdk/src' import { TenantAgent } from '../TenantAgent' import { TenantsApi } from '../TenantsApi' import { TenantAgentContextProvider } from '../context/TenantAgentContextProvider' @@ -15,7 +16,7 @@ const AgentContextProviderMock = TenantAgentContextProvider as jest.Mock { registerInstance: jest.fn(), resolve: jest.fn(() => wallet), } as unknown as DependencyManager - const mockConfig = jest.fn() as unknown as AgentConfig createChildSpy.mockReturnValue(tenantDependencyManager) - extendSpy.mockReturnValue(mockConfig) const tenantAgentContext = await tenantSessionCoordinator.getContextForSession(tenantRecord) @@ -103,7 +101,7 @@ describe('TenantSessionCoordinator', () => { expect(extendSpy).toHaveBeenCalledWith(tenantRecord.config) expect(createChildSpy).toHaveBeenCalledWith() expect(tenantDependencyManager.registerInstance).toHaveBeenCalledWith(AgentContext, expect.any(AgentContext)) - expect(tenantDependencyManager.registerInstance).toHaveBeenCalledWith(AgentConfig, mockConfig) + expect(tenantDependencyManager.registerInstance).toHaveBeenCalledWith(AgentConfig, expect.any(AgentConfig)) expect(tenantSessionCoordinator.tenantAgentContextMapping.tenant1).toEqual({ agentContext: tenantAgentContext, @@ -194,8 +192,8 @@ describe('TenantSessionCoordinator', () => { }) // Initialize should only be called once - expect(wallet.initialize).toHaveBeenCalledTimes(1) expect(wallet.initialize).toHaveBeenCalledWith(tenantRecord.config.walletConfig) + expect(wallet.initialize).toHaveBeenCalledTimes(1) expect(tenantAgentContext1).toBe(tenantAgentContext2) }) diff --git a/packages/tenants/tests/setup.ts b/packages/tenants/tests/setup.ts index 4955aeb601..78143033f2 100644 --- a/packages/tenants/tests/setup.ts +++ b/packages/tenants/tests/setup.ts @@ -1,3 +1,3 @@ import 'reflect-metadata' -jest.setTimeout(20000) +jest.setTimeout(120000) diff --git a/packages/tenants/tests/tenant-sessions.e2e.test.ts b/packages/tenants/tests/tenant-sessions.e2e.test.ts index c1e0a212b1..24a66caad9 100644 --- a/packages/tenants/tests/tenant-sessions.e2e.test.ts +++ b/packages/tenants/tests/tenant-sessions.e2e.test.ts @@ -1,9 +1,10 @@ import type { InitConfig } from '@aries-framework/core' -import { Agent } from '@aries-framework/core' +import { ConnectionsModule, Agent } from '@aries-framework/core' import { agentDependencies } from '@aries-framework/node' -import testLogger from '../../core/tests/logger' +import { testLogger, indySdk } from '../../core/tests' +import { IndySdkModule } from '../../indy-sdk/src' import { TenantsModule } from '@aries-framework/tenants' @@ -15,7 +16,6 @@ const agentConfig: InitConfig = { }, logger: testLogger, endpoints: ['rxjs:tenant-agent1'], - autoAcceptConnections: true, } // Create multi-tenant agent @@ -24,6 +24,10 @@ const agent = new Agent({ dependencies: agentDependencies, modules: { tenants: new TenantsModule({ sessionAcquireTimeout: 10000 }), + indySdk: new IndySdkModule({ indySdk }), + connections: new ConnectionsModule({ + autoAcceptConnections: true, + }), }, }) diff --git a/packages/tenants/tests/tenants.e2e.test.ts b/packages/tenants/tests/tenants.e2e.test.ts index 853f44643a..8ca6bb40e0 100644 --- a/packages/tenants/tests/tenants.e2e.test.ts +++ b/packages/tenants/tests/tenants.e2e.test.ts @@ -1,11 +1,12 @@ import type { InitConfig } from '@aries-framework/core' -import { OutOfBandRecord, Agent } from '@aries-framework/core' +import { ConnectionsModule, OutOfBandRecord, Agent } from '@aries-framework/core' import { agentDependencies } from '@aries-framework/node' import { SubjectInboundTransport } from '../../../tests/transport/SubjectInboundTransport' import { SubjectOutboundTransport } from '../../../tests/transport/SubjectOutboundTransport' -import testLogger from '../../core/tests/logger' +import { testLogger, indySdk } from '../../core/tests' +import { IndySdkModule } from '../../indy-sdk/src' import { TenantsModule } from '@aries-framework/tenants' @@ -17,7 +18,6 @@ const agent1Config: InitConfig = { }, logger: testLogger, endpoints: ['rxjs:tenant-agent1'], - autoAcceptConnections: true, } const agent2Config: InitConfig = { @@ -28,7 +28,6 @@ const agent2Config: InitConfig = { }, logger: testLogger, endpoints: ['rxjs:tenant-agent2'], - autoAcceptConnections: true, } // Create multi-tenant agents @@ -36,6 +35,10 @@ const agent1 = new Agent({ config: agent1Config, modules: { tenants: new TenantsModule(), + indySdk: new IndySdkModule({ indySdk }), + connections: new ConnectionsModule({ + autoAcceptConnections: true, + }), }, dependencies: agentDependencies, }) @@ -44,6 +47,10 @@ const agent2 = new Agent({ config: agent2Config, modules: { tenants: new TenantsModule(), + indySdk: new IndySdkModule({ indySdk }), + connections: new ConnectionsModule({ + autoAcceptConnections: true, + }), }, dependencies: agentDependencies, }) diff --git a/samples/extension-module/dummy/DummyApi.ts b/samples/extension-module/dummy/DummyApi.ts index 1bb998336c..9d4aa765d3 100644 --- a/samples/extension-module/dummy/DummyApi.ts +++ b/samples/extension-module/dummy/DummyApi.ts @@ -5,7 +5,6 @@ import { OutboundMessageContext, AgentContext, ConnectionService, - Dispatcher, injectable, MessageSender, } from '@aries-framework/core' @@ -22,7 +21,6 @@ export class DummyApi { private agentContext: AgentContext public constructor( - dispatcher: Dispatcher, messageSender: MessageSender, dummyService: DummyService, connectionService: ConnectionService, @@ -33,7 +31,10 @@ export class DummyApi { this.connectionService = connectionService this.agentContext = agentContext - this.registerMessageHandlers(dispatcher) + this.agentContext.dependencyManager.registerMessageHandlers([ + new DummyRequestHandler(this.dummyService), + new DummyResponseHandler(this.dummyService), + ]) } /** @@ -93,9 +94,4 @@ export class DummyApi { public findAllByQuery(query: Query): Promise { return this.dummyService.findAllByQuery(this.agentContext, query) } - - private registerMessageHandlers(dispatcher: Dispatcher) { - dispatcher.registerMessageHandler(new DummyRequestHandler(this.dummyService)) - dispatcher.registerMessageHandler(new DummyResponseHandler(this.dummyService)) - } } diff --git a/samples/extension-module/dummy/messages/DummyResponseMessage.ts b/samples/extension-module/dummy/messages/DummyResponseMessage.ts index 294a4c9830..45f1b0cb99 100644 --- a/samples/extension-module/dummy/messages/DummyResponseMessage.ts +++ b/samples/extension-module/dummy/messages/DummyResponseMessage.ts @@ -19,5 +19,5 @@ export class DummyResponseMessage extends DidCommV1Message { @IsValidMessageType(DummyResponseMessage.type) public readonly type = DummyResponseMessage.type.messageTypeUri - public static readonly type = parseMessageType('https://2060.io/didcomm/dummy/1.0/response') + public static readonly type = parseMessageType('https://didcomm.org/dummy/1.0/response') } diff --git a/samples/extension-module/dummy/services/DummyService.ts b/samples/extension-module/dummy/services/DummyService.ts index 0aa7a56747..580565296c 100644 --- a/samples/extension-module/dummy/services/DummyService.ts +++ b/samples/extension-module/dummy/services/DummyService.ts @@ -41,7 +41,7 @@ export class DummyService { // Create record const record = new DummyRecord({ connectionId: connectionRecord.id, - threadId: message.id, + threadId: message.threadId, state: DummyState.Init, }) @@ -79,7 +79,7 @@ export class DummyService { // Create record const record = new DummyRecord({ connectionId: connectionRecord.id, - threadId: messageContext.message.id, + threadId: messageContext.message.threadId, state: DummyState.RequestReceived, }) diff --git a/samples/extension-module/package.json b/samples/extension-module/package.json index b668ec5151..97ac103498 100644 --- a/samples/extension-module/package.json +++ b/samples/extension-module/package.json @@ -19,9 +19,9 @@ }, "dependencies": { "@types/express": "^4.17.13", - "@types/uuid": "^8.3.1", - "@types/ws": "^7.4.6", - "class-validator": "0.13.1", + "@types/uuid": "^9.0.1", + "@types/ws": "^8.5.4", + "class-validator": "0.14.0", "rxjs": "^7.2.0" } } diff --git a/samples/extension-module/requester.ts b/samples/extension-module/requester.ts index ac83b076ce..c148a35eb0 100644 --- a/samples/extension-module/requester.ts +++ b/samples/extension-module/requester.ts @@ -7,6 +7,7 @@ import { ConsoleLogger, LogLevel, WsOutboundTransport, + ConnectionsModule, } from '@aries-framework/core' import { agentDependencies } from '@aries-framework/node' import { filter, first, firstValueFrom, map, ReplaySubject, timeout } from 'rxjs' @@ -28,10 +29,12 @@ const run = async () => { key: 'requester', }, logger: new ConsoleLogger(LogLevel.info), - autoAcceptConnections: true, }, modules: { dummy: new DummyModule(), + connections: new ConnectionsModule({ + autoAcceptConnections: true, + }), }, dependencies: agentDependencies, }) diff --git a/samples/extension-module/responder.ts b/samples/extension-module/responder.ts index 3a5e003165..103424481d 100644 --- a/samples/extension-module/responder.ts +++ b/samples/extension-module/responder.ts @@ -1,7 +1,7 @@ import type { DummyStateChangedEvent } from './dummy' import type { Socket } from 'net' -import { Agent, ConsoleLogger, LogLevel } from '@aries-framework/core' +import { Agent, ConnectionsModule, ConsoleLogger, LogLevel } from '@aries-framework/core' import { agentDependencies, HttpInboundTransport, WsInboundTransport } from '@aries-framework/node' import express from 'express' import { Server } from 'ws' @@ -28,10 +28,12 @@ const run = async () => { key: 'responder', }, logger: new ConsoleLogger(LogLevel.debug), - autoAcceptConnections: true, }, modules: { dummy: new DummyModule({ autoAcceptRequests }), + connections: new ConnectionsModule({ + autoAcceptConnections: true, + }), }, dependencies: agentDependencies, }) diff --git a/samples/extension-module/tests/dummy.e2e.test.ts b/samples/extension-module/tests/dummy.e2e.test.ts index c9aa891d02..4ae3b51069 100644 --- a/samples/extension-module/tests/dummy.e2e.test.ts +++ b/samples/extension-module/tests/dummy.e2e.test.ts @@ -4,8 +4,10 @@ import type { ConnectionRecord } from '@aries-framework/core' import { Agent } from '@aries-framework/core' import { Subject } from 'rxjs' +import { indySdk } from '../../../packages/core/tests' import { getAgentOptions, makeConnection } from '../../../packages/core/tests/helpers' import testLogger from '../../../packages/core/tests/logger' +import { IndySdkModule } from '../../../packages/indy-sdk/src' import { SubjectInboundTransport } from '../../../tests/transport/SubjectInboundTransport' import { SubjectOutboundTransport } from '../../../tests/transport/SubjectOutboundTransport' import { DummyModule } from '../dummy/DummyModule' @@ -13,14 +15,17 @@ import { DummyState } from '../dummy/repository' import { waitForDummyRecord } from './helpers' +const modules = { + dummy: new DummyModule(), + indySdk: new IndySdkModule({ indySdk }), +} + const bobAgentOptions = getAgentOptions( 'Bob Dummy', { endpoints: ['rxjs:bob'], }, - { - dummy: new DummyModule(), - } + modules ) const aliceAgentOptions = getAgentOptions( @@ -28,18 +33,12 @@ const aliceAgentOptions = getAgentOptions( { endpoints: ['rxjs:alice'], }, - { - dummy: new DummyModule(), - } + modules ) describe('Dummy extension module test', () => { - let bobAgent: Agent<{ - dummy: DummyModule - }> - let aliceAgent: Agent<{ - dummy: DummyModule - }> + let bobAgent: Agent + let aliceAgent: Agent let aliceConnection: ConnectionRecord beforeEach(async () => { diff --git a/samples/extension-module/tests/setup.ts b/samples/extension-module/tests/setup.ts index 226f7031fa..34e38c9705 100644 --- a/samples/extension-module/tests/setup.ts +++ b/samples/extension-module/tests/setup.ts @@ -1 +1 @@ -jest.setTimeout(20000) +jest.setTimeout(120000) diff --git a/samples/mediator.ts b/samples/mediator.ts index a8bb12d794..2a90fda9a9 100644 --- a/samples/mediator.ts +++ b/samples/mediator.ts @@ -21,6 +21,8 @@ import { Server } from 'ws' import { TestLogger } from '../packages/core/tests/logger' import { + ConnectionsModule, + MediatorModule, HttpOutboundTransport, Agent, ConnectionInvitationMessage, @@ -47,13 +49,23 @@ const agentConfig: InitConfig = { id: process.env.WALLET_NAME || 'AriesFrameworkJavaScript', key: process.env.WALLET_KEY || 'AriesFrameworkJavaScript', }, - autoAcceptConnections: true, - autoAcceptMediationRequests: true, + logger, } // Set up agent -const agent = new Agent({ config: agentConfig, dependencies: agentDependencies }) +const agent = new Agent({ + config: agentConfig, + dependencies: agentDependencies, + modules: { + mediator: new MediatorModule({ + autoAcceptMediationRequests: true, + }), + connections: new ConnectionsModule({ + autoAcceptConnections: true, + }), + }, +}) const config = agent.config // Create all transports diff --git a/scripts/add-ref-napi-resolution.js b/scripts/add-ref-napi-resolution.js new file mode 100644 index 0000000000..d8f01d135f --- /dev/null +++ b/scripts/add-ref-napi-resolution.js @@ -0,0 +1,15 @@ +const fs = require('fs') +const path = require('path') + +// Read package.json +const packageJsonPath = path.join(__dirname, '..', 'package.json') +const packageJson = JSON.parse(fs.readFileSync(packageJsonPath)) + +// Add ref-napi resolution +packageJson.resolutions = { + ...packageJson.resolutions, + 'ref-napi': 'npm:@2060.io/ref-napi', +} + +// Write package.json +fs.writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2)) diff --git a/tests/InMemoryStorageService.ts b/tests/InMemoryStorageService.ts index cd4415a2e5..c5ba42820f 100644 --- a/tests/InMemoryStorageService.ts +++ b/tests/InMemoryStorageService.ts @@ -2,13 +2,7 @@ import type { AgentContext } from '../packages/core/src/agent' import type { BaseRecord, TagsBase } from '../packages/core/src/storage/BaseRecord' import type { StorageService, BaseRecordConstructor, Query } from '../packages/core/src/storage/StorageService' -import { - RecordNotFoundError, - RecordDuplicateError, - JsonTransformer, - AriesFrameworkError, - injectable, -} from '@aries-framework/core' +import { RecordNotFoundError, RecordDuplicateError, JsonTransformer, injectable } from '@aries-framework/core' interface StorageRecord { value: Record @@ -17,11 +11,18 @@ interface StorageRecord { id: string } +interface InMemoryRecords { + [id: string]: StorageRecord +} + @injectable() -export class InMemoryStorageService implements StorageService { - public records: { [id: string]: StorageRecord } +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export class InMemoryStorageService = BaseRecord> + implements StorageService +{ + public records: InMemoryRecords - public constructor(records: { [id: string]: StorageRecord } = {}) { + public constructor(records: InMemoryRecords = {}) { this.records = records } @@ -35,6 +36,7 @@ export class InMemoryStorageService implement /** @inheritDoc */ public async save(agentContext: AgentContext, record: T) { + record.updatedAt = new Date() const value = JsonTransformer.toJSON(record) if (this.records[record.id]) { @@ -51,6 +53,7 @@ export class InMemoryStorageService implement /** @inheritDoc */ public async update(agentContext: AgentContext, record: T): Promise { + record.updatedAt = new Date() const value = JsonTransformer.toJSON(record) delete value._tags @@ -122,31 +125,55 @@ export class InMemoryStorageService implement recordClass: BaseRecordConstructor, query: Query ): Promise { - if (query.$and || query.$or || query.$not) { - throw new AriesFrameworkError( - 'Advanced wallet query features $and, $or or $not not supported in in memory storage' - ) - } - const records = Object.values(this.records) - .filter((record) => { - const tags = record.tags as TagsBase - - for (const [key, value] of Object.entries(query)) { - if (Array.isArray(value)) { - const tagValue = tags[key] - if (!Array.isArray(tagValue) || !value.every((v) => tagValue.includes(v))) { - return false - } - } else if (tags[key] !== value) { - return false - } - } - - return true - }) + .filter((record) => record.type === recordClass.type) + .filter((record) => filterByQuery(record, query)) .map((record) => this.recordToInstance(record, recordClass)) return records } } + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +function filterByQuery>(record: StorageRecord, query: Query) { + const { $and, $or, $not, ...restQuery } = query + + if ($not) { + throw new Error('$not query not supported in in memory storage') + } + + // Top level query + if (!matchSimpleQuery(record, restQuery)) return false + + // All $and queries MUST match + if ($and) { + const allAndMatch = ($and as Query[]).every((and) => filterByQuery(record, and)) + if (!allAndMatch) return false + } + + // Only one $or queries has to match + if ($or) { + const oneOrMatch = ($or as Query[]).some((or) => filterByQuery(record, or)) + if (!oneOrMatch) return false + } + + return true +} + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +function matchSimpleQuery>(record: StorageRecord, query: Query) { + const tags = record.tags as TagsBase + + for (const [key, value] of Object.entries(query)) { + if (Array.isArray(value)) { + const tagValue = tags[key] + if (!Array.isArray(tagValue) || !value.every((v) => tagValue.includes(v))) { + return false + } + } else if (tags[key] !== value) { + return false + } + } + + return true +} diff --git a/tests/e2e-askar-indy-sdk-wallet-subject.test.ts b/tests/e2e-askar-indy-sdk-wallet-subject.test.ts new file mode 100644 index 0000000000..36aab1fda7 --- /dev/null +++ b/tests/e2e-askar-indy-sdk-wallet-subject.test.ts @@ -0,0 +1,115 @@ +import type { SubjectMessage } from './transport/SubjectInboundTransport' +import type { AnonCredsTestsAgent } from '../packages/anoncreds/tests/legacyAnonCredsSetup' + +import { Subject } from 'rxjs' + +import { + getAskarAnonCredsIndyModules, + getLegacyAnonCredsModules, +} from '../packages/anoncreds/tests/legacyAnonCredsSetup' +import { getAgentOptions } from '../packages/core/tests/helpers' + +import { e2eTest } from './e2e-test' +import { describeRunInNodeVersion } from './runInVersion' +import { SubjectInboundTransport } from './transport/SubjectInboundTransport' +import { SubjectOutboundTransport } from './transport/SubjectOutboundTransport' + +import { + Agent, + AutoAcceptCredential, + MediatorModule, + MediatorPickupStrategy, + MediationRecipientModule, +} from '@aries-framework/core' + +const recipientAgentOptions = getAgentOptions( + 'E2E Askar Subject Recipient', + {}, + { + ...getAskarAnonCredsIndyModules({ + autoAcceptCredentials: AutoAcceptCredential.ContentApproved, + }), + mediationRecipient: new MediationRecipientModule({ + mediatorPickupStrategy: MediatorPickupStrategy.PickUpV1, + }), + } +) +const mediatorAgentOptions = getAgentOptions( + 'E2E Askar Subject Mediator', + { + endpoints: ['rxjs:mediator'], + }, + { + ...getAskarAnonCredsIndyModules({ + autoAcceptCredentials: AutoAcceptCredential.ContentApproved, + }), + mediator: new MediatorModule({ autoAcceptMediationRequests: true }), + } +) +const senderAgentOptions = getAgentOptions( + 'E2E Indy SDK Subject Sender', + { + endpoints: ['rxjs:sender'], + }, + { + ...getLegacyAnonCredsModules({ + autoAcceptCredentials: AutoAcceptCredential.ContentApproved, + }), + mediationRecipient: new MediationRecipientModule({ + mediatorPollingInterval: 1000, + mediatorPickupStrategy: MediatorPickupStrategy.PickUpV1, + }), + } +) + +// Performance issues outside of Node 18 +describeRunInNodeVersion([18], 'E2E Askar-AnonCredsRS-IndyVDR Subject tests', () => { + let recipientAgent: AnonCredsTestsAgent + let mediatorAgent: AnonCredsTestsAgent + let senderAgent: AnonCredsTestsAgent + + beforeEach(async () => { + recipientAgent = new Agent(recipientAgentOptions) as AnonCredsTestsAgent + mediatorAgent = new Agent(mediatorAgentOptions) as AnonCredsTestsAgent + senderAgent = new Agent(senderAgentOptions) as AnonCredsTestsAgent + }) + + afterEach(async () => { + await recipientAgent.shutdown() + await recipientAgent.wallet.delete() + await mediatorAgent.shutdown() + await mediatorAgent.wallet.delete() + await senderAgent.shutdown() + await senderAgent.wallet.delete() + }) + + test('Full Subject flow (connect, request mediation, issue, verify)', async () => { + const mediatorMessages = new Subject() + const senderMessages = new Subject() + + const subjectMap = { + 'rxjs:mediator': mediatorMessages, + 'rxjs:sender': senderMessages, + } + + // Recipient Setup + recipientAgent.registerOutboundTransport(new SubjectOutboundTransport(subjectMap)) + await recipientAgent.initialize() + + // Mediator Setup + mediatorAgent.registerOutboundTransport(new SubjectOutboundTransport(subjectMap)) + mediatorAgent.registerInboundTransport(new SubjectInboundTransport(mediatorMessages)) + await mediatorAgent.initialize() + + // Sender Setup + senderAgent.registerOutboundTransport(new SubjectOutboundTransport(subjectMap)) + senderAgent.registerInboundTransport(new SubjectInboundTransport(senderMessages)) + await senderAgent.initialize() + + await e2eTest({ + mediatorAgent, + senderAgent, + recipientAgent, + }) + }) +}) diff --git a/tests/e2e-http.test.ts b/tests/e2e-http.test.ts index 4bd2396d41..befa8fe000 100644 --- a/tests/e2e-http.test.ts +++ b/tests/e2e-http.test.ts @@ -1,38 +1,75 @@ +import type { AnonCredsTestsAgent } from '../packages/anoncreds/tests/legacyAnonCredsSetup' + +import { getLegacyAnonCredsModules } from '../packages/anoncreds/tests/legacyAnonCredsSetup' import { getAgentOptions } from '../packages/core/tests/helpers' import { e2eTest } from './e2e-test' -import { HttpOutboundTransport, Agent, AutoAcceptCredential, MediatorPickupStrategy } from '@aries-framework/core' +import { + HttpOutboundTransport, + Agent, + AutoAcceptCredential, + MediatorPickupStrategy, + MediationRecipientModule, + MediatorModule, +} from '@aries-framework/core' import { HttpInboundTransport } from '@aries-framework/node' -const recipientAgentOptions = getAgentOptions('E2E HTTP Recipient', { - autoAcceptCredentials: AutoAcceptCredential.ContentApproved, - mediatorPickupStrategy: MediatorPickupStrategy.PickUpV1, -}) +const recipientAgentOptions = getAgentOptions( + 'E2E HTTP Recipient', + {}, + { + ...getLegacyAnonCredsModules({ + autoAcceptCredentials: AutoAcceptCredential.ContentApproved, + }), + mediationRecipient: new MediationRecipientModule({ + mediatorPickupStrategy: MediatorPickupStrategy.PickUpV1, + }), + } +) const mediatorPort = 3000 -const mediatorAgentOptions = getAgentOptions('E2E HTTP Mediator', { - endpoints: [`http://localhost:${mediatorPort}`], - autoAcceptMediationRequests: true, -}) +const mediatorAgentOptions = getAgentOptions( + 'E2E HTTP Mediator', + { + endpoints: [`http://localhost:${mediatorPort}`], + }, + { + ...getLegacyAnonCredsModules({ + autoAcceptCredentials: AutoAcceptCredential.ContentApproved, + }), + mediator: new MediatorModule({ + autoAcceptMediationRequests: true, + }), + } +) const senderPort = 3001 -const senderAgentOptions = getAgentOptions('E2E HTTP Sender', { - endpoints: [`http://localhost:${senderPort}`], - mediatorPollingInterval: 1000, - autoAcceptCredentials: AutoAcceptCredential.ContentApproved, - mediatorPickupStrategy: MediatorPickupStrategy.PickUpV1, -}) +const senderAgentOptions = getAgentOptions( + 'E2E HTTP Sender', + { + endpoints: [`http://localhost:${senderPort}`], + }, + { + ...getLegacyAnonCredsModules({ + autoAcceptCredentials: AutoAcceptCredential.ContentApproved, + }), + mediationRecipient: new MediationRecipientModule({ + mediatorPollingInterval: 1000, + mediatorPickupStrategy: MediatorPickupStrategy.PickUpV1, + }), + } +) describe('E2E HTTP tests', () => { - let recipientAgent: Agent - let mediatorAgent: Agent - let senderAgent: Agent + let recipientAgent: AnonCredsTestsAgent + let mediatorAgent: AnonCredsTestsAgent + let senderAgent: AnonCredsTestsAgent beforeEach(async () => { - recipientAgent = new Agent(recipientAgentOptions) - mediatorAgent = new Agent(mediatorAgentOptions) - senderAgent = new Agent(senderAgentOptions) + recipientAgent = new Agent(recipientAgentOptions) as AnonCredsTestsAgent + mediatorAgent = new Agent(mediatorAgentOptions) as AnonCredsTestsAgent + senderAgent = new Agent(senderAgentOptions) as AnonCredsTestsAgent }) afterEach(async () => { diff --git a/tests/e2e-subject.test.ts b/tests/e2e-subject.test.ts index 1b6cdb09cc..a724d4bedd 100644 --- a/tests/e2e-subject.test.ts +++ b/tests/e2e-subject.test.ts @@ -1,39 +1,72 @@ import type { SubjectMessage } from './transport/SubjectInboundTransport' +import type { AnonCredsTestsAgent } from '../packages/anoncreds/tests/legacyAnonCredsSetup' import { Subject } from 'rxjs' +import { getLegacyAnonCredsModules } from '../packages/anoncreds/tests/legacyAnonCredsSetup' import { getAgentOptions } from '../packages/core/tests/helpers' import { e2eTest } from './e2e-test' import { SubjectInboundTransport } from './transport/SubjectInboundTransport' import { SubjectOutboundTransport } from './transport/SubjectOutboundTransport' -import { Agent, AutoAcceptCredential, MediatorPickupStrategy } from '@aries-framework/core' +import { + Agent, + AutoAcceptCredential, + MediatorModule, + MediatorPickupStrategy, + MediationRecipientModule, +} from '@aries-framework/core' -const recipientAgentOptions = getAgentOptions('E2E Subject Recipient', { - autoAcceptCredentials: AutoAcceptCredential.ContentApproved, - mediatorPickupStrategy: MediatorPickupStrategy.PickUpV1, -}) -const mediatorAgentOptions = getAgentOptions('E2E Subject Mediator', { - endpoints: ['rxjs:mediator'], - autoAcceptMediationRequests: true, -}) -const senderAgentOptions = getAgentOptions('E2E Subject Sender', { - endpoints: ['rxjs:sender'], - mediatorPollingInterval: 1000, - autoAcceptCredentials: AutoAcceptCredential.ContentApproved, - mediatorPickupStrategy: MediatorPickupStrategy.PickUpV1, -}) +const recipientAgentOptions = getAgentOptions( + 'E2E Subject Recipient', + {}, + { + ...getLegacyAnonCredsModules({ + autoAcceptCredentials: AutoAcceptCredential.ContentApproved, + }), + mediationRecipient: new MediationRecipientModule({ + mediatorPickupStrategy: MediatorPickupStrategy.PickUpV1, + }), + } +) +const mediatorAgentOptions = getAgentOptions( + 'E2E Subject Mediator', + { + endpoints: ['rxjs:mediator'], + }, + { + ...getLegacyAnonCredsModules({ + autoAcceptCredentials: AutoAcceptCredential.ContentApproved, + }), + mediator: new MediatorModule({ autoAcceptMediationRequests: true }), + } +) +const senderAgentOptions = getAgentOptions( + 'E2E Subject Sender', + { + endpoints: ['rxjs:sender'], + }, + { + ...getLegacyAnonCredsModules({ + autoAcceptCredentials: AutoAcceptCredential.ContentApproved, + }), + mediationRecipient: new MediationRecipientModule({ + mediatorPollingInterval: 1000, + mediatorPickupStrategy: MediatorPickupStrategy.PickUpV1, + }), + } +) describe('E2E Subject tests', () => { - let recipientAgent: Agent - let mediatorAgent: Agent - let senderAgent: Agent + let recipientAgent: AnonCredsTestsAgent + let mediatorAgent: AnonCredsTestsAgent + let senderAgent: AnonCredsTestsAgent beforeEach(async () => { - recipientAgent = new Agent(recipientAgentOptions) - mediatorAgent = new Agent(mediatorAgentOptions) - senderAgent = new Agent(senderAgentOptions) + recipientAgent = new Agent(recipientAgentOptions) as AnonCredsTestsAgent + mediatorAgent = new Agent(mediatorAgentOptions) as AnonCredsTestsAgent + senderAgent = new Agent(senderAgentOptions) as AnonCredsTestsAgent }) afterEach(async () => { diff --git a/tests/e2e-test.ts b/tests/e2e-test.ts index 86fb6dfe83..ffacd8be0a 100644 --- a/tests/e2e-test.ts +++ b/tests/e2e-test.ts @@ -1,17 +1,21 @@ -import type { Agent } from '@aries-framework/core' +import type { AnonCredsTestsAgent } from '../packages/anoncreds/tests/legacyAnonCredsSetup' +import { V1CredentialPreview } from '../packages/anoncreds/src/protocols/credentials/v1' +import { + issueLegacyAnonCredsCredential, + presentLegacyAnonCredsProof, + prepareForAnonCredsIssuance, +} from '../packages/anoncreds/tests/legacyAnonCredsSetup' import { sleep } from '../packages/core/src/utils/sleep' -import { issueCredential, makeConnection, prepareForIssuance, presentProof } from '../packages/core/tests/helpers' +import { setupEventReplaySubjects } from '../packages/core/tests' +import { makeConnection } from '../packages/core/tests/helpers' import { - V1CredentialPreview, - AttributeFilter, CredentialState, MediationState, - PredicateType, - ProofAttributeInfo, - ProofPredicateInfo, ProofState, + CredentialEventTypes, + ProofEventTypes, } from '@aries-framework/core' export async function e2eTest({ @@ -19,10 +23,15 @@ export async function e2eTest({ recipientAgent, senderAgent, }: { - mediatorAgent: Agent - recipientAgent: Agent - senderAgent: Agent + mediatorAgent: AnonCredsTestsAgent + recipientAgent: AnonCredsTestsAgent + senderAgent: AnonCredsTestsAgent }) { + const [senderReplay, recipientReplay] = setupEventReplaySubjects( + [senderAgent, recipientAgent], + [CredentialEventTypes.CredentialStateChanged, ProofEventTypes.ProofStateChanged] + ) + // Make connection between mediator and recipient const [mediatorRecipientConnection, recipientMediatorConnection] = await makeConnection(mediatorAgent, recipientAgent) expect(recipientMediatorConnection).toBeConnectedWith(mediatorRecipientConnection) @@ -41,14 +50,22 @@ export async function e2eTest({ const [recipientSenderConnection, senderRecipientConnection] = await makeConnection(recipientAgent, senderAgent) expect(recipientSenderConnection).toBeConnectedWith(senderRecipientConnection) + // Create link secret with default options. This should create a default link secret. + await recipientAgent.modules.anoncreds.createLinkSecret() + // Issue credential from sender to recipient - const { definition } = await prepareForIssuance(senderAgent, ['name', 'age', 'dateOfBirth']) - const { holderCredential, issuerCredential } = await issueCredential({ + const { credentialDefinition } = await prepareForAnonCredsIssuance(senderAgent, { + attributeNames: ['name', 'age', 'dateOfBirth'], + }) + const { holderCredentialExchangeRecord, issuerCredentialExchangeRecord } = await issueLegacyAnonCredsCredential({ issuerAgent: senderAgent, + issuerReplay: senderReplay, holderAgent: recipientAgent, - issuerConnectionId: senderRecipientConnection.id, - credentialTemplate: { - credentialDefinitionId: definition.id, + holderReplay: recipientReplay, + + issuerHolderConnectionId: senderRecipientConnection.id, + offer: { + credentialDefinitionId: credentialDefinition.credentialDefinitionId, attributes: V1CredentialPreview.fromRecord({ name: 'John', age: '25', @@ -58,39 +75,46 @@ export async function e2eTest({ }, }) - expect(holderCredential.state).toBe(CredentialState.Done) - expect(issuerCredential.state).toBe(CredentialState.Done) + expect(holderCredentialExchangeRecord.state).toBe(CredentialState.Done) + expect(issuerCredentialExchangeRecord.state).toBe(CredentialState.Done) // Present Proof from recipient to sender - const definitionRestriction = [ - new AttributeFilter({ - credentialDefinitionId: definition.id, - }), - ] - const { holderProof, verifierProof } = await presentProof({ + const { holderProofExchangeRecord, verifierProofExchangeRecord } = await presentLegacyAnonCredsProof({ verifierAgent: senderAgent, + verifierReplay: senderReplay, + holderAgent: recipientAgent, - verifierConnectionId: senderRecipientConnection.id, - presentationTemplate: { + holderReplay: recipientReplay, + + verifierHolderConnectionId: senderRecipientConnection.id, + request: { attributes: { - name: new ProofAttributeInfo({ + name: { name: 'name', - restrictions: definitionRestriction, - }), + restrictions: [ + { + cred_def_id: credentialDefinition.credentialDefinitionId, + }, + ], + }, }, predicates: { - olderThan21: new ProofPredicateInfo({ + olderThan21: { name: 'age', - restrictions: definitionRestriction, - predicateType: PredicateType.LessThan, - predicateValue: 20000712, - }), + restrictions: [ + { + cred_def_id: credentialDefinition.credentialDefinitionId, + }, + ], + p_type: '<=', + p_value: 20000712, + }, }, }, }) - expect(holderProof.state).toBe(ProofState.Done) - expect(verifierProof.state).toBe(ProofState.Done) + expect(holderProofExchangeRecord.state).toBe(ProofState.Done) + expect(verifierProofExchangeRecord.state).toBe(ProofState.Done) // We want to stop the mediator polling before the agent is shutdown. await recipientAgent.mediationRecipient.stopMessagePickup() diff --git a/tests/e2e-ws-pickup-v2.test.ts b/tests/e2e-ws-pickup-v2.test.ts index 45c641c7d7..3ee7ffb404 100644 --- a/tests/e2e-ws-pickup-v2.test.ts +++ b/tests/e2e-ws-pickup-v2.test.ts @@ -1,39 +1,74 @@ +import type { AnonCredsTestsAgent } from '../packages/anoncreds/tests/legacyAnonCredsSetup' + +import { getLegacyAnonCredsModules } from '../packages/anoncreds/tests/legacyAnonCredsSetup' import { getAgentOptions } from '../packages/core/tests/helpers' import { e2eTest } from './e2e-test' -import { Agent, WsOutboundTransport, AutoAcceptCredential, MediatorPickupStrategy } from '@aries-framework/core' +import { + Agent, + WsOutboundTransport, + AutoAcceptCredential, + MediatorPickupStrategy, + MediationRecipientModule, + MediatorModule, +} from '@aries-framework/core' import { WsInboundTransport } from '@aries-framework/node' -const recipientOptions = getAgentOptions('E2E WS Pickup V2 Recipient ', { - autoAcceptCredentials: AutoAcceptCredential.ContentApproved, - mediatorPickupStrategy: MediatorPickupStrategy.PickUpV2, -}) +const recipientOptions = getAgentOptions( + 'E2E WS Pickup V2 Recipient ', + {}, + { + ...getLegacyAnonCredsModules({ + autoAcceptCredentials: AutoAcceptCredential.ContentApproved, + }), + mediationRecipient: new MediationRecipientModule({ + mediatorPickupStrategy: MediatorPickupStrategy.PickUpV2, + }), + } +) // FIXME: port numbers should not depend on availability from other test suites that use web sockets const mediatorPort = 4100 -const mediatorOptions = getAgentOptions('E2E WS Pickup V2 Mediator', { - endpoints: [`ws://localhost:${mediatorPort}`], - autoAcceptMediationRequests: true, -}) +const mediatorOptions = getAgentOptions( + 'E2E WS Pickup V2 Mediator', + { + endpoints: [`ws://localhost:${mediatorPort}`], + }, + { + ...getLegacyAnonCredsModules({ + autoAcceptCredentials: AutoAcceptCredential.ContentApproved, + }), + mediator: new MediatorModule({ autoAcceptMediationRequests: true }), + } +) const senderPort = 4101 -const senderOptions = getAgentOptions('E2E WS Pickup V2 Sender', { - endpoints: [`ws://localhost:${senderPort}`], - mediatorPollingInterval: 1000, - autoAcceptCredentials: AutoAcceptCredential.ContentApproved, - mediatorPickupStrategy: MediatorPickupStrategy.PickUpV2, -}) +const senderOptions = getAgentOptions( + 'E2E WS Pickup V2 Sender', + { + endpoints: [`ws://localhost:${senderPort}`], + }, + { + ...getLegacyAnonCredsModules({ + autoAcceptCredentials: AutoAcceptCredential.ContentApproved, + }), + mediationRecipient: new MediationRecipientModule({ + mediatorPollingInterval: 1000, + mediatorPickupStrategy: MediatorPickupStrategy.PickUpV1, + }), + } +) describe('E2E WS Pickup V2 tests', () => { - let recipientAgent: Agent - let mediatorAgent: Agent - let senderAgent: Agent + let recipientAgent: AnonCredsTestsAgent + let mediatorAgent: AnonCredsTestsAgent + let senderAgent: AnonCredsTestsAgent beforeEach(async () => { - recipientAgent = new Agent(recipientOptions) - mediatorAgent = new Agent(mediatorOptions) - senderAgent = new Agent(senderOptions) + recipientAgent = new Agent(recipientOptions) as AnonCredsTestsAgent + mediatorAgent = new Agent(mediatorOptions) as AnonCredsTestsAgent + senderAgent = new Agent(senderOptions) as AnonCredsTestsAgent }) afterEach(async () => { diff --git a/tests/e2e-ws.test.ts b/tests/e2e-ws.test.ts index e0bd5f27ab..b3969a8748 100644 --- a/tests/e2e-ws.test.ts +++ b/tests/e2e-ws.test.ts @@ -1,38 +1,73 @@ +import type { AnonCredsTestsAgent } from '../packages/anoncreds/tests/legacyAnonCredsSetup' + +import { getLegacyAnonCredsModules } from '../packages/anoncreds/tests/legacyAnonCredsSetup' import { getAgentOptions } from '../packages/core/tests/helpers' import { e2eTest } from './e2e-test' -import { Agent, WsOutboundTransport, AutoAcceptCredential, MediatorPickupStrategy } from '@aries-framework/core' +import { + Agent, + WsOutboundTransport, + AutoAcceptCredential, + MediatorPickupStrategy, + MediationRecipientModule, + MediatorModule, +} from '@aries-framework/core' import { WsInboundTransport } from '@aries-framework/node' -const recipientAgentOptions = getAgentOptions('E2E WS Recipient ', { - autoAcceptCredentials: AutoAcceptCredential.ContentApproved, - mediatorPickupStrategy: MediatorPickupStrategy.PickUpV1, -}) +const recipientAgentOptions = getAgentOptions( + 'E2E WS Recipient ', + {}, + { + ...getLegacyAnonCredsModules({ + autoAcceptCredentials: AutoAcceptCredential.ContentApproved, + }), + mediationRecipient: new MediationRecipientModule({ + mediatorPickupStrategy: MediatorPickupStrategy.PickUpV1, + }), + } +) const mediatorPort = 4000 -const mediatorAgentOptions = getAgentOptions('E2E WS Mediator', { - endpoints: [`ws://localhost:${mediatorPort}`], - autoAcceptMediationRequests: true, -}) +const mediatorAgentOptions = getAgentOptions( + 'E2E WS Mediator', + { + endpoints: [`ws://localhost:${mediatorPort}`], + }, + { + ...getLegacyAnonCredsModules({ + autoAcceptCredentials: AutoAcceptCredential.ContentApproved, + }), + mediator: new MediatorModule({ autoAcceptMediationRequests: true }), + } +) const senderPort = 4001 -const senderAgentOptions = getAgentOptions('E2E WS Sender', { - endpoints: [`ws://localhost:${senderPort}`], - mediatorPollingInterval: 1000, - autoAcceptCredentials: AutoAcceptCredential.ContentApproved, - mediatorPickupStrategy: MediatorPickupStrategy.PickUpV1, -}) +const senderAgentOptions = getAgentOptions( + 'E2E WS Sender', + { + endpoints: [`ws://localhost:${senderPort}`], + }, + { + ...getLegacyAnonCredsModules({ + autoAcceptCredentials: AutoAcceptCredential.ContentApproved, + }), + mediationRecipient: new MediationRecipientModule({ + mediatorPollingInterval: 1000, + mediatorPickupStrategy: MediatorPickupStrategy.PickUpV1, + }), + } +) describe('E2E WS tests', () => { - let recipientAgent: Agent - let mediatorAgent: Agent - let senderAgent: Agent + let recipientAgent: AnonCredsTestsAgent + let mediatorAgent: AnonCredsTestsAgent + let senderAgent: AnonCredsTestsAgent beforeEach(async () => { - recipientAgent = new Agent(recipientAgentOptions) - mediatorAgent = new Agent(mediatorAgentOptions) - senderAgent = new Agent(senderAgentOptions) + recipientAgent = new Agent(recipientAgentOptions) as AnonCredsTestsAgent + mediatorAgent = new Agent(mediatorAgentOptions) as AnonCredsTestsAgent + senderAgent = new Agent(senderAgentOptions) as AnonCredsTestsAgent }) afterEach(async () => { diff --git a/tests/jest.config.ts b/tests/jest.config.ts index c9431e4a48..3218944397 100644 --- a/tests/jest.config.ts +++ b/tests/jest.config.ts @@ -4,7 +4,6 @@ import base from '../jest.config.base' const config: Config.InitialOptions = { ...base, - name: '@aries-framework/e2e-test', displayName: '@aries-framework/e2e-test', setupFilesAfterEnv: ['../packages/core/tests/setup.ts'], } diff --git a/tests/runInVersion.ts b/tests/runInVersion.ts new file mode 100644 index 0000000000..86afbe3889 --- /dev/null +++ b/tests/runInVersion.ts @@ -0,0 +1,12 @@ +type NodeVersions = 14 | 16 | 17 | 18 + +export function describeRunInNodeVersion(versions: NodeVersions[], ...parameters: Parameters) { + const runtimeVersion = process.version + const mappedVersions = versions.map((version) => `v${version}.`) + + if (mappedVersions.some((version) => runtimeVersion.startsWith(version))) { + describe(...parameters) + } else { + describe.skip(...parameters) + } +} diff --git a/tests/transport/SubjectOutboundTransport.ts b/tests/transport/SubjectOutboundTransport.ts index 7a7adfaa8e..44f64555af 100644 --- a/tests/transport/SubjectOutboundTransport.ts +++ b/tests/transport/SubjectOutboundTransport.ts @@ -11,7 +11,7 @@ export class SubjectOutboundTransport implements OutboundTransport { private agent!: Agent private stop$!: Subject - public supportedSchemes = ['rxjs'] + public supportedSchemes = ['rxjs', 'wss'] public constructor(subjectMap: { [key: string]: Subject | undefined }) { this.subjectMap = subjectMap @@ -29,7 +29,7 @@ export class SubjectOutboundTransport implements OutboundTransport { } public async sendMessage(outboundPackage: OutboundPackage) { - const messageReceiver = this.agent.injectionContainer.resolve(MessageReceiver) + const messageReceiver = this.agent.dependencyManager.resolve(MessageReceiver) this.logger.debug(`Sending outbound message to endpoint ${outboundPackage.endpoint}`, { endpoint: outboundPackage.endpoint, }) diff --git a/tsconfig.build.json b/tsconfig.build.json index 3ff691c0e8..45d3c20c52 100644 --- a/tsconfig.build.json +++ b/tsconfig.build.json @@ -12,7 +12,9 @@ "allowSyntheticDefaultImports": true, "resolveJsonModule": true, "experimentalDecorators": true, - "emitDecoratorMetadata": true + "emitDecoratorMetadata": true, + // TODO: we should update code to assume errors are of type 'unknown' + "useUnknownInCatchVariables": false }, "exclude": ["node_modules", "build", "**/*.test.ts", "**/__tests__/*.ts", "**/__mocks__/*.ts", "**/build/**"] } diff --git a/txn.txt b/txn.txt new file mode 100644 index 0000000000..0c5b0dcf42 --- /dev/null +++ b/txn.txt @@ -0,0 +1,4 @@ +{"reqSignature": {}, "txn": {"data": {"data": {"alias": "Node1", "blskey": "4N8aUNHSgjQVgkpm8nhNEfDf6txHznoYREg9kirmJrkivgL4oSEimFF6nsQ6M41QvhM2Z33nves5vfSn9n1UwNFJBYtWVnHYMATn76vLuL3zU88KyeAYcHfsih3He6UHcXDxcaecHVz6jhCYz1P2UZn2bDVruL5wXpehgBfBaLKm3Ba", "blskey_pop": "RahHYiCvoNCtPTrVtP7nMC5eTYrsUA8WjXbdhNc8debh1agE9bGiJxWBXYNFbnJXoXhWFMvyqhqhRoq737YQemH5ik9oL7R4NTTCz2LEZhkgLJzB3QRQqJyBNyv7acbdHrAT8nQ9UkLbaVL9NBpnWXBTw4LEMePaSHEw66RzPNdAX1", "client_ip": "127.0.0.1", "client_port": 9702, "node_ip": "127.0.0.1", "node_port": 9701, "services": ["VALIDATOR"]}, "dest": "Gw6pDLhcBcoQesN72qfotTgFa7cbuqZpkX3Xo6pLhPhv"}, "metadata": {"from": "Th7MpTaRZVRYnPiabds81Y"}, "type": "0"}, "txnMetadata": {"seqNo": 1, "txnId": "fea82e10e894419fe2bea7d96296a6d46f50f93f9eeda954ec461b2ed2950b62"}, "ver": "1"} +{"reqSignature": {}, "txn": {"data": {"data": {"alias": "Node2", "blskey": "37rAPpXVoxzKhz7d9gkUe52XuXryuLXoM6P6LbWDB7LSbG62Lsb33sfG7zqS8TK1MXwuCHj1FKNzVpsnafmqLG1vXN88rt38mNFs9TENzm4QHdBzsvCuoBnPH7rpYYDo9DZNJePaDvRvqJKByCabubJz3XXKbEeshzpz4Ma5QYpJqjk", "blskey_pop": "Qr658mWZ2YC8JXGXwMDQTzuZCWF7NK9EwxphGmcBvCh6ybUuLxbG65nsX4JvD4SPNtkJ2w9ug1yLTj6fgmuDg41TgECXjLCij3RMsV8CwewBVgVN67wsA45DFWvqvLtu4rjNnE9JbdFTc1Z4WCPA3Xan44K1HoHAq9EVeaRYs8zoF5", "client_ip": "127.0.0.1", "client_port": 9704, "node_ip": "127.0.0.1", "node_port": 9703, "services": ["VALIDATOR"]}, "dest": "8ECVSk179mjsjKRLWiQtssMLgp6EPhWXtaYyStWPSGAb"}, "metadata": {"from": "EbP4aYNeTHL6q385GuVpRV"}, "type": "0"}, "txnMetadata": {"seqNo": 2, "txnId": "1ac8aece2a18ced660fef8694b61aac3af08ba875ce3026a160acbc3a3af35fc"}, "ver": "1"} +{"reqSignature": {}, "txn": {"data": {"data": {"alias": "Node3", "blskey": "3WFpdbg7C5cnLYZwFZevJqhubkFALBfCBBok15GdrKMUhUjGsk3jV6QKj6MZgEubF7oqCafxNdkm7eswgA4sdKTRc82tLGzZBd6vNqU8dupzup6uYUf32KTHTPQbuUM8Yk4QFXjEf2Usu2TJcNkdgpyeUSX42u5LqdDDpNSWUK5deC5", "blskey_pop": "QwDeb2CkNSx6r8QC8vGQK3GRv7Yndn84TGNijX8YXHPiagXajyfTjoR87rXUu4G4QLk2cF8NNyqWiYMus1623dELWwx57rLCFqGh7N4ZRbGDRP4fnVcaKg1BcUxQ866Ven4gw8y4N56S5HzxXNBZtLYmhGHvDtk6PFkFwCvxYrNYjh", "client_ip": "127.0.0.1", "client_port": 9706, "node_ip": "127.0.0.1", "node_port": 9705, "services": ["VALIDATOR"]}, "dest": "DKVxG2fXXTU8yT5N7hGEbXB3dfdAnYv1JczDUHpmDxya"}, "metadata": {"from": "4cU41vWW82ArfxJxHkzXPG"}, "type": "0"}, "txnMetadata": {"seqNo": 3, "txnId": "7e9f355dffa78ed24668f0e0e369fd8c224076571c51e2ea8be5f26479edebe4"}, "ver": "1"} +{"reqSignature": {}, "txn": {"data": {"data": {"alias": "Node4", "blskey": "2zN3bHM1m4rLz54MJHYSwvqzPchYp8jkHswveCLAEJVcX6Mm1wHQD1SkPYMzUDTZvWvhuE6VNAkK3KxVeEmsanSmvjVkReDeBEMxeDaayjcZjFGPydyey1qxBHmTvAnBKoPydvuTAqx5f7YNNRAdeLmUi99gERUU7TD8KfAa6MpQ9bw", "blskey_pop": "RPLagxaR5xdimFzwmzYnz4ZhWtYQEj8iR5ZU53T2gitPCyCHQneUn2Huc4oeLd2B2HzkGnjAff4hWTJT6C7qHYB1Mv2wU5iHHGFWkhnTX9WsEAbunJCV2qcaXScKj4tTfvdDKfLiVuU2av6hbsMztirRze7LvYBkRHV3tGwyCptsrP", "client_ip": "127.0.0.1", "client_port": 9708, "node_ip": "127.0.0.1", "node_port": 9707, "services": ["VALIDATOR"]}, "dest": "4PS3EDQ3dW1tci1Bp6543CfuuebjFrg36kLAUcskGfaA"}, "metadata": {"from": "TWwCRQRZ2ZHMJFn9TzLp7W"}, "type": "0"}, "txnMetadata": {"seqNo": 4, "txnId": "aa5e817d7cc626170eca175822029339a444eb0ee8f0bd20d3b0b76e566fb008"}, "ver": "1"} diff --git a/yarn.lock b/yarn.lock index 7897dca13a..87c7ee9ec5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2,79 +2,60 @@ # yarn lockfile v1 -"@ampproject/remapping@^2.1.0": - version "2.2.0" - resolved "https://registry.yarnpkg.com/@ampproject/remapping/-/remapping-2.2.0.tgz#56c133824780de3174aed5ab6834f3026790154d" - integrity sha512-qRmjj8nj9qmLTQXXmaR1cck3UXSRMPrbsLJAasZpF+t3riI71BXed5ebIOYwQntykeZuhjsdweEc9BxH5Jc26w== +"@ampproject/remapping@^2.2.0": + version "2.2.1" + resolved "https://registry.yarnpkg.com/@ampproject/remapping/-/remapping-2.2.1.tgz#99e8e11851128b8702cd57c33684f1d0f260b630" + integrity sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg== dependencies: - "@jridgewell/gen-mapping" "^0.1.0" + "@jridgewell/gen-mapping" "^0.3.0" "@jridgewell/trace-mapping" "^0.3.9" -"@aries-framework/node@0.2.5": - version "0.2.5" - resolved "https://registry.yarnpkg.com/@aries-framework/node/-/node-0.2.5.tgz#f23a08813355eec066c9c0942743039d72f87bc5" - integrity sha512-dz0a2RscytlGr5zPK6+ic0EpGE2qKbvCfvwm2MhqVC6qZQEOSo6lbsiIItDh1j+CyG/vIJiZFAUH5s+EzgNN4A== - dependencies: - "@aries-framework/core" "0.2.5" - express "^4.17.1" - ffi-napi "^4.0.3" - indy-sdk "^1.16.0-dev-1636" - node-fetch "^2.6.1" - ref-napi "^3.0.3" - ws "^7.5.3" - -"@azure/core-asynciterator-polyfill@^1.0.0": +"@azure/core-asynciterator-polyfill@^1.0.2": version "1.0.2" resolved "https://registry.yarnpkg.com/@azure/core-asynciterator-polyfill/-/core-asynciterator-polyfill-1.0.2.tgz#0dd3849fb8d97f062a39db0e5cadc9ffaf861fec" integrity sha512-3rkP4LnnlWawl0LZptJOdXNrT/fHp2eQMadoasa6afspXdpGrtPZuAQc2PD0cpgyuoXtUWyC3tv7xfntjGS5Dw== -"@babel/code-frame@7.12.11": - version "7.12.11" - resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.12.11.tgz#f4ad435aa263db935b8f10f2c552d23fb716a63f" - integrity sha512-Zt1yodBx1UcyiePMSkWnU4hPqhwq7hGi2nFL1LeA3EUl+q2LQx16MISgJ0+z7dnmgvP9QtIleuETGOiOH1RcIw== - dependencies: - "@babel/highlight" "^7.10.4" - -"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.12.13", "@babel/code-frame@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.18.6.tgz#3b25d38c89600baa2dcc219edfa88a74eb2c427a" - integrity sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q== +"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.12.13", "@babel/code-frame@^7.18.6", "@babel/code-frame@^7.21.4": + version "7.21.4" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.21.4.tgz#d0fa9e4413aca81f2b23b9442797bda1826edb39" + integrity sha512-LYvhNKfwWSPpocw8GI7gpK2nq3HSDuEPC/uSYaALSJu9xjsalaaYFOq0Pwt5KmVqwEbZlDu81aLXwBOmD/Fv9g== dependencies: "@babel/highlight" "^7.18.6" -"@babel/compat-data@^7.17.7", "@babel/compat-data@^7.18.8", "@babel/compat-data@^7.19.1": - version "7.19.1" - resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.19.1.tgz#72d647b4ff6a4f82878d184613353af1dd0290f9" - integrity sha512-72a9ghR0gnESIa7jBN53U32FOVCEoztyIlKaNoU05zRhEecduGK9L9c3ww7Mp06JiR+0ls0GBPFJQwwtjn9ksg== - -"@babel/core@^7.0.0", "@babel/core@^7.1.0", "@babel/core@^7.1.6", "@babel/core@^7.12.3", "@babel/core@^7.7.2", "@babel/core@^7.8.0": - version "7.19.1" - resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.19.1.tgz#c8fa615c5e88e272564ace3d42fbc8b17bfeb22b" - integrity sha512-1H8VgqXme4UXCRv7/Wa1bq7RVymKOzC7znjyFM8KiEzwFqcKUKYNoQef4GhdklgNvoBXyW4gYhuBNCM5o1zImw== - dependencies: - "@ampproject/remapping" "^2.1.0" - "@babel/code-frame" "^7.18.6" - "@babel/generator" "^7.19.0" - "@babel/helper-compilation-targets" "^7.19.1" - "@babel/helper-module-transforms" "^7.19.0" - "@babel/helpers" "^7.19.0" - "@babel/parser" "^7.19.1" - "@babel/template" "^7.18.10" - "@babel/traverse" "^7.19.1" - "@babel/types" "^7.19.0" +"@babel/compat-data@^7.17.7", "@babel/compat-data@^7.20.5", "@babel/compat-data@^7.21.4": + version "7.21.4" + resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.21.4.tgz#457ffe647c480dff59c2be092fc3acf71195c87f" + integrity sha512-/DYyDpeCfaVinT40FPGdkkb+lYSKvsVuMjDAG7jPOWWiM1ibOaB9CXJAlc4d1QpP/U2q2P9jbrSlClKSErd55g== + +"@babel/core@^7.11.6", "@babel/core@^7.12.3", "@babel/core@^7.13.16", "@babel/core@^7.20.0": + version "7.21.4" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.21.4.tgz#c6dc73242507b8e2a27fd13a9c1814f9fa34a659" + integrity sha512-qt/YV149Jman/6AfmlxJ04LMIu8bMoyl3RB91yTFrxQmgbrSvQMy7cI8Q62FHx1t8wJ8B5fu0UDoLwHAhUo1QA== + dependencies: + "@ampproject/remapping" "^2.2.0" + "@babel/code-frame" "^7.21.4" + "@babel/generator" "^7.21.4" + "@babel/helper-compilation-targets" "^7.21.4" + "@babel/helper-module-transforms" "^7.21.2" + "@babel/helpers" "^7.21.0" + "@babel/parser" "^7.21.4" + "@babel/template" "^7.20.7" + "@babel/traverse" "^7.21.4" + "@babel/types" "^7.21.4" convert-source-map "^1.7.0" debug "^4.1.0" gensync "^1.0.0-beta.2" - json5 "^2.2.1" + json5 "^2.2.2" semver "^6.3.0" -"@babel/generator@^7.19.0", "@babel/generator@^7.5.0", "@babel/generator@^7.7.2": - version "7.19.0" - resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.19.0.tgz#785596c06425e59334df2ccee63ab166b738419a" - integrity sha512-S1ahxf1gZ2dpoiFgA+ohK9DIpz50bJ0CWs7Zlzb54Z4sG8qmdIrGrVqmy1sAtTVRb+9CU6U8VqT9L0Zj7hxHVg== +"@babel/generator@^7.20.0", "@babel/generator@^7.21.4", "@babel/generator@^7.7.2": + version "7.21.4" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.21.4.tgz#64a94b7448989f421f919d5239ef553b37bb26bc" + integrity sha512-NieM3pVIYW2SwGzKoqfPrQsf4xGs9M9AIG3ThppsSRmO+m7eQhmI6amajKMUeIO37wFfsvnvcxQFx6x6iqxDnA== dependencies: - "@babel/types" "^7.19.0" + "@babel/types" "^7.21.4" "@jridgewell/gen-mapping" "^0.3.2" + "@jridgewell/trace-mapping" "^0.3.17" jsesc "^2.5.1" "@babel/helper-annotate-as-pure@^7.18.6": @@ -84,44 +65,38 @@ dependencies: "@babel/types" "^7.18.6" -"@babel/helper-builder-binary-assignment-operator-visitor@^7.18.6": - version "7.18.9" - resolved "https://registry.yarnpkg.com/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.18.9.tgz#acd4edfd7a566d1d51ea975dff38fd52906981bb" - integrity sha512-yFQ0YCHoIqarl8BCRwBL8ulYUaZpz3bNsA7oFepAzee+8/+ImtADXNOmO5vJvsPff3qi+hvpkY/NYBTrBQgdNw== - dependencies: - "@babel/helper-explode-assignable-expression" "^7.18.6" - "@babel/types" "^7.18.9" - -"@babel/helper-compilation-targets@^7.17.7", "@babel/helper-compilation-targets@^7.18.9", "@babel/helper-compilation-targets@^7.19.0", "@babel/helper-compilation-targets@^7.19.1": - version "7.19.1" - resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.19.1.tgz#7f630911d83b408b76fe584831c98e5395d7a17c" - integrity sha512-LlLkkqhCMyz2lkQPvJNdIYU7O5YjWRgC2R4omjCTpZd8u8KMQzZvX4qce+/BluN1rcQiV7BoGUpmQ0LeHerbhg== +"@babel/helper-compilation-targets@^7.17.7", "@babel/helper-compilation-targets@^7.18.9", "@babel/helper-compilation-targets@^7.20.7", "@babel/helper-compilation-targets@^7.21.4": + version "7.21.4" + resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.21.4.tgz#770cd1ce0889097ceacb99418ee6934ef0572656" + integrity sha512-Fa0tTuOXZ1iL8IeDFUWCzjZcn+sJGd9RZdH9esYVjEejGmzf+FFYQpMi/kZUk2kPy/q1H3/GPw7np8qar/stfg== dependencies: - "@babel/compat-data" "^7.19.1" - "@babel/helper-validator-option" "^7.18.6" + "@babel/compat-data" "^7.21.4" + "@babel/helper-validator-option" "^7.21.0" browserslist "^4.21.3" + lru-cache "^5.1.1" semver "^6.3.0" -"@babel/helper-create-class-features-plugin@^7.18.6", "@babel/helper-create-class-features-plugin@^7.19.0": - version "7.19.0" - resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.19.0.tgz#bfd6904620df4e46470bae4850d66be1054c404b" - integrity sha512-NRz8DwF4jT3UfrmUoZjd0Uph9HQnP30t7Ash+weACcyNkiYTywpIjDBgReJMKgr+n86sn2nPVVmJ28Dm053Kqw== +"@babel/helper-create-class-features-plugin@^7.18.6", "@babel/helper-create-class-features-plugin@^7.21.0": + version "7.21.4" + resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.21.4.tgz#3a017163dc3c2ba7deb9a7950849a9586ea24c18" + integrity sha512-46QrX2CQlaFRF4TkwfTt6nJD7IHq8539cCL7SDpqWSDeJKY1xylKKY5F/33mJhLZ3mFvKv2gGrVS6NkyF6qs+Q== dependencies: "@babel/helper-annotate-as-pure" "^7.18.6" "@babel/helper-environment-visitor" "^7.18.9" - "@babel/helper-function-name" "^7.19.0" - "@babel/helper-member-expression-to-functions" "^7.18.9" + "@babel/helper-function-name" "^7.21.0" + "@babel/helper-member-expression-to-functions" "^7.21.0" "@babel/helper-optimise-call-expression" "^7.18.6" - "@babel/helper-replace-supers" "^7.18.9" + "@babel/helper-replace-supers" "^7.20.7" + "@babel/helper-skip-transparent-expression-wrappers" "^7.20.0" "@babel/helper-split-export-declaration" "^7.18.6" -"@babel/helper-create-regexp-features-plugin@^7.18.6": - version "7.19.0" - resolved "https://registry.yarnpkg.com/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.19.0.tgz#7976aca61c0984202baca73d84e2337a5424a41b" - integrity sha512-htnV+mHX32DF81amCDrwIDr8nrp1PTm+3wfBN9/v8QJOLEioOCOG7qNyq0nHeFiWbT3Eb7gsPwEmV64UCQ1jzw== +"@babel/helper-create-regexp-features-plugin@^7.18.6", "@babel/helper-create-regexp-features-plugin@^7.20.5": + version "7.21.4" + resolved "https://registry.yarnpkg.com/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.21.4.tgz#40411a8ab134258ad2cf3a3d987ec6aa0723cee5" + integrity sha512-M00OuhU+0GyZ5iBBN9czjugzWrEq2vDpf/zCYHxxf93ul/Q5rv+a5h+/+0WnI1AebHNVtl5bFV0qsJoH23DbfA== dependencies: "@babel/helper-annotate-as-pure" "^7.18.6" - regexpu-core "^5.1.0" + regexpu-core "^5.3.1" "@babel/helper-define-polyfill-provider@^0.3.3": version "0.3.3" @@ -140,20 +115,13 @@ resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.18.9.tgz#0c0cee9b35d2ca190478756865bb3528422f51be" integrity sha512-3r/aACDJ3fhQ/EVgFy0hpj8oHyHpQc+LPtJoY9SzTThAsStm4Ptegq92vqKoE3vD706ZVFWITnMnxucw+S9Ipg== -"@babel/helper-explode-assignable-expression@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.18.6.tgz#41f8228ef0a6f1a036b8dfdfec7ce94f9a6bc096" - integrity sha512-eyAYAsQmB80jNfg4baAtLeWAQHfHFiR483rzFK+BhETlGZaQC9bsfrugfXDCbRHLQbIA7U5NxhhOxN7p/dWIcg== - dependencies: - "@babel/types" "^7.18.6" - -"@babel/helper-function-name@^7.18.9", "@babel/helper-function-name@^7.19.0": - version "7.19.0" - resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.19.0.tgz#941574ed5390682e872e52d3f38ce9d1bef4648c" - integrity sha512-WAwHBINyrpqywkUH0nTnNgI5ina5TFn85HKS0pbPDfxFfhyR/aNQEn4hGi1P1JyT//I0t4OgXUlofzWILRvS5w== +"@babel/helper-function-name@^7.18.9", "@babel/helper-function-name@^7.19.0", "@babel/helper-function-name@^7.21.0": + version "7.21.0" + resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.21.0.tgz#d552829b10ea9f120969304023cd0645fa00b1b4" + integrity sha512-HfK1aMRanKHpxemaY2gqBmL04iAPOPRj7DxtNbiDOrJK+gdwkiNRVpCpUJYbUT+aZyemKN8brqTOxzCaG6ExRg== dependencies: - "@babel/template" "^7.18.10" - "@babel/types" "^7.19.0" + "@babel/template" "^7.20.7" + "@babel/types" "^7.21.0" "@babel/helper-hoist-variables@^7.18.6": version "7.18.6" @@ -162,33 +130,33 @@ dependencies: "@babel/types" "^7.18.6" -"@babel/helper-member-expression-to-functions@^7.18.9": - version "7.18.9" - resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.18.9.tgz#1531661e8375af843ad37ac692c132841e2fd815" - integrity sha512-RxifAh2ZoVU67PyKIO4AMi1wTenGfMR/O/ae0CCRqwgBAt5v7xjdtRw7UoSbsreKrQn5t7r89eruK/9JjYHuDg== +"@babel/helper-member-expression-to-functions@^7.20.7", "@babel/helper-member-expression-to-functions@^7.21.0": + version "7.21.0" + resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.21.0.tgz#319c6a940431a133897148515877d2f3269c3ba5" + integrity sha512-Muu8cdZwNN6mRRNG6lAYErJ5X3bRevgYR2O8wN0yn7jJSnGDu6eG59RfT29JHxGUovyfrh6Pj0XzmR7drNVL3Q== dependencies: - "@babel/types" "^7.18.9" + "@babel/types" "^7.21.0" -"@babel/helper-module-imports@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.18.6.tgz#1e3ebdbbd08aad1437b428c50204db13c5a3ca6e" - integrity sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA== +"@babel/helper-module-imports@^7.18.6", "@babel/helper-module-imports@^7.21.4": + version "7.21.4" + resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.21.4.tgz#ac88b2f76093637489e718a90cec6cf8a9b029af" + integrity sha512-orajc5T2PsRYUN3ZryCEFeMDYwyw09c/pZeaQEZPH0MpKzSvn3e0uXsDBu3k03VI+9DBiRo+l22BfKTpKwa/Wg== dependencies: - "@babel/types" "^7.18.6" + "@babel/types" "^7.21.4" -"@babel/helper-module-transforms@^7.18.6", "@babel/helper-module-transforms@^7.19.0": - version "7.19.0" - resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.19.0.tgz#309b230f04e22c58c6a2c0c0c7e50b216d350c30" - integrity sha512-3HBZ377Fe14RbLIA+ac3sY4PTgpxHVkFrESaWhoI5PuyXPBBX8+C34qblV9G89ZtycGJCmCI/Ut+VUDK4bltNQ== +"@babel/helper-module-transforms@^7.21.2": + version "7.21.2" + resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.21.2.tgz#160caafa4978ac8c00ac66636cb0fa37b024e2d2" + integrity sha512-79yj2AR4U/Oqq/WOV7Lx6hUjau1Zfo4cI+JLAVYeMV5XIlbOhmjEk5ulbTc9fMpmlojzZHkUUxAiK+UKn+hNQQ== dependencies: "@babel/helper-environment-visitor" "^7.18.9" "@babel/helper-module-imports" "^7.18.6" - "@babel/helper-simple-access" "^7.18.6" + "@babel/helper-simple-access" "^7.20.2" "@babel/helper-split-export-declaration" "^7.18.6" - "@babel/helper-validator-identifier" "^7.18.6" - "@babel/template" "^7.18.10" - "@babel/traverse" "^7.19.0" - "@babel/types" "^7.19.0" + "@babel/helper-validator-identifier" "^7.19.1" + "@babel/template" "^7.20.7" + "@babel/traverse" "^7.21.2" + "@babel/types" "^7.21.2" "@babel/helper-optimise-call-expression@^7.18.6": version "7.18.6" @@ -197,35 +165,46 @@ dependencies: "@babel/types" "^7.18.6" -"@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.10.4", "@babel/helper-plugin-utils@^7.12.13", "@babel/helper-plugin-utils@^7.14.5", "@babel/helper-plugin-utils@^7.16.7", "@babel/helper-plugin-utils@^7.18.6", "@babel/helper-plugin-utils@^7.18.9", "@babel/helper-plugin-utils@^7.19.0", "@babel/helper-plugin-utils@^7.8.0": - version "7.19.0" - resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.19.0.tgz#4796bb14961521f0f8715990bee2fb6e51ce21bf" - integrity sha512-40Ryx7I8mT+0gaNxm8JGTZFUITNqdLAgdg0hXzeVZxVD6nFsdhQvip6v8dqkRHzsz1VFpFAaOCHNn0vKBL7Czw== +"@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.10.4", "@babel/helper-plugin-utils@^7.12.13", "@babel/helper-plugin-utils@^7.14.5", "@babel/helper-plugin-utils@^7.16.7", "@babel/helper-plugin-utils@^7.18.6", "@babel/helper-plugin-utils@^7.18.9", "@babel/helper-plugin-utils@^7.19.0", "@babel/helper-plugin-utils@^7.20.2", "@babel/helper-plugin-utils@^7.8.0": + version "7.20.2" + resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.20.2.tgz#d1b9000752b18d0877cff85a5c376ce5c3121629" + integrity sha512-8RvlJG2mj4huQ4pZ+rU9lqKi9ZKiRmuvGuM2HlWmkmgOhbs6zEAw6IEiJ5cQqGbDzGZOhwuOQNtZMi/ENLjZoQ== -"@babel/helper-replace-supers@^7.18.6", "@babel/helper-replace-supers@^7.18.9": - version "7.19.1" - resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.19.1.tgz#e1592a9b4b368aa6bdb8784a711e0bcbf0612b78" - integrity sha512-T7ahH7wV0Hfs46SFh5Jz3s0B6+o8g3c+7TMxu7xKfmHikg7EAZ3I2Qk9LFhjxXq8sL7UkP5JflezNwoZa8WvWw== +"@babel/helper-remap-async-to-generator@^7.18.9": + version "7.18.9" + resolved "https://registry.yarnpkg.com/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.18.9.tgz#997458a0e3357080e54e1d79ec347f8a8cd28519" + integrity sha512-dI7q50YKd8BAv3VEfgg7PS7yD3Rtbi2J1XMXaalXO0W0164hYLnh8zpjRS0mte9MfVp/tltvr/cfdXPvJr1opA== + dependencies: + "@babel/helper-annotate-as-pure" "^7.18.6" + "@babel/helper-environment-visitor" "^7.18.9" + "@babel/helper-wrap-function" "^7.18.9" + "@babel/types" "^7.18.9" + +"@babel/helper-replace-supers@^7.18.6", "@babel/helper-replace-supers@^7.20.7": + version "7.20.7" + resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.20.7.tgz#243ecd2724d2071532b2c8ad2f0f9f083bcae331" + integrity sha512-vujDMtB6LVfNW13jhlCrp48QNslK6JXi7lQG736HVbHz/mbf4Dc7tIRh1Xf5C0rF7BP8iiSxGMCmY6Ci1ven3A== dependencies: "@babel/helper-environment-visitor" "^7.18.9" - "@babel/helper-member-expression-to-functions" "^7.18.9" + "@babel/helper-member-expression-to-functions" "^7.20.7" "@babel/helper-optimise-call-expression" "^7.18.6" - "@babel/traverse" "^7.19.1" - "@babel/types" "^7.19.0" + "@babel/template" "^7.20.7" + "@babel/traverse" "^7.20.7" + "@babel/types" "^7.20.7" -"@babel/helper-simple-access@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.18.6.tgz#d6d8f51f4ac2978068df934b569f08f29788c7ea" - integrity sha512-iNpIgTgyAvDQpDj76POqg+YEt8fPxx3yaNBg3S30dxNKm2SWfYhD0TGrK/Eu9wHpUW63VQU894TsTg+GLbUa1g== +"@babel/helper-simple-access@^7.20.2": + version "7.20.2" + resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.20.2.tgz#0ab452687fe0c2cfb1e2b9e0015de07fc2d62dd9" + integrity sha512-+0woI/WPq59IrqDYbVGfshjT5Dmk/nnbdpcF8SnMhhXObpTq2KNBdLFRFrkVdbDOyUmHBCxzm5FHV1rACIkIbA== dependencies: - "@babel/types" "^7.18.6" + "@babel/types" "^7.20.2" -"@babel/helper-skip-transparent-expression-wrappers@^7.18.9": - version "7.18.9" - resolved "https://registry.yarnpkg.com/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.18.9.tgz#778d87b3a758d90b471e7b9918f34a9a02eb5818" - integrity sha512-imytd2gHi3cJPsybLRbmFrF7u5BIEuI2cNheyKi3/iOBC63kNn3q8Crn2xVuESli0aM4KYsyEqKyS7lFL8YVtw== +"@babel/helper-skip-transparent-expression-wrappers@^7.20.0": + version "7.20.0" + resolved "https://registry.yarnpkg.com/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.20.0.tgz#fbe4c52f60518cab8140d77101f0e63a8a230684" + integrity sha512-5y1JYeNKfvnT8sZcK9DVRtpTbGiomYIHviSP3OQWmDPU3DeH4a1ZlT/N2lyQ5P8egjcRaT/Y9aNqUxK0WsnIIg== dependencies: - "@babel/types" "^7.18.9" + "@babel/types" "^7.20.0" "@babel/helper-split-export-declaration@^7.18.6": version "7.18.6" @@ -234,31 +213,41 @@ dependencies: "@babel/types" "^7.18.6" -"@babel/helper-string-parser@^7.18.10": - version "7.18.10" - resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.18.10.tgz#181f22d28ebe1b3857fa575f5c290b1aaf659b56" - integrity sha512-XtIfWmeNY3i4t7t4D2t02q50HvqHybPqW2ki1kosnvWCwuCMeo81Jf0gwr85jy/neUdg5XDdeFE/80DXiO+njw== +"@babel/helper-string-parser@^7.19.4": + version "7.19.4" + resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.19.4.tgz#38d3acb654b4701a9b77fb0615a96f775c3a9e63" + integrity sha512-nHtDoQcuqFmwYNYPz3Rah5ph2p8PFeFCsZk9A/48dPc/rGocJ5J3hAAZ7pb76VWX3fZKu+uEr/FhH5jLx7umrw== -"@babel/helper-validator-identifier@^7.18.6": +"@babel/helper-validator-identifier@^7.18.6", "@babel/helper-validator-identifier@^7.19.1": version "7.19.1" resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz#7eea834cf32901ffdc1a7ee555e2f9c27e249ca2" integrity sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w== -"@babel/helper-validator-option@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.18.6.tgz#bf0d2b5a509b1f336099e4ff36e1a63aa5db4db8" - integrity sha512-XO7gESt5ouv/LRJdrVjkShckw6STTaB7l9BrpBaAHDeF5YZT+01PCwmR0SJHnkW6i8OwW/EVWRShfi4j2x+KQw== +"@babel/helper-validator-option@^7.21.0": + version "7.21.0" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.21.0.tgz#8224c7e13ace4bafdc4004da2cf064ef42673180" + integrity sha512-rmL/B8/f0mKS2baE9ZpyTcTavvEuWhTTW8amjzXNvYG4AwBsqTLikfXsEofsJEfKHf+HQVQbFOHy6o+4cnC/fQ== -"@babel/helpers@^7.19.0": - version "7.19.0" - resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.19.0.tgz#f30534657faf246ae96551d88dd31e9d1fa1fc18" - integrity sha512-DRBCKGwIEdqY3+rPJgG/dKfQy9+08rHIAJx8q2p+HSWP87s2HCrQmaAMMyMll2kIXKCW0cO1RdQskx15Xakftg== +"@babel/helper-wrap-function@^7.18.9": + version "7.20.5" + resolved "https://registry.yarnpkg.com/@babel/helper-wrap-function/-/helper-wrap-function-7.20.5.tgz#75e2d84d499a0ab3b31c33bcfe59d6b8a45f62e3" + integrity sha512-bYMxIWK5mh+TgXGVqAtnu5Yn1un+v8DDZtqyzKRLUzrh70Eal2O3aZ7aPYiMADO4uKlkzOiRiZ6GX5q3qxvW9Q== dependencies: + "@babel/helper-function-name" "^7.19.0" "@babel/template" "^7.18.10" - "@babel/traverse" "^7.19.0" - "@babel/types" "^7.19.0" + "@babel/traverse" "^7.20.5" + "@babel/types" "^7.20.5" + +"@babel/helpers@^7.21.0": + version "7.21.0" + resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.21.0.tgz#9dd184fb5599862037917cdc9eecb84577dc4e7e" + integrity sha512-XXve0CBtOW0pd7MRzzmoyuSj0e3SEzj8pgyFxnTT1NJZL38BD1MK7yYrm8yefRPIDvNNe14xR4FdbHwpInD4rA== + dependencies: + "@babel/template" "^7.20.7" + "@babel/traverse" "^7.21.0" + "@babel/types" "^7.21.0" -"@babel/highlight@^7.10.4", "@babel/highlight@^7.18.6": +"@babel/highlight@^7.18.6": version "7.18.6" resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.18.6.tgz#81158601e93e2563795adcbfbdf5d64be3f2ecdf" integrity sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g== @@ -267,12 +256,22 @@ chalk "^2.0.0" js-tokens "^4.0.0" -"@babel/parser@^7.0.0", "@babel/parser@^7.1.0", "@babel/parser@^7.1.6", "@babel/parser@^7.14.7", "@babel/parser@^7.18.10", "@babel/parser@^7.19.1": - version "7.19.1" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.19.1.tgz#6f6d6c2e621aad19a92544cc217ed13f1aac5b4c" - integrity sha512-h7RCSorm1DdTVGJf3P2Mhj3kdnkmF/EiysUkzS2TdgAYqyjFdMQJbVuXOBej2SBJaXan/lIVtT6KkGbyyq753A== +"@babel/parser@^7.1.0", "@babel/parser@^7.13.16", "@babel/parser@^7.14.0", "@babel/parser@^7.14.7", "@babel/parser@^7.20.0", "@babel/parser@^7.20.7", "@babel/parser@^7.21.4": + version "7.21.4" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.21.4.tgz#94003fdfc520bbe2875d4ae557b43ddb6d880f17" + integrity sha512-alVJj7k7zIxqBZ7BTRhz0IqJFxW1VJbm6N8JbcYhQ186df9ZBPbZBmWSqAMXwHGsCJdYks7z/voa3ibiS5bCIw== + +"@babel/plugin-proposal-async-generator-functions@^7.0.0": + version "7.20.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.20.7.tgz#bfb7276d2d573cb67ba379984a2334e262ba5326" + integrity sha512-xMbiLsn/8RK7Wq7VeVytytS2L6qE69bXPB10YCmMdDZbKF4okCqY74pI/jJQ/8U0b/F6NrT2+14b8/P9/3AMGA== + dependencies: + "@babel/helper-environment-visitor" "^7.18.9" + "@babel/helper-plugin-utils" "^7.20.2" + "@babel/helper-remap-async-to-generator" "^7.18.9" + "@babel/plugin-syntax-async-generators" "^7.8.4" -"@babel/plugin-proposal-class-properties@^7.0.0", "@babel/plugin-proposal-class-properties@^7.1.0": +"@babel/plugin-proposal-class-properties@^7.0.0", "@babel/plugin-proposal-class-properties@^7.13.0": version "7.18.6" resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.18.6.tgz#b110f59741895f7ec21a6fff696ec46265c446a3" integrity sha512-cumfXOF0+nzZrrN8Rf0t7M+tF6sZc7vhQwYQck9q1/5w2OExlD+b4v4RpMJFaV1Z7WcDRgO6FqvxqxGlwo+RHQ== @@ -288,7 +287,7 @@ "@babel/helper-plugin-utils" "^7.18.9" "@babel/plugin-syntax-export-default-from" "^7.18.6" -"@babel/plugin-proposal-nullish-coalescing-operator@^7.0.0", "@babel/plugin-proposal-nullish-coalescing-operator@^7.1.0": +"@babel/plugin-proposal-nullish-coalescing-operator@^7.0.0", "@babel/plugin-proposal-nullish-coalescing-operator@^7.13.8": version "7.18.6" resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.18.6.tgz#fdd940a99a740e577d6c753ab6fbb43fdb9467e1" integrity sha512-wQxQzxYeJqHcfppzBDnm1yAY0jSRkUXR2z8RePZYrKwMKgMlE8+Z6LUno+bd6LvbGh8Gltvy74+9pIYkr+XkKA== @@ -297,15 +296,15 @@ "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.3" "@babel/plugin-proposal-object-rest-spread@^7.0.0": - version "7.18.9" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.18.9.tgz#f9434f6beb2c8cae9dfcf97d2a5941bbbf9ad4e7" - integrity sha512-kDDHQ5rflIeY5xl69CEqGEZ0KY369ehsCIEbTGb4siHG5BE9sga/T0r0OUwyZNLMmZE79E1kbsqAjwFCW4ds6Q== + version "7.20.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.20.7.tgz#aa662940ef425779c75534a5c41e9d936edc390a" + integrity sha512-d2S98yCiLxDVmBmE8UjGcfPvNEUbA1U5q5WxaWFUGRzJSVAZqm5W6MbPct0jxnegUZ0niLeNX+IOzEs7wYg9Dg== dependencies: - "@babel/compat-data" "^7.18.8" - "@babel/helper-compilation-targets" "^7.18.9" - "@babel/helper-plugin-utils" "^7.18.9" + "@babel/compat-data" "^7.20.5" + "@babel/helper-compilation-targets" "^7.20.7" + "@babel/helper-plugin-utils" "^7.20.2" "@babel/plugin-syntax-object-rest-spread" "^7.8.3" - "@babel/plugin-transform-parameters" "^7.18.8" + "@babel/plugin-transform-parameters" "^7.20.7" "@babel/plugin-proposal-optional-catch-binding@^7.0.0": version "7.18.6" @@ -315,13 +314,13 @@ "@babel/helper-plugin-utils" "^7.18.6" "@babel/plugin-syntax-optional-catch-binding" "^7.8.3" -"@babel/plugin-proposal-optional-chaining@^7.0.0", "@babel/plugin-proposal-optional-chaining@^7.1.0": - version "7.18.9" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.18.9.tgz#e8e8fe0723f2563960e4bf5e9690933691915993" - integrity sha512-v5nwt4IqBXihxGsW2QmCWMDS3B3bzGIk/EQVZz2ei7f3NJl8NzAJVvUmpDW5q1CRNY+Beb/k58UAH1Km1N411w== +"@babel/plugin-proposal-optional-chaining@^7.0.0", "@babel/plugin-proposal-optional-chaining@^7.13.12": + version "7.21.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.21.0.tgz#886f5c8978deb7d30f678b2e24346b287234d3ea" + integrity sha512-p4zeefM72gpmEe2fkUr/OnOXpWEf8nAgk7ZYVqqfFiyIG7oFfVZcCrU64hWn5xp4tQ9LkV4bTIa5rD0KANpKNA== dependencies: - "@babel/helper-plugin-utils" "^7.18.9" - "@babel/helper-skip-transparent-expression-wrappers" "^7.18.9" + "@babel/helper-plugin-utils" "^7.20.2" + "@babel/helper-skip-transparent-expression-wrappers" "^7.20.0" "@babel/plugin-syntax-optional-chaining" "^7.8.3" "@babel/plugin-syntax-async-generators@^7.8.4": @@ -359,12 +358,12 @@ dependencies: "@babel/helper-plugin-utils" "^7.18.6" -"@babel/plugin-syntax-flow@^7.0.0", "@babel/plugin-syntax-flow@^7.18.6", "@babel/plugin-syntax-flow@^7.2.0": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-flow/-/plugin-syntax-flow-7.18.6.tgz#774d825256f2379d06139be0c723c4dd444f3ca1" - integrity sha512-LUbR+KNTBWCUAqRG9ex5Gnzu2IOkt8jRJbHHXFT9q+L9zm7M/QQbEqXyw1n1pohYvOyWC8CjeyjrSaIwiYjK7A== +"@babel/plugin-syntax-flow@^7.0.0", "@babel/plugin-syntax-flow@^7.18.0", "@babel/plugin-syntax-flow@^7.18.6": + version "7.21.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-flow/-/plugin-syntax-flow-7.21.4.tgz#3e37fca4f06d93567c1cd9b75156422e90a67107" + integrity sha512-l9xd3N+XG4fZRxEP3vXdK6RW7vN1Uf5dxzRC/09wV86wqZ/YYQooBIGNsiRdfNR3/q2/5pPzV4B54J/9ctX5jw== dependencies: - "@babel/helper-plugin-utils" "^7.18.6" + "@babel/helper-plugin-utils" "^7.20.2" "@babel/plugin-syntax-import-meta@^7.8.3": version "7.10.4" @@ -380,12 +379,12 @@ dependencies: "@babel/helper-plugin-utils" "^7.8.0" -"@babel/plugin-syntax-jsx@^7.0.0", "@babel/plugin-syntax-jsx@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.18.6.tgz#a8feef63b010150abd97f1649ec296e849943ca0" - integrity sha512-6mmljtAedFGTWu2p/8WIORGwy+61PLgOMPOdazc7YoJ9ZCWUyFy3A6CpPkRKLKD1ToAesxX8KGEViAiLo9N+7Q== +"@babel/plugin-syntax-jsx@^7.0.0", "@babel/plugin-syntax-jsx@^7.18.6", "@babel/plugin-syntax-jsx@^7.21.4", "@babel/plugin-syntax-jsx@^7.7.2": + version "7.21.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.21.4.tgz#f264ed7bf40ffc9ec239edabc17a50c4f5b6fea2" + integrity sha512-5hewiLct5OKyh6PLKEYaFclcqtIgCb6bmELouxjF6up5q3Sov7rOayW4RwhbaBL0dit8rA80GNfY+UuDp2mBbQ== dependencies: - "@babel/helper-plugin-utils" "^7.18.6" + "@babel/helper-plugin-utils" "^7.20.2" "@babel/plugin-syntax-logical-assignment-operators@^7.8.3": version "7.10.4" @@ -436,19 +435,28 @@ dependencies: "@babel/helper-plugin-utils" "^7.14.5" -"@babel/plugin-syntax-typescript@^7.18.6", "@babel/plugin-syntax-typescript@^7.7.2": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.18.6.tgz#1c09cd25795c7c2b8a4ba9ae49394576d4133285" - integrity sha512-mAWAuq4rvOepWCBid55JuRNvpTNf2UGVgoz4JV0fXEKolsVZDzsa4NqCef758WZJj/GDu0gVGItjKFiClTAmZA== +"@babel/plugin-syntax-typescript@^7.20.0", "@babel/plugin-syntax-typescript@^7.7.2": + version "7.21.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.21.4.tgz#2751948e9b7c6d771a8efa59340c15d4a2891ff8" + integrity sha512-xz0D39NvhQn4t4RNsHmDnnsaQizIlUkdtYvLs8La1BlfjQ6JEwxkJGeqJMW2tAXx+q6H+WFuUTXNdYVpEya0YA== dependencies: - "@babel/helper-plugin-utils" "^7.18.6" + "@babel/helper-plugin-utils" "^7.20.2" "@babel/plugin-transform-arrow-functions@^7.0.0": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.18.6.tgz#19063fcf8771ec7b31d742339dac62433d0611fe" - integrity sha512-9S9X9RUefzrsHZmKMbDXxweEH+YlE8JJEuat9FdvW9Qh1cw7W64jELCtWNkPBPX5En45uy28KGvA/AySqUh8CQ== + version "7.20.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.20.7.tgz#bea332b0e8b2dab3dafe55a163d8227531ab0551" + integrity sha512-3poA5E7dzDomxj9WXWwuD6A5F3kc7VXwIJO+E+J8qtDtS+pXPAhrgEyh+9GBwBgPq1Z+bB+/JD60lp5jsN7JPQ== dependencies: - "@babel/helper-plugin-utils" "^7.18.6" + "@babel/helper-plugin-utils" "^7.20.2" + +"@babel/plugin-transform-async-to-generator@^7.0.0": + version "7.20.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.20.7.tgz#dfee18623c8cb31deb796aa3ca84dda9cea94354" + integrity sha512-Uo5gwHPT9vgnSXQxqGtpdufUiWp96gk7yiP4Mp5bm1QMkEmLXBO7PAGYbKoJ6DhAwiNkcHFBol/x5zZZkL/t0Q== + dependencies: + "@babel/helper-module-imports" "^7.18.6" + "@babel/helper-plugin-utils" "^7.20.2" + "@babel/helper-remap-async-to-generator" "^7.18.9" "@babel/plugin-transform-block-scoped-functions@^7.0.0": version "7.18.6" @@ -458,63 +466,56 @@ "@babel/helper-plugin-utils" "^7.18.6" "@babel/plugin-transform-block-scoping@^7.0.0": - version "7.18.9" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.18.9.tgz#f9b7e018ac3f373c81452d6ada8bd5a18928926d" - integrity sha512-5sDIJRV1KtQVEbt/EIBwGy4T01uYIo4KRB3VUqzkhrAIOGx7AoctL9+Ux88btY0zXdDyPJ9mW+bg+v+XEkGmtw== + version "7.21.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.21.0.tgz#e737b91037e5186ee16b76e7ae093358a5634f02" + integrity sha512-Mdrbunoh9SxwFZapeHVrwFmri16+oYotcZysSzhNIVDwIAb1UV+kvnxULSYq9J3/q5MDG+4X6w8QVgD1zhBXNQ== dependencies: - "@babel/helper-plugin-utils" "^7.18.9" + "@babel/helper-plugin-utils" "^7.20.2" "@babel/plugin-transform-classes@^7.0.0": - version "7.19.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.19.0.tgz#0e61ec257fba409c41372175e7c1e606dc79bb20" - integrity sha512-YfeEE9kCjqTS9IitkgfJuxjcEtLUHMqa8yUJ6zdz8vR7hKuo6mOy2C05P0F1tdMmDCeuyidKnlrw/iTppHcr2A== + version "7.21.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.21.0.tgz#f469d0b07a4c5a7dbb21afad9e27e57b47031665" + integrity sha512-RZhbYTCEUAe6ntPehC4hlslPWosNHDox+vAs4On/mCLRLfoDVHf6hVEd7kuxr1RnHwJmxFfUM3cZiZRmPxJPXQ== dependencies: "@babel/helper-annotate-as-pure" "^7.18.6" - "@babel/helper-compilation-targets" "^7.19.0" + "@babel/helper-compilation-targets" "^7.20.7" "@babel/helper-environment-visitor" "^7.18.9" - "@babel/helper-function-name" "^7.19.0" + "@babel/helper-function-name" "^7.21.0" "@babel/helper-optimise-call-expression" "^7.18.6" - "@babel/helper-plugin-utils" "^7.19.0" - "@babel/helper-replace-supers" "^7.18.9" + "@babel/helper-plugin-utils" "^7.20.2" + "@babel/helper-replace-supers" "^7.20.7" "@babel/helper-split-export-declaration" "^7.18.6" globals "^11.1.0" "@babel/plugin-transform-computed-properties@^7.0.0": - version "7.18.9" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.18.9.tgz#2357a8224d402dad623caf6259b611e56aec746e" - integrity sha512-+i0ZU1bCDymKakLxn5srGHrsAPRELC2WIbzwjLhHW9SIE1cPYkLCL0NlnXMZaM1vhfgA2+M7hySk42VBvrkBRw== + version "7.20.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.20.7.tgz#704cc2fd155d1c996551db8276d55b9d46e4d0aa" + integrity sha512-Lz7MvBK6DTjElHAmfu6bfANzKcxpyNPeYBGEafyA6E5HtRpjpZwU+u7Qrgz/2OR0z+5TvKYbPdphfSaAcZBrYQ== dependencies: - "@babel/helper-plugin-utils" "^7.18.9" + "@babel/helper-plugin-utils" "^7.20.2" + "@babel/template" "^7.20.7" "@babel/plugin-transform-destructuring@^7.0.0": - version "7.18.13" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.18.13.tgz#9e03bc4a94475d62b7f4114938e6c5c33372cbf5" - integrity sha512-TodpQ29XekIsex2A+YJPj5ax2plkGa8YYY6mFjCohk/IG9IY42Rtuj1FuDeemfg2ipxIFLzPeA83SIBnlhSIow== - dependencies: - "@babel/helper-plugin-utils" "^7.18.9" - -"@babel/plugin-transform-exponentiation-operator@^7.0.0": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.18.6.tgz#421c705f4521888c65e91fdd1af951bfefd4dacd" - integrity sha512-wzEtc0+2c88FVR34aQmiz56dxEkxr2g8DQb/KfaFa1JYXOFVsbhvAonFN6PwVWj++fKmku8NP80plJ5Et4wqHw== + version "7.21.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.21.3.tgz#73b46d0fd11cd6ef57dea8a381b1215f4959d401" + integrity sha512-bp6hwMFzuiE4HqYEyoGJ/V2LeIWn+hLVKc4pnj++E5XQptwhtcGmSayM029d/j2X1bPKGTlsyPwAubuU22KhMA== dependencies: - "@babel/helper-builder-binary-assignment-operator-visitor" "^7.18.6" - "@babel/helper-plugin-utils" "^7.18.6" + "@babel/helper-plugin-utils" "^7.20.2" -"@babel/plugin-transform-flow-strip-types@^7.0.0", "@babel/plugin-transform-flow-strip-types@^7.18.6": - version "7.19.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-flow-strip-types/-/plugin-transform-flow-strip-types-7.19.0.tgz#e9e8606633287488216028719638cbbb2f2dde8f" - integrity sha512-sgeMlNaQVbCSpgLSKP4ZZKfsJVnFnNQlUSk6gPYzR/q7tzCgQF2t8RBKAP6cKJeZdveei7Q7Jm527xepI8lNLg== +"@babel/plugin-transform-flow-strip-types@^7.0.0", "@babel/plugin-transform-flow-strip-types@^7.21.0": + version "7.21.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-flow-strip-types/-/plugin-transform-flow-strip-types-7.21.0.tgz#6aeca0adcb81dc627c8986e770bfaa4d9812aff5" + integrity sha512-FlFA2Mj87a6sDkW4gfGrQQqwY/dLlBAyJa2dJEZ+FHXUVHBflO2wyKvg+OOEzXfrKYIa4HWl0mgmbCzt0cMb7w== dependencies: - "@babel/helper-plugin-utils" "^7.19.0" + "@babel/helper-plugin-utils" "^7.20.2" "@babel/plugin-syntax-flow" "^7.18.6" "@babel/plugin-transform-for-of@^7.0.0": - version "7.18.8" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.18.8.tgz#6ef8a50b244eb6a0bdbad0c7c61877e4e30097c1" - integrity sha512-yEfTRnjuskWYo0k1mHUqrVWaZwrdq8AYbfrpqULOJOaucGSp4mNMVps+YtA8byoevxS/urwU75vyhQIxcCgiBQ== + version "7.21.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.21.0.tgz#964108c9988de1a60b4be2354a7d7e245f36e86e" + integrity sha512-LlUYlydgDkKpIY7mcBWvyPPmMcOphEyYA27Ef4xpbh1IiDNLr0kZsos2nf92vz3IccvJI25QUwp86Eo5s6HmBQ== dependencies: - "@babel/helper-plugin-utils" "^7.18.6" + "@babel/helper-plugin-utils" "^7.20.2" "@babel/plugin-transform-function-name@^7.0.0": version "7.18.9" @@ -539,22 +540,22 @@ dependencies: "@babel/helper-plugin-utils" "^7.18.6" -"@babel/plugin-transform-modules-commonjs@^7.0.0", "@babel/plugin-transform-modules-commonjs@^7.1.0": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.18.6.tgz#afd243afba166cca69892e24a8fd8c9f2ca87883" - integrity sha512-Qfv2ZOWikpvmedXQJDSbxNqy7Xr/j2Y8/KfijM0iJyKkBTmWuvCA1yeH1yDM7NJhBW/2aXxeucLj6i80/LAJ/Q== +"@babel/plugin-transform-modules-commonjs@^7.0.0", "@babel/plugin-transform-modules-commonjs@^7.13.8", "@babel/plugin-transform-modules-commonjs@^7.21.2": + version "7.21.2" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.21.2.tgz#6ff5070e71e3192ef2b7e39820a06fb78e3058e7" + integrity sha512-Cln+Yy04Gxua7iPdj6nOV96smLGjpElir5YwzF0LBPKoPlLDNJePNlrGGaybAJkd0zKRnOVXOgizSqPYMNYkzA== dependencies: - "@babel/helper-module-transforms" "^7.18.6" - "@babel/helper-plugin-utils" "^7.18.6" - "@babel/helper-simple-access" "^7.18.6" - babel-plugin-dynamic-import-node "^2.3.3" + "@babel/helper-module-transforms" "^7.21.2" + "@babel/helper-plugin-utils" "^7.20.2" + "@babel/helper-simple-access" "^7.20.2" -"@babel/plugin-transform-object-assign@^7.0.0": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-object-assign/-/plugin-transform-object-assign-7.18.6.tgz#7830b4b6f83e1374a5afb9f6111bcfaea872cdd2" - integrity sha512-mQisZ3JfqWh2gVXvfqYCAAyRs6+7oev+myBsTwW5RnPhYXOTuCEw2oe3YgxlXMViXUS53lG8koulI7mJ+8JE+A== +"@babel/plugin-transform-named-capturing-groups-regex@^7.0.0": + version "7.20.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.20.5.tgz#626298dd62ea51d452c3be58b285d23195ba69a8" + integrity sha512-mOW4tTzi5iTLnw+78iEq3gr8Aoq4WNRGpmSlrogqaiCBoR1HFhpU4JkpQFOHfeYx3ReVIFWOQJS4aZBRvuZ6mA== dependencies: - "@babel/helper-plugin-utils" "^7.18.6" + "@babel/helper-create-regexp-features-plugin" "^7.20.5" + "@babel/helper-plugin-utils" "^7.20.2" "@babel/plugin-transform-object-super@^7.0.0": version "7.18.6" @@ -564,12 +565,12 @@ "@babel/helper-plugin-utils" "^7.18.6" "@babel/helper-replace-supers" "^7.18.6" -"@babel/plugin-transform-parameters@^7.0.0", "@babel/plugin-transform-parameters@^7.18.8": - version "7.18.8" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.18.8.tgz#ee9f1a0ce6d78af58d0956a9378ea3427cccb48a" - integrity sha512-ivfbE3X2Ss+Fj8nnXvKJS6sjRG4gzwPMsP+taZC+ZzEGjAYlvENixmt1sZ5Ca6tWls+BlKSGKPJ6OOXvXCbkFg== +"@babel/plugin-transform-parameters@^7.0.0", "@babel/plugin-transform-parameters@^7.20.7": + version "7.21.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.21.3.tgz#18fc4e797cf6d6d972cb8c411dbe8a809fa157db" + integrity sha512-Wxc+TvppQG9xWFYatvCGPvZ6+SIUxQ2ZdiBP+PHYMIjnPXD+uThCshaz4NZOnODAtBjjcVQQ/3OKs9LW28purQ== dependencies: - "@babel/helper-plugin-utils" "^7.18.6" + "@babel/helper-plugin-utils" "^7.20.2" "@babel/plugin-transform-property-literals@^7.0.0": version "7.18.6" @@ -586,45 +587,37 @@ "@babel/helper-plugin-utils" "^7.18.6" "@babel/plugin-transform-react-jsx-self@^7.0.0": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.18.6.tgz#3849401bab7ae8ffa1e3e5687c94a753fc75bda7" - integrity sha512-A0LQGx4+4Jv7u/tWzoJF7alZwnBDQd6cGLh9P+Ttk4dpiL+J5p7NSNv/9tlEFFJDq3kjxOavWmbm6t0Gk+A3Ig== + version "7.21.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.21.0.tgz#ec98d4a9baafc5a1eb398da4cf94afbb40254a54" + integrity sha512-f/Eq+79JEu+KUANFks9UZCcvydOOGMgF7jBrcwjHa5jTZD8JivnhCJYvmlhR/WTXBWonDExPoW0eO/CR4QJirA== dependencies: - "@babel/helper-plugin-utils" "^7.18.6" + "@babel/helper-plugin-utils" "^7.20.2" "@babel/plugin-transform-react-jsx-source@^7.0.0": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.18.6.tgz#06e9ae8a14d2bc19ce6e3c447d842032a50598fc" - integrity sha512-utZmlASneDfdaMh0m/WausbjUjEdGrQJz0vFK93d7wD3xf5wBtX219+q6IlCNZeguIcxS2f/CvLZrlLSvSHQXw== + version "7.19.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.19.6.tgz#88578ae8331e5887e8ce28e4c9dc83fb29da0b86" + integrity sha512-RpAi004QyMNisst/pvSanoRdJ4q+jMCWyk9zdw/CyLB9j8RXEahodR6l2GyttDRyEVWZtbN+TpLiHJ3t34LbsQ== dependencies: - "@babel/helper-plugin-utils" "^7.18.6" + "@babel/helper-plugin-utils" "^7.19.0" "@babel/plugin-transform-react-jsx@^7.0.0": - version "7.19.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.19.0.tgz#b3cbb7c3a00b92ec8ae1027910e331ba5c500eb9" - integrity sha512-UVEvX3tXie3Szm3emi1+G63jyw1w5IcMY0FSKM+CRnKRI5Mr1YbCNgsSTwoTwKphQEG9P+QqmuRFneJPZuHNhg== + version "7.21.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.21.0.tgz#656b42c2fdea0a6d8762075d58ef9d4e3c4ab8a2" + integrity sha512-6OAWljMvQrZjR2DaNhVfRz6dkCAVV+ymcLUmaf8bccGOHn2v5rHJK3tTpij0BuhdYWP4LLaqj5lwcdlpAAPuvg== dependencies: "@babel/helper-annotate-as-pure" "^7.18.6" "@babel/helper-module-imports" "^7.18.6" - "@babel/helper-plugin-utils" "^7.19.0" + "@babel/helper-plugin-utils" "^7.20.2" "@babel/plugin-syntax-jsx" "^7.18.6" - "@babel/types" "^7.19.0" - -"@babel/plugin-transform-regenerator@^7.0.0": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.18.6.tgz#585c66cb84d4b4bf72519a34cfce761b8676ca73" - integrity sha512-poqRI2+qiSdeldcz4wTSTXBRryoq3Gc70ye7m7UD5Ww0nE29IXqMl6r7Nd15WBgRd74vloEMlShtH6CKxVzfmQ== - dependencies: - "@babel/helper-plugin-utils" "^7.18.6" - regenerator-transform "^0.15.0" + "@babel/types" "^7.21.0" "@babel/plugin-transform-runtime@^7.0.0": - version "7.19.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.19.1.tgz#a3df2d7312eea624c7889a2dcd37fd1dfd25b2c6" - integrity sha512-2nJjTUFIzBMP/f/miLxEK9vxwW/KUXsdvN4sR//TmuDhe6yU2h57WmIOE12Gng3MDP/xpjUV/ToZRdcf8Yj4fA== + version "7.21.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.21.4.tgz#2e1da21ca597a7d01fc96b699b21d8d2023191aa" + integrity sha512-1J4dhrw1h1PqnNNpzwxQ2UBymJUF8KuPjAAnlLwZcGhHAIqUigFW7cdK6GHoB64ubY4qXQNYknoUeks4Wz7CUA== dependencies: - "@babel/helper-module-imports" "^7.18.6" - "@babel/helper-plugin-utils" "^7.19.0" + "@babel/helper-module-imports" "^7.21.4" + "@babel/helper-plugin-utils" "^7.20.2" babel-plugin-polyfill-corejs2 "^0.3.3" babel-plugin-polyfill-corejs3 "^0.6.0" babel-plugin-polyfill-regenerator "^0.4.1" @@ -638,12 +631,12 @@ "@babel/helper-plugin-utils" "^7.18.6" "@babel/plugin-transform-spread@^7.0.0": - version "7.19.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-spread/-/plugin-transform-spread-7.19.0.tgz#dd60b4620c2fec806d60cfaae364ec2188d593b6" - integrity sha512-RsuMk7j6n+r752EtzyScnWkQyuJdli6LdO5Klv8Yx0OfPVTcQkIUfS8clx5e9yHXzlnhOZF3CbQ8C2uP5j074w== + version "7.20.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-spread/-/plugin-transform-spread-7.20.7.tgz#c2d83e0b99d3bf83e07b11995ee24bf7ca09401e" + integrity sha512-ewBbHQ+1U/VnH1fxltbJqDeWBU1oNLG8Dj11uIv3xVf7nrQu0bPGe5Rf716r7K5Qz+SqtAOVswoVunoiBtGhxw== dependencies: - "@babel/helper-plugin-utils" "^7.19.0" - "@babel/helper-skip-transparent-expression-wrappers" "^7.18.9" + "@babel/helper-plugin-utils" "^7.20.2" + "@babel/helper-skip-transparent-expression-wrappers" "^7.20.0" "@babel/plugin-transform-sticky-regex@^7.0.0": version "7.18.6" @@ -659,14 +652,15 @@ dependencies: "@babel/helper-plugin-utils" "^7.18.9" -"@babel/plugin-transform-typescript@^7.18.6", "@babel/plugin-transform-typescript@^7.5.0": - version "7.19.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.19.1.tgz#adcf180a041dcbd29257ad31b0c65d4de531ce8d" - integrity sha512-+ILcOU+6mWLlvCwnL920m2Ow3wWx3Wo8n2t5aROQmV55GZt+hOiLvBaa3DNzRjSEHa1aauRs4/YLmkCfFkhhRQ== +"@babel/plugin-transform-typescript@^7.21.3", "@babel/plugin-transform-typescript@^7.5.0": + version "7.21.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.21.3.tgz#316c5be579856ea890a57ebc5116c5d064658f2b" + integrity sha512-RQxPz6Iqt8T0uw/WsJNReuBpWpBqs/n7mNo18sKLoTbMp+UrEekhH+pKSVC7gWz+DNjo9gryfV8YzCiT45RgMw== dependencies: - "@babel/helper-create-class-features-plugin" "^7.19.0" - "@babel/helper-plugin-utils" "^7.19.0" - "@babel/plugin-syntax-typescript" "^7.18.6" + "@babel/helper-annotate-as-pure" "^7.18.6" + "@babel/helper-create-class-features-plugin" "^7.21.0" + "@babel/helper-plugin-utils" "^7.20.2" + "@babel/plugin-syntax-typescript" "^7.20.0" "@babel/plugin-transform-unicode-regex@^7.0.0": version "7.18.6" @@ -676,28 +670,30 @@ "@babel/helper-create-regexp-features-plugin" "^7.18.6" "@babel/helper-plugin-utils" "^7.18.6" -"@babel/preset-flow@^7.0.0": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/preset-flow/-/preset-flow-7.18.6.tgz#83f7602ba566e72a9918beefafef8ef16d2810cb" - integrity sha512-E7BDhL64W6OUqpuyHnSroLnqyRTcG6ZdOBl1OKI/QK/HJfplqK/S3sq1Cckx7oTodJ5yOXyfw7rEADJ6UjoQDQ== +"@babel/preset-flow@^7.13.13": + version "7.21.4" + resolved "https://registry.yarnpkg.com/@babel/preset-flow/-/preset-flow-7.21.4.tgz#a5de2a1cafa61f0e0b3af9b30ff0295d38d3608f" + integrity sha512-F24cSq4DIBmhq4OzK3dE63NHagb27OPE3eWR+HLekt4Z3Y5MzIIUGF3LlLgV0gN8vzbDViSY7HnrReNVCJXTeA== dependencies: - "@babel/helper-plugin-utils" "^7.18.6" - "@babel/helper-validator-option" "^7.18.6" - "@babel/plugin-transform-flow-strip-types" "^7.18.6" + "@babel/helper-plugin-utils" "^7.20.2" + "@babel/helper-validator-option" "^7.21.0" + "@babel/plugin-transform-flow-strip-types" "^7.21.0" -"@babel/preset-typescript@^7.1.0": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/preset-typescript/-/preset-typescript-7.18.6.tgz#ce64be3e63eddc44240c6358daefac17b3186399" - integrity sha512-s9ik86kXBAnD760aybBucdpnLsAt0jK1xqJn2juOn9lkOvSHV60os5hxoVJsPzMQxvnUJFAlkont2DvvaYEBtQ== +"@babel/preset-typescript@^7.13.0": + version "7.21.4" + resolved "https://registry.yarnpkg.com/@babel/preset-typescript/-/preset-typescript-7.21.4.tgz#b913ac8e6aa8932e47c21b01b4368d8aa239a529" + integrity sha512-sMLNWY37TCdRH/bJ6ZeeOH1nPuanED7Ai9Y/vH31IPqalioJ6ZNFUWONsakhv4r4n+I6gm5lmoE0olkgib/j/A== dependencies: - "@babel/helper-plugin-utils" "^7.18.6" - "@babel/helper-validator-option" "^7.18.6" - "@babel/plugin-transform-typescript" "^7.18.6" + "@babel/helper-plugin-utils" "^7.20.2" + "@babel/helper-validator-option" "^7.21.0" + "@babel/plugin-syntax-jsx" "^7.21.4" + "@babel/plugin-transform-modules-commonjs" "^7.21.2" + "@babel/plugin-transform-typescript" "^7.21.3" -"@babel/register@^7.0.0": - version "7.18.9" - resolved "https://registry.yarnpkg.com/@babel/register/-/register-7.18.9.tgz#1888b24bc28d5cc41c412feb015e9ff6b96e439c" - integrity sha512-ZlbnXDcNYHMR25ITwwNKT88JiaukkdVj/nG7r3wnuXkOTHc60Uy05PwMCPre0hSkY68E6zK3xz+vUJSP2jWmcw== +"@babel/register@^7.13.16": + version "7.21.0" + resolved "https://registry.yarnpkg.com/@babel/register/-/register-7.21.0.tgz#c97bf56c2472e063774f31d344c592ebdcefa132" + integrity sha512-9nKsPmYDi5DidAqJaQooxIhsLJiNMkGr8ypQ8Uic7cIox7UCDsM7HuUGxdGT7mSDTYbqzIdsOWzfBton/YJrMw== dependencies: clone-deep "^4.0.1" find-cache-dir "^2.0.0" @@ -705,45 +701,50 @@ pirates "^4.0.5" source-map-support "^0.5.16" -"@babel/runtime@^7.8.4": - version "7.19.0" - resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.19.0.tgz#22b11c037b094d27a8a2504ea4dcff00f50e2259" - integrity sha512-eR8Lo9hnDS7tqkO7NsV+mKvCmv5boaXFSZ70DnfhcgiEne8hv9oCEd36Klw74EtizEqLsy4YnW8UWwpBVolHZA== +"@babel/regjsgen@^0.8.0": + version "0.8.0" + resolved "https://registry.yarnpkg.com/@babel/regjsgen/-/regjsgen-0.8.0.tgz#f0ba69b075e1f05fb2825b7fad991e7adbb18310" + integrity sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA== + +"@babel/runtime@^7.0.0": + version "7.21.0" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.21.0.tgz#5b55c9d394e5fcf304909a8b00c07dc217b56673" + integrity sha512-xwII0//EObnq89Ji5AKYQaRYiW/nZ3llSv29d49IuxPhKbtJoLP+9QUUZ4nVragQVtaVGeZrpB+ZtG/Pdy/POw== dependencies: - regenerator-runtime "^0.13.4" + regenerator-runtime "^0.13.11" -"@babel/template@^7.0.0", "@babel/template@^7.18.10", "@babel/template@^7.3.3": - version "7.18.10" - resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.18.10.tgz#6f9134835970d1dbf0835c0d100c9f38de0c5e71" - integrity sha512-TI+rCtooWHr3QJ27kJxfjutghu44DLnasDMwpDqCXVTal9RLp3RSYNh4NdBrRP2cQAoG9A8juOQl6P6oZG4JxA== +"@babel/template@^7.0.0", "@babel/template@^7.18.10", "@babel/template@^7.20.7", "@babel/template@^7.3.3": + version "7.20.7" + resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.20.7.tgz#a15090c2839a83b02aa996c0b4994005841fd5a8" + integrity sha512-8SegXApWe6VoNw0r9JHpSteLKTpTiLZ4rMlGIm9JQ18KiCtyQiAMEazujAHrUS5flrcqYZa75ukev3P6QmUwUw== dependencies: "@babel/code-frame" "^7.18.6" - "@babel/parser" "^7.18.10" - "@babel/types" "^7.18.10" + "@babel/parser" "^7.20.7" + "@babel/types" "^7.20.7" -"@babel/traverse@^7.0.0", "@babel/traverse@^7.19.0", "@babel/traverse@^7.19.1", "@babel/traverse@^7.7.2": - version "7.19.1" - resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.19.1.tgz#0fafe100a8c2a603b4718b1d9bf2568d1d193347" - integrity sha512-0j/ZfZMxKukDaag2PtOPDbwuELqIar6lLskVPPJDjXMXjfLb1Obo/1yjxIGqqAJrmfaTIY3z2wFLAQ7qSkLsuA== +"@babel/traverse@^7.20.0", "@babel/traverse@^7.20.5", "@babel/traverse@^7.20.7", "@babel/traverse@^7.21.0", "@babel/traverse@^7.21.2", "@babel/traverse@^7.21.4", "@babel/traverse@^7.7.2": + version "7.21.4" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.21.4.tgz#a836aca7b116634e97a6ed99976236b3282c9d36" + integrity sha512-eyKrRHKdyZxqDm+fV1iqL9UAHMoIg0nDaGqfIOd8rKH17m5snv7Gn4qgjBoFfLz9APvjFU/ICT00NVCv1Epp8Q== dependencies: - "@babel/code-frame" "^7.18.6" - "@babel/generator" "^7.19.0" + "@babel/code-frame" "^7.21.4" + "@babel/generator" "^7.21.4" "@babel/helper-environment-visitor" "^7.18.9" - "@babel/helper-function-name" "^7.19.0" + "@babel/helper-function-name" "^7.21.0" "@babel/helper-hoist-variables" "^7.18.6" "@babel/helper-split-export-declaration" "^7.18.6" - "@babel/parser" "^7.19.1" - "@babel/types" "^7.19.0" + "@babel/parser" "^7.21.4" + "@babel/types" "^7.21.4" debug "^4.1.0" globals "^11.1.0" -"@babel/types@^7.0.0", "@babel/types@^7.18.10", "@babel/types@^7.18.6", "@babel/types@^7.18.9", "@babel/types@^7.19.0", "@babel/types@^7.3.0", "@babel/types@^7.3.3": - version "7.19.0" - resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.19.0.tgz#75f21d73d73dc0351f3368d28db73465f4814600" - integrity sha512-YuGopBq3ke25BVSiS6fgF49Ul9gH1x70Bcr6bqRLjWCkcX8Hre1/5+z+IiWOIerRMSSEfGZVB9z9kyq7wVs9YA== +"@babel/types@^7.0.0", "@babel/types@^7.18.6", "@babel/types@^7.18.9", "@babel/types@^7.20.0", "@babel/types@^7.20.2", "@babel/types@^7.20.5", "@babel/types@^7.20.7", "@babel/types@^7.21.0", "@babel/types@^7.21.2", "@babel/types@^7.21.4", "@babel/types@^7.3.0", "@babel/types@^7.3.3": + version "7.21.4" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.21.4.tgz#2d5d6bb7908699b3b416409ffd3b5daa25b030d4" + integrity sha512-rU2oY501qDxE8Pyo7i/Orqma4ziCOrby0/9mvbDUGEfvZjb279Nk9k19e2fiCxHbRRpY2ZyrgW1eq22mvmOIzA== dependencies: - "@babel/helper-string-parser" "^7.18.10" - "@babel/helper-validator-identifier" "^7.18.6" + "@babel/helper-string-parser" "^7.19.4" + "@babel/helper-validator-identifier" "^7.19.1" to-fast-properties "^2.0.0" "@bcoe/v8-coverage@^0.2.3": @@ -751,13 +752,158 @@ resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39" integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw== -"@cnakazawa/watch@^1.0.3": - version "1.0.4" - resolved "https://registry.yarnpkg.com/@cnakazawa/watch/-/watch-1.0.4.tgz#f864ae85004d0fcab6f50be9141c4da368d1656a" - integrity sha512-v9kIhKwjeZThiWrLmj0y17CWoyddASLj9O2yvbZkbvw/N3rWOYy9zkV66ursAoVr0mV15bL8g0c4QZUE6cdDoQ== +"@cheqd/sdk@cjs": + version "2.3.0" + resolved "https://registry.yarnpkg.com/@cheqd/sdk/-/sdk-2.3.0.tgz#0594ccb501d1ad74f3360fa5beb12f002dc8e8d2" + integrity sha512-ncHwBdAAyauuLLWHXUNzUJo6Y7z02nhNOt87n2owUhvf5K2UbNcHHB/CzNwcQIc7OGcShPABydug8KE1ueNk/Q== + dependencies: + "@cheqd/ts-proto" "^2.2.0" + "@cosmjs/amino" "^0.29.5" + "@cosmjs/crypto" "^0.29.5" + "@cosmjs/encoding" "^0.29.5" + "@cosmjs/math" "^0.29.5" + "@cosmjs/proto-signing" "^0.29.5" + "@cosmjs/stargate" "^0.29.5" + "@cosmjs/tendermint-rpc" "^0.29.5" + "@cosmjs/utils" "^0.29.5" + "@stablelib/ed25519" "^1.0.3" + cosmjs-types "^0.5.2" + did-jwt "^6.11.6" + did-resolver "^4.1.0" + multiformats "^9.9.0" + uuid "^9.0.0" + +"@cheqd/ts-proto@^2.2.0", "@cheqd/ts-proto@cjs": + version "2.2.0" + resolved "https://registry.yarnpkg.com/@cheqd/ts-proto/-/ts-proto-2.2.0.tgz#c296a9fff23f47fba84f9c3354439b8fc91129f4" + integrity sha512-COTDndE/haSUPndVYaJGAVT4OMIrVSibGfLrKol9CXZBasmUUJx5rVFOpL34wYq6VcOrfF2TN+63TRePRUBWpA== + dependencies: + long "^5.2.1" + protobufjs "^7.2.3" + +"@confio/ics23@^0.6.8": + version "0.6.8" + resolved "https://registry.yarnpkg.com/@confio/ics23/-/ics23-0.6.8.tgz#2a6b4f1f2b7b20a35d9a0745bb5a446e72930b3d" + integrity sha512-wB6uo+3A50m0sW/EWcU64xpV/8wShZ6bMTa7pF8eYsTrSkQA7oLUIJcs/wb8g4y2Oyq701BaGiO6n/ak5WXO1w== + dependencies: + "@noble/hashes" "^1.0.0" + protobufjs "^6.8.8" + +"@cosmjs/amino@^0.29.5": + version "0.29.5" + resolved "https://registry.yarnpkg.com/@cosmjs/amino/-/amino-0.29.5.tgz#053b4739a90b15b9e2b781ccd484faf64bd49aec" + integrity sha512-Qo8jpC0BiziTSUqpkNatBcwtKNhCovUnFul9SlT/74JUCdLYaeG5hxr3q1cssQt++l4LvlcpF+OUXL48XjNjLw== + dependencies: + "@cosmjs/crypto" "^0.29.5" + "@cosmjs/encoding" "^0.29.5" + "@cosmjs/math" "^0.29.5" + "@cosmjs/utils" "^0.29.5" + +"@cosmjs/crypto@^0.29.5": + version "0.29.5" + resolved "https://registry.yarnpkg.com/@cosmjs/crypto/-/crypto-0.29.5.tgz#ab99fc382b93d8a8db075780cf07487a0f9519fd" + integrity sha512-2bKkaLGictaNL0UipQCL6C1afaisv6k8Wr/GCLx9FqiyFkh9ZgRHDyetD64ZsjnWV/N/D44s/esI+k6oPREaiQ== + dependencies: + "@cosmjs/encoding" "^0.29.5" + "@cosmjs/math" "^0.29.5" + "@cosmjs/utils" "^0.29.5" + "@noble/hashes" "^1" + bn.js "^5.2.0" + elliptic "^6.5.4" + libsodium-wrappers "^0.7.6" + +"@cosmjs/encoding@^0.29.5": + version "0.29.5" + resolved "https://registry.yarnpkg.com/@cosmjs/encoding/-/encoding-0.29.5.tgz#009a4b1c596cdfd326f30ccfa79f5e56daa264f2" + integrity sha512-G4rGl/Jg4dMCw5u6PEZHZcoHnUBlukZODHbm/wcL4Uu91fkn5jVo5cXXZcvs4VCkArVGrEj/52eUgTZCmOBGWQ== dependencies: - exec-sh "^0.3.2" - minimist "^1.2.0" + base64-js "^1.3.0" + bech32 "^1.1.4" + readonly-date "^1.0.0" + +"@cosmjs/json-rpc@^0.29.5": + version "0.29.5" + resolved "https://registry.yarnpkg.com/@cosmjs/json-rpc/-/json-rpc-0.29.5.tgz#5e483a9bd98a6270f935adf0dfd8a1e7eb777fe4" + integrity sha512-C78+X06l+r9xwdM1yFWIpGl03LhB9NdM1xvZpQHwgCOl0Ir/WV8pw48y3Ez2awAoUBRfTeejPe4KvrE6NoIi/w== + dependencies: + "@cosmjs/stream" "^0.29.5" + xstream "^11.14.0" + +"@cosmjs/math@^0.29.5": + version "0.29.5" + resolved "https://registry.yarnpkg.com/@cosmjs/math/-/math-0.29.5.tgz#722c96e080d6c2b62215ce9f4c70da7625b241b6" + integrity sha512-2GjKcv+A9f86MAWYLUkjhw1/WpRl2R1BTb3m9qPG7lzMA7ioYff9jY5SPCfafKdxM4TIQGxXQlYGewQL16O68Q== + dependencies: + bn.js "^5.2.0" + +"@cosmjs/proto-signing@^0.29.5": + version "0.29.5" + resolved "https://registry.yarnpkg.com/@cosmjs/proto-signing/-/proto-signing-0.29.5.tgz#af3b62a46c2c2f1d2327d678b13b7262db1fe87c" + integrity sha512-QRrS7CiKaoETdgIqvi/7JC2qCwCR7lnWaUsTzh/XfRy3McLkEd+cXbKAW3cygykv7IN0VAEIhZd2lyIfT8KwNA== + dependencies: + "@cosmjs/amino" "^0.29.5" + "@cosmjs/crypto" "^0.29.5" + "@cosmjs/encoding" "^0.29.5" + "@cosmjs/math" "^0.29.5" + "@cosmjs/utils" "^0.29.5" + cosmjs-types "^0.5.2" + long "^4.0.0" + +"@cosmjs/socket@^0.29.5": + version "0.29.5" + resolved "https://registry.yarnpkg.com/@cosmjs/socket/-/socket-0.29.5.tgz#a48df6b4c45dc6a6ef8e47232725dd4aa556ac2d" + integrity sha512-5VYDupIWbIXq3ftPV1LkS5Ya/T7Ol/AzWVhNxZ79hPe/mBfv1bGau/LqIYOm2zxGlgm9hBHOTmWGqNYDwr9LNQ== + dependencies: + "@cosmjs/stream" "^0.29.5" + isomorphic-ws "^4.0.1" + ws "^7" + xstream "^11.14.0" + +"@cosmjs/stargate@^0.29.5": + version "0.29.5" + resolved "https://registry.yarnpkg.com/@cosmjs/stargate/-/stargate-0.29.5.tgz#d597af1c85a3c2af7b5bdbec34d5d40692cc09e4" + integrity sha512-hjEv8UUlJruLrYGJcUZXM/CziaINOKwfVm2BoSdUnNTMxGvY/jC1ABHKeZUYt9oXHxEJ1n9+pDqzbKc8pT0nBw== + dependencies: + "@confio/ics23" "^0.6.8" + "@cosmjs/amino" "^0.29.5" + "@cosmjs/encoding" "^0.29.5" + "@cosmjs/math" "^0.29.5" + "@cosmjs/proto-signing" "^0.29.5" + "@cosmjs/stream" "^0.29.5" + "@cosmjs/tendermint-rpc" "^0.29.5" + "@cosmjs/utils" "^0.29.5" + cosmjs-types "^0.5.2" + long "^4.0.0" + protobufjs "~6.11.3" + xstream "^11.14.0" + +"@cosmjs/stream@^0.29.5": + version "0.29.5" + resolved "https://registry.yarnpkg.com/@cosmjs/stream/-/stream-0.29.5.tgz#350981cac496d04939b92ee793b9b19f44bc1d4e" + integrity sha512-TToTDWyH1p05GBtF0Y8jFw2C+4783ueDCmDyxOMM6EU82IqpmIbfwcdMOCAm0JhnyMh+ocdebbFvnX/sGKzRAA== + dependencies: + xstream "^11.14.0" + +"@cosmjs/tendermint-rpc@^0.29.5": + version "0.29.5" + resolved "https://registry.yarnpkg.com/@cosmjs/tendermint-rpc/-/tendermint-rpc-0.29.5.tgz#f205c10464212bdf843f91bb2e4a093b618cb5c2" + integrity sha512-ar80twieuAxsy0x2za/aO3kBr2DFPAXDmk2ikDbmkda+qqfXgl35l9CVAAjKRqd9d+cRvbQyb5M4wy6XQpEV6w== + dependencies: + "@cosmjs/crypto" "^0.29.5" + "@cosmjs/encoding" "^0.29.5" + "@cosmjs/json-rpc" "^0.29.5" + "@cosmjs/math" "^0.29.5" + "@cosmjs/socket" "^0.29.5" + "@cosmjs/stream" "^0.29.5" + "@cosmjs/utils" "^0.29.5" + axios "^0.21.2" + readonly-date "^1.0.0" + xstream "^11.14.0" + +"@cosmjs/utils@^0.29.5": + version "0.29.5" + resolved "https://registry.yarnpkg.com/@cosmjs/utils/-/utils-0.29.5.tgz#3fed1b3528ae8c5f1eb5d29b68755bebfd3294ee" + integrity sha512-m7h+RXDUxOzEOGt4P+3OVPX7PuakZT3GBmaM/Y2u+abN3xZkziykD/NvedYFvvCCdQo714XcGl33bwifS9FZPQ== "@cspotcode/source-map-support@^0.8.0": version "0.8.1" @@ -817,22 +963,39 @@ "@digitalcredentials/jsonld-signatures" "^9.3.1" credentials-context "^2.0.0" -"@eslint/eslintrc@^0.4.3": - version "0.4.3" - resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-0.4.3.tgz#9e42981ef035beb3dd49add17acb96e8ff6f394c" - integrity sha512-J6KFFz5QCYUJq3pf0mjEcCJVERbzv71PUIDczuh9JkwGEzced6CO5ADLHB1rbf/+oPBtoPfMYNOpGDzCANlbXw== +"@eslint-community/eslint-utils@^4.2.0": + version "4.4.0" + resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz#a23514e8fb9af1269d5f7788aa556798d61c6b59" + integrity sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA== + dependencies: + eslint-visitor-keys "^3.3.0" + +"@eslint-community/regexpp@^4.4.0": + version "4.5.0" + resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.5.0.tgz#f6f729b02feee2c749f57e334b7a1b5f40a81724" + integrity sha512-vITaYzIcNmjn5tF5uxcZ/ft7/RXGrMUIS9HalWckEOF6ESiwXKoMzAQf2UW0aVd6rnOeExTJVd5hmWXucBKGXQ== + +"@eslint/eslintrc@^2.0.2": + version "2.0.2" + resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-2.0.2.tgz#01575e38707add677cf73ca1589abba8da899a02" + integrity sha512-3W4f5tDUra+pA+FzgugqL2pRimUTDJWKr7BINqOpkZrC0uYI0NIc0/JFgBROCU07HR6GieA5m3/rsPIhDmCXTQ== dependencies: ajv "^6.12.4" - debug "^4.1.1" - espree "^7.3.0" - globals "^13.9.0" - ignore "^4.0.6" + debug "^4.3.2" + espree "^9.5.1" + globals "^13.19.0" + ignore "^5.2.0" import-fresh "^3.2.1" - js-yaml "^3.13.1" - minimatch "^3.0.4" + js-yaml "^4.1.0" + minimatch "^3.1.2" strip-json-comments "^3.1.1" -"@gar/promisify@^1.0.1": +"@eslint/js@8.39.0": + version "8.39.0" + resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.39.0.tgz#58b536bcc843f4cd1e02a7e6171da5c040f4d44b" + integrity sha512-kf9RB0Fg7NZfap83B3QOqOGg9QmD9yBudqQXzzOtn3i4y7ZUXe5ONeW34Gwi+TxhH4mvj72R1Zc300KUMa9Bng== + +"@gar/promisify@^1.0.1", "@gar/promisify@^1.1.3": version "1.1.3" resolved "https://registry.yarnpkg.com/@gar/promisify/-/promisify-1.1.3.tgz#555193ab2e3bb3b6adc3d551c9c030d9e860daf6" integrity sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw== @@ -849,16 +1012,21 @@ dependencies: "@hapi/hoek" "^9.0.0" -"@humanwhocodes/config-array@^0.5.0": - version "0.5.0" - resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.5.0.tgz#1407967d4c6eecd7388f83acf1eaf4d0c6e58ef9" - integrity sha512-FagtKFz74XrTl7y6HCzQpwDfXP0yhxe9lHLD1UZxjvZIcbyRz8zTFF/yYNfSfzU414eDwZ1SrO0Qvtyf+wFMQg== +"@humanwhocodes/config-array@^0.11.8": + version "0.11.8" + resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.11.8.tgz#03595ac2075a4dc0f191cc2131de14fbd7d410b9" + integrity sha512-UybHIJzJnR5Qc/MsD9Kr+RpO2h+/P1GhOwdiLPXK5TWk5sgTdu88bTD9UP+CKbPPh5Rni1u0GjAdYQLemG8g+g== dependencies: - "@humanwhocodes/object-schema" "^1.2.0" + "@humanwhocodes/object-schema" "^1.2.1" debug "^4.1.1" - minimatch "^3.0.4" + minimatch "^3.0.5" + +"@humanwhocodes/module-importer@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz#af5b2691a22b44be847b0ca81641c5fb6ad0172c" + integrity sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA== -"@humanwhocodes/object-schema@^1.2.0": +"@humanwhocodes/object-schema@^1.2.1": version "1.2.1" resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz#b520529ec21d8e5945a1851dfd1c32e94e39ff45" integrity sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA== @@ -868,6 +1036,67 @@ resolved "https://registry.yarnpkg.com/@hutson/parse-repository-url/-/parse-repository-url-3.0.2.tgz#98c23c950a3d9b6c8f0daed06da6c3af06981340" integrity sha512-H9XAx3hc0BQHY6l+IFSWHDySypcXsvsuLhgYLUGywmJ5pswRVQJUHpOsobnLYp2ZUaUlKiKDrgWWhosOwAEM8Q== +"@hyperledger/anoncreds-nodejs@^0.1.0-dev.15": + version "0.1.0-dev.15" + resolved "https://registry.yarnpkg.com/@hyperledger/anoncreds-nodejs/-/anoncreds-nodejs-0.1.0-dev.15.tgz#8d248f53c318d91e82030a7fb23740fe4b2bef7a" + integrity sha512-3QiKzjVhbQ+N07vMiR0XCBxxy51RwhKf/z0/mHiVYy1ZcmuTok5dE/jTjAwmHh+jvuAwqk+O4ebWmFItRx1K7Q== + dependencies: + "@hyperledger/anoncreds-shared" "0.1.0-dev.15" + "@mapbox/node-pre-gyp" "^1.0.10" + ffi-napi "4.0.3" + node-cache "5.1.2" + ref-array-di "1.2.2" + ref-napi "3.0.3" + ref-struct-di "1.1.1" + +"@hyperledger/anoncreds-shared@0.1.0-dev.15", "@hyperledger/anoncreds-shared@^0.1.0-dev.15": + version "0.1.0-dev.15" + resolved "https://registry.yarnpkg.com/@hyperledger/anoncreds-shared/-/anoncreds-shared-0.1.0-dev.15.tgz#644e9cbc16a174f46b2f54dd7cb215a251d3da0a" + integrity sha512-nTO5KDTlDxpadk1j/r5T8E4wfS16rWKgZpyyG6dxg/7WhwZQkIcTsbPNnPH+NHklGju/ee+WT7rWlojpJ6XFVQ== + +"@hyperledger/aries-askar-nodejs@^0.1.0-dev.8": + version "0.1.0-dev.8" + resolved "https://registry.yarnpkg.com/@hyperledger/aries-askar-nodejs/-/aries-askar-nodejs-0.1.0-dev.8.tgz#a8a6b2969a03f6af8610db27f80fcd847384edc3" + integrity sha512-HzAJ7yZb+NwadV4P9a5gXRlqbYMPWy+3wFZvEkzfTl7Km2LxZat9WyHkMrcc9i2PXH1NZEK56XchzTmqG2zw3Q== + dependencies: + "@hyperledger/aries-askar-shared" "0.1.0-dev.8" + "@mapbox/node-pre-gyp" "^1.0.10" + ffi-napi "^4.0.3" + node-cache "^5.1.2" + ref-array-di "^1.2.2" + ref-napi "^3.0.3" + ref-struct-di "^1.1.1" + +"@hyperledger/aries-askar-shared@0.1.0-dev.8", "@hyperledger/aries-askar-shared@^0.1.0-dev.8": + version "0.1.0-dev.8" + resolved "https://registry.yarnpkg.com/@hyperledger/aries-askar-shared/-/aries-askar-shared-0.1.0-dev.8.tgz#9cafec424e6390f38c8098b908f6434881217a54" + integrity sha512-Zf0njf/4Lx8u12WGdEC0yYlPcczdk38yLCn9w6UdczzK0Lp+4YGDkSvpbvtCP7EXAykuMOQ3/P1iRBWnjygGpg== + dependencies: + fast-text-encoding "^1.0.3" + +"@hyperledger/indy-vdr-nodejs@^0.1.0-dev.14": + version "0.1.0-dev.14" + resolved "https://registry.yarnpkg.com/@hyperledger/indy-vdr-nodejs/-/indy-vdr-nodejs-0.1.0-dev.14.tgz#d21590d5464341fb701b654c132c75c96e1cba8f" + integrity sha512-iuR4At4Vs2tQhctZH84KlWKFL1JI6BXa+2dnBauQXhMEQj8q2l5kTH8TjJeXPD1PyOnbLB9ry4tfx+/a0A2AKg== + dependencies: + "@hyperledger/indy-vdr-shared" "0.1.0-dev.14" + "@mapbox/node-pre-gyp" "^1.0.10" + "@types/ref-array-di" "^1.2.5" + ffi-napi "^4.0.3" + ref-array-di "^1.2.2" + ref-napi "^3.0.3" + ref-struct-di "^1.1.1" + +"@hyperledger/indy-vdr-shared@0.1.0-dev.14", "@hyperledger/indy-vdr-shared@^0.1.0-dev.14": + version "0.1.0-dev.14" + resolved "https://registry.yarnpkg.com/@hyperledger/indy-vdr-shared/-/indy-vdr-shared-0.1.0-dev.14.tgz#7dc3d61f997c1bcf737b23133ed0d08fec2a901d" + integrity sha512-CsZUcqybgtvVEVD1uHHgCGY3tddYcePJKjkEar14pss4w2IxbhRYnzkfp0+lWYUQnAY7rGX8A0MxRxwd7K1Yhw== + +"@isaacs/string-locale-compare@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@isaacs/string-locale-compare/-/string-locale-compare-1.1.0.tgz#291c227e93fd407a96ecd59879a35809120e432b" + integrity sha512-SQ7Kzhh9+D+ZW9MA0zkYv3VXhIDNx+LzM6EJ+/65I3QY+enU6Itte7E5XX7EWrqLW2FN4n06GWzBnPoC3th2aQ== + "@istanbuljs/load-nyc-config@^1.0.0": version "1.1.0" resolved "https://registry.yarnpkg.com/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz#fd3db1d59ecf7cf121e80650bb86712f9b55eced" @@ -884,170 +1113,192 @@ resolved "https://registry.yarnpkg.com/@istanbuljs/schema/-/schema-0.1.3.tgz#e45e384e4b8ec16bce2fd903af78450f6bf7ec98" integrity sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA== -"@jest/console@^27.5.1": - version "27.5.1" - resolved "https://registry.yarnpkg.com/@jest/console/-/console-27.5.1.tgz#260fe7239602fe5130a94f1aa386eff54b014bba" - integrity sha512-kZ/tNpS3NXn0mlXXXPNuDZnb4c0oZ20r4K5eemM2k30ZC3G0T02nXUvyhf5YdbXWHPEJLc9qGLxEZ216MdL+Zg== +"@jest/console@^29.5.0": + version "29.5.0" + resolved "https://registry.yarnpkg.com/@jest/console/-/console-29.5.0.tgz#593a6c5c0d3f75689835f1b3b4688c4f8544cb57" + integrity sha512-NEpkObxPwyw/XxZVLPmAGKE89IQRp4puc6IQRPru6JKd1M3fW9v1xM1AnzIJE65hbCkzQAdnL8P47e9hzhiYLQ== dependencies: - "@jest/types" "^27.5.1" + "@jest/types" "^29.5.0" "@types/node" "*" chalk "^4.0.0" - jest-message-util "^27.5.1" - jest-util "^27.5.1" + jest-message-util "^29.5.0" + jest-util "^29.5.0" slash "^3.0.0" -"@jest/core@^27.5.1": - version "27.5.1" - resolved "https://registry.yarnpkg.com/@jest/core/-/core-27.5.1.tgz#267ac5f704e09dc52de2922cbf3af9edcd64b626" - integrity sha512-AK6/UTrvQD0Cd24NSqmIA6rKsu0tKIxfiCducZvqxYdmMisOYAsdItspT+fQDQYARPf8XgjAFZi0ogW2agH5nQ== +"@jest/core@^29.5.0": + version "29.5.0" + resolved "https://registry.yarnpkg.com/@jest/core/-/core-29.5.0.tgz#76674b96904484e8214614d17261cc491e5f1f03" + integrity sha512-28UzQc7ulUrOQw1IsN/kv1QES3q2kkbl/wGslyhAclqZ/8cMdB5M68BffkIdSJgKBUt50d3hbwJ92XESlE7LiQ== dependencies: - "@jest/console" "^27.5.1" - "@jest/reporters" "^27.5.1" - "@jest/test-result" "^27.5.1" - "@jest/transform" "^27.5.1" - "@jest/types" "^27.5.1" + "@jest/console" "^29.5.0" + "@jest/reporters" "^29.5.0" + "@jest/test-result" "^29.5.0" + "@jest/transform" "^29.5.0" + "@jest/types" "^29.5.0" "@types/node" "*" ansi-escapes "^4.2.1" chalk "^4.0.0" - emittery "^0.8.1" + ci-info "^3.2.0" exit "^0.1.2" graceful-fs "^4.2.9" - jest-changed-files "^27.5.1" - jest-config "^27.5.1" - jest-haste-map "^27.5.1" - jest-message-util "^27.5.1" - jest-regex-util "^27.5.1" - jest-resolve "^27.5.1" - jest-resolve-dependencies "^27.5.1" - jest-runner "^27.5.1" - jest-runtime "^27.5.1" - jest-snapshot "^27.5.1" - jest-util "^27.5.1" - jest-validate "^27.5.1" - jest-watcher "^27.5.1" + jest-changed-files "^29.5.0" + jest-config "^29.5.0" + jest-haste-map "^29.5.0" + jest-message-util "^29.5.0" + jest-regex-util "^29.4.3" + jest-resolve "^29.5.0" + jest-resolve-dependencies "^29.5.0" + jest-runner "^29.5.0" + jest-runtime "^29.5.0" + jest-snapshot "^29.5.0" + jest-util "^29.5.0" + jest-validate "^29.5.0" + jest-watcher "^29.5.0" micromatch "^4.0.4" - rimraf "^3.0.0" + pretty-format "^29.5.0" slash "^3.0.0" strip-ansi "^6.0.0" -"@jest/create-cache-key-function@^26.5.0": - version "26.6.2" - resolved "https://registry.yarnpkg.com/@jest/create-cache-key-function/-/create-cache-key-function-26.6.2.tgz#04cf439207a4fd12418d8aee551cddc86f9ac5f5" - integrity sha512-LgEuqU1f/7WEIPYqwLPIvvHuc1sB6gMVbT6zWhin3txYUNYK/kGQrC1F2WR4gR34YlI9bBtViTm5z98RqVZAaw== +"@jest/create-cache-key-function@^29.2.1": + version "29.5.0" + resolved "https://registry.yarnpkg.com/@jest/create-cache-key-function/-/create-cache-key-function-29.5.0.tgz#24e019d03e634be4affe8bcee787d75a36ae57a2" + integrity sha512-LIDZyZgnZss7uikvBKBB/USWwG+GO8+GnwRWT+YkCGDGsqLQlhm9BC3z6+7+eMs1kUlvXQIWEzBR8Q2Pnvx6lg== dependencies: - "@jest/types" "^26.6.2" + "@jest/types" "^29.5.0" -"@jest/environment@^27.5.1": - version "27.5.1" - resolved "https://registry.yarnpkg.com/@jest/environment/-/environment-27.5.1.tgz#d7425820511fe7158abbecc010140c3fd3be9c74" - integrity sha512-/WQjhPJe3/ghaol/4Bq480JKXV/Rfw8nQdN7f41fM8VDHLcxKXou6QyXAh3EFr9/bVG3x74z1NWDkP87EiY8gA== +"@jest/environment@^29.5.0": + version "29.5.0" + resolved "https://registry.yarnpkg.com/@jest/environment/-/environment-29.5.0.tgz#9152d56317c1fdb1af389c46640ba74ef0bb4c65" + integrity sha512-5FXw2+wD29YU1d4I2htpRX7jYnAyTRjP2CsXQdo9SAM8g3ifxWPSV0HnClSn71xwctr0U3oZIIH+dtbfmnbXVQ== dependencies: - "@jest/fake-timers" "^27.5.1" - "@jest/types" "^27.5.1" + "@jest/fake-timers" "^29.5.0" + "@jest/types" "^29.5.0" "@types/node" "*" - jest-mock "^27.5.1" + jest-mock "^29.5.0" -"@jest/fake-timers@^27.5.1": - version "27.5.1" - resolved "https://registry.yarnpkg.com/@jest/fake-timers/-/fake-timers-27.5.1.tgz#76979745ce0579c8a94a4678af7a748eda8ada74" - integrity sha512-/aPowoolwa07k7/oM3aASneNeBGCmGQsc3ugN4u6s4C/+s5M64MFo/+djTdiwcbQlRfFElGuDXWzaWj6QgKObQ== +"@jest/expect-utils@^29.5.0": + version "29.5.0" + resolved "https://registry.yarnpkg.com/@jest/expect-utils/-/expect-utils-29.5.0.tgz#f74fad6b6e20f924582dc8ecbf2cb800fe43a036" + integrity sha512-fmKzsidoXQT2KwnrwE0SQq3uj8Z763vzR8LnLBwC2qYWEFpjX8daRsk6rHUM1QvNlEW/UJXNXm59ztmJJWs2Mg== dependencies: - "@jest/types" "^27.5.1" - "@sinonjs/fake-timers" "^8.0.1" + jest-get-type "^29.4.3" + +"@jest/expect@^29.5.0": + version "29.5.0" + resolved "https://registry.yarnpkg.com/@jest/expect/-/expect-29.5.0.tgz#80952f5316b23c483fbca4363ce822af79c38fba" + integrity sha512-PueDR2HGihN3ciUNGr4uelropW7rqUfTiOn+8u0leg/42UhblPxHkfoh0Ruu3I9Y1962P3u2DY4+h7GVTSVU6g== + dependencies: + expect "^29.5.0" + jest-snapshot "^29.5.0" + +"@jest/fake-timers@^29.5.0": + version "29.5.0" + resolved "https://registry.yarnpkg.com/@jest/fake-timers/-/fake-timers-29.5.0.tgz#d4d09ec3286b3d90c60bdcd66ed28d35f1b4dc2c" + integrity sha512-9ARvuAAQcBwDAqOnglWq2zwNIRUDtk/SCkp/ToGEhFv5r86K21l+VEs0qNTaXtyiY0lEePl3kylijSYJQqdbDg== + dependencies: + "@jest/types" "^29.5.0" + "@sinonjs/fake-timers" "^10.0.2" "@types/node" "*" - jest-message-util "^27.5.1" - jest-mock "^27.5.1" - jest-util "^27.5.1" + jest-message-util "^29.5.0" + jest-mock "^29.5.0" + jest-util "^29.5.0" -"@jest/globals@^27.5.1": - version "27.5.1" - resolved "https://registry.yarnpkg.com/@jest/globals/-/globals-27.5.1.tgz#7ac06ce57ab966566c7963431cef458434601b2b" - integrity sha512-ZEJNB41OBQQgGzgyInAv0UUfDDj3upmHydjieSxFvTRuZElrx7tXg/uVQ5hYVEwiXs3+aMsAeEc9X7xiSKCm4Q== +"@jest/globals@^29.5.0": + version "29.5.0" + resolved "https://registry.yarnpkg.com/@jest/globals/-/globals-29.5.0.tgz#6166c0bfc374c58268677539d0c181f9c1833298" + integrity sha512-S02y0qMWGihdzNbUiqSAiKSpSozSuHX5UYc7QbnHP+D9Lyw8DgGGCinrN9uSuHPeKgSSzvPom2q1nAtBvUsvPQ== dependencies: - "@jest/environment" "^27.5.1" - "@jest/types" "^27.5.1" - expect "^27.5.1" + "@jest/environment" "^29.5.0" + "@jest/expect" "^29.5.0" + "@jest/types" "^29.5.0" + jest-mock "^29.5.0" -"@jest/reporters@^27.5.1": - version "27.5.1" - resolved "https://registry.yarnpkg.com/@jest/reporters/-/reporters-27.5.1.tgz#ceda7be96170b03c923c37987b64015812ffec04" - integrity sha512-cPXh9hWIlVJMQkVk84aIvXuBB4uQQmFqZiacloFuGiP3ah1sbCxCosidXFDfqG8+6fO1oR2dTJTlsOy4VFmUfw== +"@jest/reporters@^29.5.0": + version "29.5.0" + resolved "https://registry.yarnpkg.com/@jest/reporters/-/reporters-29.5.0.tgz#985dfd91290cd78ddae4914ba7921bcbabe8ac9b" + integrity sha512-D05STXqj/M8bP9hQNSICtPqz97u7ffGzZu+9XLucXhkOFBqKcXe04JLZOgIekOxdb73MAoBUFnqvf7MCpKk5OA== dependencies: "@bcoe/v8-coverage" "^0.2.3" - "@jest/console" "^27.5.1" - "@jest/test-result" "^27.5.1" - "@jest/transform" "^27.5.1" - "@jest/types" "^27.5.1" + "@jest/console" "^29.5.0" + "@jest/test-result" "^29.5.0" + "@jest/transform" "^29.5.0" + "@jest/types" "^29.5.0" + "@jridgewell/trace-mapping" "^0.3.15" "@types/node" "*" chalk "^4.0.0" collect-v8-coverage "^1.0.0" exit "^0.1.2" - glob "^7.1.2" + glob "^7.1.3" graceful-fs "^4.2.9" istanbul-lib-coverage "^3.0.0" istanbul-lib-instrument "^5.1.0" istanbul-lib-report "^3.0.0" istanbul-lib-source-maps "^4.0.0" istanbul-reports "^3.1.3" - jest-haste-map "^27.5.1" - jest-resolve "^27.5.1" - jest-util "^27.5.1" - jest-worker "^27.5.1" + jest-message-util "^29.5.0" + jest-util "^29.5.0" + jest-worker "^29.5.0" slash "^3.0.0" - source-map "^0.6.0" string-length "^4.0.1" - terminal-link "^2.0.0" - v8-to-istanbul "^8.1.0" + strip-ansi "^6.0.0" + v8-to-istanbul "^9.0.1" -"@jest/source-map@^27.5.1": - version "27.5.1" - resolved "https://registry.yarnpkg.com/@jest/source-map/-/source-map-27.5.1.tgz#6608391e465add4205eae073b55e7f279e04e8cf" - integrity sha512-y9NIHUYF3PJRlHk98NdC/N1gl88BL08aQQgu4k4ZopQkCw9t9cV8mtl3TV8b/YCB8XaVTFrmUTAJvjsntDireg== +"@jest/schemas@^29.4.3": + version "29.4.3" + resolved "https://registry.yarnpkg.com/@jest/schemas/-/schemas-29.4.3.tgz#39cf1b8469afc40b6f5a2baaa146e332c4151788" + integrity sha512-VLYKXQmtmuEz6IxJsrZwzG9NvtkQsWNnWMsKxqWNu3+CnfzJQhp0WDDKWLVV9hLKr0l3SLLFRqcYHjhtyuDVxg== dependencies: + "@sinclair/typebox" "^0.25.16" + +"@jest/source-map@^29.4.3": + version "29.4.3" + resolved "https://registry.yarnpkg.com/@jest/source-map/-/source-map-29.4.3.tgz#ff8d05cbfff875d4a791ab679b4333df47951d20" + integrity sha512-qyt/mb6rLyd9j1jUts4EQncvS6Yy3PM9HghnNv86QBlV+zdL2inCdK1tuVlL+J+lpiw2BI67qXOrX3UurBqQ1w== + dependencies: + "@jridgewell/trace-mapping" "^0.3.15" callsites "^3.0.0" graceful-fs "^4.2.9" - source-map "^0.6.0" -"@jest/test-result@^27.5.1": - version "27.5.1" - resolved "https://registry.yarnpkg.com/@jest/test-result/-/test-result-27.5.1.tgz#56a6585fa80f7cdab72b8c5fc2e871d03832f5bb" - integrity sha512-EW35l2RYFUcUQxFJz5Cv5MTOxlJIQs4I7gxzi2zVU7PJhOwfYq1MdC5nhSmYjX1gmMmLPvB3sIaC+BkcHRBfag== +"@jest/test-result@^29.5.0": + version "29.5.0" + resolved "https://registry.yarnpkg.com/@jest/test-result/-/test-result-29.5.0.tgz#7c856a6ca84f45cc36926a4e9c6b57f1973f1408" + integrity sha512-fGl4rfitnbfLsrfx1uUpDEESS7zM8JdgZgOCQuxQvL1Sn/I6ijeAVQWGfXI9zb1i9Mzo495cIpVZhA0yr60PkQ== dependencies: - "@jest/console" "^27.5.1" - "@jest/types" "^27.5.1" + "@jest/console" "^29.5.0" + "@jest/types" "^29.5.0" "@types/istanbul-lib-coverage" "^2.0.0" collect-v8-coverage "^1.0.0" -"@jest/test-sequencer@^27.5.1": - version "27.5.1" - resolved "https://registry.yarnpkg.com/@jest/test-sequencer/-/test-sequencer-27.5.1.tgz#4057e0e9cea4439e544c6353c6affe58d095745b" - integrity sha512-LCheJF7WB2+9JuCS7VB/EmGIdQuhtqjRNI9A43idHv3E4KltCTsPsLxvdaubFHSYwY/fNjMWjl6vNRhDiN7vpQ== +"@jest/test-sequencer@^29.5.0": + version "29.5.0" + resolved "https://registry.yarnpkg.com/@jest/test-sequencer/-/test-sequencer-29.5.0.tgz#34d7d82d3081abd523dbddc038a3ddcb9f6d3cc4" + integrity sha512-yPafQEcKjkSfDXyvtgiV4pevSeyuA6MQr6ZIdVkWJly9vkqjnFfcfhRQqpD5whjoU8EORki752xQmjaqoFjzMQ== dependencies: - "@jest/test-result" "^27.5.1" + "@jest/test-result" "^29.5.0" graceful-fs "^4.2.9" - jest-haste-map "^27.5.1" - jest-runtime "^27.5.1" + jest-haste-map "^29.5.0" + slash "^3.0.0" -"@jest/transform@^27.5.1": - version "27.5.1" - resolved "https://registry.yarnpkg.com/@jest/transform/-/transform-27.5.1.tgz#6c3501dcc00c4c08915f292a600ece5ecfe1f409" - integrity sha512-ipON6WtYgl/1329g5AIJVbUuEh0wZVbdpGwC99Jw4LwuoBNS95MVphU6zOeD9pDkon+LLbFL7lOQRapbB8SCHw== +"@jest/transform@^29.5.0": + version "29.5.0" + resolved "https://registry.yarnpkg.com/@jest/transform/-/transform-29.5.0.tgz#cf9c872d0965f0cbd32f1458aa44a2b1988b00f9" + integrity sha512-8vbeZWqLJOvHaDfeMuoHITGKSz5qWc9u04lnWrQE3VyuSw604PzQM824ZeX9XSjUCeDiE3GuxZe5UKa8J61NQw== dependencies: - "@babel/core" "^7.1.0" - "@jest/types" "^27.5.1" + "@babel/core" "^7.11.6" + "@jest/types" "^29.5.0" + "@jridgewell/trace-mapping" "^0.3.15" babel-plugin-istanbul "^6.1.1" chalk "^4.0.0" - convert-source-map "^1.4.0" - fast-json-stable-stringify "^2.0.0" + convert-source-map "^2.0.0" + fast-json-stable-stringify "^2.1.0" graceful-fs "^4.2.9" - jest-haste-map "^27.5.1" - jest-regex-util "^27.5.1" - jest-util "^27.5.1" + jest-haste-map "^29.5.0" + jest-regex-util "^29.4.3" + jest-util "^29.5.0" micromatch "^4.0.4" pirates "^4.0.4" slash "^3.0.0" - source-map "^0.6.1" - write-file-atomic "^3.0.0" + write-file-atomic "^4.0.2" "@jest/types@^26.6.2": version "26.6.2" @@ -1071,38 +1322,60 @@ "@types/yargs" "^16.0.0" chalk "^4.0.0" -"@jridgewell/gen-mapping@^0.1.0": - version "0.1.1" - resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.1.1.tgz#e5d2e450306a9491e3bd77e323e38d7aff315996" - integrity sha512-sQXCasFk+U8lWYEe66WxRDOE9PjVz4vSM51fTu3Hw+ClTpUSQb718772vH3pyS5pShp6lvQM7SxgIDXXXmOX7w== +"@jest/types@^29.5.0": + version "29.5.0" + resolved "https://registry.yarnpkg.com/@jest/types/-/types-29.5.0.tgz#f59ef9b031ced83047c67032700d8c807d6e1593" + integrity sha512-qbu7kN6czmVRc3xWFQcAN03RAUamgppVUdXrvl1Wr3jlNF93o9mJbGcDWrwGB6ht44u7efB1qCFgVQmca24Uog== dependencies: - "@jridgewell/set-array" "^1.0.0" - "@jridgewell/sourcemap-codec" "^1.4.10" + "@jest/schemas" "^29.4.3" + "@types/istanbul-lib-coverage" "^2.0.0" + "@types/istanbul-reports" "^3.0.0" + "@types/node" "*" + "@types/yargs" "^17.0.8" + chalk "^4.0.0" -"@jridgewell/gen-mapping@^0.3.2": - version "0.3.2" - resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz#c1aedc61e853f2bb9f5dfe6d4442d3b565b253b9" - integrity sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A== +"@jridgewell/gen-mapping@^0.3.0", "@jridgewell/gen-mapping@^0.3.2": + version "0.3.3" + resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz#7e02e6eb5df901aaedb08514203b096614024098" + integrity sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ== dependencies: "@jridgewell/set-array" "^1.0.1" "@jridgewell/sourcemap-codec" "^1.4.10" "@jridgewell/trace-mapping" "^0.3.9" -"@jridgewell/resolve-uri@^3.0.3": +"@jridgewell/resolve-uri@3.1.0": version "3.1.0" resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz#2203b118c157721addfe69d47b70465463066d78" integrity sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w== -"@jridgewell/set-array@^1.0.0", "@jridgewell/set-array@^1.0.1": +"@jridgewell/resolve-uri@^3.0.3": + version "3.1.1" + resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz#c08679063f279615a3326583ba3a90d1d82cc721" + integrity sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA== + +"@jridgewell/set-array@^1.0.1": version "1.1.2" resolved "https://registry.yarnpkg.com/@jridgewell/set-array/-/set-array-1.1.2.tgz#7c6cf998d6d20b914c0a55a91ae928ff25965e72" integrity sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw== -"@jridgewell/sourcemap-codec@^1.4.10": +"@jridgewell/source-map@^0.3.2": + version "0.3.3" + resolved "https://registry.yarnpkg.com/@jridgewell/source-map/-/source-map-0.3.3.tgz#8108265659d4c33e72ffe14e33d6cc5eb59f2fda" + integrity sha512-b+fsZXeLYi9fEULmfBrhxn4IrPlINf8fiNarzTof004v3lFdntdwa9PF7vFJqm3mg7s+ScJMxXaE3Acp1irZcg== + dependencies: + "@jridgewell/gen-mapping" "^0.3.0" + "@jridgewell/trace-mapping" "^0.3.9" + +"@jridgewell/sourcemap-codec@1.4.14": version "1.4.14" resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz#add4c98d341472a289190b424efbdb096991bb24" integrity sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw== +"@jridgewell/sourcemap-codec@^1.4.10": + version "1.4.15" + resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz#d7c6e6755c78567a951e04ab52ef0fd26de59f32" + integrity sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg== + "@jridgewell/trace-mapping@0.3.9": version "0.3.9" resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz#6534fd5933a53ba7cbf3a17615e273a0d1273ff9" @@ -1111,684 +1384,109 @@ "@jridgewell/resolve-uri" "^3.0.3" "@jridgewell/sourcemap-codec" "^1.4.10" -"@jridgewell/trace-mapping@^0.3.9": - version "0.3.15" - resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.15.tgz#aba35c48a38d3fd84b37e66c9c0423f9744f9774" - integrity sha512-oWZNOULl+UbhsgB51uuZzglikfIKSUBO/M9W2OfEjn7cmqoAiCgmv9lyACTUacZwBz0ITnJ2NqjU8Tx0DHL88g== - dependencies: - "@jridgewell/resolve-uri" "^3.0.3" - "@jridgewell/sourcemap-codec" "^1.4.10" - -"@lerna/add@4.0.0": - version "4.0.0" - resolved "https://registry.yarnpkg.com/@lerna/add/-/add-4.0.0.tgz#c36f57d132502a57b9e7058d1548b7a565ef183f" - integrity sha512-cpmAH1iS3k8JBxNvnMqrGTTjbY/ZAiKa1ChJzFevMYY3eeqbvhsBKnBcxjRXtdrJ6bd3dCQM+ZtK+0i682Fhng== - dependencies: - "@lerna/bootstrap" "4.0.0" - "@lerna/command" "4.0.0" - "@lerna/filter-options" "4.0.0" - "@lerna/npm-conf" "4.0.0" - "@lerna/validation-error" "4.0.0" - dedent "^0.7.0" - npm-package-arg "^8.1.0" - p-map "^4.0.0" - pacote "^11.2.6" - semver "^7.3.4" - -"@lerna/bootstrap@4.0.0": - version "4.0.0" - resolved "https://registry.yarnpkg.com/@lerna/bootstrap/-/bootstrap-4.0.0.tgz#5f5c5e2c6cfc8fcec50cb2fbe569a8c607101891" - integrity sha512-RkS7UbeM2vu+kJnHzxNRCLvoOP9yGNgkzRdy4UV2hNalD7EP41bLvRVOwRYQ7fhc2QcbhnKNdOBihYRL0LcKtw== - dependencies: - "@lerna/command" "4.0.0" - "@lerna/filter-options" "4.0.0" - "@lerna/has-npm-version" "4.0.0" - "@lerna/npm-install" "4.0.0" - "@lerna/package-graph" "4.0.0" - "@lerna/pulse-till-done" "4.0.0" - "@lerna/rimraf-dir" "4.0.0" - "@lerna/run-lifecycle" "4.0.0" - "@lerna/run-topologically" "4.0.0" - "@lerna/symlink-binary" "4.0.0" - "@lerna/symlink-dependencies" "4.0.0" - "@lerna/validation-error" "4.0.0" - dedent "^0.7.0" - get-port "^5.1.1" - multimatch "^5.0.0" - npm-package-arg "^8.1.0" - npmlog "^4.1.2" - p-map "^4.0.0" - p-map-series "^2.1.0" - p-waterfall "^2.1.1" - read-package-tree "^5.3.1" - semver "^7.3.4" - -"@lerna/changed@4.0.0": - version "4.0.0" - resolved "https://registry.yarnpkg.com/@lerna/changed/-/changed-4.0.0.tgz#b9fc76cea39b9292a6cd263f03eb57af85c9270b" - integrity sha512-cD+KuPRp6qiPOD+BO6S6SN5cARspIaWSOqGBpGnYzLb4uWT8Vk4JzKyYtc8ym1DIwyoFXHosXt8+GDAgR8QrgQ== - dependencies: - "@lerna/collect-updates" "4.0.0" - "@lerna/command" "4.0.0" - "@lerna/listable" "4.0.0" - "@lerna/output" "4.0.0" - -"@lerna/check-working-tree@4.0.0": - version "4.0.0" - resolved "https://registry.yarnpkg.com/@lerna/check-working-tree/-/check-working-tree-4.0.0.tgz#257e36a602c00142e76082a19358e3e1ae8dbd58" - integrity sha512-/++bxM43jYJCshBiKP5cRlCTwSJdRSxVmcDAXM+1oUewlZJVSVlnks5eO0uLxokVFvLhHlC5kHMc7gbVFPHv6Q== +"@jridgewell/trace-mapping@^0.3.12", "@jridgewell/trace-mapping@^0.3.15", "@jridgewell/trace-mapping@^0.3.17", "@jridgewell/trace-mapping@^0.3.9": + version "0.3.18" + resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.18.tgz#25783b2086daf6ff1dcb53c9249ae480e4dd4cd6" + integrity sha512-w+niJYzMHdd7USdiH2U6869nqhD2nbfZXND5Yp93qIbEmnDNk7PD48o+YchRVpzMU7M6jVCbenTR7PA1FLQ9pA== dependencies: - "@lerna/collect-uncommitted" "4.0.0" - "@lerna/describe-ref" "4.0.0" - "@lerna/validation-error" "4.0.0" + "@jridgewell/resolve-uri" "3.1.0" + "@jridgewell/sourcemap-codec" "1.4.14" -"@lerna/child-process@4.0.0": - version "4.0.0" - resolved "https://registry.yarnpkg.com/@lerna/child-process/-/child-process-4.0.0.tgz#341b96a57dffbd9705646d316e231df6fa4df6e1" - integrity sha512-XtCnmCT9eyVsUUHx6y/CTBYdV9g2Cr/VxyseTWBgfIur92/YKClfEtJTbOh94jRT62hlKLqSvux/UhxXVh613Q== +"@lerna/child-process@6.6.1": + version "6.6.1" + resolved "https://registry.yarnpkg.com/@lerna/child-process/-/child-process-6.6.1.tgz#e31bc411ad6d474cf7b676904da6f77f58fd64eb" + integrity sha512-yUCDCcRNNbI9UUsUB6FYEmDHpo5Tn/f0q5D7vhDP4i6Or8kBj82y7+e31hwfLvK2ykOYlDVs2MxAluH/+QUBOQ== dependencies: chalk "^4.1.0" execa "^5.0.0" strong-log-transformer "^2.1.0" -"@lerna/clean@4.0.0": - version "4.0.0" - resolved "https://registry.yarnpkg.com/@lerna/clean/-/clean-4.0.0.tgz#8f778b6f2617aa2a936a6b5e085ae62498e57dc5" - integrity sha512-uugG2iN9k45ITx2jtd8nEOoAtca8hNlDCUM0N3lFgU/b1mEQYAPRkqr1qs4FLRl/Y50ZJ41wUz1eazS+d/0osA== - dependencies: - "@lerna/command" "4.0.0" - "@lerna/filter-options" "4.0.0" - "@lerna/prompt" "4.0.0" - "@lerna/pulse-till-done" "4.0.0" - "@lerna/rimraf-dir" "4.0.0" - p-map "^4.0.0" - p-map-series "^2.1.0" - p-waterfall "^2.1.1" - -"@lerna/cli@4.0.0": - version "4.0.0" - resolved "https://registry.yarnpkg.com/@lerna/cli/-/cli-4.0.0.tgz#8eabd334558836c1664df23f19acb95e98b5bbf3" - integrity sha512-Neaw3GzFrwZiRZv2g7g6NwFjs3er1vhraIniEs0jjVLPMNC4eata0na3GfE5yibkM/9d3gZdmihhZdZ3EBdvYA== - dependencies: - "@lerna/global-options" "4.0.0" - dedent "^0.7.0" - npmlog "^4.1.2" - yargs "^16.2.0" - -"@lerna/collect-uncommitted@4.0.0": - version "4.0.0" - resolved "https://registry.yarnpkg.com/@lerna/collect-uncommitted/-/collect-uncommitted-4.0.0.tgz#855cd64612969371cfc2453b90593053ff1ba779" - integrity sha512-ufSTfHZzbx69YNj7KXQ3o66V4RC76ffOjwLX0q/ab//61bObJ41n03SiQEhSlmpP+gmFbTJ3/7pTe04AHX9m/g== - dependencies: - "@lerna/child-process" "4.0.0" - chalk "^4.1.0" - npmlog "^4.1.2" - -"@lerna/collect-updates@4.0.0": - version "4.0.0" - resolved "https://registry.yarnpkg.com/@lerna/collect-updates/-/collect-updates-4.0.0.tgz#8e208b1bafd98a372ff1177f7a5e288f6bea8041" - integrity sha512-bnNGpaj4zuxsEkyaCZLka9s7nMs58uZoxrRIPJ+nrmrZYp1V5rrd+7/NYTuunOhY2ug1sTBvTAxj3NZQ+JKnOw== +"@lerna/create@6.6.1": + version "6.6.1" + resolved "https://registry.yarnpkg.com/@lerna/create/-/create-6.6.1.tgz#fc20f09e10b612d424a576775ad6eefe6aa96517" + integrity sha512-GDmHFhQ0mr0RcXWXrsLyfMV6ch/dZV/Ped1e6sFVQhsLL9P+FFXX1ZWxa/dQQ90VWF2qWcmK0+S/L3kUz2xvTA== dependencies: - "@lerna/child-process" "4.0.0" - "@lerna/describe-ref" "4.0.0" - minimatch "^3.0.4" - npmlog "^4.1.2" - slash "^3.0.0" - -"@lerna/command@4.0.0": - version "4.0.0" - resolved "https://registry.yarnpkg.com/@lerna/command/-/command-4.0.0.tgz#991c7971df8f5bf6ae6e42c808869a55361c1b98" - integrity sha512-LM9g3rt5FsPNFqIHUeRwWXLNHJ5NKzOwmVKZ8anSp4e1SPrv2HNc1V02/9QyDDZK/w+5POXH5lxZUI1CHaOK/A== - dependencies: - "@lerna/child-process" "4.0.0" - "@lerna/package-graph" "4.0.0" - "@lerna/project" "4.0.0" - "@lerna/validation-error" "4.0.0" - "@lerna/write-log-file" "4.0.0" - clone-deep "^4.0.1" + "@lerna/child-process" "6.6.1" dedent "^0.7.0" - execa "^5.0.0" - is-ci "^2.0.0" - npmlog "^4.1.2" - -"@lerna/conventional-commits@4.0.0": - version "4.0.0" - resolved "https://registry.yarnpkg.com/@lerna/conventional-commits/-/conventional-commits-4.0.0.tgz#660fb2c7b718cb942ead70110df61f18c6f99750" - integrity sha512-CSUQRjJHFrH8eBn7+wegZLV3OrNc0Y1FehYfYGhjLE2SIfpCL4bmfu/ViYuHh9YjwHaA+4SX6d3hR+xkeseKmw== - dependencies: - "@lerna/validation-error" "4.0.0" - conventional-changelog-angular "^5.0.12" - conventional-changelog-core "^4.2.2" - conventional-recommended-bump "^6.1.0" fs-extra "^9.1.0" - get-stream "^6.0.0" - lodash.template "^4.5.0" - npm-package-arg "^8.1.0" - npmlog "^4.1.2" - pify "^5.0.0" - semver "^7.3.4" - -"@lerna/create-symlink@4.0.0": - version "4.0.0" - resolved "https://registry.yarnpkg.com/@lerna/create-symlink/-/create-symlink-4.0.0.tgz#8c5317ce5ae89f67825443bd7651bf4121786228" - integrity sha512-I0phtKJJdafUiDwm7BBlEUOtogmu8+taxq6PtIrxZbllV9hWg59qkpuIsiFp+no7nfRVuaasNYHwNUhDAVQBig== - dependencies: - cmd-shim "^4.1.0" - fs-extra "^9.1.0" - npmlog "^4.1.2" - -"@lerna/create@4.0.0": - version "4.0.0" - resolved "https://registry.yarnpkg.com/@lerna/create/-/create-4.0.0.tgz#b6947e9b5dfb6530321952998948c3e63d64d730" - integrity sha512-mVOB1niKByEUfxlbKTM1UNECWAjwUdiioIbRQZEeEabtjCL69r9rscIsjlGyhGWCfsdAG5wfq4t47nlDXdLLag== - dependencies: - "@lerna/child-process" "4.0.0" - "@lerna/command" "4.0.0" - "@lerna/npm-conf" "4.0.0" - "@lerna/validation-error" "4.0.0" - dedent "^0.7.0" - fs-extra "^9.1.0" - globby "^11.0.2" - init-package-json "^2.0.2" - npm-package-arg "^8.1.0" - p-reduce "^2.1.0" - pacote "^11.2.6" + init-package-json "^3.0.2" + npm-package-arg "8.1.1" + p-reduce "^2.1.0" + pacote "^13.6.1" pify "^5.0.0" semver "^7.3.4" slash "^3.0.0" validate-npm-package-license "^3.0.4" - validate-npm-package-name "^3.0.0" - whatwg-url "^8.4.0" + validate-npm-package-name "^4.0.0" yargs-parser "20.2.4" -"@lerna/describe-ref@4.0.0": - version "4.0.0" - resolved "https://registry.yarnpkg.com/@lerna/describe-ref/-/describe-ref-4.0.0.tgz#53c53b4ea65fdceffa072a62bfebe6772c45d9ec" - integrity sha512-eTU5+xC4C5Gcgz+Ey4Qiw9nV2B4JJbMulsYJMW8QjGcGh8zudib7Sduj6urgZXUYNyhYpRs+teci9M2J8u+UvQ== - dependencies: - "@lerna/child-process" "4.0.0" - npmlog "^4.1.2" - -"@lerna/diff@4.0.0": - version "4.0.0" - resolved "https://registry.yarnpkg.com/@lerna/diff/-/diff-4.0.0.tgz#6d3071817aaa4205a07bf77cfc6e932796d48b92" - integrity sha512-jYPKprQVg41+MUMxx6cwtqsNm0Yxx9GDEwdiPLwcUTFx+/qKCEwifKNJ1oGIPBxyEHX2PFCOjkK39lHoj2qiag== - dependencies: - "@lerna/child-process" "4.0.0" - "@lerna/command" "4.0.0" - "@lerna/validation-error" "4.0.0" - npmlog "^4.1.2" - -"@lerna/exec@4.0.0": - version "4.0.0" - resolved "https://registry.yarnpkg.com/@lerna/exec/-/exec-4.0.0.tgz#eb6cb95cb92d42590e9e2d628fcaf4719d4a8be6" - integrity sha512-VGXtL/b/JfY84NB98VWZpIExfhLOzy0ozm/0XaS4a2SmkAJc5CeUfrhvHxxkxiTBLkU+iVQUyYEoAT0ulQ8PCw== - dependencies: - "@lerna/child-process" "4.0.0" - "@lerna/command" "4.0.0" - "@lerna/filter-options" "4.0.0" - "@lerna/profiler" "4.0.0" - "@lerna/run-topologically" "4.0.0" - "@lerna/validation-error" "4.0.0" - p-map "^4.0.0" - -"@lerna/filter-options@4.0.0": - version "4.0.0" - resolved "https://registry.yarnpkg.com/@lerna/filter-options/-/filter-options-4.0.0.tgz#ac94cc515d7fa3b47e2f7d74deddeabb1de5e9e6" - integrity sha512-vV2ANOeZhOqM0rzXnYcFFCJ/kBWy/3OA58irXih9AMTAlQLymWAK0akWybl++sUJ4HB9Hx12TOqaXbYS2NM5uw== - dependencies: - "@lerna/collect-updates" "4.0.0" - "@lerna/filter-packages" "4.0.0" - dedent "^0.7.0" - npmlog "^4.1.2" - -"@lerna/filter-packages@4.0.0": - version "4.0.0" - resolved "https://registry.yarnpkg.com/@lerna/filter-packages/-/filter-packages-4.0.0.tgz#b1f70d70e1de9cdd36a4e50caa0ac501f8d012f2" - integrity sha512-+4AJIkK7iIiOaqCiVTYJxh/I9qikk4XjNQLhE3kixaqgMuHl1NQ99qXRR0OZqAWB9mh8Z1HA9bM5K1HZLBTOqA== - dependencies: - "@lerna/validation-error" "4.0.0" - multimatch "^5.0.0" - npmlog "^4.1.2" - -"@lerna/get-npm-exec-opts@4.0.0": - version "4.0.0" - resolved "https://registry.yarnpkg.com/@lerna/get-npm-exec-opts/-/get-npm-exec-opts-4.0.0.tgz#dc955be94a4ae75c374ef9bce91320887d34608f" - integrity sha512-yvmkerU31CTWS2c7DvmAWmZVeclPBqI7gPVr5VATUKNWJ/zmVcU4PqbYoLu92I9Qc4gY1TuUplMNdNuZTSL7IQ== - dependencies: - npmlog "^4.1.2" - -"@lerna/get-packed@4.0.0": - version "4.0.0" - resolved "https://registry.yarnpkg.com/@lerna/get-packed/-/get-packed-4.0.0.tgz#0989d61624ac1f97e393bdad2137c49cd7a37823" - integrity sha512-rfWONRsEIGyPJTxFzC8ECb3ZbsDXJbfqWYyeeQQDrJRPnEJErlltRLPLgC2QWbxFgFPsoDLeQmFHJnf0iDfd8w== - dependencies: - fs-extra "^9.1.0" - ssri "^8.0.1" - tar "^6.1.0" - -"@lerna/github-client@4.0.0": - version "4.0.0" - resolved "https://registry.yarnpkg.com/@lerna/github-client/-/github-client-4.0.0.tgz#2ced67721363ef70f8e12ffafce4410918f4a8a4" - integrity sha512-2jhsldZtTKXYUBnOm23Lb0Fx8G4qfSXF9y7UpyUgWUj+YZYd+cFxSuorwQIgk5P4XXrtVhsUesIsli+BYSThiw== - dependencies: - "@lerna/child-process" "4.0.0" - "@octokit/plugin-enterprise-rest" "^6.0.1" - "@octokit/rest" "^18.1.0" - git-url-parse "^11.4.4" - npmlog "^4.1.2" - -"@lerna/gitlab-client@4.0.0": - version "4.0.0" - resolved "https://registry.yarnpkg.com/@lerna/gitlab-client/-/gitlab-client-4.0.0.tgz#00dad73379c7b38951d4b4ded043504c14e2b67d" - integrity sha512-OMUpGSkeDWFf7BxGHlkbb35T7YHqVFCwBPSIR6wRsszY8PAzCYahtH3IaJzEJyUg6vmZsNl0FSr3pdA2skhxqA== - dependencies: - node-fetch "^2.6.1" - npmlog "^4.1.2" - whatwg-url "^8.4.0" - -"@lerna/global-options@4.0.0": - version "4.0.0" - resolved "https://registry.yarnpkg.com/@lerna/global-options/-/global-options-4.0.0.tgz#c7d8b0de6a01d8a845e2621ea89e7f60f18c6a5f" - integrity sha512-TRMR8afAHxuYBHK7F++Ogop2a82xQjoGna1dvPOY6ltj/pEx59pdgcJfYcynYqMkFIk8bhLJJN9/ndIfX29FTQ== - -"@lerna/has-npm-version@4.0.0": - version "4.0.0" - resolved "https://registry.yarnpkg.com/@lerna/has-npm-version/-/has-npm-version-4.0.0.tgz#d3fc3292c545eb28bd493b36e6237cf0279f631c" - integrity sha512-LQ3U6XFH8ZmLCsvsgq1zNDqka0Xzjq5ibVN+igAI5ccRWNaUsE/OcmsyMr50xAtNQMYMzmpw5GVLAivT2/YzCg== - dependencies: - "@lerna/child-process" "4.0.0" - semver "^7.3.4" - -"@lerna/import@4.0.0": - version "4.0.0" - resolved "https://registry.yarnpkg.com/@lerna/import/-/import-4.0.0.tgz#bde656c4a451fa87ae41733ff8a8da60547c5465" - integrity sha512-FaIhd+4aiBousKNqC7TX1Uhe97eNKf5/SC7c5WZANVWtC7aBWdmswwDt3usrzCNpj6/Wwr9EtEbYROzxKH8ffg== - dependencies: - "@lerna/child-process" "4.0.0" - "@lerna/command" "4.0.0" - "@lerna/prompt" "4.0.0" - "@lerna/pulse-till-done" "4.0.0" - "@lerna/validation-error" "4.0.0" - dedent "^0.7.0" - fs-extra "^9.1.0" - p-map-series "^2.1.0" - -"@lerna/info@4.0.0": - version "4.0.0" - resolved "https://registry.yarnpkg.com/@lerna/info/-/info-4.0.0.tgz#b9fb0e479d60efe1623603958a831a88b1d7f1fc" - integrity sha512-8Uboa12kaCSZEn4XRfPz5KU9XXoexSPS4oeYGj76s2UQb1O1GdnEyfjyNWoUl1KlJ2i/8nxUskpXIftoFYH0/Q== - dependencies: - "@lerna/command" "4.0.0" - "@lerna/output" "4.0.0" - envinfo "^7.7.4" - -"@lerna/init@4.0.0": - version "4.0.0" - resolved "https://registry.yarnpkg.com/@lerna/init/-/init-4.0.0.tgz#dadff67e6dfb981e8ccbe0e6a310e837962f6c7a" - integrity sha512-wY6kygop0BCXupzWj5eLvTUqdR7vIAm0OgyV9WHpMYQGfs1V22jhztt8mtjCloD/O0nEe4tJhdG62XU5aYmPNQ== - dependencies: - "@lerna/child-process" "4.0.0" - "@lerna/command" "4.0.0" - fs-extra "^9.1.0" - p-map "^4.0.0" - write-json-file "^4.3.0" - -"@lerna/link@4.0.0": - version "4.0.0" - resolved "https://registry.yarnpkg.com/@lerna/link/-/link-4.0.0.tgz#c3a38aabd44279d714e90f2451e31b63f0fb65ba" - integrity sha512-KlvPi7XTAcVOByfaLlOeYOfkkDcd+bejpHMCd1KcArcFTwijOwXOVi24DYomIeHvy6HsX/IUquJ4PPUJIeB4+w== - dependencies: - "@lerna/command" "4.0.0" - "@lerna/package-graph" "4.0.0" - "@lerna/symlink-dependencies" "4.0.0" - p-map "^4.0.0" - slash "^3.0.0" - -"@lerna/list@4.0.0": - version "4.0.0" - resolved "https://registry.yarnpkg.com/@lerna/list/-/list-4.0.0.tgz#24b4e6995bd73f81c556793fe502b847efd9d1d7" - integrity sha512-L2B5m3P+U4Bif5PultR4TI+KtW+SArwq1i75QZ78mRYxPc0U/piau1DbLOmwrdqr99wzM49t0Dlvl6twd7GHFg== - dependencies: - "@lerna/command" "4.0.0" - "@lerna/filter-options" "4.0.0" - "@lerna/listable" "4.0.0" - "@lerna/output" "4.0.0" - -"@lerna/listable@4.0.0": - version "4.0.0" - resolved "https://registry.yarnpkg.com/@lerna/listable/-/listable-4.0.0.tgz#d00d6cb4809b403f2b0374fc521a78e318b01214" - integrity sha512-/rPOSDKsOHs5/PBLINZOkRIX1joOXUXEtyUs5DHLM8q6/RP668x/1lFhw6Dx7/U+L0+tbkpGtZ1Yt0LewCLgeQ== - dependencies: - "@lerna/query-graph" "4.0.0" - chalk "^4.1.0" - columnify "^1.5.4" - -"@lerna/log-packed@4.0.0": - version "4.0.0" - resolved "https://registry.yarnpkg.com/@lerna/log-packed/-/log-packed-4.0.0.tgz#95168fe2e26ac6a71e42f4be857519b77e57a09f" - integrity sha512-+dpCiWbdzgMAtpajLToy9PO713IHoE6GV/aizXycAyA07QlqnkpaBNZ8DW84gHdM1j79TWockGJo9PybVhrrZQ== - dependencies: - byte-size "^7.0.0" - columnify "^1.5.4" - has-unicode "^2.0.1" - npmlog "^4.1.2" - -"@lerna/npm-conf@4.0.0": - version "4.0.0" - resolved "https://registry.yarnpkg.com/@lerna/npm-conf/-/npm-conf-4.0.0.tgz#b259fd1e1cee2bf5402b236e770140ff9ade7fd2" - integrity sha512-uS7H02yQNq3oejgjxAxqq/jhwGEE0W0ntr8vM3EfpCW1F/wZruwQw+7bleJQ9vUBjmdXST//tk8mXzr5+JXCfw== - dependencies: - config-chain "^1.1.12" - pify "^5.0.0" - -"@lerna/npm-dist-tag@4.0.0": - version "4.0.0" - resolved "https://registry.yarnpkg.com/@lerna/npm-dist-tag/-/npm-dist-tag-4.0.0.tgz#d1e99b4eccd3414142f0548ad331bf2d53f3257a" - integrity sha512-F20sg28FMYTgXqEQihgoqSfwmq+Id3zT23CnOwD+XQMPSy9IzyLf1fFVH319vXIw6NF6Pgs4JZN2Qty6/CQXGw== - dependencies: - "@lerna/otplease" "4.0.0" - npm-package-arg "^8.1.0" - npm-registry-fetch "^9.0.0" - npmlog "^4.1.2" - -"@lerna/npm-install@4.0.0": - version "4.0.0" - resolved "https://registry.yarnpkg.com/@lerna/npm-install/-/npm-install-4.0.0.tgz#31180be3ab3b7d1818a1a0c206aec156b7094c78" - integrity sha512-aKNxq2j3bCH3eXl3Fmu4D54s/YLL9WSwV8W7X2O25r98wzrO38AUN6AB9EtmAx+LV/SP15et7Yueg9vSaanRWg== - dependencies: - "@lerna/child-process" "4.0.0" - "@lerna/get-npm-exec-opts" "4.0.0" - fs-extra "^9.1.0" - npm-package-arg "^8.1.0" - npmlog "^4.1.2" - signal-exit "^3.0.3" - write-pkg "^4.0.0" - -"@lerna/npm-publish@4.0.0": - version "4.0.0" - resolved "https://registry.yarnpkg.com/@lerna/npm-publish/-/npm-publish-4.0.0.tgz#84eb62e876fe949ae1fd62c60804423dbc2c4472" - integrity sha512-vQb7yAPRo5G5r77DRjHITc9piR9gvEKWrmfCH7wkfBnGWEqu7n8/4bFQ7lhnkujvc8RXOsYpvbMQkNfkYibD/w== - dependencies: - "@lerna/otplease" "4.0.0" - "@lerna/run-lifecycle" "4.0.0" - fs-extra "^9.1.0" - libnpmpublish "^4.0.0" - npm-package-arg "^8.1.0" - npmlog "^4.1.2" - pify "^5.0.0" - read-package-json "^3.0.0" - -"@lerna/npm-run-script@4.0.0": - version "4.0.0" - resolved "https://registry.yarnpkg.com/@lerna/npm-run-script/-/npm-run-script-4.0.0.tgz#dfebf4f4601442e7c0b5214f9fb0d96c9350743b" - integrity sha512-Jmyh9/IwXJjOXqKfIgtxi0bxi1pUeKe5bD3S81tkcy+kyng/GNj9WSqD5ZggoNP2NP//s4CLDAtUYLdP7CU9rA== - dependencies: - "@lerna/child-process" "4.0.0" - "@lerna/get-npm-exec-opts" "4.0.0" - npmlog "^4.1.2" - -"@lerna/otplease@4.0.0": - version "4.0.0" - resolved "https://registry.yarnpkg.com/@lerna/otplease/-/otplease-4.0.0.tgz#84972eb43448f8a1077435ba1c5e59233b725850" - integrity sha512-Sgzbqdk1GH4psNiT6hk+BhjOfIr/5KhGBk86CEfHNJTk9BK4aZYyJD4lpDbDdMjIV4g03G7pYoqHzH765T4fxw== - dependencies: - "@lerna/prompt" "4.0.0" - -"@lerna/output@4.0.0": - version "4.0.0" - resolved "https://registry.yarnpkg.com/@lerna/output/-/output-4.0.0.tgz#b1d72215c0e35483e4f3e9994debc82c621851f2" - integrity sha512-Un1sHtO1AD7buDQrpnaYTi2EG6sLF+KOPEAMxeUYG5qG3khTs2Zgzq5WE3dt2N/bKh7naESt20JjIW6tBELP0w== - dependencies: - npmlog "^4.1.2" - -"@lerna/pack-directory@4.0.0": - version "4.0.0" - resolved "https://registry.yarnpkg.com/@lerna/pack-directory/-/pack-directory-4.0.0.tgz#8b617db95d20792f043aaaa13a9ccc0e04cb4c74" - integrity sha512-NJrmZNmBHS+5aM+T8N6FVbaKFScVqKlQFJNY2k7nsJ/uklNKsLLl6VhTQBPwMTbf6Tf7l6bcKzpy7aePuq9UiQ== - dependencies: - "@lerna/get-packed" "4.0.0" - "@lerna/package" "4.0.0" - "@lerna/run-lifecycle" "4.0.0" - npm-packlist "^2.1.4" - npmlog "^4.1.2" - tar "^6.1.0" - temp-write "^4.0.0" - -"@lerna/package-graph@4.0.0": - version "4.0.0" - resolved "https://registry.yarnpkg.com/@lerna/package-graph/-/package-graph-4.0.0.tgz#16a00253a8ac810f72041481cb46bcee8d8123dd" - integrity sha512-QED2ZCTkfXMKFoTGoccwUzjHtZMSf3UKX14A4/kYyBms9xfFsesCZ6SLI5YeySEgcul8iuIWfQFZqRw+Qrjraw== - dependencies: - "@lerna/prerelease-id-from-version" "4.0.0" - "@lerna/validation-error" "4.0.0" - npm-package-arg "^8.1.0" - npmlog "^4.1.2" - semver "^7.3.4" - -"@lerna/package@4.0.0": - version "4.0.0" - resolved "https://registry.yarnpkg.com/@lerna/package/-/package-4.0.0.tgz#1b4c259c4bcff45c876ee1d591a043aacbc0d6b7" - integrity sha512-l0M/izok6FlyyitxiQKr+gZLVFnvxRQdNhzmQ6nRnN9dvBJWn+IxxpM+cLqGACatTnyo9LDzNTOj2Db3+s0s8Q== - dependencies: - load-json-file "^6.2.0" - npm-package-arg "^8.1.0" - write-pkg "^4.0.0" - -"@lerna/prerelease-id-from-version@4.0.0": - version "4.0.0" - resolved "https://registry.yarnpkg.com/@lerna/prerelease-id-from-version/-/prerelease-id-from-version-4.0.0.tgz#c7e0676fcee1950d85630e108eddecdd5b48c916" - integrity sha512-GQqguzETdsYRxOSmdFZ6zDBXDErIETWOqomLERRY54f4p+tk4aJjoVdd9xKwehC9TBfIFvlRbL1V9uQGHh1opg== - dependencies: - semver "^7.3.4" - -"@lerna/profiler@4.0.0": - version "4.0.0" - resolved "https://registry.yarnpkg.com/@lerna/profiler/-/profiler-4.0.0.tgz#8a53ab874522eae15d178402bff90a14071908e9" - integrity sha512-/BaEbqnVh1LgW/+qz8wCuI+obzi5/vRE8nlhjPzdEzdmWmZXuCKyWSEzAyHOJWw1ntwMiww5dZHhFQABuoFz9Q== - dependencies: - fs-extra "^9.1.0" - npmlog "^4.1.2" - upath "^2.0.1" - -"@lerna/project@4.0.0": - version "4.0.0" - resolved "https://registry.yarnpkg.com/@lerna/project/-/project-4.0.0.tgz#ff84893935833533a74deff30c0e64ddb7f0ba6b" - integrity sha512-o0MlVbDkD5qRPkFKlBZsXZjoNTWPyuL58564nSfZJ6JYNmgAptnWPB2dQlAc7HWRZkmnC2fCkEdoU+jioPavbg== - dependencies: - "@lerna/package" "4.0.0" - "@lerna/validation-error" "4.0.0" - cosmiconfig "^7.0.0" - dedent "^0.7.0" - dot-prop "^6.0.1" - glob-parent "^5.1.1" - globby "^11.0.2" - load-json-file "^6.2.0" - npmlog "^4.1.2" - p-map "^4.0.0" - resolve-from "^5.0.0" - write-json-file "^4.3.0" - -"@lerna/prompt@4.0.0": - version "4.0.0" - resolved "https://registry.yarnpkg.com/@lerna/prompt/-/prompt-4.0.0.tgz#5ec69a803f3f0db0ad9f221dad64664d3daca41b" - integrity sha512-4Ig46oCH1TH5M7YyTt53fT6TuaKMgqUUaqdgxvp6HP6jtdak6+amcsqB8YGz2eQnw/sdxunx84DfI9XpoLj4bQ== - dependencies: - inquirer "^7.3.3" - npmlog "^4.1.2" - -"@lerna/publish@4.0.0": - version "4.0.0" - resolved "https://registry.yarnpkg.com/@lerna/publish/-/publish-4.0.0.tgz#f67011305adeba120066a3b6d984a5bb5fceef65" - integrity sha512-K8jpqjHrChH22qtkytA5GRKIVFEtqBF6JWj1I8dWZtHs4Jywn8yB1jQ3BAMLhqmDJjWJtRck0KXhQQKzDK2UPg== - dependencies: - "@lerna/check-working-tree" "4.0.0" - "@lerna/child-process" "4.0.0" - "@lerna/collect-updates" "4.0.0" - "@lerna/command" "4.0.0" - "@lerna/describe-ref" "4.0.0" - "@lerna/log-packed" "4.0.0" - "@lerna/npm-conf" "4.0.0" - "@lerna/npm-dist-tag" "4.0.0" - "@lerna/npm-publish" "4.0.0" - "@lerna/otplease" "4.0.0" - "@lerna/output" "4.0.0" - "@lerna/pack-directory" "4.0.0" - "@lerna/prerelease-id-from-version" "4.0.0" - "@lerna/prompt" "4.0.0" - "@lerna/pulse-till-done" "4.0.0" - "@lerna/run-lifecycle" "4.0.0" - "@lerna/run-topologically" "4.0.0" - "@lerna/validation-error" "4.0.0" - "@lerna/version" "4.0.0" - fs-extra "^9.1.0" - libnpmaccess "^4.0.1" - npm-package-arg "^8.1.0" - npm-registry-fetch "^9.0.0" - npmlog "^4.1.2" - p-map "^4.0.0" - p-pipe "^3.1.0" - pacote "^11.2.6" - semver "^7.3.4" - -"@lerna/pulse-till-done@4.0.0": - version "4.0.0" - resolved "https://registry.yarnpkg.com/@lerna/pulse-till-done/-/pulse-till-done-4.0.0.tgz#04bace7d483a8205c187b806bcd8be23d7bb80a3" - integrity sha512-Frb4F7QGckaybRhbF7aosLsJ5e9WuH7h0KUkjlzSByVycxY91UZgaEIVjS2oN9wQLrheLMHl6SiFY0/Pvo0Cxg== - dependencies: - npmlog "^4.1.2" - -"@lerna/query-graph@4.0.0": - version "4.0.0" - resolved "https://registry.yarnpkg.com/@lerna/query-graph/-/query-graph-4.0.0.tgz#09dd1c819ac5ee3f38db23931143701f8a6eef63" - integrity sha512-YlP6yI3tM4WbBmL9GCmNDoeQyzcyg1e4W96y/PKMZa5GbyUvkS2+Jc2kwPD+5KcXou3wQZxSPzR3Te5OenaDdg== - dependencies: - "@lerna/package-graph" "4.0.0" - -"@lerna/resolve-symlink@4.0.0": - version "4.0.0" - resolved "https://registry.yarnpkg.com/@lerna/resolve-symlink/-/resolve-symlink-4.0.0.tgz#6d006628a210c9b821964657a9e20a8c9a115e14" - integrity sha512-RtX8VEUzqT+uLSCohx8zgmjc6zjyRlh6i/helxtZTMmc4+6O4FS9q5LJas2uGO2wKvBlhcD6siibGt7dIC3xZA== - dependencies: - fs-extra "^9.1.0" - npmlog "^4.1.2" - read-cmd-shim "^2.0.0" - -"@lerna/rimraf-dir@4.0.0": - version "4.0.0" - resolved "https://registry.yarnpkg.com/@lerna/rimraf-dir/-/rimraf-dir-4.0.0.tgz#2edf3b62d4eb0ef4e44e430f5844667d551ec25a" - integrity sha512-QNH9ABWk9mcMJh2/muD9iYWBk1oQd40y6oH+f3wwmVGKYU5YJD//+zMiBI13jxZRtwBx0vmBZzkBkK1dR11cBg== - dependencies: - "@lerna/child-process" "4.0.0" - npmlog "^4.1.2" - path-exists "^4.0.0" - rimraf "^3.0.2" - -"@lerna/run-lifecycle@4.0.0": - version "4.0.0" - resolved "https://registry.yarnpkg.com/@lerna/run-lifecycle/-/run-lifecycle-4.0.0.tgz#e648a46f9210a9bcd7c391df6844498cb5079334" - integrity sha512-IwxxsajjCQQEJAeAaxF8QdEixfI7eLKNm4GHhXHrgBu185JcwScFZrj9Bs+PFKxwb+gNLR4iI5rpUdY8Y0UdGQ== - dependencies: - "@lerna/npm-conf" "4.0.0" - npm-lifecycle "^3.1.5" - npmlog "^4.1.2" - -"@lerna/run-topologically@4.0.0": - version "4.0.0" - resolved "https://registry.yarnpkg.com/@lerna/run-topologically/-/run-topologically-4.0.0.tgz#af846eeee1a09b0c2be0d1bfb5ef0f7b04bb1827" - integrity sha512-EVZw9hGwo+5yp+VL94+NXRYisqgAlj0jWKWtAIynDCpghRxCE5GMO3xrQLmQgqkpUl9ZxQFpICgYv5DW4DksQA== - dependencies: - "@lerna/query-graph" "4.0.0" - p-queue "^6.6.2" - -"@lerna/run@4.0.0": - version "4.0.0" - resolved "https://registry.yarnpkg.com/@lerna/run/-/run-4.0.0.tgz#4bc7fda055a729487897c23579694f6183c91262" - integrity sha512-9giulCOzlMPzcZS/6Eov6pxE9gNTyaXk0Man+iCIdGJNMrCnW7Dme0Z229WWP/UoxDKg71F2tMsVVGDiRd8fFQ== - dependencies: - "@lerna/command" "4.0.0" - "@lerna/filter-options" "4.0.0" - "@lerna/npm-run-script" "4.0.0" - "@lerna/output" "4.0.0" - "@lerna/profiler" "4.0.0" - "@lerna/run-topologically" "4.0.0" - "@lerna/timer" "4.0.0" - "@lerna/validation-error" "4.0.0" - p-map "^4.0.0" - -"@lerna/symlink-binary@4.0.0": - version "4.0.0" - resolved "https://registry.yarnpkg.com/@lerna/symlink-binary/-/symlink-binary-4.0.0.tgz#21009f62d53a425f136cb4c1a32c6b2a0cc02d47" - integrity sha512-zualodWC4q1QQc1pkz969hcFeWXOsVYZC5AWVtAPTDfLl+TwM7eG/O6oP+Rr3fFowspxo6b1TQ6sYfDV6HXNWA== - dependencies: - "@lerna/create-symlink" "4.0.0" - "@lerna/package" "4.0.0" - fs-extra "^9.1.0" - p-map "^4.0.0" - -"@lerna/symlink-dependencies@4.0.0": - version "4.0.0" - resolved "https://registry.yarnpkg.com/@lerna/symlink-dependencies/-/symlink-dependencies-4.0.0.tgz#8910eca084ae062642d0490d8972cf2d98e9ebbd" - integrity sha512-BABo0MjeUHNAe2FNGty1eantWp8u83BHSeIMPDxNq0MuW2K3CiQRaeWT3EGPAzXpGt0+hVzBrA6+OT0GPn7Yuw== - dependencies: - "@lerna/create-symlink" "4.0.0" - "@lerna/resolve-symlink" "4.0.0" - "@lerna/symlink-binary" "4.0.0" - fs-extra "^9.1.0" - p-map "^4.0.0" - p-map-series "^2.1.0" - -"@lerna/timer@4.0.0": - version "4.0.0" - resolved "https://registry.yarnpkg.com/@lerna/timer/-/timer-4.0.0.tgz#a52e51bfcd39bfd768988049ace7b15c1fd7a6da" - integrity sha512-WFsnlaE7SdOvjuyd05oKt8Leg3ENHICnvX3uYKKdByA+S3g+TCz38JsNs7OUZVt+ba63nC2nbXDlUnuT2Xbsfg== - -"@lerna/validation-error@4.0.0": - version "4.0.0" - resolved "https://registry.yarnpkg.com/@lerna/validation-error/-/validation-error-4.0.0.tgz#af9d62fe8304eaa2eb9a6ba1394f9aa807026d35" - integrity sha512-1rBOM5/koiVWlRi3V6dB863E1YzJS8v41UtsHgMr6gB2ncJ2LsQtMKlJpi3voqcgh41H8UsPXR58RrrpPpufyw== - dependencies: - npmlog "^4.1.2" - -"@lerna/version@4.0.0": - version "4.0.0" - resolved "https://registry.yarnpkg.com/@lerna/version/-/version-4.0.0.tgz#532659ec6154d8a8789c5ab53878663e244e3228" - integrity sha512-otUgiqs5W9zGWJZSCCMRV/2Zm2A9q9JwSDS7s/tlKq4mWCYriWo7+wsHEA/nPTMDyYyBO5oyZDj+3X50KDUzeA== - dependencies: - "@lerna/check-working-tree" "4.0.0" - "@lerna/child-process" "4.0.0" - "@lerna/collect-updates" "4.0.0" - "@lerna/command" "4.0.0" - "@lerna/conventional-commits" "4.0.0" - "@lerna/github-client" "4.0.0" - "@lerna/gitlab-client" "4.0.0" - "@lerna/output" "4.0.0" - "@lerna/prerelease-id-from-version" "4.0.0" - "@lerna/prompt" "4.0.0" - "@lerna/run-lifecycle" "4.0.0" - "@lerna/run-topologically" "4.0.0" - "@lerna/validation-error" "4.0.0" - chalk "^4.1.0" - dedent "^0.7.0" - load-json-file "^6.2.0" - minimatch "^3.0.4" - npmlog "^4.1.2" - p-map "^4.0.0" - p-pipe "^3.1.0" - p-reduce "^2.1.0" - p-waterfall "^2.1.1" - semver "^7.3.4" - slash "^3.0.0" - temp-write "^4.0.0" - write-json-file "^4.3.0" - -"@lerna/write-log-file@4.0.0": - version "4.0.0" - resolved "https://registry.yarnpkg.com/@lerna/write-log-file/-/write-log-file-4.0.0.tgz#18221a38a6a307d6b0a5844dd592ad53fa27091e" - integrity sha512-XRG5BloiArpXRakcnPHmEHJp+4AtnhRtpDIHSghmXD5EichI1uD73J7FgPp30mm2pDRq3FdqB0NbwSEsJ9xFQg== - dependencies: - npmlog "^4.1.2" - write-file-atomic "^3.0.3" +"@lerna/legacy-package-management@6.6.1": + version "6.6.1" + resolved "https://registry.yarnpkg.com/@lerna/legacy-package-management/-/legacy-package-management-6.6.1.tgz#1f44af40098b9396a4f698514ff2b87016b1ee3d" + integrity sha512-0EYxSFr34VgeudA5rvjGJSY7s4seITMVB7AJ9LRFv9QDUk6jpvapV13ZAaKnhDTxX5vNCfnJuWHXXWq0KyPF/Q== + dependencies: + "@npmcli/arborist" "6.2.3" + "@npmcli/run-script" "4.1.7" + "@nrwl/devkit" ">=15.5.2 < 16" + "@octokit/rest" "19.0.3" + byte-size "7.0.0" + chalk "4.1.0" + clone-deep "4.0.1" + cmd-shim "5.0.0" + columnify "1.6.0" + config-chain "1.1.12" + conventional-changelog-core "4.2.4" + conventional-recommended-bump "6.1.0" + cosmiconfig "7.0.0" + dedent "0.7.0" + dot-prop "6.0.1" + execa "5.0.0" + file-url "3.0.0" + find-up "5.0.0" + fs-extra "9.1.0" + get-port "5.1.1" + get-stream "6.0.0" + git-url-parse "13.1.0" + glob-parent "5.1.2" + globby "11.1.0" + graceful-fs "4.2.10" + has-unicode "2.0.1" + inquirer "8.2.4" + is-ci "2.0.0" + is-stream "2.0.0" + libnpmpublish "6.0.4" + load-json-file "6.2.0" + make-dir "3.1.0" + minimatch "3.0.5" + multimatch "5.0.0" + node-fetch "2.6.7" + npm-package-arg "8.1.1" + npm-packlist "5.1.1" + npm-registry-fetch "14.0.3" + npmlog "6.0.2" + p-map "4.0.0" + p-map-series "2.1.0" + p-queue "6.6.2" + p-waterfall "2.1.1" + pacote "13.6.2" + pify "5.0.0" + pretty-format "29.4.3" + read-cmd-shim "3.0.0" + read-package-json "5.0.1" + resolve-from "5.0.0" + semver "7.3.8" + signal-exit "3.0.7" + slash "3.0.0" + ssri "9.0.1" + strong-log-transformer "2.1.0" + tar "6.1.11" + temp-dir "1.0.0" + tempy "1.0.0" + upath "2.0.1" + uuid "8.3.2" + write-file-atomic "4.0.1" + write-pkg "4.0.0" + yargs "16.2.0" "@mapbox/node-pre-gyp@1.0.9": version "1.0.9" @@ -1805,6 +1503,21 @@ semver "^7.3.5" tar "^6.1.11" +"@mapbox/node-pre-gyp@^1.0.10": + version "1.0.10" + resolved "https://registry.yarnpkg.com/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.10.tgz#8e6735ccebbb1581e5a7e652244cadc8a844d03c" + integrity sha512-4ySo4CjzStuprMwk35H5pPbkymjv1SF3jGLj6rAHp/xT/RF7TL7bd9CTm1xDY49K2qF7jmR/g7k+SkLETP6opA== + dependencies: + detect-libc "^2.0.0" + https-proxy-agent "^5.0.0" + make-dir "^3.1.0" + node-fetch "^2.6.7" + nopt "^5.0.0" + npmlog "^5.0.1" + rimraf "^3.0.2" + semver "^7.3.5" + tar "^6.1.11" + "@mattrglobal/bbs-signatures@1.0.0": version "1.0.0" resolved "https://registry.yarnpkg.com/@mattrglobal/bbs-signatures/-/bbs-signatures-1.0.0.tgz#8ff272c6d201aadab7e08bd84dbfd6e0d48ba12d" @@ -1853,6 +1566,11 @@ resolved "https://registry.yarnpkg.com/@multiformats/base-x/-/base-x-4.0.1.tgz#95ff0fa58711789d53aefb2590a8b7a4e715d121" integrity sha512-eMk0b9ReBbV23xXU693TAIrLyeO5iTgBZGSJfpqriG8UkYvr/hC9u9pyMlAakDNHWmbhMZCDs6KQO0jzKD8OTw== +"@noble/hashes@^1", "@noble/hashes@^1.0.0": + version "1.3.0" + resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.3.0.tgz#085fd70f6d7d9d109671090ccae1d3bec62554a1" + integrity sha512-ilHEACi9DwqJB0pw7kv+Apvh50jiiSyR/cQ3y4W7lOR5mhvn/50FLUfsnfJz0BDZtl/RR16kXvptiv6q1msYZg== + "@nodelib/fs.scandir@2.1.5": version "2.1.5" resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5" @@ -1866,7 +1584,7 @@ resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz#5bd262af94e9d25bd1e71b05deed44876a222e8b" integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A== -"@nodelib/fs.walk@^1.2.3": +"@nodelib/fs.walk@^1.2.3", "@nodelib/fs.walk@^1.2.8": version "1.2.8" resolved "https://registry.yarnpkg.com/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz#e95737e8bb6746ddedf69c556953494f196fe69a" integrity sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg== @@ -1874,10 +1592,44 @@ "@nodelib/fs.scandir" "2.1.5" fastq "^1.6.0" -"@npmcli/ci-detect@^1.0.0": - version "1.4.0" - resolved "https://registry.yarnpkg.com/@npmcli/ci-detect/-/ci-detect-1.4.0.tgz#18478bbaa900c37bfbd8a2006a6262c62e8b0fe1" - integrity sha512-3BGrt6FLjqM6br5AhWRKTr3u5GIVkjRYeAFrMp3HjnfICrg4xOrVRwFavKT6tsp++bq5dluL5t8ME/Nha/6c1Q== +"@npmcli/arborist@6.2.3": + version "6.2.3" + resolved "https://registry.yarnpkg.com/@npmcli/arborist/-/arborist-6.2.3.tgz#31f8aed2588341864d3811151d929c01308f8e71" + integrity sha512-lpGOC2ilSJXcc2zfW9QtukcCTcMbl3fVI0z4wvFB2AFIl0C+Q6Wv7ccrpdrQa8rvJ1ZVuc6qkX7HVTyKlzGqKA== + dependencies: + "@isaacs/string-locale-compare" "^1.1.0" + "@npmcli/fs" "^3.1.0" + "@npmcli/installed-package-contents" "^2.0.0" + "@npmcli/map-workspaces" "^3.0.2" + "@npmcli/metavuln-calculator" "^5.0.0" + "@npmcli/name-from-folder" "^2.0.0" + "@npmcli/node-gyp" "^3.0.0" + "@npmcli/package-json" "^3.0.0" + "@npmcli/query" "^3.0.0" + "@npmcli/run-script" "^6.0.0" + bin-links "^4.0.1" + cacache "^17.0.4" + common-ancestor-path "^1.0.1" + hosted-git-info "^6.1.1" + json-parse-even-better-errors "^3.0.0" + json-stringify-nice "^1.1.4" + minimatch "^6.1.6" + nopt "^7.0.0" + npm-install-checks "^6.0.0" + npm-package-arg "^10.1.0" + npm-pick-manifest "^8.0.1" + npm-registry-fetch "^14.0.3" + npmlog "^7.0.1" + pacote "^15.0.8" + parse-conflict-json "^3.0.0" + proc-log "^3.0.0" + promise-all-reject-late "^1.0.0" + promise-call-limit "^1.0.1" + read-package-json-fast "^3.0.2" + semver "^7.3.7" + ssri "^10.0.1" + treeverse "^3.0.0" + walk-up-path "^1.0.0" "@npmcli/fs@^1.0.0": version "1.1.1" @@ -1887,21 +1639,51 @@ "@gar/promisify" "^1.0.1" semver "^7.3.5" -"@npmcli/git@^2.1.0": - version "2.1.0" - resolved "https://registry.yarnpkg.com/@npmcli/git/-/git-2.1.0.tgz#2fbd77e147530247d37f325930d457b3ebe894f6" - integrity sha512-/hBFX/QG1b+N7PZBFs0bi+evgRZcK9nWBxQKZkGoXUT5hJSwl5c4d7y8/hm+NQZRPhQ67RzFaj5UM9YeyKoryw== +"@npmcli/fs@^2.1.0": + version "2.1.2" + resolved "https://registry.yarnpkg.com/@npmcli/fs/-/fs-2.1.2.tgz#a9e2541a4a2fec2e69c29b35e6060973da79b865" + integrity sha512-yOJKRvohFOaLqipNtwYB9WugyZKhC/DZC4VYPmpaCzDBrA8YpK3qHZ8/HGscMnE4GqbkLNuVcCnxkeQEdGt6LQ== dependencies: - "@npmcli/promise-spawn" "^1.3.2" - lru-cache "^6.0.0" + "@gar/promisify" "^1.1.3" + semver "^7.3.5" + +"@npmcli/fs@^3.1.0": + version "3.1.0" + resolved "https://registry.yarnpkg.com/@npmcli/fs/-/fs-3.1.0.tgz#233d43a25a91d68c3a863ba0da6a3f00924a173e" + integrity sha512-7kZUAaLscfgbwBQRbvdMYaZOWyMEcPTH/tJjnyAWJ/dvvs9Ef+CERx/qJb9GExJpl1qipaDGn7KqHnFGGixd0w== + dependencies: + semver "^7.3.5" + +"@npmcli/git@^3.0.0": + version "3.0.2" + resolved "https://registry.yarnpkg.com/@npmcli/git/-/git-3.0.2.tgz#5c5de6b4d70474cf2d09af149ce42e4e1dacb931" + integrity sha512-CAcd08y3DWBJqJDpfuVL0uijlq5oaXaOJEKHKc4wqrjd00gkvTZB+nFuLn+doOOKddaQS9JfqtNoFCO2LCvA3w== + dependencies: + "@npmcli/promise-spawn" "^3.0.0" + lru-cache "^7.4.4" mkdirp "^1.0.4" - npm-pick-manifest "^6.1.1" + npm-pick-manifest "^7.0.0" + proc-log "^2.0.0" promise-inflight "^1.0.1" promise-retry "^2.0.1" semver "^7.3.5" which "^2.0.2" -"@npmcli/installed-package-contents@^1.0.6": +"@npmcli/git@^4.0.0": + version "4.0.4" + resolved "https://registry.yarnpkg.com/@npmcli/git/-/git-4.0.4.tgz#cdf74f21b1d440c0756fb28159d935129d9daa33" + integrity sha512-5yZghx+u5M47LghaybLCkdSyFzV/w4OuH12d96HO389Ik9CDsLaDZJVynSGGVJOLn6gy/k7Dz5XYcplM3uxXRg== + dependencies: + "@npmcli/promise-spawn" "^6.0.0" + lru-cache "^7.4.4" + npm-pick-manifest "^8.0.0" + proc-log "^3.0.0" + promise-inflight "^1.0.1" + promise-retry "^2.0.1" + semver "^7.3.5" + which "^3.0.0" + +"@npmcli/installed-package-contents@^1.0.7": version "1.0.7" resolved "https://registry.yarnpkg.com/@npmcli/installed-package-contents/-/installed-package-contents-1.0.7.tgz#ab7408c6147911b970a8abe261ce512232a3f4fa" integrity sha512-9rufe0wnJusCQoLpV9ZPKIVP55itrM5BxOXs10DmdbRfgWtHy1LDyskbwRnBghuB0PrF7pNPOqREVtpz4HqzKw== @@ -1909,6 +1691,34 @@ npm-bundled "^1.1.1" npm-normalize-package-bin "^1.0.1" +"@npmcli/installed-package-contents@^2.0.0", "@npmcli/installed-package-contents@^2.0.1": + version "2.0.2" + resolved "https://registry.yarnpkg.com/@npmcli/installed-package-contents/-/installed-package-contents-2.0.2.tgz#bfd817eccd9e8df200919e73f57f9e3d9e4f9e33" + integrity sha512-xACzLPhnfD51GKvTOOuNX2/V4G4mz9/1I2MfDoye9kBM3RYe5g2YbscsaGoTlaWqkxeiapBWyseULVKpSVHtKQ== + dependencies: + npm-bundled "^3.0.0" + npm-normalize-package-bin "^3.0.0" + +"@npmcli/map-workspaces@^3.0.2": + version "3.0.3" + resolved "https://registry.yarnpkg.com/@npmcli/map-workspaces/-/map-workspaces-3.0.3.tgz#476944b63cd1f65bf83c6fdc7f4ca7be56906b1f" + integrity sha512-HlCvFuTzw4UNoKyZdqiNrln+qMF71QJkxy2dsusV8QQdoa89e2TF4dATCzBxbl4zzRzdDoWWyP5ADVrNAH9cRQ== + dependencies: + "@npmcli/name-from-folder" "^2.0.0" + glob "^9.3.1" + minimatch "^7.4.2" + read-package-json-fast "^3.0.0" + +"@npmcli/metavuln-calculator@^5.0.0": + version "5.0.1" + resolved "https://registry.yarnpkg.com/@npmcli/metavuln-calculator/-/metavuln-calculator-5.0.1.tgz#426b3e524c2008bcc82dbc2ef390aefedd643d76" + integrity sha512-qb8Q9wIIlEPj3WeA1Lba91R4ZboPL0uspzV0F9uwP+9AYMVB2zOoa7Pbk12g6D2NHAinSbHh6QYmGuRyHZ874Q== + dependencies: + cacache "^17.0.0" + json-parse-even-better-errors "^3.0.0" + pacote "^15.0.0" + semver "^7.3.5" + "@npmcli/move-file@^1.0.1": version "1.1.2" resolved "https://registry.yarnpkg.com/@npmcli/move-file/-/move-file-1.1.2.tgz#1a82c3e372f7cae9253eb66d72543d6b8685c674" @@ -1917,64 +1727,196 @@ mkdirp "^1.0.4" rimraf "^3.0.2" -"@npmcli/node-gyp@^1.0.2": - version "1.0.3" - resolved "https://registry.yarnpkg.com/@npmcli/node-gyp/-/node-gyp-1.0.3.tgz#a912e637418ffc5f2db375e93b85837691a43a33" - integrity sha512-fnkhw+fmX65kiLqk6E3BFLXNC26rUhK90zVwe2yncPliVT/Qos3xjhTLE59Df8KnPlcwIERXKVlU1bXoUQ+liA== +"@npmcli/move-file@^2.0.0": + version "2.0.1" + resolved "https://registry.yarnpkg.com/@npmcli/move-file/-/move-file-2.0.1.tgz#26f6bdc379d87f75e55739bab89db525b06100e4" + integrity sha512-mJd2Z5TjYWq/ttPLLGqArdtnC74J6bOzg4rMDnN+p1xTacZ2yPRCk2y0oSWQtygLR9YVQXgOcONrwtnk3JupxQ== + dependencies: + mkdirp "^1.0.4" + rimraf "^3.0.2" -"@npmcli/promise-spawn@^1.2.0", "@npmcli/promise-spawn@^1.3.2": - version "1.3.2" - resolved "https://registry.yarnpkg.com/@npmcli/promise-spawn/-/promise-spawn-1.3.2.tgz#42d4e56a8e9274fba180dabc0aea6e38f29274f5" - integrity sha512-QyAGYo/Fbj4MXeGdJcFzZ+FkDkomfRBrPM+9QYJSg+PxgAUL+LU3FneQk37rKR2/zjqkCV1BLHccX98wRXG3Sg== +"@npmcli/name-from-folder@^2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@npmcli/name-from-folder/-/name-from-folder-2.0.0.tgz#c44d3a7c6d5c184bb6036f4d5995eee298945815" + integrity sha512-pwK+BfEBZJbKdNYpHHRTNBwBoqrN/iIMO0AiGvYsp3Hoaq0WbgGSWQR6SCldZovoDpY3yje5lkFUe6gsDgJ2vg== + +"@npmcli/node-gyp@^2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@npmcli/node-gyp/-/node-gyp-2.0.0.tgz#8c20e53e34e9078d18815c1d2dda6f2420d75e35" + integrity sha512-doNI35wIe3bBaEgrlPfdJPaCpUR89pJWep4Hq3aRdh6gKazIVWfs0jHttvSSoq47ZXgC7h73kDsUl8AoIQUB+A== + +"@npmcli/node-gyp@^3.0.0": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@npmcli/node-gyp/-/node-gyp-3.0.0.tgz#101b2d0490ef1aa20ed460e4c0813f0db560545a" + integrity sha512-gp8pRXC2oOxu0DUE1/M3bYtb1b3/DbJ5aM113+XJBgfXdussRAsX0YOrOhdd8WvnAR6auDBvJomGAkLKA5ydxA== + +"@npmcli/package-json@^3.0.0": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@npmcli/package-json/-/package-json-3.0.0.tgz#c9219a197e1be8dbf43c4ef8767a72277c0533b6" + integrity sha512-NnuPuM97xfiCpbTEJYtEuKz6CFbpUHtaT0+5via5pQeI25omvQDFbp1GcGJ/c4zvL/WX0qbde6YiLgfZbWFgvg== + dependencies: + json-parse-even-better-errors "^3.0.0" + +"@npmcli/promise-spawn@^3.0.0": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@npmcli/promise-spawn/-/promise-spawn-3.0.0.tgz#53283b5f18f855c6925f23c24e67c911501ef573" + integrity sha512-s9SgS+p3a9Eohe68cSI3fi+hpcZUmXq5P7w0kMlAsWVtR7XbK3ptkZqKT2cK1zLDObJ3sR+8P59sJE0w/KTL1g== dependencies: infer-owner "^1.0.4" -"@npmcli/run-script@^1.8.2": - version "1.8.6" - resolved "https://registry.yarnpkg.com/@npmcli/run-script/-/run-script-1.8.6.tgz#18314802a6660b0d4baa4c3afe7f1ad39d8c28b7" - integrity sha512-e42bVZnC6VluBZBAFEr3YrdqSspG3bgilyg4nSLBJ7TRGNCzxHa92XAHxQBLYg0BmgwO4b2mf3h/l5EkEWRn3g== +"@npmcli/promise-spawn@^6.0.0", "@npmcli/promise-spawn@^6.0.1": + version "6.0.2" + resolved "https://registry.yarnpkg.com/@npmcli/promise-spawn/-/promise-spawn-6.0.2.tgz#c8bc4fa2bd0f01cb979d8798ba038f314cfa70f2" + integrity sha512-gGq0NJkIGSwdbUt4yhdF8ZrmkGKVz9vAdVzpOfnom+V8PLSmSOVhZwbNvZZS1EYcJN5hzzKBxmmVVAInM6HQLg== + dependencies: + which "^3.0.0" + +"@npmcli/query@^3.0.0": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@npmcli/query/-/query-3.0.0.tgz#51a0dfb85811e04f244171f164b6bc83b36113a7" + integrity sha512-MFNDSJNgsLZIEBVZ0Q9w9K7o07j5N4o4yjtdz2uEpuCZlXGMuPENiRaFYk0vRqAA64qVuUQwC05g27fRtfUgnA== dependencies: - "@npmcli/node-gyp" "^1.0.2" - "@npmcli/promise-spawn" "^1.3.2" - node-gyp "^7.1.0" - read-package-json-fast "^2.0.1" + postcss-selector-parser "^6.0.10" -"@octokit/auth-token@^2.4.4": - version "2.5.0" - resolved "https://registry.yarnpkg.com/@octokit/auth-token/-/auth-token-2.5.0.tgz#27c37ea26c205f28443402477ffd261311f21e36" - integrity sha512-r5FVUJCOLl19AxiuZD2VRZ/ORjp/4IN98Of6YJoJOkY75CIBuYfmiNHGrDwXr+aLGG55igl9QrxX3hbiXlLb+g== +"@npmcli/run-script@4.1.7": + version "4.1.7" + resolved "https://registry.yarnpkg.com/@npmcli/run-script/-/run-script-4.1.7.tgz#b1a2f57568eb738e45e9ea3123fb054b400a86f7" + integrity sha512-WXr/MyM4tpKA4BotB81NccGAv8B48lNH0gRoILucbcAhTQXLCoi6HflMV3KdXubIqvP9SuLsFn68Z7r4jl+ppw== dependencies: - "@octokit/types" "^6.0.3" + "@npmcli/node-gyp" "^2.0.0" + "@npmcli/promise-spawn" "^3.0.0" + node-gyp "^9.0.0" + read-package-json-fast "^2.0.3" + which "^2.0.2" -"@octokit/core@^3.5.1": - version "3.6.0" - resolved "https://registry.yarnpkg.com/@octokit/core/-/core-3.6.0.tgz#3376cb9f3008d9b3d110370d90e0a1fcd5fe6085" - integrity sha512-7RKRKuA4xTjMhY+eG3jthb3hlZCsOwg3rztWh75Xc+ShDWOfDDATWbeZpAHBNRpm4Tv9WgBMOy1zEJYXG6NJ7Q== - dependencies: - "@octokit/auth-token" "^2.4.4" - "@octokit/graphql" "^4.5.8" - "@octokit/request" "^5.6.3" - "@octokit/request-error" "^2.0.5" - "@octokit/types" "^6.0.3" +"@npmcli/run-script@^4.1.0": + version "4.2.1" + resolved "https://registry.yarnpkg.com/@npmcli/run-script/-/run-script-4.2.1.tgz#c07c5c71bc1c70a5f2a06b0d4da976641609b946" + integrity sha512-7dqywvVudPSrRCW5nTHpHgeWnbBtz8cFkOuKrecm6ih+oO9ciydhWt6OF7HlqupRRmB8Q/gECVdB9LMfToJbRg== + dependencies: + "@npmcli/node-gyp" "^2.0.0" + "@npmcli/promise-spawn" "^3.0.0" + node-gyp "^9.0.0" + read-package-json-fast "^2.0.3" + which "^2.0.2" + +"@npmcli/run-script@^6.0.0": + version "6.0.0" + resolved "https://registry.yarnpkg.com/@npmcli/run-script/-/run-script-6.0.0.tgz#f89e322c729e26ae29db6cc8cc76559074aac208" + integrity sha512-ql+AbRur1TeOdl1FY+RAwGW9fcr4ZwiVKabdvm93mujGREVuVLbdkXRJDrkTXSdCjaxYydr1wlA2v67jxWG5BQ== + dependencies: + "@npmcli/node-gyp" "^3.0.0" + "@npmcli/promise-spawn" "^6.0.0" + node-gyp "^9.0.0" + read-package-json-fast "^3.0.0" + which "^3.0.0" + +"@nrwl/cli@15.9.2": + version "15.9.2" + resolved "https://registry.yarnpkg.com/@nrwl/cli/-/cli-15.9.2.tgz#82537d3d85410b0143d37a3b4fade09675356084" + integrity sha512-QoCmyrcGakHAYTJaNBbOerRQAmqJHMYGCdqtQidV+aP9p1Dy33XxDELfhd+IYmGqngutXuEWChNpWNhPloLnoA== + dependencies: + nx "15.9.2" + +"@nrwl/devkit@>=15.5.2 < 16": + version "15.9.2" + resolved "https://registry.yarnpkg.com/@nrwl/devkit/-/devkit-15.9.2.tgz#482b89f1bf88d3600b11f8b7e3e4452c5766eca4" + integrity sha512-2DvTstVZb91m+d4wqUJMBHQ3elxyabdmFE6/3aXmtOGeDxTyXyDzf/1O6JvBBiL8K6XC3ZYchjtxUHgxl/NJ5A== + dependencies: + ejs "^3.1.7" + ignore "^5.0.4" + semver "7.3.4" + tmp "~0.2.1" + tslib "^2.3.0" + +"@nrwl/nx-darwin-arm64@15.9.2": + version "15.9.2" + resolved "https://registry.yarnpkg.com/@nrwl/nx-darwin-arm64/-/nx-darwin-arm64-15.9.2.tgz#612d8d714ec876cafd6f1483bf5565704d1b75be" + integrity sha512-Yv+OVsQt3C/hmWOC+YhJZQlsyph5w1BHfbp4jyCvV1ZXBbb8NdvwxgDHPWXxKPTc1EXuB7aEX3qzxM3/OWEUJg== + +"@nrwl/nx-darwin-x64@15.9.2": + version "15.9.2" + resolved "https://registry.yarnpkg.com/@nrwl/nx-darwin-x64/-/nx-darwin-x64-15.9.2.tgz#3f77bd90dbabf4782d81f773cfb2739a443e595f" + integrity sha512-qHfdluHlPzV0UHOwj1ZJ+qNEhzfLGiBuy1cOth4BSzDlvMnkuqBWoprfaXoztzYcus2NSILY1/7b3Jw4DAWmMw== + +"@nrwl/nx-linux-arm-gnueabihf@15.9.2": + version "15.9.2" + resolved "https://registry.yarnpkg.com/@nrwl/nx-linux-arm-gnueabihf/-/nx-linux-arm-gnueabihf-15.9.2.tgz#3374a5a1692b222ce18f2213a47b4d68fb509e70" + integrity sha512-0GzwbablosnYnnJDCJvAeZv8LlelSrNwUnGhe43saeoZdAew35Ay1E34zBrg/GCGTASuz+knEEYFM+gDD9Mc6A== + +"@nrwl/nx-linux-arm64-gnu@15.9.2": + version "15.9.2" + resolved "https://registry.yarnpkg.com/@nrwl/nx-linux-arm64-gnu/-/nx-linux-arm64-gnu-15.9.2.tgz#e3ec95c6ee3285c77422886cf4cbec1f04804460" + integrity sha512-3mFIY7iUTPG45hSIRaM2DmraCy8W6hNoArAGRrTgYw40BIJHtLrW+Rt7DLyvVXaYCvrKugWOKtxC+jG7kpIZVA== + +"@nrwl/nx-linux-arm64-musl@15.9.2": + version "15.9.2" + resolved "https://registry.yarnpkg.com/@nrwl/nx-linux-arm64-musl/-/nx-linux-arm64-musl-15.9.2.tgz#72ce601d256083ded7380c598f1b3eb4dc2a3472" + integrity sha512-FNBnXEtockwxZa4I3NqggrJp0YIbNokJvt/clrICP+ijOacdUDkv8mJedavobkFsRsNq9gzCbRbUScKymrOLrg== + +"@nrwl/nx-linux-x64-gnu@15.9.2": + version "15.9.2" + resolved "https://registry.yarnpkg.com/@nrwl/nx-linux-x64-gnu/-/nx-linux-x64-gnu-15.9.2.tgz#2da6bb50cd80d699310e91c7331baa6cfc8ce197" + integrity sha512-gHWsP5lbe4FNQCa1Q/VLxIuik+BqAOcSzyPjdUa4gCDcbxPa8xiE57PgXB5E1XUzOWNnDTlXa/Ll07/TIuKuog== + +"@nrwl/nx-linux-x64-musl@15.9.2": + version "15.9.2" + resolved "https://registry.yarnpkg.com/@nrwl/nx-linux-x64-musl/-/nx-linux-x64-musl-15.9.2.tgz#39b3bda5868a53b722f1d42700dce71c5ff3f6b9" + integrity sha512-EaFUukCbmoHsYECX2AS4pxXH933yesBFVvBgD38DkoFDxDoJMVt6JqYwm+d5R7S4R2P9U3l++aurljQTRq567Q== + +"@nrwl/nx-win32-arm64-msvc@15.9.2": + version "15.9.2" + resolved "https://registry.yarnpkg.com/@nrwl/nx-win32-arm64-msvc/-/nx-win32-arm64-msvc-15.9.2.tgz#bc350be5cb7d0bfa6c2c5ced40c5af163a457a2c" + integrity sha512-PGAe7QMr51ivx1X3avvs8daNlvv1wGo3OFrobjlu5rSyjC1Y3qHwT9+wdlwzNZ93FIqWOq09s+rE5gfZRfpdAg== + +"@nrwl/nx-win32-x64-msvc@15.9.2": + version "15.9.2" + resolved "https://registry.yarnpkg.com/@nrwl/nx-win32-x64-msvc/-/nx-win32-x64-msvc-15.9.2.tgz#3e46c3f7af196bdbf0deb336ec4f9448c54e4a9f" + integrity sha512-Q8onNzhuAZ0l9DNkm8D4Z1AEIzJr8JiT4L2fVBLYrV/R75C2HS3q7lzvfo6oqMY6mXge1cFPcrTtg3YXBQaSWA== + +"@nrwl/tao@15.9.2": + version "15.9.2" + resolved "https://registry.yarnpkg.com/@nrwl/tao/-/tao-15.9.2.tgz#e970efa8b3fb828007b02286e9e505247032b5b3" + integrity sha512-+LqNC37w9c6q6Ukdpf0z0tt1PQFNi4gwhHpJvkYQiKRETHjyrrlyqTNEPEyA7PI62RuYC6VrpVw2gzI7ufqZEA== + dependencies: + nx "15.9.2" + +"@octokit/auth-token@^3.0.0": + version "3.0.3" + resolved "https://registry.yarnpkg.com/@octokit/auth-token/-/auth-token-3.0.3.tgz#ce7e48a3166731f26068d7a7a7996b5da58cbe0c" + integrity sha512-/aFM2M4HVDBT/jjDBa84sJniv1t9Gm/rLkalaz9htOm+L+8JMj1k9w0CkUdcxNyNxZPlTxKPVko+m1VlM58ZVA== + dependencies: + "@octokit/types" "^9.0.0" + +"@octokit/core@^4.0.0": + version "4.2.0" + resolved "https://registry.yarnpkg.com/@octokit/core/-/core-4.2.0.tgz#8c253ba9605aca605bc46187c34fcccae6a96648" + integrity sha512-AgvDRUg3COpR82P7PBdGZF/NNqGmtMq2NiPqeSsDIeCfYFOZ9gddqWNQHnFdEUf+YwOj4aZYmJnlPp7OXmDIDg== + dependencies: + "@octokit/auth-token" "^3.0.0" + "@octokit/graphql" "^5.0.0" + "@octokit/request" "^6.0.0" + "@octokit/request-error" "^3.0.0" + "@octokit/types" "^9.0.0" before-after-hook "^2.2.0" universal-user-agent "^6.0.0" -"@octokit/endpoint@^6.0.1": - version "6.0.12" - resolved "https://registry.yarnpkg.com/@octokit/endpoint/-/endpoint-6.0.12.tgz#3b4d47a4b0e79b1027fb8d75d4221928b2d05658" - integrity sha512-lF3puPwkQWGfkMClXb4k/eUT/nZKQfxinRWJrdZaJO85Dqwo/G0yOC434Jr2ojwafWJMYqFGFa5ms4jJUgujdA== +"@octokit/endpoint@^7.0.0": + version "7.0.5" + resolved "https://registry.yarnpkg.com/@octokit/endpoint/-/endpoint-7.0.5.tgz#2bb2a911c12c50f10014183f5d596ce30ac67dd1" + integrity sha512-LG4o4HMY1Xoaec87IqQ41TQ+glvIeTKqfjkCEmt5AIwDZJwQeVZFIEYXrYY6yLwK+pAScb9Gj4q+Nz2qSw1roA== dependencies: - "@octokit/types" "^6.0.3" + "@octokit/types" "^9.0.0" is-plain-object "^5.0.0" universal-user-agent "^6.0.0" -"@octokit/graphql@^4.5.8": - version "4.8.0" - resolved "https://registry.yarnpkg.com/@octokit/graphql/-/graphql-4.8.0.tgz#664d9b11c0e12112cbf78e10f49a05959aa22cc3" - integrity sha512-0gv+qLSBLKF0z8TKaSKTsS39scVKF9dbMxJpj3U0vC7wjNWFuIpL/z76Qe2fiuCbDRcJSavkXsVtMS6/dtQQsg== +"@octokit/graphql@^5.0.0": + version "5.0.5" + resolved "https://registry.yarnpkg.com/@octokit/graphql/-/graphql-5.0.5.tgz#a4cb3ea73f83b861893a6370ee82abb36e81afd2" + integrity sha512-Qwfvh3xdqKtIznjX9lz2D458r7dJPP8l6r4GQkIdWQouZwHQK0mVT88uwiU2bdTU2OtT1uOlKpRciUWldpG0yQ== dependencies: - "@octokit/request" "^5.6.0" - "@octokit/types" "^6.0.3" + "@octokit/request" "^6.0.0" + "@octokit/types" "^9.0.0" universal-user-agent "^6.0.0" "@octokit/openapi-types@^12.11.0": @@ -1982,73 +1924,105 @@ resolved "https://registry.yarnpkg.com/@octokit/openapi-types/-/openapi-types-12.11.0.tgz#da5638d64f2b919bca89ce6602d059f1b52d3ef0" integrity sha512-VsXyi8peyRq9PqIz/tpqiL2w3w80OgVMwBHltTml3LmVvXiphgeqmY9mvBw9Wu7e0QWk/fqD37ux8yP5uVekyQ== -"@octokit/plugin-enterprise-rest@^6.0.1": +"@octokit/openapi-types@^14.0.0": + version "14.0.0" + resolved "https://registry.yarnpkg.com/@octokit/openapi-types/-/openapi-types-14.0.0.tgz#949c5019028c93f189abbc2fb42f333290f7134a" + integrity sha512-HNWisMYlR8VCnNurDU6os2ikx0s0VyEjDYHNS/h4cgb8DeOxQ0n72HyinUtdDVxJhFy3FWLGl0DJhfEWk3P5Iw== + +"@octokit/openapi-types@^17.0.0": + version "17.0.0" + resolved "https://registry.yarnpkg.com/@octokit/openapi-types/-/openapi-types-17.0.0.tgz#7356d287f48b20e9a1f497ef8dfaabdff9cf8622" + integrity sha512-V8BVJGN0ZmMlURF55VFHFd/L92XQQ43KvFjNmY1IYbCN3V/h/uUFV6iQi19WEHM395Nn+1qhUbViCAD/1czzog== + +"@octokit/plugin-enterprise-rest@6.0.1": version "6.0.1" resolved "https://registry.yarnpkg.com/@octokit/plugin-enterprise-rest/-/plugin-enterprise-rest-6.0.1.tgz#e07896739618dab8da7d4077c658003775f95437" integrity sha512-93uGjlhUD+iNg1iWhUENAtJata6w5nE+V4urXOAlIXdco6xNZtUSfYY8dzp3Udy74aqO/B5UZL80x/YMa5PKRw== -"@octokit/plugin-paginate-rest@^2.16.8": - version "2.21.3" - resolved "https://registry.yarnpkg.com/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-2.21.3.tgz#7f12532797775640dbb8224da577da7dc210c87e" - integrity sha512-aCZTEf0y2h3OLbrgKkrfFdjRL6eSOo8komneVQJnYecAxIej7Bafor2xhuDJOIFau4pk0i/P28/XgtbyPF0ZHw== +"@octokit/plugin-paginate-rest@^3.0.0": + version "3.1.0" + resolved "https://registry.yarnpkg.com/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-3.1.0.tgz#86f8be759ce2d6d7c879a31490fd2f7410b731f0" + integrity sha512-+cfc40pMzWcLkoDcLb1KXqjX0jTGYXjKuQdFQDc6UAknISJHnZTiBqld6HDwRJvD4DsouDKrWXNbNV0lE/3AXA== dependencies: - "@octokit/types" "^6.40.0" + "@octokit/types" "^6.41.0" "@octokit/plugin-request-log@^1.0.4": version "1.0.4" resolved "https://registry.yarnpkg.com/@octokit/plugin-request-log/-/plugin-request-log-1.0.4.tgz#5e50ed7083a613816b1e4a28aeec5fb7f1462e85" integrity sha512-mLUsMkgP7K/cnFEw07kWqXGF5LKrOkD+lhCrKvPHXWDywAwuDUeDwWBpc69XK3pNX0uKiVt8g5z96PJ6z9xCFA== -"@octokit/plugin-rest-endpoint-methods@^5.12.0": - version "5.16.2" - resolved "https://registry.yarnpkg.com/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-5.16.2.tgz#7ee8bf586df97dd6868cf68f641354e908c25342" - integrity sha512-8QFz29Fg5jDuTPXVtey05BLm7OB+M8fnvE64RNegzX7U+5NUXcOcnpTIK0YfSHBg8gYd0oxIq3IZTe9SfPZiRw== +"@octokit/plugin-rest-endpoint-methods@^6.0.0": + version "6.8.1" + resolved "https://registry.yarnpkg.com/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-6.8.1.tgz#97391fda88949eb15f68dc291957ccbe1d3e8ad1" + integrity sha512-QrlaTm8Lyc/TbU7BL/8bO49vp+RZ6W3McxxmmQTgYxf2sWkO8ZKuj4dLhPNJD6VCUW1hetCmeIM0m6FTVpDiEg== dependencies: - "@octokit/types" "^6.39.0" + "@octokit/types" "^8.1.1" deprecation "^2.3.1" -"@octokit/request-error@^2.0.5", "@octokit/request-error@^2.1.0": - version "2.1.0" - resolved "https://registry.yarnpkg.com/@octokit/request-error/-/request-error-2.1.0.tgz#9e150357831bfc788d13a4fd4b1913d60c74d677" - integrity sha512-1VIvgXxs9WHSjicsRwq8PlR2LR2x6DwsJAaFgzdi0JfJoGSO8mYI/cHJQ+9FbN21aa+DrgNLnwObmyeSC8Rmpg== +"@octokit/request-error@^3.0.0": + version "3.0.3" + resolved "https://registry.yarnpkg.com/@octokit/request-error/-/request-error-3.0.3.tgz#ef3dd08b8e964e53e55d471acfe00baa892b9c69" + integrity sha512-crqw3V5Iy2uOU5Np+8M/YexTlT8zxCfI+qu+LxUB7SZpje4Qmx3mub5DfEKSO8Ylyk0aogi6TYdf6kxzh2BguQ== dependencies: - "@octokit/types" "^6.0.3" + "@octokit/types" "^9.0.0" deprecation "^2.0.0" once "^1.4.0" -"@octokit/request@^5.6.0", "@octokit/request@^5.6.3": - version "5.6.3" - resolved "https://registry.yarnpkg.com/@octokit/request/-/request-5.6.3.tgz#19a022515a5bba965ac06c9d1334514eb50c48b0" - integrity sha512-bFJl0I1KVc9jYTe9tdGGpAMPy32dLBXXo1dS/YwSCTL/2nd9XeHsY616RE3HPXDVk+a+dBuzyz5YdlXwcDTr2A== +"@octokit/request@^6.0.0": + version "6.2.3" + resolved "https://registry.yarnpkg.com/@octokit/request/-/request-6.2.3.tgz#76d5d6d44da5c8d406620a4c285d280ae310bdb4" + integrity sha512-TNAodj5yNzrrZ/VxP+H5HiYaZep0H3GU0O7PaF+fhDrt8FPrnkei9Aal/txsN/1P7V3CPiThG0tIvpPDYUsyAA== dependencies: - "@octokit/endpoint" "^6.0.1" - "@octokit/request-error" "^2.1.0" - "@octokit/types" "^6.16.1" + "@octokit/endpoint" "^7.0.0" + "@octokit/request-error" "^3.0.0" + "@octokit/types" "^9.0.0" is-plain-object "^5.0.0" node-fetch "^2.6.7" universal-user-agent "^6.0.0" -"@octokit/rest@^18.1.0": - version "18.12.0" - resolved "https://registry.yarnpkg.com/@octokit/rest/-/rest-18.12.0.tgz#f06bc4952fc87130308d810ca9d00e79f6988881" - integrity sha512-gDPiOHlyGavxr72y0guQEhLsemgVjwRePayJ+FcKc2SJqKUbxbkvf5kAZEWA/MKvsfYlQAMVzNJE3ezQcxMJ2Q== +"@octokit/rest@19.0.3": + version "19.0.3" + resolved "https://registry.yarnpkg.com/@octokit/rest/-/rest-19.0.3.tgz#b9a4e8dc8d53e030d611c053153ee6045f080f02" + integrity sha512-5arkTsnnRT7/sbI4fqgSJ35KiFaN7zQm0uQiQtivNQLI8RQx8EHwJCajcTUwmaCMNDg7tdCvqAnc7uvHHPxrtQ== dependencies: - "@octokit/core" "^3.5.1" - "@octokit/plugin-paginate-rest" "^2.16.8" + "@octokit/core" "^4.0.0" + "@octokit/plugin-paginate-rest" "^3.0.0" "@octokit/plugin-request-log" "^1.0.4" - "@octokit/plugin-rest-endpoint-methods" "^5.12.0" + "@octokit/plugin-rest-endpoint-methods" "^6.0.0" -"@octokit/types@^6.0.3", "@octokit/types@^6.16.1", "@octokit/types@^6.39.0", "@octokit/types@^6.40.0": +"@octokit/types@^6.41.0": version "6.41.0" resolved "https://registry.yarnpkg.com/@octokit/types/-/types-6.41.0.tgz#e58ef78d78596d2fb7df9c6259802464b5f84a04" integrity sha512-eJ2jbzjdijiL3B4PrSQaSjuF2sPEQPVCPzBvTHJD9Nz+9dw2SGH4K4xeQJ77YfTq5bRQ+bD8wT11JbeDPmxmGg== dependencies: "@octokit/openapi-types" "^12.11.0" -"@peculiar/asn1-schema@^2.1.6": - version "2.3.0" - resolved "https://registry.yarnpkg.com/@peculiar/asn1-schema/-/asn1-schema-2.3.0.tgz#5368416eb336138770c692ffc2bab119ee3ae917" - integrity sha512-DtNLAG4vmDrdSJFPe7rypkcj597chNQL7u+2dBtYo5mh7VW2+im6ke+O0NVr8W1f4re4C3F71LhoMb0Yxqa48Q== +"@octokit/types@^8.1.1": + version "8.2.1" + resolved "https://registry.yarnpkg.com/@octokit/types/-/types-8.2.1.tgz#a6de091ae68b5541f8d4fcf9a12e32836d4648aa" + integrity sha512-8oWMUji8be66q2B9PmEIUyQm00VPDPun07umUWSaCwxmeaquFBro4Hcc3ruVoDo3zkQyZBlRvhIMEYS3pBhanw== + dependencies: + "@octokit/openapi-types" "^14.0.0" + +"@octokit/types@^9.0.0": + version "9.1.2" + resolved "https://registry.yarnpkg.com/@octokit/types/-/types-9.1.2.tgz#1a8d35b1f4a3d2ad386e223f249dd5f7506979c1" + integrity sha512-LPbJIuu1WNoRHbN4UMysEdlissRFpTCWyoKT7kHPufI8T+XX33/qilfMWJo3mCOjNIKu0+43oSQPf+HJa0+TTQ== + dependencies: + "@octokit/openapi-types" "^17.0.0" + +"@parcel/watcher@2.0.4": + version "2.0.4" + resolved "https://registry.yarnpkg.com/@parcel/watcher/-/watcher-2.0.4.tgz#f300fef4cc38008ff4b8c29d92588eced3ce014b" + integrity sha512-cTDi+FUDBIUOBKEtj+nhiJ71AZVlkAsQFuGQTun5tV9mwQBQgZvhCzG+URPQc8myeN32yRVZEfVAPCs1RW+Jvg== + dependencies: + node-addon-api "^3.2.1" + node-gyp-build "^4.3.0" + +"@peculiar/asn1-schema@^2.3.6": + version "2.3.6" + resolved "https://registry.yarnpkg.com/@peculiar/asn1-schema/-/asn1-schema-2.3.6.tgz#3dd3c2ade7f702a9a94dfb395c192f5fa5d6b922" + integrity sha512-izNRxPoaeJeg/AyH8hER6s+H7p4itk+03QCa4sbxI3lNdseQYCuxzgsuNK8bTXChtLTjpJz6NmXKA73qLa3rCA== dependencies: asn1js "^3.0.5" pvtsutils "^1.3.2" @@ -2062,154 +2036,257 @@ tslib "^2.0.0" "@peculiar/webcrypto@^1.0.22": - version "1.4.0" - resolved "https://registry.yarnpkg.com/@peculiar/webcrypto/-/webcrypto-1.4.0.tgz#f941bd95285a0f8a3d2af39ccda5197b80cd32bf" - integrity sha512-U58N44b2m3OuTgpmKgf0LPDOmP3bhwNz01vAnj1mBwxBASRhptWYK+M3zG+HBkDqGQM+bFsoIihTW8MdmPXEqg== + version "1.4.3" + resolved "https://registry.yarnpkg.com/@peculiar/webcrypto/-/webcrypto-1.4.3.tgz#078b3e8f598e847b78683dc3ba65feb5029b93a7" + integrity sha512-VtaY4spKTdN5LjJ04im/d/joXuvLbQdgy5Z4DXF4MFZhQ+MTrejbNMkfZBp1Bs3O5+bFqnJgyGdPuZQflvIa5A== dependencies: - "@peculiar/asn1-schema" "^2.1.6" + "@peculiar/asn1-schema" "^2.3.6" "@peculiar/json-schema" "^1.1.12" pvtsutils "^1.3.2" + tslib "^2.5.0" + webcrypto-core "^1.7.7" + +"@pkgr/utils@^2.3.1": + version "2.3.1" + resolved "https://registry.yarnpkg.com/@pkgr/utils/-/utils-2.3.1.tgz#0a9b06ffddee364d6642b3cd562ca76f55b34a03" + integrity sha512-wfzX8kc1PMyUILA+1Z/EqoE4UCXGy0iRGMhPwdfae1+f0OXlLqCk+By+aMzgJBzR9AzS4CDizioG6Ss1gvAFJw== + dependencies: + cross-spawn "^7.0.3" + is-glob "^4.0.3" + open "^8.4.0" + picocolors "^1.0.0" + tiny-glob "^0.2.9" tslib "^2.4.0" - webcrypto-core "^1.7.4" -"@react-native-community/cli-debugger-ui@^5.0.1": - version "5.0.1" - resolved "https://registry.yarnpkg.com/@react-native-community/cli-debugger-ui/-/cli-debugger-ui-5.0.1.tgz#6b1f3367b8e5211e899983065ea2e72c1901d75f" - integrity sha512-5gGKaaXYOVE423BUqxIfvfAVSj5Cg1cU/TpGbeg/iqpy2CfqyWqJB3tTuVUbOOiOvR5wbU8tti6pIi1pchJ+oA== +"@protobufjs/aspromise@^1.1.1", "@protobufjs/aspromise@^1.1.2": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@protobufjs/aspromise/-/aspromise-1.1.2.tgz#9b8b0cc663d669a7d8f6f5d0893a14d348f30fbf" + integrity sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ== + +"@protobufjs/base64@^1.1.2": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@protobufjs/base64/-/base64-1.1.2.tgz#4c85730e59b9a1f1f349047dbf24296034bb2735" + integrity sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg== + +"@protobufjs/codegen@^2.0.4": + version "2.0.4" + resolved "https://registry.yarnpkg.com/@protobufjs/codegen/-/codegen-2.0.4.tgz#7ef37f0d010fb028ad1ad59722e506d9262815cb" + integrity sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg== + +"@protobufjs/eventemitter@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz#355cbc98bafad5978f9ed095f397621f1d066b70" + integrity sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q== + +"@protobufjs/fetch@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@protobufjs/fetch/-/fetch-1.1.0.tgz#ba99fb598614af65700c1619ff06d454b0d84c45" + integrity sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ== + dependencies: + "@protobufjs/aspromise" "^1.1.1" + "@protobufjs/inquire" "^1.1.0" + +"@protobufjs/float@^1.0.2": + version "1.0.2" + resolved "https://registry.yarnpkg.com/@protobufjs/float/-/float-1.0.2.tgz#5e9e1abdcb73fc0a7cb8b291df78c8cbd97b87d1" + integrity sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ== + +"@protobufjs/inquire@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@protobufjs/inquire/-/inquire-1.1.0.tgz#ff200e3e7cf2429e2dcafc1140828e8cc638f089" + integrity sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q== + +"@protobufjs/path@^1.1.2": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@protobufjs/path/-/path-1.1.2.tgz#6cc2b20c5c9ad6ad0dccfd21ca7673d8d7fbf68d" + integrity sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA== + +"@protobufjs/pool@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@protobufjs/pool/-/pool-1.1.0.tgz#09fd15f2d6d3abfa9b65bc366506d6ad7846ff54" + integrity sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw== + +"@protobufjs/utf8@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@protobufjs/utf8/-/utf8-1.1.0.tgz#a777360b5b39a1a2e5106f8e858f2fd2d060c570" + integrity sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw== + +"@react-native-community/cli-clean@^10.1.1": + version "10.1.1" + resolved "https://registry.yarnpkg.com/@react-native-community/cli-clean/-/cli-clean-10.1.1.tgz#4c73ce93a63a24d70c0089d4025daac8184ff504" + integrity sha512-iNsrjzjIRv9yb5y309SWJ8NDHdwYtnCpmxZouQDyOljUdC9MwdZ4ChbtA4rwQyAwgOVfS9F/j56ML3Cslmvrxg== + dependencies: + "@react-native-community/cli-tools" "^10.1.1" + chalk "^4.1.2" + execa "^1.0.0" + prompts "^2.4.0" + +"@react-native-community/cli-config@^10.1.1": + version "10.1.1" + resolved "https://registry.yarnpkg.com/@react-native-community/cli-config/-/cli-config-10.1.1.tgz#08dcc5d7ca1915647dc06507ed853fe0c1488395" + integrity sha512-p4mHrjC+s/ayiNVG6T35GdEGdP6TuyBUg5plVGRJfTl8WT6LBfLYLk+fz/iETrEZ/YkhQIsQcEUQC47MqLNHog== + dependencies: + "@react-native-community/cli-tools" "^10.1.1" + chalk "^4.1.2" + cosmiconfig "^5.1.0" + deepmerge "^3.2.0" + glob "^7.1.3" + joi "^17.2.1" + +"@react-native-community/cli-debugger-ui@^10.0.0": + version "10.0.0" + resolved "https://registry.yarnpkg.com/@react-native-community/cli-debugger-ui/-/cli-debugger-ui-10.0.0.tgz#4bb6d41c7e46449714dc7ba5d9f5b41ef0ea7c57" + integrity sha512-8UKLcvpSNxnUTRy8CkCl27GGLqZunQ9ncGYhSrWyKrU9SWBJJGeZwi2k2KaoJi5FvF2+cD0t8z8cU6lsq2ZZmA== dependencies: serve-static "^1.13.1" -"@react-native-community/cli-hermes@^5.0.1": - version "5.0.1" - resolved "https://registry.yarnpkg.com/@react-native-community/cli-hermes/-/cli-hermes-5.0.1.tgz#039d064bf2dcd5043beb7dcd6cdf5f5cdd51e7fc" - integrity sha512-nD+ZOFvu5MfjLB18eDJ01MNiFrzj8SDtENjGpf0ZRFndOWASDAmU54/UlU/wj8OzTToK1+S1KY7j2P2M1gleww== +"@react-native-community/cli-doctor@^10.2.2": + version "10.2.2" + resolved "https://registry.yarnpkg.com/@react-native-community/cli-doctor/-/cli-doctor-10.2.2.tgz#b1893604fa9fc8971064e7c00042350f96868bfe" + integrity sha512-49Ep2aQOF0PkbAR/TcyMjOm9XwBa8VQr+/Zzf4SJeYwiYLCT1NZRAVAVjYRXl0xqvq5S5mAGZZShS4AQl4WsZw== + dependencies: + "@react-native-community/cli-config" "^10.1.1" + "@react-native-community/cli-platform-ios" "^10.2.1" + "@react-native-community/cli-tools" "^10.1.1" + chalk "^4.1.2" + command-exists "^1.2.8" + envinfo "^7.7.2" + execa "^1.0.0" + hermes-profile-transformer "^0.0.6" + ip "^1.1.5" + node-stream-zip "^1.9.1" + ora "^5.4.1" + prompts "^2.4.0" + semver "^6.3.0" + strip-ansi "^5.2.0" + sudo-prompt "^9.0.0" + wcwidth "^1.0.1" + +"@react-native-community/cli-hermes@^10.2.0": + version "10.2.0" + resolved "https://registry.yarnpkg.com/@react-native-community/cli-hermes/-/cli-hermes-10.2.0.tgz#cc252f435b149f74260bc918ce22fdf58033a87e" + integrity sha512-urfmvNeR8IiO/Sd92UU3xPO+/qI2lwCWQnxOkWaU/i2EITFekE47MD6MZrfVulRVYRi5cuaFqKZO/ccOdOB/vQ== dependencies: - "@react-native-community/cli-platform-android" "^5.0.1" - "@react-native-community/cli-tools" "^5.0.1" - chalk "^3.0.0" + "@react-native-community/cli-platform-android" "^10.2.0" + "@react-native-community/cli-tools" "^10.1.1" + chalk "^4.1.2" hermes-profile-transformer "^0.0.6" ip "^1.1.5" -"@react-native-community/cli-platform-android@^5.0.1", "@react-native-community/cli-platform-android@^5.0.1-alpha.1": - version "5.0.1" - resolved "https://registry.yarnpkg.com/@react-native-community/cli-platform-android/-/cli-platform-android-5.0.1.tgz#7f761e1818e5a099877ec59a1b739553fd6a6905" - integrity sha512-qv9GJX6BJ+Y4qvV34vgxKwwN1cnveXUdP6y2YmTW7XoAYs5YUzKqHajpY58EyucAL2y++6+573t5y4U/9IIoww== +"@react-native-community/cli-platform-android@10.2.0", "@react-native-community/cli-platform-android@^10.2.0": + version "10.2.0" + resolved "https://registry.yarnpkg.com/@react-native-community/cli-platform-android/-/cli-platform-android-10.2.0.tgz#0bc689270a5f1d9aaf9e723181d43ca4dbfffdef" + integrity sha512-CBenYwGxwFdObZTn1lgxWtMGA5ms2G/ALQhkS+XTAD7KHDrCxFF9yT/fnAjFZKM6vX/1TqGI1RflruXih3kAhw== dependencies: - "@react-native-community/cli-tools" "^5.0.1" - chalk "^3.0.0" + "@react-native-community/cli-tools" "^10.1.1" + chalk "^4.1.2" execa "^1.0.0" - fs-extra "^8.1.0" glob "^7.1.3" - jetifier "^1.6.2" - lodash "^4.17.15" logkitty "^0.7.1" - slash "^3.0.0" - xmldoc "^1.1.2" -"@react-native-community/cli-platform-ios@^5.0.1-alpha.1": - version "5.0.2" - resolved "https://registry.yarnpkg.com/@react-native-community/cli-platform-ios/-/cli-platform-ios-5.0.2.tgz#62485534053c0dad28a67de188248de177f4b0fb" - integrity sha512-IAJ2B3j2BTsQUJZ4R6cVvnTbPq0Vza7+dOgP81ISz2BKRtQ0VqNFv+VOALH2jLaDzf4t7NFlskzIXFqWqy2BLg== +"@react-native-community/cli-platform-ios@10.2.1", "@react-native-community/cli-platform-ios@^10.2.1": + version "10.2.1" + resolved "https://registry.yarnpkg.com/@react-native-community/cli-platform-ios/-/cli-platform-ios-10.2.1.tgz#2e6bd2cb6d48cbb8720d7b7265bb1bab80745f72" + integrity sha512-hz4zu4Y6eyj7D0lnZx8Mf2c2si8y+zh/zUTgCTaPPLzQD8jSZNNBtUUiA1cARm2razpe8marCZ1QbTMAGbf3mg== dependencies: - "@react-native-community/cli-tools" "^5.0.1" - chalk "^3.0.0" + "@react-native-community/cli-tools" "^10.1.1" + chalk "^4.1.2" + execa "^1.0.0" + fast-xml-parser "^4.0.12" glob "^7.1.3" - js-yaml "^3.13.1" - lodash "^4.17.15" - plist "^3.0.1" - xcode "^2.0.0" + ora "^5.4.1" -"@react-native-community/cli-server-api@^5.0.1": - version "5.0.1" - resolved "https://registry.yarnpkg.com/@react-native-community/cli-server-api/-/cli-server-api-5.0.1.tgz#3cf92dac766fab766afedf77df3fe4d5f51e4d2b" - integrity sha512-OOxL+y9AOZayQzmSW+h5T54wQe+QBc/f67Y9QlWzzJhkKJdYx+S4VOooHoD5PFJzGbYaxhu2YF17p517pcEIIA== +"@react-native-community/cli-plugin-metro@^10.2.2": + version "10.2.2" + resolved "https://registry.yarnpkg.com/@react-native-community/cli-plugin-metro/-/cli-plugin-metro-10.2.2.tgz#766914e3c8007dfe52b253544c4f6cd8549919ac" + integrity sha512-sTGjZlD3OGqbF9v1ajwUIXhGmjw9NyJ/14Lo0sg7xH8Pv4qUd5ZvQ6+DWYrQn3IKFUMfGFWYyL81ovLuPylrpw== dependencies: - "@react-native-community/cli-debugger-ui" "^5.0.1" - "@react-native-community/cli-tools" "^5.0.1" + "@react-native-community/cli-server-api" "^10.1.1" + "@react-native-community/cli-tools" "^10.1.1" + chalk "^4.1.2" + execa "^1.0.0" + metro "0.73.9" + metro-config "0.73.9" + metro-core "0.73.9" + metro-react-native-babel-transformer "0.73.9" + metro-resolver "0.73.9" + metro-runtime "0.73.9" + readline "^1.3.0" + +"@react-native-community/cli-server-api@^10.1.1": + version "10.1.1" + resolved "https://registry.yarnpkg.com/@react-native-community/cli-server-api/-/cli-server-api-10.1.1.tgz#e382269de281bb380c2e685431364fbbb8c1cb3a" + integrity sha512-NZDo/wh4zlm8as31UEBno2bui8+ufzsZV+KN7QjEJWEM0levzBtxaD+4je0OpfhRIIkhaRm2gl/vVf7OYAzg4g== + dependencies: + "@react-native-community/cli-debugger-ui" "^10.0.0" + "@react-native-community/cli-tools" "^10.1.1" compression "^1.7.1" connect "^3.6.5" errorhandler "^1.5.0" - nocache "^2.1.0" + nocache "^3.0.1" pretty-format "^26.6.2" serve-static "^1.13.1" - ws "^1.1.0" + ws "^7.5.1" -"@react-native-community/cli-tools@^5.0.1": - version "5.0.1" - resolved "https://registry.yarnpkg.com/@react-native-community/cli-tools/-/cli-tools-5.0.1.tgz#9ee564dbe20448becd6bce9fbea1b59aa5797919" - integrity sha512-XOX5w98oSE8+KnkMZZPMRT7I5TaP8fLbDl0tCu40S7Epz+Zz924n80fmdu6nUDIfPT1nV6yH1hmHmWAWTDOR+Q== +"@react-native-community/cli-tools@^10.1.1": + version "10.1.1" + resolved "https://registry.yarnpkg.com/@react-native-community/cli-tools/-/cli-tools-10.1.1.tgz#fa66e509c0d3faa31f7bb87ed7d42ad63f368ddd" + integrity sha512-+FlwOnZBV+ailEzXjcD8afY2ogFEBeHOw/8+XXzMgPaquU2Zly9B+8W089tnnohO3yfiQiZqkQlElP423MY74g== dependencies: - chalk "^3.0.0" - lodash "^4.17.15" + appdirsjs "^1.2.4" + chalk "^4.1.2" + find-up "^5.0.0" mime "^2.4.1" node-fetch "^2.6.0" open "^6.2.0" - shell-quote "1.6.1" + ora "^5.4.1" + semver "^6.3.0" + shell-quote "^1.7.3" -"@react-native-community/cli-types@^5.0.1": - version "5.0.1" - resolved "https://registry.yarnpkg.com/@react-native-community/cli-types/-/cli-types-5.0.1.tgz#8c5db4011988b0836d27a5efe230cb34890915dc" - integrity sha512-BesXnuFFlU/d1F3+sHhvKt8fUxbQlAbZ3hhMEImp9A6sopl8TEtryUGJ1dbazGjRXcADutxvjwT/i3LJVTIQug== +"@react-native-community/cli-types@^10.0.0": + version "10.0.0" + resolved "https://registry.yarnpkg.com/@react-native-community/cli-types/-/cli-types-10.0.0.tgz#046470c75ec18f8b3bd906e54e43a6f678e01a45" + integrity sha512-31oUM6/rFBZQfSmDQsT1DX/5fjqfxg7sf2u8kTPJK7rXVya5SRpAMaCXsPAG0omsmJxXt+J9HxUi3Ic+5Ux5Iw== dependencies: - ora "^3.4.0" + joi "^17.2.1" -"@react-native-community/cli@^5.0.1-alpha.1": - version "5.0.1" - resolved "https://registry.yarnpkg.com/@react-native-community/cli/-/cli-5.0.1.tgz#1f7a66d813d5daf102e593f3c550650fa0cc8314" - integrity sha512-9VzSYUYSEqxEH5Ib2UNSdn2eyPiYZ4T7Y79o9DKtRBuSaUIwbCUdZtIm+UUjBpLS1XYBkW26FqL8/UdZDmQvXw== - dependencies: - "@react-native-community/cli-debugger-ui" "^5.0.1" - "@react-native-community/cli-hermes" "^5.0.1" - "@react-native-community/cli-server-api" "^5.0.1" - "@react-native-community/cli-tools" "^5.0.1" - "@react-native-community/cli-types" "^5.0.1" - appdirsjs "^1.2.4" - chalk "^3.0.0" - command-exists "^1.2.8" - commander "^2.19.0" - cosmiconfig "^5.1.0" - deepmerge "^3.2.0" - envinfo "^7.7.2" +"@react-native-community/cli@10.2.2": + version "10.2.2" + resolved "https://registry.yarnpkg.com/@react-native-community/cli/-/cli-10.2.2.tgz#3fa438ba7f19f83e07bc337765fc1cabdcf2cac2" + integrity sha512-aZVcVIqj+OG6CrliR/Yn8wHxrvyzbFBY9cj7n0MvRw/P54QUru2nNqUTSSbqv0Qaa297yHJbe6kFDojDMSTM8Q== + dependencies: + "@react-native-community/cli-clean" "^10.1.1" + "@react-native-community/cli-config" "^10.1.1" + "@react-native-community/cli-debugger-ui" "^10.0.0" + "@react-native-community/cli-doctor" "^10.2.2" + "@react-native-community/cli-hermes" "^10.2.0" + "@react-native-community/cli-plugin-metro" "^10.2.2" + "@react-native-community/cli-server-api" "^10.1.1" + "@react-native-community/cli-tools" "^10.1.1" + "@react-native-community/cli-types" "^10.0.0" + chalk "^4.1.2" + commander "^9.4.1" execa "^1.0.0" find-up "^4.1.0" fs-extra "^8.1.0" - glob "^7.1.3" graceful-fs "^4.1.3" - joi "^17.2.1" - leven "^3.1.0" - lodash "^4.17.15" - metro "^0.64.0" - metro-config "^0.64.0" - metro-core "^0.64.0" - metro-react-native-babel-transformer "^0.64.0" - metro-resolver "^0.64.0" - metro-runtime "^0.64.0" - minimist "^1.2.0" - mkdirp "^0.5.1" - node-stream-zip "^1.9.1" - ora "^3.4.0" - pretty-format "^26.6.2" prompts "^2.4.0" semver "^6.3.0" - serve-static "^1.13.1" - strip-ansi "^5.2.0" - sudo-prompt "^9.0.0" - wcwidth "^1.0.1" "@react-native/assets@1.0.0": version "1.0.0" resolved "https://registry.yarnpkg.com/@react-native/assets/-/assets-1.0.0.tgz#c6f9bf63d274bafc8e970628de24986b30a55c8e" integrity sha512-KrwSpS1tKI70wuKl68DwJZYEvXktDHdZMG0k2AXD/rJVSlB23/X2CB2cutVR0HwNMJIal9HOUOBB2rVfa6UGtQ== -"@react-native/normalize-color@1.0.0": - version "1.0.0" - resolved "https://registry.yarnpkg.com/@react-native/normalize-color/-/normalize-color-1.0.0.tgz#c52a99d4fe01049102d47dc45d40cbde4f720ab6" - integrity sha512-xUNRvNmCl3UGCPbbHvfyFMnpvLPoOjDCcp5bT9m2k+TF/ZBklEQwhPZlkrxRx2NhgFh1X3a5uL7mJ7ZR+8G7Qg== +"@react-native/normalize-color@*", "@react-native/normalize-color@2.1.0": + version "2.1.0" + resolved "https://registry.yarnpkg.com/@react-native/normalize-color/-/normalize-color-2.1.0.tgz#939b87a9849e81687d3640c5efa2a486ac266f91" + integrity sha512-Z1jQI2NpdFJCVgpY+8Dq/Bt3d+YUi1928Q+/CZm/oh66fzM0RUl54vvuXlPJKybH4pdCZey1eDTPaLHkMPNgWA== -"@react-native/polyfills@1.0.0": - version "1.0.0" - resolved "https://registry.yarnpkg.com/@react-native/polyfills/-/polyfills-1.0.0.tgz#05bb0031533598f9458cf65a502b8df0eecae780" - integrity sha512-0jbp4RxjYopTsIdLl+/Fy2TiwVYHy4mgeu07DG4b/LyM0OS/+lPP5c9sbnt/AMlnF6qz2JRZpPpGw1eMNS6A4w== +"@react-native/polyfills@2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@react-native/polyfills/-/polyfills-2.0.0.tgz#4c40b74655c83982c8cf47530ee7dc13d957b6aa" + integrity sha512-K0aGNn1TjalKj+65D7ycc1//H9roAQ51GJVk5ZJQFb2teECGmzd86bYDC0aYdbRf7gtovescq4Zt6FR0tgXiHQ== "@sideway/address@^4.1.3": version "4.1.4" @@ -2218,35 +2295,67 @@ dependencies: "@hapi/hoek" "^9.0.0" -"@sideway/formula@^3.0.0": - version "3.0.0" - resolved "https://registry.yarnpkg.com/@sideway/formula/-/formula-3.0.0.tgz#fe158aee32e6bd5de85044be615bc08478a0a13c" - integrity sha512-vHe7wZ4NOXVfkoRb8T5otiENVlT7a3IAiw7H5M2+GO+9CDgcVUUsX1zalAztCmwyOr2RUTGJdgB+ZvSVqmdHmg== +"@sideway/formula@^3.0.1": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@sideway/formula/-/formula-3.0.1.tgz#80fcbcbaf7ce031e0ef2dd29b1bfc7c3f583611f" + integrity sha512-/poHZJJVjx3L+zVD6g9KgHfYnb443oi7wLu/XKojDviHy6HOEOA6z1Trk5aR1dGcmPenJEgb2sK2I80LeS3MIg== "@sideway/pinpoint@^2.0.0": version "2.0.0" resolved "https://registry.yarnpkg.com/@sideway/pinpoint/-/pinpoint-2.0.0.tgz#cff8ffadc372ad29fd3f78277aeb29e632cc70df" integrity sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ== -"@sinonjs/commons@^1.7.0": - version "1.8.3" - resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-1.8.3.tgz#3802ddd21a50a949b6721ddd72da36e67e7f1b2d" - integrity sha512-xkNcLAn/wZaX14RPlwizcKicDk9G3F8m2nU3L7Ukm5zBgTwiT0wsoFAHx9Jq56fJA1z/7uKGtCRu16sOUCLIHQ== +"@sigstore/protobuf-specs@^0.1.0": + version "0.1.0" + resolved "https://registry.yarnpkg.com/@sigstore/protobuf-specs/-/protobuf-specs-0.1.0.tgz#957cb64ea2f5ce527cc9cf02a096baeb0d2b99b4" + integrity sha512-a31EnjuIDSX8IXBUib3cYLDRlPMU36AWX4xS8ysLaNu4ZzUesDiPt83pgrW2X1YLMe5L2HbDyaKK5BrL4cNKaQ== + +"@sinclair/typebox@^0.25.16": + version "0.25.24" + resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.25.24.tgz#8c7688559979f7079aacaf31aa881c3aa410b718" + integrity sha512-XJfwUVUKDHF5ugKwIcxEgc9k8b7HbznCp6eUfWgu710hMPNIO4aw4/zB5RogDQz8nd6gyCDpU9O/m6qYEWY6yQ== + +"@sinonjs/commons@^2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-2.0.0.tgz#fd4ca5b063554307e8327b4564bd56d3b73924a3" + integrity sha512-uLa0j859mMrg2slwQYdO/AkrOfmH+X6LTVmNTS9CqexuE2IvVORIkSpJLqePAbEnKJ77aMmCwr1NUZ57120Xcg== dependencies: type-detect "4.0.8" -"@sinonjs/fake-timers@^8.0.1": - version "8.1.0" - resolved "https://registry.yarnpkg.com/@sinonjs/fake-timers/-/fake-timers-8.1.0.tgz#3fdc2b6cb58935b21bfb8d1625eb1300484316e7" - integrity sha512-OAPJUAtgeINhh/TAlUID4QTs53Njm7xzddaVlEs/SXwgtiD1tW22zAB/W1wdqfrpmikgaWQ9Fw6Ws+hsiRm5Vg== +"@sinonjs/fake-timers@^10.0.2": + version "10.0.2" + resolved "https://registry.yarnpkg.com/@sinonjs/fake-timers/-/fake-timers-10.0.2.tgz#d10549ed1f423d80639c528b6c7f5a1017747d0c" + integrity sha512-SwUDyjWnah1AaNl7kxsa7cfLhlTYoiyhDAIgyh+El30YvXs/o7OLXpYH88Zdhyx9JExKrmHDJ+10bwIcY80Jmw== dependencies: - "@sinonjs/commons" "^1.7.0" + "@sinonjs/commons" "^2.0.0" "@sovpro/delimited-stream@^1.1.0": version "1.1.0" resolved "https://registry.yarnpkg.com/@sovpro/delimited-stream/-/delimited-stream-1.1.0.tgz#4334bba7ee241036e580fdd99c019377630d26b4" integrity sha512-kQpk267uxB19X3X2T1mvNMjyvIEonpNSHrMlK5ZaBU6aZxw7wPbpgKJOjHN3+/GPVpXgAV9soVT2oyHpLkLtyw== +"@sphereon/openid4vci-client@^0.4.0": + version "0.4.0" + resolved "https://registry.yarnpkg.com/@sphereon/openid4vci-client/-/openid4vci-client-0.4.0.tgz#f48c2bb42041b9eab13669de23ba917785c83b24" + integrity sha512-N9ytyV3DHAjBjd67jMowmBMmD9/4Sxkehsrpd1I9Hxg5TO1K+puUPsPXj8Zh4heIWSzT5xBsGTSqXdF0LlrDwQ== + dependencies: + "@sphereon/ssi-types" "^0.9.0" + cross-fetch "^3.1.5" + debug "^4.3.4" + uint8arrays "^3.1.1" + +"@sphereon/ssi-types@^0.9.0": + version "0.9.0" + resolved "https://registry.yarnpkg.com/@sphereon/ssi-types/-/ssi-types-0.9.0.tgz#d140eb6abd77381926d0da7ac51b3c4b96a31b4b" + integrity sha512-umCr/syNcmvMMbQ+i/r/mwjI1Qw2aFPp9AwBTvTo1ailAVaaJjJGPkkVz1K9/2NZATNdDiQ3A8yGzdVJoKh9pA== + dependencies: + jwt-decode "^3.1.2" + +"@stablelib/aead@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@stablelib/aead/-/aead-1.0.1.tgz#c4b1106df9c23d1b867eb9b276d8f42d5fc4c0c3" + integrity sha512-q39ik6sxGHewqtO0nP4BuSe3db5G1fEJE8ukvngS2gLkBXyy6E7pLubhbYgnkDFv6V8cWaxcE4Xn0t6LWcJkyg== + "@stablelib/binary@^1.0.0", "@stablelib/binary@^1.0.1": version "1.0.1" resolved "https://registry.yarnpkg.com/@stablelib/binary/-/binary-1.0.1.tgz#c5900b94368baf00f811da5bdb1610963dfddf7f" @@ -2254,7 +2363,37 @@ dependencies: "@stablelib/int" "^1.0.1" -"@stablelib/ed25519@^1.0.2": +"@stablelib/bytes@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@stablelib/bytes/-/bytes-1.0.1.tgz#0f4aa7b03df3080b878c7dea927d01f42d6a20d8" + integrity sha512-Kre4Y4kdwuqL8BR2E9hV/R5sOrUj6NanZaZis0V6lX5yzqC3hBuVSDXUIBqQv/sCpmuWRiHLwqiT1pqqjuBXoQ== + +"@stablelib/chacha20poly1305@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@stablelib/chacha20poly1305/-/chacha20poly1305-1.0.1.tgz#de6b18e283a9cb9b7530d8767f99cde1fec4c2ee" + integrity sha512-MmViqnqHd1ymwjOQfghRKw2R/jMIGT3wySN7cthjXCBdO+qErNPUBnRzqNpnvIwg7JBCg3LdeCZZO4de/yEhVA== + dependencies: + "@stablelib/aead" "^1.0.1" + "@stablelib/binary" "^1.0.1" + "@stablelib/chacha" "^1.0.1" + "@stablelib/constant-time" "^1.0.1" + "@stablelib/poly1305" "^1.0.1" + "@stablelib/wipe" "^1.0.1" + +"@stablelib/chacha@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@stablelib/chacha/-/chacha-1.0.1.tgz#deccfac95083e30600c3f92803a3a1a4fa761371" + integrity sha512-Pmlrswzr0pBzDofdFuVe1q7KdsHKhhU24e8gkEwnTGOmlC7PADzLVxGdn2PoNVBBabdg0l/IfLKg6sHAbTQugg== + dependencies: + "@stablelib/binary" "^1.0.1" + "@stablelib/wipe" "^1.0.1" + +"@stablelib/constant-time@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@stablelib/constant-time/-/constant-time-1.0.1.tgz#bde361465e1cf7b9753061b77e376b0ca4c77e35" + integrity sha512-tNOs3uD0vSJcK6z1fvef4Y+buN7DXhzHDPqRLSXUel1UfqMB1PWNsnnAezrKfEwTLpN0cGH2p9NNjs6IqeD0eg== + +"@stablelib/ed25519@^1.0.2", "@stablelib/ed25519@^1.0.3": version "1.0.3" resolved "https://registry.yarnpkg.com/@stablelib/ed25519/-/ed25519-1.0.3.tgz#f8fdeb6f77114897c887bb6a3138d659d3f35996" integrity sha512-puIMWaX9QlRsbhxfDc5i+mNPMY+0TmQEskunY1rZEBPi1acBCVQAhnsk/1Hk50DGPtVsZtAWQg4NHGlVaO9Hqg== @@ -2273,6 +2412,21 @@ resolved "https://registry.yarnpkg.com/@stablelib/int/-/int-1.0.1.tgz#75928cc25d59d73d75ae361f02128588c15fd008" integrity sha512-byr69X/sDtDiIjIV6m4roLVWnNNlRGzsvxw+agj8CIEazqWGOQp2dTYgQhtyVXV9wpO6WyXRQUzLV/JRNumT2w== +"@stablelib/keyagreement@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@stablelib/keyagreement/-/keyagreement-1.0.1.tgz#4612efb0a30989deb437cd352cee637ca41fc50f" + integrity sha512-VKL6xBwgJnI6l1jKrBAfn265cspaWBPAPEc62VBQrWHLqVgNRE09gQ/AnOEyKUWrrqfD+xSQ3u42gJjLDdMDQg== + dependencies: + "@stablelib/bytes" "^1.0.1" + +"@stablelib/poly1305@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@stablelib/poly1305/-/poly1305-1.0.1.tgz#93bfb836c9384685d33d70080718deae4ddef1dc" + integrity sha512-1HlG3oTSuQDOhSnLwJRKeTRSAdFNVB/1djy2ZbS35rBSJ/PFqx9cf9qatinWghC2UbfOYD8AcrtbUQl8WoxabA== + dependencies: + "@stablelib/constant-time" "^1.0.1" + "@stablelib/wipe" "^1.0.1" + "@stablelib/random@1.0.0": version "1.0.0" resolved "https://registry.yarnpkg.com/@stablelib/random/-/random-1.0.0.tgz#f441495075cdeaa45de16d7ddcc269c0b8edb16b" @@ -2312,11 +2466,45 @@ resolved "https://registry.yarnpkg.com/@stablelib/wipe/-/wipe-1.0.1.tgz#d21401f1d59ade56a62e139462a97f104ed19a36" integrity sha512-WfqfX/eXGiAd3RJe4VU2snh/ZPwtSjLG4ynQ/vYzvghTh7dHFcI1wl+nrkWG6lGhukOxOsUHfv8dUXr58D0ayg== +"@stablelib/x25519@^1.0.2": + version "1.0.3" + resolved "https://registry.yarnpkg.com/@stablelib/x25519/-/x25519-1.0.3.tgz#13c8174f774ea9f3e5e42213cbf9fc68a3c7b7fd" + integrity sha512-KnTbKmUhPhHavzobclVJQG5kuivH+qDLpe84iRqX3CLrKp881cF160JvXJ+hjn1aMyCwYOKeIZefIH/P5cJoRw== + dependencies: + "@stablelib/keyagreement" "^1.0.1" + "@stablelib/random" "^1.0.2" + "@stablelib/wipe" "^1.0.1" + +"@stablelib/xchacha20@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@stablelib/xchacha20/-/xchacha20-1.0.1.tgz#e98808d1f7d8b20e3ff37c71a3062a2a955d9a8c" + integrity sha512-1YkiZnFF4veUwBVhDnDYwo6EHeKzQK4FnLiO7ezCl/zu64uG0bCCAUROJaBkaLH+5BEsO3W7BTXTguMbSLlWSw== + dependencies: + "@stablelib/binary" "^1.0.1" + "@stablelib/chacha" "^1.0.1" + "@stablelib/wipe" "^1.0.1" + +"@stablelib/xchacha20poly1305@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@stablelib/xchacha20poly1305/-/xchacha20poly1305-1.0.1.tgz#addcaf30b92dd956f76b3357888e2f91b92e7a61" + integrity sha512-B1Abj0sMJ8h3HNmGnJ7vHBrAvxuNka6cJJoZ1ILN7iuacXp7sUYcgOVEOTLWj+rtQMpspY9tXSCRLPmN1mQNWg== + dependencies: + "@stablelib/aead" "^1.0.1" + "@stablelib/chacha20poly1305" "^1.0.1" + "@stablelib/constant-time" "^1.0.1" + "@stablelib/wipe" "^1.0.1" + "@stablelib/xchacha20" "^1.0.1" + "@tootallnate/once@1": version "1.1.2" resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-1.1.2.tgz#ccb91445360179a04e7fe6aff78c00ffc1eeaf82" integrity sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw== +"@tootallnate/once@2": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-2.0.0.tgz#f544a148d3ab35801c1f633a7441fd87c2e484bf" + integrity sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A== + "@tsconfig/node10@^1.0.7": version "1.0.9" resolved "https://registry.yarnpkg.com/@tsconfig/node10/-/node10-1.0.9.tgz#df4907fc07a886922637b15e02d4cebc4c0021b2" @@ -2337,13 +2525,26 @@ resolved "https://registry.yarnpkg.com/@tsconfig/node16/-/node16-1.0.3.tgz#472eaab5f15c1ffdd7f8628bd4c4f753995ec79e" integrity sha512-yOlFc+7UtL/89t2ZhjPvvB/DeAr3r+Dq58IgzsFkOAvVC6NMJXmCGjbptdXdR9qsX7pKcTL+s87FtYREi2dEEQ== -"@types/babel__core@^7.0.0", "@types/babel__core@^7.1.14": - version "7.1.19" - resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.1.19.tgz#7b497495b7d1b4812bdb9d02804d0576f43ee460" - integrity sha512-WEOTgRsbYkvA/KCsDwVEGkd7WAr1e3g31VHQ8zy5gul/V1qKullU/BU5I68X5v7V3GnB9eotmom4v5a5gjxorw== +"@tufjs/canonical-json@1.0.0": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@tufjs/canonical-json/-/canonical-json-1.0.0.tgz#eade9fd1f537993bc1f0949f3aea276ecc4fab31" + integrity sha512-QTnf++uxunWvG2z3UFNzAoQPHxnSXOwtaI3iJ+AohhV+5vONuArPjJE7aPXPVXfXJsqrVbZBu9b81AJoSd09IQ== + +"@tufjs/models@1.0.3": + version "1.0.3" + resolved "https://registry.yarnpkg.com/@tufjs/models/-/models-1.0.3.tgz#e6cb8a86834da7459a7c836cd892dee56b4bab44" + integrity sha512-mkFEqqRisi13DmR5pX4x+Zk97EiU8djTtpNW1GeuX410y/raAsq/T3ZCjwoRIZ8/cIBfW0olK/sywlAiWevDVw== dependencies: - "@babel/parser" "^7.1.0" - "@babel/types" "^7.0.0" + "@tufjs/canonical-json" "1.0.0" + minimatch "^7.4.6" + +"@types/babel__core@^7.1.14": + version "7.20.0" + resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.20.0.tgz#61bc5a4cae505ce98e1e36c5445e4bee060d8891" + integrity sha512-+n8dL/9GWblDO0iU6eZAwEIJVr5DWigtle+Q6HLOrh/pdbXOhOtqzq8VPPE2zvNJzSKY4vH/z3iT3tn0A3ypiQ== + dependencies: + "@babel/parser" "^7.20.7" + "@babel/types" "^7.20.7" "@types/babel__generator" "*" "@types/babel__template" "*" "@types/babel__traverse" "*" @@ -2363,10 +2564,10 @@ "@babel/parser" "^7.1.0" "@babel/types" "^7.0.0" -"@types/babel__traverse@*", "@types/babel__traverse@^7.0.4", "@types/babel__traverse@^7.0.6": - version "7.18.2" - resolved "https://registry.yarnpkg.com/@types/babel__traverse/-/babel__traverse-7.18.2.tgz#235bf339d17185bdec25e024ca19cce257cc7309" - integrity sha512-FcFaxOr2V5KZCviw1TnutEMVUVsGt4D2hP1TAfXZAMKuHYW3xQhe3jTxNPWutgCJ3/X1c5yX8ZoGVEItxKbwBg== +"@types/babel__traverse@*", "@types/babel__traverse@^7.0.6": + version "7.18.3" + resolved "https://registry.yarnpkg.com/@types/babel__traverse/-/babel__traverse-7.18.3.tgz#dfc508a85781e5698d5b33443416b6268c4b3e8d" + integrity sha512-1kbcJ40lLB7MHsj39U4Sh1uTd2E7rLEa79kmDpI6cy+XiXsteB3POdQomoq4FxszMrO3ZYchkhYJw7A2862b3w== dependencies: "@babel/types" "^7.3.0" @@ -2393,51 +2594,53 @@ "@types/node" "*" "@types/cors@^2.8.10": - version "2.8.12" - resolved "https://registry.yarnpkg.com/@types/cors/-/cors-2.8.12.tgz#6b2c510a7ad7039e98e7b8d3d6598f4359e5c080" - integrity sha512-vt+kDhq/M2ayberEtJcIN/hxXy1Pk+59g2FV/ZQceeaTyCtCucjL2Q7FXlFjtWn4n15KCr1NE2lNNFhp0lEThw== + version "2.8.13" + resolved "https://registry.yarnpkg.com/@types/cors/-/cors-2.8.13.tgz#b8ade22ba455a1b8cb3b5d3f35910fd204f84f94" + integrity sha512-RG8AStHlUiV5ysZQKq97copd2UmVYw3/pRMLefISZ3S1hK104Cwm7iLQ3fTKx+lsUH2CE8FlLaYeEA2LSeqYUA== + dependencies: + "@types/node" "*" -"@types/eslint@^7.2.13": - version "7.29.0" - resolved "https://registry.yarnpkg.com/@types/eslint/-/eslint-7.29.0.tgz#e56ddc8e542815272720bb0b4ccc2aff9c3e1c78" - integrity sha512-VNcvioYDH8/FxaeTKkM4/TiTwt6pBV9E3OfGmvaw8tPl0rrHCJ4Ll15HRT+pMiFAf/MLQvAzC+6RzUMEL9Ceng== +"@types/eslint@^8.21.2": + version "8.37.0" + resolved "https://registry.yarnpkg.com/@types/eslint/-/eslint-8.37.0.tgz#29cebc6c2a3ac7fea7113207bf5a828fdf4d7ef1" + integrity sha512-Piet7dG2JBuDIfohBngQ3rCt7MgO9xCO4xIMKxBThCq5PNRB91IjlJ10eJVwfoNtvTErmxLzwBZ7rHZtbOMmFQ== dependencies: "@types/estree" "*" "@types/json-schema" "*" "@types/estree@*": - version "1.0.0" - resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.0.tgz#5fb2e536c1ae9bf35366eed879e827fa59ca41c2" - integrity sha512-WulqXMDUTYAXCjZnk6JtIHPigp55cVtDgDrO2gHRwhyJto21+1zbVCtOYB2L1F9w4qCQ0rOGWBnBe0FNTiEJIQ== + version "1.0.1" + resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.1.tgz#aa22750962f3bf0e79d753d3cc067f010c95f194" + integrity sha512-LG4opVs2ANWZ1TJoKc937iMmNstM/d0ae1vNbnBvBhqCSezgVUOzcLCqbI5elV8Vy6WKwKjaqR+zO9VKirBBCA== "@types/events@^3.0.0": version "3.0.0" resolved "https://registry.yarnpkg.com/@types/events/-/events-3.0.0.tgz#2862f3f58a9a7f7c3e78d79f130dd4d71c25c2a7" integrity sha512-EaObqwIvayI5a8dCzhFrjKzVwKLxjoG9T6Ppd5CEo07LRKfQ8Yokw54r5+Wq7FaBQ+yXRvQAYPrHwya1/UFt9g== -"@types/express-serve-static-core@^4.17.18": - version "4.17.31" - resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.17.31.tgz#a1139efeab4e7323834bb0226e62ac019f474b2f" - integrity sha512-DxMhY+NAsTwMMFHBTtJFNp5qiHKJ7TeqOo23zVEM9alT1Ml27Q3xcTH0xwxn7Q0BbMcVEJOs/7aQtUWupUQN3Q== +"@types/express-serve-static-core@^4.17.33": + version "4.17.33" + resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.17.33.tgz#de35d30a9d637dc1450ad18dd583d75d5733d543" + integrity sha512-TPBqmR/HRYI3eC2E5hmiivIzv+bidAfXofM+sbonAGvyDhySGw9/PQZFt2BLOrjUUR++4eJVpx6KnLQK1Fk9tA== dependencies: "@types/node" "*" "@types/qs" "*" "@types/range-parser" "*" -"@types/express@^4.17.13": - version "4.17.14" - resolved "https://registry.yarnpkg.com/@types/express/-/express-4.17.14.tgz#143ea0557249bc1b3b54f15db4c81c3d4eb3569c" - integrity sha512-TEbt+vaPFQ+xpxFLFssxUDXj5cWCxZJjIcB7Yg0k0GMHGtgtQgpvx/MUQUeAkNbA9AAGrwkAsoeItdTgS7FMyg== +"@types/express@^4.17.13", "@types/express@^4.17.15": + version "4.17.17" + resolved "https://registry.yarnpkg.com/@types/express/-/express-4.17.17.tgz#01d5437f6ef9cfa8668e616e13c2f2ac9a491ae4" + integrity sha512-Q4FmmuLGBG58btUnfS1c1r/NQdlp3DMfGDGig8WhfpA2YRUtEkxAjkZb0yvplJGYdF1fsQ81iMDcH24sSCNC/Q== dependencies: "@types/body-parser" "*" - "@types/express-serve-static-core" "^4.17.18" + "@types/express-serve-static-core" "^4.17.33" "@types/qs" "*" "@types/serve-static" "*" "@types/ffi-napi@^4.0.5": - version "4.0.6" - resolved "https://registry.yarnpkg.com/@types/ffi-napi/-/ffi-napi-4.0.6.tgz#cd1c65cc9e701de664e640ccb17a2e823a674d44" - integrity sha512-yrBtqeVD1aeVo271jXVEo3iAtbzSGVGRssJv9W9JlUfg5Z5FgHJx2MV88GRwVATu/XWg6zyenW/cb1MNAuOtaQ== + version "4.0.7" + resolved "https://registry.yarnpkg.com/@types/ffi-napi/-/ffi-napi-4.0.7.tgz#b3a9beeae160c74adca801ca1c9defb1ec0a1a32" + integrity sha512-2CvLfgxCUUSj7qVab6/uFLyVpgVd2gEV4H/TQEHHn6kZTV8iTesz9uo0bckhwzsh71atutOv8P3JmvRX2ZvpZg== dependencies: "@types/node" "*" "@types/ref-napi" "*" @@ -2448,26 +2651,27 @@ resolved "https://registry.yarnpkg.com/@types/figlet/-/figlet-1.5.5.tgz#da93169178f0187da288c313ab98ab02fb1e8b8c" integrity sha512-0sMBeFoqdGgdXoR/hgKYSWMpFufSpToosNsI2VgmkPqZJgeEXsXNu2hGr0FN401dBro2tNO5y2D6uw3UxVaxbg== -"@types/graceful-fs@^4.1.2": - version "4.1.5" - resolved "https://registry.yarnpkg.com/@types/graceful-fs/-/graceful-fs-4.1.5.tgz#21ffba0d98da4350db64891f92a9e5db3cdb4e15" - integrity sha512-anKkLmZZ+xm4p8JWBf4hElkM4XR+EZeA2M9BAkkTldmcyDY4mbdIJnRghDJH3Ov5ooY7/UAoENtmdMSkaAd7Cw== +"@types/graceful-fs@^4.1.3": + version "4.1.6" + resolved "https://registry.yarnpkg.com/@types/graceful-fs/-/graceful-fs-4.1.6.tgz#e14b2576a1c25026b7f02ede1de3b84c3a1efeae" + integrity sha512-Sig0SNORX9fdW+bQuTEovKj3uHcUL6LQKbCrrqb1X7J6/ReAbhCXRAhc+SMejhLELFj2QcyuxmUooZ4bt5ReSw== dependencies: "@types/node" "*" -"@types/indy-sdk-react-native@npm:@types/indy-sdk@^1.16.21", "@types/indy-sdk@^1.16.21": - version "1.16.21" - resolved "https://registry.yarnpkg.com/@types/indy-sdk/-/indy-sdk-1.16.21.tgz#bb6178e2a515115b1bf225fb78506a3017d08aa8" - integrity sha512-SIu1iOa77lkxkGlW09OinFwebe7U5oDYwI70NnPoe9nbDr63i0FozITWEyIdC1BloKvZRXne6nM4i9zy6E3n6g== +"@types/indy-sdk@*", "@types/indy-sdk@1.16.26", "@types/indy-sdk@^1.16.26": + version "1.16.26" + resolved "https://registry.yarnpkg.com/@types/indy-sdk/-/indy-sdk-1.16.26.tgz#871f82c3f7d241d649aff5eb6800048890efb8f8" + integrity sha512-KlnjsVsX/7yTmyyIlHWcytlBHoQ1vPGeiLnLv5y1vDftL6OQ5V+hebfAr7d3roMEsjCTH3qKkklwGcj1qS90YA== dependencies: buffer "^6.0.0" -"@types/inquirer@^8.1.3": - version "8.2.3" - resolved "https://registry.yarnpkg.com/@types/inquirer/-/inquirer-8.2.3.tgz#985515d04879a0d0c1f5f49ec375767410ba9dab" - integrity sha512-ZlBqD+8WIVNy3KIVkl+Qne6bGLW2erwN0GJXY9Ri/9EMbyupee3xw3H0Mmv5kJoLyNpfd/oHlwKxO0DUDH7yWA== +"@types/inquirer@^8.2.6": + version "8.2.6" + resolved "https://registry.yarnpkg.com/@types/inquirer/-/inquirer-8.2.6.tgz#abd41a5fb689c7f1acb12933d787d4262a02a0ab" + integrity sha512-3uT88kxg8lNzY8ay2ZjP44DKcRaTGztqeIvN2zHvhzIBH/uAPaL75aBtdNRKbA7xXoMbBt5kX0M00VKAnfOYlA== dependencies: "@types/through" "*" + rxjs "^7.2.0" "@types/istanbul-lib-coverage@*", "@types/istanbul-lib-coverage@^2.0.0", "@types/istanbul-lib-coverage@^2.0.1": version "2.0.4" @@ -2488,15 +2692,15 @@ dependencies: "@types/istanbul-lib-report" "*" -"@types/jest@^26.0.23": - version "26.0.24" - resolved "https://registry.yarnpkg.com/@types/jest/-/jest-26.0.24.tgz#943d11976b16739185913a1936e0de0c4a7d595a" - integrity sha512-E/X5Vib8BWqZNRlDxj9vYXhsDwPYbPINqKF9BsnSoon4RQ0D9moEuLD8txgyypFLH7J4+Lho9Nr/c8H0Fi+17w== +"@types/jest@^29.5.0": + version "29.5.1" + resolved "https://registry.yarnpkg.com/@types/jest/-/jest-29.5.1.tgz#83c818aa9a87da27d6da85d3378e5a34d2f31a47" + integrity sha512-tEuVcHrpaixS36w7hpsfLBLpjtMRJUE09/MHXn923LOVojDwyC14cWcfc0rDs0VEfUyYmt/+iX1kxxp+gZMcaQ== dependencies: - jest-diff "^26.0.0" - pretty-format "^26.0.0" + expect "^29.0.0" + pretty-format "^29.0.0" -"@types/json-schema@*", "@types/json-schema@^7.0.7": +"@types/json-schema@*", "@types/json-schema@^7.0.9": version "7.0.11" resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.11.tgz#d421b6c527a3037f7c84433fd2c4229e016863d3" integrity sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ== @@ -2506,10 +2710,15 @@ resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee" integrity sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ== -"@types/luxon@^1.27.0": - version "1.27.1" - resolved "https://registry.yarnpkg.com/@types/luxon/-/luxon-1.27.1.tgz#aceeb2d5be8fccf541237e184e37ecff5faa9096" - integrity sha512-cPiXpOvPFDr2edMnOXlz3UBDApwUfR+cpizvxCy0n3vp9bz/qe8BWzHPIEFcy+ogUOyjKuCISgyq77ELZPmkkg== +"@types/long@^4.0.1": + version "4.0.2" + resolved "https://registry.yarnpkg.com/@types/long/-/long-4.0.2.tgz#b74129719fc8d11c01868010082d483b7545591a" + integrity sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA== + +"@types/luxon@^3.2.0": + version "3.3.0" + resolved "https://registry.yarnpkg.com/@types/luxon/-/luxon-3.3.0.tgz#a61043a62c0a72696c73a0a305c544c96501e006" + integrity sha512-uKRI5QORDnrGFYgcdAVnHvEIvEZ8noTpP/Bg+HeUzZghwinDlIS87DEenV5r1YoOF9G4x600YsUXLWZ19rmTmg== "@types/mime@*": version "3.0.1" @@ -2526,7 +2735,7 @@ resolved "https://registry.yarnpkg.com/@types/minimist/-/minimist-1.2.2.tgz#ee771e2ba4b3dc5b372935d549fd9617bf345b8c" integrity sha512-jhuKLIRrhvCPLqwPcx6INqmKeiA5EWrsCOPhrlFSrbrmU4ZMPjj5Ul/oLCMDO98XRUIwVm78xICz4EPCektzeQ== -"@types/node-fetch@^2.5.10": +"@types/node-fetch@2.6.2": version "2.6.2" resolved "https://registry.yarnpkg.com/@types/node-fetch/-/node-fetch-2.6.2.tgz#d1a9c5fd049d9415dce61571557104dec3ec81da" integrity sha512-DHqhlq5jeESLy19TYhLakJ07kNumXWjcDdxXsLUMJZ6ue8VZJj4kLPQVE/2mdHh3xZziNF1xppu5lwmS53HR+A== @@ -2534,10 +2743,10 @@ "@types/node" "*" form-data "^3.0.0" -"@types/node@*", "@types/node@^15.14.4": - version "15.14.9" - resolved "https://registry.yarnpkg.com/@types/node/-/node-15.14.9.tgz#bc43c990c3c9be7281868bbc7b8fdd6e2b57adfa" - integrity sha512-qjd88DrCxupx/kJD5yQgZdcYKZKSIGBVDIBE1/LTGcNm3d2Np/jxojkdePDdfnBHJc5W7vSMpbJ1aB7p/Py69A== +"@types/node@*", "@types/node@>=13.7.0", "@types/node@^16.11.7": + version "16.18.24" + resolved "https://registry.yarnpkg.com/@types/node/-/node-16.18.24.tgz#f21925dd56cd3467b4e1e0c5071d0f2af5e9a316" + integrity sha512-zvSN2Esek1aeLdKDYuntKAYjti9Z2oT4I8bfkLLhIxHlv3dwZ5vvATxOc31820iYm4hQRCwjUgDpwSMFjfTUnw== "@types/normalize-package-data@^2.4.0": version "2.4.1" @@ -2555,14 +2764,9 @@ integrity sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA== "@types/prettier@^2.1.5": - version "2.7.1" - resolved "https://registry.yarnpkg.com/@types/prettier/-/prettier-2.7.1.tgz#dfd20e2dc35f027cdd6c1908e80a5ddc7499670e" - integrity sha512-ri0UmynRRvZiiUJdiz38MmIblKK+oH30MztdBVR95dv/Ubw6neWSb8u1XpRb72L4qsZOhz+L+z9JD40SJmfWow== - -"@types/prop-types@*": - version "15.7.5" - resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.5.tgz#5f19d2b85a98e9558036f6a3cacc8819420f05cf" - integrity sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w== + version "2.7.2" + resolved "https://registry.yarnpkg.com/@types/prettier/-/prettier-2.7.2.tgz#6c2324641cc4ba050a8c710b2b251b377581fbf0" + integrity sha512-KufADq8uQqo1pYKVIYzfKbJfBAc0sOeXqGbFaSpv8MRmC/zXgowNZmFcbngndGk922QDmOASEXUZCaY48gs4cg== "@types/qs@*": version "6.9.7" @@ -2574,45 +2778,36 @@ resolved "https://registry.yarnpkg.com/@types/range-parser/-/range-parser-1.2.4.tgz#cd667bcfdd025213aafb7ca5915a932590acdcdc" integrity sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw== -"@types/react-native@^0.64.10": - version "0.64.27" - resolved "https://registry.yarnpkg.com/@types/react-native/-/react-native-0.64.27.tgz#f16e0b713c733c2476e7b16c92bf767f0675c560" - integrity sha512-vOEGMQGKNp6B1UfofKvCit2AxwByI6cbSa71E2uuxuvFr7FATVlykDbUS/Yht1HJhnbP5qlNOYw4ocUvDGjwbA== - dependencies: - "@types/react" "^17" - -"@types/react@^17": - version "17.0.50" - resolved "https://registry.yarnpkg.com/@types/react/-/react-17.0.50.tgz#39abb4f7098f546cfcd6b51207c90c4295ee81fc" - integrity sha512-ZCBHzpDb5skMnc1zFXAXnL3l1FAdi+xZvwxK+PkglMmBrwjpp9nKaWuEvrGnSifCJmBFGxZOOFuwC6KH/s0NuA== +"@types/ref-array-di@^1.2.5": + version "1.2.5" + resolved "https://registry.yarnpkg.com/@types/ref-array-di/-/ref-array-di-1.2.5.tgz#822b9be5b934398fafd5c26264d8de80d487747d" + integrity sha512-dA/Himb7ca/Tf5vqLOhi7LewAAoOXghlocw7gAqvNrmLybAtu+w2BLzEsbFWAtx5ElNzMEHDaRybueYViFROjQ== dependencies: - "@types/prop-types" "*" - "@types/scheduler" "*" - csstype "^3.0.2" + "@types/ref-napi" "*" "@types/ref-napi@*", "@types/ref-napi@^3.0.4": - version "3.0.5" - resolved "https://registry.yarnpkg.com/@types/ref-napi/-/ref-napi-3.0.5.tgz#8db441d381737af5c353d7dd89c7593b5f2080c8" - integrity sha512-u+L/RdwTuJes3pDypOVR/MtcqzoULu8Z8yulP6Tw5z7eXV1ba1llizNVFtI/m2iPfDy/dPPt+3ar1QCgonTzsw== + version "3.0.7" + resolved "https://registry.yarnpkg.com/@types/ref-napi/-/ref-napi-3.0.7.tgz#20adc93a7a2f9f992dfb17409fd748e6f4bf403d" + integrity sha512-CzPwr36VkezSpaJGdQX/UrczMSDsDgsWQQFEfQkS799Ft7n/s183a53lsql7RwVq+Ik4yLEgI84pRnLC0XXRlA== dependencies: "@types/node" "*" "@types/ref-struct-di@*": - version "1.1.7" - resolved "https://registry.yarnpkg.com/@types/ref-struct-di/-/ref-struct-di-1.1.7.tgz#85e0149858a81a14f12f15ff31a6dffa42bab2d3" - integrity sha512-nnHR26qrCnQqxwHTv+rqzu/hGgDZl45TUs4bO6ZjpuC8/M2JoXFxk63xrWmAmqsLe55oxOgAWssyr3YHAMY89g== + version "1.1.8" + resolved "https://registry.yarnpkg.com/@types/ref-struct-di/-/ref-struct-di-1.1.8.tgz#df8cbf7b9bbbc03f476dcbe1958f92bf443f17d9" + integrity sha512-t5jwtHlEH6c3rgBRtMQTAtysROr1gWt/ZfcytolK+45dag747fUdgmZy/iQs5q41jinMnr62nxwI0Q8GkdK9TA== dependencies: "@types/ref-napi" "*" -"@types/scheduler@*": - version "0.16.2" - resolved "https://registry.yarnpkg.com/@types/scheduler/-/scheduler-0.16.2.tgz#1a62f89525723dde24ba1b01b092bf5df8ad4d39" - integrity sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew== +"@types/semver@^7.3.12": + version "7.3.13" + resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.3.13.tgz#da4bfd73f49bd541d28920ab0e2bf0ee80f71c91" + integrity sha512-21cFJr9z3g5dW8B0CVI9g2O9beqaThGQ6ZFBqHfwhzLDKUxaqTIy3vnfah/UPkfOiF2pLq+tGz+W8RyCskuslw== "@types/serve-static@*": - version "1.15.0" - resolved "https://registry.yarnpkg.com/@types/serve-static/-/serve-static-1.15.0.tgz#c7930ff61afb334e121a9da780aac0d9b8f34155" - integrity sha512-z5xyF6uh8CbjAu9760KDKsH2FcDxZ2tFCsA4HIMWE6IkiYMXfVoa+4f9KX+FN0ZLsaMw1WNG2ETLA6N+/YA+cg== + version "1.15.1" + resolved "https://registry.yarnpkg.com/@types/serve-static/-/serve-static-1.15.1.tgz#86b1753f0be4f9a1bee68d459fcda5be4ea52b5d" + integrity sha512-NUo5XNiAdULrJENtJXZZ3fHtfMolzZwczzBbnAeBbqBwG+LaG6YaJtuwzwGSQZ2wsCrxjEhNNjAkKigy3n8teQ== dependencies: "@types/mime" "*" "@types/node" "*" @@ -2629,27 +2824,27 @@ dependencies: "@types/node" "*" -"@types/uuid@^8.3.0", "@types/uuid@^8.3.1": - version "8.3.4" - resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-8.3.4.tgz#bd86a43617df0594787d38b735f55c805becf1bc" - integrity sha512-c/I8ZRb51j+pYGAu5CrFMRxqZ2ke4y2grEBO5AUjgSkSk+qT2Ea+OdWElz/OiMf5MNpn2b17kuVBwZLQJXzihw== +"@types/uuid@^9.0.1": + version "9.0.1" + resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-9.0.1.tgz#98586dc36aee8dacc98cc396dbca8d0429647aa6" + integrity sha512-rFT3ak0/2trgvp4yYZo5iKFEPsET7vKydKF+VRCxlQ9bpheehyAJH89dAkaLEq/j/RZXJIqcgsmPJKUP1Z28HA== -"@types/validator@^13.1.3": - version "13.7.7" - resolved "https://registry.yarnpkg.com/@types/validator/-/validator-13.7.7.tgz#e87cf34dd08522d21acf30130fd8941f433b81b5" - integrity sha512-jiEw2kTUJ8Jsh4A1K4b5Pkjj9Xz6FktLLOQ36ZVLRkmxFbpTvAV2VRoKMojz8UlZxNg/2dZqzpigH4JYn1bkQg== +"@types/validator@^13.7.10": + version "13.7.15" + resolved "https://registry.yarnpkg.com/@types/validator/-/validator-13.7.15.tgz#408c99d1b5f0eecc78109c11f896f72d1f026a10" + integrity sha512-yeinDVQunb03AEP8luErFcyf/7Lf7AzKCD0NXfgVoGCCQDNpZET8Jgq74oBgqKld3hafLbfzt/3inUdQvaFeXQ== "@types/varint@^6.0.0": - version "6.0.0" - resolved "https://registry.yarnpkg.com/@types/varint/-/varint-6.0.0.tgz#4ad73c23cbc9b7e44379a7729ace7ed9c8bc9854" - integrity sha512-2jBazyxGl4644tvu3VAez8UA/AtrcEetT9HOeAbqZ/vAcRVL/ZDFQjSS7rkWusU5cyONQVUz+nwwrNZdMva4ow== + version "6.0.1" + resolved "https://registry.yarnpkg.com/@types/varint/-/varint-6.0.1.tgz#018d424627c7951d370d73816e97e143dc99523b" + integrity sha512-fQdOiZpDMBvaEdl12P1x7xlTPRAtd7qUUtVaWgkCy8DC//wCv19nqFFtrnR3y/ac6VFY0UUvYuQqfKzZTSE26w== dependencies: "@types/node" "*" -"@types/ws@^7.4.6": - version "7.4.7" - resolved "https://registry.yarnpkg.com/@types/ws/-/ws-7.4.7.tgz#f7c390a36f7a0679aa69de2d501319f4f8d9b702" - integrity sha512-JQbbmxZTZehdc2iszGKs5oC3NFnjeay7mtAWrdt7qNtAVK0g19muApzAy4bm9byz79xa2ZnO/BOBC2R8RC5Lww== +"@types/ws@^8.5.4": + version "8.5.4" + resolved "https://registry.yarnpkg.com/@types/ws/-/ws-8.5.4.tgz#bb10e36116d6e570dd943735f86c933c1587b8a5" + integrity sha512-zdQDHKUgcX/zBc4GrwsE/7dVdAD8JR4EuiAXiiUhhfyIJXXb2+PrGshFyeXWQPMmmZ2XxgaqclgpIC7eTXc1mg== dependencies: "@types/node" "*" @@ -2659,88 +2854,109 @@ integrity sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA== "@types/yargs@^15.0.0": - version "15.0.14" - resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-15.0.14.tgz#26d821ddb89e70492160b66d10a0eb6df8f6fb06" - integrity sha512-yEJzHoxf6SyQGhBhIYGXQDSCkJjB6HohDShto7m8vaKg9Yp0Yn8+71J9eakh2bnPg6BfsH9PRMhiRTZnd4eXGQ== + version "15.0.15" + resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-15.0.15.tgz#e609a2b1ef9e05d90489c2f5f45bbfb2be092158" + integrity sha512-IziEYMU9XoVj8hWg7k+UJrXALkGFjWJhn5QFEv9q4p+v40oZhSuC135M38st8XPjICL7Ey4TV64ferBGUoJhBg== dependencies: "@types/yargs-parser" "*" "@types/yargs@^16.0.0": - version "16.0.4" - resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-16.0.4.tgz#26aad98dd2c2a38e421086ea9ad42b9e51642977" - integrity sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw== + version "16.0.5" + resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-16.0.5.tgz#12cc86393985735a283e387936398c2f9e5f88e3" + integrity sha512-AxO/ADJOBFJScHbWhq2xAhlWP24rY4aCEG/NFaMvbT3X2MgRsLjhjQwsn0Zi5zn0LG9jUhCCZMeX9Dkuw6k+vQ== dependencies: "@types/yargs-parser" "*" -"@typescript-eslint/eslint-plugin@^4.26.1": - version "4.33.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.33.0.tgz#c24dc7c8069c7706bc40d99f6fa87edcb2005276" - integrity sha512-aINiAxGVdOl1eJyVjaWn/YcVAq4Gi/Yo35qHGCnqbWVz61g39D0h23veY/MA0rFFGfxK7TySg2uwDeNv+JgVpg== - dependencies: - "@typescript-eslint/experimental-utils" "4.33.0" - "@typescript-eslint/scope-manager" "4.33.0" - debug "^4.3.1" - functional-red-black-tree "^1.0.1" - ignore "^5.1.8" - regexpp "^3.1.0" - semver "^7.3.5" +"@types/yargs@^17.0.8": + version "17.0.24" + resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-17.0.24.tgz#b3ef8d50ad4aa6aecf6ddc97c580a00f5aa11902" + integrity sha512-6i0aC7jV6QzQB8ne1joVZ0eSFIstHsCrobmOtghM11yGlH0j43FKL2UhWdELkyps0zuf7qVTUVCCR+tgSlyLLw== + dependencies: + "@types/yargs-parser" "*" + +"@typescript-eslint/eslint-plugin@^5.48.1": + version "5.59.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.59.1.tgz#9b09ee1541bff1d2cebdcb87e7ce4a4003acde08" + integrity sha512-AVi0uazY5quFB9hlp2Xv+ogpfpk77xzsgsIEWyVS7uK/c7MZ5tw7ZPbapa0SbfkqE0fsAMkz5UwtgMLVk2BQAg== + dependencies: + "@eslint-community/regexpp" "^4.4.0" + "@typescript-eslint/scope-manager" "5.59.1" + "@typescript-eslint/type-utils" "5.59.1" + "@typescript-eslint/utils" "5.59.1" + debug "^4.3.4" + grapheme-splitter "^1.0.4" + ignore "^5.2.0" + natural-compare-lite "^1.4.0" + semver "^7.3.7" tsutils "^3.21.0" -"@typescript-eslint/experimental-utils@4.33.0": - version "4.33.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-4.33.0.tgz#6f2a786a4209fa2222989e9380b5331b2810f7fd" - integrity sha512-zeQjOoES5JFjTnAhI5QY7ZviczMzDptls15GFsI6jyUOq0kOf9+WonkhtlIhh0RgHRnqj5gdNxW5j1EvAyYg6Q== +"@typescript-eslint/parser@^5.48.1": + version "5.59.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.59.1.tgz#73c2c12127c5c1182d2e5b71a8fa2a85d215cbb4" + integrity sha512-nzjFAN8WEu6yPRDizIFyzAfgK7nybPodMNFGNH0M9tei2gYnYszRDqVA0xlnRjkl7Hkx2vYrEdb6fP2a21cG1g== dependencies: - "@types/json-schema" "^7.0.7" - "@typescript-eslint/scope-manager" "4.33.0" - "@typescript-eslint/types" "4.33.0" - "@typescript-eslint/typescript-estree" "4.33.0" - eslint-scope "^5.1.1" - eslint-utils "^3.0.0" - -"@typescript-eslint/parser@^4.26.1": - version "4.33.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-4.33.0.tgz#dfe797570d9694e560528d18eecad86c8c744899" - integrity sha512-ZohdsbXadjGBSK0/r+d87X0SBmKzOq4/S5nzK6SBgJspFo9/CUDJ7hjayuze+JK7CZQLDMroqytp7pOcFKTxZA== - dependencies: - "@typescript-eslint/scope-manager" "4.33.0" - "@typescript-eslint/types" "4.33.0" - "@typescript-eslint/typescript-estree" "4.33.0" - debug "^4.3.1" - -"@typescript-eslint/scope-manager@4.33.0": - version "4.33.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-4.33.0.tgz#d38e49280d983e8772e29121cf8c6e9221f280a3" - integrity sha512-5IfJHpgTsTZuONKbODctL4kKuQje/bzBRkwHE8UOZ4f89Zeddg+EGZs8PD8NcN4LdM3ygHWYB3ukPAYjvl/qbQ== - dependencies: - "@typescript-eslint/types" "4.33.0" - "@typescript-eslint/visitor-keys" "4.33.0" - -"@typescript-eslint/types@4.33.0": - version "4.33.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-4.33.0.tgz#a1e59036a3b53ae8430ceebf2a919dc7f9af6d72" - integrity sha512-zKp7CjQzLQImXEpLt2BUw1tvOMPfNoTAfb8l51evhYbOEEzdWyQNmHWWGPR6hwKJDAi+1VXSBmnhL9kyVTTOuQ== - -"@typescript-eslint/typescript-estree@4.33.0": - version "4.33.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-4.33.0.tgz#0dfb51c2908f68c5c08d82aefeaf166a17c24609" - integrity sha512-rkWRY1MPFzjwnEVHsxGemDzqqddw2QbTJlICPD9p9I9LfsO8fdmfQPOX3uKfUaGRDFJbfrtm/sXhVXN4E+bzCA== - dependencies: - "@typescript-eslint/types" "4.33.0" - "@typescript-eslint/visitor-keys" "4.33.0" - debug "^4.3.1" - globby "^11.0.3" - is-glob "^4.0.1" - semver "^7.3.5" + "@typescript-eslint/scope-manager" "5.59.1" + "@typescript-eslint/types" "5.59.1" + "@typescript-eslint/typescript-estree" "5.59.1" + debug "^4.3.4" + +"@typescript-eslint/scope-manager@5.59.1": + version "5.59.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.59.1.tgz#8a20222719cebc5198618a5d44113705b51fd7fe" + integrity sha512-mau0waO5frJctPuAzcxiNWqJR5Z8V0190FTSqRw1Q4Euop6+zTwHAf8YIXNwDOT29tyUDrQ65jSg9aTU/H0omA== + dependencies: + "@typescript-eslint/types" "5.59.1" + "@typescript-eslint/visitor-keys" "5.59.1" + +"@typescript-eslint/type-utils@5.59.1": + version "5.59.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-5.59.1.tgz#63981d61684fd24eda2f9f08c0a47ecb000a2111" + integrity sha512-ZMWQ+Oh82jWqWzvM3xU+9y5U7MEMVv6GLioM3R5NJk6uvP47kZ7YvlgSHJ7ERD6bOY7Q4uxWm25c76HKEwIjZw== + dependencies: + "@typescript-eslint/typescript-estree" "5.59.1" + "@typescript-eslint/utils" "5.59.1" + debug "^4.3.4" + tsutils "^3.21.0" + +"@typescript-eslint/types@5.59.1": + version "5.59.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.59.1.tgz#03f3fedd1c044cb336ebc34cc7855f121991f41d" + integrity sha512-dg0ICB+RZwHlysIy/Dh1SP+gnXNzwd/KS0JprD3Lmgmdq+dJAJnUPe1gNG34p0U19HvRlGX733d/KqscrGC1Pg== + +"@typescript-eslint/typescript-estree@5.59.1": + version "5.59.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.59.1.tgz#4aa546d27fd0d477c618f0ca00b483f0ec84c43c" + integrity sha512-lYLBBOCsFltFy7XVqzX0Ju+Lh3WPIAWxYpmH/Q7ZoqzbscLiCW00LeYCdsUnnfnj29/s1WovXKh2gwCoinHNGA== + dependencies: + "@typescript-eslint/types" "5.59.1" + "@typescript-eslint/visitor-keys" "5.59.1" + debug "^4.3.4" + globby "^11.1.0" + is-glob "^4.0.3" + semver "^7.3.7" tsutils "^3.21.0" -"@typescript-eslint/visitor-keys@4.33.0": - version "4.33.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-4.33.0.tgz#2a22f77a41604289b7a186586e9ec48ca92ef1dd" - integrity sha512-uqi/2aSz9g2ftcHWf8uLPJA70rUv6yuMW5Bohw+bwcuzaxQIHaKFZCKGoGXIrc9vkTJ3+0txM73K0Hq3d5wgIg== +"@typescript-eslint/utils@5.59.1": + version "5.59.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-5.59.1.tgz#d89fc758ad23d2157cfae53f0b429bdf15db9473" + integrity sha512-MkTe7FE+K1/GxZkP5gRj3rCztg45bEhsd8HYjczBuYm+qFHP5vtZmjx3B0yUCDotceQ4sHgTyz60Ycl225njmA== + dependencies: + "@eslint-community/eslint-utils" "^4.2.0" + "@types/json-schema" "^7.0.9" + "@types/semver" "^7.3.12" + "@typescript-eslint/scope-manager" "5.59.1" + "@typescript-eslint/types" "5.59.1" + "@typescript-eslint/typescript-estree" "5.59.1" + eslint-scope "^5.1.1" + semver "^7.3.7" + +"@typescript-eslint/visitor-keys@5.59.1": + version "5.59.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.59.1.tgz#0d96c36efb6560d7fb8eb85de10442c10d8f6058" + integrity sha512-6waEYwBTCWryx0VJmP7JaM4FpipLsFl9CvYf2foAE8Qh/Y0s+bxWysciwOs0LTBED4JCaNxTZ5rGadB14M6dwA== dependencies: - "@typescript-eslint/types" "4.33.0" - eslint-visitor-keys "^2.0.0" + "@typescript-eslint/types" "5.59.1" + eslint-visitor-keys "^3.3.0" "@unimodules/core@*": version "7.1.2" @@ -2757,6 +2973,26 @@ expo-modules-autolinking "^0.0.3" invariant "^2.2.4" +"@yarnpkg/lockfile@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@yarnpkg/lockfile/-/lockfile-1.1.0.tgz#e77a97fbd345b76d83245edcd17d393b1b41fb31" + integrity sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ== + +"@yarnpkg/parsers@^3.0.0-rc.18": + version "3.0.0-rc.42" + resolved "https://registry.yarnpkg.com/@yarnpkg/parsers/-/parsers-3.0.0-rc.42.tgz#3814e90a81bb1f9c06cc83c6a009139c55efe94d" + integrity sha512-eW9Mbegmb5bJjwawJM9ghjUjUqciNMhC6L7XrQPF/clXS5bbP66MstsgCT5hy9VlfUh/CfBT+0Wucf531dMjHA== + dependencies: + js-yaml "^3.10.0" + tslib "^2.4.0" + +"@zkochan/js-yaml@0.0.6": + version "0.0.6" + resolved "https://registry.yarnpkg.com/@zkochan/js-yaml/-/js-yaml-0.0.6.tgz#975f0b306e705e28b8068a07737fa46d3fc04826" + integrity sha512-nzvgl3VfhcELQ8LyVrYOru+UtAy1nrygk2+AGbTm8a5YcO6o8lSjAT+pfg3vJWxIoZKOUhrK6UU7xW/+00kQrg== + dependencies: + argparse "^2.0.1" + JSONStream@^1.0.4: version "1.3.5" resolved "https://registry.yarnpkg.com/JSONStream/-/JSONStream-1.3.5.tgz#3208c1f08d3a4d99261ab64f92302bc15e111ca0" @@ -2765,16 +3001,16 @@ JSONStream@^1.0.4: jsonparse "^1.2.0" through ">=2.2.7 <3" -abab@^2.0.3, abab@^2.0.5: - version "2.0.6" - resolved "https://registry.yarnpkg.com/abab/-/abab-2.0.6.tgz#41b80f2c871d19686216b82309231cfd3cb3d291" - integrity sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA== - -abbrev@1: +abbrev@1, abbrev@^1.0.0: version "1.1.1" resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8" integrity sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q== +abbrev@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-2.0.0.tgz#cf59829b8b4f03f89dda2771cb7f3653828c89bf" + integrity sha512-6/mh1E2u2YgEsCHdY0Yx5oW+61gZU+1vXaoiHHrpKeuRNNgFvS+/jrwHiQhB5apAf5oB7UB7E19ol2R2LKH8hQ== + abort-controller@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/abort-controller/-/abort-controller-3.0.0.tgz#eaf54d53b62bae4138e809ca225c8439a6efb392" @@ -2795,38 +3031,20 @@ accepts@^1.3.7, accepts@~1.3.5, accepts@~1.3.7, accepts@~1.3.8: mime-types "~2.1.34" negotiator "0.6.3" -acorn-globals@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/acorn-globals/-/acorn-globals-6.0.0.tgz#46cdd39f0f8ff08a876619b55f5ac8a6dc770b45" - integrity sha512-ZQl7LOWaF5ePqqcX4hLuv/bLXYQNfNWw2c0/yX/TsPRKamzHcTGQnlCjHT3TsmkOUVEPS3crCxiPfdzE/Trlhg== - dependencies: - acorn "^7.1.1" - acorn-walk "^7.1.1" - -acorn-jsx@^5.3.1: +acorn-jsx@^5.3.2: version "5.3.2" resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937" integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== -acorn-walk@^7.1.1: - version "7.2.0" - resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-7.2.0.tgz#0de889a601203909b0fbe07b8938dc21d2e967bc" - integrity sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA== - acorn-walk@^8.1.1: version "8.2.0" resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.2.0.tgz#741210f2e2426454508853a2f44d0ab83b7f69c1" integrity sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA== -acorn@^7.1.1, acorn@^7.4.0: - version "7.4.1" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.4.1.tgz#feaed255973d2e77555b83dbc08851a6c63520fa" - integrity sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A== - -acorn@^8.2.4, acorn@^8.4.1: - version "8.8.0" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.8.0.tgz#88c0187620435c7f6015803f5539dae05a9dbea8" - integrity sha512-QOxyigPVrpZ2GXT+PFyZTl6TtOFc5egxHIP9IlQ+RbupQuX4RkT/Bee4/kQuC02Xkzg84JcT7oLYtDIQxp+v7w== +acorn@^8.4.1, acorn@^8.5.0, acorn@^8.8.0: + version "8.8.2" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.8.2.tgz#1b2f25db02af965399b9776b0c2c391276d37c4a" + integrity sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw== add-stream@^1.0.0: version "1.0.0" @@ -2840,13 +3058,13 @@ agent-base@6, agent-base@^6.0.2: dependencies: debug "4" -agentkeepalive@^4.1.3: - version "4.2.1" - resolved "https://registry.yarnpkg.com/agentkeepalive/-/agentkeepalive-4.2.1.tgz#a7975cbb9f83b367f06c90cc51ff28fe7d499717" - integrity sha512-Zn4cw2NEqd+9fiSVWMscnjyQ1a8Yfoc5oBajLeo5w+YBHgDUcEBY2hS4YpTz6iN5f/2zQiktcuM6tS8x1p9dpA== +agentkeepalive@^4.1.3, agentkeepalive@^4.2.1: + version "4.3.0" + resolved "https://registry.yarnpkg.com/agentkeepalive/-/agentkeepalive-4.3.0.tgz#bb999ff07412653c1803b3ced35e50729830a255" + integrity sha512-7Epl1Blf4Sy37j4v9f9FjICCh4+KAQOyXgHEwlyBiAQLbhKdq/i2QQU3amQalS/wPhdPzDXPL5DMR5bkn+YeWg== dependencies: debug "^4.1.0" - depd "^1.1.2" + depd "^2.0.0" humanize-ms "^1.2.1" aggregate-error@^3.0.0: @@ -2857,7 +3075,7 @@ aggregate-error@^3.0.0: clean-stack "^2.0.0" indent-string "^4.0.0" -ajv@^6.10.0, ajv@^6.12.3, ajv@^6.12.4: +ajv@^6.10.0, ajv@^6.12.4: version "6.12.6" resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g== @@ -2867,16 +3085,6 @@ ajv@^6.10.0, ajv@^6.12.3, ajv@^6.12.4: json-schema-traverse "^0.4.1" uri-js "^4.2.2" -ajv@^8.0.1: - version "8.11.0" - resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.11.0.tgz#977e91dd96ca669f54a11e23e378e33b884a565f" - integrity sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg== - dependencies: - fast-deep-equal "^3.1.1" - json-schema-traverse "^1.0.0" - require-from-string "^2.0.2" - uri-js "^4.2.2" - anser@^1.4.9: version "1.4.10" resolved "https://registry.yarnpkg.com/anser/-/anser-1.4.10.tgz#befa3eddf282684bd03b63dcda3927aef8c2e35b" @@ -2937,18 +3145,10 @@ ansi-styles@^5.0.0: resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-5.2.0.tgz#07449690ad45777d1924ac2abb2fc8895dba836b" integrity sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA== -anymatch@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-2.0.0.tgz#bcb24b4f37934d9aa7ac17b4adaf89e7c76ef2eb" - integrity sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw== - dependencies: - micromatch "^3.1.4" - normalize-path "^2.1.1" - anymatch@^3.0.3: - version "3.1.2" - resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.2.tgz#c0557c096af32f106198f4f4e2a383537e378716" - integrity sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg== + version "3.1.3" + resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.3.tgz#790c58b19ba1720a84205b57c618d5ad8524973e" + integrity sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw== dependencies: normalize-path "^3.0.0" picomatch "^2.0.4" @@ -2984,6 +3184,14 @@ are-we-there-yet@^3.0.0: delegates "^1.0.0" readable-stream "^3.6.0" +are-we-there-yet@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/are-we-there-yet/-/are-we-there-yet-4.0.0.tgz#3ff397dc14f08b52dd8b2a64d3cee154ab8760d2" + integrity sha512-nSXlV+u3vtVjRgihdTzbfWYzxPWGo424zPgQbHD0ZqIla3jqYAewDcvee0Ua2hjS5IfTAmjGlx1Jf0PKwjZDEw== + dependencies: + delegates "^1.0.0" + readable-stream "^4.1.0" + are-we-there-yet@~1.1.2: version "1.1.7" resolved "https://registry.yarnpkg.com/are-we-there-yet/-/are-we-there-yet-1.1.7.tgz#b15474a932adab4ff8a50d9adfa7e4e926f21146" @@ -3004,6 +3212,11 @@ argparse@^1.0.7: dependencies: sprintf-js "~1.0.2" +argparse@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38" + integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== + arr-diff@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/arr-diff/-/arr-diff-4.0.0.tgz#d6461074febfec71e7e15235761a329a5dc7c520" @@ -3029,16 +3242,19 @@ array-back@^4.0.1, array-back@^4.0.2: resolved "https://registry.yarnpkg.com/array-back/-/array-back-4.0.2.tgz#8004e999a6274586beeb27342168652fdb89fa1e" integrity sha512-NbdMezxqf94cnNfWLL7V/im0Ub+Anbb0IoZhvzie8+4HJ4nMQuzHuy49FkGYCJK2yAloZ3meiB6AVMClbrI1vg== +array-buffer-byte-length@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/array-buffer-byte-length/-/array-buffer-byte-length-1.0.0.tgz#fabe8bc193fea865f317fe7807085ee0dee5aead" + integrity sha512-LPuwb2P+NrQw3XhxGc36+XSvuBPopovXYTR9Ew++Du9Yb/bx5AzBfrIsBoj0EZUifjQU+sHL21sseZ3jerWO/A== + dependencies: + call-bind "^1.0.2" + is-array-buffer "^3.0.1" + array-differ@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/array-differ/-/array-differ-3.0.0.tgz#3cbb3d0f316810eafcc47624734237d6aee4ae6b" integrity sha512-THtfYS6KtME/yIAhKjZ2ul7XI96lQGHRputJQHO80LAWQnuGP4iCIN8vdMRboGbIEYBwU33q8Tch1os2+X0kMg== -array-filter@~0.0.0: - version "0.0.1" - resolved "https://registry.yarnpkg.com/array-filter/-/array-filter-0.0.1.tgz#7da8cf2e26628ed732803581fd21f67cacd2eeec" - integrity sha512-VW0FpCIhjZdarWjIz8Vpva7U95fl2Jn+b+mmFFMLn8PIVscOQcAgEznwUzTEuUHuqZqIxwzRlcaN/urTFFQoiw== - array-flatten@1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2" @@ -3049,26 +3265,24 @@ array-ify@^1.0.0: resolved "https://registry.yarnpkg.com/array-ify/-/array-ify-1.0.0.tgz#9e528762b4a9066ad163a6962a364418e9626ece" integrity sha512-c5AMf34bKdvPhQ7tBGhqkgKNUzMr4WUs+WDtC2ZUGOUncbxKMTvqxYctiseW3+L4bA8ec+GcZ6/A/FW4m8ukng== -array-includes@^3.1.4: - version "3.1.5" - resolved "https://registry.yarnpkg.com/array-includes/-/array-includes-3.1.5.tgz#2c320010db8d31031fd2a5f6b3bbd4b1aad31bdb" - integrity sha512-iSDYZMMyTPkiFasVqfuAQnWAYcvO/SeBSCGKePoEthjp4LEMTe4uLc7b025o4jAZpHhihh8xPo99TNWUWWkGDQ== +array-includes@^3.1.6: + version "3.1.6" + resolved "https://registry.yarnpkg.com/array-includes/-/array-includes-3.1.6.tgz#9e9e720e194f198266ba9e18c29e6a9b0e4b225f" + integrity sha512-sgTbLvL6cNnw24FnbaDyjmvddQ2ML8arZsgaJhoABMoplz/4QRhtrYS+alr1BUM1Bwp6dhx8vVCBSLG+StwOFw== dependencies: call-bind "^1.0.2" define-properties "^1.1.4" - es-abstract "^1.19.5" - get-intrinsic "^1.1.1" + es-abstract "^1.20.4" + get-intrinsic "^1.1.3" is-string "^1.0.7" -array-map@~0.0.0: - version "0.0.0" - resolved "https://registry.yarnpkg.com/array-map/-/array-map-0.0.0.tgz#88a2bab73d1cf7bcd5c1b118a003f66f665fa662" - integrity sha512-123XMszMB01QKVptpDQ7x1m1pP5NmJIG1kbl0JSPPRezvwQChxAN0Gvzo7rvR1IZ2tOL2tmiy7kY/KKgnpVVpg== - -array-reduce@~0.0.0: - version "0.0.0" - resolved "https://registry.yarnpkg.com/array-reduce/-/array-reduce-0.0.0.tgz#173899d3ffd1c7d9383e4479525dbe278cab5f2b" - integrity sha512-8jR+StqaC636u7h3ye1co3lQRefgVVUQUhuAmRbDqIMeR2yuXzRvkCNQiQ5J/wbREmoBLNtp13dhaaVpZQDRUw== +array-index@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/array-index/-/array-index-1.0.0.tgz#ec56a749ee103e4e08c790b9c353df16055b97f9" + integrity sha512-jesyNbBkLQgGZMSwA1FanaFjalb1mZUGxGeUEkSDidzgrbjBGhvizJkaItdhkt8eIHFOJC7nDsrXk+BaehTdRw== + dependencies: + debug "^2.2.0" + es6-symbol "^3.0.2" array-union@^2.1.0: version "2.1.0" @@ -3080,26 +3294,25 @@ array-unique@^0.3.2: resolved "https://registry.yarnpkg.com/array-unique/-/array-unique-0.3.2.tgz#a894b75d4bc4f6cd679ef3244a9fd8f46ae2d428" integrity sha512-SleRWjh9JUud2wH1hPs9rZBZ33H6T9HOiL0uwGnGx9FpE6wKGyfWugmbkEOIs6qWrZhg0LWeLziLrEwQJhs5mQ== -array.prototype.flat@^1.2.5: - version "1.3.0" - resolved "https://registry.yarnpkg.com/array.prototype.flat/-/array.prototype.flat-1.3.0.tgz#0b0c1567bf57b38b56b4c97b8aa72ab45e4adc7b" - integrity sha512-12IUEkHsAhA4DY5s0FPgNXIdc8VRSqD9Zp78a5au9abH/SOBrsp082JOWFNTjkMozh8mqcdiKuaLGhPeYztxSw== +array.prototype.flat@^1.3.1: + version "1.3.1" + resolved "https://registry.yarnpkg.com/array.prototype.flat/-/array.prototype.flat-1.3.1.tgz#ffc6576a7ca3efc2f46a143b9d1dda9b4b3cf5e2" + integrity sha512-roTU0KWIOmJ4DRLmwKd19Otg0/mT3qPNt0Qb3GWW8iObuZXxrjB/pzn0R3hqpRSWg4HCwqx+0vwOnWnvlOyeIA== dependencies: call-bind "^1.0.2" - define-properties "^1.1.3" - es-abstract "^1.19.2" + define-properties "^1.1.4" + es-abstract "^1.20.4" es-shim-unscopables "^1.0.0" -array.prototype.reduce@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/array.prototype.reduce/-/array.prototype.reduce-1.0.4.tgz#8167e80089f78bff70a99e20bd4201d4663b0a6f" - integrity sha512-WnM+AjG/DvLRLo4DDl+r+SvCzYtD2Jd9oeBYMcEaI7t3fFrHY9M53/wdLcTvmZNQ70IU6Htj0emFkZ5TS+lrdw== +array.prototype.flatmap@^1.3.1: + version "1.3.1" + resolved "https://registry.yarnpkg.com/array.prototype.flatmap/-/array.prototype.flatmap-1.3.1.tgz#1aae7903c2100433cb8261cd4ed310aab5c4a183" + integrity sha512-8UGn9O1FDVvMNB0UlLv4voxRMze7+FpHyF5mSMRjWHUMlpoDViniy05870VlxhfgTnLbpuwTzvD76MTtWxB/mQ== dependencies: call-bind "^1.0.2" - define-properties "^1.1.3" - es-abstract "^1.19.2" - es-array-method-boxes-properly "^1.0.0" - is-string "^1.0.7" + define-properties "^1.1.4" + es-abstract "^1.20.4" + es-shim-unscopables "^1.0.0" arrify@^1.0.1: version "1.0.1" @@ -3111,7 +3324,7 @@ arrify@^2.0.1: resolved "https://registry.yarnpkg.com/arrify/-/arrify-2.0.1.tgz#c9655e9331e0abcd588d2a7cad7e9956f66701fa" integrity sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug== -asap@^2.0.0, asap@~2.0.6: +asap@~2.0.6: version "2.0.6" resolved "https://registry.yarnpkg.com/asap/-/asap-2.0.6.tgz#e50347611d7e690943208bbdafebcbc2fb866d46" integrity sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA== @@ -3121,13 +3334,6 @@ asmcrypto.js@^0.22.0: resolved "https://registry.yarnpkg.com/asmcrypto.js/-/asmcrypto.js-0.22.0.tgz#38fc1440884d802c7bd37d1d23c2b26a5cd5d2d2" integrity sha512-usgMoyXjMbx/ZPdzTSXExhMPur2FTdz/Vo5PVx2gIaBcdAAJNOFlsdgqveM8Cff7W0v+xrf9BwjOV26JSAF9qA== -asn1@~0.2.3: - version "0.2.6" - resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.6.tgz#0d3a7bb6e64e02a90c0303b31f292868ea09a08d" - integrity sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ== - dependencies: - safer-buffer "~2.1.0" - asn1js@^3.0.1, asn1js@^3.0.5: version "3.0.5" resolved "https://registry.yarnpkg.com/asn1js/-/asn1js-3.0.5.tgz#5ea36820443dbefb51cc7f88a2ebb5b462114f38" @@ -3137,11 +3343,6 @@ asn1js@^3.0.1, asn1js@^3.0.5: pvutils "^1.1.3" tslib "^2.4.0" -assert-plus@1.0.0, assert-plus@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-1.0.0.tgz#f12e0f3c5d77b0b1cdd9146942e4e96c1e4dd525" - integrity sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw== - assign-symbols@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/assign-symbols/-/assign-symbols-1.0.0.tgz#59667f41fadd4f20ccbc2bb96b8d4f7f78ec0367" @@ -3159,29 +3360,22 @@ astral-regex@^1.0.0: resolved "https://registry.yarnpkg.com/astral-regex/-/astral-regex-1.0.0.tgz#6c8c3fb827dd43ee3918f27b82782ab7658a6fd9" integrity sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg== -astral-regex@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/astral-regex/-/astral-regex-2.0.0.tgz#483143c567aeed4785759c0865786dc77d7d2e31" - integrity sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ== - async-limiter@~1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/async-limiter/-/async-limiter-1.0.1.tgz#dd379e94f0db8310b08291f9d64c3209766617fd" integrity sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ== -async-mutex@^0.3.2: - version "0.3.2" - resolved "https://registry.yarnpkg.com/async-mutex/-/async-mutex-0.3.2.tgz#1485eda5bda1b0ec7c8df1ac2e815757ad1831df" - integrity sha512-HuTK7E7MT7jZEh1P9GtRW9+aTWiDWWi9InbZ5hjxrnRa39KS4BW04+xLBhYNS2aXhHUIKZSw3gj4Pn1pj+qGAA== +async-mutex@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/async-mutex/-/async-mutex-0.4.0.tgz#ae8048cd4d04ace94347507504b3cf15e631c25f" + integrity sha512-eJFZ1YhRR8UN8eBLoNzcDPcy/jqjsg6I1AP+KvWQX80BqOSW1oJPJXDylPUEeMr2ZQvHgnQ//Lp6f3RQ1zI7HA== dependencies: - tslib "^2.3.1" + tslib "^2.4.0" -async@^2.4.0: - version "2.6.4" - resolved "https://registry.yarnpkg.com/async/-/async-2.6.4.tgz#706b7ff6084664cd7eae713f6f965433b5504221" - integrity sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA== - dependencies: - lodash "^4.17.14" +async@^3.2.2, async@^3.2.3: + version "3.2.4" + resolved "https://registry.yarnpkg.com/async/-/async-3.2.4.tgz#2d22e00f8cddeb5fde5dd33522b56d1cf569a81c" + integrity sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ== asynckit@^0.4.0: version "0.4.0" @@ -3198,15 +3392,26 @@ atob@^2.1.2: resolved "https://registry.yarnpkg.com/atob/-/atob-2.1.2.tgz#6d9517eb9e030d2436666651e86bd9f6f13533c9" integrity sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg== -aws-sign2@~0.7.0: - version "0.7.0" - resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.7.0.tgz#b46e890934a9591f2d2f6f86d7e6a9f1b3fe76a8" - integrity sha512-08kcGqnYf/YmjoRhfxyu+CLxBjUtHLXLXX/vUfx9l2LYzG3c1m61nrpyFUZI6zeS+Li/wWMMidD9KgrqtGq3mA== +available-typed-arrays@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz#92f95616501069d07d10edb2fc37d3e1c65123b7" + integrity sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw== + +axios@^0.21.2: + version "0.21.4" + resolved "https://registry.yarnpkg.com/axios/-/axios-0.21.4.tgz#c67b90dc0568e5c1cf2b0b858c43ba28e2eda575" + integrity sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg== + dependencies: + follow-redirects "^1.14.0" -aws4@^1.8.0: - version "1.11.0" - resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.11.0.tgz#d61f46d83b2519250e2784daf5b09479a8b41c59" - integrity sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA== +axios@^1.0.0: + version "1.3.6" + resolved "https://registry.yarnpkg.com/axios/-/axios-1.3.6.tgz#1ace9a9fb994314b5f6327960918406fa92c6646" + integrity sha512-PEcdkk7JcdPiMDkvM4K6ZBRYq9keuVJsToxm2zQIM70Qqo2WHTdJZMXcG9X+RmRp2VPNUQC8W1RAGbgt6b1yMg== + dependencies: + follow-redirects "^1.15.0" + form-data "^4.0.0" + proxy-from-env "^1.1.0" b64-lite@^1.3.1, b64-lite@^1.4.0: version "1.4.0" @@ -3227,27 +3432,19 @@ babel-core@^7.0.0-bridge.0: resolved "https://registry.yarnpkg.com/babel-core/-/babel-core-7.0.0-bridge.0.tgz#95a492ddd90f9b4e9a4a1da14eb335b87b634ece" integrity sha512-poPX9mZH/5CSanm50Q+1toVci6pv5KSRv/5TWCwtzQS5XEwn40BcCrgIeMFWP9CKKIniKXNxoIOnOq4VVlGXhg== -babel-jest@^27.5.1: - version "27.5.1" - resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-27.5.1.tgz#a1bf8d61928edfefd21da27eb86a695bfd691444" - integrity sha512-cdQ5dXjGRd0IBRATiQ4mZGlGlRE8kJpjPOixdNRdT+m3UcNqmYWN6rK6nvtXYfY3D76cb8s/O1Ss8ea24PIwcg== +babel-jest@^29.5.0: + version "29.5.0" + resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-29.5.0.tgz#3fe3ddb109198e78b1c88f9ebdecd5e4fc2f50a5" + integrity sha512-mA4eCDh5mSo2EcA9xQjVTpmbbNk32Zb3Q3QFQsNhaK56Q+yoXowzFodLux30HRgyOho5rsQ6B0P9QpMkvvnJ0Q== dependencies: - "@jest/transform" "^27.5.1" - "@jest/types" "^27.5.1" + "@jest/transform" "^29.5.0" "@types/babel__core" "^7.1.14" babel-plugin-istanbul "^6.1.1" - babel-preset-jest "^27.5.1" + babel-preset-jest "^29.5.0" chalk "^4.0.0" graceful-fs "^4.2.9" slash "^3.0.0" -babel-plugin-dynamic-import-node@^2.3.3: - version "2.3.3" - resolved "https://registry.yarnpkg.com/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.3.tgz#84fda19c976ec5c6defef57f9427b3def66e17a3" - integrity sha512-jZVI+s9Zg3IqA/kdi0i6UDCybUI3aSBLnglhYbSSjKlV7yF1F/5LWv8MakQmvYpnbJDS6fcBL2KzHSxNCMtWSQ== - dependencies: - object.assign "^4.1.0" - babel-plugin-istanbul@^6.1.1: version "6.1.1" resolved "https://registry.yarnpkg.com/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz#fa88ec59232fd9b4e36dbbc540a8ec9a9b47da73" @@ -3259,14 +3456,14 @@ babel-plugin-istanbul@^6.1.1: istanbul-lib-instrument "^5.0.4" test-exclude "^6.0.0" -babel-plugin-jest-hoist@^27.5.1: - version "27.5.1" - resolved "https://registry.yarnpkg.com/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-27.5.1.tgz#9be98ecf28c331eb9f5df9c72d6f89deb8181c2e" - integrity sha512-50wCwD5EMNW4aRpOwtqzyZHIewTYNxLA4nhB+09d8BIssfNfzBRhkBIHiaPv1Si226TQSvp8gxAJm2iY2qs2hQ== +babel-plugin-jest-hoist@^29.5.0: + version "29.5.0" + resolved "https://registry.yarnpkg.com/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.5.0.tgz#a97db437936f441ec196990c9738d4b88538618a" + integrity sha512-zSuuuAlTMT4mzLj2nPnUm6fsE6270vdOfnpbJ+RmruU75UhLFvL0N2NgI7xpeS7NaB6hGqmd5pVpGTDYvi4Q3w== dependencies: "@babel/template" "^7.3.3" "@babel/types" "^7.3.3" - "@types/babel__core" "^7.0.0" + "@types/babel__core" "^7.1.14" "@types/babel__traverse" "^7.0.6" babel-plugin-polyfill-corejs2@^0.3.3: @@ -3316,7 +3513,7 @@ babel-preset-current-node-syntax@^1.0.0: "@babel/plugin-syntax-optional-chaining" "^7.8.3" "@babel/plugin-syntax-top-level-await" "^7.8.3" -babel-preset-fbjs@^3.3.0: +babel-preset-fbjs@^3.4.0: version "3.4.0" resolved "https://registry.yarnpkg.com/babel-preset-fbjs/-/babel-preset-fbjs-3.4.0.tgz#38a14e5a7a3b285a3f3a86552d650dca5cf6111c" integrity sha512-9ywCsCvo1ojrw0b+XYk7aFvTH6D9064t0RIL1rtMf3nsa02Xw41MS7sZw216Im35xj/UY0PDBQsa1brUDDF1Ow== @@ -3349,12 +3546,12 @@ babel-preset-fbjs@^3.3.0: "@babel/plugin-transform-template-literals" "^7.0.0" babel-plugin-syntax-trailing-function-commas "^7.0.0-beta.0" -babel-preset-jest@^27.5.1: - version "27.5.1" - resolved "https://registry.yarnpkg.com/babel-preset-jest/-/babel-preset-jest-27.5.1.tgz#91f10f58034cb7989cb4f962b69fa6eef6a6bc81" - integrity sha512-Nptf2FzlPCWYuJg41HBqXVT8ym6bXOevuCTbhxlUpjwtysGaIWFvDEjp4y+G7fl13FgOdjs7P/DmErqH7da0Ag== +babel-preset-jest@^29.5.0: + version "29.5.0" + resolved "https://registry.yarnpkg.com/babel-preset-jest/-/babel-preset-jest-29.5.0.tgz#57bc8cc88097af7ff6a5ab59d1cd29d52a5916e2" + integrity sha512-JOMloxOqdiBSxMAzjRaH023/vvcaSaec49zvg+2LmNsktC7ei39LTJGw02J+9uUtTZUq6xbLyJ4dxe9sSmIuAg== dependencies: - babel-plugin-jest-hoist "^27.5.1" + babel-plugin-jest-hoist "^29.5.0" babel-preset-current-node-syntax "^1.0.0" balanced-match@^1.0.0: @@ -3374,7 +3571,7 @@ base-x@^3.0.2: dependencies: safe-buffer "^5.0.1" -base64-js@*, base64-js@^1.1.2, base64-js@^1.3.0, base64-js@^1.3.1, base64-js@^1.5.1: +base64-js@*, base64-js@^1.1.2, base64-js@^1.3.0, base64-js@^1.3.1: version "1.5.1" resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== @@ -3392,27 +3589,40 @@ base@^0.11.1: mixin-deep "^1.2.0" pascalcase "^0.1.1" -bcrypt-pbkdf@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz#a4301d389b6a43f9b67ff3ca11a3f6637e360e9e" - integrity sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w== - dependencies: - tweetnacl "^0.14.3" +bech32@^1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/bech32/-/bech32-1.1.4.tgz#e38c9f37bf179b8eb16ae3a772b40c356d4832e9" + integrity sha512-s0IrSOzLlbvX7yp4WBfPITzpAU8sqQcpsmwXDiKwrG4r491vwCO/XpejasRNl0piBMe/DvP4Tz0mIS/X1DPJBQ== + +bech32@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/bech32/-/bech32-2.0.0.tgz#078d3686535075c8c79709f054b1b226a133b355" + integrity sha512-LcknSilhIGatDAsY1ak2I8VtGaHNhgMSYVxFrGLXv+xLHytaKZKcaUJJUE7qmBr7h33o5YQwP55pMI0xmkpJwg== before-after-hook@^2.2.0: - version "2.2.2" - resolved "https://registry.yarnpkg.com/before-after-hook/-/before-after-hook-2.2.2.tgz#a6e8ca41028d90ee2c24222f201c90956091613e" - integrity sha512-3pZEU3NT5BFUo/AD5ERPWOgQOCZITni6iavr5AUw5AUwQjMlI0kzu5btnyD39AF0gUEsDPwJT+oY1ORBJijPjQ== + version "2.2.3" + resolved "https://registry.yarnpkg.com/before-after-hook/-/before-after-hook-2.2.3.tgz#c51e809c81a4e354084422b9b26bad88249c517c" + integrity sha512-NzUnlZexiaH/46WDhANlyR2bXRopNg4F/zuSA3OpZnllCUgRaOF2znDioDWrmbNVsuZk6l9pMquQB38cfBZwkQ== -big-integer@1.6.x: +big-integer@^1.6.51: version "1.6.51" resolved "https://registry.yarnpkg.com/big-integer/-/big-integer-1.6.51.tgz#0df92a5d9880560d3ff2d5fd20245c889d130686" integrity sha512-GPEid2Y9QU1Exl1rpO9B2IPJGHPSupF5GnVIP0blYvNOMer2bTvSWs1jGOUg04hTmu67nmLsQ9TBo1puaotBHg== bignumber.js@^9.0.0: - version "9.1.0" - resolved "https://registry.yarnpkg.com/bignumber.js/-/bignumber.js-9.1.0.tgz#8d340146107fe3a6cb8d40699643c302e8773b62" - integrity sha512-4LwHK4nfDOraBCtst+wOWIHbu1vhvAPJK8g8nROd4iuc3PSEjWif/qwbkh8jwCJz6yDBvtU4KPynETgrfh7y3A== + version "9.1.1" + resolved "https://registry.yarnpkg.com/bignumber.js/-/bignumber.js-9.1.1.tgz#c4df7dc496bd849d4c9464344c1aa74228b4dac6" + integrity sha512-pHm4LsMJ6lzgNGVfZHjMoO8sdoRhOzOH4MLmY65Jg70bpxCKu5iOHNJyfF6OyvYw7t8Fpf35RuzUyqnQsj8Vig== + +bin-links@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/bin-links/-/bin-links-4.0.1.tgz#afeb0549e642f61ff889b58ea2f8dca78fb9d8d3" + integrity sha512-bmFEM39CyX336ZGGRsGPlc6jZHriIoHacOQcTt72MktIjpPhZoP4te2jOyUXF3BLILmJ8aNLncoPVeIIFlrDeA== + dependencies: + cmd-shim "^6.0.0" + npm-normalize-package-bin "^3.0.0" + read-cmd-shim "^4.0.0" + write-file-atomic "^5.0.0" bindings@^1.3.1: version "1.5.0" @@ -3421,15 +3631,29 @@ bindings@^1.3.1: dependencies: file-uri-to-path "1.0.0" -bn.js@^5.2.0: +bl@^4.0.3, bl@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/bl/-/bl-4.1.0.tgz#451535264182bec2fbbc83a62ab98cf11d9f7b3a" + integrity sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w== + dependencies: + buffer "^5.5.0" + inherits "^2.0.4" + readable-stream "^3.4.0" + +bn.js@^4.11.9: + version "4.12.0" + resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.12.0.tgz#775b3f278efbb9718eec7361f483fb36fbbfea88" + integrity sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA== + +bn.js@^5.2.0, bn.js@^5.2.1: version "5.2.1" resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-5.2.1.tgz#0bc527a6a0d18d0aa8d5b0538ce4a77dccfa7b70" integrity sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ== -body-parser@1.20.0: - version "1.20.0" - resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.20.0.tgz#3de69bd89011c11573d7bfee6a64f11b6bd27cc5" - integrity sha512-DfJ+q6EPcGKZD1QWUjSpqp+Q7bDQTsQIF4zfUAtZ6qk+H/3/QRhg9CEp39ss+/T2vw0+HaidC0ecJj/DRLIaKg== +body-parser@1.20.1: + version "1.20.1" + resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.20.1.tgz#b1812a8912c195cd371a3ee5e66faa2338a5c668" + integrity sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw== dependencies: bytes "3.1.2" content-type "~1.0.4" @@ -3439,7 +3663,7 @@ body-parser@1.20.0: http-errors "2.0.0" iconv-lite "0.4.24" on-finished "2.4.1" - qs "6.10.3" + qs "6.11.0" raw-body "2.5.1" type-is "~1.6.18" unpipe "1.0.0" @@ -3457,20 +3681,6 @@ borc@^3.0.0: json-text-sequence "~0.3.0" readable-stream "^3.6.0" -bplist-creator@0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/bplist-creator/-/bplist-creator-0.1.0.tgz#018a2d1b587f769e379ef5519103730f8963ba1e" - integrity sha512-sXaHZicyEEmY86WyueLTQesbeoH/mquvarJaQNbjuOQO+7gbFcDEWqKmcWA4cOTLzFlfgvkiVxolk1k5bBIpmg== - dependencies: - stream-buffers "2.2.x" - -bplist-parser@0.3.1: - version "0.3.1" - resolved "https://registry.yarnpkg.com/bplist-parser/-/bplist-parser-0.3.1.tgz#e1c90b2ca2a9f9474cc72f6862bbf3fee8341fd1" - integrity sha512-PyJxiNtA5T2PlLIeBot4lbp7rj4OadzjnMZD/G5zuBNt8ei/yCU7+wW0h2bag9vr8c+/WuRWmSxbqAl9hL1rBA== - dependencies: - big-integer "1.6.x" - brace-expansion@^1.1.7: version "1.1.11" resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" @@ -3479,6 +3689,13 @@ brace-expansion@^1.1.7: balanced-match "^1.0.0" concat-map "0.0.1" +brace-expansion@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-2.0.1.tgz#1edc459e0f0c548486ecf9fc99f2221364b9a0ae" + integrity sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA== + dependencies: + balanced-match "^1.0.0" + braces@^2.3.1: version "2.3.2" resolved "https://registry.yarnpkg.com/braces/-/braces-2.3.2.tgz#5979fd3f14cd531565e5fa2df1abfff1dfaee729" @@ -3502,20 +3719,20 @@ braces@^3.0.2: dependencies: fill-range "^7.0.1" -browser-process-hrtime@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz#3c9b4b7d782c8121e56f10106d84c0d0ffc94626" - integrity sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow== +brorand@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/brorand/-/brorand-1.1.0.tgz#12c25efe40a45e3c323eb8675a0a0ce57b22371f" + integrity sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w== -browserslist@^4.21.3, browserslist@^4.21.4: - version "4.21.4" - resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.21.4.tgz#e7496bbc67b9e39dd0f98565feccdcb0d4ff6987" - integrity sha512-CBHJJdDmgjl3daYjN5Cp5kbTf1mUhZoS+beLklHIvkOWscs83YAhLlF3Wsh/lciQYAcbBJgTOD44VtG31ZM4Hw== +browserslist@^4.21.3, browserslist@^4.21.5: + version "4.21.5" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.21.5.tgz#75c5dae60063ee641f977e00edd3cfb2fb7af6a7" + integrity sha512-tUkiguQGW7S3IhB7N+c2MV/HZPSCPAAiYBZXLsBhFB/PCy6ZKKsZrmBayHV9fdGV/ARIfJ14NkxKzRDjvp7L6w== dependencies: - caniuse-lite "^1.0.30001400" - electron-to-chromium "^1.4.251" - node-releases "^2.0.6" - update-browserslist-db "^1.0.9" + caniuse-lite "^1.0.30001449" + electron-to-chromium "^1.4.284" + node-releases "^2.0.8" + update-browserslist-db "^1.0.10" bs-logger@0.x: version "0.2.6" @@ -3543,7 +3760,15 @@ buffer-from@^1.0.0: resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5" integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ== -buffer@^6.0.0, buffer@^6.0.2, buffer@^6.0.3: +buffer@^5.5.0: + version "5.7.1" + resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.7.1.tgz#ba62e7c13133053582197160851a8f648e99eed0" + integrity sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ== + dependencies: + base64-js "^1.3.1" + ieee754 "^1.1.13" + +buffer@^6.0.0, buffer@^6.0.3: version "6.0.3" resolved "https://registry.yarnpkg.com/buffer/-/buffer-6.0.3.tgz#2ace578459cc8fbe2a70aaa8f52ee63b6a74c6c6" integrity sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA== @@ -3556,15 +3781,17 @@ builtins@^1.0.3: resolved "https://registry.yarnpkg.com/builtins/-/builtins-1.0.3.tgz#cb94faeb61c8696451db36534e1422f94f0aee88" integrity sha512-uYBjakWipfaO/bXI7E8rq6kpwHRZK5cNYrUv2OzZSI/FvmdMyXJ2tG9dKcjEC5YHmHpUAwsargWIZNWdxb/bnQ== -byline@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/byline/-/byline-5.0.0.tgz#741c5216468eadc457b03410118ad77de8c1ddb1" - integrity sha512-s6webAy+R4SR8XVuJWt2V2rGvhnrhxN+9S15GNuTK3wKPOXFF6RNc+8ug2XhH+2s4f+uudG4kUVYmYOQWL2g0Q== +builtins@^5.0.0: + version "5.0.1" + resolved "https://registry.yarnpkg.com/builtins/-/builtins-5.0.1.tgz#87f6db9ab0458be728564fa81d876d8d74552fa9" + integrity sha512-qwVpFEHNfhYJIzNRBvd2C1kyo6jz3ZSMPyyuR47OPdiKWlbYnZNyDWuyR175qDnAJLiCo5fBBqPb3RiXgWlkOQ== + dependencies: + semver "^7.0.0" -byte-size@^7.0.0: - version "7.0.1" - resolved "https://registry.yarnpkg.com/byte-size/-/byte-size-7.0.1.tgz#b1daf3386de7ab9d706b941a748dbfc71130dee3" - integrity sha512-crQdqyCwhokxwV1UyDzLZanhkugAgft7vt0qbbdt60C6Zf3CAiGmtUCylbtYwrU6loOUw3euGrNtW1J651ot1A== +byte-size@7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/byte-size/-/byte-size-7.0.0.tgz#36528cd1ca87d39bd9abd51f5715dc93b6ceb032" + integrity sha512-NNiBxKgxybMBtWdmvx7ZITJi4ZG+CYUgwOSZTfqB1qogkRHrhbQE/R2r5Fh94X+InN5MCYz6SvB/ejHMj/HbsQ== bytes@3.0.0: version "3.0.0" @@ -3576,7 +3803,7 @@ bytes@3.1.2: resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.2.tgz#8b0beeb98605adf1b128fa4386403c009e0221a5" integrity sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg== -cacache@^15.0.5, cacache@^15.2.0: +cacache@^15.2.0: version "15.3.0" resolved "https://registry.yarnpkg.com/cacache/-/cacache-15.3.0.tgz#dc85380fb2f556fe3dda4c719bfa0ec875a7f1eb" integrity sha512-VVdYzXEn+cnbXpFgWs5hTT7OScegHVmLhJIR8Ufqk3iFD6A6j5iSX1KuBTfNEv4tdJWE2PzA6IVFtcLC7fN9wQ== @@ -3600,6 +3827,49 @@ cacache@^15.0.5, cacache@^15.2.0: tar "^6.0.2" unique-filename "^1.1.1" +cacache@^16.0.0, cacache@^16.1.0: + version "16.1.3" + resolved "https://registry.yarnpkg.com/cacache/-/cacache-16.1.3.tgz#a02b9f34ecfaf9a78c9f4bc16fceb94d5d67a38e" + integrity sha512-/+Emcj9DAXxX4cwlLmRI9c166RuL3w30zp4R7Joiv2cQTtTtA+jeuCAjH3ZlGnYS3tKENSrKhAzVVP9GVyzeYQ== + dependencies: + "@npmcli/fs" "^2.1.0" + "@npmcli/move-file" "^2.0.0" + chownr "^2.0.0" + fs-minipass "^2.1.0" + glob "^8.0.1" + infer-owner "^1.0.4" + lru-cache "^7.7.1" + minipass "^3.1.6" + minipass-collect "^1.0.2" + minipass-flush "^1.0.5" + minipass-pipeline "^1.2.4" + mkdirp "^1.0.4" + p-map "^4.0.0" + promise-inflight "^1.0.1" + rimraf "^3.0.2" + ssri "^9.0.0" + tar "^6.1.11" + unique-filename "^2.0.0" + +cacache@^17.0.0, cacache@^17.0.4: + version "17.0.5" + resolved "https://registry.yarnpkg.com/cacache/-/cacache-17.0.5.tgz#6dbec26c11f1f6a2b558bc11ed3316577c339ebc" + integrity sha512-Y/PRQevNSsjAPWykl9aeGz8Pr+OI6BYM9fYDNMvOkuUiG9IhG4LEmaYrZZZvioMUEQ+cBCxT0v8wrnCURccyKA== + dependencies: + "@npmcli/fs" "^3.1.0" + fs-minipass "^3.0.0" + glob "^9.3.1" + lru-cache "^7.7.1" + minipass "^4.0.0" + minipass-collect "^1.0.2" + minipass-flush "^1.0.5" + minipass-pipeline "^1.2.4" + p-map "^4.0.0" + promise-inflight "^1.0.1" + ssri "^10.0.0" + tar "^6.1.11" + unique-filename "^3.0.0" + cache-base@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/cache-base/-/cache-base-1.0.1.tgz#0a7f46416831c8b662ee36fe4e7c59d76f666ab2" @@ -3666,29 +3936,30 @@ camelcase@^6.0.0, camelcase@^6.2.0: resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.3.0.tgz#5685b95eb209ac9c0c177467778c9c84df58ba9a" integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA== -caniuse-lite@^1.0.30001400: - version "1.0.30001412" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001412.tgz#30f67d55a865da43e0aeec003f073ea8764d5d7c" - integrity sha512-+TeEIee1gS5bYOiuf+PS/kp2mrXic37Hl66VY6EAfxasIk5fELTktK2oOezYed12H8w7jt3s512PpulQidPjwA== +caniuse-lite@^1.0.30001449: + version "1.0.30001481" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001481.tgz#f58a717afe92f9e69d0e35ff64df596bfad93912" + integrity sha512-KCqHwRnaa1InZBtqXzP98LPg0ajCVujMKjqKDhZEthIpAsJl/YEIa3YvXjGXPVqzZVguccuu7ga9KOE1J9rKPQ== canonicalize@^1.0.1: version "1.0.8" resolved "https://registry.yarnpkg.com/canonicalize/-/canonicalize-1.0.8.tgz#24d1f1a00ed202faafd9bf8e63352cd4450c6df1" integrity sha512-0CNTVCLZggSh7bc5VkX5WWPWO+cyZbNd07IHIsSXLia/eAq+r836hgk+8BKoEh7949Mda87VUOitx5OddVj64A== -capture-exit@^2.0.0: +canonicalize@^2.0.0: version "2.0.0" - resolved "https://registry.yarnpkg.com/capture-exit/-/capture-exit-2.0.0.tgz#fb953bfaebeb781f62898239dabb426d08a509a4" - integrity sha512-PiT/hQmTonHhl/HFGN+Lx3JJUznrVYJ3+AQsnthneZbvW7x+f08Tk7yLJTLEOUvBTbduLeeBkxEaYXUOUrRq6g== - dependencies: - rsvp "^4.8.4" + resolved "https://registry.yarnpkg.com/canonicalize/-/canonicalize-2.0.0.tgz#32be2cef4446d67fd5348027a384cae28f17226a" + integrity sha512-ulDEYPv7asdKvqahuAY35c1selLdzDwHqugK92hfkzvlDCwXRRelDkR+Er33md/PtnpqHemgkuDPanZ4fiYZ8w== -caseless@~0.12.0: - version "0.12.0" - resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc" - integrity sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw== +chalk@4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.0.tgz#4e14870a618d9e2edd97dd8345fd9d9dc315646a" + integrity sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A== + dependencies: + ansi-styles "^4.1.0" + supports-color "^7.1.0" -chalk@^2.0.0, chalk@^2.0.1, chalk@^2.4.2: +chalk@^2.0.0, chalk@^2.4.2: version "2.4.2" resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== @@ -3697,15 +3968,7 @@ chalk@^2.0.0, chalk@^2.0.1, chalk@^2.4.2: escape-string-regexp "^1.0.5" supports-color "^5.3.0" -chalk@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-3.0.0.tgz#3f73c2bf526591f574cc492c51e2456349f844e4" - integrity sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg== - dependencies: - ansi-styles "^4.1.0" - supports-color "^7.1.0" - -chalk@^4.0.0, chalk@^4.1.0: +chalk@^4.0.0, chalk@^4.0.2, chalk@^4.1.0, chalk@^4.1.1, chalk@^4.1.2: version "4.1.2" resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== @@ -3739,16 +4002,16 @@ ci-info@^2.0.0: integrity sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ== ci-info@^3.2.0: - version "3.4.0" - resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-3.4.0.tgz#b28484fd436cbc267900364f096c9dc185efb251" - integrity sha512-t5QdPT5jq3o262DOQ8zA6E1tlH2upmUc4Hlvrbx1pGYJuiiHl7O7rvVNI+l8HTVhd/q3Qc9vqimkNk5yiXsAug== + version "3.8.0" + resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-3.8.0.tgz#81408265a5380c929f0bc665d62256628ce9ef91" + integrity sha512-eXTggHWSooYhq49F2opQhuHWgzucfF2YgODK4e1566GQs5BIfP30B0oenwBJHfWxAs2fyPB1s7Mg949zLf61Yw== cjs-module-lexer@^1.0.0: version "1.2.2" resolved "https://registry.yarnpkg.com/cjs-module-lexer/-/cjs-module-lexer-1.2.2.tgz#9f84ba3244a512f3a54e5277e8eef4c489864e40" integrity sha512-cOU9usZw8/dXIXKtwa8pM0OTJQuJkxMN6w30csNRUerHfeQ5R6U3kkU/FtJeIf3M202OHfY2U8ccInBG7/xogA== -class-transformer@0.5.1: +class-transformer@0.5.1, class-transformer@^0.5.1: version "0.5.1" resolved "https://registry.yarnpkg.com/class-transformer/-/class-transformer-0.5.1.tgz#24147d5dffd2a6cea930a3250a677addf96ab336" integrity sha512-SQa1Ws6hUbfC98vKGxZH3KFY0Y1lm5Zm0SY8XX9zbK7FJCyVEac3ATW0RIpwzW+oOfmHE5PMPufDG9hCfoEOMw== @@ -3763,14 +4026,14 @@ class-utils@^0.3.5: isobject "^3.0.0" static-extend "^0.1.1" -class-validator@0.13.1: - version "0.13.1" - resolved "https://registry.yarnpkg.com/class-validator/-/class-validator-0.13.1.tgz#381b2001ee6b9e05afd133671fbdf760da7dec67" - integrity sha512-zWIeYFhUitvAHBwNhDdCRK09hWx+P0HUwFE8US8/CxFpMVzkUK8RJl7yOIE+BVu2lxyPNgeOaFv78tLE47jBIg== +class-validator@0.14.0, class-validator@^0.14.0: + version "0.14.0" + resolved "https://registry.yarnpkg.com/class-validator/-/class-validator-0.14.0.tgz#40ed0ecf3c83b2a8a6a320f4edb607be0f0df159" + integrity sha512-ct3ltplN8I9fOwUd8GrP8UQixwff129BkEtuWDKL5W45cQuLd19xqmTLu5ge78YDm/fdje6FMt0hGOhl0lii3A== dependencies: - "@types/validator" "^13.1.3" - libphonenumber-js "^1.9.7" - validator "^13.5.2" + "@types/validator" "^13.7.10" + libphonenumber-js "^1.10.14" + validator "^13.7.0" clean-stack@^2.0.0: version "2.2.0" @@ -3782,24 +4045,22 @@ clear@^0.1.0: resolved "https://registry.yarnpkg.com/clear/-/clear-0.1.0.tgz#b81b1e03437a716984fd7ac97c87d73bdfe7048a" integrity sha512-qMjRnoL+JDPJHeLePZJuao6+8orzHMGP04A8CdwCNsKhRbOnKRjefxONR7bwILT3MHecxKBjHkKL/tkZ8r4Uzw== -cli-cursor@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-2.1.0.tgz#b35dac376479facc3e94747d41d0d0f5238ffcb5" - integrity sha512-8lgKz8LmCRYZZQDpRyT2m5rKJ08TnU4tR9FFFW2rxpxR1FzWi4PQ/NfyODchAatHaUgnSPVcx/R5w6NuTBzFiw== - dependencies: - restore-cursor "^2.0.0" - -cli-cursor@^3.1.0: +cli-cursor@3.1.0, cli-cursor@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-3.1.0.tgz#264305a7ae490d1d03bf0c9ba7c925d1753af307" integrity sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw== dependencies: restore-cursor "^3.1.0" -cli-spinners@^2.0.0: - version "2.7.0" - resolved "https://registry.yarnpkg.com/cli-spinners/-/cli-spinners-2.7.0.tgz#f815fd30b5f9eaac02db604c7a231ed7cb2f797a" - integrity sha512-qu3pN8Y3qHNgE2AFweciB1IfMnmZ/fsNTEE+NOFjmGB2F/7rLhnhzppvpCnN4FovtP26k8lHyy9ptEbNwWFLzw== +cli-spinners@2.6.1: + version "2.6.1" + resolved "https://registry.yarnpkg.com/cli-spinners/-/cli-spinners-2.6.1.tgz#adc954ebe281c37a6319bfa401e6dd2488ffb70d" + integrity sha512-x/5fWmGMnbKQAaNwN+UZlV79qBLM9JFnJuJ03gIi5whrob0xV0ofNVHy9DhwGdsMJQc2OKv0oGmLzvaqvAVv+g== + +cli-spinners@^2.5.0: + version "2.8.0" + resolved "https://registry.yarnpkg.com/cli-spinners/-/cli-spinners-2.8.0.tgz#e97a3e2bd00e6d85aa0c13d7f9e3ce236f7787fc" + integrity sha512-/eG5sJcvEIwxcdYM86k5tPwn0MUzkX5YY3eImTGpJOZgVe4SdTMY14vQpcxgBzJ0wXwAYrS8E+c3uHeK4JNyzQ== cli-width@^3.0.0: version "3.0.0" @@ -3824,7 +4085,16 @@ cliui@^7.0.2: strip-ansi "^6.0.0" wrap-ansi "^7.0.0" -clone-deep@^4.0.1: +cliui@^8.0.1: + version "8.0.1" + resolved "https://registry.yarnpkg.com/cliui/-/cliui-8.0.1.tgz#0c04b075db02cbfe60dc8e6cf2f5486b1a3608aa" + integrity sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ== + dependencies: + string-width "^4.2.0" + strip-ansi "^6.0.1" + wrap-ansi "^7.0.0" + +clone-deep@4.0.1, clone-deep@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/clone-deep/-/clone-deep-4.0.1.tgz#c19fd9bdbbf85942b4fd979c84dcf7d5f07c2387" integrity sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ== @@ -3833,18 +4103,28 @@ clone-deep@^4.0.1: kind-of "^6.0.2" shallow-clone "^3.0.0" +clone@2.x: + version "2.1.2" + resolved "https://registry.yarnpkg.com/clone/-/clone-2.1.2.tgz#1b7f4b9f591f1e8f83670401600345a02887435f" + integrity sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w== + clone@^1.0.2: version "1.0.4" resolved "https://registry.yarnpkg.com/clone/-/clone-1.0.4.tgz#da309cc263df15994c688ca902179ca3c7cd7c7e" integrity sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg== -cmd-shim@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/cmd-shim/-/cmd-shim-4.1.0.tgz#b3a904a6743e9fede4148c6f3800bf2a08135bdd" - integrity sha512-lb9L7EM4I/ZRVuljLPEtUJOP+xiQVknZ4ZMpMgEp4JzNldPb27HU03hi6K1/6CoIuit/Zm/LQXySErFeXxDprw== +cmd-shim@5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/cmd-shim/-/cmd-shim-5.0.0.tgz#8d0aaa1a6b0708630694c4dbde070ed94c707724" + integrity sha512-qkCtZ59BidfEwHltnJwkyVZn+XQojdAySM1D1gSeh11Z4pW1Kpolkyo53L5noc0nrxmIvyFwTmJRo4xs7FFLPw== dependencies: mkdirp-infer-owner "^2.0.0" +cmd-shim@^6.0.0: + version "6.0.1" + resolved "https://registry.yarnpkg.com/cmd-shim/-/cmd-shim-6.0.1.tgz#a65878080548e1dca760b3aea1e21ed05194da9d" + integrity sha512-S9iI9y0nKR4hwEQsVWpyxld/6kRfGepGfzff83FcaiEBpmvlbA2nnGe7Cylgrx2f/p1P5S5wpRm9oL8z1PbS3Q== + co@^4.6.0: version "4.6.0" resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184" @@ -3902,12 +4182,7 @@ colorette@^1.0.7: resolved "https://registry.yarnpkg.com/colorette/-/colorette-1.4.0.tgz#5190fbb87276259a86ad700bff2c6d6faa3fca40" integrity sha512-Y2oEozpomLn7Q3HFP7dpww7AtMJplbM9lGZP6RDfHqmbeRjiwRg4n6VM6j4KLmRke85uWEI7JqF17f3pqdRA0g== -colors@^1.1.2: - version "1.4.0" - resolved "https://registry.yarnpkg.com/colors/-/colors-1.4.0.tgz#c50491479d4c1bdaed2c9ced32cf7c7dc2360f78" - integrity sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA== - -columnify@^1.5.4: +columnify@1.6.0: version "1.6.0" resolved "https://registry.yarnpkg.com/columnify/-/columnify-1.6.0.tgz#6989531713c9008bb29735e61e37acf5bd553cf3" integrity sha512-lomjuFZKfM6MSAnV9aCZC9sc0qGbmZdfygNv+nCpqVkSKdCxCklLtd16O0EILGkImHw9ZpHkAnHaB+8Zxq5W6Q== @@ -3915,7 +4190,7 @@ columnify@^1.5.4: strip-ansi "^6.0.1" wcwidth "^1.0.0" -combined-stream@^1.0.6, combined-stream@^1.0.8, combined-stream@~1.0.6: +combined-stream@^1.0.8: version "1.0.8" resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== @@ -3954,7 +4229,7 @@ command-line-usage@^6.1.0: table-layout "^1.0.2" typical "^5.2.0" -commander@^2.15.0, commander@^2.19.0: +commander@^2.15.0, commander@^2.20.0: 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== @@ -3964,16 +4239,21 @@ commander@^7.2.0: resolved "https://registry.yarnpkg.com/commander/-/commander-7.2.0.tgz#a36cb57d0b501ce108e4d20559a150a391d97ab7" integrity sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw== -commander@^8.3.0: - version "8.3.0" - resolved "https://registry.yarnpkg.com/commander/-/commander-8.3.0.tgz#4837ea1b2da67b9c616a67afbb0fafee567bca66" - integrity sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww== +commander@^9.4.1: + version "9.5.0" + resolved "https://registry.yarnpkg.com/commander/-/commander-9.5.0.tgz#bc08d1eb5cedf7ccb797a96199d41c7bc3e60d30" + integrity sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ== commander@~2.13.0: version "2.13.0" resolved "https://registry.yarnpkg.com/commander/-/commander-2.13.0.tgz#6964bca67685df7c1f1430c584f07d7597885b9c" integrity sha512-MVuS359B+YzaWqjCL/c+22gfryv+mCBPHAv3zyVI2GN8EY6IRP8VwtasXn8jyyhvvq84R4ImN1OKRtcbIasjYA== +common-ancestor-path@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/common-ancestor-path/-/common-ancestor-path-1.0.1.tgz#4f7d2d1394d91b7abdf51871c62f71eadb0182a7" + integrity sha512-L3sHRo1pXXEqX8VU28kfgUY+YGsk09hPqZiZmLacNib6XNTCM8ubYeT7ryXQw8asB1sKgcU5lkB7ONug08aB8w== + commondir@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/commondir/-/commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b" @@ -4032,10 +4312,10 @@ concat-stream@^2.0.0: readable-stream "^3.0.2" typedarray "^0.0.6" -config-chain@^1.1.12: - version "1.1.13" - resolved "https://registry.yarnpkg.com/config-chain/-/config-chain-1.1.13.tgz#fad0795aa6a6cdaff9ed1b68e9dff94372c232f4" - integrity sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ== +config-chain@1.1.12: + version "1.1.12" + resolved "https://registry.yarnpkg.com/config-chain/-/config-chain-1.1.12.tgz#0fde8d091200eb5e808caf25fe618c02f48e4efa" + integrity sha512-a1eOIcu8+7lUInge4Rpf/n4Krkf3Dd9lqhljRzII1/Zno/kRtUWnznPO3jOKBmTEktkt3fkxisUcivoj0ebzoA== dependencies: ini "^1.3.4" proto-list "~1.2.1" @@ -4063,14 +4343,14 @@ content-disposition@0.5.4: safe-buffer "5.2.1" content-type@~1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.4.tgz#e138cc75e040c727b1966fe5e5f8c9aee256fe3b" - integrity sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA== + version "1.0.5" + resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.5.tgz#8b773162656d1d1086784c8f23a54ce6d73d7918" + integrity sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA== -conventional-changelog-angular@^5.0.12: - version "5.0.13" - resolved "https://registry.yarnpkg.com/conventional-changelog-angular/-/conventional-changelog-angular-5.0.13.tgz#896885d63b914a70d4934b59d2fe7bde1832b28c" - integrity sha512-i/gipMxs7s8L/QeuavPF2hLnJgH6pEZAttySB6aiQLWcX3puWDL3ACVmvBhJGxnAy52Qc15ua26BufY6KpmrVA== +conventional-changelog-angular@5.0.12: + version "5.0.12" + resolved "https://registry.yarnpkg.com/conventional-changelog-angular/-/conventional-changelog-angular-5.0.12.tgz#c979b8b921cbfe26402eb3da5bbfda02d865a2b9" + integrity sha512-5GLsbnkR/7A89RyHLvvoExbiGbd9xKdKqDTrArnPbOqBqG/2wIosu0fHwpeIRI8Tl94MhVNBXcLJZl92ZQ5USw== dependencies: compare-func "^2.0.0" q "^1.5.1" @@ -4084,7 +4364,7 @@ conventional-changelog-conventionalcommits@^5.0.0: lodash "^4.17.15" q "^1.5.1" -conventional-changelog-core@^4.2.2: +conventional-changelog-core@4.2.4: version "4.2.4" resolved "https://registry.yarnpkg.com/conventional-changelog-core/-/conventional-changelog-core-4.2.4.tgz#e50d047e8ebacf63fac3dc67bf918177001e1e9f" integrity sha512-gDVS+zVJHE2v4SLc6B0sLsPiloR0ygU7HaDW14aNJE1v4SlqJPILPl/aJC7YdtRE4CybBf8gDwObBvKha8Xlyg== @@ -4144,7 +4424,7 @@ conventional-commits-parser@^3.2.0: split2 "^3.0.0" through2 "^4.0.0" -conventional-recommended-bump@^6.1.0: +conventional-recommended-bump@6.1.0, conventional-recommended-bump@^6.1.0: version "6.1.0" resolved "https://registry.yarnpkg.com/conventional-recommended-bump/-/conventional-recommended-bump-6.1.0.tgz#cfa623285d1de554012f2ffde70d9c8a22231f55" integrity sha512-uiApbSiNGM/kkdL9GTOLAqC4hbptObFo4wW2QRyHsKciGAfQuLU1ShZ1BIVI/+K2BE/W1AWYQMCXAsv4dyKPaw== @@ -4158,12 +4438,15 @@ conventional-recommended-bump@^6.1.0: meow "^8.0.0" q "^1.5.1" -convert-source-map@^1.4.0, convert-source-map@^1.6.0, convert-source-map@^1.7.0: - version "1.8.0" - resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.8.0.tgz#f3373c32d21b4d780dd8004514684fb791ca4369" - integrity sha512-+OQdjP49zViI/6i7nIJpA8rAl4sV/JdPfU9nZs3VqOwGIgizICvuN2ru6fMd+4llL0tar18UYJXfZ/TWtmhUjA== - dependencies: - safe-buffer "~5.1.1" +convert-source-map@^1.6.0, convert-source-map@^1.7.0: + version "1.9.0" + resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.9.0.tgz#7faae62353fb4213366d0ca98358d22e8368b05f" + integrity sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A== + +convert-source-map@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-2.0.0.tgz#4b560f649fc4e918dd0ab75cf4961e8bc882d82a" + integrity sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg== cookie-signature@1.0.6: version "1.0.6" @@ -4181,16 +4464,11 @@ copy-descriptor@^0.1.0: integrity sha512-XgZ0pFcakEUlbwQEVNg3+QAis1FyTL3Qel9FYy8pSkQqoG3PNoT0bOCQtOXcOkur21r2Eq2kI+IE+gsmAEVlYw== core-js-compat@^3.25.1: - version "3.25.3" - resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.25.3.tgz#d6a442a03f4eade4555d4e640e6a06151dd95d38" - integrity sha512-xVtYpJQ5grszDHEUU9O7XbjjcZ0ccX3LgQsyqSvTnjX97ZqEgn9F5srmrwwwMtbKzDllyFPL+O+2OFMl1lU4TQ== + version "3.30.1" + resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.30.1.tgz#961541e22db9c27fc48bfc13a3cafa8734171dfe" + integrity sha512-d690npR7MC6P0gq4npTl5n2VQeNAmUrJ90n+MHiKS7W2+xno4o3F5GDEuylSdi6EJ3VssibSGXOa1r3YXD3Mhw== dependencies: - browserslist "^4.21.4" - -core-util-is@1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" - integrity sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ== + browserslist "^4.21.5" core-util-is@~1.0.0: version "1.0.3" @@ -4205,6 +4483,17 @@ cors@^2.8.5: object-assign "^4" vary "^1" +cosmiconfig@7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-7.0.0.tgz#ef9b44d773959cae63ddecd122de23853b60f8d3" + integrity sha512-pondGvTuVYDk++upghXJabWzL6Kxu6f26ljFw64Swq9v6sQPUL3EUlVDV56diOjpCayKihL6hVe8exIACU4XcA== + dependencies: + "@types/parse-json" "^4.0.0" + import-fresh "^3.2.1" + parse-json "^5.0.0" + path-type "^4.0.0" + yaml "^1.10.0" + cosmiconfig@^5.0.5, cosmiconfig@^5.1.0: version "5.2.1" resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-5.2.1.tgz#040f726809c591e77a17c0a3626ca45b4f168b1a" @@ -4215,16 +4504,13 @@ cosmiconfig@^5.0.5, cosmiconfig@^5.1.0: js-yaml "^3.13.1" parse-json "^4.0.0" -cosmiconfig@^7.0.0: - version "7.0.1" - resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-7.0.1.tgz#714d756522cace867867ccb4474c5d01bbae5d6d" - integrity sha512-a1YWNUV2HwGimB7dU2s1wUMurNKjpx60HxBB6xUM8Re+2s1g1IIfJvFR0/iCF+XHdE0GMTKTuLR32UQff4TEyQ== +cosmjs-types@^0.5.2: + version "0.5.2" + resolved "https://registry.yarnpkg.com/cosmjs-types/-/cosmjs-types-0.5.2.tgz#2d42b354946f330dfb5c90a87fdc2a36f97b965d" + integrity sha512-zxCtIJj8v3Di7s39uN4LNcN3HIE1z0B9Z0SPE8ZNQR0oSzsuSe1ACgxoFkvhkS7WBasCAFcglS11G2hyfd5tPg== dependencies: - "@types/parse-json" "^4.0.0" - import-fresh "^3.2.1" - parse-json "^5.0.0" - path-type "^4.0.0" - yaml "^1.10.0" + long "^4.0.0" + protobufjs "~6.11.2" create-require@^1.1.0: version "1.1.1" @@ -4263,72 +4549,52 @@ cross-spawn@^7.0.2, cross-spawn@^7.0.3: shebang-command "^2.0.0" which "^2.0.1" -cssom@^0.4.4: - version "0.4.4" - resolved "https://registry.yarnpkg.com/cssom/-/cssom-0.4.4.tgz#5a66cf93d2d0b661d80bf6a44fb65f5c2e4e0a10" - integrity sha512-p3pvU7r1MyyqbTk+WbNJIgJjG2VmTIaB10rI93LzVPrmDJKkzKYMtxxyAvQXR/NS6otuzveI7+7BBq3SjBS2mw== +crypto-random-string@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/crypto-random-string/-/crypto-random-string-2.0.0.tgz#ef2a7a966ec11083388369baa02ebead229b30d5" + integrity sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA== -cssom@~0.3.6: - version "0.3.8" - resolved "https://registry.yarnpkg.com/cssom/-/cssom-0.3.8.tgz#9f1276f5b2b463f2114d3f2c75250af8c1a36f4a" - integrity sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg== +cssesc@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-3.0.0.tgz#37741919903b868565e1c09ea747445cd18983ee" + integrity sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg== -cssstyle@^2.3.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/cssstyle/-/cssstyle-2.3.0.tgz#ff665a0ddbdc31864b09647f34163443d90b0852" - integrity sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A== +d@1, d@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/d/-/d-1.0.1.tgz#8698095372d58dbee346ffd0c7093f99f8f9eb5a" + integrity sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA== dependencies: - cssom "~0.3.6" - -csstype@^3.0.2: - version "3.1.1" - resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.1.1.tgz#841b532c45c758ee546a11d5bd7b7b473c8c30b9" - integrity sha512-DJR/VvkAvSZW9bTouZue2sSxDwdTN92uHjqeKVm+0dAqdfNykRzQ95tay8aXMBAAPpUiq4Qcug2L7neoRh2Egw== + es5-ext "^0.10.50" + type "^1.0.1" dargs@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/dargs/-/dargs-7.0.0.tgz#04015c41de0bcb69ec84050f3d9be0caf8d6d5cc" integrity sha512-2iy1EkLdlBzQGvbweYRFxmFath8+K7+AKB0TlhHWkNuH+TmovaMH/Wp7V7R4u7f4SnX3OgLsU9t1NI9ioDnUpg== -dashdash@^1.12.0: - version "1.14.1" - resolved "https://registry.yarnpkg.com/dashdash/-/dashdash-1.14.1.tgz#853cfa0f7cbe2fed5de20326b8dd581035f6e2f0" - integrity sha512-jRFi8UDGo6j+odZiEpjazZaWqEal3w/basFjQHQEwVtZJGDpxbH1MeYluwCS8Xq5wmLJooDlMgvVarmWfGM44g== - dependencies: - assert-plus "^1.0.0" - data-uri-to-buffer@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/data-uri-to-buffer/-/data-uri-to-buffer-3.0.1.tgz#594b8973938c5bc2c33046535785341abc4f3636" integrity sha512-WboRycPNsVw3B3TL559F7kuBUM4d8CgMEvk6xEJlOp7OBPjt6G7z8WMWlD2rOFZLk6OYfFIUGsCOWzcQH9K2og== -data-urls@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/data-urls/-/data-urls-2.0.0.tgz#156485a72963a970f5d5821aaf642bef2bf2db9b" - integrity sha512-X5eWTSXO/BJmpdIKCRuKUgSCgAN0OwliVK3yPKbwIWU1Tdw5BRajxlzMidvh+gwko9AfQ9zIj52pzF91Q3YAvQ== - dependencies: - abab "^2.0.3" - whatwg-mimetype "^2.3.0" - whatwg-url "^8.0.0" - dateformat@^3.0.0: version "3.0.3" resolved "https://registry.yarnpkg.com/dateformat/-/dateformat-3.0.3.tgz#a6e37499a4d9a9cf85ef5872044d62901c9889ae" integrity sha512-jyCETtSl3VMZMWeRo7iY1FL19ges1t55hMo5yaam4Jrsm5EPL89UQkoQRyiI+Yf4k8r2ZpdngkV8hr1lIdjb3Q== dayjs@^1.8.15: - version "1.11.5" - resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.11.5.tgz#00e8cc627f231f9499c19b38af49f56dc0ac5e93" - integrity sha512-CAdX5Q3YW3Gclyo5Vpqkgpj8fSdLQcRuzfX6mC6Phy0nfJ0eGYOeS7m4mt2plDWLAtA4TqTakvbboHvUxfe4iA== + version "1.11.7" + resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.11.7.tgz#4b296922642f70999544d1144a2c25730fce63e2" + integrity sha512-+Yw9U6YO5TQohxLcIkrXBeY73WP3ejHWVvx8XCk3gxvQDCTEmS48ZrSZCKciI7Bhl/uCMyxYtE9UqRILmFphkQ== -debug@2.6.9, debug@^2.2.0, debug@^2.3.3, debug@^2.6.9: +debug@2.6.9, debug@^2.2.0, debug@^2.3.3: version "2.6.9" resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== dependencies: ms "2.0.0" -debug@4, debug@^4.0.1, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.3, debug@^4.3.4: +debug@4, debug@^4.1.0, debug@^4.1.1, debug@^4.3.2, debug@^4.3.3, debug@^4.3.4: version "4.3.4" resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== @@ -4342,15 +4608,10 @@ debug@^3.1.0, debug@^3.2.6, debug@^3.2.7: dependencies: ms "^2.1.1" -debuglog@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/debuglog/-/debuglog-1.0.1.tgz#aa24ffb9ac3df9a2351837cfb2d279360cd78492" - integrity sha512-syBZ+rnAK3EgMsH2aYEOLUW7mZSY9Gb+0wUMCFsZvcmiz+HigA0LOcq/HoQqVuGG+EKykunc7QG2bzrponfaSw== - decamelize-keys@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/decamelize-keys/-/decamelize-keys-1.1.0.tgz#d171a87933252807eb3cb61dc1c1445d078df2d9" - integrity sha512-ocLWuYzRPoS9bfiSdDd3cxvrzovVMZnRDVEzAs+hWIVXGDbHxWMECij2OBuyB/An0FFW/nLuq6Kv1i/YC5Qfzg== + version "1.1.1" + resolved "https://registry.yarnpkg.com/decamelize-keys/-/decamelize-keys-1.1.1.tgz#04a2d523b2f18d80d0158a43b895d56dff8d19d8" + integrity sha512-WiPxgEirIV0/eIOMcnFBA3/IJZAZqKnwAwWyvvdi4lsr1WCN22nhdf/3db3DoZcUjTV2SqfzIwNyp6y2xs3nmg== dependencies: decamelize "^1.1.0" map-obj "^1.0.0" @@ -4360,17 +4621,12 @@ decamelize@^1.1.0, decamelize@^1.2.0: resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" integrity sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA== -decimal.js@^10.2.1: - version "10.4.1" - resolved "https://registry.yarnpkg.com/decimal.js/-/decimal.js-10.4.1.tgz#be75eeac4a2281aace80c1a8753587c27ef053e7" - integrity sha512-F29o+vci4DodHYT9UrR5IEbfBw9pE5eSapIJdTqXK5+6hq+t8VRxwQyKlW2i+KDKFkkJQRvFyI/QXD83h8LyQw== - -decode-uri-component@^0.2.0: +decode-uri-component@^0.2.0, decode-uri-component@^0.2.2: version "0.2.2" resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.2.tgz#e69dbe25d37941171dd540e024c444cd5188e1e9" integrity sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ== -dedent@^0.7.0: +dedent@0.7.0, dedent@^0.7.0: version "0.7.0" resolved "https://registry.yarnpkg.com/dedent/-/dedent-0.7.0.tgz#2495ddbaf6eb874abb0e1be9df22d2e5a544326c" integrity sha512-Q6fKUPqnAHAyhiUgFU7BUzLiv0kd8saH9al7tnu5Q/okj6dnupxyTgFIBjVzJATdfIAm9NAsvXNzjaKa+bxVyA== @@ -4380,7 +4636,7 @@ deep-extend@^0.6.0, deep-extend@~0.6.0: resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac" integrity sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA== -deep-is@^0.1.3, deep-is@~0.1.3: +deep-is@^0.1.3: version "0.1.4" resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831" integrity sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ== @@ -4391,21 +4647,26 @@ deepmerge@^3.2.0: integrity sha512-GRQOafGHwMHpjPx9iCvTgpu9NojZ49q794EEL94JVEw6VaeA8XTUyBKvAkOOjBX9oJNiV6G3P+T+tihFjo2TqA== deepmerge@^4.2.2: - version "4.2.2" - resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.2.2.tgz#44d2ea3679b8f4d4ffba33f03d865fc1e7bf4955" - integrity sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg== + version "4.3.1" + resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.3.1.tgz#44b5f2147cd3b00d4b56137685966f26fd25dd4a" + integrity sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A== defaults@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/defaults/-/defaults-1.0.3.tgz#c656051e9817d9ff08ed881477f3fe4019f3ef7d" - integrity sha512-s82itHOnYrN0Ib8r+z7laQz3sdE+4FP3d9Q7VLO7U+KRT+CR0GsWuyHxzdAY82I7cXv0G/twrqomTJLOssO5HA== + version "1.0.4" + resolved "https://registry.yarnpkg.com/defaults/-/defaults-1.0.4.tgz#b0b02062c1e2aa62ff5d9528f0f98baa90978d7a" + integrity sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A== dependencies: clone "^1.0.2" -define-properties@^1.1.3, define-properties@^1.1.4: - version "1.1.4" - resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.4.tgz#0b14d7bd7fbeb2f3572c3a7eda80ea5d57fb05b1" - integrity sha512-uckOqKcfaVvtBdsVkdPv3XjveQJsNQqmhXgRi8uhvWWuPYZCNlzT8qAyblUgNoXdHdjMTzAqeGjAoli8f+bzPA== +define-lazy-prop@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz#3f7ae421129bcaaac9bc74905c98a0009ec9ee7f" + integrity sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og== + +define-properties@^1.1.3, define-properties@^1.1.4, define-properties@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.2.0.tgz#52988570670c9eacedd8064f4a990f2405849bd5" + integrity sha512-xvqAVKGfT1+UAvPwKTVw/njhdQ8ZhXK4lI0bCIuCMrp2up9nPnaDftrLtmpTazqd1o+UY4zgzU+avtMbDP+ldA== dependencies: has-property-descriptors "^1.0.0" object-keys "^1.1.1" @@ -4432,6 +4693,20 @@ define-property@^2.0.2: is-descriptor "^1.0.2" isobject "^3.0.1" +del@^6.0.0: + version "6.1.1" + resolved "https://registry.yarnpkg.com/del/-/del-6.1.1.tgz#3b70314f1ec0aa325c6b14eb36b95786671edb7a" + integrity sha512-ua8BhapfP0JUJKC/zV9yHHDW/rDoDxP4Zhn3AkA6/xT6gY7jYXJiaeyBZznYVujhZZET+UgcbZiQ7sN3WqcImg== + dependencies: + globby "^11.0.1" + graceful-fs "^4.2.4" + is-glob "^4.0.1" + is-path-cwd "^2.2.0" + is-path-inside "^3.0.2" + p-map "^4.0.0" + rimraf "^3.0.2" + slash "^3.0.0" + delayed-stream@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" @@ -4447,15 +4722,19 @@ denodeify@^1.2.1: resolved "https://registry.yarnpkg.com/denodeify/-/denodeify-1.2.1.tgz#3a36287f5034e699e7577901052c2e6c94251631" integrity sha512-KNTihKNmQENUZeKu5fzfpzRqR5S2VMp4gl9RFHiWzj9DfvYQPMJ6XHKNaQxaGCXwPk6y9yme3aUoaiAe+KX+vg== -depd@2.0.0: +depd@2.0.0, depd@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/depd/-/depd-2.0.0.tgz#b696163cc757560d09cf22cc8fad1571b79e76df" integrity sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw== -depd@^1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9" - integrity sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ== +deprecated-react-native-prop-types@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/deprecated-react-native-prop-types/-/deprecated-react-native-prop-types-3.0.1.tgz#a275f84cd8519cd1665e8df3c99e9067d57a23ec" + integrity sha512-J0jCJcsk4hMlIb7xwOZKLfMpuJn6l8UtrPEzzQV5ewz5gvKNYakhBuq9h2rWX7YwHHJZFhU5W8ye7dB9oN8VcQ== + dependencies: + "@react-native/normalize-color" "*" + invariant "*" + prop-types "*" deprecation@^2.0.0, deprecation@^2.3.1: version "2.3.1" @@ -4472,11 +4751,6 @@ detect-indent@^5.0.0: resolved "https://registry.yarnpkg.com/detect-indent/-/detect-indent-5.0.0.tgz#3871cc0a6a002e8c3e5b3cf7f336264675f06b9d" integrity sha512-rlpvsxUtM0PQvy9iZe640/IWwWYyBsTApREbA1pHOpmOUIl9MkP/U4z7vTtg4Oaojvqhxt7sdufnT0EzGaR31g== -detect-indent@^6.0.0: - version "6.1.0" - resolved "https://registry.yarnpkg.com/detect-indent/-/detect-indent-6.1.0.tgz#592485ebbbf6b3b1ab2be175c8393d04ca0d57e6" - integrity sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA== - detect-libc@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-1.0.3.tgz#fa137c4bd698edf55cd5cd02ac559f91a4c4ba9b" @@ -4484,51 +4758,41 @@ detect-libc@^1.0.3: detect-libc@^2.0.0: version "2.0.1" - resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-2.0.1.tgz#e1897aa88fa6ad197862937fbc0441ef352ee0cd" - integrity sha512-463v3ZeIrcWtdgIg6vI6XUncguvr2TnGl4SzDXinkt9mSLpBJKXT3mW6xT3VQdDN11+WVs29pgvivTc4Lp8v+w== - -detect-newline@^3.0.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/detect-newline/-/detect-newline-3.1.0.tgz#576f5dfc63ae1a192ff192d8ad3af6308991b651" - integrity sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA== - -dezalgo@^1.0.0: - version "1.0.4" - resolved "https://registry.yarnpkg.com/dezalgo/-/dezalgo-1.0.4.tgz#751235260469084c132157dfa857f386d4c33d81" - integrity sha512-rXSP0bf+5n0Qonsb+SVVfNfIsimO4HEtmnIpPHY8Q1UCzKlQrDMfdobr8nJOOsRgWCyMRqeSBQzmWUMq7zvVig== - dependencies: - asap "^2.0.0" - wrappy "1" - -did-resolver@^3.1.3: - version "3.2.2" - resolved "https://registry.yarnpkg.com/did-resolver/-/did-resolver-3.2.2.tgz#6f4e252a810f785d1b28a10265fad6dffee25158" - integrity sha512-Eeo2F524VM5N3W4GwglZrnul2y6TLTwMQP3In62JdG34NZoqihYyOZLk+5wUW8sSgvIYIcJM8Dlt3xsdKZZ3tg== - -did-resolver@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/did-resolver/-/did-resolver-4.0.0.tgz#fc8f657b4cd7f44c2921051fb046599fbe7d4b31" - integrity sha512-/roxrDr9EnAmLs+s9T+8+gcpilMo+IkeytcsGO7dcxvTmVJ+0Rt60HtV8o0UXHhGBo0Q+paMH/0ffXz1rqGFYg== + resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-2.0.1.tgz#e1897aa88fa6ad197862937fbc0441ef352ee0cd" + integrity sha512-463v3ZeIrcWtdgIg6vI6XUncguvr2TnGl4SzDXinkt9mSLpBJKXT3mW6xT3VQdDN11+WVs29pgvivTc4Lp8v+w== -didcomm-node@0.3.4: - version "0.3.4" - resolved "https://registry.yarnpkg.com/didcomm-node/-/didcomm-node-0.3.4.tgz#2bab787256d03be39ee9fe9da49e6af44d265a06" - integrity sha512-XcQlpuxUU5TSwio7I6pA3w/K9m9NHCNOgENK4JPgP9DrYwtOci8yG1hmh0G9zZ6WY7Ylfmt2rBJEdDj+OPdweg== +detect-newline@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/detect-newline/-/detect-newline-3.1.0.tgz#576f5dfc63ae1a192ff192d8ad3af6308991b651" + integrity sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA== -didcomm@0.3.4: - version "0.3.4" - resolved "https://registry.yarnpkg.com/didcomm/-/didcomm-0.3.4.tgz#8c81ac39966cafd0483743db59affc95b2b20c00" - integrity sha512-jGZduR98a5tR5Mh5xuwHG8ZT1w9xHpBUzCOl2Beqylk46aJ6fBD385NbcMNcPxqgVEXZ5fuOC3TQFXvp385PzA== +did-jwt@^6.11.6: + version "6.11.6" + resolved "https://registry.yarnpkg.com/did-jwt/-/did-jwt-6.11.6.tgz#3eeb30d6bd01f33bfa17089574915845802a7d44" + integrity sha512-OfbWknRxJuUqH6Lk0x+H1FsuelGugLbBDEwsoJnicFOntIG/A4y19fn0a8RLxaQbWQ5gXg0yDq5E2huSBiiXzw== + dependencies: + "@stablelib/ed25519" "^1.0.2" + "@stablelib/random" "^1.0.1" + "@stablelib/sha256" "^1.0.1" + "@stablelib/x25519" "^1.0.2" + "@stablelib/xchacha20poly1305" "^1.0.1" + bech32 "^2.0.0" + canonicalize "^2.0.0" + did-resolver "^4.0.0" + elliptic "^6.5.4" + js-sha3 "^0.8.0" + multiformats "^9.6.5" + uint8arrays "^3.0.0" -diff-sequences@^26.6.2: - version "26.6.2" - resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-26.6.2.tgz#48ba99157de1923412eed41db6b6d4aa9ca7c0b1" - integrity sha512-Mv/TDa3nZ9sbc5soK+OoA74BsS3mL37yixCvUAQkiuA4Wz6YtwP/K47n2rv2ovzHZvoiQeA5FTQOschKkEwB0Q== +did-resolver@^4.0.0, did-resolver@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/did-resolver/-/did-resolver-4.1.0.tgz#740852083c4fd5bf9729d528eca5d105aff45eb6" + integrity sha512-S6fWHvCXkZg2IhS4RcVHxwuyVejPR7c+a4Go0xbQ9ps5kILa8viiYQgrM4gfTyeTjJ0ekgJH9gk/BawTpmkbZA== -diff-sequences@^27.5.1: - version "27.5.1" - resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-27.5.1.tgz#eaecc0d327fd68c8d9672a1e64ab8dccb2ef5327" - integrity sha512-k1gCAXAsNgLwEL+Y8Wvl+M6oEFj5bgazfZULpS5CneoPPXRaCCW7dm+q21Ky2VEE5X+VeRDBVg1Pcvvsr4TtNQ== +diff-sequences@^29.4.3: + version "29.4.3" + resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-29.4.3.tgz#9314bc1fabe09267ffeca9cbafc457d8499a13f2" + integrity sha512-ofrBgwpPhCD85kMKtE9RYFFq6OC1A89oW2vvgWZNCwxrUpRUILopY7lsYyMDSjc8g6U6aiO0Qubg6r4Wgt5ZnA== diff@^4.0.1: version "4.0.2" @@ -4556,12 +4820,12 @@ doctrine@^3.0.0: dependencies: esutils "^2.0.2" -domexception@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/domexception/-/domexception-2.0.1.tgz#fb44aefba793e1574b0af6aed2801d057529f304" - integrity sha512-yxJ2mFy/sibVQlu5qHjOkf9J3K6zgmCxgJ94u2EdvDOV09H+32LtRswEcUsmUWN72pVLOEnTSRaIVVzVQgS0dg== +dot-prop@6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/dot-prop/-/dot-prop-6.0.1.tgz#fc26b3cf142b9e59b74dbd39ed66ce620c681083" + integrity sha512-tE7ztYzXHIeyvc7N+hR3oi7FIbf/NIjVP9hmAt3yMXzrQ072/fpjGLx2GxNxGxUl5V73MEqYzioOMoVhGMJ5cA== dependencies: - webidl-conversions "^5.0.0" + is-obj "^2.0.0" dot-prop@^5.1.0: version "5.3.0" @@ -4570,14 +4834,7 @@ dot-prop@^5.1.0: dependencies: is-obj "^2.0.0" -dot-prop@^6.0.1: - version "6.0.1" - resolved "https://registry.yarnpkg.com/dot-prop/-/dot-prop-6.0.1.tgz#fc26b3cf142b9e59b74dbd39ed66ce620c681083" - integrity sha512-tE7ztYzXHIeyvc7N+hR3oi7FIbf/NIjVP9hmAt3yMXzrQ072/fpjGLx2GxNxGxUl5V73MEqYzioOMoVhGMJ5cA== - dependencies: - is-obj "^2.0.0" - -dotenv@^10.0.0: +dotenv@~10.0.0: version "10.0.0" resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-10.0.0.tgz#3d4227b8fb95f81096cdd2b66653fb2c7085ba81" integrity sha512-rlBi9d8jpv9Sf1klPjNfFAuWDjKLwTIJJ/VxtoTwIR6hnZxcEOQCZg2oIL3MWBYw5GpUDKOEnND7LXTbIpQ03Q== @@ -4587,28 +4844,40 @@ duplexer@^0.1.1: resolved "https://registry.yarnpkg.com/duplexer/-/duplexer-0.1.2.tgz#3abe43aef3835f8ae077d136ddce0f276b0400e6" integrity sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg== -ecc-jsbn@~0.1.1: - version "0.1.2" - resolved "https://registry.yarnpkg.com/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz#3a83a904e54353287874c564b7549386849a98c9" - integrity sha512-eh9O+hwRHNbG4BLTjEl3nw044CkGm5X6LoaCf7LPp7UU8Qrt47JYNi6nPX8xjW97TKGKm1ouctg0QSpZe9qrnw== - dependencies: - jsbn "~0.1.0" - safer-buffer "^2.1.0" - ee-first@1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" integrity sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow== -electron-to-chromium@^1.4.251: - version "1.4.262" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.262.tgz#25715dfbae4c2e0640517cba184715241ecd8e63" - integrity sha512-Ckn5haqmGh/xS8IbcgK3dnwAVnhDyo/WQnklWn6yaMucYTq7NNxwlGE8ElzEOnonzRLzUCo2Ot3vUb2GYUF2Hw== +ejs@^3.1.7: + version "3.1.9" + resolved "https://registry.yarnpkg.com/ejs/-/ejs-3.1.9.tgz#03c9e8777fe12686a9effcef22303ca3d8eeb361" + integrity sha512-rC+QVNMJWv+MtPgkt0y+0rVEIdbtxVADApW9JXrUVlzHetgcyczP/E7DJmWJ4fJCZF2cPcBk0laWO9ZHMG3DmQ== + dependencies: + jake "^10.8.5" -emittery@^0.8.1: - version "0.8.1" - resolved "https://registry.yarnpkg.com/emittery/-/emittery-0.8.1.tgz#bb23cc86d03b30aa75a7f734819dee2e1ba70860" - integrity sha512-uDfvUjVrfGJJhymx/kz6prltenw1u7WrCg1oa94zYY8xxVpLLUu045LAT0dhDZdXG58/EpPL/5kA180fQ/qudg== +electron-to-chromium@^1.4.284: + version "1.4.371" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.371.tgz#393983ef087268a20c926a89be30e9f0bfc803b0" + integrity sha512-jlBzY4tFcJaiUjzhRTCWAqRvTO/fWzjA3Bls0mykzGZ7zvcMP7h05W6UcgzfT9Ca1SW2xyKDOFRyI0pQeRNZGw== + +elliptic@^6.5.4: + version "6.5.4" + resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.5.4.tgz#da37cebd31e79a1367e941b592ed1fbebd58abbb" + integrity sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ== + dependencies: + bn.js "^4.11.9" + brorand "^1.1.0" + hash.js "^1.0.0" + hmac-drbg "^1.0.1" + inherits "^2.0.4" + minimalistic-assert "^1.0.1" + minimalistic-crypto-utils "^1.0.1" + +emittery@^0.13.1: + version "0.13.1" + resolved "https://registry.yarnpkg.com/emittery/-/emittery-0.13.1.tgz#c04b8c3457490e0847ae51fced3af52d338e3dad" + integrity sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ== emoji-regex@^8.0.0: version "8.0.0" @@ -4620,21 +4889,29 @@ encodeurl@~1.0.2: resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" integrity sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w== -encoding@^0.1.12: +encoding@^0.1.12, encoding@^0.1.13: version "0.1.13" resolved "https://registry.yarnpkg.com/encoding/-/encoding-0.1.13.tgz#56574afdd791f54a8e9b2785c0582a2d26210fa9" integrity sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A== dependencies: iconv-lite "^0.6.2" -end-of-stream@^1.1.0: +end-of-stream@^1.1.0, end-of-stream@^1.4.1: version "1.4.4" resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0" integrity sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q== dependencies: once "^1.4.0" -enquirer@^2.3.5: +enhanced-resolve@^5.12.0: + version "5.13.0" + resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.13.0.tgz#26d1ecc448c02de997133217b5c1053f34a0a275" + integrity sha512-eyV8f0y1+bzyfh8xAwW/WTSZpLbjhqc4ne9eGSH4Zo2ejdyiNG9pU6mf9DG8a7+Auk6MFTlNOT4Y2y/9k8GKVg== + dependencies: + graceful-fs "^4.2.4" + tapable "^2.2.0" + +enquirer@~2.3.6: version "2.3.6" resolved "https://registry.yarnpkg.com/enquirer/-/enquirer-2.3.6.tgz#2a7fe5dd634a1e4125a975ec994ff5456dc3734d" integrity sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg== @@ -4678,40 +4955,54 @@ errorhandler@^1.5.0: accepts "~1.3.7" escape-html "~1.0.3" -es-abstract@^1.19.0, es-abstract@^1.19.1, es-abstract@^1.19.2, es-abstract@^1.19.5, es-abstract@^1.20.1: - version "1.20.3" - resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.20.3.tgz#90b143ff7aedc8b3d189bcfac7f1e3e3f81e9da1" - integrity sha512-AyrnaKVpMzljIdwjzrj+LxGmj8ik2LckwXacHqrJJ/jxz6dDDBcZ7I7nlHM0FvEW8MfbWJwOd+yT2XzYW49Frw== +es-abstract@^1.19.0, es-abstract@^1.20.4: + version "1.21.2" + resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.21.2.tgz#a56b9695322c8a185dc25975aa3b8ec31d0e7eff" + integrity sha512-y/B5POM2iBnIxCiernH1G7rC9qQoM77lLIMQLuob0zhp8C56Po81+2Nj0WFKnd0pNReDTnkYryc+zhOzpEIROg== dependencies: + array-buffer-byte-length "^1.0.0" + available-typed-arrays "^1.0.5" call-bind "^1.0.2" + es-set-tostringtag "^2.0.1" es-to-primitive "^1.2.1" - function-bind "^1.1.1" function.prototype.name "^1.1.5" - get-intrinsic "^1.1.3" + get-intrinsic "^1.2.0" get-symbol-description "^1.0.0" + globalthis "^1.0.3" + gopd "^1.0.1" has "^1.0.3" has-property-descriptors "^1.0.0" + has-proto "^1.0.1" has-symbols "^1.0.3" - internal-slot "^1.0.3" - is-callable "^1.2.6" + internal-slot "^1.0.5" + is-array-buffer "^3.0.2" + is-callable "^1.2.7" is-negative-zero "^2.0.2" is-regex "^1.1.4" is-shared-array-buffer "^1.0.2" is-string "^1.0.7" + is-typed-array "^1.1.10" is-weakref "^1.0.2" - object-inspect "^1.12.2" + object-inspect "^1.12.3" object-keys "^1.1.1" object.assign "^4.1.4" regexp.prototype.flags "^1.4.3" safe-regex-test "^1.0.0" - string.prototype.trimend "^1.0.5" - string.prototype.trimstart "^1.0.5" + string.prototype.trim "^1.2.7" + string.prototype.trimend "^1.0.6" + string.prototype.trimstart "^1.0.6" + typed-array-length "^1.0.4" unbox-primitive "^1.0.2" + which-typed-array "^1.1.9" -es-array-method-boxes-properly@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/es-array-method-boxes-properly/-/es-array-method-boxes-properly-1.0.0.tgz#873f3e84418de4ee19c5be752990b2e44718d09e" - integrity sha512-wd6JXUmyHmt8T5a2xreUwKcGPq6f1f+WwIJkijUqiGcJz1qqnZgP6XIK+QyIWU5lT7imeNxUll48bziG+TSYcA== +es-set-tostringtag@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/es-set-tostringtag/-/es-set-tostringtag-2.0.1.tgz#338d502f6f674301d710b80c8592de8a15f09cd8" + integrity sha512-g3OMbtlwY3QewlqAiMLI47KywjWZoEytKr8pf6iTC8uJq5bIAH52Z9pnQ8pVL6whrCto53JZDuUIsifGeLorTg== + dependencies: + get-intrinsic "^1.1.3" + has "^1.0.3" + has-tostringtag "^1.0.0" es-shim-unscopables@^1.0.0: version "1.0.0" @@ -4729,6 +5020,32 @@ 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.50: + version "0.10.62" + resolved "https://registry.yarnpkg.com/es5-ext/-/es5-ext-0.10.62.tgz#5e6adc19a6da524bf3d1e02bbc8960e5eb49a9a5" + integrity sha512-BHLqn0klhEpnOKSrzn/Xsz2UIW8j+cGmo9JLzr8BiUapV8hPL9+FliFqjwr9ngW7jWdnxv6eO+/LqyhJVqgrjA== + dependencies: + es6-iterator "^2.0.3" + es6-symbol "^3.1.3" + next-tick "^1.1.0" + +es6-iterator@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/es6-iterator/-/es6-iterator-2.0.3.tgz#a7de889141a05a94b0854403b2d0a0fbfa98f3b7" + integrity sha512-zw4SRzoUkd+cl+ZoE15A9o1oQd920Bb0iOJMQkQhl3jNc03YqVjAhG7scf9C5KWRU/R13Orf588uCC6525o02g== + dependencies: + d "1" + es5-ext "^0.10.35" + es6-symbol "^3.1.1" + +es6-symbol@^3.0.2, es6-symbol@^3.1.1, es6-symbol@^3.1.3: + version "3.1.3" + resolved "https://registry.yarnpkg.com/es6-symbol/-/es6-symbol-3.1.3.tgz#bad5d3c1bcdac28269f4cb331e431c78ac705d18" + integrity sha512-NJ6Yn3FuDinBaBRWl/q5X/s4koRHBrgKAu+yGI6JCBeiu3qrcbJhwT2GeR/EXVfylRk8dpQVJoLEFhK+Mu31NA== + dependencies: + d "^1.0.1" + ext "^1.1.2" + escalade@^3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40" @@ -4754,75 +5071,76 @@ escape-string-regexp@^4.0.0: resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34" integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== -escodegen@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/escodegen/-/escodegen-2.0.0.tgz#5e32b12833e8aa8fa35e1bf0befa89380484c7dd" - integrity sha512-mmHKys/C8BFUGI+MAWNcSYoORYLMdPzjrknd2Vc+bUsjN5bXcr8EhrNB+UTqfL1y3I9c4fw2ihgtMPQLBRiQxw== - dependencies: - esprima "^4.0.1" - estraverse "^5.2.0" - esutils "^2.0.2" - optionator "^0.8.1" - optionalDependencies: - source-map "~0.6.1" - eslint-config-prettier@^8.3.0: - version "8.5.0" - resolved "https://registry.yarnpkg.com/eslint-config-prettier/-/eslint-config-prettier-8.5.0.tgz#5a81680ec934beca02c7b1a61cf8ca34b66feab1" - integrity sha512-obmWKLUNCnhtQRKc+tmnYuQl0pFU1ibYJQ5BGhTVB08bHe9wC8qUeG7c08dj9XX+AuPj1YSGSQIHl1pnDHZR0Q== + version "8.8.0" + resolved "https://registry.yarnpkg.com/eslint-config-prettier/-/eslint-config-prettier-8.8.0.tgz#bfda738d412adc917fd7b038857110efe98c9348" + integrity sha512-wLbQiFre3tdGgpDv67NQKnJuTlcUVYHas3k+DZCc2U2BadthoEY4B7hLPvAxaqdyOGCzuLfii2fqGph10va7oA== -eslint-import-resolver-node@^0.3.6: - version "0.3.6" - resolved "https://registry.yarnpkg.com/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.6.tgz#4048b958395da89668252001dbd9eca6b83bacbd" - integrity sha512-0En0w03NRVMn9Uiyn8YRPDKvWjxCWkslUEhGNTdGx15RvPJYQ+lbOlqrlNI2vEAs4pDYK4f/HN2TbDmk5TP0iw== +eslint-import-resolver-node@^0.3.7: + version "0.3.7" + resolved "https://registry.yarnpkg.com/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.7.tgz#83b375187d412324a1963d84fa664377a23eb4d7" + integrity sha512-gozW2blMLJCeFpBwugLTGyvVjNoeo1knonXAcatC6bjPBZitotxdWf7Gimr25N4c0AAOo4eOUfaG82IJPDpqCA== dependencies: debug "^3.2.7" - resolve "^1.20.0" + is-core-module "^2.11.0" + resolve "^1.22.1" -eslint-import-resolver-typescript@^2.4.0: - version "2.7.1" - resolved "https://registry.yarnpkg.com/eslint-import-resolver-typescript/-/eslint-import-resolver-typescript-2.7.1.tgz#a90a4a1c80da8d632df25994c4c5fdcdd02b8751" - integrity sha512-00UbgGwV8bSgUv34igBDbTOtKhqoRMy9bFjNehT40bXg6585PNIct8HhXZ0SybqB9rWtXj9crcku8ndDn/gIqQ== +eslint-import-resolver-typescript@^3.5.3: + version "3.5.5" + resolved "https://registry.yarnpkg.com/eslint-import-resolver-typescript/-/eslint-import-resolver-typescript-3.5.5.tgz#0a9034ae7ed94b254a360fbea89187b60ea7456d" + integrity sha512-TdJqPHs2lW5J9Zpe17DZNQuDnox4xo2o+0tE7Pggain9Rbc19ik8kFtXdxZ250FVx2kF4vlt2RSf4qlUpG7bhw== dependencies: debug "^4.3.4" - glob "^7.2.0" + enhanced-resolve "^5.12.0" + eslint-module-utils "^2.7.4" + get-tsconfig "^4.5.0" + globby "^13.1.3" + is-core-module "^2.11.0" is-glob "^4.0.3" - resolve "^1.22.0" - tsconfig-paths "^3.14.1" + synckit "^0.8.5" -eslint-module-utils@^2.7.3: - version "2.7.4" - resolved "https://registry.yarnpkg.com/eslint-module-utils/-/eslint-module-utils-2.7.4.tgz#4f3e41116aaf13a20792261e61d3a2e7e0583974" - integrity sha512-j4GT+rqzCoRKHwURX7pddtIPGySnX9Si/cgMI5ztrcqOPtk5dDEeZ34CQVPphnqkJytlc97Vuk05Um2mJ3gEQA== +eslint-module-utils@^2.7.4: + version "2.8.0" + resolved "https://registry.yarnpkg.com/eslint-module-utils/-/eslint-module-utils-2.8.0.tgz#e439fee65fc33f6bba630ff621efc38ec0375c49" + integrity sha512-aWajIYfsqCKRDgUfjEXNN/JlrzauMuSEy5sbd7WXbtW3EH6A6MpwEh42c7qD+MqQo9QMJ6fWLAeIJynx0g6OAw== dependencies: debug "^3.2.7" eslint-plugin-import@^2.23.4: - version "2.26.0" - resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.26.0.tgz#f812dc47be4f2b72b478a021605a59fc6fe8b88b" - integrity sha512-hYfi3FXaM8WPLf4S1cikh/r4IxnO6zrhZbEGz2b660EJRbuxgpDS5gkCuYgGWg2xxh2rBuIr4Pvhve/7c31koA== + version "2.27.5" + resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.27.5.tgz#876a6d03f52608a3e5bb439c2550588e51dd6c65" + integrity sha512-LmEt3GVofgiGuiE+ORpnvP+kAm3h6MLZJ4Q5HCyHADofsb4VzXFsRiWj3c0OFiV+3DWFh0qg3v9gcPlfc3zRow== dependencies: - array-includes "^3.1.4" - array.prototype.flat "^1.2.5" - debug "^2.6.9" + array-includes "^3.1.6" + array.prototype.flat "^1.3.1" + array.prototype.flatmap "^1.3.1" + debug "^3.2.7" doctrine "^2.1.0" - eslint-import-resolver-node "^0.3.6" - eslint-module-utils "^2.7.3" + eslint-import-resolver-node "^0.3.7" + eslint-module-utils "^2.7.4" has "^1.0.3" - is-core-module "^2.8.1" + is-core-module "^2.11.0" is-glob "^4.0.3" minimatch "^3.1.2" - object.values "^1.1.5" - resolve "^1.22.0" + object.values "^1.1.6" + resolve "^1.22.1" + semver "^6.3.0" tsconfig-paths "^3.14.1" -eslint-plugin-prettier@^3.4.0: - version "3.4.1" - resolved "https://registry.yarnpkg.com/eslint-plugin-prettier/-/eslint-plugin-prettier-3.4.1.tgz#e9ddb200efb6f3d05ffe83b1665a716af4a387e5" - integrity sha512-htg25EUYUeIhKHXjOinK4BgCcDwtLHjqaxCDsMy5nbnUMkKFvIhMVCp+5GFUXQ4Nr8lBsPqtGAqBenbpFqAA2g== +eslint-plugin-prettier@^4.2.1: + version "4.2.1" + resolved "https://registry.yarnpkg.com/eslint-plugin-prettier/-/eslint-plugin-prettier-4.2.1.tgz#651cbb88b1dab98bfd42f017a12fa6b2d993f94b" + integrity sha512-f/0rXLXUt0oFYs8ra4w49wYZBG5GKZpAYsJSm6rnYL5uVDjd+zowwMwVZHnAjf4edNrKpCDYfXDgmRE/Ak7QyQ== dependencies: prettier-linter-helpers "^1.0.0" +eslint-plugin-workspaces@^0.8.0: + version "0.8.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-workspaces/-/eslint-plugin-workspaces-0.8.0.tgz#e723a1333a8ddddbc416220a9e03578603de0f85" + integrity sha512-8BhKZaGFpl0xAVo7KHaWffaBvvroaOeLuqLkVsMNZvMaN6ZHKYx7QZoaXC/Y299tG3wvN6v7hu27VBHmyg4q4g== + dependencies: + find-workspaces "^0.1.0" + eslint-scope@^5.1.1: version "5.1.1" resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-5.1.1.tgz#e786e59a66cb92b3f6c1fb0d508aab174848f48c" @@ -4831,94 +5149,83 @@ eslint-scope@^5.1.1: esrecurse "^4.3.0" estraverse "^4.1.1" -eslint-utils@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/eslint-utils/-/eslint-utils-2.1.0.tgz#d2de5e03424e707dc10c74068ddedae708741b27" - integrity sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg== - dependencies: - eslint-visitor-keys "^1.1.0" - -eslint-utils@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/eslint-utils/-/eslint-utils-3.0.0.tgz#8aebaface7345bb33559db0a1f13a1d2d48c3672" - integrity sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA== +eslint-scope@^7.2.0: + version "7.2.0" + resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-7.2.0.tgz#f21ebdafda02352f103634b96dd47d9f81ca117b" + integrity sha512-DYj5deGlHBfMt15J7rdtyKNq/Nqlv5KfU4iodrQ019XESsRnwXH9KAE0y3cwtUHDo2ob7CypAnCqefh6vioWRw== dependencies: - eslint-visitor-keys "^2.0.0" - -eslint-visitor-keys@^1.1.0, eslint-visitor-keys@^1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz#30ebd1ef7c2fdff01c3a4f151044af25fab0523e" - integrity sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ== - -eslint-visitor-keys@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz#f65328259305927392c938ed44eb0a5c9b2bd303" - integrity sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw== + esrecurse "^4.3.0" + estraverse "^5.2.0" -eslint@^7.28.0: - version "7.32.0" - resolved "https://registry.yarnpkg.com/eslint/-/eslint-7.32.0.tgz#c6d328a14be3fb08c8d1d21e12c02fdb7a2a812d" - integrity sha512-VHZ8gX+EDfz+97jGcgyGCyRia/dPOd6Xh9yPv8Bl1+SoaIwD+a/vlrOmGRUyOYu7MwUhc7CxqeaDZU13S4+EpA== - dependencies: - "@babel/code-frame" "7.12.11" - "@eslint/eslintrc" "^0.4.3" - "@humanwhocodes/config-array" "^0.5.0" +eslint-visitor-keys@^3.3.0, eslint-visitor-keys@^3.4.0: + version "3.4.0" + resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.4.0.tgz#c7f0f956124ce677047ddbc192a68f999454dedc" + integrity sha512-HPpKPUBQcAsZOsHAFwTtIKcYlCje62XB7SEAcxjtmW6TD1WVpkS6i6/hOVtTZIl4zGj/mBqpFVGvaDneik+VoQ== + +eslint@^8.36.0: + version "8.39.0" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.39.0.tgz#7fd20a295ef92d43809e914b70c39fd5a23cf3f1" + integrity sha512-mwiok6cy7KTW7rBpo05k6+p4YVZByLNjAZ/ACB9DRCu4YDRwjXI01tWHp6KAUWelsBetTxKK/2sHB0vdS8Z2Og== + dependencies: + "@eslint-community/eslint-utils" "^4.2.0" + "@eslint-community/regexpp" "^4.4.0" + "@eslint/eslintrc" "^2.0.2" + "@eslint/js" "8.39.0" + "@humanwhocodes/config-array" "^0.11.8" + "@humanwhocodes/module-importer" "^1.0.1" + "@nodelib/fs.walk" "^1.2.8" ajv "^6.10.0" chalk "^4.0.0" cross-spawn "^7.0.2" - debug "^4.0.1" + debug "^4.3.2" doctrine "^3.0.0" - enquirer "^2.3.5" escape-string-regexp "^4.0.0" - eslint-scope "^5.1.1" - eslint-utils "^2.1.0" - eslint-visitor-keys "^2.0.0" - espree "^7.3.1" - esquery "^1.4.0" + eslint-scope "^7.2.0" + eslint-visitor-keys "^3.4.0" + espree "^9.5.1" + esquery "^1.4.2" esutils "^2.0.2" fast-deep-equal "^3.1.3" file-entry-cache "^6.0.1" - functional-red-black-tree "^1.0.1" - glob-parent "^5.1.2" - globals "^13.6.0" - ignore "^4.0.6" + find-up "^5.0.0" + glob-parent "^6.0.2" + globals "^13.19.0" + grapheme-splitter "^1.0.4" + ignore "^5.2.0" import-fresh "^3.0.0" imurmurhash "^0.1.4" is-glob "^4.0.0" - js-yaml "^3.13.1" + is-path-inside "^3.0.3" + js-sdsl "^4.1.4" + js-yaml "^4.1.0" json-stable-stringify-without-jsonify "^1.0.1" levn "^0.4.1" lodash.merge "^4.6.2" - minimatch "^3.0.4" + minimatch "^3.1.2" natural-compare "^1.4.0" optionator "^0.9.1" - progress "^2.0.0" - regexpp "^3.1.0" - semver "^7.2.1" - strip-ansi "^6.0.0" + strip-ansi "^6.0.1" strip-json-comments "^3.1.0" - table "^6.0.9" text-table "^0.2.0" - v8-compile-cache "^2.0.3" -espree@^7.3.0, espree@^7.3.1: - version "7.3.1" - resolved "https://registry.yarnpkg.com/espree/-/espree-7.3.1.tgz#f2df330b752c6f55019f8bd89b7660039c1bbbb6" - integrity sha512-v3JCNCE64umkFpmkFGqzVKsOT0tN1Zr+ueqLZfpV1Ob8e+CEgPWa+OxCoGH3tnhimMKIaBm4m/vaRpJ/krRz2g== +espree@^9.5.1: + version "9.5.1" + resolved "https://registry.yarnpkg.com/espree/-/espree-9.5.1.tgz#4f26a4d5f18905bf4f2e0bd99002aab807e96dd4" + integrity sha512-5yxtHSZXRSW5pvv3hAlXM5+/Oswi1AUFqBmbibKb5s6bp3rGIDkyXU6xCoyuuLhijr4SFwPrXRoZjz0AZDN9tg== dependencies: - acorn "^7.4.0" - acorn-jsx "^5.3.1" - eslint-visitor-keys "^1.3.0" + acorn "^8.8.0" + acorn-jsx "^5.3.2" + eslint-visitor-keys "^3.4.0" -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.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== -esquery@^1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.4.0.tgz#2148ffc38b82e8c7057dfed48425b3e61f0f24a5" - integrity sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w== +esquery@^1.4.2: + version "1.5.0" + resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.5.0.tgz#6ce17738de8577694edd7361c57182ac8cb0db0b" + integrity sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg== dependencies: estraverse "^5.1.0" @@ -4964,10 +5271,20 @@ events@^3.3.0: resolved "https://registry.yarnpkg.com/events/-/events-3.3.0.tgz#31a95ad0a924e2d2c419a813aeb2c4e878ea7400" integrity sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q== -exec-sh@^0.3.2: - version "0.3.6" - resolved "https://registry.yarnpkg.com/exec-sh/-/exec-sh-0.3.6.tgz#ff264f9e325519a60cb5e273692943483cca63bc" - integrity sha512-nQn+hI3yp+oD0huYhKwvYI32+JFeq+XkNcD1GAo3Y/MjxsfVGmrrzrnzjWiNY6f+pUCP440fThsFh5gZrRAU/w== +execa@5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/execa/-/execa-5.0.0.tgz#4029b0007998a841fbd1032e5f4de86a3c1e3376" + integrity sha512-ov6w/2LCiuyO4RLYGdpFGjkcs0wMTgGE8PrkTHikeUy5iJekXyPIKUjifk5CsE0pt7sMCrMZ3YNqoCj6idQOnQ== + dependencies: + cross-spawn "^7.0.3" + get-stream "^6.0.0" + human-signals "^2.1.0" + is-stream "^2.0.0" + merge-stream "^2.0.0" + npm-run-path "^4.0.1" + onetime "^5.1.2" + signal-exit "^3.0.3" + strip-final-newline "^2.0.0" execa@^1.0.0: version "1.0.0" @@ -5015,15 +5332,16 @@ expand-brackets@^2.1.4: snapdragon "^0.8.1" to-regex "^3.0.1" -expect@^27.5.1: - version "27.5.1" - resolved "https://registry.yarnpkg.com/expect/-/expect-27.5.1.tgz#83ce59f1e5bdf5f9d2b94b61d2050db48f3fef74" - integrity sha512-E1q5hSUG2AmYQwQJ041nvgpkODHQvB+RKlB4IYdru6uJsyFTRyZAP463M+1lINorwbqAmUggi6+WwkD8lCS/Dw== +expect@^29.0.0, expect@^29.5.0: + version "29.5.0" + resolved "https://registry.yarnpkg.com/expect/-/expect-29.5.0.tgz#68c0509156cb2a0adb8865d413b137eeaae682f7" + integrity sha512-yM7xqUrCO2JdpFo4XpM82t+PJBFybdqoQuJLDGeDX2ij8NZzqRHyu3Hp188/JX7SWqud+7t4MUdvcgGBICMHZg== dependencies: - "@jest/types" "^27.5.1" - jest-get-type "^27.5.1" - jest-matcher-utils "^27.5.1" - jest-message-util "^27.5.1" + "@jest/expect-utils" "^29.5.0" + jest-get-type "^29.4.3" + jest-matcher-utils "^29.5.0" + jest-message-util "^29.5.0" + jest-util "^29.5.0" expo-modules-autolinking@^0.0.3: version "0.0.3" @@ -5037,20 +5355,20 @@ expo-modules-autolinking@^0.0.3: fs-extra "^9.1.0" expo-random@*: - version "12.3.0" - resolved "https://registry.yarnpkg.com/expo-random/-/expo-random-12.3.0.tgz#4a45bcb14e285a4a9161e4a5dc82ff6c3fc2ac0c" - integrity sha512-q+AsTfGNT+Q+fb2sRrYtRkI3g5tV4H0kuYXM186aueILGO/vLn/YYFa7xFZj1IZ8LJZg2h96JDPDpsqHfRG2mQ== + version "13.1.1" + resolved "https://registry.yarnpkg.com/expo-random/-/expo-random-13.1.1.tgz#15e781911d5db4fbcee75e26ac109bc2523fe00c" + integrity sha512-+KkhGp7xW45GvMRzlcSOzvDwzTgyXo6C84GaG4GI43rOdECBQ2lGUJ12st39OtfZm1lORNskpi66DjnuJ73g9w== dependencies: base64-js "^1.3.0" express@^4.17.1: - version "4.18.1" - resolved "https://registry.yarnpkg.com/express/-/express-4.18.1.tgz#7797de8b9c72c857b9cd0e14a5eea80666267caf" - integrity sha512-zZBcOX9TfehHQhtupq57OF8lFZ3UZi08Y97dwFCkD8p9d/d2Y3M+ykKcwaMDEL+4qyUolgBDX6AblpR3fL212Q== + version "4.18.2" + resolved "https://registry.yarnpkg.com/express/-/express-4.18.2.tgz#3fabe08296e930c796c19e3c516979386ba9fd59" + integrity sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ== dependencies: accepts "~1.3.8" array-flatten "1.1.1" - body-parser "1.20.0" + body-parser "1.20.1" content-disposition "0.5.4" content-type "~1.0.4" cookie "0.5.0" @@ -5069,7 +5387,7 @@ express@^4.17.1: parseurl "~1.3.3" path-to-regexp "0.1.7" proxy-addr "~2.0.7" - qs "6.10.3" + qs "6.11.0" range-parser "~1.2.1" safe-buffer "5.2.1" send "0.18.0" @@ -5080,6 +5398,13 @@ express@^4.17.1: utils-merge "1.0.1" vary "~1.1.2" +ext@^1.1.2: + version "1.7.0" + resolved "https://registry.yarnpkg.com/ext/-/ext-1.7.0.tgz#0ea4383c0103d60e70be99e9a7f11027a33c4f5f" + integrity sha512-6hxeJYaL110a9b5TEJSj0gojyHQAmA2ch5Os+ySCiA1QGdS697XWY1pzsrSjqA9LDEEgdB/KypIlR59RcLuHYw== + dependencies: + type "^2.7.2" + extend-shallow@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/extend-shallow/-/extend-shallow-2.0.1.tgz#51af7d614ad9a9f610ea1bafbb989d6b1c56890f" @@ -5095,11 +5420,6 @@ extend-shallow@^3.0.0, extend-shallow@^3.0.2: assign-symbols "^1.0.0" is-extendable "^1.0.1" -extend@~3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa" - integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g== - external-editor@^3.0.3: version "3.1.0" resolved "https://registry.yarnpkg.com/external-editor/-/external-editor-3.1.0.tgz#cb03f740befae03ea4d283caed2741a83f335495" @@ -5123,16 +5443,6 @@ extglob@^2.0.4: snapdragon "^0.8.1" to-regex "^3.0.1" -extsprintf@1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.3.0.tgz#96918440e3041a7a414f8c52e3c574eb3c3e1e05" - integrity sha512-11Ndz7Nv+mvAC1j0ktTa7fAb0vLyGGX+rMHNBYQviQDGU0Hw7lhctJANqbPhu9nV9/izT/IntTgZ7Im/9LJs9g== - -extsprintf@^1.2.0: - version "1.4.1" - resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.4.1.tgz#8d172c064867f235c0c84a596806d279bf4bcc07" - integrity sha512-Wrk35e8ydCKDj/ArClo1VrPVmN8zph5V4AtHwIuHhvMXsKf73UT3BOD+azBIW+3wOJ4FhEH7zyaJCFvChjYvMA== - fast-base64-decode@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/fast-base64-decode/-/fast-base64-decode-1.0.0.tgz#b434a0dd7d92b12b43f26819300d2dafb83ee418" @@ -5148,7 +5458,18 @@ fast-diff@^1.1.2: resolved "https://registry.yarnpkg.com/fast-diff/-/fast-diff-1.2.0.tgz#73ee11982d86caaf7959828d519cfe927fac5f03" integrity sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w== -fast-glob@^3.2.5, fast-glob@^3.2.9: +fast-glob@3.2.7: + version "3.2.7" + resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.7.tgz#fd6cb7a2d7e9aa7a7846111e85a196d6b2f766a1" + integrity sha512-rYGMRwip6lUMvYD3BTScMwT1HtAs2d71SMv66Vrxs0IekGZEjhM0pcMfjQPnknBt2zeCwQMEupiN02ZP4DiT1Q== + dependencies: + "@nodelib/fs.stat" "^2.0.2" + "@nodelib/fs.walk" "^1.2.3" + glob-parent "^5.1.2" + merge2 "^1.3.0" + micromatch "^4.0.4" + +fast-glob@^3.2.11, fast-glob@^3.2.12, fast-glob@^3.2.5, fast-glob@^3.2.9: version "3.2.12" resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.12.tgz#7f39ec99c2e6ab030337142da9e0c18f37afae80" integrity sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w== @@ -5159,12 +5480,12 @@ fast-glob@^3.2.5, fast-glob@^3.2.9: merge2 "^1.3.0" micromatch "^4.0.4" -fast-json-stable-stringify@2.x, fast-json-stable-stringify@^2.0.0: +fast-json-stable-stringify@2.x, fast-json-stable-stringify@^2.0.0, fast-json-stable-stringify@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== -fast-levenshtein@^2.0.6, fast-levenshtein@~2.0.6: +fast-levenshtein@^2.0.6: version "2.0.6" resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" integrity sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw== @@ -5174,10 +5495,17 @@ fast-text-encoding@^1.0.3: resolved "https://registry.yarnpkg.com/fast-text-encoding/-/fast-text-encoding-1.0.6.tgz#0aa25f7f638222e3396d72bf936afcf1d42d6867" integrity sha512-VhXlQgj9ioXCqGstD37E/HBeqEGV/qOD/kmbVG8h5xKBYvM1L3lR1Zn4555cQ8GkYbJa8aJSipLPndE1k6zK2w== +fast-xml-parser@^4.0.12: + version "4.2.2" + resolved "https://registry.yarnpkg.com/fast-xml-parser/-/fast-xml-parser-4.2.2.tgz#cb7310d1e9cf42d22c687b0fae41f3c926629368" + integrity sha512-DLzIPtQqmvmdq3VUKR7T6omPK/VCRNqgFlGtbESfyhcH2R4I8EzK1/K6E8PkRCK2EabWrUHK32NjYRbEFnnz0Q== + dependencies: + strnum "^1.0.5" + fastq@^1.6.0: - version "1.13.0" - resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.13.0.tgz#616760f88a7526bdfc596b7cab8c18938c36b98c" - integrity sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw== + version "1.15.0" + resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.15.0.tgz#d04d07c6a2a68fe4599fea8d2e103a937fae6b3a" + integrity sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw== dependencies: reusify "^1.0.4" @@ -5193,7 +5521,7 @@ fetch-blob@^2.1.1: resolved "https://registry.yarnpkg.com/fetch-blob/-/fetch-blob-2.1.2.tgz#a7805db1361bd44c1ef62bb57fb5fe8ea173ef3c" integrity sha512-YKqtUDwqLyfyMnmbw8XD6Q8j9i/HggKtPEI+pZ1+8bvheBu78biSmNaXWusx1TauGqtUUGx/cBb1mKdq2rLYow== -ffi-napi@^4.0.3: +ffi-napi@4.0.3, ffi-napi@^4.0.3: version "4.0.3" resolved "https://registry.yarnpkg.com/ffi-napi/-/ffi-napi-4.0.3.tgz#27a8d42a8ea938457154895c59761fbf1a10f441" integrity sha512-PMdLCIvDY9mS32RxZ0XGb95sonPRal8aqRhLbeEtWKZTe2A87qRFG9HjOhvG8EX2UmQw5XNRMIOT+1MYlWmdeg== @@ -5206,11 +5534,11 @@ ffi-napi@^4.0.3: ref-struct-di "^1.1.0" figlet@^1.5.2: - version "1.5.2" - resolved "https://registry.yarnpkg.com/figlet/-/figlet-1.5.2.tgz#dda34ff233c9a48e36fcff6741aeb5bafe49b634" - integrity sha512-WOn21V8AhyE1QqVfPIVxe3tupJacq1xGkPTB4iagT6o+P2cAgEOOwIxMftr4+ZCTI6d551ij9j61DFr0nsP2uQ== + version "1.6.0" + resolved "https://registry.yarnpkg.com/figlet/-/figlet-1.6.0.tgz#812050fa9f01043b4d44ddeb11f20fb268fa4b93" + integrity sha512-31EQGhCEITv6+hi2ORRPyn3bulaV9Fl4xOdR169cBzH/n1UqcxsiSB/noo6SJdD7Kfb1Ljit+IgR1USvF/XbdA== -figures@^3.0.0: +figures@3.2.0, figures@^3.0.0: version "3.2.0" resolved "https://registry.yarnpkg.com/figures/-/figures-3.2.0.tgz#625c18bd293c604dc4a8ddb2febf0c88341746af" integrity sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg== @@ -5229,6 +5557,18 @@ file-uri-to-path@1.0.0: resolved "https://registry.yarnpkg.com/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz#553a7b8446ff6f684359c445f1e37a05dacc33dd" integrity sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw== +file-url@3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/file-url/-/file-url-3.0.0.tgz#247a586a746ce9f7a8ed05560290968afc262a77" + integrity sha512-g872QGsHexznxkIAdK8UiZRe7SkE6kvylShU4Nsj8NvfvZag7S0QuQ4IgvPDkk75HxgjIVDwycFTDAgIiO4nDA== + +filelist@^1.0.1: + version "1.0.4" + resolved "https://registry.yarnpkg.com/filelist/-/filelist-1.0.4.tgz#f78978a1e944775ff9e62e744424f215e58352b5" + integrity sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q== + dependencies: + minimatch "^5.0.1" + fill-range@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-4.0.0.tgz#d544811d428f98eb06a63dc402d2403c328c38f7" @@ -5293,6 +5633,14 @@ find-replace@^3.0.0: dependencies: array-back "^3.0.1" +find-up@5.0.0, find-up@^5.0.0, find-up@~5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-5.0.0.tgz#4c92819ecb7083561e4f4a240a86be5198f536fc" + integrity sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng== + dependencies: + locate-path "^6.0.0" + path-exists "^4.0.0" + find-up@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/find-up/-/find-up-2.1.0.tgz#45d1b7e506c717ddd482775a2b77920a3c0c57a7" @@ -5315,13 +5663,14 @@ find-up@^4.0.0, find-up@^4.1.0: locate-path "^5.0.0" path-exists "^4.0.0" -find-up@~5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/find-up/-/find-up-5.0.0.tgz#4c92819ecb7083561e4f4a240a86be5198f536fc" - integrity sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng== +find-workspaces@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/find-workspaces/-/find-workspaces-0.1.0.tgz#c01ddc81a1814b2c18927b26adb82afc97b63cea" + integrity sha512-DmHumOdSCtwY6qW6Syx3a/W6ZGYLhGiwqWCiPOsld4sxP9yeRh3LraKeu+G3l5ilgt8jOUAgjDHT4MOFZ8dQ3Q== dependencies: - locate-path "^6.0.0" - path-exists "^4.0.0" + fast-glob "^3.2.12" + type-fest "^3.2.0" + yaml "^2.1.3" flat-cache@^3.0.4: version "3.0.4" @@ -5331,31 +5680,43 @@ flat-cache@^3.0.4: flatted "^3.1.0" rimraf "^3.0.2" +flat@^5.0.2: + version "5.0.2" + resolved "https://registry.yarnpkg.com/flat/-/flat-5.0.2.tgz#8ca6fe332069ffa9d324c327198c598259ceb241" + integrity sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ== + flatted@^3.1.0: version "3.2.7" resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.2.7.tgz#609f39207cb614b89d0765b477cb2d437fbf9787" integrity sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ== flow-parser@0.*: - version "0.187.1" - resolved "https://registry.yarnpkg.com/flow-parser/-/flow-parser-0.187.1.tgz#52b2c7ebd7544b75bda0676380138bc5b3de3177" - integrity sha512-ZvlTeakTTMmYGukt4EIQtLEp4ie45W+jK325uukGgiqFg2Rl7TdpOJQbOLUN2xMeGS+WvXaK0uIJ3coPGDXFGQ== + version "0.204.1" + resolved "https://registry.yarnpkg.com/flow-parser/-/flow-parser-0.204.1.tgz#a894bc5e8ad520134c1d13383b8991d03cbf8b01" + integrity sha512-PoeSF0VhSORn3hYzD/NxsQjXX1iLU0UZXzVwZXnRWjeVsedmvDo4epd7PtCQjxveGajmVlyVW35BOOOkqLqJpw== + +flow-parser@^0.185.0: + version "0.185.2" + resolved "https://registry.yarnpkg.com/flow-parser/-/flow-parser-0.185.2.tgz#cb7ee57f77377d6c5d69a469e980f6332a15e492" + integrity sha512-2hJ5ACYeJCzNtiVULov6pljKOLygy0zddoqSI1fFetM+XRPpRshFdGEijtqlamA1XwyZ+7rhryI6FQFzvtLWUQ== + +follow-redirects@^1.14.0, follow-redirects@^1.15.0: + version "1.15.2" + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.2.tgz#b460864144ba63f2681096f274c4e57026da2c13" + integrity sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA== -flow-parser@^0.121.0: - version "0.121.0" - resolved "https://registry.yarnpkg.com/flow-parser/-/flow-parser-0.121.0.tgz#9f9898eaec91a9f7c323e9e992d81ab5c58e618f" - integrity sha512-1gIBiWJNR0tKUNv8gZuk7l9rVX06OuLzY9AoGio7y/JT4V1IZErEMEq2TJS+PFcw/y0RshZ1J/27VfK1UQzYVg== +for-each@^0.3.3: + version "0.3.3" + resolved "https://registry.yarnpkg.com/for-each/-/for-each-0.3.3.tgz#69b447e88a0a5d32c3e7084f3f1710034b21376e" + integrity sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw== + dependencies: + is-callable "^1.1.3" for-in@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80" integrity sha512-7EwmXrOjyL+ChxMhmG5lnW9MPt1aIeZEwKhQzoBUdTV0N3zuwWDZYVJatDvZ2OyzPUvdIAZDsCetk3coyMfcnQ== -forever-agent@~0.6.1: - version "0.6.1" - resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91" - integrity sha512-j0KLYPhm6zeac4lz3oJ3o65qvgQCcPubiyotZrXqEaG4hNagNYO8qdlUrX5vwqv9ohqeT/Z3j6+yW067yWWdUw== - form-data@^3.0.0: version "3.0.1" resolved "https://registry.yarnpkg.com/form-data/-/form-data-3.0.1.tgz#ebd53791b78356a99af9a300d4282c4d5eb9755f" @@ -5365,13 +5726,13 @@ form-data@^3.0.0: combined-stream "^1.0.8" mime-types "^2.1.12" -form-data@~2.3.2: - version "2.3.3" - resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.3.3.tgz#dcce52c05f644f298c6a7ab936bd724ceffbf3a6" - integrity sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ== +form-data@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.0.tgz#93919daeaf361ee529584b9b31664dc12c9fa452" + integrity sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww== dependencies: asynckit "^0.4.0" - combined-stream "^1.0.6" + combined-stream "^1.0.8" mime-types "^2.1.12" forwarded@0.2.0: @@ -5391,14 +5752,29 @@ fresh@0.5.2: resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7" integrity sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q== -fs-extra@^1.0.0: +fs-constants@^1.0.0: version "1.0.0" - resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-1.0.0.tgz#cd3ce5f7e7cb6145883fcae3191e9877f8587950" - integrity sha512-VerQV6vEKuhDWD2HGOybV6v5I73syoc/cXAbKlgTC7M/oFVEtklWlp9QH2Ijw3IaWDOQcMkldSPa7zXy79Z/UQ== + resolved "https://registry.yarnpkg.com/fs-constants/-/fs-constants-1.0.0.tgz#6be0de9be998ce16af8afc24497b9ee9b7ccd9ad" + integrity sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow== + +fs-extra@9.1.0, fs-extra@^9.1.0: + version "9.1.0" + resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-9.1.0.tgz#5954460c764a8da2094ba3554bf839e6b9a7c86d" + integrity sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ== dependencies: - graceful-fs "^4.1.2" - jsonfile "^2.1.0" - klaw "^1.0.0" + at-least-node "^1.0.0" + 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.yarnpkg.com/fs-extra/-/fs-extra-11.1.1.tgz#da69f7c39f3b002378b0954bb6ae7efdc0876e2d" + integrity sha512-MGIE4HOvQCeUCzmlHs0vXpih4ysz4wg9qiSAu6cd42lVwPbTM1TjV7RusoyQqMmk/95gdQZX72u+YW+c3eEpFQ== + dependencies: + graceful-fs "^4.2.0" + jsonfile "^6.0.1" + universalify "^2.0.0" fs-extra@^8.1.0: version "8.1.0" @@ -5409,16 +5785,6 @@ fs-extra@^8.1.0: jsonfile "^4.0.0" universalify "^0.1.0" -fs-extra@^9.1.0: - version "9.1.0" - resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-9.1.0.tgz#5954460c764a8da2094ba3554bf839e6b9a7c86d" - integrity sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ== - dependencies: - at-least-node "^1.0.0" - graceful-fs "^4.2.0" - jsonfile "^6.0.1" - universalify "^2.0.0" - fs-minipass@^1.2.7: version "1.2.7" resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-1.2.7.tgz#ccff8570841e7fe4265693da88936c55aed7f7c7" @@ -5433,12 +5799,19 @@ fs-minipass@^2.0.0, fs-minipass@^2.1.0: dependencies: minipass "^3.0.0" +fs-minipass@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-3.0.1.tgz#853809af15b6d03e27638d1ab6432e6b378b085d" + integrity sha512-MhaJDcFRTuLidHrIttu0RDGyyXs/IYHVmlcxfLAEFIWjc1vdLAkdwT7Ace2u7DbitWC0toKMl5eJZRYNVreIMw== + dependencies: + minipass "^4.0.0" + fs.realpath@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw== -fsevents@^2.1.2, fsevents@^2.3.2: +fsevents@^2.3.2: version "2.3.2" resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a" integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA== @@ -5458,12 +5831,7 @@ function.prototype.name@^1.1.5: es-abstract "^1.19.0" functions-have-names "^1.2.2" -functional-red-black-tree@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz#1b0ab3bd553b2a0d6399d29c0e3ea0b252078327" - integrity sha512-dsKNQNdj6xA3T+QlADDA7mOSlX0qiMINjn0cgr+eGHGsbSHzTabcIogz2+p/iqP1Xs6EP/sS2SbqH+brGTbq0g== - -functions-have-names@^1.2.2: +functions-have-names@^1.2.2, functions-have-names@^1.2.3: version "1.2.3" resolved "https://registry.yarnpkg.com/functions-have-names/-/functions-have-names-1.2.3.tgz#0404fe4ee2ba2f607f0e0ec3c80bae994133b834" integrity sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ== @@ -5497,6 +5865,20 @@ gauge@^4.0.3: strip-ansi "^6.0.1" wide-align "^1.1.5" +gauge@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/gauge/-/gauge-5.0.0.tgz#e270ca9d97dae84abf64e5277ef1ebddc7dd1e2f" + integrity sha512-0s5T5eciEG7Q3ugkxAkFtaDhrrhXsCRivA5y8C9WMHWuI8UlMOJg7+Iwf7Mccii+Dfs3H5jHepU0joPVyQU0Lw== + dependencies: + aproba "^1.0.3 || ^2.0.0" + color-support "^1.1.3" + console-control-strings "^1.1.0" + has-unicode "^2.0.1" + signal-exit "^3.0.7" + string-width "^4.2.3" + strip-ansi "^6.0.1" + wide-align "^1.1.5" + gauge@~2.7.3: version "2.7.4" resolved "https://registry.yarnpkg.com/gauge/-/gauge-2.7.4.tgz#2c03405c7538c39d7eb37b317022e325fb018bf7" @@ -5521,10 +5903,10 @@ get-caller-file@^2.0.1, get-caller-file@^2.0.5: resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== -get-intrinsic@^1.0.2, get-intrinsic@^1.1.0, get-intrinsic@^1.1.1, get-intrinsic@^1.1.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.1.3.tgz#063c84329ad93e83893c7f4f243ef63ffa351385" - integrity sha512-QJVz1Tj7MS099PevUG5jvnt9tSkXN8K14dxQlikJuPt4uD9hHAHjLyLBiLR5zELelBdD9QNRAXZzsJx0WaDL9A== +get-intrinsic@^1.0.2, get-intrinsic@^1.1.1, get-intrinsic@^1.1.3, get-intrinsic@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.2.0.tgz#7ad1dc0535f3a2904bba075772763e5051f6d05f" + integrity sha512-L049y6nFOuom5wGyRc3/gdTLO94dySVKRACj1RmJZBQXlbTMhtNIgkWkUHq+jYmZvKf14EW1EoJnnjbmoHij0Q== dependencies: function-bind "^1.1.1" has "^1.0.3" @@ -5545,11 +5927,16 @@ get-pkg-repo@^4.0.0: through2 "^2.0.0" yargs "^16.2.0" -get-port@^5.1.1: +get-port@5.1.1: version "5.1.1" resolved "https://registry.yarnpkg.com/get-port/-/get-port-5.1.1.tgz#0469ed07563479de6efb986baf053dcd7d4e3193" integrity sha512-g/Q1aTSDOxFpchXC4i8ZWvxA1lnPqx/JHqcpIw0/LX9T8x/GBbi6YnlN5nhaKIFkT8oFsscUKgDJYxfwfS6QsQ== +get-stream@6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-6.0.0.tgz#3e0012cb6827319da2706e601a1583e8629a6718" + integrity sha512-A1B3Bh1UmL0bidM/YX2NsCOTnGJePL9rO/M+Mw3m9f2gUpfokS0hi5Eah0WSUEWZdZhIZtMjkIYS7mDfOqNHbg== + get-stream@^4.0.0: version "4.1.0" resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-4.1.0.tgz#c1b255575f3dc21d59bfc79cd3d2b46b1c3a54b5" @@ -5575,6 +5962,11 @@ get-symbol-from-current-process-h@^1.0.1, get-symbol-from-current-process-h@^1.0 resolved "https://registry.yarnpkg.com/get-symbol-from-current-process-h/-/get-symbol-from-current-process-h-1.0.2.tgz#510af52eaef873f7028854c3377f47f7bb200265" integrity sha512-syloC6fsCt62ELLrr1VKBM1ggOpMdetX9hTrdW77UQdcApPHLmf7CI7OKcN1c9kYuNxKcDe4iJ4FY9sX3aw2xw== +get-tsconfig@^4.5.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/get-tsconfig/-/get-tsconfig-4.5.0.tgz#6d52d1c7b299bd3ee9cd7638561653399ac77b0f" + integrity sha512-MjhiaIWCJ1sAU4pIQ5i5OfOuHHxVo1oYeNsWTON7jxYkod8pHocXeh+SSbmu5OZZZK73B6cbJ2XADzXehLyovQ== + get-uv-event-loop-napi-h@^1.0.5: version "1.0.6" resolved "https://registry.yarnpkg.com/get-uv-event-loop-napi-h/-/get-uv-event-loop-napi-h-1.0.6.tgz#42b0b06b74c3ed21fbac8e7c72845fdb7a200208" @@ -5587,13 +5979,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== -getpass@^0.1.1: - version "0.1.7" - resolved "https://registry.yarnpkg.com/getpass/-/getpass-0.1.7.tgz#5eff8e3e684d569ae4cb2b1282604e8ba62149fa" - integrity sha512-0fzj9JxOLfJ+XGLhR8ze3unN0KZCgZwiSSDz168VERjK8Wl8kVSdcu2kspd4s4wtAa1y/qrVRiAA0WclVsu0ng== - dependencies: - assert-plus "^1.0.0" - git-config@0.0.7: version "0.0.7" resolved "https://registry.yarnpkg.com/git-config/-/git-config-0.0.7.tgz#a9c8a3ef07a776c3d72261356d8b727b62202b28" @@ -5628,20 +6013,20 @@ git-semver-tags@^4.1.1: meow "^8.0.0" semver "^6.0.0" -git-up@^4.0.0: - version "4.0.5" - resolved "https://registry.yarnpkg.com/git-up/-/git-up-4.0.5.tgz#e7bb70981a37ea2fb8fe049669800a1f9a01d759" - integrity sha512-YUvVDg/vX3d0syBsk/CKUTib0srcQME0JyHkL5BaYdwLsiCslPWmDSi8PUMo9pXYjrryMcmsCoCgsTpSCJEQaA== +git-up@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/git-up/-/git-up-7.0.0.tgz#bace30786e36f56ea341b6f69adfd83286337467" + integrity sha512-ONdIrbBCFusq1Oy0sC71F5azx8bVkvtZtMJAsv+a6lz5YAmbNnLD6HAB4gptHZVLPR8S2/kVN6Gab7lryq5+lQ== dependencies: - is-ssh "^1.3.0" - parse-url "^6.0.0" + is-ssh "^1.4.0" + parse-url "^8.1.0" -git-url-parse@^11.4.4: - version "11.6.0" - resolved "https://registry.yarnpkg.com/git-url-parse/-/git-url-parse-11.6.0.tgz#c634b8de7faa66498a2b88932df31702c67df605" - integrity sha512-WWUxvJs5HsyHL6L08wOusa/IXYtMuCAhrMmnTjQPpBU0TTHyDhnOATNH3xNQz7YOQUsqIIPTGr4xiVti1Hsk5g== +git-url-parse@13.1.0: + version "13.1.0" + resolved "https://registry.yarnpkg.com/git-url-parse/-/git-url-parse-13.1.0.tgz#07e136b5baa08d59fabdf0e33170de425adf07b4" + integrity sha512-5FvPJP/70WkIprlUZ33bm4UAaFdjcLkJLpWft1BeZKqwR0uhhNGoKwlUaPtVb4LxCSQ++erHapRak9kWGj+FCA== dependencies: - git-up "^4.0.0" + git-up "^7.0.0" gitconfiglocal@^1.0.0: version "1.0.0" @@ -5650,14 +6035,33 @@ gitconfiglocal@^1.0.0: dependencies: ini "^1.3.2" -glob-parent@^5.1.1, glob-parent@^5.1.2: +glob-parent@5.1.2, glob-parent@^5.1.2: version "5.1.2" resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== dependencies: is-glob "^4.0.1" -glob@^7.0.0, glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6, glob@^7.2.0: +glob-parent@^6.0.2: + version "6.0.2" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-6.0.2.tgz#6d237d99083950c79290f24c7642a3de9a28f9e3" + integrity sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A== + dependencies: + is-glob "^4.0.3" + +glob@7.1.4: + version "7.1.4" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.4.tgz#aa608a2f6c577ad357e1ae5a5c26d9a8d1969255" + integrity sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.0.4" + once "^1.3.0" + path-is-absolute "^1.0.0" + +glob@^7.1.3, glob@^7.1.4: version "7.2.3" resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b" integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q== @@ -5669,19 +6073,52 @@ glob@^7.0.0, glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6, gl once "^1.3.0" path-is-absolute "^1.0.0" +glob@^8.0.1: + version "8.1.0" + resolved "https://registry.yarnpkg.com/glob/-/glob-8.1.0.tgz#d388f656593ef708ee3e34640fdfb99a9fd1c33e" + integrity sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^5.0.1" + once "^1.3.0" + +glob@^9.2.0, glob@^9.3.0, glob@^9.3.1: + version "9.3.5" + resolved "https://registry.yarnpkg.com/glob/-/glob-9.3.5.tgz#ca2ed8ca452781a3009685607fdf025a899dfe21" + integrity sha512-e1LleDykUz2Iu+MTYdkSsuWX8lvAjAcs0Xef0lNIu0S2wOAzuTxCJtcd9S3cijlwYF18EsU3rzb8jPVobxDh9Q== + dependencies: + fs.realpath "^1.0.0" + minimatch "^8.0.2" + minipass "^4.2.4" + path-scurry "^1.6.1" + globals@^11.1.0: version "11.12.0" resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e" integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA== -globals@^13.6.0, globals@^13.9.0: - version "13.17.0" - resolved "https://registry.yarnpkg.com/globals/-/globals-13.17.0.tgz#902eb1e680a41da93945adbdcb5a9f361ba69bd4" - integrity sha512-1C+6nQRb1GwGMKm2dH/E7enFAMxGTmGI7/dEdhy/DNelv85w9B72t3uc5frtMNXIbzrarJJ/lTCjcaZwbLJmyw== +globals@^13.19.0: + version "13.20.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-13.20.0.tgz#ea276a1e508ffd4f1612888f9d1bad1e2717bf82" + integrity sha512-Qg5QtVkCy/kv3FUSlu4ukeZDVf9ee0iXLAUYX13gbR17bnejFTzr4iS9bY7kwCf1NztRNm1t91fjOiyx4CSwPQ== dependencies: type-fest "^0.20.2" -globby@^11.0.2, globby@^11.0.3: +globalthis@^1.0.1, globalthis@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/globalthis/-/globalthis-1.0.3.tgz#5852882a52b80dc301b0660273e1ed082f0b6ccf" + integrity sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA== + dependencies: + define-properties "^1.1.3" + +globalyzer@0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/globalyzer/-/globalyzer-0.1.0.tgz#cb76da79555669a1519d5a8edf093afaa0bf1465" + integrity sha512-40oNTM9UfG6aBmuKxk/giHn5nQ8RVz/SS4Ir6zgzOv9/qC3kKZ9v4etGTcJbEl/NyVQH7FGU7d+X1egr57Md2Q== + +globby@11.1.0, globby@^11.0.1, globby@^11.1.0: version "11.1.0" resolved "https://registry.yarnpkg.com/globby/-/globby-11.1.0.tgz#bd4be98bb042f83d796f7e3811991fbe82a0d34b" integrity sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g== @@ -5693,11 +6130,44 @@ globby@^11.0.2, globby@^11.0.3: merge2 "^1.4.1" slash "^3.0.0" -graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.2, graceful-fs@^4.1.3, graceful-fs@^4.1.6, graceful-fs@^4.1.9, graceful-fs@^4.2.0, graceful-fs@^4.2.2, graceful-fs@^4.2.3, graceful-fs@^4.2.4, graceful-fs@^4.2.6, graceful-fs@^4.2.9: +globby@^13.1.3: + version "13.1.4" + resolved "https://registry.yarnpkg.com/globby/-/globby-13.1.4.tgz#2f91c116066bcec152465ba36e5caa4a13c01317" + integrity sha512-iui/IiiW+QrJ1X1hKH5qwlMQyv34wJAYwH1vrf8b9kBA4sNiif3gKsMHa+BrdnOpEudWjpotfa7LrTzB1ERS/g== + dependencies: + dir-glob "^3.0.1" + fast-glob "^3.2.11" + ignore "^5.2.0" + merge2 "^1.4.1" + slash "^4.0.0" + +globrex@^0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/globrex/-/globrex-0.1.2.tgz#dd5d9ec826232730cd6793a5e33a9302985e6098" + integrity sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg== + +gopd@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/gopd/-/gopd-1.0.1.tgz#29ff76de69dac7489b7c0918a5788e56477c332c" + integrity sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA== + dependencies: + get-intrinsic "^1.1.3" + +graceful-fs@4.2.10: version "4.2.10" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.10.tgz#147d3a006da4ca3ce14728c7aefc287c367d7a6c" integrity sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA== +graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.2, graceful-fs@^4.1.3, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.4, graceful-fs@^4.2.6, graceful-fs@^4.2.9: + version "4.2.11" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3" + integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== + +grapheme-splitter@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz#9cf3a665c6247479896834af35cf1dbb4400767e" + integrity sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ== + handlebars@^4.7.6, handlebars@^4.7.7: version "4.7.7" resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.7.7.tgz#9ce33416aad02dbd6c8fafa8240d5d98004945a1" @@ -5710,19 +6180,6 @@ handlebars@^4.7.6, handlebars@^4.7.7: optionalDependencies: uglify-js "^3.1.4" -har-schema@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-2.0.0.tgz#a94c2224ebcac04782a0d9035521f24735b7ec92" - integrity sha512-Oqluz6zhGX8cyRaTQlFMPw80bSJVG2x/cFb8ZPhUILGgHka9SsokCCOQgpveePerqidZOrT14ipqfJb7ILcW5Q== - -har-validator@~5.1.3: - version "5.1.5" - resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-5.1.5.tgz#1f0803b9f8cb20c0fa13822df1ecddb36bde1efd" - integrity sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w== - dependencies: - ajv "^6.12.3" - har-schema "^2.0.0" - hard-rejection@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/hard-rejection/-/hard-rejection-2.1.0.tgz#1c6eda5c1685c63942766d79bb40ae773cecd883" @@ -5750,6 +6207,11 @@ has-property-descriptors@^1.0.0: dependencies: get-intrinsic "^1.1.1" +has-proto@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/has-proto/-/has-proto-1.0.1.tgz#1885c1305538958aff469fef37937c22795408e0" + integrity sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg== + has-symbols@^1.0.2, has-symbols@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.3.tgz#bb7b2c4349251dce87b125f7bdf874aa7c8b39f8" @@ -5762,7 +6224,7 @@ has-tostringtag@^1.0.0: dependencies: has-symbols "^1.0.2" -has-unicode@^2.0.0, has-unicode@^2.0.1: +has-unicode@2.0.1, has-unicode@^2.0.0, has-unicode@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/has-unicode/-/has-unicode-2.0.1.tgz#e0e6fe6a28cf51138855e086d1691e771de2a8b9" integrity sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ== @@ -5803,12 +6265,27 @@ has@^1.0.3: resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796" integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw== dependencies: - function-bind "^1.1.1" + function-bind "^1.1.1" + +hash.js@^1.0.0, hash.js@^1.0.3: + version "1.1.7" + resolved "https://registry.yarnpkg.com/hash.js/-/hash.js-1.1.7.tgz#0babca538e8d4ee4a0f8988d68866537a003cf42" + integrity sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA== + dependencies: + inherits "^2.0.3" + minimalistic-assert "^1.0.1" -hermes-engine@~0.7.0: - version "0.7.2" - resolved "https://registry.yarnpkg.com/hermes-engine/-/hermes-engine-0.7.2.tgz#303cd99d23f68e708b223aec2d49d5872985388b" - integrity sha512-E2DkRaO97gwL98LPhgfkMqhHiNsrAjIfEk3wWYn2Y31xdkdWn0572H7RnVcGujMJVqZNJvtknxlpsUb8Wzc3KA== +hermes-estree@0.8.0: + version "0.8.0" + resolved "https://registry.yarnpkg.com/hermes-estree/-/hermes-estree-0.8.0.tgz#530be27243ca49f008381c1f3e8b18fb26bf9ec0" + integrity sha512-W6JDAOLZ5pMPMjEiQGLCXSSV7pIBEgRR5zGkxgmzGSXHOxqV5dC/M1Zevqpbm9TZDE5tu358qZf8Vkzmsc+u7Q== + +hermes-parser@0.8.0: + version "0.8.0" + resolved "https://registry.yarnpkg.com/hermes-parser/-/hermes-parser-0.8.0.tgz#116dceaba32e45b16d6aefb5c4c830eaeba2d257" + integrity sha512-yZKalg1fTYG5eOiToLUaw69rQfZq/fi+/NtEXRU7N87K/XobNRhRWorh80oSge2lWUiZfTgUvRJH+XgZWrhoqA== + dependencies: + hermes-estree "0.8.0" hermes-profile-transformer@^0.0.6: version "0.0.6" @@ -5817,11 +6294,27 @@ hermes-profile-transformer@^0.0.6: dependencies: source-map "^0.7.3" +hmac-drbg@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/hmac-drbg/-/hmac-drbg-1.0.1.tgz#d2745701025a6c775a6c545793ed502fc0c649a1" + integrity sha512-Tti3gMqLdZfhOQY1Mzf/AanLiqh1WTiJgEj26ZuYQ9fbkLomzGchCws4FyrSd4VkpBfiNhaE1On+lOz894jvXg== + dependencies: + hash.js "^1.0.3" + minimalistic-assert "^1.0.0" + minimalistic-crypto-utils "^1.0.1" + hosted-git-info@^2.1.4: version "2.8.9" resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.9.tgz#dffc0bf9a21c02209090f2aa69429e1414daf3f9" integrity sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw== +hosted-git-info@^3.0.6: + version "3.0.8" + resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-3.0.8.tgz#6e35d4cc87af2c5f816e4cb9ce350ba87a3f370d" + integrity sha512-aXpmwoOhRBrw6X3j0h5RloK4x1OzsxMPyxqIHyNfSe2pypkVTZFpEiRoSipPEPlMrh0HW/XsjkJ5WgnCirpNUw== + dependencies: + lru-cache "^6.0.0" + hosted-git-info@^4.0.0, hosted-git-info@^4.0.1: version "4.1.0" resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-4.1.0.tgz#827b82867e9ff1c8d0c4d9d53880397d2c86d224" @@ -5829,22 +6322,29 @@ hosted-git-info@^4.0.0, hosted-git-info@^4.0.1: dependencies: lru-cache "^6.0.0" -html-encoding-sniffer@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/html-encoding-sniffer/-/html-encoding-sniffer-2.0.1.tgz#42a6dc4fd33f00281176e8b23759ca4e4fa185f3" - integrity sha512-D5JbOMBIR/TVZkubHT+OyT2705QvogUW4IBn6nHd756OwieSF9aDYFj4dv6HHEVGYbHaLETa3WggZYWWMyy3ZQ== +hosted-git-info@^5.0.0: + version "5.2.1" + resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-5.2.1.tgz#0ba1c97178ef91f3ab30842ae63d6a272341156f" + integrity sha512-xIcQYMnhcx2Nr4JTjsFmwwnr9vldugPy9uVm0o87bjqqWMv9GaqsTeT+i99wTl0mk1uLxJtHxLb8kymqTENQsw== dependencies: - whatwg-encoding "^1.0.5" + lru-cache "^7.5.1" + +hosted-git-info@^6.0.0, hosted-git-info@^6.1.1: + version "6.1.1" + resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-6.1.1.tgz#629442c7889a69c05de604d52996b74fe6f26d58" + integrity sha512-r0EI+HBMcXadMrugk0GCQ+6BQV39PiWAZVfq7oIckeGiN7sjRGyQxPdft3nQekFTCQbYxLBH+/axZMeH8UX6+w== + dependencies: + lru-cache "^7.5.1" html-escaper@^2.0.0: version "2.0.2" resolved "https://registry.yarnpkg.com/html-escaper/-/html-escaper-2.0.2.tgz#dfd60027da36a36dfcbe236262c00a5822681453" integrity sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg== -http-cache-semantics@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz#49e91c5cbf36c9b94bcfcd71c23d5249ec74e390" - integrity sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ== +http-cache-semantics@^4.1.0, http-cache-semantics@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz#abe02fcb2985460bf0323be664436ec3476a6d5a" + integrity sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ== http-errors@2.0.0: version "2.0.0" @@ -5866,14 +6366,14 @@ http-proxy-agent@^4.0.1: agent-base "6" debug "4" -http-signature@~1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.2.0.tgz#9aecd925114772f3d95b65a60abb8f7c18fbace1" - integrity sha512-CAbnr6Rz4CYQkLYUtSNXxQPUH2gK8f3iWexVlsnMeD+GjlsQ0Xsy1cOX+mN3dtxYomRy21CiOzU8Uhw6OwncEQ== +http-proxy-agent@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz#5129800203520d434f142bc78ff3c170800f2b43" + integrity sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w== dependencies: - assert-plus "^1.0.0" - jsprim "^1.2.2" - sshpk "^1.7.0" + "@tootallnate/once" "2" + agent-base "6" + debug "4" https-proxy-agent@^5.0.0: version "5.0.1" @@ -5895,11 +6395,6 @@ humanize-ms@^1.2.1: dependencies: ms "^2.0.0" -husky@^7.0.1: - version "7.0.4" - resolved "https://registry.yarnpkg.com/husky/-/husky-7.0.4.tgz#242048245dc49c8fb1bf0cc7cfb98dd722531535" - integrity sha512-vbaCKN2QLtP/vD4yvs6iz6hBEo6wkSzs8HpRah1Z6aGmF2KW5PdYuAd7uX5a+OyBZHBhd+TFLqgjUgytQr4RvQ== - iconv-lite@0.4.24, iconv-lite@^0.4.24, iconv-lite@^0.4.4: version "0.4.24" resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" @@ -5919,22 +6414,31 @@ ieee754@^1.1.13, ieee754@^1.2.1: resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== -ignore-walk@^3.0.1, ignore-walk@^3.0.3: +ignore-walk@^3.0.1: version "3.0.4" resolved "https://registry.yarnpkg.com/ignore-walk/-/ignore-walk-3.0.4.tgz#c9a09f69b7c7b479a5d74ac1a3c0d4236d2a6335" integrity sha512-PY6Ii8o1jMRA1z4F2hRkH/xN59ox43DavKvD3oDpfurRlOJyAHpifIwpbdv1n4jt4ov0jSpw3kQ4GhJnpBL6WQ== dependencies: minimatch "^3.0.4" -ignore@^4.0.6: - version "4.0.6" - resolved "https://registry.yarnpkg.com/ignore/-/ignore-4.0.6.tgz#750e3db5862087b4737ebac8207ffd1ef27b25fc" - integrity sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg== +ignore-walk@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/ignore-walk/-/ignore-walk-5.0.1.tgz#5f199e23e1288f518d90358d461387788a154776" + integrity sha512-yemi4pMf51WKT7khInJqAvsIGzoqYXblnsz0ql8tM+yi1EKYTY1evX4NAbJrLL/Aanr2HyZeluqU+Oi7MGHokw== + dependencies: + minimatch "^5.0.1" + +ignore-walk@^6.0.0: + version "6.0.2" + resolved "https://registry.yarnpkg.com/ignore-walk/-/ignore-walk-6.0.2.tgz#c48f48397cf8ef6174fcc28aa5f8c1de6203d389" + integrity sha512-ezmQ1Dg2b3jVZh2Dh+ar6Eu2MqNSTkyb32HU2MAQQQX9tKM3q/UQ/9lf03lQ5hW+fOeoMnwxwkleZ0xcNp0/qg== + dependencies: + minimatch "^7.4.2" -ignore@^5.1.8, ignore@^5.2.0: - version "5.2.0" - resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.2.0.tgz#6d3bac8fa7fe0d45d9f9be7bac2fc279577e345a" - integrity sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ== +ignore@^5.0.4, ignore@^5.2.0: + version "5.2.4" + resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.2.4.tgz#a291c0c6178ff1b960befe47fcdec301674a6324" + integrity sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ== image-size@^0.6.0: version "0.6.3" @@ -5975,14 +6479,7 @@ indent-string@^4.0.0: resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-4.0.0.tgz#624f8f4497d619b2d9768531d58f4122854d7251" integrity sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg== -indy-sdk-react-native@^0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/indy-sdk-react-native/-/indy-sdk-react-native-0.3.0.tgz#37b20476bf1207d3dea7b66dba65bf44ed0c903a" - integrity sha512-3qaB4R7QDNQRI9ijpSvMaow/HlZYMB2LdJlRtbhefmrjQYwpz9oSqB595NPKajBIoIxzgDaUdBkK7kmwMY90Xg== - dependencies: - buffer "^6.0.2" - -indy-sdk@^1.16.0-dev-1636: +indy-sdk@^1.16.0-dev-1636, indy-sdk@^1.16.0-dev-1655: version "1.16.0-dev-1655" resolved "https://registry.yarnpkg.com/indy-sdk/-/indy-sdk-1.16.0-dev-1655.tgz#098c38df4a6eb4e13f89c0b86ebe9636944b71e0" integrity sha512-MSWRY8rdnGAegs4v4AnzE6CT9O/3JBMUiE45I0Ihj2DMuH+XS1EJZUQEJsyis6aOQzRavv/xVtaBC8o+6azKuw== @@ -6004,7 +6501,7 @@ inflight@^1.0.4: once "^1.3.0" wrappy "1" -inherits@2, inherits@2.0.4, inherits@^2.0.3, inherits@~2.0.3: +inherits@2, inherits@2.0.4, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.3: version "2.0.4" resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== @@ -6019,18 +6516,39 @@ iniparser@~1.0.5: resolved "https://registry.yarnpkg.com/iniparser/-/iniparser-1.0.5.tgz#836d6befe6dfbfcee0bccf1cf9f2acc7027f783d" integrity sha512-i40MWqgTU6h/70NtMsDVVDLjDYWwcIR1yIEVDPfxZIJno9z9L4s83p/V7vAu2i48Vj0gpByrkGFub7ko9XvPrw== -init-package-json@^2.0.2: - version "2.0.5" - resolved "https://registry.yarnpkg.com/init-package-json/-/init-package-json-2.0.5.tgz#78b85f3c36014db42d8f32117252504f68022646" - integrity sha512-u1uGAtEFu3VA6HNl/yUWw57jmKEMx8SKOxHhxjGnOFUiIlFnohKDFg4ZrPpv9wWqk44nDxGJAtqjdQFm+9XXQA== +init-package-json@3.0.2, init-package-json@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/init-package-json/-/init-package-json-3.0.2.tgz#f5bc9bac93f2bdc005778bc2271be642fecfcd69" + integrity sha512-YhlQPEjNFqlGdzrBfDNRLhvoSgX7iQRgSxgsNknRQ9ITXFT7UMfVMWhBTOh2Y+25lRnGrv5Xz8yZwQ3ACR6T3A== dependencies: - npm-package-arg "^8.1.5" + npm-package-arg "^9.0.1" promzard "^0.3.0" - read "~1.0.1" - read-package-json "^4.1.1" + read "^1.0.7" + read-package-json "^5.0.0" semver "^7.3.5" validate-npm-package-license "^3.0.4" - validate-npm-package-name "^3.0.0" + validate-npm-package-name "^4.0.0" + +inquirer@8.2.4: + version "8.2.4" + resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-8.2.4.tgz#ddbfe86ca2f67649a67daa6f1051c128f684f0b4" + integrity sha512-nn4F01dxU8VeKfq192IjLsxu0/OmMZ4Lg3xKAns148rCaXP6ntAoEkVYZThWjwON8AlzdZZi6oqnhNbxUG9hVg== + dependencies: + ansi-escapes "^4.2.1" + chalk "^4.1.1" + cli-cursor "^3.1.0" + cli-width "^3.0.0" + external-editor "^3.0.3" + figures "^3.0.0" + lodash "^4.17.21" + mute-stream "0.0.8" + ora "^5.4.1" + run-async "^2.4.0" + rxjs "^7.5.5" + string-width "^4.1.0" + strip-ansi "^6.0.0" + through "^2.3.6" + wrap-ansi "^7.0.0" inquirer@^7.3.3: version "7.3.3" @@ -6051,21 +6569,37 @@ inquirer@^7.3.3: strip-ansi "^6.0.0" through "^2.3.6" -internal-slot@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/internal-slot/-/internal-slot-1.0.3.tgz#7347e307deeea2faac2ac6205d4bc7d34967f59c" - integrity sha512-O0DB1JC/sPyZl7cIo78n5dR7eUSwwpYPiXRhTzNxZVAMUuB8vlnRFyLxdrVToks6XPLVnFfbzaVd5WLjhgg+vA== +inquirer@^8.2.4, inquirer@^8.2.5: + version "8.2.5" + resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-8.2.5.tgz#d8654a7542c35a9b9e069d27e2df4858784d54f8" + integrity sha512-QAgPDQMEgrDssk1XiwwHoOGYF9BAbUcc1+j+FhEvaOt8/cKRqyLn0U5qA6F74fGhTMGxf92pOvPBeh29jQJDTQ== + dependencies: + ansi-escapes "^4.2.1" + chalk "^4.1.1" + cli-cursor "^3.1.0" + cli-width "^3.0.0" + external-editor "^3.0.3" + figures "^3.0.0" + lodash "^4.17.21" + mute-stream "0.0.8" + ora "^5.4.1" + run-async "^2.4.0" + rxjs "^7.5.5" + string-width "^4.1.0" + strip-ansi "^6.0.0" + through "^2.3.6" + wrap-ansi "^7.0.0" + +internal-slot@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/internal-slot/-/internal-slot-1.0.5.tgz#f2a2ee21f668f8627a4667f309dc0f4fb6674986" + integrity sha512-Y+R5hJrzs52QCG2laLn4udYVnxsfny9CpOhNhUvk/SSSVyF6T27FzRbF0sroPidSu3X8oEAkOn2K804mjpt6UQ== dependencies: - get-intrinsic "^1.1.0" + get-intrinsic "^1.2.0" has "^1.0.3" side-channel "^1.0.4" -interpret@^1.0.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.4.0.tgz#665ab8bc4da27a774a40584e812e3e0fa45b1a1e" - integrity sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA== - -invariant@^2.2.4: +invariant@*, invariant@^2.2.4: version "2.2.4" resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.4.tgz#610f3c92c9359ce1db616e538008d23ff35158e6" integrity sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA== @@ -6101,6 +6635,15 @@ is-accessor-descriptor@^1.0.0: dependencies: kind-of "^6.0.0" +is-array-buffer@^3.0.1, is-array-buffer@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/is-array-buffer/-/is-array-buffer-3.0.2.tgz#f2653ced8412081638ecb0ebbd0c41c6e0aecbbe" + integrity sha512-y+FyyR/w8vfIRq4eQcM1EYgSTnmHXPqaF+IgzgraytCFq5Xh8lllDVmAZolPJiZttZLeFSINPYMaEJ7/vWUa1w== + dependencies: + call-bind "^1.0.2" + get-intrinsic "^1.2.0" + is-typed-array "^1.1.10" + is-arrayish@^0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" @@ -6126,22 +6669,22 @@ is-buffer@^1.1.5: resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be" integrity sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w== -is-callable@^1.1.4, is-callable@^1.2.6: +is-callable@^1.1.3, is-callable@^1.1.4, is-callable@^1.2.7: version "1.2.7" resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.7.tgz#3bc2a85ea742d9e36205dcacdd72ca1fdc51b055" integrity sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA== -is-ci@^2.0.0: +is-ci@2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/is-ci/-/is-ci-2.0.0.tgz#6bc6334181810e04b5c22b3d589fdca55026404c" integrity sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w== dependencies: ci-info "^2.0.0" -is-core-module@^2.5.0, is-core-module@^2.8.1, is-core-module@^2.9.0: - version "2.10.0" - resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.10.0.tgz#9012ede0a91c69587e647514e1d5277019e728ed" - integrity sha512-Erxj2n/LDAZ7H8WNJXd9tw38GYM3dv8rk8Zcs+jJuxYTW7sozH+SS8NtrSjVL1/vpLvWi1hxy96IzjJ3EHTJJg== +is-core-module@^2.11.0, is-core-module@^2.5.0, is-core-module@^2.8.1: + version "2.12.0" + resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.12.0.tgz#36ad62f6f73c8253fd6472517a12483cf03e7ec4" + integrity sha512-RECHCBCd/viahWmwj6enj19sKbHfJrddi/6cBDsNTKbNq0f7VeaUkBo60BqzvPqo/W54ChS62Z5qyun7cfOMqQ== dependencies: has "^1.0.3" @@ -6189,6 +6732,11 @@ is-directory@^0.3.1: resolved "https://registry.yarnpkg.com/is-directory/-/is-directory-0.3.1.tgz#61339b6f2475fc772fd9c9d83f5c8575dc154ae1" integrity sha512-yVChGzahRFvbkscn2MlwGismPO12i9+znNruC5gVEntG3qu0xQMzsGg/JFbrsqDOHtHFPci+V5aP5T9I+yeKqw== +is-docker@^2.0.0, is-docker@^2.1.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/is-docker/-/is-docker-2.2.1.tgz#33eeabe23cfe86f14bde4408a02c0cfb853acdaa" + integrity sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ== + is-extendable@^0.1.0, is-extendable@^0.1.1: version "0.1.1" resolved "https://registry.yarnpkg.com/is-extendable/-/is-extendable-0.1.1.tgz#62b110e289a471418e3ec36a617d472e301dfc89" @@ -6235,6 +6783,11 @@ is-glob@^4.0.0, is-glob@^4.0.1, is-glob@^4.0.3: dependencies: is-extglob "^2.1.1" +is-interactive@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-interactive/-/is-interactive-1.0.0.tgz#cea6e6ae5c870a7b0a0004070b7b587e0252912e" + integrity sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w== + is-lambda@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/is-lambda/-/is-lambda-1.0.1.tgz#3d9877899e6a53efc0160504cde15f82e6f061d5" @@ -6269,16 +6822,21 @@ is-obj@^2.0.0: resolved "https://registry.yarnpkg.com/is-obj/-/is-obj-2.0.0.tgz#473fb05d973705e3fd9620545018ca8e22ef4982" integrity sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w== +is-path-cwd@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/is-path-cwd/-/is-path-cwd-2.2.0.tgz#67d43b82664a7b5191fd9119127eb300048a9fdb" + integrity sha512-w942bTcih8fdJPJmQHFzkS76NEP8Kzzvmw92cXsazb8intwLqPibPPdXf4ANdKV3rYMuuQYGIWtvz9JilB3NFQ== + +is-path-inside@^3.0.2, is-path-inside@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-3.0.3.tgz#d231362e53a07ff2b0e0ea7fed049161ffd16283" + 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.yarnpkg.com/is-plain-obj/-/is-plain-obj-1.1.0.tgz#71a50c8429dfca773c92a390a4a03b39fcd51d3e" integrity sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg== -is-plain-obj@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-2.1.0.tgz#45e42e37fccf1f40da8e5f76ee21515840c09287" - integrity sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA== - is-plain-object@^2.0.3, is-plain-object@^2.0.4: version "2.0.4" resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-2.0.4.tgz#2c163b3fafb1b606d9d17928f05c2a1c38e07677" @@ -6291,11 +6849,6 @@ is-plain-object@^5.0.0: resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-5.0.0.tgz#4427f50ab3429e9025ea7d52e9043a9ef4159344" integrity sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q== -is-potential-custom-element-name@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz#171ed6f19e3ac554394edf78caa05784a45bebb5" - integrity sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ== - is-regex@^1.1.4: version "1.1.4" resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.1.4.tgz#eef5663cd59fa4c0ae339505323df6854bb15958" @@ -6311,13 +6864,18 @@ is-shared-array-buffer@^1.0.2: dependencies: call-bind "^1.0.2" -is-ssh@^1.3.0: +is-ssh@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/is-ssh/-/is-ssh-1.4.0.tgz#4f8220601d2839d8fa624b3106f8e8884f01b8b2" integrity sha512-x7+VxdxOdlV3CYpjvRLBv5Lo9OJerlYanjwFrPR9fuGPjCiNiCzFgAWpiLAohSbsnH4ZAys3SBh+hq5rJosxUQ== dependencies: protocols "^2.0.1" +is-stream@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.0.tgz#bde9c32680d6fae04129d6ac9d921ce7815f78e3" + integrity sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw== + is-stream@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44" @@ -6349,10 +6907,21 @@ is-text-path@^1.0.1: dependencies: text-extensions "^1.0.0" -is-typedarray@^1.0.0, is-typedarray@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" - integrity sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA== +is-typed-array@^1.1.10, is-typed-array@^1.1.9: + version "1.1.10" + resolved "https://registry.yarnpkg.com/is-typed-array/-/is-typed-array-1.1.10.tgz#36a5b5cb4189b575d1a3e4b08536bfb485801e3f" + integrity sha512-PJqgEHiWZvMpaFZ3uTc8kHPM4+4ADTlDniuQL7cU/UDA0Ql7F70yGfHph3cLNe+c9toaigv+DFzTJKhc2CtO6A== + dependencies: + available-typed-arrays "^1.0.5" + call-bind "^1.0.2" + for-each "^0.3.3" + gopd "^1.0.1" + has-tostringtag "^1.0.0" + +is-unicode-supported@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz#3f26c76a809593b52bfa2ecb5710ed2779b522a7" + integrity sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw== is-weakref@^1.0.2: version "1.0.2" @@ -6371,6 +6940,13 @@ is-wsl@^1.1.0: resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-1.1.0.tgz#1f16e4aa22b04d1336b66188a66af3c600c3a66d" integrity sha512-gfygJYZ2gLTDlmbWMI0CE2MwnFzSN/2SZfkMlItC4K/JBlsWVDB0bO6XhqcY13YXE7iMcAJnzTCJjPiTeJJ0Mw== +is-wsl@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-2.2.0.tgz#74a4c76e77ca9fd3f932f290c17ea326cd157271" + integrity sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww== + dependencies: + is-docker "^2.0.0" + isarray@1.0.0, isarray@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" @@ -6416,10 +6992,10 @@ isomorphic-webcrypto@^2.3.8: expo-random "*" react-native-securerandom "^0.1.1" -isstream@~0.1.2: - version "0.1.2" - resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a" - integrity sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g== +isomorphic-ws@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/isomorphic-ws/-/isomorphic-ws-4.0.1.tgz#55fd4cd6c5e6491e76dc125938dd863f5cd4f2dc" + integrity sha512-BhBvN2MBpWTaSHdWRb/bwdZJ1WaehQ2L1KngkCkfLUGF0mAWAT1sQUQacEmQ0jXkFw/czDXPNQSL5u2/Krsz1w== istanbul-lib-coverage@^3.0.0, istanbul-lib-coverage@^3.2.0: version "3.2.0" @@ -6427,9 +7003,9 @@ istanbul-lib-coverage@^3.0.0, istanbul-lib-coverage@^3.2.0: integrity sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw== istanbul-lib-instrument@^5.0.4, istanbul-lib-instrument@^5.1.0: - version "5.2.0" - resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.0.tgz#31d18bdd127f825dd02ea7bfdfd906f8ab840e9f" - integrity sha512-6Lthe1hqXHBNsqvgDzGO6l03XNeu3CrG4RqQ1KM9+l5+jNGpEJfIELx1NS3SEHmJQA8np/u+E4EPRKRiu6m19A== + version "5.2.1" + resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz#d10c8885c2125574e1c231cacadf955675e1ce3d" + integrity sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg== dependencies: "@babel/core" "^7.12.3" "@babel/parser" "^7.14.7" @@ -6463,370 +7039,301 @@ istanbul-reports@^3.1.3: html-escaper "^2.0.0" istanbul-lib-report "^3.0.0" -jest-changed-files@^27.5.1: - version "27.5.1" - resolved "https://registry.yarnpkg.com/jest-changed-files/-/jest-changed-files-27.5.1.tgz#a348aed00ec9bf671cc58a66fcbe7c3dfd6a68f5" - integrity sha512-buBLMiByfWGCoMsLLzGUUSpAmIAGnbR2KJoMN10ziLhOLvP4e0SlypHnAel8iqQXTrcbmfEY9sSqae5sgUsTvw== +jake@^10.8.5: + version "10.8.5" + resolved "https://registry.yarnpkg.com/jake/-/jake-10.8.5.tgz#f2183d2c59382cb274226034543b9c03b8164c46" + integrity sha512-sVpxYeuAhWt0OTWITwT98oyV0GsXyMlXCF+3L1SuafBVUIr/uILGRB+NqwkzhgXKvoJpDIpQvqkUALgdmQsQxw== + dependencies: + async "^3.2.3" + chalk "^4.0.2" + filelist "^1.0.1" + minimatch "^3.0.4" + +jest-changed-files@^29.5.0: + version "29.5.0" + resolved "https://registry.yarnpkg.com/jest-changed-files/-/jest-changed-files-29.5.0.tgz#e88786dca8bf2aa899ec4af7644e16d9dcf9b23e" + integrity sha512-IFG34IUMUaNBIxjQXF/iu7g6EcdMrGRRxaUSw92I/2g2YC6vCdTltl4nHvt7Ci5nSJwXIkCu8Ka1DKF+X7Z1Ag== dependencies: - "@jest/types" "^27.5.1" execa "^5.0.0" - throat "^6.0.1" + p-limit "^3.1.0" -jest-circus@^27.5.1: - version "27.5.1" - resolved "https://registry.yarnpkg.com/jest-circus/-/jest-circus-27.5.1.tgz#37a5a4459b7bf4406e53d637b49d22c65d125ecc" - integrity sha512-D95R7x5UtlMA5iBYsOHFFbMD/GVA4R/Kdq15f7xYWUfWHBto9NYRsOvnSauTgdF+ogCpJ4tyKOXhUifxS65gdw== +jest-circus@^29.5.0: + version "29.5.0" + resolved "https://registry.yarnpkg.com/jest-circus/-/jest-circus-29.5.0.tgz#b5926989449e75bff0d59944bae083c9d7fb7317" + integrity sha512-gq/ongqeQKAplVxqJmbeUOJJKkW3dDNPY8PjhJ5G0lBRvu0e3EWGxGy5cI4LAGA7gV2UHCtWBI4EMXK8c9nQKA== dependencies: - "@jest/environment" "^27.5.1" - "@jest/test-result" "^27.5.1" - "@jest/types" "^27.5.1" + "@jest/environment" "^29.5.0" + "@jest/expect" "^29.5.0" + "@jest/test-result" "^29.5.0" + "@jest/types" "^29.5.0" "@types/node" "*" chalk "^4.0.0" co "^4.6.0" dedent "^0.7.0" - expect "^27.5.1" is-generator-fn "^2.0.0" - jest-each "^27.5.1" - jest-matcher-utils "^27.5.1" - jest-message-util "^27.5.1" - jest-runtime "^27.5.1" - jest-snapshot "^27.5.1" - jest-util "^27.5.1" - pretty-format "^27.5.1" + jest-each "^29.5.0" + jest-matcher-utils "^29.5.0" + jest-message-util "^29.5.0" + jest-runtime "^29.5.0" + jest-snapshot "^29.5.0" + jest-util "^29.5.0" + p-limit "^3.1.0" + pretty-format "^29.5.0" + pure-rand "^6.0.0" slash "^3.0.0" stack-utils "^2.0.3" - throat "^6.0.1" -jest-cli@^27.5.1: - version "27.5.1" - resolved "https://registry.yarnpkg.com/jest-cli/-/jest-cli-27.5.1.tgz#278794a6e6458ea8029547e6c6cbf673bd30b145" - integrity sha512-Hc6HOOwYq4/74/c62dEE3r5elx8wjYqxY0r0G/nFrLDPMFRu6RA/u8qINOIkvhxG7mMQ5EJsOGfRpI8L6eFUVw== +jest-cli@^29.5.0: + version "29.5.0" + resolved "https://registry.yarnpkg.com/jest-cli/-/jest-cli-29.5.0.tgz#b34c20a6d35968f3ee47a7437ff8e53e086b4a67" + integrity sha512-L1KcP1l4HtfwdxXNFCL5bmUbLQiKrakMUriBEcc1Vfz6gx31ORKdreuWvmQVBit+1ss9NNR3yxjwfwzZNdQXJw== dependencies: - "@jest/core" "^27.5.1" - "@jest/test-result" "^27.5.1" - "@jest/types" "^27.5.1" + "@jest/core" "^29.5.0" + "@jest/test-result" "^29.5.0" + "@jest/types" "^29.5.0" chalk "^4.0.0" exit "^0.1.2" graceful-fs "^4.2.9" import-local "^3.0.2" - jest-config "^27.5.1" - jest-util "^27.5.1" - jest-validate "^27.5.1" + jest-config "^29.5.0" + jest-util "^29.5.0" + jest-validate "^29.5.0" prompts "^2.0.1" - yargs "^16.2.0" + yargs "^17.3.1" -jest-config@^27.5.1: - version "27.5.1" - resolved "https://registry.yarnpkg.com/jest-config/-/jest-config-27.5.1.tgz#5c387de33dca3f99ad6357ddeccd91bf3a0e4a41" - integrity sha512-5sAsjm6tGdsVbW9ahcChPAFCk4IlkQUknH5AvKjuLTSlcO/wCZKyFdn7Rg0EkC+OGgWODEy2hDpWB1PgzH0JNA== +jest-config@^29.5.0: + version "29.5.0" + resolved "https://registry.yarnpkg.com/jest-config/-/jest-config-29.5.0.tgz#3cc972faec8c8aaea9ae158c694541b79f3748da" + integrity sha512-kvDUKBnNJPNBmFFOhDbm59iu1Fii1Q6SxyhXfvylq3UTHbg6o7j/g8k2dZyXWLvfdKB1vAPxNZnMgtKJcmu3kA== dependencies: - "@babel/core" "^7.8.0" - "@jest/test-sequencer" "^27.5.1" - "@jest/types" "^27.5.1" - babel-jest "^27.5.1" + "@babel/core" "^7.11.6" + "@jest/test-sequencer" "^29.5.0" + "@jest/types" "^29.5.0" + babel-jest "^29.5.0" chalk "^4.0.0" ci-info "^3.2.0" deepmerge "^4.2.2" - glob "^7.1.1" + glob "^7.1.3" graceful-fs "^4.2.9" - jest-circus "^27.5.1" - jest-environment-jsdom "^27.5.1" - jest-environment-node "^27.5.1" - jest-get-type "^27.5.1" - jest-jasmine2 "^27.5.1" - jest-regex-util "^27.5.1" - jest-resolve "^27.5.1" - jest-runner "^27.5.1" - jest-util "^27.5.1" - jest-validate "^27.5.1" + jest-circus "^29.5.0" + jest-environment-node "^29.5.0" + jest-get-type "^29.4.3" + jest-regex-util "^29.4.3" + jest-resolve "^29.5.0" + jest-runner "^29.5.0" + jest-util "^29.5.0" + jest-validate "^29.5.0" micromatch "^4.0.4" parse-json "^5.2.0" - pretty-format "^27.5.1" + pretty-format "^29.5.0" slash "^3.0.0" strip-json-comments "^3.1.1" -jest-diff@^26.0.0: - version "26.6.2" - resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-26.6.2.tgz#1aa7468b52c3a68d7d5c5fdcdfcd5e49bd164394" - integrity sha512-6m+9Z3Gv9wN0WFVasqjCL/06+EFCMTqDEUl/b87HYK2rAPTyfz4ZIuSlPhY51PIQRWx5TaxeF1qmXKe9gfN3sA== - dependencies: - chalk "^4.0.0" - diff-sequences "^26.6.2" - jest-get-type "^26.3.0" - pretty-format "^26.6.2" - -jest-diff@^27.5.1: - version "27.5.1" - resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-27.5.1.tgz#a07f5011ac9e6643cf8a95a462b7b1ecf6680def" - integrity sha512-m0NvkX55LDt9T4mctTEgnZk3fmEg3NRYutvMPWM/0iPnkFj2wIeF45O1718cMSOFO1vINkqmxqD8vE37uTEbqw== +jest-diff@^29.5.0: + version "29.5.0" + resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-29.5.0.tgz#e0d83a58eb5451dcc1fa61b1c3ee4e8f5a290d63" + integrity sha512-LtxijLLZBduXnHSniy0WMdaHjmQnt3g5sa16W4p0HqukYTTsyTW3GD1q41TyGl5YFXj/5B2U6dlh5FM1LIMgxw== dependencies: chalk "^4.0.0" - diff-sequences "^27.5.1" - jest-get-type "^27.5.1" - pretty-format "^27.5.1" + diff-sequences "^29.4.3" + jest-get-type "^29.4.3" + pretty-format "^29.5.0" -jest-docblock@^27.5.1: - version "27.5.1" - resolved "https://registry.yarnpkg.com/jest-docblock/-/jest-docblock-27.5.1.tgz#14092f364a42c6108d42c33c8cf30e058e25f6c0" - integrity sha512-rl7hlABeTsRYxKiUfpHrQrG4e2obOiTQWfMEH3PxPjOtdsfLQO4ReWSZaQ7DETm4xu07rl4q/h4zcKXyU0/OzQ== +jest-docblock@^29.4.3: + version "29.4.3" + resolved "https://registry.yarnpkg.com/jest-docblock/-/jest-docblock-29.4.3.tgz#90505aa89514a1c7dceeac1123df79e414636ea8" + integrity sha512-fzdTftThczeSD9nZ3fzA/4KkHtnmllawWrXO69vtI+L9WjEIuXWs4AmyME7lN5hU7dB0sHhuPfcKofRsUb/2Fg== dependencies: detect-newline "^3.0.0" -jest-each@^27.5.1: - version "27.5.1" - resolved "https://registry.yarnpkg.com/jest-each/-/jest-each-27.5.1.tgz#5bc87016f45ed9507fed6e4702a5b468a5b2c44e" - integrity sha512-1Ff6p+FbhT/bXQnEouYy00bkNSY7OUpfIcmdl8vZ31A1UUaurOLPA8a8BbJOF2RDUElwJhmeaV7LnagI+5UwNQ== +jest-each@^29.5.0: + version "29.5.0" + resolved "https://registry.yarnpkg.com/jest-each/-/jest-each-29.5.0.tgz#fc6e7014f83eac68e22b7195598de8554c2e5c06" + integrity sha512-HM5kIJ1BTnVt+DQZ2ALp3rzXEl+g726csObrW/jpEGl+CDSSQpOJJX2KE/vEg8cxcMXdyEPu6U4QX5eruQv5hA== dependencies: - "@jest/types" "^27.5.1" + "@jest/types" "^29.5.0" chalk "^4.0.0" - jest-get-type "^27.5.1" - jest-util "^27.5.1" - pretty-format "^27.5.1" - -jest-environment-jsdom@^27.5.1: - version "27.5.1" - resolved "https://registry.yarnpkg.com/jest-environment-jsdom/-/jest-environment-jsdom-27.5.1.tgz#ea9ccd1fc610209655a77898f86b2b559516a546" - integrity sha512-TFBvkTC1Hnnnrka/fUb56atfDtJ9VMZ94JkjTbggl1PEpwrYtUBKMezB3inLmWqQsXYLcMwNoDQwoBTAvFfsfw== - dependencies: - "@jest/environment" "^27.5.1" - "@jest/fake-timers" "^27.5.1" - "@jest/types" "^27.5.1" - "@types/node" "*" - jest-mock "^27.5.1" - jest-util "^27.5.1" - jsdom "^16.6.0" - -jest-environment-node@^27.5.1: - version "27.5.1" - resolved "https://registry.yarnpkg.com/jest-environment-node/-/jest-environment-node-27.5.1.tgz#dedc2cfe52fab6b8f5714b4808aefa85357a365e" - integrity sha512-Jt4ZUnxdOsTGwSRAfKEnE6BcwsSPNOijjwifq5sDFSA2kesnXTvNqKHYgM0hDq3549Uf/KzdXNYn4wMZJPlFLw== - dependencies: - "@jest/environment" "^27.5.1" - "@jest/fake-timers" "^27.5.1" - "@jest/types" "^27.5.1" + jest-get-type "^29.4.3" + jest-util "^29.5.0" + pretty-format "^29.5.0" + +jest-environment-node@^29.2.1, jest-environment-node@^29.5.0: + version "29.5.0" + resolved "https://registry.yarnpkg.com/jest-environment-node/-/jest-environment-node-29.5.0.tgz#f17219d0f0cc0e68e0727c58b792c040e332c967" + integrity sha512-ExxuIK/+yQ+6PRGaHkKewYtg6hto2uGCgvKdb2nfJfKXgZ17DfXjvbZ+jA1Qt9A8EQSfPnt5FKIfnOO3u1h9qw== + dependencies: + "@jest/environment" "^29.5.0" + "@jest/fake-timers" "^29.5.0" + "@jest/types" "^29.5.0" "@types/node" "*" - jest-mock "^27.5.1" - jest-util "^27.5.1" + jest-mock "^29.5.0" + jest-util "^29.5.0" jest-get-type@^26.3.0: version "26.3.0" resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-26.3.0.tgz#e97dc3c3f53c2b406ca7afaed4493b1d099199e0" integrity sha512-TpfaviN1R2pQWkIihlfEanwOXK0zcxrKEE4MlU6Tn7keoXdN6/3gK/xl0yEh8DOunn5pOVGKf8hB4R9gVh04ig== -jest-get-type@^27.5.1: - version "27.5.1" - resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-27.5.1.tgz#3cd613c507b0f7ace013df407a1c1cd578bcb4f1" - integrity sha512-2KY95ksYSaK7DMBWQn6dQz3kqAf3BB64y2udeG+hv4KfSOb9qwcYQstTJc1KCbsix+wLZWZYN8t7nwX3GOBLRw== - -jest-haste-map@^26.5.2: - version "26.6.2" - resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-26.6.2.tgz#dd7e60fe7dc0e9f911a23d79c5ff7fb5c2cafeaa" - integrity sha512-easWIJXIw71B2RdR8kgqpjQrbMRWQBgiBwXYEhtGUTaX+doCjBheluShdDMeR8IMfJiTqH4+zfhtg29apJf/8w== - dependencies: - "@jest/types" "^26.6.2" - "@types/graceful-fs" "^4.1.2" - "@types/node" "*" - anymatch "^3.0.3" - fb-watchman "^2.0.0" - graceful-fs "^4.2.4" - jest-regex-util "^26.0.0" - jest-serializer "^26.6.2" - jest-util "^26.6.2" - jest-worker "^26.6.2" - micromatch "^4.0.2" - sane "^4.0.3" - walker "^1.0.7" - optionalDependencies: - fsevents "^2.1.2" +jest-get-type@^29.4.3: + version "29.4.3" + resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-29.4.3.tgz#1ab7a5207c995161100b5187159ca82dd48b3dd5" + integrity sha512-J5Xez4nRRMjk8emnTpWrlkyb9pfRQQanDrvWHhsR1+VUfbwxi30eVcZFlcdGInRibU4G5LwHXpI7IRHU0CY+gg== -jest-haste-map@^27.5.1: - version "27.5.1" - resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-27.5.1.tgz#9fd8bd7e7b4fa502d9c6164c5640512b4e811e7f" - integrity sha512-7GgkZ4Fw4NFbMSDSpZwXeBiIbx+t/46nJ2QitkOjvwPYyZmqttu2TDSimMHP1EkPOi4xUZAN1doE5Vd25H4Jng== +jest-haste-map@^29.5.0: + version "29.5.0" + resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-29.5.0.tgz#69bd67dc9012d6e2723f20a945099e972b2e94de" + integrity sha512-IspOPnnBro8YfVYSw6yDRKh/TiCdRngjxeacCps1cQ9cgVN6+10JUcuJ1EabrgYLOATsIAigxA0rLR9x/YlrSA== dependencies: - "@jest/types" "^27.5.1" - "@types/graceful-fs" "^4.1.2" + "@jest/types" "^29.5.0" + "@types/graceful-fs" "^4.1.3" "@types/node" "*" anymatch "^3.0.3" fb-watchman "^2.0.0" graceful-fs "^4.2.9" - jest-regex-util "^27.5.1" - jest-serializer "^27.5.1" - jest-util "^27.5.1" - jest-worker "^27.5.1" + jest-regex-util "^29.4.3" + jest-util "^29.5.0" + jest-worker "^29.5.0" micromatch "^4.0.4" - walker "^1.0.7" + walker "^1.0.8" optionalDependencies: fsevents "^2.3.2" -jest-jasmine2@^27.5.1: - version "27.5.1" - resolved "https://registry.yarnpkg.com/jest-jasmine2/-/jest-jasmine2-27.5.1.tgz#a037b0034ef49a9f3d71c4375a796f3b230d1ac4" - integrity sha512-jtq7VVyG8SqAorDpApwiJJImd0V2wv1xzdheGHRGyuT7gZm6gG47QEskOlzsN1PG/6WNaCo5pmwMHDf3AkG2pQ== - dependencies: - "@jest/environment" "^27.5.1" - "@jest/source-map" "^27.5.1" - "@jest/test-result" "^27.5.1" - "@jest/types" "^27.5.1" - "@types/node" "*" - chalk "^4.0.0" - co "^4.6.0" - expect "^27.5.1" - is-generator-fn "^2.0.0" - jest-each "^27.5.1" - jest-matcher-utils "^27.5.1" - jest-message-util "^27.5.1" - jest-runtime "^27.5.1" - jest-snapshot "^27.5.1" - jest-util "^27.5.1" - pretty-format "^27.5.1" - throat "^6.0.1" - -jest-leak-detector@^27.5.1: - version "27.5.1" - resolved "https://registry.yarnpkg.com/jest-leak-detector/-/jest-leak-detector-27.5.1.tgz#6ec9d54c3579dd6e3e66d70e3498adf80fde3fb8" - integrity sha512-POXfWAMvfU6WMUXftV4HolnJfnPOGEu10fscNCA76KBpRRhcMN2c8d3iT2pxQS3HLbA+5X4sOUPzYO2NUyIlHQ== +jest-leak-detector@^29.5.0: + version "29.5.0" + resolved "https://registry.yarnpkg.com/jest-leak-detector/-/jest-leak-detector-29.5.0.tgz#cf4bdea9615c72bac4a3a7ba7e7930f9c0610c8c" + integrity sha512-u9YdeeVnghBUtpN5mVxjID7KbkKE1QU4f6uUwuxiY0vYRi9BUCLKlPEZfDGR67ofdFmDz9oPAy2G92Ujrntmow== dependencies: - jest-get-type "^27.5.1" - pretty-format "^27.5.1" + jest-get-type "^29.4.3" + pretty-format "^29.5.0" -jest-matcher-utils@^27.5.1: - version "27.5.1" - resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-27.5.1.tgz#9c0cdbda8245bc22d2331729d1091308b40cf8ab" - integrity sha512-z2uTx/T6LBaCoNWNFWwChLBKYxTMcGBRjAt+2SbP929/Fflb9aa5LGma654Rz8z9HLxsrUaYzxE9T/EFIL/PAw== +jest-matcher-utils@^29.5.0: + version "29.5.0" + resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-29.5.0.tgz#d957af7f8c0692c5453666705621ad4abc2c59c5" + integrity sha512-lecRtgm/rjIK0CQ7LPQwzCs2VwW6WAahA55YBuI+xqmhm7LAaxokSB8C97yJeYyT+HvQkH741StzpU41wohhWw== dependencies: chalk "^4.0.0" - jest-diff "^27.5.1" - jest-get-type "^27.5.1" - pretty-format "^27.5.1" + jest-diff "^29.5.0" + jest-get-type "^29.4.3" + pretty-format "^29.5.0" -jest-message-util@^27.5.1: - version "27.5.1" - resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-27.5.1.tgz#bdda72806da10d9ed6425e12afff38cd1458b6cf" - integrity sha512-rMyFe1+jnyAAf+NHwTclDz0eAaLkVDdKVHHBFWsBWHnnh5YeJMNWWsv7AbFYXfK3oTqvL7VTWkhNLu1jX24D+g== +jest-message-util@^29.5.0: + version "29.5.0" + resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-29.5.0.tgz#1f776cac3aca332ab8dd2e3b41625435085c900e" + integrity sha512-Kijeg9Dag6CKtIDA7O21zNTACqD5MD/8HfIV8pdD94vFyFuer52SigdC3IQMhab3vACxXMiFk+yMHNdbqtyTGA== dependencies: "@babel/code-frame" "^7.12.13" - "@jest/types" "^27.5.1" + "@jest/types" "^29.5.0" "@types/stack-utils" "^2.0.0" chalk "^4.0.0" graceful-fs "^4.2.9" micromatch "^4.0.4" - pretty-format "^27.5.1" + pretty-format "^29.5.0" slash "^3.0.0" stack-utils "^2.0.3" -jest-mock@^27.5.1: - version "27.5.1" - resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-27.5.1.tgz#19948336d49ef4d9c52021d34ac7b5f36ff967d6" - integrity sha512-K4jKbY1d4ENhbrG2zuPWaQBvDly+iZ2yAW+T1fATN78hc0sInwn7wZB8XtlNnvHug5RMwV897Xm4LqmPM4e2Og== +jest-mock@^29.5.0: + version "29.5.0" + resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-29.5.0.tgz#26e2172bcc71d8b0195081ff1f146ac7e1518aed" + integrity sha512-GqOzvdWDE4fAV2bWQLQCkujxYWL7RxjCnj71b5VhDAGOevB3qj3Ovg26A5NI84ZpODxyzaozXLOh2NCgkbvyaw== dependencies: - "@jest/types" "^27.5.1" + "@jest/types" "^29.5.0" "@types/node" "*" + jest-util "^29.5.0" jest-pnp-resolver@^1.2.2: - version "1.2.2" - resolved "https://registry.yarnpkg.com/jest-pnp-resolver/-/jest-pnp-resolver-1.2.2.tgz#b704ac0ae028a89108a4d040b3f919dfddc8e33c" - integrity sha512-olV41bKSMm8BdnuMsewT4jqlZ8+3TCARAXjZGT9jcoSnrfUnRCqnMoF9XEeoWjbzObpqF9dRhHQj0Xb9QdF6/w== - -jest-regex-util@^26.0.0: - version "26.0.0" - resolved "https://registry.yarnpkg.com/jest-regex-util/-/jest-regex-util-26.0.0.tgz#d25e7184b36e39fd466c3bc41be0971e821fee28" - integrity sha512-Gv3ZIs/nA48/Zvjrl34bf+oD76JHiGDUxNOVgUjh3j890sblXryjY4rss71fPtD/njchl6PSE2hIhvyWa1eT0A== + version "1.2.3" + resolved "https://registry.yarnpkg.com/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz#930b1546164d4ad5937d5540e711d4d38d4cad2e" + integrity sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w== -jest-regex-util@^27.5.1: +jest-regex-util@^27.0.6: version "27.5.1" resolved "https://registry.yarnpkg.com/jest-regex-util/-/jest-regex-util-27.5.1.tgz#4da143f7e9fd1e542d4aa69617b38e4a78365b95" integrity sha512-4bfKq2zie+x16okqDXjXn9ql2B0dScQu+vcwe4TvFVhkVyuWLqpZrZtXxLLWoXYgn0E87I6r6GRYHF7wFZBUvg== -jest-resolve-dependencies@^27.5.1: - version "27.5.1" - resolved "https://registry.yarnpkg.com/jest-resolve-dependencies/-/jest-resolve-dependencies-27.5.1.tgz#d811ecc8305e731cc86dd79741ee98fed06f1da8" - integrity sha512-QQOOdY4PE39iawDn5rzbIePNigfe5B9Z91GDD1ae/xNDlu9kaat8QQ5EKnNmVWPV54hUdxCVwwj6YMgR2O7IOg== +jest-regex-util@^29.4.3: + version "29.4.3" + resolved "https://registry.yarnpkg.com/jest-regex-util/-/jest-regex-util-29.4.3.tgz#a42616141e0cae052cfa32c169945d00c0aa0bb8" + integrity sha512-O4FglZaMmWXbGHSQInfXewIsd1LMn9p3ZXB/6r4FOkyhX2/iP/soMG98jGvk/A3HAN78+5VWcBGO0BJAPRh4kg== + +jest-resolve-dependencies@^29.5.0: + version "29.5.0" + resolved "https://registry.yarnpkg.com/jest-resolve-dependencies/-/jest-resolve-dependencies-29.5.0.tgz#f0ea29955996f49788bf70996052aa98e7befee4" + integrity sha512-sjV3GFr0hDJMBpYeUuGduP+YeCRbd7S/ck6IvL3kQ9cpySYKqcqhdLLC2rFwrcL7tz5vYibomBrsFYWkIGGjOg== dependencies: - "@jest/types" "^27.5.1" - jest-regex-util "^27.5.1" - jest-snapshot "^27.5.1" + jest-regex-util "^29.4.3" + jest-snapshot "^29.5.0" -jest-resolve@^27.5.1: - version "27.5.1" - resolved "https://registry.yarnpkg.com/jest-resolve/-/jest-resolve-27.5.1.tgz#a2f1c5a0796ec18fe9eb1536ac3814c23617b384" - integrity sha512-FFDy8/9E6CV83IMbDpcjOhumAQPDyETnU2KZ1O98DwTnz8AOBsW/Xv3GySr1mOZdItLR+zDZ7I/UdTFbgSOVCw== +jest-resolve@^29.5.0: + version "29.5.0" + resolved "https://registry.yarnpkg.com/jest-resolve/-/jest-resolve-29.5.0.tgz#b053cc95ad1d5f6327f0ac8aae9f98795475ecdc" + integrity sha512-1TzxJ37FQq7J10jPtQjcc+MkCkE3GBpBecsSUWJ0qZNJpmg6m0D9/7II03yJulm3H/fvVjgqLh/k2eYg+ui52w== dependencies: - "@jest/types" "^27.5.1" chalk "^4.0.0" graceful-fs "^4.2.9" - jest-haste-map "^27.5.1" + jest-haste-map "^29.5.0" jest-pnp-resolver "^1.2.2" - jest-util "^27.5.1" - jest-validate "^27.5.1" + jest-util "^29.5.0" + jest-validate "^29.5.0" resolve "^1.20.0" - resolve.exports "^1.1.0" + resolve.exports "^2.0.0" slash "^3.0.0" -jest-runner@^27.5.1: - version "27.5.1" - resolved "https://registry.yarnpkg.com/jest-runner/-/jest-runner-27.5.1.tgz#071b27c1fa30d90540805c5645a0ec167c7b62e5" - integrity sha512-g4NPsM4mFCOwFKXO4p/H/kWGdJp9V8kURY2lX8Me2drgXqG7rrZAx5kv+5H7wtt/cdFIjhqYx1HrlqWHaOvDaQ== +jest-runner@^29.5.0: + version "29.5.0" + resolved "https://registry.yarnpkg.com/jest-runner/-/jest-runner-29.5.0.tgz#6a57c282eb0ef749778d444c1d758c6a7693b6f8" + integrity sha512-m7b6ypERhFghJsslMLhydaXBiLf7+jXy8FwGRHO3BGV1mcQpPbwiqiKUR2zU2NJuNeMenJmlFZCsIqzJCTeGLQ== dependencies: - "@jest/console" "^27.5.1" - "@jest/environment" "^27.5.1" - "@jest/test-result" "^27.5.1" - "@jest/transform" "^27.5.1" - "@jest/types" "^27.5.1" + "@jest/console" "^29.5.0" + "@jest/environment" "^29.5.0" + "@jest/test-result" "^29.5.0" + "@jest/transform" "^29.5.0" + "@jest/types" "^29.5.0" "@types/node" "*" chalk "^4.0.0" - emittery "^0.8.1" + emittery "^0.13.1" graceful-fs "^4.2.9" - jest-docblock "^27.5.1" - jest-environment-jsdom "^27.5.1" - jest-environment-node "^27.5.1" - jest-haste-map "^27.5.1" - jest-leak-detector "^27.5.1" - jest-message-util "^27.5.1" - jest-resolve "^27.5.1" - jest-runtime "^27.5.1" - jest-util "^27.5.1" - jest-worker "^27.5.1" - source-map-support "^0.5.6" - throat "^6.0.1" - -jest-runtime@^27.5.1: - version "27.5.1" - resolved "https://registry.yarnpkg.com/jest-runtime/-/jest-runtime-27.5.1.tgz#4896003d7a334f7e8e4a53ba93fb9bcd3db0a1af" - integrity sha512-o7gxw3Gf+H2IGt8fv0RiyE1+r83FJBRruoA+FXrlHw6xEyBsU8ugA6IPfTdVyA0w8HClpbK+DGJxH59UrNMx8A== - dependencies: - "@jest/environment" "^27.5.1" - "@jest/fake-timers" "^27.5.1" - "@jest/globals" "^27.5.1" - "@jest/source-map" "^27.5.1" - "@jest/test-result" "^27.5.1" - "@jest/transform" "^27.5.1" - "@jest/types" "^27.5.1" + jest-docblock "^29.4.3" + jest-environment-node "^29.5.0" + jest-haste-map "^29.5.0" + jest-leak-detector "^29.5.0" + jest-message-util "^29.5.0" + jest-resolve "^29.5.0" + jest-runtime "^29.5.0" + jest-util "^29.5.0" + jest-watcher "^29.5.0" + jest-worker "^29.5.0" + p-limit "^3.1.0" + source-map-support "0.5.13" + +jest-runtime@^29.5.0: + version "29.5.0" + resolved "https://registry.yarnpkg.com/jest-runtime/-/jest-runtime-29.5.0.tgz#c83f943ee0c1da7eb91fa181b0811ebd59b03420" + integrity sha512-1Hr6Hh7bAgXQP+pln3homOiEZtCDZFqwmle7Ew2j8OlbkIu6uE3Y/etJQG8MLQs3Zy90xrp2C0BRrtPHG4zryw== + dependencies: + "@jest/environment" "^29.5.0" + "@jest/fake-timers" "^29.5.0" + "@jest/globals" "^29.5.0" + "@jest/source-map" "^29.4.3" + "@jest/test-result" "^29.5.0" + "@jest/transform" "^29.5.0" + "@jest/types" "^29.5.0" + "@types/node" "*" chalk "^4.0.0" cjs-module-lexer "^1.0.0" collect-v8-coverage "^1.0.0" - execa "^5.0.0" glob "^7.1.3" graceful-fs "^4.2.9" - jest-haste-map "^27.5.1" - jest-message-util "^27.5.1" - jest-mock "^27.5.1" - jest-regex-util "^27.5.1" - jest-resolve "^27.5.1" - jest-snapshot "^27.5.1" - jest-util "^27.5.1" + jest-haste-map "^29.5.0" + jest-message-util "^29.5.0" + jest-mock "^29.5.0" + jest-regex-util "^29.4.3" + jest-resolve "^29.5.0" + jest-snapshot "^29.5.0" + jest-util "^29.5.0" slash "^3.0.0" strip-bom "^4.0.0" -jest-serializer@^26.6.2: - version "26.6.2" - resolved "https://registry.yarnpkg.com/jest-serializer/-/jest-serializer-26.6.2.tgz#d139aafd46957d3a448f3a6cdabe2919ba0742d1" - integrity sha512-S5wqyz0DXnNJPd/xfIzZ5Xnp1HrJWBczg8mMfMpN78OJ5eDxXyf+Ygld9wX1DnUWbIbhM1YDY95NjR4CBXkb2g== - dependencies: - "@types/node" "*" - graceful-fs "^4.2.4" - -jest-serializer@^27.5.1: +jest-serializer@^27.0.6: version "27.5.1" resolved "https://registry.yarnpkg.com/jest-serializer/-/jest-serializer-27.5.1.tgz#81438410a30ea66fd57ff730835123dea1fb1f64" integrity sha512-jZCyo6iIxO1aqUxpuBlwTDMkzOAJS4a3eYz3YzgxxVQFwLeSA7Jfq5cbqCY+JLvTDrWirgusI/0KwxKMgrdf7w== @@ -6834,47 +7341,36 @@ jest-serializer@^27.5.1: "@types/node" "*" graceful-fs "^4.2.9" -jest-snapshot@^27.5.1: - version "27.5.1" - resolved "https://registry.yarnpkg.com/jest-snapshot/-/jest-snapshot-27.5.1.tgz#b668d50d23d38054a51b42c4039cab59ae6eb6a1" - integrity sha512-yYykXI5a0I31xX67mgeLw1DZ0bJB+gpq5IpSuCAoyDi0+BhgU/RIrL+RTzDmkNTchvDFWKP8lp+w/42Z3us5sA== +jest-snapshot@^29.5.0: + version "29.5.0" + resolved "https://registry.yarnpkg.com/jest-snapshot/-/jest-snapshot-29.5.0.tgz#c9c1ce0331e5b63cd444e2f95a55a73b84b1e8ce" + integrity sha512-x7Wolra5V0tt3wRs3/ts3S6ciSQVypgGQlJpz2rsdQYoUKxMxPNaoHMGJN6qAuPJqS+2iQ1ZUn5kl7HCyls84g== dependencies: - "@babel/core" "^7.7.2" + "@babel/core" "^7.11.6" "@babel/generator" "^7.7.2" + "@babel/plugin-syntax-jsx" "^7.7.2" "@babel/plugin-syntax-typescript" "^7.7.2" "@babel/traverse" "^7.7.2" - "@babel/types" "^7.0.0" - "@jest/transform" "^27.5.1" - "@jest/types" "^27.5.1" - "@types/babel__traverse" "^7.0.4" + "@babel/types" "^7.3.3" + "@jest/expect-utils" "^29.5.0" + "@jest/transform" "^29.5.0" + "@jest/types" "^29.5.0" + "@types/babel__traverse" "^7.0.6" "@types/prettier" "^2.1.5" babel-preset-current-node-syntax "^1.0.0" chalk "^4.0.0" - expect "^27.5.1" + expect "^29.5.0" graceful-fs "^4.2.9" - jest-diff "^27.5.1" - jest-get-type "^27.5.1" - jest-haste-map "^27.5.1" - jest-matcher-utils "^27.5.1" - jest-message-util "^27.5.1" - jest-util "^27.5.1" + jest-diff "^29.5.0" + jest-get-type "^29.4.3" + jest-matcher-utils "^29.5.0" + jest-message-util "^29.5.0" + jest-util "^29.5.0" natural-compare "^1.4.0" - pretty-format "^27.5.1" - semver "^7.3.2" - -jest-util@^26.6.2: - version "26.6.2" - resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-26.6.2.tgz#907535dbe4d5a6cb4c47ac9b926f6af29576cbc1" - integrity sha512-MDW0fKfsn0OI7MS7Euz6h8HNDXVQ0gaM9uW6RjfDmd1DAFcaxX9OqIakHIqhbnmF08Cf2DLDG+ulq8YQQ0Lp0Q== - dependencies: - "@jest/types" "^26.6.2" - "@types/node" "*" - chalk "^4.0.0" - graceful-fs "^4.2.4" - is-ci "^2.0.0" - micromatch "^4.0.2" + pretty-format "^29.5.0" + semver "^7.3.5" -jest-util@^27.0.0, jest-util@^27.5.1: +jest-util@^27.2.0: version "27.5.1" resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-27.5.1.tgz#3ba9771e8e31a0b85da48fe0b0891fb86c01c2f9" integrity sha512-Kv2o/8jNvX1MQ0KGtw480E/w4fBCDOnH6+6DmeKi6LZUIlKA5kwY0YNdlzaWTiVgxqAqik11QyxDOKk543aKXw== @@ -6886,6 +7382,18 @@ jest-util@^27.0.0, jest-util@^27.5.1: graceful-fs "^4.2.9" picomatch "^2.2.3" +jest-util@^29.0.0, jest-util@^29.5.0: + version "29.5.0" + resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-29.5.0.tgz#24a4d3d92fc39ce90425311b23c27a6e0ef16b8f" + integrity sha512-RYMgG/MTadOr5t8KdhejfvUU82MxsCu5MF6KuDUHl+NuwzUt+Sm6jJWxTJVrDR1j5M/gJVCPKQEpWXY+yIQ6lQ== + dependencies: + "@jest/types" "^29.5.0" + "@types/node" "*" + chalk "^4.0.0" + ci-info "^3.2.0" + graceful-fs "^4.2.9" + picomatch "^2.2.3" + jest-validate@^26.5.2: version "26.6.2" resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-26.6.2.tgz#23d380971587150467342911c3d7b4ac57ab20ec" @@ -6898,80 +7406,95 @@ jest-validate@^26.5.2: leven "^3.1.0" pretty-format "^26.6.2" -jest-validate@^27.5.1: - version "27.5.1" - resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-27.5.1.tgz#9197d54dc0bdb52260b8db40b46ae668e04df067" - integrity sha512-thkNli0LYTmOI1tDB3FI1S1RTp/Bqyd9pTarJwL87OIBFuqEb5Apv5EaApEudYg4g86e3CT6kM0RowkhtEnCBQ== +jest-validate@^29.5.0: + version "29.5.0" + resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-29.5.0.tgz#8e5a8f36178d40e47138dc00866a5f3bd9916ffc" + integrity sha512-pC26etNIi+y3HV8A+tUGr/lph9B18GnzSRAkPaaZJIE1eFdiYm6/CewuiJQ8/RlfHd1u/8Ioi8/sJ+CmbA+zAQ== dependencies: - "@jest/types" "^27.5.1" + "@jest/types" "^29.5.0" camelcase "^6.2.0" chalk "^4.0.0" - jest-get-type "^27.5.1" + jest-get-type "^29.4.3" leven "^3.1.0" - pretty-format "^27.5.1" + pretty-format "^29.5.0" -jest-watcher@^27.5.1: - version "27.5.1" - resolved "https://registry.yarnpkg.com/jest-watcher/-/jest-watcher-27.5.1.tgz#71bd85fb9bde3a2c2ec4dc353437971c43c642a2" - integrity sha512-z676SuD6Z8o8qbmEGhoEUFOM1+jfEiL3DXHK/xgEiG2EyNYfFG60jluWcupY6dATjfEsKQuibReS1djInQnoVw== +jest-watcher@^29.5.0: + version "29.5.0" + resolved "https://registry.yarnpkg.com/jest-watcher/-/jest-watcher-29.5.0.tgz#cf7f0f949828ba65ddbbb45c743a382a4d911363" + integrity sha512-KmTojKcapuqYrKDpRwfqcQ3zjMlwu27SYext9pt4GlF5FUgB+7XE1mcCnSm6a4uUpFyQIkb6ZhzZvHl+jiBCiA== dependencies: - "@jest/test-result" "^27.5.1" - "@jest/types" "^27.5.1" + "@jest/test-result" "^29.5.0" + "@jest/types" "^29.5.0" "@types/node" "*" ansi-escapes "^4.2.1" chalk "^4.0.0" - jest-util "^27.5.1" + emittery "^0.13.1" + jest-util "^29.5.0" string-length "^4.0.1" -jest-worker@^26.0.0, jest-worker@^26.6.2: - version "26.6.2" - resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-26.6.2.tgz#7f72cbc4d643c365e27b9fd775f9d0eaa9c7a8ed" - integrity sha512-KWYVV1c4i+jbMpaBC+U++4Va0cp8OisU185o73T1vo99hqi7w8tSJfUXYswwqqrjzwxa6KpRK54WhPvwf5w6PQ== +jest-worker@^27.2.0: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-27.5.1.tgz#8d146f0900e8973b106b6f73cc1e9a8cb86f8db0" + integrity sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg== dependencies: "@types/node" "*" merge-stream "^2.0.0" - supports-color "^7.0.0" + supports-color "^8.0.0" -jest-worker@^27.5.1: - version "27.5.1" - resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-27.5.1.tgz#8d146f0900e8973b106b6f73cc1e9a8cb86f8db0" - integrity sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg== +jest-worker@^29.5.0: + version "29.5.0" + resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-29.5.0.tgz#bdaefb06811bd3384d93f009755014d8acb4615d" + integrity sha512-NcrQnevGoSp4b5kg+akIpthoAFHxPBcb5P6mYPY0fUNT+sSvmtu6jlkEle3anczUKIKEbMxFimk9oTP/tpIPgA== dependencies: "@types/node" "*" + jest-util "^29.5.0" merge-stream "^2.0.0" supports-color "^8.0.0" -jest@^27.0.4: - version "27.5.1" - resolved "https://registry.yarnpkg.com/jest/-/jest-27.5.1.tgz#dadf33ba70a779be7a6fc33015843b51494f63fc" - integrity sha512-Yn0mADZB89zTtjkPJEXwrac3LHudkQMR+Paqa8uxJHCBr9agxztUifWCyiYrjhMPBoUVBjyny0I7XH6ozDr7QQ== +jest@^29.5.0: + version "29.5.0" + resolved "https://registry.yarnpkg.com/jest/-/jest-29.5.0.tgz#f75157622f5ce7ad53028f2f8888ab53e1f1f24e" + integrity sha512-juMg3he2uru1QoXX078zTa7pO85QyB9xajZc6bU+d9yEGwrKX6+vGmJQ3UdVZsvTEUARIdObzH68QItim6OSSQ== dependencies: - "@jest/core" "^27.5.1" + "@jest/core" "^29.5.0" + "@jest/types" "^29.5.0" import-local "^3.0.2" - jest-cli "^27.5.1" - -jetifier@^1.6.2: - version "1.6.8" - resolved "https://registry.yarnpkg.com/jetifier/-/jetifier-1.6.8.tgz#e88068697875cbda98c32472902c4d3756247798" - integrity sha512-3Zi16h6L5tXDRQJTb221cnRoVG9/9OvreLdLU2/ZjRv/GILL+2Cemt0IKvkowwkDpvouAU1DQPOJ7qaiHeIdrw== + jest-cli "^29.5.0" joi@^17.2.1: - version "17.6.1" - resolved "https://registry.yarnpkg.com/joi/-/joi-17.6.1.tgz#e77422f277091711599634ac39a409e599d7bdaa" - integrity sha512-Hl7/iBklIX345OCM1TiFSCZRVaAOLDGlWCp0Df2vWYgBgjkezaR7Kvm3joBciBHQjZj5sxXs859r6eqsRSlG8w== + version "17.9.2" + resolved "https://registry.yarnpkg.com/joi/-/joi-17.9.2.tgz#8b2e4724188369f55451aebd1d0b1d9482470690" + integrity sha512-Itk/r+V4Dx0V3c7RLFdRh12IOjySm2/WGPMubBT92cQvRfYZhPM2W0hZlctjj72iES8jsRCwp7S/cRmWBnJ4nw== dependencies: "@hapi/hoek" "^9.0.0" "@hapi/topo" "^5.0.0" "@sideway/address" "^4.1.3" - "@sideway/formula" "^3.0.0" + "@sideway/formula" "^3.0.1" "@sideway/pinpoint" "^2.0.0" +js-sdsl@^4.1.4: + version "4.4.0" + resolved "https://registry.yarnpkg.com/js-sdsl/-/js-sdsl-4.4.0.tgz#8b437dbe642daa95760400b602378ed8ffea8430" + integrity sha512-FfVSdx6pJ41Oa+CF7RDaFmTnCaFhua+SNYQX74riGOpl96x+2jQCqEfQ2bnXu/5DPCqlRuiqyvTJM0Qjz26IVg== + +js-sha3@^0.8.0: + version "0.8.0" + resolved "https://registry.yarnpkg.com/js-sha3/-/js-sha3-0.8.0.tgz#b9b7a5da73afad7dedd0f8c463954cbde6818840" + integrity sha512-gF1cRrHhIzNfToc802P800N8PpXS+evLLXfsVpowqmAFR9uwbi89WvXg2QspOmXL8QL86J4T1EpFu+yUkwJY3Q== + "js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== -js-yaml@^3.13.1: +js-yaml@4.1.0, js-yaml@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602" + integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA== + dependencies: + argparse "^2.0.1" + +js-yaml@^3.10.0, js-yaml@^3.13.1: version "3.14.1" resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.1.tgz#dae812fdb3825fa306609a8717383c50c36a0537" integrity sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g== @@ -6979,74 +7502,36 @@ js-yaml@^3.13.1: argparse "^1.0.7" esprima "^4.0.0" -jsbn@~0.1.0: - version "0.1.1" - resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513" - integrity sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg== - -jsc-android@^245459.0.0: - version "245459.0.0" - resolved "https://registry.yarnpkg.com/jsc-android/-/jsc-android-245459.0.0.tgz#e584258dd0b04c9159a27fb104cd5d491fd202c9" - integrity sha512-wkjURqwaB1daNkDi2OYYbsLnIdC/lUM2nPXQKRs5pqEU9chDg435bjvo+LSaHotDENygHQDHe+ntUkkw2gwMtg== - -jscodeshift@^0.11.0: - version "0.11.0" - resolved "https://registry.yarnpkg.com/jscodeshift/-/jscodeshift-0.11.0.tgz#4f95039408f3f06b0e39bb4d53bc3139f5330e2f" - integrity sha512-SdRK2C7jjs4k/kT2mwtO07KJN9RnjxtKn03d9JVj6c3j9WwaLcFYsICYDnLAzY0hp+wG2nxl+Cm2jWLiNVYb8g== - dependencies: - "@babel/core" "^7.1.6" - "@babel/parser" "^7.1.6" - "@babel/plugin-proposal-class-properties" "^7.1.0" - "@babel/plugin-proposal-nullish-coalescing-operator" "^7.1.0" - "@babel/plugin-proposal-optional-chaining" "^7.1.0" - "@babel/plugin-transform-modules-commonjs" "^7.1.0" - "@babel/preset-flow" "^7.0.0" - "@babel/preset-typescript" "^7.1.0" - "@babel/register" "^7.0.0" +jsc-android@^250231.0.0: + version "250231.0.0" + resolved "https://registry.yarnpkg.com/jsc-android/-/jsc-android-250231.0.0.tgz#91720f8df382a108872fa4b3f558f33ba5e95262" + integrity sha512-rS46PvsjYmdmuz1OAWXY/1kCYG7pnf1TBqeTiOJr1iDz7s5DLxxC9n/ZMknLDxzYzNVfI7R95MH10emSSG1Wuw== + +jscodeshift@^0.13.1: + version "0.13.1" + resolved "https://registry.yarnpkg.com/jscodeshift/-/jscodeshift-0.13.1.tgz#69bfe51e54c831296380585c6d9e733512aecdef" + integrity sha512-lGyiEbGOvmMRKgWk4vf+lUrCWO/8YR8sUR3FKF1Cq5fovjZDlIcw3Hu5ppLHAnEXshVffvaM0eyuY/AbOeYpnQ== + dependencies: + "@babel/core" "^7.13.16" + "@babel/parser" "^7.13.16" + "@babel/plugin-proposal-class-properties" "^7.13.0" + "@babel/plugin-proposal-nullish-coalescing-operator" "^7.13.8" + "@babel/plugin-proposal-optional-chaining" "^7.13.12" + "@babel/plugin-transform-modules-commonjs" "^7.13.8" + "@babel/preset-flow" "^7.13.13" + "@babel/preset-typescript" "^7.13.0" + "@babel/register" "^7.13.16" babel-core "^7.0.0-bridge.0" - colors "^1.1.2" + chalk "^4.1.2" flow-parser "0.*" graceful-fs "^4.2.4" micromatch "^3.1.10" neo-async "^2.5.0" node-dir "^0.1.17" - recast "^0.20.3" - temp "^0.8.1" + recast "^0.20.4" + temp "^0.8.4" write-file-atomic "^2.3.0" -jsdom@^16.6.0: - version "16.7.0" - resolved "https://registry.yarnpkg.com/jsdom/-/jsdom-16.7.0.tgz#918ae71965424b197c819f8183a754e18977b710" - integrity sha512-u9Smc2G1USStM+s/x1ru5Sxrl6mPYCbByG1U/hUmqaVsm4tbNyS7CicOSRyuGQYZhTu0h84qkZZQ/I+dzizSVw== - dependencies: - abab "^2.0.5" - acorn "^8.2.4" - acorn-globals "^6.0.0" - cssom "^0.4.4" - cssstyle "^2.3.0" - data-urls "^2.0.0" - decimal.js "^10.2.1" - domexception "^2.0.1" - escodegen "^2.0.0" - form-data "^3.0.0" - html-encoding-sniffer "^2.0.1" - http-proxy-agent "^4.0.1" - https-proxy-agent "^5.0.0" - is-potential-custom-element-name "^1.0.1" - nwsapi "^2.2.0" - parse5 "6.0.1" - saxes "^5.0.1" - symbol-tree "^3.2.4" - tough-cookie "^4.0.0" - w3c-hr-time "^1.0.2" - w3c-xmlserializer "^2.0.0" - webidl-conversions "^6.1.0" - whatwg-encoding "^1.0.5" - whatwg-mimetype "^2.3.0" - whatwg-url "^8.5.0" - ws "^7.4.6" - xml-name-validator "^3.0.0" - jsesc@^2.5.1: version "2.5.2" resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-2.5.2.tgz#80564d2e483dacf6e8ef209650a67df3f0c283a4" @@ -7062,32 +7547,32 @@ json-parse-better-errors@^1.0.1: resolved "https://registry.yarnpkg.com/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz#bb867cfb3450e69107c131d1c514bab3dc8bcaa9" integrity sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw== -json-parse-even-better-errors@^2.3.0: +json-parse-even-better-errors@^2.3.0, json-parse-even-better-errors@^2.3.1: version "2.3.1" resolved "https://registry.yarnpkg.com/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz#7c47805a94319928e05777405dc12e1f7a4ee02d" integrity sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w== +json-parse-even-better-errors@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/json-parse-even-better-errors/-/json-parse-even-better-errors-3.0.0.tgz#2cb2ee33069a78870a0c7e3da560026b89669cf7" + integrity sha512-iZbGHafX/59r39gPwVPRBGw0QQKnA7tte5pSMrhWOW7swGsVvVTjmfyAV9pNqk8YGT7tRCdxRu8uzcgZwoDooA== + json-schema-traverse@^0.4.1: version "0.4.1" resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== -json-schema-traverse@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz#ae7bcb3656ab77a73ba5c49bf654f38e6b6860e2" - integrity sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug== - -json-schema@0.4.0: - version "0.4.0" - resolved "https://registry.yarnpkg.com/json-schema/-/json-schema-0.4.0.tgz#f7de4cf6efab838ebaeb3236474cbba5a1930ab5" - integrity sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA== - json-stable-stringify-without-jsonify@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651" integrity sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw== -json-stringify-safe@^5.0.1, json-stringify-safe@~5.0.1: +json-stringify-nice@^1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/json-stringify-nice/-/json-stringify-nice-1.1.4.tgz#2c937962b80181d3f317dd39aa323e14f5a60a67" + integrity sha512-5Z5RFW63yxReJ7vANgW6eZFGWaQvnPE3WNmZoOJrSkGju2etKA2L5rrOa1sm877TVTFt57A80BH1bArcmlLfPw== + +json-stringify-safe@^5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb" integrity sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA== @@ -7099,24 +7584,22 @@ json-text-sequence@~0.3.0: dependencies: "@sovpro/delimited-stream" "^1.1.0" -json5@2.x, json5@^2.2.1: - version "2.2.1" - resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.1.tgz#655d50ed1e6f95ad1a3caababd2b0efda10b395c" - integrity sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA== - -json5@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/json5/-/json5-1.0.1.tgz#779fb0018604fa854eacbf6252180d83543e3dbe" - integrity sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow== +json5@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/json5/-/json5-1.0.2.tgz#63d98d60f21b313b77c4d6da18bfa69d80e1d593" + integrity sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA== dependencies: minimist "^1.2.0" -jsonfile@^2.1.0: - version "2.4.0" - resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-2.4.0.tgz#3736a2b428b87bbda0cc83b53fa3d633a35c2ae8" - integrity sha512-PKllAqbgLgxHaj8TElYymKCAgrASebJrWpTnEkOaTowt23VKXXN0sUeriJ+eh7y6ufb/CC5ap11pz71/cM0hUw== - optionalDependencies: - graceful-fs "^4.1.6" +json5@^2.2.2, json5@^2.2.3: + version "2.2.3" + resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283" + integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg== + +jsonc-parser@3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/jsonc-parser/-/jsonc-parser-3.2.0.tgz#31ff3f4c2b9793f89c67212627c51c6394f88e76" + integrity sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w== jsonfile@^4.0.0: version "4.0.0" @@ -7134,25 +7617,25 @@ jsonfile@^6.0.1: optionalDependencies: graceful-fs "^4.1.6" -jsonify@~0.0.0: - version "0.0.0" - resolved "https://registry.yarnpkg.com/jsonify/-/jsonify-0.0.0.tgz#2c74b6ee41d93ca51b7b5aaee8f503631d252a73" - integrity sha512-trvBk1ki43VZptdBI5rIlG4YOzyeH/WefQt5rj1grasPn4iiZWKet8nkgc4GlsAylaztn0qZfUYOiTsASJFdNA== - jsonparse@^1.2.0, jsonparse@^1.3.1: version "1.3.1" resolved "https://registry.yarnpkg.com/jsonparse/-/jsonparse-1.3.1.tgz#3f4dae4a91fac315f71062f8521cc239f1366280" integrity sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg== -jsprim@^1.2.2: - version "1.4.2" - resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.2.tgz#712c65533a15c878ba59e9ed5f0e26d5b77c5feb" - integrity sha512-P2bSOMAc/ciLz6DzgjVlGJP9+BrJWu5UDGK70C2iweC5QBIeFf0ZXRvGjEj2uYgrY2MkAAhsSWHDWlFtEroZWw== - dependencies: - assert-plus "1.0.0" - extsprintf "1.3.0" - json-schema "0.4.0" - verror "1.10.0" +just-diff-apply@^5.2.0: + version "5.5.0" + resolved "https://registry.yarnpkg.com/just-diff-apply/-/just-diff-apply-5.5.0.tgz#771c2ca9fa69f3d2b54e7c3f5c1dfcbcc47f9f0f" + integrity sha512-OYTthRfSh55WOItVqwpefPtNt2VdKsq5AnAK6apdtR6yCH8pr0CmSr710J0Mf+WdQy7K/OzMy7K2MgAfdQURDw== + +just-diff@^6.0.0: + version "6.0.2" + resolved "https://registry.yarnpkg.com/just-diff/-/just-diff-6.0.2.tgz#03b65908543ac0521caf6d8eb85035f7d27ea285" + integrity sha512-S59eriX5u3/QhMNq3v/gm8Kd0w8OS6Tz2FS1NG4blv+z0MuQcBRJyFWjdovM0Rad4/P4aUPFtnkNjMjyMlMSYA== + +jwt-decode@^3.1.2: + version "3.1.2" + resolved "https://registry.yarnpkg.com/jwt-decode/-/jwt-decode-3.1.2.tgz#3fb319f3675a2df0c2895c8f5e9fa4b67b04ed59" + integrity sha512-UfpWE/VZn0iP50d8cz9NrZLM9lSWhcJ+0Gt/nm4by88UL+J1SiKN8/5dkjMmbEzwL2CAe+67GsegCbIKtbp75A== kind-of@^3.0.2, kind-of@^3.0.3, kind-of@^3.2.0: version "3.2.2" @@ -7178,13 +7661,6 @@ kind-of@^6.0.0, kind-of@^6.0.2, kind-of@^6.0.3: resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.3.tgz#07c05034a6c349fa06e24fa35aa76db4580ce4dd" integrity sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw== -klaw@^1.0.0: - version "1.3.1" - resolved "https://registry.yarnpkg.com/klaw/-/klaw-1.3.1.tgz#4088433b46b3b1ba259d78785d8e96f73ba02439" - integrity sha512-TED5xi9gGQjGpNnvRWknrwAB1eL5GciPfVFOt3Vk1OJCVDQbzuSfrF3hkUQKlsgKrG1F+0t5W0m+Fje1jIt8rw== - optionalDependencies: - graceful-fs "^4.1.9" - kleur@^3.0.3: version "3.0.3" resolved "https://registry.yarnpkg.com/kleur/-/kleur-3.0.3.tgz#a79c9ecc86ee1ce3fa6206d1216c501f147fc07e" @@ -7203,29 +7679,87 @@ ky@^0.25.1: resolved "https://registry.yarnpkg.com/ky/-/ky-0.25.1.tgz#0df0bd872a9cc57e31acd5dbc1443547c881bfbc" integrity sha512-PjpCEWlIU7VpiMVrTwssahkYXX1by6NCT0fhTUX34F3DTinARlgMpriuroolugFPcMgpPWrOW4mTb984Qm1RXA== -lerna@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/lerna/-/lerna-4.0.0.tgz#b139d685d50ea0ca1be87713a7c2f44a5b678e9e" - integrity sha512-DD/i1znurfOmNJb0OBw66NmNqiM8kF6uIrzrJ0wGE3VNdzeOhz9ziWLYiRaZDGGwgbcjOo6eIfcx9O5Qynz+kg== - dependencies: - "@lerna/add" "4.0.0" - "@lerna/bootstrap" "4.0.0" - "@lerna/changed" "4.0.0" - "@lerna/clean" "4.0.0" - "@lerna/cli" "4.0.0" - "@lerna/create" "4.0.0" - "@lerna/diff" "4.0.0" - "@lerna/exec" "4.0.0" - "@lerna/import" "4.0.0" - "@lerna/info" "4.0.0" - "@lerna/init" "4.0.0" - "@lerna/link" "4.0.0" - "@lerna/list" "4.0.0" - "@lerna/publish" "4.0.0" - "@lerna/run" "4.0.0" - "@lerna/version" "4.0.0" +lerna@^6.5.1: + version "6.6.1" + resolved "https://registry.yarnpkg.com/lerna/-/lerna-6.6.1.tgz#4897171aed64e244a2d0f9000eef5c5b228f9332" + integrity sha512-WJtrvmbmR+6hMB9b5pvsxJzew0lRL6hARgW/My9BM4vYaxwPIA2I0riv3qQu5Zd7lYse7FEqJkTnl9Kn1bXhLA== + dependencies: + "@lerna/child-process" "6.6.1" + "@lerna/create" "6.6.1" + "@lerna/legacy-package-management" "6.6.1" + "@npmcli/arborist" "6.2.3" + "@npmcli/run-script" "4.1.7" + "@nrwl/devkit" ">=15.5.2 < 16" + "@octokit/plugin-enterprise-rest" "6.0.1" + "@octokit/rest" "19.0.3" + byte-size "7.0.0" + chalk "4.1.0" + clone-deep "4.0.1" + cmd-shim "5.0.0" + columnify "1.6.0" + config-chain "1.1.12" + conventional-changelog-angular "5.0.12" + conventional-changelog-core "4.2.4" + conventional-recommended-bump "6.1.0" + cosmiconfig "7.0.0" + dedent "0.7.0" + dot-prop "6.0.1" + envinfo "^7.7.4" + execa "5.0.0" + fs-extra "9.1.0" + get-port "5.1.1" + get-stream "6.0.0" + git-url-parse "13.1.0" + glob-parent "5.1.2" + globby "11.1.0" + graceful-fs "4.2.10" + has-unicode "2.0.1" import-local "^3.0.2" - npmlog "^4.1.2" + init-package-json "3.0.2" + inquirer "^8.2.4" + is-ci "2.0.0" + is-stream "2.0.0" + js-yaml "^4.1.0" + libnpmaccess "6.0.3" + libnpmpublish "6.0.4" + load-json-file "6.2.0" + make-dir "3.1.0" + minimatch "3.0.5" + multimatch "5.0.0" + node-fetch "2.6.7" + npm-package-arg "8.1.1" + npm-packlist "5.1.1" + npm-registry-fetch "^14.0.3" + npmlog "^6.0.2" + nx ">=15.5.2 < 16" + p-map "4.0.0" + p-map-series "2.1.0" + p-pipe "3.1.0" + p-queue "6.6.2" + p-reduce "2.1.0" + p-waterfall "2.1.1" + pacote "13.6.2" + pify "5.0.0" + read-cmd-shim "3.0.0" + read-package-json "5.0.1" + resolve-from "5.0.0" + rimraf "^4.4.1" + semver "^7.3.8" + signal-exit "3.0.7" + slash "3.0.0" + ssri "9.0.1" + strong-log-transformer "2.1.0" + tar "6.1.11" + temp-dir "1.0.0" + typescript "^3 || ^4" + upath "^2.0.1" + uuid "8.3.2" + validate-npm-package-license "3.0.4" + validate-npm-package-name "4.0.0" + write-file-atomic "4.0.1" + write-pkg "4.0.0" + yargs "16.2.0" + yargs-parser "20.2.4" leven@^3.1.0: version "3.1.0" @@ -7240,56 +7774,55 @@ levn@^0.4.1: prelude-ls "^1.2.1" type-check "~0.4.0" -levn@~0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/levn/-/levn-0.3.0.tgz#3b09924edf9f083c0490fdd4c0bc4421e04764ee" - integrity sha512-0OO4y2iOHix2W6ujICbKIaEQXvFQHue65vUG3pb5EUomzPI90z9hsA1VsO/dbIIpC53J8gxM9Q4Oho0jrCM/yA== - dependencies: - prelude-ls "~1.1.2" - type-check "~0.3.2" - -libnpmaccess@^4.0.1: - version "4.0.3" - resolved "https://registry.yarnpkg.com/libnpmaccess/-/libnpmaccess-4.0.3.tgz#dfb0e5b0a53c315a2610d300e46b4ddeb66e7eec" - integrity sha512-sPeTSNImksm8O2b6/pf3ikv4N567ERYEpeKRPSmqlNt1dTZbvgpJIzg5vAhXHpw2ISBsELFRelk0jEahj1c6nQ== +libnpmaccess@6.0.3: + version "6.0.3" + resolved "https://registry.yarnpkg.com/libnpmaccess/-/libnpmaccess-6.0.3.tgz#473cc3e4aadb2bc713419d92e45d23b070d8cded" + integrity sha512-4tkfUZprwvih2VUZYMozL7EMKgQ5q9VW2NtRyxWtQWlkLTAWHRklcAvBN49CVqEkhUw7vTX2fNgB5LzgUucgYg== dependencies: aproba "^2.0.0" minipass "^3.1.1" - npm-package-arg "^8.1.2" - npm-registry-fetch "^11.0.0" + npm-package-arg "^9.0.1" + npm-registry-fetch "^13.0.0" -libnpmpublish@^4.0.0: - version "4.0.2" - resolved "https://registry.yarnpkg.com/libnpmpublish/-/libnpmpublish-4.0.2.tgz#be77e8bf5956131bcb45e3caa6b96a842dec0794" - integrity sha512-+AD7A2zbVeGRCFI2aO//oUmapCwy7GHqPXFJh3qpToSRNU+tXKJ2YFUgjt04LPPAf2dlEH95s6EhIHM1J7bmOw== +libnpmpublish@6.0.4: + version "6.0.4" + resolved "https://registry.yarnpkg.com/libnpmpublish/-/libnpmpublish-6.0.4.tgz#adb41ec6b0c307d6f603746a4d929dcefb8f1a0b" + integrity sha512-lvAEYW8mB8QblL6Q/PI/wMzKNvIrF7Kpujf/4fGS/32a2i3jzUXi04TNyIBcK6dQJ34IgywfaKGh+Jq4HYPFmg== dependencies: - normalize-package-data "^3.0.2" - npm-package-arg "^8.1.2" - npm-registry-fetch "^11.0.0" - semver "^7.1.3" - ssri "^8.0.1" + normalize-package-data "^4.0.0" + npm-package-arg "^9.0.1" + npm-registry-fetch "^13.0.0" + semver "^7.3.7" + ssri "^9.0.0" + +libphonenumber-js@^1.10.14: + version "1.10.28" + resolved "https://registry.yarnpkg.com/libphonenumber-js/-/libphonenumber-js-1.10.28.tgz#cae7e929cad96cee5ecc9449027192ecba39ee72" + integrity sha512-1eAgjLrZA0+2Wgw4hs+4Q/kEBycxQo8ZLYnmOvZ3AlM8ImAVAJgDPlZtISLEzD1vunc2q8s2Pn7XwB7I8U3Kzw== -libphonenumber-js@^1.9.7: - version "1.10.13" - resolved "https://registry.yarnpkg.com/libphonenumber-js/-/libphonenumber-js-1.10.13.tgz#0b5833c7fdbf671140530d83531c6753f7e0ea3c" - integrity sha512-b74iyWmwb4GprAUPjPkJ11GTC7KX4Pd3onpJfKxYyY8y9Rbb4ERY47LvCMEDM09WD3thiLDMXtkfDK/AX+zT7Q== +libsodium-wrappers@^0.7.6: + version "0.7.11" + resolved "https://registry.yarnpkg.com/libsodium-wrappers/-/libsodium-wrappers-0.7.11.tgz#53bd20606dffcc54ea2122133c7da38218f575f7" + integrity sha512-SrcLtXj7BM19vUKtQuyQKiQCRJPgbpauzl3s0rSwD+60wtHqSUuqcoawlMDheCJga85nKOQwxNYQxf/CKAvs6Q== + dependencies: + libsodium "^0.7.11" + +libsodium@^0.7.11: + version "0.7.11" + resolved "https://registry.yarnpkg.com/libsodium/-/libsodium-0.7.11.tgz#cd10aae7bcc34a300cc6ad0ac88fcca674cfbc2e" + integrity sha512-WPfJ7sS53I2s4iM58QxY3Inb83/6mjlYgcmZs7DJsvDlnmVUwNinBCi5vBT43P6bHRy01O4zsMU2CoVR6xJ40A== lines-and-columns@^1.1.6: version "1.2.4" resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz#eca284f75d2965079309dc0ad9255abb2ebc1632" integrity sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg== -load-json-file@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-4.0.0.tgz#2f5f45ab91e33216234fd53adab668eb4ec0993b" - integrity sha512-Kx8hMakjX03tiGTLAIdJ+lL0htKnXjEZN6hk/tozf/WOuYGdZBJrZ+rCJRbVCugsjB3jMLn9746NsQIf5VjBMw== - dependencies: - graceful-fs "^4.1.2" - parse-json "^4.0.0" - pify "^3.0.0" - strip-bom "^3.0.0" +lines-and-columns@~2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-2.0.3.tgz#b2f0badedb556b747020ab8ea7f0373e22efac1b" + integrity sha512-cNOjgCnLB+FnvWWtyRTzmB3POJ+cXxTA81LoW7u8JdmhfXzriropYwpjShnz1QLLWsQwY7nIxoDmcPTwphDK9w== -load-json-file@^6.2.0: +load-json-file@6.2.0: version "6.2.0" resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-6.2.0.tgz#5c7770b42cafa97074ca2848707c61662f4251a1" integrity sha512-gUD/epcRms75Cw8RT1pUdHugZYM5ce64ucs2GEISABwkRsOQr0q2wm/MV2TKThycIe5e0ytRweW2RZxclogCdQ== @@ -7299,6 +7832,16 @@ load-json-file@^6.2.0: strip-bom "^4.0.0" type-fest "^0.6.0" +load-json-file@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-4.0.0.tgz#2f5f45ab91e33216234fd53adab668eb4ec0993b" + integrity sha512-Kx8hMakjX03tiGTLAIdJ+lL0htKnXjEZN6hk/tozf/WOuYGdZBJrZ+rCJRbVCugsjB3jMLn9746NsQIf5VjBMw== + dependencies: + graceful-fs "^4.1.2" + parse-json "^4.0.0" + pify "^3.0.0" + strip-bom "^3.0.0" + locate-path@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-2.0.0.tgz#2b568b265eec944c6d9c0de9c3dbbbca0354cd8e" @@ -7329,11 +7872,6 @@ locate-path@^6.0.0: dependencies: p-locate "^5.0.0" -lodash._reinterpolate@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz#0ccf2d89166af03b3663c796538b75ac6e114d9d" - integrity sha512-xYHt68QRoYGjeeM/XOE1uJtvXQAgvszfBhjV4yvsQH0u2i9I6cI6c6/eG4Hh3UAOVn0y/xAXwmTzEay49Q//HA== - lodash.camelcase@^4.3.0: version "4.3.0" resolved "https://registry.yarnpkg.com/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz#b28aa6288a2b9fc651035c7711f65ab6190331a6" @@ -7359,42 +7897,23 @@ lodash.merge@^4.6.2: resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a" integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ== -lodash.template@^4.5.0: - version "4.5.0" - resolved "https://registry.yarnpkg.com/lodash.template/-/lodash.template-4.5.0.tgz#f976195cf3f347d0d5f52483569fe8031ccce8ab" - integrity sha512-84vYFxIkmidUiFxidA/KjjH9pAycqW+h980j7Fuz5qxRtO9pgB7MDFTdys1N7A5mcucRiDyEq4fusljItR1T/A== - dependencies: - lodash._reinterpolate "^3.0.0" - lodash.templatesettings "^4.0.0" - -lodash.templatesettings@^4.0.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/lodash.templatesettings/-/lodash.templatesettings-4.2.0.tgz#e481310f049d3cf6d47e912ad09313b154f0fb33" - integrity sha512-stgLz+i3Aa9mZgnjr/O+v9ruKZsPsndy7qPZOchbqk2cnTU1ZaldKK+v7m54WoKIyxiuMZTKT2H81F8BeAc3ZQ== - dependencies: - lodash._reinterpolate "^3.0.0" - lodash.throttle@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/lodash.throttle/-/lodash.throttle-4.1.1.tgz#c23e91b710242ac70c37f1e1cda9274cc39bf2f4" integrity sha512-wIkUCfVKpVsWo3JSZlc+8MB5it+2AN5W8J7YVMST30UrvcQNZ1Okbj+rbVniijTWE6FGYy4XJq/rHkas8qJMLQ== -lodash.truncate@^4.4.2: - version "4.4.2" - resolved "https://registry.yarnpkg.com/lodash.truncate/-/lodash.truncate-4.4.2.tgz#5a350da0b1113b837ecfffd5812cbe58d6eae193" - integrity sha512-jttmRe7bRse52OsWIMDLaXxWqRAmtIUccAQ3garviCqJjafXOfNMO0yMfNpdD6zbGaTU0P5Nz7e7gAT6cKmJRw== - -lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.7.0: +lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.21: version "4.17.21" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== -log-symbols@^2.2.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-2.2.0.tgz#5740e1c5d6f0dfda4ad9323b5332107ef6b4c40a" - integrity sha512-VeIAFslyIerEJLXHziedo2basKbMKtTw3vfn5IzG0XTjhAVEJyNHnL2p7vc+wBDSdQuUpNw3M2u6xb9QsAY5Eg== +log-symbols@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-4.1.0.tgz#3fbdbb95b4683ac9fc785111e792e558d4abd503" + integrity sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg== dependencies: - chalk "^2.0.1" + chalk "^4.1.0" + is-unicode-supported "^0.1.0" logkitty@^0.7.1: version "0.7.1" @@ -7405,6 +7924,16 @@ logkitty@^0.7.1: dayjs "^1.8.15" yargs "^15.1.0" +long@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/long/-/long-4.0.0.tgz#9a7b71cfb7d361a194ea555241c92f7468d5bf28" + integrity sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA== + +long@^5.0.0, long@^5.2.1: + version "5.2.3" + resolved "https://registry.yarnpkg.com/long/-/long-5.2.3.tgz#a3ba97f3877cf1d778eccbcb048525ebb77499e1" + integrity sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q== + loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf" @@ -7412,6 +7941,13 @@ loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.4.0: dependencies: js-tokens "^3.0.0 || ^4.0.0" +lru-cache@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-5.1.1.tgz#1da27e6710271947695daf6848e847f01d84b920" + integrity sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w== + dependencies: + yallist "^3.0.2" + lru-cache@^6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94" @@ -7419,15 +7955,32 @@ lru-cache@^6.0.0: dependencies: yallist "^4.0.0" +lru-cache@^7.4.4, lru-cache@^7.5.1, lru-cache@^7.7.1: + version "7.18.3" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-7.18.3.tgz#f793896e0fd0e954a59dfdd82f0773808df6aa89" + integrity sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA== + +lru-cache@^9.0.0: + version "9.1.1" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-9.1.1.tgz#c58a93de58630b688de39ad04ef02ef26f1902f1" + integrity sha512-65/Jky17UwSb0BuB9V+MyDpsOtXKmYwzhyl+cOa9XUiI4uV2Ouy/2voFP3+al0BjZbJgMBD8FojMpAf+Z+qn4A== + lru_map@^0.4.1: version "0.4.1" resolved "https://registry.yarnpkg.com/lru_map/-/lru_map-0.4.1.tgz#f7b4046283c79fb7370c36f8fca6aee4324b0a98" integrity sha512-I+lBvqMMFfqaV8CJCISjI3wbjmwVu/VyOoU7+qtu9d7ioW5klMgsTTiUOUp+DJvfTTzKXoPbyC6YfgkNcyPSOg== -luxon@^1.27.0: - version "1.28.0" - resolved "https://registry.yarnpkg.com/luxon/-/luxon-1.28.0.tgz#e7f96daad3938c06a62de0fb027115d251251fbf" - integrity sha512-TfTiyvZhwBYM/7QdAVDh+7dBTBA29v4ik0Ce9zda3Mnf8on1S5KJI8P2jKFZ8+5C0jhmr0KwJEO/Wdpm0VeWJQ== +luxon@^3.3.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/luxon/-/luxon-3.3.0.tgz#d73ab5b5d2b49a461c47cedbc7e73309b4805b48" + integrity sha512-An0UCfG/rSiqtAIiBPO0Y9/zAnHUZxAMiCpTd5h2smgsj7GGmcenvrvww2cqNA8/4A5ZrD1gJpHN2mIHZQF+Mg== + +make-dir@3.1.0, make-dir@^3.0.0, make-dir@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-3.1.0.tgz#415e967046b3a7f1d185277d84aa58203726a13f" + integrity sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw== + dependencies: + semver "^6.0.0" make-dir@^2.0.0, make-dir@^2.1.0: version "2.1.0" @@ -7437,40 +7990,55 @@ make-dir@^2.0.0, make-dir@^2.1.0: pify "^4.0.1" semver "^5.6.0" -make-dir@^3.0.0, make-dir@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-3.1.0.tgz#415e967046b3a7f1d185277d84aa58203726a13f" - integrity sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw== - dependencies: - semver "^6.0.0" - make-error@1.x, make-error@^1.1.1, make-error@^1.3.6: version "1.3.6" resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.6.tgz#2eb2e37ea9b67c4891f684a1394799af484cf7a2" integrity sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw== -make-fetch-happen@^8.0.9: - version "8.0.14" - resolved "https://registry.yarnpkg.com/make-fetch-happen/-/make-fetch-happen-8.0.14.tgz#aaba73ae0ab5586ad8eaa68bd83332669393e222" - integrity sha512-EsS89h6l4vbfJEtBZnENTOFk8mCRpY5ru36Xe5bcX1KYIli2mkSHqoFsp5O1wMDvTJJzxe/4THpCTtygjeeGWQ== +make-fetch-happen@^10.0.3, make-fetch-happen@^10.0.6: + version "10.2.1" + resolved "https://registry.yarnpkg.com/make-fetch-happen/-/make-fetch-happen-10.2.1.tgz#f5e3835c5e9817b617f2770870d9492d28678164" + integrity sha512-NgOPbRiaQM10DYXvN3/hhGVI2M5MtITFryzBGxHM5p4wnFxsVCbxkrBrDsk+EZ5OB4jEOT7AjDxtdF+KVEFT7w== dependencies: - agentkeepalive "^4.1.3" - cacache "^15.0.5" + agentkeepalive "^4.2.1" + cacache "^16.1.0" http-cache-semantics "^4.1.0" - http-proxy-agent "^4.0.1" + http-proxy-agent "^5.0.0" https-proxy-agent "^5.0.0" is-lambda "^1.0.1" - lru-cache "^6.0.0" - minipass "^3.1.3" + lru-cache "^7.7.1" + minipass "^3.1.6" minipass-collect "^1.0.2" - minipass-fetch "^1.3.2" + minipass-fetch "^2.0.3" minipass-flush "^1.0.5" minipass-pipeline "^1.2.4" + negotiator "^0.6.3" promise-retry "^2.0.1" - socks-proxy-agent "^5.0.0" - ssri "^8.0.0" + socks-proxy-agent "^7.0.0" + ssri "^9.0.0" + +make-fetch-happen@^11.0.0, make-fetch-happen@^11.0.1: + version "11.1.0" + resolved "https://registry.yarnpkg.com/make-fetch-happen/-/make-fetch-happen-11.1.0.tgz#f26b05e89317e960b75fd5e080e40d40f8d7b2a5" + integrity sha512-7ChuOzCb1LzdQZrTy0ky6RsCoMYeM+Fh4cY0+4zsJVhNcH5Q3OJojLY1mGkD0xAhWB29lskECVb6ZopofwjldA== + dependencies: + agentkeepalive "^4.2.1" + cacache "^17.0.0" + http-cache-semantics "^4.1.1" + http-proxy-agent "^5.0.0" + https-proxy-agent "^5.0.0" + is-lambda "^1.0.1" + lru-cache "^7.7.1" + minipass "^4.0.0" + minipass-fetch "^3.0.0" + minipass-flush "^1.0.5" + minipass-pipeline "^1.2.4" + negotiator "^0.6.3" + promise-retry "^2.0.1" + socks-proxy-agent "^7.0.0" + ssri "^10.0.0" -make-fetch-happen@^9.0.1, make-fetch-happen@^9.1.0: +make-fetch-happen@^9.1.0: version "9.1.0" resolved "https://registry.yarnpkg.com/make-fetch-happen/-/make-fetch-happen-9.1.0.tgz#53085a09e7971433e6765f7971bf63f4e05cb968" integrity sha512-+zopwDy7DNknmwPQplem5lAZX/eCOzSvSNNcSKm5eVwTkOBzoktEfXsa9L23J/GIRhxRsaxzkPEhrJEpE2F4Gg== @@ -7531,6 +8099,11 @@ media-typer@0.3.0: resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" integrity sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ== +memoize-one@^5.0.0: + version "5.2.1" + resolved "https://registry.yarnpkg.com/memoize-one/-/memoize-one-5.2.1.tgz#8337aa3c4335581839ec01c3d594090cebe8f00e" + integrity sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q== + meow@^8.0.0: version "8.1.2" resolved "https://registry.yarnpkg.com/meow/-/meow-8.1.2.tgz#bcbe45bda0ee1729d350c03cffc8395a36c4e897" @@ -7568,92 +8141,106 @@ methods@~1.1.2: resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" integrity sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w== -metro-babel-register@0.64.0: - version "0.64.0" - resolved "https://registry.yarnpkg.com/metro-babel-register/-/metro-babel-register-0.64.0.tgz#1a2d23f68da8b8ee42e78dca37ad21a5f4d3647d" - integrity sha512-Kf6YvE3kIRumGnjK0Q9LqGDIdnsX9eFGtNBmBuCVDuB9wGGA/5CgX8We8W7Y44dz1RGTcHJRhfw5iGg+pwC3aQ== - dependencies: - "@babel/core" "^7.0.0" - "@babel/plugin-proposal-class-properties" "^7.0.0" - "@babel/plugin-proposal-nullish-coalescing-operator" "^7.0.0" - "@babel/plugin-proposal-optional-chaining" "^7.0.0" - "@babel/plugin-transform-flow-strip-types" "^7.0.0" - "@babel/plugin-transform-modules-commonjs" "^7.0.0" - "@babel/register" "^7.0.0" - escape-string-regexp "^1.0.5" - -metro-babel-transformer@0.64.0: - version "0.64.0" - resolved "https://registry.yarnpkg.com/metro-babel-transformer/-/metro-babel-transformer-0.64.0.tgz#a21f8a989a5ea60c1109456e21bd4d9374194ea0" - integrity sha512-itZaxKTgmKGEZWxNzbSZBc22NngrMZzoUNuU92aHSTGkYi2WH4XlvzEHsstmIKHMsRVKl75cA+mNmgk4gBFJKw== +metro-babel-transformer@0.73.9: + version "0.73.9" + resolved "https://registry.yarnpkg.com/metro-babel-transformer/-/metro-babel-transformer-0.73.9.tgz#bec8aaaf1bbdc2e469fde586fde455f8b2a83073" + integrity sha512-DlYwg9wwYIZTHtic7dyD4BP0SDftoltZ3clma76nHu43blMWsCnrImHeHsAVne3XsQ+RJaSRxhN5nkG2VyVHwA== dependencies: - "@babel/core" "^7.0.0" - metro-source-map "0.64.0" + "@babel/core" "^7.20.0" + hermes-parser "0.8.0" + metro-source-map "0.73.9" nullthrows "^1.1.1" -metro-cache-key@0.64.0: - version "0.64.0" - resolved "https://registry.yarnpkg.com/metro-cache-key/-/metro-cache-key-0.64.0.tgz#98d0a94332453c4c52b74f72c07cc62a5c264c4f" - integrity sha512-O9B65G8L/fopck45ZhdRosyVZdMtUQuX5mBWEC1NRj02iWBIUPLmYMjrunqIe8vHipCMp3DtTCm/65IlBmO8jg== +metro-cache-key@0.73.9: + version "0.73.9" + resolved "https://registry.yarnpkg.com/metro-cache-key/-/metro-cache-key-0.73.9.tgz#7d8c441a3b7150f7b201273087ef3cf7d3435d9f" + integrity sha512-uJg+6Al7UoGIuGfoxqPBy6y1Ewq7Y8/YapGYIDh6sohInwt/kYKnPZgLDYHIPvY2deORnQ/2CYo4tOeBTnhCXQ== -metro-cache@0.64.0: - version "0.64.0" - resolved "https://registry.yarnpkg.com/metro-cache/-/metro-cache-0.64.0.tgz#a769503e12521d9e9d95ce5840ffb2efdb4e8703" - integrity sha512-QvGfxe/1QQYM9XOlR8W1xqE9eHDw/AgJIgYGn/TxZxBu9Zga+Rgs1omeSZju45D8w5VWgMr83ma5kACgzvOecg== +metro-cache@0.73.9: + version "0.73.9" + resolved "https://registry.yarnpkg.com/metro-cache/-/metro-cache-0.73.9.tgz#773c2df6ba53434e58ccbe421b0c54e6da8d2890" + integrity sha512-upiRxY8rrQkUWj7ieACD6tna7xXuXdu2ZqrheksT79ePI0aN/t0memf6WcyUtJUMHZetke3j+ppELNvlmp3tOw== dependencies: - metro-core "0.64.0" - mkdirp "^0.5.1" - rimraf "^2.5.4" + metro-core "0.73.9" + rimraf "^3.0.2" -metro-config@0.64.0, metro-config@^0.64.0: - version "0.64.0" - resolved "https://registry.yarnpkg.com/metro-config/-/metro-config-0.64.0.tgz#b634fa05cffd06b1e50e4339c200f90a42924afb" - integrity sha512-QhM4asnX5KhlRWaugwVGNNXhX0Z85u5nK0UQ/A90bBb4xWyXqUe20e788VtdA75rkQiiI6wXTCIHWT0afbnjwQ== +metro-config@0.73.9: + version "0.73.9" + resolved "https://registry.yarnpkg.com/metro-config/-/metro-config-0.73.9.tgz#6b43c70681bdd6b00f44400fc76dddbe53374500" + integrity sha512-NiWl1nkYtjqecDmw77tbRbXnzIAwdO6DXGZTuKSkH+H/c1NKq1eizO8Fe+NQyFtwR9YLqn8Q0WN1nmkwM1j8CA== dependencies: cosmiconfig "^5.0.5" jest-validate "^26.5.2" - metro "0.64.0" - metro-cache "0.64.0" - metro-core "0.64.0" - metro-runtime "0.64.0" + metro "0.73.9" + metro-cache "0.73.9" + metro-core "0.73.9" + metro-runtime "0.73.9" -metro-core@0.64.0, metro-core@^0.64.0: - version "0.64.0" - resolved "https://registry.yarnpkg.com/metro-core/-/metro-core-0.64.0.tgz#7616b27acfe7baa476f6cd6bd9e70ae64fa62541" - integrity sha512-v8ZQ5j72EaUwamQ8pLfHlOHTyp7SbdazvHPzFGDpHnwIQqIT0Bw3Syg8R4regTlVG3ngpeSEAi005UITljmMcQ== +metro-core@0.73.9: + version "0.73.9" + resolved "https://registry.yarnpkg.com/metro-core/-/metro-core-0.73.9.tgz#410c5c0aeae840536c10039f68098fdab3da568e" + integrity sha512-1NTs0IErlKcFTfYyRT3ljdgrISWpl1nys+gaHkXapzTSpvtX9F1NQNn5cgAuE+XIuTJhbsCdfIJiM2JXbrJQaQ== dependencies: - jest-haste-map "^26.5.2" lodash.throttle "^4.1.1" - metro-resolver "0.64.0" + metro-resolver "0.73.9" + +metro-file-map@0.73.9: + version "0.73.9" + resolved "https://registry.yarnpkg.com/metro-file-map/-/metro-file-map-0.73.9.tgz#09c04a8e8ef1eaa6ecb2b9cb8cb53bb0fa0167ec" + integrity sha512-R/Wg3HYeQhYY3ehWtfedw8V0ne4lpufG7a21L3GWer8tafnC9pmjoCKEbJz9XZkVj9i1FtxE7UTbrtZNeIILxQ== + dependencies: + abort-controller "^3.0.0" + anymatch "^3.0.3" + debug "^2.2.0" + fb-watchman "^2.0.0" + graceful-fs "^4.2.4" + invariant "^2.2.4" + jest-regex-util "^27.0.6" + jest-serializer "^27.0.6" + jest-util "^27.2.0" + jest-worker "^27.2.0" + micromatch "^4.0.4" + nullthrows "^1.1.1" + walker "^1.0.7" + optionalDependencies: + fsevents "^2.3.2" -metro-hermes-compiler@0.64.0: - version "0.64.0" - resolved "https://registry.yarnpkg.com/metro-hermes-compiler/-/metro-hermes-compiler-0.64.0.tgz#e6043d7aa924e5b2be99bd3f602e693685d15386" - integrity sha512-CLAjVDWGAoGhbi2ZyPHnH5YDdfrDIx6+tzFWfHGIMTZkYBXsYta9IfYXBV8lFb6BIbrXLjlXZAOoosknetMPOA== +metro-hermes-compiler@0.73.9: + version "0.73.9" + resolved "https://registry.yarnpkg.com/metro-hermes-compiler/-/metro-hermes-compiler-0.73.9.tgz#6f473e67e8f76066066f00e2e0ecce865f7d445d" + integrity sha512-5B3vXIwQkZMSh3DQQY23XpTCpX9kPLqZbA3rDuAcbGW0tzC3f8dCenkyBb0GcCzyTDncJeot/A7oVCVK6zapwg== -metro-inspector-proxy@0.64.0: - version "0.64.0" - resolved "https://registry.yarnpkg.com/metro-inspector-proxy/-/metro-inspector-proxy-0.64.0.tgz#9a481b3f49773d5418e028178efec68f861bec88" - integrity sha512-KywbH3GNSz9Iqw4UH3smgaV2dBHHYMISeN7ORntDL/G+xfgPc6vt13d+zFb907YpUcXj5N0vdoiAHI5V/0y8IA== +metro-inspector-proxy@0.73.9: + version "0.73.9" + resolved "https://registry.yarnpkg.com/metro-inspector-proxy/-/metro-inspector-proxy-0.73.9.tgz#8e11cd300adf3f904f1f5afe28b198312cdcd8c2" + integrity sha512-B3WrWZnlYhtTrv0IaX3aUAhi2qVILPAZQzb5paO1e+xrz4YZHk9c7dXv7qe7B/IQ132e3w46y3AL7rFo90qVjA== dependencies: connect "^3.6.5" debug "^2.2.0" - ws "^1.1.5" - yargs "^15.3.1" + ws "^7.5.1" + yargs "^17.5.1" + +metro-minify-terser@0.73.9: + version "0.73.9" + resolved "https://registry.yarnpkg.com/metro-minify-terser/-/metro-minify-terser-0.73.9.tgz#301aef2e106b0802f7a14ef0f2b4883b20c80018" + integrity sha512-MTGPu2qV5qtzPJ2SqH6s58awHDtZ4jd7lmmLR+7TXDwtZDjIBA0YVfI0Zak2Haby2SqoNKrhhUns/b4dPAQAVg== + dependencies: + terser "^5.15.0" -metro-minify-uglify@0.64.0: - version "0.64.0" - resolved "https://registry.yarnpkg.com/metro-minify-uglify/-/metro-minify-uglify-0.64.0.tgz#da6ab4dda030e3211f5924e7f41ed308d466068f" - integrity sha512-DRwRstqXR5qfte9Nuwoov5dRXxL7fJeVlO5fGyOajWeO3+AgPjvjXh/UcLJqftkMWTPGUFuzAD5/7JC5v5FLWw== +metro-minify-uglify@0.73.9: + version "0.73.9" + resolved "https://registry.yarnpkg.com/metro-minify-uglify/-/metro-minify-uglify-0.73.9.tgz#cf4f8c19b688deea103905689ec736c2f2acd733" + integrity sha512-gzxD/7WjYcnCNGiFJaA26z34rjOp+c/Ft++194Wg91lYep3TeWQ0CnH8t2HRS7AYDHU81SGWgvD3U7WV0g4LGA== dependencies: uglify-es "^3.1.9" -metro-react-native-babel-preset@0.64.0: - version "0.64.0" - resolved "https://registry.yarnpkg.com/metro-react-native-babel-preset/-/metro-react-native-babel-preset-0.64.0.tgz#76861408681dfda3c1d962eb31a8994918c976f8" - integrity sha512-HcZ0RWQRuJfpPiaHyFQJzcym+/dDIVUPwUAXWoub/C4GkGu+mPjp8vqK6g0FxokCnnI2TK0gZTza2IDfiNNscQ== +metro-react-native-babel-preset@0.73.9: + version "0.73.9" + resolved "https://registry.yarnpkg.com/metro-react-native-babel-preset/-/metro-react-native-babel-preset-0.73.9.tgz#ef54637dd20f025197beb49e71309a9c539e73e2" + integrity sha512-AoD7v132iYDV4K78yN2OLgTPwtAKn0XlD2pOhzyBxiI8PeXzozhbKyPV7zUOJUPETj+pcEVfuYj5ZN/8+bhbCw== dependencies: - "@babel/core" "^7.0.0" + "@babel/core" "^7.20.0" + "@babel/plugin-proposal-async-generator-functions" "^7.0.0" "@babel/plugin-proposal-class-properties" "^7.0.0" "@babel/plugin-proposal-export-default-from" "^7.0.0" "@babel/plugin-proposal-nullish-coalescing-operator" "^7.0.0" @@ -7662,27 +8249,25 @@ metro-react-native-babel-preset@0.64.0: "@babel/plugin-proposal-optional-chaining" "^7.0.0" "@babel/plugin-syntax-dynamic-import" "^7.0.0" "@babel/plugin-syntax-export-default-from" "^7.0.0" - "@babel/plugin-syntax-flow" "^7.2.0" + "@babel/plugin-syntax-flow" "^7.18.0" "@babel/plugin-syntax-nullish-coalescing-operator" "^7.0.0" "@babel/plugin-syntax-optional-chaining" "^7.0.0" "@babel/plugin-transform-arrow-functions" "^7.0.0" + "@babel/plugin-transform-async-to-generator" "^7.0.0" "@babel/plugin-transform-block-scoping" "^7.0.0" "@babel/plugin-transform-classes" "^7.0.0" "@babel/plugin-transform-computed-properties" "^7.0.0" "@babel/plugin-transform-destructuring" "^7.0.0" - "@babel/plugin-transform-exponentiation-operator" "^7.0.0" "@babel/plugin-transform-flow-strip-types" "^7.0.0" - "@babel/plugin-transform-for-of" "^7.0.0" "@babel/plugin-transform-function-name" "^7.0.0" "@babel/plugin-transform-literals" "^7.0.0" "@babel/plugin-transform-modules-commonjs" "^7.0.0" - "@babel/plugin-transform-object-assign" "^7.0.0" + "@babel/plugin-transform-named-capturing-groups-regex" "^7.0.0" "@babel/plugin-transform-parameters" "^7.0.0" "@babel/plugin-transform-react-display-name" "^7.0.0" "@babel/plugin-transform-react-jsx" "^7.0.0" "@babel/plugin-transform-react-jsx-self" "^7.0.0" "@babel/plugin-transform-react-jsx-source" "^7.0.0" - "@babel/plugin-transform-regenerator" "^7.0.0" "@babel/plugin-transform-runtime" "^7.0.0" "@babel/plugin-transform-shorthand-properties" "^7.0.0" "@babel/plugin-transform-spread" "^7.0.0" @@ -7693,144 +8278,147 @@ metro-react-native-babel-preset@0.64.0: "@babel/template" "^7.0.0" react-refresh "^0.4.0" -metro-react-native-babel-transformer@0.64.0, metro-react-native-babel-transformer@^0.64.0: - version "0.64.0" - resolved "https://registry.yarnpkg.com/metro-react-native-babel-transformer/-/metro-react-native-babel-transformer-0.64.0.tgz#eafef756972f20efdc51bd5361d55f8598355623" - integrity sha512-K1sHO3ODBFCr7uEiCQ4RvVr+cQg0EHQF8ChVPnecGh/WDD8udrTq9ECwB0dRfMjAvlsHtRUlJm6ZSI8UPgum2w== - dependencies: - "@babel/core" "^7.0.0" - babel-preset-fbjs "^3.3.0" - metro-babel-transformer "0.64.0" - metro-react-native-babel-preset "0.64.0" - metro-source-map "0.64.0" +metro-react-native-babel-transformer@0.73.9: + version "0.73.9" + resolved "https://registry.yarnpkg.com/metro-react-native-babel-transformer/-/metro-react-native-babel-transformer-0.73.9.tgz#4f4f0cfa5119bab8b53e722fabaf90687d0cbff0" + integrity sha512-DSdrEHuQ22ixY7DyipyKkIcqhOJrt5s6h6X7BYJCP9AMUfXOwLe2biY3BcgJz5GOXv8/Akry4vTCvQscVS1otQ== + dependencies: + "@babel/core" "^7.20.0" + babel-preset-fbjs "^3.4.0" + hermes-parser "0.8.0" + metro-babel-transformer "0.73.9" + metro-react-native-babel-preset "0.73.9" + metro-source-map "0.73.9" nullthrows "^1.1.1" -metro-resolver@0.64.0, metro-resolver@^0.64.0: - version "0.64.0" - resolved "https://registry.yarnpkg.com/metro-resolver/-/metro-resolver-0.64.0.tgz#21126b44f31346ac2ce0b06b77ef65e8c9e2294a" - integrity sha512-cJ26Id8Zf+HmS/1vFwu71K3u7ep/+HeXXAJIeVDYf+niE7AWB9FijyMtAlQgbD8elWqv1leJCnQ/xHRFBfGKYA== +metro-resolver@0.73.9: + version "0.73.9" + resolved "https://registry.yarnpkg.com/metro-resolver/-/metro-resolver-0.73.9.tgz#f3cf77e6c7606a34aa81bad40edb856aad671cf3" + integrity sha512-Ej3wAPOeNRPDnJmkK0zk7vJ33iU07n+oPhpcf5L0NFkWneMmSM2bflMPibI86UjzZGmRfn0AhGhs8yGeBwQ/Xg== dependencies: absolute-path "^0.0.0" -metro-runtime@0.64.0, metro-runtime@^0.64.0: - version "0.64.0" - resolved "https://registry.yarnpkg.com/metro-runtime/-/metro-runtime-0.64.0.tgz#cdaa1121d91041bf6345f2a69eb7c2fb289eff7b" - integrity sha512-m7XbWOaIOeFX7YcxUhmnOi6Pg8EaeL89xyZ+quZyZVF1aNoTr4w8FfbKxvijpjsytKHIZtd+43m2Wt5JrqyQmQ== +metro-runtime@0.73.9: + version "0.73.9" + resolved "https://registry.yarnpkg.com/metro-runtime/-/metro-runtime-0.73.9.tgz#0b24c0b066b8629ee855a6e5035b65061fef60d5" + integrity sha512-d5Hs83FpKB9r8q8Vb95+fa6ESpwysmPr4lL1I2rM2qXAFiO7OAPT9Bc23WmXgidkBtD0uUFdB2lG+H1ATz8rZg== + dependencies: + "@babel/runtime" "^7.0.0" + react-refresh "^0.4.0" -metro-source-map@0.64.0: - version "0.64.0" - resolved "https://registry.yarnpkg.com/metro-source-map/-/metro-source-map-0.64.0.tgz#4310e17c3d4539c6369688022494ad66fa4d39a1" - integrity sha512-OCG2rtcp5cLEGYvAbfkl6mEc0J2FPRP4/UCEly+juBk7hawS9bCBMBfhJm/HIsvY1frk6nT2Vsl1O8YBbwyx2g== +metro-source-map@0.73.9: + version "0.73.9" + resolved "https://registry.yarnpkg.com/metro-source-map/-/metro-source-map-0.73.9.tgz#89ca41f6346aeb12f7f23496fa363e520adafebe" + integrity sha512-l4VZKzdqafipriETYR6lsrwtavCF1+CMhCOY9XbyWeTrpGSNgJQgdeJpttzEZTHQQTLR0csQo0nD1ef3zEP6IQ== dependencies: - "@babel/traverse" "^7.0.0" - "@babel/types" "^7.0.0" + "@babel/traverse" "^7.20.0" + "@babel/types" "^7.20.0" invariant "^2.2.4" - metro-symbolicate "0.64.0" + metro-symbolicate "0.73.9" nullthrows "^1.1.1" - ob1 "0.64.0" + ob1 "0.73.9" source-map "^0.5.6" vlq "^1.0.0" -metro-symbolicate@0.64.0: - version "0.64.0" - resolved "https://registry.yarnpkg.com/metro-symbolicate/-/metro-symbolicate-0.64.0.tgz#405c21438ab553c29f6841da52ca76ee87bb06ac" - integrity sha512-qIi+YRrDWnLVmydj6gwidYLPaBsakZRibGWSspuXgHAxOI3UuLwlo4dpQ73Et0gyHjI7ZvRMRY8JPiOntf9AQQ== +metro-symbolicate@0.73.9: + version "0.73.9" + resolved "https://registry.yarnpkg.com/metro-symbolicate/-/metro-symbolicate-0.73.9.tgz#cb452299a36e5b86b2826e7426d51221635c48bf" + integrity sha512-4TUOwxRHHqbEHxRqRJ3wZY5TA8xq7AHMtXrXcjegMH9FscgYztsrIG9aNBUBS+VLB6g1qc6BYbfIgoAnLjCDyw== dependencies: invariant "^2.2.4" - metro-source-map "0.64.0" + metro-source-map "0.73.9" nullthrows "^1.1.1" source-map "^0.5.6" through2 "^2.0.1" vlq "^1.0.0" -metro-transform-plugins@0.64.0: - version "0.64.0" - resolved "https://registry.yarnpkg.com/metro-transform-plugins/-/metro-transform-plugins-0.64.0.tgz#41d3dce0f2966bbd79fea1ecff61bcc8a00e4665" - integrity sha512-iTIRBD/wBI98plfxj8jAoNUUXfXLNlyvcjPtshhpGvdwu9pzQilGfnDnOaaK+vbITcOk9w5oQectXyJwAqTr1A== +metro-transform-plugins@0.73.9: + version "0.73.9" + resolved "https://registry.yarnpkg.com/metro-transform-plugins/-/metro-transform-plugins-0.73.9.tgz#9fffbe1b24269e3d114286fa681abc570072d9b8" + integrity sha512-r9NeiqMngmooX2VOKLJVQrMuV7PAydbqst5bFhdVBPcFpZkxxqyzjzo+kzrszGy2UpSQBZr2P1L6OMjLHwQwfQ== dependencies: - "@babel/core" "^7.0.0" - "@babel/generator" "^7.5.0" + "@babel/core" "^7.20.0" + "@babel/generator" "^7.20.0" "@babel/template" "^7.0.0" - "@babel/traverse" "^7.0.0" + "@babel/traverse" "^7.20.0" nullthrows "^1.1.1" -metro-transform-worker@0.64.0: - version "0.64.0" - resolved "https://registry.yarnpkg.com/metro-transform-worker/-/metro-transform-worker-0.64.0.tgz#f94429b2c42b13cb1c93be4c2e25e97f2d27ca60" - integrity sha512-wegRtK8GyLF6IPZRBJp+zsORgA4iX0h1DRpknyAMDCtSbJ4VU2xV/AojteOgAsDvY3ucAGsvfuZLNDJHUdUNHQ== - dependencies: - "@babel/core" "^7.0.0" - "@babel/generator" "^7.5.0" - "@babel/parser" "^7.0.0" - "@babel/types" "^7.0.0" - babel-preset-fbjs "^3.3.0" - metro "0.64.0" - metro-babel-transformer "0.64.0" - metro-cache "0.64.0" - metro-cache-key "0.64.0" - metro-hermes-compiler "0.64.0" - metro-source-map "0.64.0" - metro-transform-plugins "0.64.0" +metro-transform-worker@0.73.9: + version "0.73.9" + resolved "https://registry.yarnpkg.com/metro-transform-worker/-/metro-transform-worker-0.73.9.tgz#30384cef2d5e35a4abe91b15bf1a8344f5720441" + integrity sha512-Rq4b489sIaTUENA+WCvtu9yvlT/C6zFMWhU4sq+97W29Zj0mPBjdk+qGT5n1ZBgtBIJzZWt1KxeYuc17f4aYtQ== + dependencies: + "@babel/core" "^7.20.0" + "@babel/generator" "^7.20.0" + "@babel/parser" "^7.20.0" + "@babel/types" "^7.20.0" + babel-preset-fbjs "^3.4.0" + metro "0.73.9" + metro-babel-transformer "0.73.9" + metro-cache "0.73.9" + metro-cache-key "0.73.9" + metro-hermes-compiler "0.73.9" + metro-source-map "0.73.9" + metro-transform-plugins "0.73.9" nullthrows "^1.1.1" -metro@0.64.0, metro@^0.64.0: - version "0.64.0" - resolved "https://registry.yarnpkg.com/metro/-/metro-0.64.0.tgz#0091a856cfbcc94dd576da563eee466e96186195" - integrity sha512-G2OC08Rzfs0kqnSEuKo2yZxR+/eNUpA93Ru45c60uN0Dw3HPrDi+ZBipgFftC6iLE0l+6hu8roFFIofotWxybw== +metro@0.73.9: + version "0.73.9" + resolved "https://registry.yarnpkg.com/metro/-/metro-0.73.9.tgz#150e69a6735fab0bcb4f6ee97fd1efc65b3ec36f" + integrity sha512-BlYbPmTF60hpetyNdKhdvi57dSqutb+/oK0u3ni4emIh78PiI0axGo7RfdsZ/mn3saASXc94tDbpC5yn7+NpEg== dependencies: "@babel/code-frame" "^7.0.0" - "@babel/core" "^7.0.0" - "@babel/generator" "^7.5.0" - "@babel/parser" "^7.0.0" + "@babel/core" "^7.20.0" + "@babel/generator" "^7.20.0" + "@babel/parser" "^7.20.0" "@babel/template" "^7.0.0" - "@babel/traverse" "^7.0.0" - "@babel/types" "^7.0.0" + "@babel/traverse" "^7.20.0" + "@babel/types" "^7.20.0" absolute-path "^0.0.0" accepts "^1.3.7" - async "^2.4.0" + async "^3.2.2" chalk "^4.0.0" ci-info "^2.0.0" connect "^3.6.5" debug "^2.2.0" denodeify "^1.2.1" error-stack-parser "^2.0.6" - fs-extra "^1.0.0" - graceful-fs "^4.1.3" + graceful-fs "^4.2.4" + hermes-parser "0.8.0" image-size "^0.6.0" invariant "^2.2.4" - jest-haste-map "^26.5.2" - jest-worker "^26.0.0" + jest-worker "^27.2.0" lodash.throttle "^4.1.1" - metro-babel-register "0.64.0" - metro-babel-transformer "0.64.0" - metro-cache "0.64.0" - metro-cache-key "0.64.0" - metro-config "0.64.0" - metro-core "0.64.0" - metro-hermes-compiler "0.64.0" - metro-inspector-proxy "0.64.0" - metro-minify-uglify "0.64.0" - metro-react-native-babel-preset "0.64.0" - metro-resolver "0.64.0" - metro-runtime "0.64.0" - metro-source-map "0.64.0" - metro-symbolicate "0.64.0" - metro-transform-plugins "0.64.0" - metro-transform-worker "0.64.0" + metro-babel-transformer "0.73.9" + metro-cache "0.73.9" + metro-cache-key "0.73.9" + metro-config "0.73.9" + metro-core "0.73.9" + metro-file-map "0.73.9" + metro-hermes-compiler "0.73.9" + metro-inspector-proxy "0.73.9" + metro-minify-terser "0.73.9" + metro-minify-uglify "0.73.9" + metro-react-native-babel-preset "0.73.9" + metro-resolver "0.73.9" + metro-runtime "0.73.9" + metro-source-map "0.73.9" + metro-symbolicate "0.73.9" + metro-transform-plugins "0.73.9" + metro-transform-worker "0.73.9" mime-types "^2.1.27" - mkdirp "^0.5.1" node-fetch "^2.2.0" nullthrows "^1.1.1" - rimraf "^2.5.4" + rimraf "^3.0.2" serialize-error "^2.1.0" source-map "^0.5.6" strip-ansi "^6.0.0" temp "0.8.3" throat "^5.0.0" - ws "^1.1.5" - yargs "^15.3.1" + ws "^7.5.1" + yargs "^17.5.1" -micromatch@^3.1.10, micromatch@^3.1.4: +micromatch@^3.1.10: version "3.1.10" resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-3.1.10.tgz#70859bc95c9840952f359a068a3fc49f9ecfac23" integrity sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg== @@ -7849,7 +8437,7 @@ micromatch@^3.1.10, micromatch@^3.1.4: snapdragon "^0.8.1" to-regex "^3.0.2" -micromatch@^4.0.2, micromatch@^4.0.4: +micromatch@^4.0.4: version "4.0.5" resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.5.tgz#bc8999a7cbbf77cdc89f132f6e467051b49090c6" integrity sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA== @@ -7862,7 +8450,7 @@ mime-db@1.52.0, "mime-db@>= 1.43.0 < 2": resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70" integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== -mime-types@^2.1.12, mime-types@^2.1.27, mime-types@~2.1.19, mime-types@~2.1.24, mime-types@~2.1.34: +mime-types@^2.1.12, mime-types@^2.1.27, mime-types@~2.1.24, mime-types@~2.1.34: version "2.1.35" resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a" integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== @@ -7879,11 +8467,6 @@ mime@^2.4.1: resolved "https://registry.yarnpkg.com/mime/-/mime-2.6.0.tgz#a2a682a95cd4d0cb1d6257e28f83da7e35800367" integrity sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg== -mimic-fn@^1.0.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-1.2.0.tgz#820c86a39334640e99516928bd03fca88057d022" - integrity sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ== - mimic-fn@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" @@ -7894,13 +8477,58 @@ min-indent@^1.0.0: resolved "https://registry.yarnpkg.com/min-indent/-/min-indent-1.0.1.tgz#a63f681673b30571fbe8bc25686ae746eefa9869" integrity sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg== -minimatch@^3.0.2, minimatch@^3.0.4, minimatch@^3.1.1, minimatch@^3.1.2: +minimalistic-assert@^1.0.0, minimalistic-assert@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz#2e194de044626d4a10e7f7fbc00ce73e83e4d5c7" + integrity sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A== + +minimalistic-crypto-utils@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz#f6c00c1c0b082246e5c4d99dfb8c7c083b2b582a" + integrity sha512-JIYlbt6g8i5jKfJ3xz7rF0LXmv2TkDxBLUkiBeZ7bAx4GnnNMr8xFpGnOxn6GhTEHx3SjRrZEoU+j04prX1ktg== + +minimatch@3.0.5: + version "3.0.5" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.5.tgz#4da8f1290ee0f0f8e83d60ca69f8f134068604a3" + integrity sha512-tUpxzX0VAzJHjLu0xUfFv1gwVp9ba3IOuRAVH2EGuRW8a5emA2FlACLqiT/lDVtS1W+TGNwqz3sWaNyLgDJWuw== + dependencies: + brace-expansion "^1.1.7" + +minimatch@^3.0.2, minimatch@^3.0.4, minimatch@^3.0.5, minimatch@^3.1.1, minimatch@^3.1.2: version "3.1.2" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== dependencies: brace-expansion "^1.1.7" +minimatch@^5.0.1: + version "5.1.6" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-5.1.6.tgz#1cfcb8cf5522ea69952cd2af95ae09477f122a96" + integrity sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g== + dependencies: + brace-expansion "^2.0.1" + +minimatch@^6.1.6: + version "6.2.0" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-6.2.0.tgz#2b70fd13294178c69c04dfc05aebdb97a4e79e42" + integrity sha512-sauLxniAmvnhhRjFwPNnJKaPFYyddAgbYdeUpHULtCT/GhzdCx/MDNy+Y40lBxTQUrMzDE8e0S43Z5uqfO0REg== + dependencies: + brace-expansion "^2.0.1" + +minimatch@^7.4.2, minimatch@^7.4.6: + version "7.4.6" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-7.4.6.tgz#845d6f254d8f4a5e4fd6baf44d5f10c8448365fb" + integrity sha512-sBz8G/YjVniEz6lKPNpKxXwazJe4c19fEfV2GDMX6AjFz+MX9uDWIZW8XreVhkFW3fkIdTv/gxWr/Kks5FFAVw== + dependencies: + brace-expansion "^2.0.1" + +minimatch@^8.0.2: + version "8.0.4" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-8.0.4.tgz#847c1b25c014d4e9a7f68aaf63dedd668a626229" + integrity sha512-W0Wvr9HyFXZRGIDgCicunpQ299OKXs9RgZfaukz4qAW/pJhcpUfupc9c+OObPOFueNy8VSrZgEmDtk6Kh4WzDA== + dependencies: + brace-expansion "^2.0.1" + minimist-options@4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/minimist-options/-/minimist-options-4.1.0.tgz#c0655713c53a8a2ebd77ffa247d342c40f010619" @@ -7910,10 +8538,10 @@ minimist-options@4.1.0: is-plain-obj "^1.1.0" kind-of "^6.0.3" -minimist@^1.1.1, minimist@^1.2.0, minimist@^1.2.5, minimist@^1.2.6: - version "1.2.6" - resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.6.tgz#8637a5b759ea0d6e98702cfb3a9283323c93af44" - integrity sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q== +minimist@^1.2.0, minimist@^1.2.5, minimist@^1.2.6: + version "1.2.8" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c" + integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA== minipass-collect@^1.0.2: version "1.0.2" @@ -7922,7 +8550,7 @@ minipass-collect@^1.0.2: dependencies: minipass "^3.0.0" -minipass-fetch@^1.3.0, minipass-fetch@^1.3.2: +minipass-fetch@^1.3.2: version "1.4.1" resolved "https://registry.yarnpkg.com/minipass-fetch/-/minipass-fetch-1.4.1.tgz#d75e0091daac1b0ffd7e9d41629faff7d0c1f1b6" integrity sha512-CGH1eblLq26Y15+Azk7ey4xh0J/XfJfrCox5LDJiKqI2Q2iwOLOKrlmIaODiSQS8d18jalF6y2K2ePUm0CmShw== @@ -7933,6 +8561,28 @@ minipass-fetch@^1.3.0, minipass-fetch@^1.3.2: optionalDependencies: encoding "^0.1.12" +minipass-fetch@^2.0.3: + version "2.1.2" + resolved "https://registry.yarnpkg.com/minipass-fetch/-/minipass-fetch-2.1.2.tgz#95560b50c472d81a3bc76f20ede80eaed76d8add" + integrity sha512-LT49Zi2/WMROHYoqGgdlQIZh8mLPZmOrN2NdJjMXxYe4nkN6FUyuPuOAOedNJDrx0IRGg9+4guZewtp8hE6TxA== + dependencies: + minipass "^3.1.6" + minipass-sized "^1.0.3" + minizlib "^2.1.2" + optionalDependencies: + encoding "^0.1.13" + +minipass-fetch@^3.0.0: + version "3.0.2" + resolved "https://registry.yarnpkg.com/minipass-fetch/-/minipass-fetch-3.0.2.tgz#2f7275ae13f2fb0f2a469cee4f78250c25c80ab3" + integrity sha512-/ZpF1CQaWYqjbhfFgKNt3azxztEpc/JUPuMkqOgrnMQqcU8CbE409AUdJYTIWryl3PP5CBaTJZT71N49MXP/YA== + dependencies: + minipass "^4.0.0" + minipass-sized "^1.0.3" + minizlib "^2.1.2" + optionalDependencies: + encoding "^0.1.13" + minipass-flush@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/minipass-flush/-/minipass-flush-1.0.5.tgz#82e7135d7e89a50ffe64610a787953c4c4cbb373" @@ -7970,13 +8620,23 @@ 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: - version "3.3.4" - resolved "https://registry.yarnpkg.com/minipass/-/minipass-3.3.4.tgz#ca99f95dd77c43c7a76bf51e6d200025eee0ffae" - integrity sha512-I9WPbWHCGu8W+6k1ZiGpPu0GkoKBeorkfKNuAFBNS1HNFJvke82sxvI5bzcCNpWPorkOO5QQ+zomzzwRxejXiw== +minipass@^3.0.0, minipass@^3.1.0, minipass@^3.1.1, minipass@^3.1.3, minipass@^3.1.6: + version "3.3.6" + resolved "https://registry.yarnpkg.com/minipass/-/minipass-3.3.6.tgz#7bba384db3a1520d18c9c0e5251c3444e95dd94a" + integrity sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw== dependencies: yallist "^4.0.0" +minipass@^4.0.0, minipass@^4.2.4: + version "4.2.8" + resolved "https://registry.yarnpkg.com/minipass/-/minipass-4.2.8.tgz#f0010f64393ecfc1d1ccb5f582bcaf45f48e1a3a" + integrity sha512-fNzuVyifolSLFL4NzpF+wEF4qrgqaaKX0haXPQEdQ7NKAN+WecoKMHV09YcuL/DHxrUsYQOK3MiuDf7Ip2OXfQ== + +minipass@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/minipass/-/minipass-5.0.0.tgz#3e9788ffb90b694a5d0ec94479a45b5d8738133d" + integrity sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ== + minizlib@^1.3.3: version "1.3.3" resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-1.3.3.tgz#2290de96818a34c29551c8a8d301216bd65a861d" @@ -7984,7 +8644,7 @@ minizlib@^1.3.3: dependencies: minipass "^2.9.0" -minizlib@^2.0.0, minizlib@^2.1.1: +minizlib@^2.0.0, minizlib@^2.1.1, minizlib@^2.1.2: version "2.1.2" resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-2.1.2.tgz#e90d3466ba209b932451508a11ce3d3632145931" integrity sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg== @@ -8046,7 +8706,12 @@ msrcrypto@^1.5.6: resolved "https://registry.yarnpkg.com/msrcrypto/-/msrcrypto-1.5.8.tgz#be419be4945bf134d8af52e9d43be7fa261f4a1c" integrity sha512-ujZ0TRuozHKKm6eGbKHfXef7f+esIhEckmThVnz7RNyiOJd7a6MXj2JGBoL9cnPDW+JMG16MoTUh5X+XXjI66Q== -multimatch@^5.0.0: +multiformats@^9.4.2, multiformats@^9.6.5, multiformats@^9.9.0: + version "9.9.0" + resolved "https://registry.yarnpkg.com/multiformats/-/multiformats-9.9.0.tgz#c68354e7d21037a8f1f8833c8ccd68618e8f1d37" + integrity sha512-HoMUjhH9T8DDBNT+6xzkrd9ga/XiBI4xLr58LJACwK6G3HTOPeMz4nB4KJs33L2BelrIJa7P0VuNaVF3hMYfjg== + +multimatch@5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/multimatch/-/multimatch-5.0.0.tgz#932b800963cea7a31a033328fa1e0c3a1874dbe6" integrity sha512-ypMKuglUrZUD99Tk2bUQ+xNQj43lPEfAeX2o9cTteAmShXy2VHDJpuwu1o0xqoKCt9jLVAvwyFKdLTPXKAfJyA== @@ -8063,9 +8728,9 @@ mute-stream@0.0.8, mute-stream@~0.0.4: integrity sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA== nan@^2.11.1: - version "2.16.0" - resolved "https://registry.yarnpkg.com/nan/-/nan-2.16.0.tgz#664f43e45460fb98faf00edca0bb0d7b8dce7916" - integrity sha512-UdAqHyFngu7TfQKsCBgAA6pWDkT8MAO7d0jyOecVhN5354xbLqdn8mV9Tat9gepAupm0bt2DbeaSC8vS52MuFA== + version "2.17.0" + resolved "https://registry.yarnpkg.com/nan/-/nan-2.17.0.tgz#c0150a2368a182f033e9aa5195ec76ea41a199cb" + integrity sha512-2ZTgtl0nJsO0KQCjEpxcIr5D+Yv90plTitZt9JBfQvVJDS5seMl3FOvsh3+9CoYWXf/1l5OaZzzF6nDm4cagaQ== nanomatch@^1.2.9: version "1.2.13" @@ -8084,6 +8749,11 @@ nanomatch@^1.2.9: snapdragon "^0.8.1" to-regex "^3.0.1" +natural-compare-lite@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/natural-compare-lite/-/natural-compare-lite-1.4.0.tgz#17b09581988979fddafe0201e931ba933c96cbb4" + integrity sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g== + natural-compare@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" @@ -8098,7 +8768,7 @@ needle@^2.5.2: iconv-lite "^0.4.4" sax "^1.2.4" -negotiator@0.6.3, negotiator@^0.6.2: +negotiator@0.6.3, negotiator@^0.6.2, negotiator@^0.6.3: version "0.6.3" resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.3.tgz#58e323a72fedc0d6f9cd4d31fe49f51479590ccd" integrity sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg== @@ -8148,21 +8818,43 @@ neon-cli@0.8.2: validate-npm-package-license "^3.0.4" validate-npm-package-name "^3.0.0" +next-tick@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/next-tick/-/next-tick-1.1.0.tgz#1836ee30ad56d67ef281b22bd199f709449b35eb" + integrity sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ== + nice-try@^1.0.4: version "1.0.5" resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366" integrity sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ== -nocache@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/nocache/-/nocache-2.1.0.tgz#120c9ffec43b5729b1d5de88cd71aa75a0ba491f" - integrity sha512-0L9FvHG3nfnnmaEQPjT9xhfN4ISk0A8/2j4M37Np4mcDesJjHgEUfgPhdCyZuFI954tjokaIj/A3NdpFNdEh4Q== +nocache@^3.0.1: + version "3.0.4" + resolved "https://registry.yarnpkg.com/nocache/-/nocache-3.0.4.tgz#5b37a56ec6e09fc7d401dceaed2eab40c8bfdf79" + integrity sha512-WDD0bdg9mbq6F4mRxEYcPWwfA1vxd0mrvKOyxI7Xj/atfRHVeutzuWByG//jfm4uPzp0y4Kj051EORCBSQMycw== + +nock@^13.3.0: + version "13.3.0" + resolved "https://registry.yarnpkg.com/nock/-/nock-13.3.0.tgz#b13069c1a03f1ad63120f994b04bfd2556925768" + integrity sha512-HHqYQ6mBeiMc+N038w8LkMpDCRquCHWeNmN3v6645P3NhN2+qXOBqvPqo7Rt1VyCMzKhJ733wZqw5B7cQVFNPg== + dependencies: + debug "^4.1.0" + json-stringify-safe "^5.0.1" + lodash "^4.17.21" + propagate "^2.0.0" -node-addon-api@^3.0.0: +node-addon-api@^3.0.0, node-addon-api@^3.2.1: version "3.2.1" resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-3.2.1.tgz#81325e0a2117789c0128dab65e7e38f07ceba161" integrity sha512-mmcei9JghVNDYydghQmeDX8KoAm0FAiYyIcUt/N4nhyAipB17pllZQDOJD2fotxABnt4Mdz+dKTO7eftLg4d0A== +node-cache@5.1.2, node-cache@^5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/node-cache/-/node-cache-5.1.2.tgz#f264dc2ccad0a780e76253a694e9fd0ed19c398d" + integrity sha512-t1QzWwnk4sjLWaQAS8CHgOJ+RAfmHpxFWmc36IWTiWHQfs0w5JDMBS1b1ZxQteo0vVVuWJvIUKHDkkeK7vIGCg== + dependencies: + clone "2.x" + node-dir@^0.1.17: version "0.1.17" resolved "https://registry.yarnpkg.com/node-dir/-/node-dir-0.1.17.tgz#5f5665d93351335caabef8f1c554516cf5f1e4e5" @@ -8170,7 +8862,7 @@ node-dir@^0.1.17: dependencies: minimatch "^3.0.2" -node-fetch@2.6.7, node-fetch@^2.0, node-fetch@^2.2.0, node-fetch@^2.6.0, node-fetch@^2.6.1, node-fetch@^2.6.7: +node-fetch@2.6.7: version "2.6.7" resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.7.tgz#24de9fba827e3b4ae44dc8b20256a379160052ad" integrity sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ== @@ -8185,43 +8877,17 @@ node-fetch@3.0.0-beta.9: data-uri-to-buffer "^3.0.1" fetch-blob "^2.1.1" -node-gyp-build@^4.2.1: - version "4.5.0" - resolved "https://registry.yarnpkg.com/node-gyp-build/-/node-gyp-build-4.5.0.tgz#7a64eefa0b21112f89f58379da128ac177f20e40" - integrity sha512-2iGbaQBV+ITgCz76ZEjmhUKAKVf7xfY1sRl4UiKQspfZMH2h06SyhNsnSVy50cwkFQDGLyif6m/6uFXHkOZ6rg== - -node-gyp@^5.0.2: - version "5.1.1" - resolved "https://registry.yarnpkg.com/node-gyp/-/node-gyp-5.1.1.tgz#eb915f7b631c937d282e33aed44cb7a025f62a3e" - integrity sha512-WH0WKGi+a4i4DUt2mHnvocex/xPLp9pYt5R6M2JdFB7pJ7Z34hveZ4nDTGTiLXCkitA9T8HFZjhinBCiVHYcWw== +node-fetch@^2.2.0, node-fetch@^2.6.0, node-fetch@^2.6.1, node-fetch@^2.6.7: + version "2.6.9" + resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.9.tgz#7c7f744b5cc6eb5fd404e0c7a9fec630a55657e6" + integrity sha512-DJm/CJkZkRjKKj4Zi4BsKVZh3ValV5IR5s7LVZnW+6YMh0W1BfNA8XSs6DLMGYlId5F3KnA70uu2qepcR08Qqg== dependencies: - env-paths "^2.2.0" - glob "^7.1.4" - graceful-fs "^4.2.2" - mkdirp "^0.5.1" - nopt "^4.0.1" - npmlog "^4.1.2" - request "^2.88.0" - rimraf "^2.6.3" - semver "^5.7.1" - tar "^4.4.12" - which "^1.3.1" + whatwg-url "^5.0.0" -node-gyp@^7.1.0: - version "7.1.2" - resolved "https://registry.yarnpkg.com/node-gyp/-/node-gyp-7.1.2.tgz#21a810aebb187120251c3bcec979af1587b188ae" - integrity sha512-CbpcIo7C3eMu3dL1c3d0xw449fHIGALIJsRP4DDPHpyiW8vcriNY7ubh9TE4zEKfSxscY7PjeFnshE7h75ynjQ== - dependencies: - env-paths "^2.2.0" - glob "^7.1.4" - graceful-fs "^4.2.3" - nopt "^5.0.0" - npmlog "^4.1.2" - request "^2.88.2" - rimraf "^3.0.2" - semver "^7.3.2" - tar "^6.0.2" - which "^2.0.2" +node-gyp-build@^4.2.1, node-gyp-build@^4.3.0: + version "4.6.0" + resolved "https://registry.yarnpkg.com/node-gyp-build/-/node-gyp-build-4.6.0.tgz#0c52e4cbf54bbd28b709820ef7b6a3c2d6209055" + integrity sha512-NTZVKn9IylLwUzaKjkas1e4u2DLNcV4rdYagA4PWdPwW87Bi7z+BznyKSRwS/761tV/lzCGXplWsiaMjLqP2zQ== node-gyp@^8.0.0: version "8.4.1" @@ -8239,6 +8905,22 @@ node-gyp@^8.0.0: tar "^6.1.2" which "^2.0.2" +node-gyp@^9.0.0: + version "9.3.1" + resolved "https://registry.yarnpkg.com/node-gyp/-/node-gyp-9.3.1.tgz#1e19f5f290afcc9c46973d68700cbd21a96192e4" + integrity sha512-4Q16ZCqq3g8awk6UplT7AuxQ35XN4R/yf/+wSAwcBUAjg7l58RTactWaP8fIDTi0FzI7YcVLujwExakZlfWkXg== + dependencies: + env-paths "^2.2.0" + glob "^7.1.4" + graceful-fs "^4.2.6" + make-fetch-happen "^10.0.3" + nopt "^6.0.0" + npmlog "^6.0.0" + rimraf "^3.0.2" + semver "^7.3.5" + tar "^6.1.2" + which "^2.0.2" + node-int64@^0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/node-int64/-/node-int64-0.4.0.tgz#87a9065cdb355d3182d8f94ce11188b825c68a3b" @@ -8260,17 +8942,17 @@ node-pre-gyp@0.17.0: semver "^5.7.1" tar "^4.4.13" -node-releases@^2.0.6: - version "2.0.6" - resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.6.tgz#8a7088c63a55e493845683ebf3c828d8c51c5503" - integrity sha512-PiVXnNuFm5+iYkLBNeq5211hvO38y63T0i2KKh2KnUs3RpzJ+JtODFjkD8yjLwnDkTYF1eKXheUwdssR+NRZdg== +node-releases@^2.0.8: + version "2.0.10" + resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.10.tgz#c311ebae3b6a148c89b1813fd7c4d3c024ef537f" + integrity sha512-5GFldHPXVG/YZmFzJvKK2zDSzPKhEp0+ZR5SVaoSag9fsL5YgHbUHDfnG5494ISANDcK4KwPXAx2xqVEydmd7w== node-stream-zip@^1.9.1: version "1.15.0" resolved "https://registry.yarnpkg.com/node-stream-zip/-/node-stream-zip-1.15.0.tgz#158adb88ed8004c6c49a396b50a6a5de3bca33ea" integrity sha512-LN4fydt9TqhZhThkZIVQnF9cwjU3qmUH9h78Mx/K7d3VvfRqqwthLwJEUOEL0QPZ0XQmNN7be5Ggit5+4dq3Bw== -nopt@^4.0.1, nopt@^4.0.3: +nopt@^4.0.3: version "4.0.3" resolved "https://registry.yarnpkg.com/nopt/-/nopt-4.0.3.tgz#a375cad9d02fd921278d954c2254d5aa57e15e48" integrity sha512-CvaGwVMztSMJLOeXPrez7fyfObdZqNUK1cPAEzLHrTybIua9pMdmmPR5YwtfNftIOMv3DPUhFaxsZMNTQO20Kg== @@ -8285,7 +8967,21 @@ nopt@^5.0.0: dependencies: abbrev "1" -normalize-package-data@^2.0.0, normalize-package-data@^2.3.2, normalize-package-data@^2.5.0: +nopt@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/nopt/-/nopt-6.0.0.tgz#245801d8ebf409c6df22ab9d95b65e1309cdb16d" + integrity sha512-ZwLpbTgdhuZUnZzjd7nb1ZV+4DoiC6/sfiVKok72ym/4Tlf+DFdlHYmT2JPmcNNWV6Pi3SDf1kT+A4r9RTuT9g== + dependencies: + abbrev "^1.0.0" + +nopt@^7.0.0: + version "7.1.0" + resolved "https://registry.yarnpkg.com/nopt/-/nopt-7.1.0.tgz#91f6a3366182176e72ecab93a09c19b63b485f28" + integrity sha512-ZFPLe9Iu0tnx7oWhFxAo4s7QTn8+NNDDxYNaKLjE7Dp0tbakQ3M1QhQzsnzXHQBTUO3K9BmwaxnyO8Ayn2I95Q== + dependencies: + abbrev "^2.0.0" + +normalize-package-data@^2.3.2, normalize-package-data@^2.5.0: version "2.5.0" resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.5.0.tgz#e66db1838b200c1dfc233225d12cb36520e234a8" integrity sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA== @@ -8295,7 +8991,7 @@ normalize-package-data@^2.0.0, normalize-package-data@^2.3.2, normalize-package- semver "2 || 3 || 4 || 5" validate-npm-package-license "^3.0.1" -normalize-package-data@^3.0.0, normalize-package-data@^3.0.2: +normalize-package-data@^3.0.0: version "3.0.3" resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-3.0.3.tgz#dbcc3e2da59509a0983422884cd172eefdfa525e" integrity sha512-p2W1sgqij3zMMyRC067Dg16bfzVH+w7hyegmpIvZ4JNjqtGOVAIvLmjBx3yP7YTe9vKJgkoNOPjwQGogDoMXFA== @@ -8305,65 +9001,120 @@ normalize-package-data@^3.0.0, normalize-package-data@^3.0.2: semver "^7.3.4" validate-npm-package-license "^3.0.1" -normalize-path@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-2.1.1.tgz#1ab28b556e198363a8c1a6f7e6fa20137fe6aed9" - integrity sha512-3pKJwH184Xo/lnH6oyP1q2pMd7HcypqqmRs91/6/i2CGtWwIKGCkOOMTm/zXbgTEWHw1uNpNi/igc3ePOYHb6w== +normalize-package-data@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-4.0.1.tgz#b46b24e0616d06cadf9d5718b29b6d445a82a62c" + integrity sha512-EBk5QKKuocMJhB3BILuKhmaPjI8vNRSpIfO9woLC6NyHVkKKdVEdAO1mrT0ZfxNR1lKwCcTkuZfmGIFdizZ8Pg== + dependencies: + hosted-git-info "^5.0.0" + is-core-module "^2.8.1" + semver "^7.3.5" + validate-npm-package-license "^3.0.4" + +normalize-package-data@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-5.0.0.tgz#abcb8d7e724c40d88462b84982f7cbf6859b4588" + integrity sha512-h9iPVIfrVZ9wVYQnxFgtw1ugSvGEMOlyPWWtm8BMJhnwyEL/FLbYbTY3V3PpjI/BUK67n9PEWDu6eHzu1fB15Q== dependencies: - remove-trailing-separator "^1.0.1" + hosted-git-info "^6.0.0" + is-core-module "^2.8.1" + semver "^7.3.5" + validate-npm-package-license "^3.0.4" normalize-path@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== -normalize-url@^6.1.0: - version "6.1.0" - resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-6.1.0.tgz#40d0885b535deffe3f3147bec877d05fe4c5668a" - integrity sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A== - -npm-bundled@^1.0.1, npm-bundled@^1.1.1: +npm-bundled@^1.0.1, npm-bundled@^1.1.1, npm-bundled@^1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/npm-bundled/-/npm-bundled-1.1.2.tgz#944c78789bd739035b70baa2ca5cc32b8d860bc1" integrity sha512-x5DHup0SuyQcmL3s7Rx/YQ8sbw/Hzg0rj48eN0dV7hf5cmQq5PXIeioroH3raV1QC1yh3uTYuMThvEQF3iKgGQ== dependencies: npm-normalize-package-bin "^1.0.1" -npm-install-checks@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/npm-install-checks/-/npm-install-checks-4.0.0.tgz#a37facc763a2fde0497ef2c6d0ac7c3fbe00d7b4" - integrity sha512-09OmyDkNLYwqKPOnbI8exiOZU2GVVmQp7tgez2BPi5OZC8M82elDAps7sxC4l//uSUtotWqoEIDwjRvWH4qz8w== +npm-bundled@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/npm-bundled/-/npm-bundled-2.0.1.tgz#94113f7eb342cd7a67de1e789f896b04d2c600f4" + integrity sha512-gZLxXdjEzE/+mOstGDqR6b0EkhJ+kM6fxM6vUuckuctuVPh80Q6pw/rSZj9s4Gex9GxWtIicO1pc8DB9KZWudw== + dependencies: + npm-normalize-package-bin "^2.0.0" + +npm-bundled@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/npm-bundled/-/npm-bundled-3.0.0.tgz#7e8e2f8bb26b794265028491be60321a25a39db7" + integrity sha512-Vq0eyEQy+elFpzsKjMss9kxqb9tG3YHg4dsyWuUENuzvSUWe1TCnW/vV9FkhvBk/brEDoDiVd+M1Btosa6ImdQ== + dependencies: + npm-normalize-package-bin "^3.0.0" + +npm-install-checks@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/npm-install-checks/-/npm-install-checks-5.0.0.tgz#5ff27d209a4e3542b8ac6b0c1db6063506248234" + integrity sha512-65lUsMI8ztHCxFz5ckCEC44DRvEGdZX5usQFriauxHEwt7upv1FKaQEmAtU0YnOAdwuNWCmk64xYiQABNrEyLA== dependencies: semver "^7.1.1" -npm-lifecycle@^3.1.5: - version "3.1.5" - resolved "https://registry.yarnpkg.com/npm-lifecycle/-/npm-lifecycle-3.1.5.tgz#9882d3642b8c82c815782a12e6a1bfeed0026309" - integrity sha512-lDLVkjfZmvmfvpvBzA4vzee9cn+Me4orq0QF8glbswJVEbIcSNWib7qGOffolysc3teCqbbPZZkzbr3GQZTL1g== +npm-install-checks@^6.0.0: + version "6.1.1" + resolved "https://registry.yarnpkg.com/npm-install-checks/-/npm-install-checks-6.1.1.tgz#b459b621634d06546664207fde16810815808db1" + integrity sha512-dH3GmQL4vsPtld59cOn8uY0iOqRmqKvV+DLGwNXV/Q7MDgD2QfOADWd/mFXcIE5LVhYYGjA3baz6W9JneqnuCw== dependencies: - byline "^5.0.0" - graceful-fs "^4.1.15" - node-gyp "^5.0.2" - resolve-from "^4.0.0" - slide "^1.1.6" - uid-number "0.0.6" - umask "^1.1.0" - which "^1.3.1" + semver "^7.1.1" -npm-normalize-package-bin@^1.0.0, npm-normalize-package-bin@^1.0.1: +npm-normalize-package-bin@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/npm-normalize-package-bin/-/npm-normalize-package-bin-1.0.1.tgz#6e79a41f23fd235c0623218228da7d9c23b8f6e2" integrity sha512-EPfafl6JL5/rU+ot6P3gRSCpPDW5VmIzX959Ob1+ySFUuuYHWHekXpwdUZcKP5C+DS4GEtdJluwBjnsNDl+fSA== -npm-package-arg@^8.0.0, npm-package-arg@^8.0.1, npm-package-arg@^8.1.0, npm-package-arg@^8.1.2, npm-package-arg@^8.1.5: - version "8.1.5" - resolved "https://registry.yarnpkg.com/npm-package-arg/-/npm-package-arg-8.1.5.tgz#3369b2d5fe8fdc674baa7f1786514ddc15466e44" - integrity sha512-LhgZrg0n0VgvzVdSm1oiZworPbTxYHUJCgtsJW8mGvlDpxTM1vSJc3m5QZeUkhAHIzbz3VCHd/R4osi1L1Tg/Q== +npm-normalize-package-bin@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/npm-normalize-package-bin/-/npm-normalize-package-bin-2.0.0.tgz#9447a1adaaf89d8ad0abe24c6c84ad614a675fff" + integrity sha512-awzfKUO7v0FscrSpRoogyNm0sajikhBWpU0QMrW09AMi9n1PoKU6WaIqUzuJSQnpciZZmJ/jMZ2Egfmb/9LiWQ== + +npm-normalize-package-bin@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/npm-normalize-package-bin/-/npm-normalize-package-bin-3.0.0.tgz#6097436adb4ef09e2628b59a7882576fe53ce485" + integrity sha512-g+DPQSkusnk7HYXr75NtzkIP4+N81i3RPsGFidF3DzHd9MT9wWngmqoeg/fnHFz5MNdtG4w03s+QnhewSLTT2Q== + +npm-package-arg@8.1.1: + version "8.1.1" + resolved "https://registry.yarnpkg.com/npm-package-arg/-/npm-package-arg-8.1.1.tgz#00ebf16ac395c63318e67ce66780a06db6df1b04" + integrity sha512-CsP95FhWQDwNqiYS+Q0mZ7FAEDytDZAkNxQqea6IaAFJTAY9Lhhqyl0irU/6PMc7BGfUmnsbHcqxJD7XuVM/rg== dependencies: - hosted-git-info "^4.0.1" - semver "^7.3.4" + hosted-git-info "^3.0.6" + semver "^7.0.0" validate-npm-package-name "^3.0.0" +npm-package-arg@^10.0.0, npm-package-arg@^10.1.0: + version "10.1.0" + resolved "https://registry.yarnpkg.com/npm-package-arg/-/npm-package-arg-10.1.0.tgz#827d1260a683806685d17193073cc152d3c7e9b1" + integrity sha512-uFyyCEmgBfZTtrKk/5xDfHp6+MdrqGotX/VoOyEEl3mBwiEE5FlBaePanazJSVMPT7vKepcjYBY2ztg9A3yPIA== + dependencies: + hosted-git-info "^6.0.0" + proc-log "^3.0.0" + semver "^7.3.5" + validate-npm-package-name "^5.0.0" + +npm-package-arg@^9.0.0, npm-package-arg@^9.0.1: + version "9.1.2" + resolved "https://registry.yarnpkg.com/npm-package-arg/-/npm-package-arg-9.1.2.tgz#fc8acecb00235f42270dda446f36926ddd9ac2bc" + integrity sha512-pzd9rLEx4TfNJkovvlBSLGhq31gGu2QDexFPWT19yCDh0JgnRhlBLNo5759N0AJmBk+kQ9Y/hXoLnlgFD+ukmg== + dependencies: + hosted-git-info "^5.0.0" + proc-log "^2.0.1" + semver "^7.3.5" + validate-npm-package-name "^4.0.0" + +npm-packlist@5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/npm-packlist/-/npm-packlist-5.1.1.tgz#79bcaf22a26b6c30aa4dd66b976d69cc286800e0" + integrity sha512-UfpSvQ5YKwctmodvPPkK6Fwk603aoVsf8AEbmVKAEECrfvL8SSe1A2YIwrJ6xmTHAITKPwwZsWo7WwEbNk0kxw== + dependencies: + glob "^8.0.1" + ignore-walk "^5.0.1" + npm-bundled "^1.1.2" + npm-normalize-package-bin "^1.0.1" + npm-packlist@^1.4.8: version "1.4.8" resolved "https://registry.yarnpkg.com/npm-packlist/-/npm-packlist-1.4.8.tgz#56ee6cc135b9f98ad3d51c1c95da22bbb9b2ef3e" @@ -8373,51 +9124,81 @@ npm-packlist@^1.4.8: npm-bundled "^1.0.1" npm-normalize-package-bin "^1.0.1" -npm-packlist@^2.1.4: - version "2.2.2" - resolved "https://registry.yarnpkg.com/npm-packlist/-/npm-packlist-2.2.2.tgz#076b97293fa620f632833186a7a8f65aaa6148c8" - integrity sha512-Jt01acDvJRhJGthnUJVF/w6gumWOZxO7IkpY/lsX9//zqQgnF7OJaxgQXcerd4uQOLu7W5bkb4mChL9mdfm+Zg== +npm-packlist@^5.1.0: + version "5.1.3" + resolved "https://registry.yarnpkg.com/npm-packlist/-/npm-packlist-5.1.3.tgz#69d253e6fd664b9058b85005905012e00e69274b" + integrity sha512-263/0NGrn32YFYi4J533qzrQ/krmmrWwhKkzwTuM4f/07ug51odoaNjUexxO4vxlzURHcmYMH1QjvHjsNDKLVg== dependencies: - glob "^7.1.6" - ignore-walk "^3.0.3" - npm-bundled "^1.1.1" - npm-normalize-package-bin "^1.0.1" + glob "^8.0.1" + ignore-walk "^5.0.1" + npm-bundled "^2.0.0" + npm-normalize-package-bin "^2.0.0" -npm-pick-manifest@^6.0.0, npm-pick-manifest@^6.1.1: - version "6.1.1" - resolved "https://registry.yarnpkg.com/npm-pick-manifest/-/npm-pick-manifest-6.1.1.tgz#7b5484ca2c908565f43b7f27644f36bb816f5148" - integrity sha512-dBsdBtORT84S8V8UTad1WlUyKIY9iMsAmqxHbLdeEeBNMLQDlDWWra3wYUx9EBEIiG/YwAy0XyNHDd2goAsfuA== +npm-packlist@^7.0.0: + version "7.0.4" + resolved "https://registry.yarnpkg.com/npm-packlist/-/npm-packlist-7.0.4.tgz#033bf74110eb74daf2910dc75144411999c5ff32" + integrity sha512-d6RGEuRrNS5/N84iglPivjaJPxhDbZmlbTwTDX2IbcRHG5bZCdtysYMhwiPvcF4GisXHGn7xsxv+GQ7T/02M5Q== dependencies: - npm-install-checks "^4.0.0" - npm-normalize-package-bin "^1.0.1" - npm-package-arg "^8.1.2" - semver "^7.3.4" + ignore-walk "^6.0.0" -npm-registry-fetch@^11.0.0: - version "11.0.0" - resolved "https://registry.yarnpkg.com/npm-registry-fetch/-/npm-registry-fetch-11.0.0.tgz#68c1bb810c46542760d62a6a965f85a702d43a76" - integrity sha512-jmlgSxoDNuhAtxUIG6pVwwtz840i994dL14FoNVZisrmZW5kWd63IUTNv1m/hyRSGSqWjCUp/YZlS1BJyNp9XA== +npm-pick-manifest@^7.0.0: + version "7.0.2" + resolved "https://registry.yarnpkg.com/npm-pick-manifest/-/npm-pick-manifest-7.0.2.tgz#1d372b4e7ea7c6712316c0e99388a73ed3496e84" + integrity sha512-gk37SyRmlIjvTfcYl6RzDbSmS9Y4TOBXfsPnoYqTHARNgWbyDiCSMLUpmALDj4jjcTZpURiEfsSHJj9k7EV4Rw== + dependencies: + npm-install-checks "^5.0.0" + npm-normalize-package-bin "^2.0.0" + npm-package-arg "^9.0.0" + semver "^7.3.5" + +npm-pick-manifest@^8.0.0, npm-pick-manifest@^8.0.1: + version "8.0.1" + resolved "https://registry.yarnpkg.com/npm-pick-manifest/-/npm-pick-manifest-8.0.1.tgz#c6acd97d1ad4c5dbb80eac7b386b03ffeb289e5f" + integrity sha512-mRtvlBjTsJvfCCdmPtiu2bdlx8d/KXtF7yNXNWe7G0Z36qWA9Ny5zXsI2PfBZEv7SXgoxTmNaTzGSbbzDZChoA== dependencies: - make-fetch-happen "^9.0.1" - minipass "^3.1.3" - minipass-fetch "^1.3.0" - minipass-json-stream "^1.0.1" - minizlib "^2.0.0" - npm-package-arg "^8.0.0" + npm-install-checks "^6.0.0" + npm-normalize-package-bin "^3.0.0" + npm-package-arg "^10.0.0" + semver "^7.3.5" -npm-registry-fetch@^9.0.0: - version "9.0.0" - resolved "https://registry.yarnpkg.com/npm-registry-fetch/-/npm-registry-fetch-9.0.0.tgz#86f3feb4ce00313bc0b8f1f8f69daae6face1661" - integrity sha512-PuFYYtnQ8IyVl6ib9d3PepeehcUeHN9IO5N/iCRhyg9tStQcqGQBRVHmfmMWPDERU3KwZoHFvbJ4FPXPspvzbA== +npm-registry-fetch@14.0.3: + version "14.0.3" + resolved "https://registry.yarnpkg.com/npm-registry-fetch/-/npm-registry-fetch-14.0.3.tgz#8545e321c2b36d2c6fe6e009e77e9f0e527f547b" + integrity sha512-YaeRbVNpnWvsGOjX2wk5s85XJ7l1qQBGAp724h8e2CZFFhMSuw9enom7K1mWVUtvXO1uUSFIAPofQK0pPN0ZcA== dependencies: - "@npmcli/ci-detect" "^1.0.0" - lru-cache "^6.0.0" - make-fetch-happen "^8.0.9" - minipass "^3.1.3" - minipass-fetch "^1.3.0" + make-fetch-happen "^11.0.0" + minipass "^4.0.0" + minipass-fetch "^3.0.0" minipass-json-stream "^1.0.1" - minizlib "^2.0.0" - npm-package-arg "^8.0.0" + minizlib "^2.1.2" + npm-package-arg "^10.0.0" + proc-log "^3.0.0" + +npm-registry-fetch@^13.0.0, npm-registry-fetch@^13.0.1: + version "13.3.1" + resolved "https://registry.yarnpkg.com/npm-registry-fetch/-/npm-registry-fetch-13.3.1.tgz#bb078b5fa6c52774116ae501ba1af2a33166af7e" + integrity sha512-eukJPi++DKRTjSBRcDZSDDsGqRK3ehbxfFUcgaRd0Yp6kRwOwh2WVn0r+8rMB4nnuzvAk6rQVzl6K5CkYOmnvw== + dependencies: + make-fetch-happen "^10.0.6" + minipass "^3.1.6" + minipass-fetch "^2.0.3" + minipass-json-stream "^1.0.1" + minizlib "^2.1.2" + npm-package-arg "^9.0.1" + proc-log "^2.0.0" + +npm-registry-fetch@^14.0.0, npm-registry-fetch@^14.0.3: + version "14.0.4" + resolved "https://registry.yarnpkg.com/npm-registry-fetch/-/npm-registry-fetch-14.0.4.tgz#43dfa55ce7c0d0c545d625c7a916bab5b95f7038" + integrity sha512-pMS2DRkwg+M44ct65zrN/Cr9IHK1+n6weuefAo6Er4lc+/8YBCU0Czq04H3ZiSigluh7pb2rMM5JpgcytctB+Q== + dependencies: + make-fetch-happen "^11.0.0" + minipass "^4.0.0" + minipass-fetch "^3.0.0" + minipass-json-stream "^1.0.1" + minizlib "^2.1.2" + npm-package-arg "^10.0.0" + proc-log "^3.0.0" npm-run-path@^2.0.0: version "2.0.2" @@ -8433,6 +9214,16 @@ npm-run-path@^4.0.1: dependencies: path-key "^3.0.0" +npmlog@6.0.2, npmlog@^6.0.0, npmlog@^6.0.2: + version "6.0.2" + resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-6.0.2.tgz#c8166017a42f2dea92d6453168dd865186a70830" + integrity sha512-/vBvz5Jfr9dT/aFWd0FIRf+T/Q2WBsLENygUaFUqstqsycmZAP/t5BvFJTK0viFmSUxiUKTUplWy5vt+rvKIxg== + dependencies: + are-we-there-yet "^3.0.0" + console-control-strings "^1.1.0" + gauge "^4.0.3" + set-blocking "^2.0.0" + npmlog@^4.1.2: version "4.1.2" resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-4.1.2.tgz#08a7f2a8bf734604779a9efa4ad5cc717abb954b" @@ -8453,14 +9244,14 @@ npmlog@^5.0.1: gauge "^3.0.0" set-blocking "^2.0.0" -npmlog@^6.0.0: - version "6.0.2" - resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-6.0.2.tgz#c8166017a42f2dea92d6453168dd865186a70830" - integrity sha512-/vBvz5Jfr9dT/aFWd0FIRf+T/Q2WBsLENygUaFUqstqsycmZAP/t5BvFJTK0viFmSUxiUKTUplWy5vt+rvKIxg== +npmlog@^7.0.1: + version "7.0.1" + resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-7.0.1.tgz#7372151a01ccb095c47d8bf1d0771a4ff1f53ac8" + integrity sha512-uJ0YFk/mCQpLBt+bxN88AKd+gyqZvZDbtiNxk6Waqcj2aPRyfVx8ITawkyQynxUagInjdYT1+qj4NfA5KJJUxg== dependencies: - are-we-there-yet "^3.0.0" + are-we-there-yet "^4.0.0" console-control-strings "^1.1.0" - gauge "^4.0.3" + gauge "^5.0.0" set-blocking "^2.0.0" nullthrows@^1.1.1: @@ -8473,20 +9264,61 @@ number-is-nan@^1.0.0: resolved "https://registry.yarnpkg.com/number-is-nan/-/number-is-nan-1.0.1.tgz#097b602b53422a522c1afb8790318336941a011d" integrity sha512-4jbtZXNAsfZbAHiiqjLPBiCl16dES1zI4Hpzzxw61Tk+loF+sBDBKx1ICKKKwIqQ7M0mFn1TmkN7euSncWgHiQ== -nwsapi@^2.2.0: - version "2.2.2" - resolved "https://registry.yarnpkg.com/nwsapi/-/nwsapi-2.2.2.tgz#e5418863e7905df67d51ec95938d67bf801f0bb0" - integrity sha512-90yv+6538zuvUMnN+zCr8LuV6bPFdq50304114vJYJ8RDyK8D5O9Phpbd6SZWgI7PwzmmfN1upeOJlvybDSgCw== - -oauth-sign@~0.9.0: - version "0.9.0" - resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.9.0.tgz#47a7b016baa68b5fa0ecf3dee08a85c679ac6455" - integrity sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ== - -ob1@0.64.0: - version "0.64.0" - resolved "https://registry.yarnpkg.com/ob1/-/ob1-0.64.0.tgz#f254a55a53ca395c4f9090e28a85483eac5eba19" - integrity sha512-CO1N+5dhvy+MoAwxz8+fymEUcwsT4a+wHhrHFb02LppcJdHxgcBWviwEhUwKOD2kLMQ7ijrrzybOqpGcqEtvpQ== +nx@15.9.2, "nx@>=15.5.2 < 16": + version "15.9.2" + resolved "https://registry.yarnpkg.com/nx/-/nx-15.9.2.tgz#d7ace1e5ae64a47f1b553dc5da08dbdd858bde96" + integrity sha512-wtcs+wsuplSckvgk+bV+/XuGlo+sVWzSG0RpgWBjQYeqA3QsVFEAPVY66Z5cSoukDbTV77ddcAjEw+Rz8oOR1A== + dependencies: + "@nrwl/cli" "15.9.2" + "@nrwl/tao" "15.9.2" + "@parcel/watcher" "2.0.4" + "@yarnpkg/lockfile" "^1.1.0" + "@yarnpkg/parsers" "^3.0.0-rc.18" + "@zkochan/js-yaml" "0.0.6" + axios "^1.0.0" + chalk "^4.1.0" + cli-cursor "3.1.0" + cli-spinners "2.6.1" + cliui "^7.0.2" + dotenv "~10.0.0" + enquirer "~2.3.6" + fast-glob "3.2.7" + figures "3.2.0" + flat "^5.0.2" + fs-extra "^11.1.0" + glob "7.1.4" + ignore "^5.0.4" + js-yaml "4.1.0" + jsonc-parser "3.2.0" + lines-and-columns "~2.0.3" + minimatch "3.0.5" + npm-run-path "^4.0.1" + open "^8.4.0" + semver "7.3.4" + string-width "^4.2.3" + strong-log-transformer "^2.1.0" + tar-stream "~2.2.0" + tmp "~0.2.1" + tsconfig-paths "^4.1.2" + tslib "^2.3.0" + v8-compile-cache "2.3.0" + yargs "^17.6.2" + yargs-parser "21.1.1" + optionalDependencies: + "@nrwl/nx-darwin-arm64" "15.9.2" + "@nrwl/nx-darwin-x64" "15.9.2" + "@nrwl/nx-linux-arm-gnueabihf" "15.9.2" + "@nrwl/nx-linux-arm64-gnu" "15.9.2" + "@nrwl/nx-linux-arm64-musl" "15.9.2" + "@nrwl/nx-linux-x64-gnu" "15.9.2" + "@nrwl/nx-linux-x64-musl" "15.9.2" + "@nrwl/nx-win32-arm64-msvc" "15.9.2" + "@nrwl/nx-win32-x64-msvc" "15.9.2" + +ob1@0.73.9: + version "0.73.9" + resolved "https://registry.yarnpkg.com/ob1/-/ob1-0.73.9.tgz#d5677a0dd3e2f16ad84231278d79424436c38c59" + integrity sha512-kHOzCOFXmAM26fy7V/YuXNKne2TyRiXbFAvPBIbuedJCZZWQZHLdPzMeXJI4Egt6IcfDttRzN3jQ90wOwq1iNw== object-assign@^4, object-assign@^4.1.0, object-assign@^4.1.1: version "4.1.1" @@ -8502,10 +9334,10 @@ object-copy@^0.1.0: define-property "^0.2.5" kind-of "^3.0.3" -object-inspect@^1.10.3, object-inspect@^1.12.2, object-inspect@^1.9.0: - version "1.12.2" - resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.12.2.tgz#c0641f26394532f28ab8d796ab954e43c009a8ea" - integrity sha512-z+cPxW0QGUp0mcqcsgQyLVRDoXFQbXOwBaqyF7VIgI4TWNQsDHrBpUQslRmIfAoYWdYzs6UlKJtB2XJpTaNSpQ== +object-inspect@^1.10.3, object-inspect@^1.12.3, object-inspect@^1.9.0: + version "1.12.3" + resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.12.3.tgz#ba62dffd67ee256c8c086dfae69e016cd1f198b9" + integrity sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g== object-keys@^1.1.1: version "1.1.1" @@ -8519,7 +9351,7 @@ object-visit@^1.0.0: dependencies: isobject "^3.0.0" -object.assign@^4.1.0, object.assign@^4.1.4: +object.assign@^4.1.4: version "4.1.4" resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.4.tgz#9673c7c7c351ab8c4d0b516f4343ebf4dfb7799f" integrity sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ== @@ -8529,16 +9361,6 @@ object.assign@^4.1.0, object.assign@^4.1.4: has-symbols "^1.0.3" object-keys "^1.1.1" -object.getownpropertydescriptors@^2.0.3: - version "2.1.4" - resolved "https://registry.yarnpkg.com/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.4.tgz#7965e6437a57278b587383831a9b829455a4bc37" - integrity sha512-sccv3L/pMModT6dJAYF3fzGMVcb38ysQ0tEE6ixv2yXJDtEIPph268OlAdJj5/qZMZDq2g/jqvwppt36uS/uQQ== - dependencies: - array.prototype.reduce "^1.0.4" - call-bind "^1.0.2" - define-properties "^1.1.4" - es-abstract "^1.20.1" - object.pick@^1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/object.pick/-/object.pick-1.3.0.tgz#87a10ac4c1694bd2e1cbf53591a66141fb5dd747" @@ -8546,14 +9368,14 @@ object.pick@^1.3.0: dependencies: isobject "^3.0.1" -object.values@^1.1.5: - version "1.1.5" - resolved "https://registry.yarnpkg.com/object.values/-/object.values-1.1.5.tgz#959f63e3ce9ef108720333082131e4a459b716ac" - integrity sha512-QUZRW0ilQ3PnPpbNtgdNV1PDbEqLIiSFB3l+EnGtBQ/8SUTLj1PZwtQHABZtLgwpJZTSZhuGLOGk57Drx2IvYg== +object.values@^1.1.6: + version "1.1.6" + resolved "https://registry.yarnpkg.com/object.values/-/object.values-1.1.6.tgz#4abbaa71eba47d63589d402856f908243eea9b1d" + integrity sha512-FVVTkD1vENCsAcwNs9k6jea2uHC/X0+JcjG8YA60FN5CMaJmG95wT9jek/xX9nornqGRrBkKtzuAu2wuHpKqvw== dependencies: call-bind "^1.0.2" - define-properties "^1.1.3" - es-abstract "^1.19.1" + define-properties "^1.1.4" + es-abstract "^1.20.4" on-finished@2.4.1: version "2.4.1" @@ -8581,13 +9403,6 @@ once@^1.3.0, once@^1.3.1, once@^1.4.0: dependencies: wrappy "1" -onetime@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/onetime/-/onetime-2.0.1.tgz#067428230fd67443b2794b22bba528b6867962d4" - integrity sha512-oyyPpiMaKARvvcgip+JV+7zci5L8D1W9RZIz2l1o08AM3pfspitVWnPt3mzHcBPp12oYMTy0pqrFs/C+m3EwsQ== - dependencies: - mimic-fn "^1.0.0" - onetime@^5.1.0, onetime@^5.1.2: version "5.1.2" resolved "https://registry.yarnpkg.com/onetime/-/onetime-5.1.2.tgz#d0e96ebb56b07476df1dd9c4806e5237985ca45e" @@ -8602,17 +9417,14 @@ open@^6.2.0: dependencies: is-wsl "^1.1.0" -optionator@^0.8.1: - version "0.8.3" - resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.8.3.tgz#84fa1d036fe9d3c7e21d99884b601167ec8fb495" - integrity sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA== +open@^8.4.0: + version "8.4.2" + resolved "https://registry.yarnpkg.com/open/-/open-8.4.2.tgz#5b5ffe2a8f793dcd2aad73e550cb87b59cb084f9" + integrity sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ== dependencies: - deep-is "~0.1.3" - fast-levenshtein "~2.0.6" - levn "~0.3.0" - prelude-ls "~1.1.2" - type-check "~0.3.2" - word-wrap "~1.2.3" + define-lazy-prop "^2.0.0" + is-docker "^2.1.1" + is-wsl "^2.2.0" optionator@^0.9.1: version "0.9.1" @@ -8626,21 +9438,19 @@ optionator@^0.9.1: type-check "^0.4.0" word-wrap "^1.2.3" -options@>=0.0.5: - version "0.0.6" - resolved "https://registry.yarnpkg.com/options/-/options-0.0.6.tgz#ec22d312806bb53e731773e7cdaefcf1c643128f" - integrity sha512-bOj3L1ypm++N+n7CEbbe473A414AB7z+amKYshRb//iuL3MpdDCLhPnw6aVTdKB9g5ZRVHIEp8eUln6L2NUStg== - -ora@^3.4.0: - version "3.4.0" - resolved "https://registry.yarnpkg.com/ora/-/ora-3.4.0.tgz#bf0752491059a3ef3ed4c85097531de9fdbcd318" - integrity sha512-eNwHudNbO1folBP3JsZ19v9azXWtQZjICdr3Q0TDPIaeBQ3mXLrh54wM+er0+hSp+dWKf+Z8KM58CYzEyIYxYg== +ora@^5.4.1: + version "5.4.1" + resolved "https://registry.yarnpkg.com/ora/-/ora-5.4.1.tgz#1b2678426af4ac4a509008e5e4ac9e9959db9e18" + integrity sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ== dependencies: - chalk "^2.4.2" - cli-cursor "^2.1.0" - cli-spinners "^2.0.0" - log-symbols "^2.2.0" - strip-ansi "^5.2.0" + bl "^4.1.0" + chalk "^4.1.0" + cli-cursor "^3.1.0" + cli-spinners "^2.5.0" + is-interactive "^1.0.0" + is-unicode-supported "^0.1.0" + log-symbols "^4.1.0" + strip-ansi "^6.0.0" wcwidth "^1.0.1" os-homedir@^1.0.0: @@ -8680,7 +9490,7 @@ p-limit@^2.0.0, p-limit@^2.2.0: dependencies: p-try "^2.0.0" -p-limit@^3.0.2: +p-limit@^3.0.2, p-limit@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-3.1.0.tgz#e1daccbe78d0d1388ca18c64fea38e3e57e3706b" integrity sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ== @@ -8715,24 +9525,24 @@ p-locate@^5.0.0: dependencies: p-limit "^3.0.2" -p-map-series@^2.1.0: +p-map-series@2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/p-map-series/-/p-map-series-2.1.0.tgz#7560d4c452d9da0c07e692fdbfe6e2c81a2a91f2" integrity sha512-RpYIIK1zXSNEOdwxcfe7FdvGcs7+y5n8rifMhMNWvaxRNMPINJHF5GDeuVxWqnfrcHPSCnp7Oo5yNXHId9Av2Q== -p-map@^4.0.0: +p-map@4.0.0, p-map@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/p-map/-/p-map-4.0.0.tgz#bb2f95a5eda2ec168ec9274e06a747c3e2904d2b" integrity sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ== dependencies: aggregate-error "^3.0.0" -p-pipe@^3.1.0: +p-pipe@3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/p-pipe/-/p-pipe-3.1.0.tgz#48b57c922aa2e1af6a6404cb7c6bf0eb9cc8e60e" integrity sha512-08pj8ATpzMR0Y80x50yJHn37NF6vjrqHutASaX5LiH5npS9XPvrUmscd9MF5R4fuYRHOxQR1FfMIlF7AzwoPqw== -p-queue@^6.6.2: +p-queue@6.6.2: version "6.6.2" resolved "https://registry.yarnpkg.com/p-queue/-/p-queue-6.6.2.tgz#2068a9dcf8e67dd0ec3e7a2bcb76810faa85e426" integrity sha512-RwFpb72c/BhQLEXIZ5K2e+AhgNVmIejGlTgiB9MzZ0e93GRvqZ7uSi0dvRF7/XIXDeNkra2fNHBxTyPDGySpjQ== @@ -8740,7 +9550,7 @@ p-queue@^6.6.2: eventemitter3 "^4.0.4" p-timeout "^3.2.0" -p-reduce@^2.0.0, p-reduce@^2.1.0: +p-reduce@2.1.0, p-reduce@^2.0.0, p-reduce@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/p-reduce/-/p-reduce-2.1.0.tgz#09408da49507c6c274faa31f28df334bc712b64a" integrity sha512-2USApvnsutq8uoxZBGbbWM0JIYLiEMJ9RlaN7fAzVNb9OZN0SHjjTTfIcb667XynS5Y1VhwDJVDa72TnPzAYWw== @@ -8762,37 +9572,63 @@ p-try@^2.0.0: resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== -p-waterfall@^2.1.1: +p-waterfall@2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/p-waterfall/-/p-waterfall-2.1.1.tgz#63153a774f472ccdc4eb281cdb2967fcf158b2ee" integrity sha512-RRTnDb2TBG/epPRI2yYXsimO0v3BXC8Yd3ogr1545IaqKK17VGhbWVeGGN+XfCm/08OK8635nH31c8bATkHuSw== dependencies: p-reduce "^2.0.0" -pacote@^11.2.6: - version "11.3.5" - resolved "https://registry.yarnpkg.com/pacote/-/pacote-11.3.5.tgz#73cf1fc3772b533f575e39efa96c50be8c3dc9d2" - integrity sha512-fT375Yczn4zi+6Hkk2TBe1x1sP8FgFsEIZ2/iWaXY2r/NkhDJfxbcn5paz1+RTFCyNf+dPnaoBDJoAxXSU8Bkg== +pacote@13.6.2, pacote@^13.6.1: + version "13.6.2" + resolved "https://registry.yarnpkg.com/pacote/-/pacote-13.6.2.tgz#0d444ba3618ab3e5cd330b451c22967bbd0ca48a" + integrity sha512-Gu8fU3GsvOPkak2CkbojR7vjs3k3P9cA6uazKTHdsdV0gpCEQq2opelnEv30KRQWgVzP5Vd/5umjcedma3MKtg== dependencies: - "@npmcli/git" "^2.1.0" - "@npmcli/installed-package-contents" "^1.0.6" - "@npmcli/promise-spawn" "^1.2.0" - "@npmcli/run-script" "^1.8.2" - cacache "^15.0.5" + "@npmcli/git" "^3.0.0" + "@npmcli/installed-package-contents" "^1.0.7" + "@npmcli/promise-spawn" "^3.0.0" + "@npmcli/run-script" "^4.1.0" + cacache "^16.0.0" chownr "^2.0.0" fs-minipass "^2.1.0" infer-owner "^1.0.4" - minipass "^3.1.3" - mkdirp "^1.0.3" - npm-package-arg "^8.0.1" - npm-packlist "^2.1.4" - npm-pick-manifest "^6.0.0" - npm-registry-fetch "^11.0.0" + minipass "^3.1.6" + mkdirp "^1.0.4" + npm-package-arg "^9.0.0" + npm-packlist "^5.1.0" + npm-pick-manifest "^7.0.0" + npm-registry-fetch "^13.0.1" + proc-log "^2.0.0" promise-retry "^2.0.1" - read-package-json-fast "^2.0.1" + read-package-json "^5.0.0" + read-package-json-fast "^2.0.3" rimraf "^3.0.2" - ssri "^8.0.1" - tar "^6.1.0" + ssri "^9.0.0" + tar "^6.1.11" + +pacote@^15.0.0, pacote@^15.0.8: + version "15.1.2" + resolved "https://registry.yarnpkg.com/pacote/-/pacote-15.1.2.tgz#78b4c1403231fab368c752943f1969c6d8f026bb" + integrity sha512-EAGJrMiIjBTBB6tWGrx9hFJTOo14B3HSAoa/W9SawFEBhUqjxN7qqaFlGVF9jfY/mIri8Mb2xafmkRgWxYXxIQ== + dependencies: + "@npmcli/git" "^4.0.0" + "@npmcli/installed-package-contents" "^2.0.1" + "@npmcli/promise-spawn" "^6.0.1" + "@npmcli/run-script" "^6.0.0" + cacache "^17.0.0" + fs-minipass "^3.0.0" + minipass "^4.0.0" + npm-package-arg "^10.0.0" + npm-packlist "^7.0.0" + npm-pick-manifest "^8.0.0" + npm-registry-fetch "^14.0.0" + proc-log "^3.0.0" + promise-retry "^2.0.1" + read-package-json "^6.0.0" + read-package-json-fast "^3.0.0" + sigstore "^1.3.0" + ssri "^10.0.0" + tar "^6.1.11" parent-module@^1.0.0: version "1.0.1" @@ -8801,6 +9637,15 @@ parent-module@^1.0.0: dependencies: callsites "^3.0.0" +parse-conflict-json@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/parse-conflict-json/-/parse-conflict-json-3.0.1.tgz#67dc55312781e62aa2ddb91452c7606d1969960c" + integrity sha512-01TvEktc68vwbJOtWZluyWeVGWjP+bZwXtPDMQVbBKzbJ/vZBif0L69KH1+cHv1SZ6e0FKLvjyHe8mqsIqYOmw== + dependencies: + json-parse-even-better-errors "^3.0.0" + just-diff "^6.0.0" + just-diff-apply "^5.2.0" + parse-json@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-4.0.0.tgz#be35f5425be1f7f6c747184f98a788cb99477ee0" @@ -8819,30 +9664,19 @@ parse-json@^5.0.0, parse-json@^5.2.0: json-parse-even-better-errors "^2.3.0" lines-and-columns "^1.1.6" -parse-path@^4.0.0: - version "4.0.4" - resolved "https://registry.yarnpkg.com/parse-path/-/parse-path-4.0.4.tgz#4bf424e6b743fb080831f03b536af9fc43f0ffea" - integrity sha512-Z2lWUis7jlmXC1jeOG9giRO2+FsuyNipeQ43HAjqAZjwSe3SEf+q/84FGPHoso3kyntbxa4c4i77t3m6fGf8cw== +parse-path@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/parse-path/-/parse-path-7.0.0.tgz#605a2d58d0a749c8594405d8cc3a2bf76d16099b" + integrity sha512-Euf9GG8WT9CdqwuWJGdf3RkUcTBArppHABkO7Lm8IzRQp0e2r/kkFnmhu4TSK30Wcu5rVAZLmfPKSBBi9tWFog== dependencies: - is-ssh "^1.3.0" - protocols "^1.4.0" - qs "^6.9.4" - query-string "^6.13.8" + protocols "^2.0.0" -parse-url@^6.0.0: - version "6.0.5" - resolved "https://registry.yarnpkg.com/parse-url/-/parse-url-6.0.5.tgz#4acab8982cef1846a0f8675fa686cef24b2f6f9b" - integrity sha512-e35AeLTSIlkw/5GFq70IN7po8fmDUjpDPY1rIK+VubRfsUvBonjQ+PBZG+vWMACnQSmNlvl524IucoDmcioMxA== +parse-url@^8.1.0: + version "8.1.0" + resolved "https://registry.yarnpkg.com/parse-url/-/parse-url-8.1.0.tgz#972e0827ed4b57fc85f0ea6b0d839f0d8a57a57d" + integrity sha512-xDvOoLU5XRrcOZvnI6b8zA6n9O9ejNk/GExuz1yBuWUGn9KA97GI6HTs6u02wKara1CeVmZhH+0TZFdWScR89w== dependencies: - is-ssh "^1.3.0" - normalize-url "^6.1.0" - parse-path "^4.0.0" - protocols "^1.4.0" - -parse5@6.0.1: - version "6.0.1" - resolved "https://registry.yarnpkg.com/parse5/-/parse5-6.0.1.tgz#e1a1c085c569b3dc08321184f19a39cc27f7c30b" - integrity sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw== + parse-path "^7.0.0" parseurl@~1.3.3: version "1.3.3" @@ -8884,6 +9718,14 @@ path-parse@^1.0.7: resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== +path-scurry@^1.6.1: + version "1.7.0" + resolved "https://registry.yarnpkg.com/path-scurry/-/path-scurry-1.7.0.tgz#99c741a2cfbce782294a39994d63748b5a24f6db" + integrity sha512-UkZUeDjczjYRE495+9thsgcVgsaCPkaw80slmfVFgllxY+IO8ubTsOpFVjDPROBqJdHfVPUFRHPBV/WciOVfWg== + dependencies: + lru-cache "^9.0.0" + minipass "^5.0.0" + path-to-regexp@0.1.7: version "0.1.7" resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c" @@ -8901,11 +9743,6 @@ path-type@^4.0.0: resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b" integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== -performance-now@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b" - integrity sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow== - picocolors@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c" @@ -8916,6 +9753,11 @@ picomatch@^2.0.4, picomatch@^2.2.3, picomatch@^2.3.1: resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== +pify@5.0.0, pify@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/pify/-/pify-5.0.0.tgz#1f5eca3f5e87ebec28cc6d54a0e4aaf00acc127f" + integrity sha512-eW/gHNMlxdSP6dmG6uJip6FXN0EQBwm2clYYd8Wul42Cwu/DK8HEftzsapcNdYe2MfLiIwZqsDk2RDEsTE79hA== + pify@^2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c" @@ -8931,11 +9773,6 @@ pify@^4.0.1: resolved "https://registry.yarnpkg.com/pify/-/pify-4.0.1.tgz#4b2cd25c50d598735c50292224fd8c6df41e3231" integrity sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g== -pify@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/pify/-/pify-5.0.0.tgz#1f5eca3f5e87ebec28cc6d54a0e4aaf00acc127f" - integrity sha512-eW/gHNMlxdSP6dmG6uJip6FXN0EQBwm2clYYd8Wul42Cwu/DK8HEftzsapcNdYe2MfLiIwZqsDk2RDEsTE79hA== - pirates@^4.0.4, pirates@^4.0.5: version "4.0.5" resolved "https://registry.yarnpkg.com/pirates/-/pirates-4.0.5.tgz#feec352ea5c3268fb23a37c702ab1699f35a5f3b" @@ -8955,29 +9792,24 @@ pkg-dir@^4.2.0: dependencies: find-up "^4.0.0" -plist@^3.0.1, plist@^3.0.5: - version "3.0.6" - resolved "https://registry.yarnpkg.com/plist/-/plist-3.0.6.tgz#7cfb68a856a7834bca6dbfe3218eb9c7740145d3" - integrity sha512-WiIVYyrp8TD4w8yCvyeIr+lkmrGRd5u0VbRnU+tP/aRLxP/YadJUYOMZJ/6hIa3oUyVCsycXvtNRgd5XBJIbiA== - dependencies: - base64-js "^1.5.1" - xmlbuilder "^15.1.1" - 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" integrity sha512-xTgYBc3fuo7Yt7JbiuFxSYGToMoz8fLoE6TC9Wx1P/u+LfeThMOAqmuyECnlBaaJb+u1m9hHiXUEtwW4OzfUJg== +postcss-selector-parser@^6.0.10: + version "6.0.11" + resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-6.0.11.tgz#2e41dc39b7ad74046e1615185185cd0b17d0c8dc" + integrity sha512-zbARubNdogI9j7WY4nQJBiNqQf3sLS3wCP4WfOidu+p28LofJqDH1tcXypGrcmMHhDk2t9wGhCsYe/+szLTy1g== + dependencies: + cssesc "^3.0.0" + util-deprecate "^1.0.2" + prelude-ls@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g== -prelude-ls@~1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54" - integrity sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w== - prettier-linter-helpers@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz#d23d41fe1375646de2d0104d3454a3008802cf7b" @@ -8986,11 +9818,20 @@ prettier-linter-helpers@^1.0.0: fast-diff "^1.1.2" prettier@^2.3.1: - version "2.7.1" - resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.7.1.tgz#e235806850d057f97bb08368a4f7d899f7760c64" - integrity sha512-ujppO+MkdPqoVINuDFDRLClm7D78qbDt0/NR+wp5FqEZOoTNAjPHWj17QRhu7geIHJfcNhRk1XVQmF8Bp3ye+g== + version "2.8.8" + resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.8.8.tgz#e8c5d7e98a4305ffe3de2e1fc4aca1a71c28b1da" + integrity sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q== -pretty-format@^26.0.0, pretty-format@^26.5.2, pretty-format@^26.6.2: +pretty-format@29.4.3: + version "29.4.3" + resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-29.4.3.tgz#25500ada21a53c9e8423205cf0337056b201244c" + integrity sha512-cvpcHTc42lcsvOOAzd3XuNWTcvk1Jmnzqeu+WsOuiPmxUJTnkbAcFNsRKvEpBEUFVUgy/GTZLulZDcDEi+CIlA== + dependencies: + "@jest/schemas" "^29.4.3" + ansi-styles "^5.0.0" + react-is "^18.0.0" + +pretty-format@^26.5.2, pretty-format@^26.6.2: version "26.6.2" resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-26.6.2.tgz#e35c2705f14cb7fe2fe94fa078345b444120fc93" integrity sha512-7AeGuCYNGmycyQbCqd/3PWH4eOoX/OiCa0uphp57NVTeAGdJGaAliecxwBDHYQCIvrW7aDBZCYeNTP/WX69mkg== @@ -9000,24 +9841,44 @@ pretty-format@^26.0.0, pretty-format@^26.5.2, pretty-format@^26.6.2: ansi-styles "^4.0.0" react-is "^17.0.1" -pretty-format@^27.5.1: - version "27.5.1" - resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-27.5.1.tgz#2181879fdea51a7a5851fb39d920faa63f01d88e" - integrity sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ== +pretty-format@^29.0.0, pretty-format@^29.5.0: + version "29.5.0" + resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-29.5.0.tgz#283134e74f70e2e3e7229336de0e4fce94ccde5a" + integrity sha512-V2mGkI31qdttvTFX7Mt4efOqHXqJWMu4/r66Xh3Z3BwZaPfPJgp6/gbwoujRpPUtfEF6AUUWx3Jim3GCw5g/Qw== dependencies: - ansi-regex "^5.0.1" + "@jest/schemas" "^29.4.3" ansi-styles "^5.0.0" - react-is "^17.0.1" + react-is "^18.0.0" + +proc-log@^2.0.0, proc-log@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/proc-log/-/proc-log-2.0.1.tgz#8f3f69a1f608de27878f91f5c688b225391cb685" + integrity sha512-Kcmo2FhfDTXdcbfDH76N7uBYHINxc/8GW7UAVuVP9I+Va3uHSerrnKV6dLooga/gh7GlgzuCCr/eoldnL1muGw== + +proc-log@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/proc-log/-/proc-log-3.0.0.tgz#fb05ef83ccd64fd7b20bbe9c8c1070fc08338dd8" + integrity sha512-++Vn7NS4Xf9NacaU9Xq3URUuqZETPsf8L4j5/ckhaRYsfPeRyzGw+iDjFhV/Jr3uNmTvvddEJFWh5R1gRgUH8A== process-nextick-args@~2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag== -progress@^2.0.0: - version "2.0.3" - resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8" - integrity sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA== +process@^0.11.10: + version "0.11.10" + resolved "https://registry.yarnpkg.com/process/-/process-0.11.10.tgz#7332300e840161bda3e69a1d1d91a7d4bc16f182" + integrity sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A== + +promise-all-reject-late@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/promise-all-reject-late/-/promise-all-reject-late-1.0.1.tgz#f8ebf13483e5ca91ad809ccc2fcf25f26f8643c2" + integrity sha512-vuf0Lf0lOxyQREH7GDIOUMLS7kz+gs8i6B+Yi8dC68a2sychGrHTJYghMBD6k7eUcH0H5P73EckCA48xijWqXw== + +promise-call-limit@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/promise-call-limit/-/promise-call-limit-1.0.2.tgz#f64b8dd9ef7693c9c7613e7dfe8d6d24de3031ea" + integrity sha512-1vTUnfI2hzui8AEIixbdAJlFY4LFDXqQswy/2eOlThAscXCY4It8FdVuI0fMJGAB2aWGbdQf/gv0skKYXmdrHA== promise-inflight@^1.0.1: version "1.0.1" @@ -9032,10 +9893,10 @@ promise-retry@^2.0.1: err-code "^2.0.2" retry "^0.12.0" -promise@^8.0.3: - version "8.2.0" - resolved "https://registry.yarnpkg.com/promise/-/promise-8.2.0.tgz#a1f6280ab67457fbfc8aad2b198c9497e9e5c806" - integrity sha512-+CMAlLHqwRYwBMXKCP+o8ns7DN+xHDUiI+0nArsiJ9y+kJVPLFxEaSw6Ha9s9H0tftxg2Yzl25wqj9G7m5wLZg== +promise@^8.3.0: + version "8.3.0" + resolved "https://registry.yarnpkg.com/promise/-/promise-8.3.0.tgz#8cb333d1edeb61ef23869fbb8a4ea0279ab60e0a" + integrity sha512-rZPNPKTOYVNEEKFaq1HqTgOwZD+4/YHS5ukLzQCypkj+OkYx7iv0mA91lJlpPPZ8vMau3IIGj5Qlwrx+8iiSmg== dependencies: asap "~2.0.6" @@ -9054,7 +9915,7 @@ promzard@^0.3.0: dependencies: read "1" -prop-types@^15.7.2: +prop-types@*: version "15.8.1" resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5" integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg== @@ -9063,17 +9924,54 @@ prop-types@^15.7.2: object-assign "^4.1.1" react-is "^16.13.1" +propagate@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/propagate/-/propagate-2.0.1.tgz#40cdedab18085c792334e64f0ac17256d38f9a45" + integrity sha512-vGrhOavPSTz4QVNuBNdcNXePNdNMaO1xj9yBeH1ScQPjk/rhg9sSlCXPhMkFuaNNW/syTvYqsnbIJxMBfRbbag== + proto-list@~1.2.1: version "1.2.4" resolved "https://registry.yarnpkg.com/proto-list/-/proto-list-1.2.4.tgz#212d5bfe1318306a420f6402b8e26ff39647a849" integrity sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA== -protocols@^1.4.0: - version "1.4.8" - resolved "https://registry.yarnpkg.com/protocols/-/protocols-1.4.8.tgz#48eea2d8f58d9644a4a32caae5d5db290a075ce8" - integrity sha512-IgjKyaUSjsROSO8/D49Ab7hP8mJgTYcqApOqdPhLoPxAplXmkp+zRvsrSQjFn5by0rhm4VH0GAUELIPpx7B1yg== - -protocols@^2.0.1: +protobufjs@^6.8.8, protobufjs@~6.11.2, protobufjs@~6.11.3: + version "6.11.3" + resolved "https://registry.yarnpkg.com/protobufjs/-/protobufjs-6.11.3.tgz#637a527205a35caa4f3e2a9a4a13ddffe0e7af74" + integrity sha512-xL96WDdCZYdU7Slin569tFX712BxsxslWwAfAhCYjQKGTq7dAU91Lomy6nLLhh/dyGhk/YH4TwTSRxTzhuHyZg== + 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@^7.2.3: + version "7.2.3" + resolved "https://registry.yarnpkg.com/protobufjs/-/protobufjs-7.2.3.tgz#01af019e40d9c6133c49acbb3ff9e30f4f0f70b2" + integrity sha512-TtpvOqwB5Gdz/PQmOjgsrGH1nHjAQVCN7JG4A6r1sXRWESL5rNMAiRcBQlCAdKxZcAbstExQePYG8xof/JVRgg== + 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" + +protocols@^2.0.0, protocols@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/protocols/-/protocols-2.0.1.tgz#8f155da3fc0f32644e83c5782c8e8212ccf70a86" integrity sha512-/XJ368cyBJ7fzLMwLKv1e4vLxOju2MNAIokcr7meSaNcVbWz/CPcW22cP04mwxOErdA5mwjA8Q6w/cdAQxVn7Q== @@ -9086,10 +9984,10 @@ proxy-addr@~2.0.7: forwarded "0.2.0" ipaddr.js "1.9.1" -psl@^1.1.28, psl@^1.1.33: - version "1.9.0" - resolved "https://registry.yarnpkg.com/psl/-/psl-1.9.0.tgz#d0df2a137f00794565fcaf3b2c00cd09f8d5a5a7" - integrity sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag== +proxy-from-env@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz#e102f16ca355424865755d2c9e8ea4f24d58c3e2" + integrity sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg== pump@^3.0.0: version "3.0.0" @@ -9099,10 +9997,15 @@ pump@^3.0.0: end-of-stream "^1.1.0" once "^1.3.1" -punycode@^2.1.0, punycode@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" - integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== +punycode@^2.1.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.0.tgz#f67fa67c94da8f4d0cfff981aee4118064199b8f" + integrity sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA== + +pure-rand@^6.0.0: + version "6.0.2" + resolved "https://registry.yarnpkg.com/pure-rand/-/pure-rand-6.0.2.tgz#a9c2ddcae9b68d736a8163036f088a2781c8b306" + integrity sha512-6Yg0ekpKICSjPswYOuC5sku/TSWaRYlA0qsXqJgM/d/4pLPHPuTxK7Nbf7jFKzAeedUhR8C7K9Uv63FBsSo8xQ== pvtsutils@^1.3.2: version "1.3.2" @@ -9121,50 +10024,23 @@ q@^1.5.1: resolved "https://registry.yarnpkg.com/q/-/q-1.5.1.tgz#7e32f75b41381291d04611f1bf14109ac00651d7" integrity sha512-kV/CThkXo6xyFEZUugw/+pIOywXcDbFYgSct5cT3gqlbkBE1SJdwy6UQoZvodiWF/ckQLZyDE/Bu1M6gVu5lVw== -qs@6.10.3: - version "6.10.3" - resolved "https://registry.yarnpkg.com/qs/-/qs-6.10.3.tgz#d6cde1b2ffca87b5aa57889816c5f81535e22e8e" - integrity sha512-wr7M2E0OFRfIfJZjKGieI8lBKb7fRCH4Fv5KNPEs7gJ8jadvotdsS08PzOKR7opXhZ/Xkjtt3WF9g38drmyRqQ== - dependencies: - side-channel "^1.0.4" - -qs@^6.9.4: +qs@6.11.0: version "6.11.0" resolved "https://registry.yarnpkg.com/qs/-/qs-6.11.0.tgz#fd0d963446f7a65e1367e01abd85429453f0c37a" integrity sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q== dependencies: side-channel "^1.0.4" -qs@~6.5.2: - version "6.5.3" - resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.3.tgz#3aeeffc91967ef6e35c0e488ef46fb296ab76aad" - integrity sha512-qxXIEh4pCGfHICj1mAJQ2/2XVZkjCDTcEgfoSQxc/fYivUZxTkk7L3bDBJSoNrEzXI17oUO5Dp07ktqE5KzczA== - -query-string@^6.13.8: - version "6.14.1" - resolved "https://registry.yarnpkg.com/query-string/-/query-string-6.14.1.tgz#7ac2dca46da7f309449ba0f86b1fd28255b0c86a" - integrity sha512-XDxAeVmpfu1/6IjyT/gXHOl+S0vQ9owggJ30hhWKdHAsNPOcasn5o9BW0eejZqL2e4vMjhAxoW3jVHcD6mbcYw== - dependencies: - decode-uri-component "^0.2.0" - filter-obj "^1.1.0" - split-on-first "^1.0.0" - strict-uri-encode "^2.0.0" - query-string@^7.0.1: - version "7.1.1" - resolved "https://registry.yarnpkg.com/query-string/-/query-string-7.1.1.tgz#754620669db978625a90f635f12617c271a088e1" - integrity sha512-MplouLRDHBZSG9z7fpuAAcI7aAYjDLhtsiVZsevsfaHWDS2IDdORKbSd1kWUA+V4zyva/HZoSfpwnYMMQDhb0w== + version "7.1.3" + resolved "https://registry.yarnpkg.com/query-string/-/query-string-7.1.3.tgz#a1cf90e994abb113a325804a972d98276fe02328" + integrity sha512-hh2WYhq4fi8+b+/2Kg9CEge4fDPvHS534aOOvOZeQ3+Vf2mCFsaFBYj0i+iXcAq6I9Vzp5fjMFBlONvayDC1qg== dependencies: - decode-uri-component "^0.2.0" + decode-uri-component "^0.2.2" filter-obj "^1.1.0" split-on-first "^1.0.0" strict-uri-encode "^2.0.0" -querystringify@^2.1.1: - version "2.2.0" - resolved "https://registry.yarnpkg.com/querystringify/-/querystringify-2.2.0.tgz#3345941b4153cb9d082d8eee4cda2016a9aef7f6" - integrity sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ== - queue-microtask@^1.2.2: version "1.2.3" resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243" @@ -9200,14 +10076,19 @@ rc@^1.2.8: minimist "^1.2.0" strip-json-comments "~2.0.1" -react-devtools-core@^4.6.0: - version "4.26.0" - resolved "https://registry.yarnpkg.com/react-devtools-core/-/react-devtools-core-4.26.0.tgz#d3d0f59d62ccf1ac03017a7e92f0fe71455019cc" - integrity sha512-OO0Q+vXtHYCXvRQ6elLiOUph3MjsCpuYktGTLnBpizYm46f8tAPuJKihGkwsceitHSJNpzNIjJaYHgX96CyTUQ== +react-devtools-core@^4.26.1: + version "4.27.6" + resolved "https://registry.yarnpkg.com/react-devtools-core/-/react-devtools-core-4.27.6.tgz#e5a613014f7506801ed6c1a97bd0e6316cc9c48a" + integrity sha512-jeFNhEzcSwpiqmw+zix5IFibNEPmUodICN7ClrlRKGktzO/3FMteMb52l1NRUiz/ABSYt9hOZ9IPgVDrg5pyUw== dependencies: shell-quote "^1.6.1" ws "^7" +"react-is@^16.12.0 || ^17.0.0 || ^18.0.0", react-is@^18.0.0: + version "18.2.0" + resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.2.0.tgz#199431eeaaa2e09f86427efbb4f1473edb47609b" + integrity sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w== + react-is@^16.13.1: version "16.13.1" resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" @@ -9218,16 +10099,17 @@ react-is@^17.0.1: resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.2.tgz#e691d4a8e9c789365655539ab372762b0efb54f0" integrity sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w== -react-native-codegen@^0.0.6: - version "0.0.6" - resolved "https://registry.yarnpkg.com/react-native-codegen/-/react-native-codegen-0.0.6.tgz#b3173faa879cf71bfade8d030f9c4698388f6909" - integrity sha512-cMvrUelD81wiPitEPiwE/TCNscIVauXxmt4NTGcy18HrUd0WRWXfYzAQGXm0eI87u3NMudNhqFj2NISJenxQHg== +react-native-codegen@^0.71.5: + version "0.71.5" + resolved "https://registry.yarnpkg.com/react-native-codegen/-/react-native-codegen-0.71.5.tgz#454a42a891cd4ca5fc436440d301044dc1349c14" + integrity sha512-rfsuc0zkuUuMjFnrT55I1mDZ+pBRp2zAiRwxck3m6qeGJBGK5OV5JH66eDQ4aa+3m0of316CqrJDRzVlYufzIg== dependencies: - flow-parser "^0.121.0" - jscodeshift "^0.11.0" + "@babel/parser" "^7.14.0" + flow-parser "^0.185.0" + jscodeshift "^0.13.1" nullthrows "^1.1.1" -react-native-fs@^2.18.0: +react-native-fs@^2.20.0: version "2.20.0" resolved "https://registry.yarnpkg.com/react-native-fs/-/react-native-fs-2.20.0.tgz#05a9362b473bfc0910772c0acbb73a78dbc810f6" integrity sha512-VkTBzs7fIDUiy/XajOSNk0XazFE9l+QlMAce7lGuebZcag5CnjszB+u4BdqzwaQOdcYb5wsJIsqq4kxInIRpJQ== @@ -9235,13 +10117,18 @@ react-native-fs@^2.18.0: base-64 "^0.1.0" utf8 "^3.0.0" -react-native-get-random-values@^1.7.0: +react-native-get-random-values@^1.8.0: version "1.8.0" resolved "https://registry.yarnpkg.com/react-native-get-random-values/-/react-native-get-random-values-1.8.0.tgz#1cb4bd4bd3966a356e59697b8f372999fe97cb16" integrity sha512-H/zghhun0T+UIJLmig3+ZuBCvF66rdbiWUfRSNS6kv5oDSpa1ZiVyvRWtuPesQpT8dXj+Bv7WJRQOUP+5TB1sA== dependencies: fast-base64-decode "^1.0.0" +react-native-gradle-plugin@^0.71.17: + version "0.71.17" + resolved "https://registry.yarnpkg.com/react-native-gradle-plugin/-/react-native-gradle-plugin-0.71.17.tgz#cf780a27270f0a32dca8184eff91555d7627dd00" + integrity sha512-OXXYgpISEqERwjSlaCiaQY6cTY5CH6j73gdkWpK0hedxtiWMWgH+i5TOi4hIGYitm9kQBeyDu+wim9fA8ROFJA== + react-native-securerandom@^0.1.1: version "0.1.1" resolved "https://registry.yarnpkg.com/react-native-securerandom/-/react-native-securerandom-0.1.1.tgz#f130623a412c338b0afadedbc204c5cbb8bf2070" @@ -9249,63 +10136,70 @@ react-native-securerandom@^0.1.1: dependencies: base64-js "*" -react-native@0.64.2: - version "0.64.2" - resolved "https://registry.yarnpkg.com/react-native/-/react-native-0.64.2.tgz#233b6ed84ac4749c8bc2a2d6cf63577a1c437d18" - integrity sha512-Ty/fFHld9DcYsFZujXYdeVjEhvSeQcwuTGXezyoOkxfiGEGrpL/uwUZvMzwShnU4zbbTKDu2PAm/uwuOittRGA== +react-native@^0.71.4: + version "0.71.7" + resolved "https://registry.yarnpkg.com/react-native/-/react-native-0.71.7.tgz#d0ae409f6ee4fc7e7a876b4ca9d8d28934133228" + integrity sha512-Id6iRLS581fJMFGbBl1jP5uSmjExtGOvw5Gvh7694zISXjsRAsFMmU+izs0pyCLqDBoHK7y4BT7WGPGw693nYw== dependencies: - "@jest/create-cache-key-function" "^26.5.0" - "@react-native-community/cli" "^5.0.1-alpha.1" - "@react-native-community/cli-platform-android" "^5.0.1-alpha.1" - "@react-native-community/cli-platform-ios" "^5.0.1-alpha.1" + "@jest/create-cache-key-function" "^29.2.1" + "@react-native-community/cli" "10.2.2" + "@react-native-community/cli-platform-android" "10.2.0" + "@react-native-community/cli-platform-ios" "10.2.1" "@react-native/assets" "1.0.0" - "@react-native/normalize-color" "1.0.0" - "@react-native/polyfills" "1.0.0" + "@react-native/normalize-color" "2.1.0" + "@react-native/polyfills" "2.0.0" abort-controller "^3.0.0" anser "^1.4.9" base64-js "^1.1.2" + deprecated-react-native-prop-types "^3.0.1" event-target-shim "^5.0.1" - hermes-engine "~0.7.0" invariant "^2.2.4" - jsc-android "^245459.0.0" - metro-babel-register "0.64.0" - metro-react-native-babel-transformer "0.64.0" - metro-runtime "0.64.0" - metro-source-map "0.64.0" + jest-environment-node "^29.2.1" + jsc-android "^250231.0.0" + memoize-one "^5.0.0" + metro-react-native-babel-transformer "0.73.9" + metro-runtime "0.73.9" + metro-source-map "0.73.9" + mkdirp "^0.5.1" nullthrows "^1.1.1" pretty-format "^26.5.2" - promise "^8.0.3" - prop-types "^15.7.2" - react-devtools-core "^4.6.0" - react-native-codegen "^0.0.6" + promise "^8.3.0" + react-devtools-core "^4.26.1" + react-native-codegen "^0.71.5" + react-native-gradle-plugin "^0.71.17" react-refresh "^0.4.0" + react-shallow-renderer "^16.15.0" regenerator-runtime "^0.13.2" - scheduler "^0.20.1" - shelljs "^0.8.4" + scheduler "^0.23.0" stacktrace-parser "^0.1.3" - use-subscription "^1.0.0" + use-sync-external-store "^1.0.0" whatwg-fetch "^3.0.0" - ws "^6.1.4" + ws "^6.2.2" react-refresh@^0.4.0: version "0.4.3" resolved "https://registry.yarnpkg.com/react-refresh/-/react-refresh-0.4.3.tgz#966f1750c191672e76e16c2efa569150cc73ab53" integrity sha512-Hwln1VNuGl/6bVwnd0Xdn1e84gT/8T9aYNL+HAKDArLCS7LWjwr7StE30IEYbIkx0Vi3vs+coQxe+SQDbGbbpA== -react@17.0.1: - version "17.0.1" - resolved "https://registry.yarnpkg.com/react/-/react-17.0.1.tgz#6e0600416bd57574e3f86d92edba3d9008726127" - integrity sha512-lG9c9UuMHdcAexXtigOZLX8exLWkW0Ku29qPRU8uhF2R9BN96dLCt0psvzPLlHc5OWkgymP3qwTRgbnw5BKx3w== +react-shallow-renderer@^16.15.0: + version "16.15.0" + resolved "https://registry.yarnpkg.com/react-shallow-renderer/-/react-shallow-renderer-16.15.0.tgz#48fb2cf9b23d23cde96708fe5273a7d3446f4457" + integrity sha512-oScf2FqQ9LFVQgA73vr86xl2NaOIX73rh+YFqcOp68CWj56tSfgtGKrEbyhCj0rSijyG9M1CYprTh39fBi5hzA== dependencies: - loose-envify "^1.1.0" object-assign "^4.1.1" + react-is "^16.12.0 || ^17.0.0 || ^18.0.0" -read-cmd-shim@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/read-cmd-shim/-/read-cmd-shim-2.0.0.tgz#4a50a71d6f0965364938e9038476f7eede3928d9" - integrity sha512-HJpV9bQpkl6KwjxlJcBoqu9Ba0PQg8TqSNIOrulGt54a0uup0HtevreFHzYzkm0lpnleRdNBzXznKrgxglEHQw== +read-cmd-shim@3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/read-cmd-shim/-/read-cmd-shim-3.0.0.tgz#62b8c638225c61e6cc607f8f4b779f3b8238f155" + integrity sha512-KQDVjGqhZk92PPNRj9ZEXEuqg8bUobSKRw+q0YQ3TKI5xkce7bUJobL4Z/OtiEbAAv70yEpYIXp4iQ9L8oPVog== -read-package-json-fast@^2.0.1: +read-cmd-shim@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/read-cmd-shim/-/read-cmd-shim-4.0.0.tgz#640a08b473a49043e394ae0c7a34dd822c73b9bb" + integrity sha512-yILWifhaSEEytfXI76kB9xEEiG1AiozaCJZ83A87ytjRiN+jVibXjedjCRNjoZviinhG+4UkalO3mWTd8u5O0Q== + +read-package-json-fast@^2.0.3: version "2.0.3" resolved "https://registry.yarnpkg.com/read-package-json-fast/-/read-package-json-fast-2.0.3.tgz#323ca529630da82cb34b36cc0b996693c98c2b83" integrity sha512-W/BKtbL+dUjTuRL2vziuYhp76s5HZ9qQhd/dKfWIZveD0O40453QNyZhC0e63lqZrAQ4jiOapVoeJ7JrszenQQ== @@ -9313,44 +10207,43 @@ read-package-json-fast@^2.0.1: json-parse-even-better-errors "^2.3.0" npm-normalize-package-bin "^1.0.1" -read-package-json@^2.0.0: - version "2.1.2" - resolved "https://registry.yarnpkg.com/read-package-json/-/read-package-json-2.1.2.tgz#6992b2b66c7177259feb8eaac73c3acd28b9222a" - integrity sha512-D1KmuLQr6ZSJS0tW8hf3WGpRlwszJOXZ3E8Yd/DNRaM5d+1wVRZdHlpGBLAuovjr28LbWvjpWkBHMxpRGGjzNA== +read-package-json-fast@^3.0.0, read-package-json-fast@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/read-package-json-fast/-/read-package-json-fast-3.0.2.tgz#394908a9725dc7a5f14e70c8e7556dff1d2b1049" + integrity sha512-0J+Msgym3vrLOUB3hzQCuZHII0xkNGCtz/HJH9xZshwv9DbDwkw1KaE3gx/e2J5rpEY5rtOy6cyhKOPrkP7FZw== dependencies: - glob "^7.1.1" - json-parse-even-better-errors "^2.3.0" - normalize-package-data "^2.0.0" - npm-normalize-package-bin "^1.0.0" + json-parse-even-better-errors "^3.0.0" + npm-normalize-package-bin "^3.0.0" -read-package-json@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/read-package-json/-/read-package-json-3.0.1.tgz#c7108f0b9390257b08c21e3004d2404c806744b9" - integrity sha512-aLcPqxovhJTVJcsnROuuzQvv6oziQx4zd3JvG0vGCL5MjTONUc4uJ90zCBC6R7W7oUKBNoR/F8pkyfVwlbxqng== +read-package-json@5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/read-package-json/-/read-package-json-5.0.1.tgz#1ed685d95ce258954596b13e2e0e76c7d0ab4c26" + integrity sha512-MALHuNgYWdGW3gKzuNMuYtcSSZbGQm94fAp16xt8VsYTLBjUSc55bLMKe6gzpWue0Tfi6CBgwCSdDAqutGDhMg== dependencies: - glob "^7.1.1" - json-parse-even-better-errors "^2.3.0" - normalize-package-data "^3.0.0" - npm-normalize-package-bin "^1.0.0" + glob "^8.0.1" + json-parse-even-better-errors "^2.3.1" + normalize-package-data "^4.0.0" + npm-normalize-package-bin "^1.0.1" -read-package-json@^4.1.1: - version "4.1.2" - resolved "https://registry.yarnpkg.com/read-package-json/-/read-package-json-4.1.2.tgz#b444d047de7c75d4a160cb056d00c0693c1df703" - integrity sha512-Dqer4pqzamDE2O4M55xp1qZMuLPqi4ldk2ya648FOMHRjwMzFhuxVrG04wd0c38IsvkVdr3vgHI6z+QTPdAjrQ== +read-package-json@^5.0.0: + version "5.0.2" + resolved "https://registry.yarnpkg.com/read-package-json/-/read-package-json-5.0.2.tgz#b8779ccfd169f523b67208a89cc912e3f663f3fa" + integrity sha512-BSzugrt4kQ/Z0krro8zhTwV1Kd79ue25IhNN/VtHFy1mG/6Tluyi+msc0UpwaoQzxSHa28mntAjIZY6kEgfR9Q== dependencies: - glob "^7.1.1" - json-parse-even-better-errors "^2.3.0" - normalize-package-data "^3.0.0" - npm-normalize-package-bin "^1.0.0" + glob "^8.0.1" + json-parse-even-better-errors "^2.3.1" + normalize-package-data "^4.0.0" + npm-normalize-package-bin "^2.0.0" -read-package-tree@^5.3.1: - version "5.3.1" - resolved "https://registry.yarnpkg.com/read-package-tree/-/read-package-tree-5.3.1.tgz#a32cb64c7f31eb8a6f31ef06f9cedf74068fe636" - integrity sha512-mLUDsD5JVtlZxjSlPPx1RETkNjjvQYuweKwNVt1Sn8kP5Jh44pvYuUHCp6xSVDZWbNxVxG5lyZJ921aJH61sTw== +read-package-json@^6.0.0: + version "6.0.1" + resolved "https://registry.yarnpkg.com/read-package-json/-/read-package-json-6.0.1.tgz#566cb06bc05dbddefba4607e9096d5a9efbcd836" + integrity sha512-AaHqXxfAVa+fNL07x8iAghfKOds/XXsu7zoouIVsbm7PEbQ3nMWXlvjcbrNLjElnUHWQtAo4QEa0RXuvD4XlpA== dependencies: - read-package-json "^2.0.0" - readdir-scoped-modules "^1.0.0" - util-promisify "^2.1.0" + glob "^9.3.0" + json-parse-even-better-errors "^3.0.0" + normalize-package-data "^5.0.0" + npm-normalize-package-bin "^3.0.0" read-pkg-up@^3.0.0: version "3.0.0" @@ -9388,26 +10281,26 @@ read-pkg@^5.2.0: parse-json "^5.0.0" type-fest "^0.6.0" -read@1, read@~1.0.1: +read@1, read@^1.0.7: version "1.0.7" resolved "https://registry.yarnpkg.com/read/-/read-1.0.7.tgz#b3da19bd052431a97671d44a42634adf710b40c4" integrity sha512-rSOKNYUmaxy0om1BNjMN4ezNT6VKK+2xF4GBhc81mkH7L60i6dp8qPYrkndNLT3QPphoII3maL9PVC9XmhHwVQ== dependencies: mute-stream "~0.0.4" -readable-stream@3, readable-stream@^3.0.0, readable-stream@^3.0.2, readable-stream@^3.6.0: - version "3.6.0" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.0.tgz#337bbda3adc0706bd3e024426a286d4b4b2c9198" - integrity sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA== +readable-stream@3, readable-stream@^3.0.0, readable-stream@^3.0.2, readable-stream@^3.1.1, readable-stream@^3.4.0, readable-stream@^3.6.0: + version "3.6.2" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.2.tgz#56a9b36ea965c00c5a93ef31eb111a0f11056967" + integrity sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA== dependencies: inherits "^2.0.3" string_decoder "^1.1.1" util-deprecate "^1.0.1" readable-stream@^2.0.6, readable-stream@~2.3.6: - version "2.3.7" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.7.tgz#1eca1cf711aef814c04f62252a36a62f6cb23b57" - integrity sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw== + version "2.3.8" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.8.tgz#91125e8042bba1b9887f49345f6277027ce8be9b" + integrity sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA== dependencies: core-util-is "~1.0.0" inherits "~2.0.3" @@ -9417,17 +10310,27 @@ readable-stream@^2.0.6, readable-stream@~2.3.6: string_decoder "~1.1.1" util-deprecate "~1.0.1" -readdir-scoped-modules@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/readdir-scoped-modules/-/readdir-scoped-modules-1.1.0.tgz#8d45407b4f870a0dcaebc0e28670d18e74514309" - integrity sha512-asaikDeqAQg7JifRsZn1NJZXo9E+VwlyCfbkZhwyISinqk5zNS6266HS5kah6P0SaQKGF6SkNnZVHUzHFYxYDw== +readable-stream@^4.1.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-4.3.0.tgz#0914d0c72db03b316c9733bb3461d64a3cc50cba" + integrity sha512-MuEnA0lbSi7JS8XM+WNJlWZkHAAdm7gETHdFK//Q/mChGyj2akEFtdLZh32jSdkWGbRwCW9pn6g3LWDdDeZnBQ== dependencies: - debuglog "^1.0.1" - dezalgo "^1.0.0" - graceful-fs "^4.1.2" - once "^1.3.0" + abort-controller "^3.0.0" + buffer "^6.0.3" + events "^3.3.0" + process "^0.11.10" + +readline@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/readline/-/readline-1.3.0.tgz#c580d77ef2cfc8752b132498060dc9793a7ac01c" + integrity sha512-k2d6ACCkiNYz222Fs/iNze30rRJ1iIicW7JuX/7/cozvih6YCkFZH+J6mAFDVgv0dRBaAyr4jDqC95R2y4IADg== + +readonly-date@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/readonly-date/-/readonly-date-1.0.0.tgz#5af785464d8c7d7c40b9d738cbde8c646f97dcd9" + integrity sha512-tMKIV7hlk0h4mO3JTmmVuIlJVXjKk3Sep9Bf5OH0O+758ruuVkUy2J9SttDLm91IEX/WHlXPSpxMGjPj4beMIQ== -recast@^0.20.3: +recast@^0.20.4: version "0.20.5" resolved "https://registry.yarnpkg.com/recast/-/recast-0.20.5.tgz#8e2c6c96827a1b339c634dd232957d230553ceae" integrity sha512-E5qICoPoNL4yU0H0NoBDntNB0Q5oMSNh9usFctYniLBluTthi3RsQVBXIJNbApOlvSwW/RGxIuokPcAc59J5fQ== @@ -9437,13 +10340,6 @@ recast@^0.20.3: source-map "~0.6.1" tslib "^2.0.1" -rechoir@^0.6.2: - version "0.6.2" - resolved "https://registry.yarnpkg.com/rechoir/-/rechoir-0.6.2.tgz#85204b54dba82d5742e28c96756ef43af50e3384" - integrity sha512-HFM8rkZ+i3zrV+4LQjwQ0W+ez98pApMGM3HUrN04j3CqzPOzl9nmP15Y8YXNm8QHGv/eacOVEjqhmWpkRV0NAw== - dependencies: - resolve "^1.1.6" - redent@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/redent/-/redent-3.0.0.tgz#e557b7998316bb53c9f1f56fa626352c6963059f" @@ -9457,7 +10353,15 @@ reduce-flatten@^2.0.0: resolved "https://registry.yarnpkg.com/reduce-flatten/-/reduce-flatten-2.0.0.tgz#734fd84e65f375d7ca4465c69798c25c9d10ae27" integrity sha512-EJ4UNY/U1t2P/2k6oqotuX2Cc3T6nxJwsM0N0asT7dhrtH1ltUxDn4NalSYmPE2rCkVpcf/X6R0wDwcFpzhd4w== -"ref-napi@^2.0.1 || ^3.0.2", ref-napi@^3.0.3: +ref-array-di@1.2.2, ref-array-di@^1.2.2: + version "1.2.2" + resolved "https://registry.yarnpkg.com/ref-array-di/-/ref-array-di-1.2.2.tgz#ceee9d667d9c424b5a91bb813457cc916fb1f64d" + integrity sha512-jhCmhqWa7kvCVrWhR/d7RemkppqPUdxEil1CtTtm7FkZV8LcHHCK3Or9GinUiFP5WY3k0djUkMvhBhx49Jb2iA== + dependencies: + array-index "^1.0.0" + debug "^3.1.0" + +ref-napi@3.0.3, "ref-napi@^2.0.1 || ^3.0.2", ref-napi@^3.0.3: version "3.0.3" resolved "https://registry.yarnpkg.com/ref-napi/-/ref-napi-3.0.3.tgz#e259bfc2bbafb3e169e8cd9ba49037dd00396b22" integrity sha512-LiMq/XDGcgodTYOMppikEtJelWsKQERbLQsYm0IOOnzhwE9xYZC7x8txNnFC9wJNOkPferQI4vD4ZkC0mDyrOA== @@ -9467,7 +10371,7 @@ reduce-flatten@^2.0.0: node-addon-api "^3.0.0" node-gyp-build "^4.2.1" -ref-struct-di@^1.1.0: +ref-struct-di@1.1.1, ref-struct-di@^1.1.0, ref-struct-di@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/ref-struct-di/-/ref-struct-di-1.1.1.tgz#5827b1d3b32372058f177547093db1fe1602dc10" integrity sha512-2Xyn/0Qgz89VT+++WP0sTosdm9oeowLP23wRJYhG4BFdMUrLj3jhwHZNEytYNYgtPKLNTP3KJX4HEgBvM1/Y2g== @@ -9491,17 +10395,10 @@ regenerate@^1.4.2: resolved "https://registry.yarnpkg.com/regenerate/-/regenerate-1.4.2.tgz#b9346d8827e8f5a32f7ba29637d398b69014848a" integrity sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A== -regenerator-runtime@^0.13.2, regenerator-runtime@^0.13.4: - version "0.13.9" - resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz#8925742a98ffd90814988d7566ad30ca3b263b52" - integrity sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA== - -regenerator-transform@^0.15.0: - version "0.15.0" - resolved "https://registry.yarnpkg.com/regenerator-transform/-/regenerator-transform-0.15.0.tgz#cbd9ead5d77fae1a48d957cf889ad0586adb6537" - integrity sha512-LsrGtPmbYg19bcPHwdtmXwbW+TqNvtY4riE3P83foeHRroMbH6/2ddFBfab3t7kbzc7v7p4wbkIecHImqt0QNg== - dependencies: - "@babel/runtime" "^7.8.4" +regenerator-runtime@^0.13.11, regenerator-runtime@^0.13.2: + version "0.13.11" + resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz#f6dca3e7ceec20590d07ada785636a90cdca17f9" + integrity sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg== regex-not@^1.0.0, regex-not@^1.0.2: version "1.0.2" @@ -9512,35 +10409,25 @@ regex-not@^1.0.0, regex-not@^1.0.2: safe-regex "^1.1.0" regexp.prototype.flags@^1.4.3: - version "1.4.3" - resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.4.3.tgz#87cab30f80f66660181a3bb7bf5981a872b367ac" - integrity sha512-fjggEOO3slI6Wvgjwflkc4NFRCTZAu5CnNfBd5qOMYhWdn67nJBBu34/TkD++eeFmd8C9r9jfXJ27+nSiRkSUA== + version "1.5.0" + resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.5.0.tgz#fe7ce25e7e4cca8db37b6634c8a2c7009199b9cb" + integrity sha512-0SutC3pNudRKgquxGoRGIz946MZVHqbNfPjBdxeOhBrdgDKlRoXmYLQN9xRbrR09ZXWeGAdPuif7egofn6v5LA== dependencies: call-bind "^1.0.2" - define-properties "^1.1.3" - functions-have-names "^1.2.2" - -regexpp@^3.1.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-3.2.0.tgz#0425a2768d8f23bad70ca4b90461fa2f1213e1b2" - integrity sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg== + define-properties "^1.2.0" + functions-have-names "^1.2.3" -regexpu-core@^5.1.0: - version "5.2.1" - resolved "https://registry.yarnpkg.com/regexpu-core/-/regexpu-core-5.2.1.tgz#a69c26f324c1e962e9ffd0b88b055caba8089139" - integrity sha512-HrnlNtpvqP1Xkb28tMhBUO2EbyUHdQlsnlAhzWcwHy8WJR53UWr7/MAvqrsQKMbV4qdpv03oTMG8iIhfsPFktQ== +regexpu-core@^5.3.1: + version "5.3.2" + resolved "https://registry.yarnpkg.com/regexpu-core/-/regexpu-core-5.3.2.tgz#11a2b06884f3527aec3e93dbbf4a3b958a95546b" + integrity sha512-RAM5FlZz+Lhmo7db9L298p2vHP5ZywrVXmVXpmAD9GuL5MPH6t9ROw1iA/wfHkQ76Qe7AaPF0nGuim96/IrQMQ== dependencies: + "@babel/regjsgen" "^0.8.0" regenerate "^1.4.2" regenerate-unicode-properties "^10.1.0" - regjsgen "^0.7.1" regjsparser "^0.9.1" unicode-match-property-ecmascript "^2.0.0" - unicode-match-property-value-ecmascript "^2.0.0" - -regjsgen@^0.7.1: - version "0.7.1" - resolved "https://registry.yarnpkg.com/regjsgen/-/regjsgen-0.7.1.tgz#ee5ef30e18d3f09b7c369b76e7c2373ed25546f6" - integrity sha512-RAt+8H2ZEzHeYWxZ3H2z6tF18zyyOnlcdaafLrm21Bguj7uZy6ULibiAFdXEtKQY4Sy7wDTwDiOazasMLc4KPA== + unicode-match-property-value-ecmascript "^2.1.0" regjsparser@^0.9.1: version "0.9.1" @@ -9549,11 +10436,6 @@ regjsparser@^0.9.1: dependencies: jsesc "~0.5.0" -remove-trailing-separator@^1.0.1: - version "1.1.0" - resolved "https://registry.yarnpkg.com/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz#c24bce2a283adad5bc3f58e0d48249b92379d8ef" - integrity sha512-/hS+Y0u3aOfIETiaiirUFwDBDzmXPvO+jAfKTitUngIPzdKc6Z0LoFjM/CK5PL4C+eKwHohlHAb6H0VFfmmUsw== - repeat-element@^1.1.2: version "1.1.4" resolved "https://registry.yarnpkg.com/repeat-element/-/repeat-element-1.1.4.tgz#be681520847ab58c7568ac75fbfad28ed42d39e9" @@ -9564,52 +10446,16 @@ repeat-string@^1.6.1: resolved "https://registry.yarnpkg.com/repeat-string/-/repeat-string-1.6.1.tgz#8dcae470e1c88abc2d600fff4a776286da75e637" integrity sha512-PV0dzCYDNfRi1jCDbJzpW7jNNDRuCOG/jI5ctQcGKt/clZD+YcPS3yIlWuTJMmESC8aevCFmWJy5wjAFgNqN6w== -request@^2.88.0, request@^2.88.2: - version "2.88.2" - resolved "https://registry.yarnpkg.com/request/-/request-2.88.2.tgz#d73c918731cb5a87da047e207234146f664d12b3" - integrity sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw== - dependencies: - aws-sign2 "~0.7.0" - aws4 "^1.8.0" - caseless "~0.12.0" - combined-stream "~1.0.6" - extend "~3.0.2" - forever-agent "~0.6.1" - form-data "~2.3.2" - har-validator "~5.1.3" - http-signature "~1.2.0" - is-typedarray "~1.0.0" - isstream "~0.1.2" - json-stringify-safe "~5.0.1" - mime-types "~2.1.19" - oauth-sign "~0.9.0" - performance-now "^2.1.0" - qs "~6.5.2" - safe-buffer "^5.1.2" - tough-cookie "~2.5.0" - tunnel-agent "^0.6.0" - uuid "^3.3.2" - require-directory@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" integrity sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q== -require-from-string@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/require-from-string/-/require-from-string-2.0.2.tgz#89a7fdd938261267318eafe14f9c32e598c36909" - integrity sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw== - require-main-filename@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-2.0.0.tgz#d0b329ecc7cc0f61649f62215be69af54aa8989b" integrity sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg== -requires-port@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff" - integrity sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ== - resolve-cwd@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-3.0.0.tgz#0f0075f1bb2544766cf73ba6a6e2adfebcb13f2d" @@ -9617,6 +10463,11 @@ resolve-cwd@^3.0.0: dependencies: resolve-from "^5.0.0" +resolve-from@5.0.0, resolve-from@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-5.0.0.tgz#c35225843df8f776df21c57557bc087e9dfdfc69" + integrity sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw== + resolve-from@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-3.0.0.tgz#b22c7af7d9d6881bc8b6e653335eebcb0a188748" @@ -9627,38 +10478,25 @@ resolve-from@^4.0.0: resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6" integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g== -resolve-from@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-5.0.0.tgz#c35225843df8f776df21c57557bc087e9dfdfc69" - integrity sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw== - resolve-url@^0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/resolve-url/-/resolve-url-0.2.1.tgz#2c637fe77c893afd2a663fe21aa9080068e2052a" integrity sha512-ZuF55hVUQaaczgOIwqWzkEcEidmlD/xl44x1UZnhOXcYuFN2S6+rcxpG+C1N3So0wvNI3DmJICUFfu2SxhBmvg== -resolve.exports@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/resolve.exports/-/resolve.exports-1.1.0.tgz#5ce842b94b05146c0e03076985d1d0e7e48c90c9" - integrity sha512-J1l+Zxxp4XK3LUDZ9m60LRJF/mAe4z6a4xyabPHk7pvK5t35dACV32iIjJDFeWZFfZlO29w6SZ67knR0tHzJtQ== +resolve.exports@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/resolve.exports/-/resolve.exports-2.0.2.tgz#f8c934b8e6a13f539e38b7098e2e36134f01e800" + integrity sha512-X2UW6Nw3n/aMgDVy+0rSqgHlv39WZAlZrXCdnbyEiKm17DSqHX4MmQMaST3FbeWR5FTuRcUwYAziZajji0Y7mg== -resolve@^1.1.6, resolve@^1.10.0, resolve@^1.14.2, resolve@^1.20.0, resolve@^1.22.0: - version "1.22.1" - resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.1.tgz#27cb2ebb53f91abb49470a928bba7558066ac177" - integrity sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw== +resolve@^1.10.0, resolve@^1.14.2, resolve@^1.20.0, resolve@^1.22.1: + version "1.22.2" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.2.tgz#0ed0943d4e301867955766c9f3e1ae6d01c6845f" + integrity sha512-Sb+mjNHOULsBv818T40qSPeRiuWLyaGMa5ewydRLFimneixmVy2zdivRl+AF6jaYPC8ERxGDmFSiqui6SfPd+g== dependencies: - is-core-module "^2.9.0" + is-core-module "^2.11.0" path-parse "^1.0.7" supports-preserve-symlinks-flag "^1.0.0" -restore-cursor@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-2.0.0.tgz#9f7ee287f82fd326d4fd162923d62129eee0dfaf" - integrity sha512-6IzJLuGi4+R14vwagDHX+JrXmPVtPpn4mffDJ1UdR7/Edm87fl6yi8mMBIVvFtJaNTUvjughmW4hwLhRG7gC1Q== - dependencies: - onetime "^2.0.0" - signal-exit "^3.0.2" - restore-cursor@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-3.1.0.tgz#39f67c54b3a7a58cea5236d95cf0034239631f7e" @@ -9687,20 +10525,27 @@ rfc4648@1.4.0: resolved "https://registry.yarnpkg.com/rfc4648/-/rfc4648-1.4.0.tgz#c75b2856ad2e2d588b6ddb985d556f1f7f2a2abd" integrity sha512-3qIzGhHlMHA6PoT6+cdPKZ+ZqtxkIvg8DZGKA5z6PQ33/uuhoJ+Ws/D/J9rXW6gXodgH8QYlz2UCl+sdUDmNIg== -rimraf@^2.5.4, rimraf@^2.6.3, rimraf@^2.7.1: +rimraf@^2.7.1: version "2.7.1" resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.7.1.tgz#35797f13a7fdadc566142c29d4f07ccad483e3ec" integrity sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w== dependencies: glob "^7.1.3" -rimraf@^3.0.0, rimraf@^3.0.2, rimraf@~3.0.2: +rimraf@^3.0.0, rimraf@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA== dependencies: glob "^7.1.3" +rimraf@^4.0.7, rimraf@^4.4.0, rimraf@^4.4.1: + version "4.4.1" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-4.4.1.tgz#bd33364f67021c5b79e93d7f4fa0568c7c21b755" + integrity sha512-Gk8NlF062+T9CqNGn6h4tls3k6T1+/nXdOcSZVikNVtlRdYpA7wRJJMoXmuvOnLW844rPjdQ7JgXCYM6PPC/og== + dependencies: + glob "^9.2.0" + rimraf@~2.2.6: version "2.2.8" resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.2.8.tgz#e439be2aaee327321952730f99a8929e4fc50582" @@ -9713,11 +10558,6 @@ rimraf@~2.6.2: dependencies: glob "^7.1.3" -rsvp@^4.8.4: - version "4.8.5" - resolved "https://registry.yarnpkg.com/rsvp/-/rsvp-4.8.5.tgz#c8f155311d167f68f21e168df71ec5b083113734" - integrity sha512-nfMOlASu9OnRJo1mbEk2cz0D56a1MBNrJ7orjRZQG10XDyuvwksKbuXNp6qa+kbn839HwjwhBzhFmdsaEAfauA== - run-async@^2.4.0: version "2.4.1" resolved "https://registry.yarnpkg.com/run-async/-/run-async-2.4.1.tgz#8440eccf99ea3e70bd409d49aab88e10c189a455" @@ -9737,10 +10577,10 @@ rxjs@^6.6.0: dependencies: tslib "^1.9.0" -rxjs@^7.2.0: - version "7.5.7" - resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-7.5.7.tgz#2ec0d57fdc89ece220d2e702730ae8f1e49def39" - integrity sha512-z9MzKh/UcOqB3i20H6rtrlaE/CgjLOvheWK/9ILrbhROGTweAi1BaFsTT9FbwZi5Trr1qNRs+MXkhmR06awzQA== +rxjs@^7.2.0, rxjs@^7.5.5, rxjs@^7.8.0: + version "7.8.0" + resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-7.8.0.tgz#90a938862a82888ff4c7359811a595e14e1e09a4" + integrity sha512-F2+gxDshqmIub1KdvZkaEfGDwLNpPvk9Fs6LD/MyQxNgMds/WH9OdDDXOmxUZpME+iSK3rQCctkL0DYyytUqMg== dependencies: tslib "^2.1.0" @@ -9770,55 +10610,46 @@ safe-regex@^1.1.0: dependencies: ret "~0.1.10" -"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: +"safer-buffer@>= 2.1.2 < 3", "safer-buffer@>= 2.1.2 < 3.0.0": version "2.1.2" resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== -sane@^4.0.3: - version "4.1.0" - resolved "https://registry.yarnpkg.com/sane/-/sane-4.1.0.tgz#ed881fd922733a6c461bc189dc2b6c006f3ffded" - integrity sha512-hhbzAgTIX8O7SHfp2c8/kREfEn4qO/9q8C9beyY6+tvZ87EpoZ3i1RIEvp27YBswnNbY9mWd6paKVmKbAgLfZA== - dependencies: - "@cnakazawa/watch" "^1.0.3" - anymatch "^2.0.0" - capture-exit "^2.0.0" - exec-sh "^0.3.2" - execa "^1.0.0" - fb-watchman "^2.0.0" - micromatch "^3.1.4" - minimist "^1.1.1" - walker "~1.0.5" - sax@^1.2.4: version "1.2.4" resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9" integrity sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw== -saxes@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/saxes/-/saxes-5.0.1.tgz#eebab953fa3b7608dbe94e5dadb15c888fa6696d" - integrity sha512-5LBh1Tls8c9xgGjw3QrMwETmTMVk0oFgvrFSvWx62llR2hcEInrKNZ2GZCCuuy2lvWrdl5jhbpeqc5hRYKFOcw== - dependencies: - xmlchars "^2.2.0" - -scheduler@^0.20.1: - version "0.20.2" - resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.20.2.tgz#4baee39436e34aa93b4874bddcbf0fe8b8b50e91" - integrity sha512-2eWfGgAqqWFGqtdMmcL5zCMK1U8KlXv8SQFGglL3CEtd0aDVDWgeF/YoCmvln55m5zSk3J/20hTaSBeSObsQDQ== +scheduler@^0.23.0: + version "0.23.0" + resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.23.0.tgz#ba8041afc3d30eb206a487b6b384002e4e61fdfe" + integrity sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw== dependencies: loose-envify "^1.1.0" - object-assign "^4.1.1" "semver@2 || 3 || 4 || 5", semver@^5.5.0, semver@^5.6.0, semver@^5.7.1: version "5.7.1" resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== -semver@7.x, semver@^7.1.1, semver@^7.1.3, semver@^7.2.1, semver@^7.3.2, semver@^7.3.4, semver@^7.3.5: - version "7.3.7" - resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.7.tgz#12c5b649afdbf9049707796e22a4028814ce523f" - integrity sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g== +semver@7.3.4: + version "7.3.4" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.4.tgz#27aaa7d2e4ca76452f98d3add093a72c943edc97" + integrity sha512-tCfb2WLjqFAtXn4KEdxIhalnRtoKFN7nAwj0B3ZXCbQloV2tq5eDbcTmT68JJD3nRJq24/XgxtQKFIpQdtvmVw== + dependencies: + lru-cache "^6.0.0" + +semver@7.3.8: + version "7.3.8" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.8.tgz#07a78feafb3f7b32347d725e33de7e2a2df67798" + integrity sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A== + dependencies: + lru-cache "^6.0.0" + +semver@7.x, semver@^7.0.0, semver@^7.1.1, semver@^7.3.2, semver@^7.3.4, semver@^7.3.5, semver@^7.3.7, semver@^7.3.8: + version "7.5.0" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.5.0.tgz#ed8c5dc8efb6c629c88b23d41dc9bf40c1d96cd0" + integrity sha512-+XC0AD/R7Q2mPSRuy2Id0+CGTZ98+8f+KvwirxOKIEyid+XSx6HbC63p+O4IndTHuX5Z+JxQ0TghCkO5Cg/2HA== dependencies: lru-cache "^6.0.0" @@ -9919,29 +10750,10 @@ shebang-regex@^3.0.0: resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== -shell-quote@1.6.1: - version "1.6.1" - resolved "https://registry.yarnpkg.com/shell-quote/-/shell-quote-1.6.1.tgz#f4781949cce402697127430ea3b3c5476f481767" - integrity sha512-V0iQEZ/uoem3NmD91rD8XiuozJnq9/ZJnbHVXHnWqP1ucAhS3yJ7sLIIzEi57wFFcK3oi3kFUC46uSyWr35mxg== - dependencies: - array-filter "~0.0.0" - array-map "~0.0.0" - array-reduce "~0.0.0" - jsonify "~0.0.0" - -shell-quote@^1.6.1: - version "1.7.3" - resolved "https://registry.yarnpkg.com/shell-quote/-/shell-quote-1.7.3.tgz#aa40edac170445b9a431e17bb62c0b881b9c4123" - integrity sha512-Vpfqwm4EnqGdlsBFNmHhxhElJYrdfcxPThu+ryKS5J8L/fhAwLazFZtq+S+TWZ9ANj2piSQLGj6NQg+lKPmxrw== - -shelljs@^0.8.4: - version "0.8.5" - resolved "https://registry.yarnpkg.com/shelljs/-/shelljs-0.8.5.tgz#de055408d8361bed66c669d2f000538ced8ee20c" - integrity sha512-TiwcRcrkhHvbrZbnRcFYMLl30Dfov3HKqzp5tO5b4pt6G/SezKcYhmDg15zXVBswHmctSAQKznqNW2LO5tTDow== - dependencies: - glob "^7.0.0" - interpret "^1.0.0" - rechoir "^0.6.2" +shell-quote@^1.6.1, shell-quote@^1.7.3: + version "1.8.1" + resolved "https://registry.yarnpkg.com/shell-quote/-/shell-quote-1.8.1.tgz#6dbf4db75515ad5bac63b4f1894c3a154c766680" + integrity sha512-6j1W9l1iAs/4xYBI1SYOVZyFcCis9b4KCLQ8fgAGG07QvzaRLVVRQvAy85yNmmZSjYjg4MWh4gNvlPujU/5LpA== side-channel@^1.0.4: version "1.0.4" @@ -9952,30 +10764,35 @@ side-channel@^1.0.4: get-intrinsic "^1.0.2" object-inspect "^1.9.0" -signal-exit@^3.0.0, signal-exit@^3.0.2, signal-exit@^3.0.3, signal-exit@^3.0.7: +signal-exit@3.0.7, signal-exit@^3.0.0, signal-exit@^3.0.2, signal-exit@^3.0.3, signal-exit@^3.0.7: version "3.0.7" resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9" integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== -simple-plist@^1.0.0: - version "1.3.1" - resolved "https://registry.yarnpkg.com/simple-plist/-/simple-plist-1.3.1.tgz#16e1d8f62c6c9b691b8383127663d834112fb017" - integrity sha512-iMSw5i0XseMnrhtIzRb7XpQEXepa9xhWxGUojHBL43SIpQuDQkh3Wpy67ZbDzZVr6EKxvwVChnVpdl8hEVLDiw== +sigstore@^1.3.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/sigstore/-/sigstore-1.4.0.tgz#2e3a28c08b1b8246744c27cfb179c525c3f164d8" + integrity sha512-N7TRpSbFjY/TrFDg6yGAQSYBrQ5s6qmPiq4pD6fkv1LoyfMsLG0NwZWG2s5q+uttLHgyVyTa0Rogx2P78rN8kQ== dependencies: - bplist-creator "0.1.0" - bplist-parser "0.3.1" - plist "^3.0.5" + "@sigstore/protobuf-specs" "^0.1.0" + make-fetch-happen "^11.0.1" + tuf-js "^1.1.3" sisteransi@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/sisteransi/-/sisteransi-1.0.5.tgz#134d681297756437cc05ca01370d3a7a571075ed" integrity sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg== -slash@^3.0.0: +slash@3.0.0, slash@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q== +slash@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/slash/-/slash-4.0.0.tgz#2422372176c4c6c5addb5e2ada885af984b396a7" + integrity sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew== + slice-ansi@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-2.1.0.tgz#cacd7693461a637a5788d92a7dd4fba068e81636" @@ -9985,20 +10802,6 @@ slice-ansi@^2.0.0: astral-regex "^1.0.0" is-fullwidth-code-point "^2.0.0" -slice-ansi@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-4.0.0.tgz#500e8dd0fd55b05815086255b3195adf2a45fe6b" - integrity sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ== - dependencies: - ansi-styles "^4.0.0" - astral-regex "^2.0.0" - is-fullwidth-code-point "^3.0.0" - -slide@^1.1.6: - version "1.1.6" - resolved "https://registry.yarnpkg.com/slide/-/slide-1.1.6.tgz#56eb027d65b4d2dce6cb2e2d32c4d4afc9e1d707" - integrity sha512-NwrtjCg+lZoqhFU8fOwl4ay2ei8PaqCBOUV3/ektPY9trO1yQ1oXEfmHAhKArUVUr/hOHvy5f6AdP17dCM0zMw== - smart-buffer@^4.2.0: version "4.2.0" resolved "https://registry.yarnpkg.com/smart-buffer/-/smart-buffer-4.2.0.tgz#6e1d71fa4f18c05f7d0ff216dd16a481d0e8d9ae" @@ -10034,15 +10837,6 @@ snapdragon@^0.8.1: source-map-resolve "^0.5.0" use "^3.1.0" -socks-proxy-agent@^5.0.0: - version "5.0.1" - resolved "https://registry.yarnpkg.com/socks-proxy-agent/-/socks-proxy-agent-5.0.1.tgz#032fb583048a29ebffec2e6a73fca0761f48177e" - 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.yarnpkg.com/socks-proxy-agent/-/socks-proxy-agent-6.2.1.tgz#2687a31f9d7185e38d530bef1944fe1f1496d6ce" @@ -10052,10 +10846,19 @@ socks-proxy-agent@^6.0.0: debug "^4.3.3" socks "^2.6.2" -socks@^2.3.3, socks@^2.6.2: - version "2.7.0" - resolved "https://registry.yarnpkg.com/socks/-/socks-2.7.0.tgz#f9225acdb841e874dca25f870e9130990f3913d0" - integrity sha512-scnOe9y4VuiNUULJN72GrM26BNOjVsfPXI+j+98PkyEfsIXroa5ofyjT+FzGvn/xHs73U2JtoBYAVx9Hl4quSA== +socks-proxy-agent@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/socks-proxy-agent/-/socks-proxy-agent-7.0.0.tgz#dc069ecf34436621acb41e3efa66ca1b5fed15b6" + integrity sha512-Fgl0YPZ902wEsAyiQ+idGd1A7rSFx/ayC1CQVMw5P+EQx2V0SgpGtf6OKFhVjPflPUl9YMmEOnmfjCdMUsygww== + dependencies: + agent-base "^6.0.2" + debug "^4.3.3" + socks "^2.6.2" + +socks@^2.6.2: + version "2.7.1" + resolved "https://registry.yarnpkg.com/socks/-/socks-2.7.1.tgz#d8e651247178fde79c0663043e07240196857d55" + integrity sha512-7maUZy1N7uo6+WVEX6psASxtNlKaNVMlGQKkG/63nEDdLOWNbiUMoLK7X4uYoLhQstau72mLgfEWcXcwsaHbYQ== dependencies: ip "^2.0.0" smart-buffer "^4.2.0" @@ -10067,13 +10870,6 @@ sort-keys@^2.0.0: dependencies: is-plain-obj "^1.0.0" -sort-keys@^4.0.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/sort-keys/-/sort-keys-4.2.0.tgz#6b7638cee42c506fff8c1cecde7376d21315be18" - integrity sha512-aUYIEU/UviqPgc8mHR6IW1EGxkAXpeRETYcrzg8cLAvUPZcpAlleSXHV2mY7G12GphSH6Gzv+4MMVSSkbdteHg== - dependencies: - is-plain-obj "^2.0.0" - source-map-resolve@^0.5.0: version "0.5.3" resolved "https://registry.yarnpkg.com/source-map-resolve/-/source-map-resolve-0.5.3.tgz#190866bece7553e1f8f267a2ee82c606b5509a1a" @@ -10085,7 +10881,15 @@ source-map-resolve@^0.5.0: source-map-url "^0.4.0" urix "^0.1.0" -source-map-support@^0.5.16, source-map-support@^0.5.21, source-map-support@^0.5.6: +source-map-support@0.5.13: + version "0.5.13" + resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.13.tgz#31b24a9c2e73c2de85066c0feb7d44767ed52932" + integrity sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w== + dependencies: + buffer-from "^1.0.0" + source-map "^0.6.0" + +source-map-support@^0.5.16, source-map-support@~0.5.20: version "0.5.21" resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.21.tgz#04fe7c7f9e1ed2d662233c28cb2b35b9f63f6e4f" integrity sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w== @@ -10114,9 +10918,9 @@ source-map@^0.7.3: integrity sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA== spdx-correct@^3.0.0: - version "3.1.1" - resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-3.1.1.tgz#dece81ac9c1e6713e5f7d1b6f17d468fa53d89a9" - integrity sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w== + version "3.2.0" + resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-3.2.0.tgz#4f5ab0668f0059e34f9c00dce331784a12de4e9c" + integrity sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA== dependencies: spdx-expression-parse "^3.0.0" spdx-license-ids "^3.0.0" @@ -10135,9 +10939,9 @@ spdx-expression-parse@^3.0.0: spdx-license-ids "^3.0.0" spdx-license-ids@^3.0.0: - version "3.0.12" - resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-3.0.12.tgz#69077835abe2710b65f03969898b6637b505a779" - integrity sha512-rr+VVSXtRhO4OHbXUiAF7xW3Bo9DuuF6C5jH+q/x15j2jniycgKbxU09Hr0WqlSLUs4i4ltHGXqTe7VHclYWyA== + version "3.0.13" + resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-3.0.13.tgz#7189a474c46f8d47c7b0da4b987bb45e908bd2d5" + integrity sha512-XkD+zwiqXHikFZm4AX/7JSCXA98U5Db4AFd5XUg/+9UNtnH75+Z9KxtpYiJZx36mUDVOwH83pl7yvCer6ewM3w== split-on-first@^1.0.0: version "1.1.0" @@ -10170,20 +10974,19 @@ sprintf-js@~1.0.2: resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" integrity sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g== -sshpk@^1.7.0: - version "1.17.0" - resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.17.0.tgz#578082d92d4fe612b13007496e543fa0fbcbe4c5" - integrity sha512-/9HIEs1ZXGhSPE8X6Ccm7Nam1z8KcoCqPdI7ecm1N33EzAetWahvQWVqLZtaZQ+IDKX4IyA2o0gBzqIMkAagHQ== - dependencies: - asn1 "~0.2.3" - assert-plus "^1.0.0" - bcrypt-pbkdf "^1.0.0" - dashdash "^1.12.0" - ecc-jsbn "~0.1.1" - getpass "^0.1.1" - jsbn "~0.1.0" - safer-buffer "^2.0.2" - tweetnacl "~0.14.0" +ssri@9.0.1, ssri@^9.0.0: + version "9.0.1" + resolved "https://registry.yarnpkg.com/ssri/-/ssri-9.0.1.tgz#544d4c357a8d7b71a19700074b6883fcb4eae057" + integrity sha512-o57Wcn66jMQvfHG1FlYbWeZWW/dHZhJXjpIcTfXldXEk5nz5lStPo3mK0OJQfGR3RbZUlbISexbljkJzuEj/8Q== + dependencies: + minipass "^3.1.1" + +ssri@^10.0.0, ssri@^10.0.1: + version "10.0.3" + resolved "https://registry.yarnpkg.com/ssri/-/ssri-10.0.3.tgz#7f83da39058ca1d599d174e9eee4237659710bf4" + integrity sha512-lJtX/BFPI/VEtxZmLfeh7pzisIs6micwZ3eruD3+ds9aPsXKlYpwDS2Q7omD6WC42WO9+bnUSzlMmfv8uK8meg== + dependencies: + minipass "^4.0.0" ssri@^8.0.0, ssri@^8.0.1: version "8.0.1" @@ -10193,9 +10996,9 @@ ssri@^8.0.0, ssri@^8.0.1: minipass "^3.1.1" stack-utils@^2.0.3: - version "2.0.5" - resolved "https://registry.yarnpkg.com/stack-utils/-/stack-utils-2.0.5.tgz#d25265fca995154659dbbfba3b49254778d2fdd5" - integrity sha512-xrQcmYhOsn/1kX+Vraq+7j4oE2j/6BFscZ0etmYg81xuM8Gq0022Pxb8+IqgOFUIaxHs0KaSb7T1+OegiNrNFA== + version "2.0.6" + resolved "https://registry.yarnpkg.com/stack-utils/-/stack-utils-2.0.6.tgz#aaf0748169c02fc33c8232abccf933f54a1cc34f" + integrity sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ== dependencies: escape-string-regexp "^2.0.0" @@ -10234,11 +11037,6 @@ str2buf@^1.3.0: resolved "https://registry.yarnpkg.com/str2buf/-/str2buf-1.3.0.tgz#a4172afff4310e67235178e738a2dbb573abead0" integrity sha512-xIBmHIUHYZDP4HyoXGHYNVmxlXLXDrtFHYT0eV6IOdEj3VO9ccaF1Ejl9Oq8iFjITllpT8FhaXb4KsNmw+3EuA== -stream-buffers@2.2.x: - version "2.2.0" - resolved "https://registry.yarnpkg.com/stream-buffers/-/stream-buffers-2.2.0.tgz#91d5f5130d1cef96dcfa7f726945188741d09ee4" - integrity sha512-uyQK/mx5QjHun80FLJTfaWE7JtwfRMKBLkMne6udYOmvH0CawotVa7TfgYHzAnpphn4+TweIx1QKMnRIbipmUg== - strict-uri-encode@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/strict-uri-encode/-/strict-uri-encode-2.0.0.tgz#b9c7330c7042862f6b142dc274bbcc5866ce3546" @@ -10270,23 +11068,32 @@ string-width@^1.0.1: is-fullwidth-code-point "^3.0.0" strip-ansi "^6.0.1" -string.prototype.trimend@^1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.5.tgz#914a65baaab25fbdd4ee291ca7dde57e869cb8d0" - integrity sha512-I7RGvmjV4pJ7O3kdf+LXFpVfdNOxtCW/2C8f6jNiW4+PQchwxkCDzlk1/7p+Wl4bqFIZeF47qAHXLuHHWKAxog== +string.prototype.trim@^1.2.7: + version "1.2.7" + resolved "https://registry.yarnpkg.com/string.prototype.trim/-/string.prototype.trim-1.2.7.tgz#a68352740859f6893f14ce3ef1bb3037f7a90533" + integrity sha512-p6TmeT1T3411M8Cgg9wBTMRtY2q9+PNy9EV1i2lIXUN/btt763oIfxwN3RR8VU6wHX8j/1CFy0L+YuThm6bgOg== dependencies: call-bind "^1.0.2" define-properties "^1.1.4" - es-abstract "^1.19.5" + es-abstract "^1.20.4" -string.prototype.trimstart@^1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.5.tgz#5466d93ba58cfa2134839f81d7f42437e8c01fef" - integrity sha512-THx16TJCGlsN0o6dl2o6ncWUsdgnLRSA23rRE5pyGBw/mLr3Ej/R2LaqCtgP8VNMGZsvMWnf9ooZPyY2bHvUFg== +string.prototype.trimend@^1.0.6: + version "1.0.6" + resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.6.tgz#c4a27fa026d979d79c04f17397f250a462944533" + integrity sha512-JySq+4mrPf9EsDBEDYMOb/lM7XQLulwg5R/m1r0PXEFqrV0qHvl58sdTilSXtKOflCsK2E8jxf+GKC0T07RWwQ== + dependencies: + call-bind "^1.0.2" + define-properties "^1.1.4" + es-abstract "^1.20.4" + +string.prototype.trimstart@^1.0.6: + version "1.0.6" + resolved "https://registry.yarnpkg.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.6.tgz#e90ab66aa8e4007d92ef591bbf3cd422c56bdcf4" + integrity sha512-omqjMDaY92pbn5HOX7f9IccLA+U1tA9GvtU4JrodiXFfYB7jPzzHpRzpglLAjtUV6bB557zwClJezTqnAiYnQA== dependencies: call-bind "^1.0.2" define-properties "^1.1.4" - es-abstract "^1.19.5" + es-abstract "^1.20.4" string_decoder@^1.1.1: version "1.3.0" @@ -10360,7 +11167,12 @@ strip-json-comments@~2.0.1: resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" integrity sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ== -strong-log-transformer@^2.1.0: +strnum@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/strnum/-/strnum-1.0.5.tgz#5c4e829fe15ad4ff0d20c3db5ac97b73c9b072db" + integrity sha512-J8bbNyKKXl5qYcR36TIO8W3mVGVHrmmxsd5PAItGkmyzwJvybiw2IVq5nqd0i4LSNSkB/sx9VHllbfFdr9k1JA== + +strong-log-transformer@2.1.0, strong-log-transformer@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/strong-log-transformer/-/strong-log-transformer-2.1.0.tgz#0f5ed78d325e0421ac6f90f7f10e691d6ae3ae10" integrity sha512-B3Hgul+z0L9a236FAUC9iZsL+nVHgoCJnqCbN588DjYxvGXaXaaFbfmQ/JhvKjZwsOukuR72XbHv71Qkug0HxA== @@ -10381,7 +11193,7 @@ supports-color@^5.3.0: dependencies: has-flag "^3.0.0" -supports-color@^7.0.0, supports-color@^7.1.0: +supports-color@^7.1.0: version "7.2.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== @@ -10395,23 +11207,23 @@ supports-color@^8.0.0: dependencies: has-flag "^4.0.0" -supports-hyperlinks@^2.0.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/supports-hyperlinks/-/supports-hyperlinks-2.3.0.tgz#3943544347c1ff90b15effb03fc14ae45ec10624" - integrity sha512-RpsAZlpWcDwOPQA22aCH4J0t7L8JmAvsCxfOSEwm7cQs3LshN36QaTkwd70DnBOXDWGssw2eUoc8CaRWT0XunA== - dependencies: - has-flag "^4.0.0" - supports-color "^7.0.0" - supports-preserve-symlinks-flag@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== -symbol-tree@^3.2.4: - version "3.2.4" - resolved "https://registry.yarnpkg.com/symbol-tree/-/symbol-tree-3.2.4.tgz#430637d248ba77e078883951fb9aa0eed7c63fa2" - integrity sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw== +symbol-observable@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-2.0.3.tgz#5b521d3d07a43c351055fa43b8355b62d33fd16a" + integrity sha512-sQV7phh2WCYAn81oAkakC5qjq2Ml0g8ozqz03wOGnx9dDlG1de6yrF+0RAzSJD8fPUow3PTSMf2SAbOGxb93BA== + +synckit@^0.8.5: + version "0.8.5" + resolved "https://registry.yarnpkg.com/synckit/-/synckit-0.8.5.tgz#b7f4358f9bb559437f9f167eb6bc46b3c9818fa3" + integrity sha512-L1dapNV6vu2s/4Sputv8xGsCdAVlb5nRDMFU/E27D44l5U6cw1g0dGd45uLc+OXjNMmF4ntiMdCimzcjFKQI8Q== + dependencies: + "@pkgr/utils" "^2.3.1" + tslib "^2.5.0" table-layout@^1.0.2: version "1.0.2" @@ -10423,18 +11235,35 @@ table-layout@^1.0.2: typical "^5.2.0" wordwrapjs "^4.0.0" -table@^6.0.9: - version "6.8.0" - resolved "https://registry.yarnpkg.com/table/-/table-6.8.0.tgz#87e28f14fa4321c3377ba286f07b79b281a3b3ca" - integrity sha512-s/fitrbVeEyHKFa7mFdkuQMWlH1Wgw/yEXMt5xACT4ZpzWFluehAxRtUUQKPuWhaLAWhFcVx6w3oC8VKaUfPGA== +tapable@^2.2.0: + version "2.2.1" + resolved "https://registry.yarnpkg.com/tapable/-/tapable-2.2.1.tgz#1967a73ef4060a82f12ab96af86d52fdb76eeca0" + integrity sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ== + +tar-stream@~2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-2.2.0.tgz#acad84c284136b060dc3faa64474aa9aebd77287" + integrity sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ== dependencies: - ajv "^8.0.1" - lodash.truncate "^4.4.2" - slice-ansi "^4.0.0" - string-width "^4.2.3" - strip-ansi "^6.0.1" + bl "^4.0.3" + end-of-stream "^1.4.1" + fs-constants "^1.0.0" + inherits "^2.0.3" + readable-stream "^3.1.1" + +tar@6.1.11: + version "6.1.11" + resolved "https://registry.yarnpkg.com/tar/-/tar-6.1.11.tgz#6760a38f003afa1b2ffd0ffe9e9abbd0eab3d621" + integrity sha512-an/KZQzQUkZCkuoAA64hM92X0Urb6VpRhAFllDzz44U2mcD5scmT3zBc4VgVpkugF580+DQn8eAFSyoQt0tznA== + dependencies: + chownr "^2.0.0" + fs-minipass "^2.0.0" + minipass "^3.0.0" + minizlib "^2.1.1" + mkdirp "^1.0.3" + yallist "^4.0.0" -tar@^4.4.12, tar@^4.4.13: +tar@^4.4.13: version "4.4.19" resolved "https://registry.yarnpkg.com/tar/-/tar-4.4.19.tgz#2e4d7263df26f2b914dee10c825ab132123742f3" integrity sha512-a20gEsvHnWe0ygBY8JbxoM4w3SJdhc7ZAuxkLqh+nvNQN2IOt0B5lLgM490X5Hl8FF0dl0tOf2ewFYAlIFgzVA== @@ -10447,33 +11276,27 @@ tar@^4.4.12, tar@^4.4.13: 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: - version "6.1.11" - resolved "https://registry.yarnpkg.com/tar/-/tar-6.1.11.tgz#6760a38f003afa1b2ffd0ffe9e9abbd0eab3d621" - integrity sha512-an/KZQzQUkZCkuoAA64hM92X0Urb6VpRhAFllDzz44U2mcD5scmT3zBc4VgVpkugF580+DQn8eAFSyoQt0tznA== +tar@^6.0.2, tar@^6.1.11, tar@^6.1.2: + version "6.1.13" + resolved "https://registry.yarnpkg.com/tar/-/tar-6.1.13.tgz#46e22529000f612180601a6fe0680e7da508847b" + integrity sha512-jdIBIN6LTIe2jqzay/2vtYLlBHa3JF42ot3h1dW8Q0PaAG4v8rm0cvpVePtau5C6OKXGGcgO9q2AMNSWxiLqKw== dependencies: chownr "^2.0.0" fs-minipass "^2.0.0" - minipass "^3.0.0" + minipass "^4.0.0" minizlib "^2.1.1" mkdirp "^1.0.3" yallist "^4.0.0" -temp-dir@^1.0.0: +temp-dir@1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/temp-dir/-/temp-dir-1.0.0.tgz#0a7c0ea26d3a39afa7e0ebea9c1fc0bc4daa011d" integrity sha512-xZFXEGbG7SNC3itwBzI3RYjq/cEhBkx2hJuKGIUOcEULmkQExXiHat2z/qkISYsuR+IKumhEfKKbV5qXmhICFQ== -temp-write@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/temp-write/-/temp-write-4.0.0.tgz#cd2e0825fc826ae72d201dc26eef3bf7e6fc9320" - integrity sha512-HIeWmj77uOOHb0QX7siN3OtwV3CTntquin6TNVg6SHOqCP3hYKmox90eeFOGaY1MqJ9WYDDjkyZrW6qS5AWpbw== - dependencies: - graceful-fs "^4.1.15" - is-stream "^2.0.0" - make-dir "^3.0.0" - temp-dir "^1.0.0" - uuid "^3.3.2" +temp-dir@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/temp-dir/-/temp-dir-2.0.0.tgz#bde92b05bdfeb1516e804c9c00ad45177f31321e" + integrity sha512-aoBAniQmmwtcKp/7BzsH8Cxzv8OL736p7v1ihGb5e9DJ9kTwGWHrQrVB5+lfVDzfGrdRzXch+ig7LHaY1JTOrg== temp@0.8.3: version "0.8.3" @@ -10483,20 +11306,33 @@ temp@0.8.3: os-tmpdir "^1.0.0" rimraf "~2.2.6" -temp@^0.8.1: +temp@^0.8.4: version "0.8.4" resolved "https://registry.yarnpkg.com/temp/-/temp-0.8.4.tgz#8c97a33a4770072e0a05f919396c7665a7dd59f2" integrity sha512-s0ZZzd0BzYv5tLSptZooSjK8oj6C+c19p7Vqta9+6NPOf7r+fxq0cJe6/oN4LTC79sy5NY8ucOJNgwsKCSbfqg== dependencies: rimraf "~2.6.2" -terminal-link@^2.0.0: - version "2.1.1" - resolved "https://registry.yarnpkg.com/terminal-link/-/terminal-link-2.1.1.tgz#14a64a27ab3c0df933ea546fba55f2d078edc994" - integrity sha512-un0FmiRUQNr5PJqy9kP7c40F5BOfpGlYTrxonDChEZB7pzZxRNp/bt+ymiy9/npwXya9KH99nJ/GXFIiUkYGFQ== +tempy@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/tempy/-/tempy-1.0.0.tgz#4f192b3ee3328a2684d0e3fc5c491425395aab65" + integrity sha512-eLXG5B1G0mRPHmgH2WydPl5v4jH35qEn3y/rA/aahKhIa91Pn119SsU7n7v/433gtT9ONzC8ISvNHIh2JSTm0w== dependencies: - ansi-escapes "^4.2.1" - supports-hyperlinks "^2.0.0" + del "^6.0.0" + is-stream "^2.0.0" + temp-dir "^2.0.0" + type-fest "^0.16.0" + unique-string "^2.0.0" + +terser@^5.15.0: + version "5.17.1" + resolved "https://registry.yarnpkg.com/terser/-/terser-5.17.1.tgz#948f10830454761e2eeedc6debe45c532c83fd69" + integrity sha512-hVl35zClmpisy6oaoKALOpS0rDYLxRFLHhRuDlEGTKey9qHjS1w9GMORjuwIMt70Wan4lwsLYyWDVnWgF+KUEw== + dependencies: + "@jridgewell/source-map" "^0.3.2" + acorn "^8.5.0" + commander "^2.20.0" + source-map-support "~0.5.20" test-exclude@^6.0.0: version "6.0.0" @@ -10522,11 +11358,6 @@ throat@^5.0.0: resolved "https://registry.yarnpkg.com/throat/-/throat-5.0.0.tgz#c5199235803aad18754a667d659b5e72ce16764b" integrity sha512-fcwX4mndzpLQKBS1DVYhGAcYaYt7vsHNIvQV+WXMvnow5cgjPphq5CaayLaGsjRdSCKZFNGt7/GYAuXaNOiYCA== -throat@^6.0.1: - version "6.0.1" - resolved "https://registry.yarnpkg.com/throat/-/throat-6.0.1.tgz#d514fedad95740c12c2d7fc70ea863eb51ade375" - integrity sha512-8hmiGIJMDlwjg7dlJ4yKGLK8EsYqKgPWbG3b4wjJddKNwc7N7Dpn08Df4szr/sZdMVeOstrdYSsqzX6BYbcB+w== - through2@^2.0.0, through2@^2.0.1: version "2.0.5" resolved "https://registry.yarnpkg.com/through2/-/through2-2.0.5.tgz#01c1e39eb31d07cb7d03a96a70823260b23132cd" @@ -10547,6 +11378,14 @@ through@2, "through@>=2.2.7 <3", through@^2.3.4, through@^2.3.6: resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" integrity sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg== +tiny-glob@^0.2.9: + version "0.2.9" + resolved "https://registry.yarnpkg.com/tiny-glob/-/tiny-glob-0.2.9.tgz#2212d441ac17928033b110f8b3640683129d31e2" + integrity sha512-g/55ssRPUjShh+xkfx9UPDXqhckHEsHr4Vd9zX55oSdGZc/MD0m3sferOkwWtp98bv+kcVfEHtRJgBVJzelrzg== + dependencies: + globalyzer "0.1.0" + globrex "^0.1.2" + tmp@^0.0.33: version "0.0.33" resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9" @@ -10554,6 +11393,13 @@ tmp@^0.0.33: dependencies: os-tmpdir "~1.0.2" +tmp@~0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.2.1.tgz#8457fc3037dcf4719c251367a1af6500ee1ccf14" + integrity sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ== + dependencies: + rimraf "^3.0.0" + tmpl@1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/tmpl/-/tmpl-1.0.5.tgz#8683e0b902bb9c20c4f726e3c0b69f36518c07cc" @@ -10606,54 +11452,34 @@ toml@^3.0.0: resolved "https://registry.yarnpkg.com/toml/-/toml-3.0.0.tgz#342160f1af1904ec9d204d03a5d61222d762c5ee" integrity sha512-y/mWCZinnvxjTKYhJ+pYxwD0mRLVvOtdS2Awbgxln6iEnt4rk0yBxeSBHkGJcPucRiG0e55mwWp+g/05rsrd6w== -tough-cookie@^4.0.0: - version "4.1.2" - resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-4.1.2.tgz#e53e84b85f24e0b65dd526f46628db6c85f6b874" - integrity sha512-G9fqXWoYFZgTc2z8Q5zaHy/vJMjm+WV0AkAeHxVCQiEB1b+dGvWzFW6QV07cY5jQ5gRkeid2qIkzkxUnmoQZUQ== - dependencies: - psl "^1.1.33" - punycode "^2.1.1" - universalify "^0.2.0" - url-parse "^1.5.3" - -tough-cookie@~2.5.0: - version "2.5.0" - resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.5.0.tgz#cd9fb2a0aa1d5a12b473bd9fb96fa3dcff65ade2" - integrity sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g== - dependencies: - psl "^1.1.28" - punycode "^2.1.1" - -tr46@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/tr46/-/tr46-2.1.0.tgz#fa87aa81ca5d5941da8cbf1f9b749dc969a4e240" - integrity sha512-15Ih7phfcdP5YxqiB+iDtLoaTz4Nd35+IiAv0kQ5FNKHzXgdWqPoTIqEDDJmXceQt4JZk6lVPT8lnDlPpGDppw== - dependencies: - punycode "^2.1.1" - tr46@~0.0.3: version "0.0.3" resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a" integrity sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw== +treeverse@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/treeverse/-/treeverse-3.0.0.tgz#dd82de9eb602115c6ebd77a574aae67003cb48c8" + integrity sha512-gcANaAnd2QDZFmHFEOF4k7uc1J/6a6z3DJMd/QwEyxLoKGiptJRwid582r7QIsFlFMIZ3SnxfS52S4hm2DHkuQ== + trim-newlines@^3.0.0: version "3.0.1" resolved "https://registry.yarnpkg.com/trim-newlines/-/trim-newlines-3.0.1.tgz#260a5d962d8b752425b32f3a7db0dcacd176c144" integrity sha512-c1PTsA3tYrIsLGkJkzHF+w9F2EyxfXGo4UyJc4pFL++FMjnq0HJS69T3M7d//gKrFKwy429bouPescbjecU+Zw== -ts-jest@^27.0.3: - version "27.1.5" - resolved "https://registry.yarnpkg.com/ts-jest/-/ts-jest-27.1.5.tgz#0ddf1b163fbaae3d5b7504a1e65c914a95cff297" - integrity sha512-Xv6jBQPoBEvBq/5i2TeSG9tt/nqkbpcurrEG1b+2yfBrcJelOZF9Ml6dmyMh7bcW9JyFbRYpR5rxROSlBLTZHA== +ts-jest@^29.0.5: + version "29.1.0" + resolved "https://registry.yarnpkg.com/ts-jest/-/ts-jest-29.1.0.tgz#4a9db4104a49b76d2b368ea775b6c9535c603891" + integrity sha512-ZhNr7Z4PcYa+JjMl62ir+zPiNJfXJN6E8hSLnaUKhOgqcn8vb3e537cpkd0FuAfRK3sR1LSqM1MOhliXNgOFPA== dependencies: bs-logger "0.x" fast-json-stable-stringify "2.x" - jest-util "^27.0.0" - json5 "2.x" + jest-util "^29.0.0" + json5 "^2.2.3" lodash.memoize "4.x" make-error "1.x" semver "7.x" - yargs-parser "20.x" + yargs-parser "^21.0.1" ts-node@^10.0.0, ts-node@^10.4.0: version "10.9.1" @@ -10679,13 +11505,22 @@ ts-typed-json@^0.3.2: resolved "https://registry.yarnpkg.com/ts-typed-json/-/ts-typed-json-0.3.2.tgz#f4f20f45950bae0a383857f7b0a94187eca1b56a" integrity sha512-Tdu3BWzaer7R5RvBIJcg9r8HrTZgpJmsX+1meXMJzYypbkj8NK2oJN0yvm4Dp/Iv6tzFa/L5jKRmEVTga6K3nA== -tsconfig-paths@^3.14.1, tsconfig-paths@^3.9.0: - version "3.14.1" - resolved "https://registry.yarnpkg.com/tsconfig-paths/-/tsconfig-paths-3.14.1.tgz#ba0734599e8ea36c862798e920bcf163277b137a" - integrity sha512-fxDhWnFSLt3VuTwtvJt5fpwxBHg5AdKWMsgcPOOIilyjymcYVZoCQF8fvFRezCNfblEXmi+PcM1eYHeOAgXCOQ== +tsconfig-paths@^3.14.1: + version "3.14.2" + resolved "https://registry.yarnpkg.com/tsconfig-paths/-/tsconfig-paths-3.14.2.tgz#6e32f1f79412decd261f92d633a9dc1cfa99f088" + integrity sha512-o/9iXgCYc5L/JxCHPe3Hvh8Q/2xm5Z+p18PESBU6Ff33695QnCHBEjcytY2q19ua7Mbl/DavtBOLq+oG0RCL+g== dependencies: "@types/json5" "^0.0.29" - json5 "^1.0.1" + json5 "^1.0.2" + minimist "^1.2.6" + strip-bom "^3.0.0" + +tsconfig-paths@^4.1.2: + version "4.2.0" + resolved "https://registry.yarnpkg.com/tsconfig-paths/-/tsconfig-paths-4.2.0.tgz#ef78e19039133446d244beac0fd6a1632e2d107c" + integrity sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg== + dependencies: + json5 "^2.2.2" minimist "^1.2.6" strip-bom "^3.0.0" @@ -10694,17 +11529,15 @@ tslib@^1.8.1, tslib@^1.9.0, tslib@^1.9.3: resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== -tslib@^2.0.0, tslib@^2.0.1, tslib@^2.1.0, tslib@^2.3.1, tslib@^2.4.0: - version "2.4.0" - resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.4.0.tgz#7cecaa7f073ce680a05847aa77be941098f36dc3" - integrity sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ== +tslib@^2.0.0, tslib@^2.0.1, tslib@^2.1.0, tslib@^2.3.0, tslib@^2.4.0, tslib@^2.5.0: + version "2.5.0" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.5.0.tgz#42bfed86f5787aeb41d031866c8f402429e0fddf" + integrity sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg== -tslog@^3.2.0: - version "3.3.4" - resolved "https://registry.yarnpkg.com/tslog/-/tslog-3.3.4.tgz#083197a908c97b3b714a0576b9dac293f223f368" - integrity sha512-N0HHuHE0e/o75ALfkioFObknHR5dVchUad4F0XyFf3gXJYB++DewEzwGI/uIOM216E5a43ovnRNEeQIq9qgm4Q== - dependencies: - source-map-support "^0.5.21" +tslog@^4.8.2: + version "4.8.2" + resolved "https://registry.yarnpkg.com/tslog/-/tslog-4.8.2.tgz#dbb0c96249e387e8a711ae6e077330ba1ef102c9" + integrity sha512-eAKIRjxfSKYLs06r1wT7oou6Uv9VN6NW9g0JPidBlqQwPBBl5+84dm7r8zSOPVq1kyfEw1P6B3/FLSpZCorAgA== tsutils@^3.21.0: version "3.21.0" @@ -10720,17 +11553,13 @@ tsyringe@^4.7.0: dependencies: tslib "^1.9.3" -tunnel-agent@^0.6.0: - version "0.6.0" - resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.6.0.tgz#27a5dea06b36b04a0a9966774b290868f0fc40fd" - integrity sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w== +tuf-js@^1.1.3: + version "1.1.4" + resolved "https://registry.yarnpkg.com/tuf-js/-/tuf-js-1.1.4.tgz#e85a936b16859c7fae23e5f040bc0f7b559b3192" + integrity sha512-Lw2JRM3HTYhEtQJM2Th3aNCPbnXirtWMl065BawwmM2pX6XStH/ZO9e8T2hh0zk/HUa+1i6j+Lv6eDitKTau6A== dependencies: - safe-buffer "^5.0.1" - -tweetnacl@^0.14.3, tweetnacl@~0.14.0: - version "0.14.5" - resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64" - integrity sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA== + "@tufjs/models" "1.0.3" + make-fetch-happen "^11.0.1" type-check@^0.4.0, type-check@~0.4.0: version "0.4.0" @@ -10739,18 +11568,16 @@ type-check@^0.4.0, type-check@~0.4.0: dependencies: prelude-ls "^1.2.1" -type-check@~0.3.2: - version "0.3.2" - resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.3.2.tgz#5884cab512cf1d355e3fb784f30804b2b520db72" - integrity sha512-ZCmOJdvOWDBYJlzAoFkC+Q0+bUyEOS1ltgp1MGU03fqHG+dbi9tBFU2Rd9QKiDZFAYrhPh2JUf7rZRIuHRKtOg== - dependencies: - prelude-ls "~1.1.2" - type-detect@4.0.8: version "4.0.8" resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c" integrity sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g== +type-fest@^0.16.0: + version "0.16.0" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.16.0.tgz#3240b891a78b0deae910dbeb86553e552a148860" + integrity sha512-eaBzG6MxNzEn9kiwvtre90cXaNLkmadMWa1zQMs3XORCXNbsH/OewwbxC5ia9dCxIxnTAsSxXJaa/p5y8DlvJg== + type-fest@^0.18.0: version "0.18.1" resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.18.1.tgz#db4bc151a4a2cf4eebf9add5db75508db6cc841f" @@ -10786,6 +11613,11 @@ type-fest@^0.8.1: resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.8.1.tgz#09e249ebde851d3b1e48d27c105444667f17b83d" integrity sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA== +type-fest@^3.2.0: + version "3.9.0" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-3.9.0.tgz#36a9e46e6583649f9e6098b267bc577275e9e4f4" + integrity sha512-hR8JP2e8UiH7SME5JZjsobBlEiatFoxpzCP+R3ZeCo7kAaG1jXQE5X/buLzogM6GJu8le9Y4OcfNuIQX0rZskA== + type-is@~1.6.18: version "1.6.18" resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131" @@ -10794,22 +11626,34 @@ type-is@~1.6.18: media-typer "0.3.0" mime-types "~2.1.24" -typedarray-to-buffer@^3.1.5: - version "3.1.5" - resolved "https://registry.yarnpkg.com/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz#a97ee7a9ff42691b9f783ff1bc5112fe3fca9080" - integrity sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q== +type@^1.0.1: + version "1.2.0" + resolved "https://registry.yarnpkg.com/type/-/type-1.2.0.tgz#848dd7698dafa3e54a6c479e759c4bc3f18847a0" + integrity sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg== + +type@^2.7.2: + version "2.7.2" + resolved "https://registry.yarnpkg.com/type/-/type-2.7.2.tgz#2376a15a3a28b1efa0f5350dcf72d24df6ef98d0" + integrity sha512-dzlvlNlt6AXU7EBSfpAscydQ7gXB+pPGsPnfJnZpiNJBDj7IaJzQlBZYGdEi4R9HmPdBv2XmWJ6YUtoTa7lmCw== + +typed-array-length@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/typed-array-length/-/typed-array-length-1.0.4.tgz#89d83785e5c4098bec72e08b319651f0eac9c1bb" + integrity sha512-KjZypGq+I/H7HI5HlOoGHkWUUGq+Q0TPhQurLbyrVrvnKTBgzLhIJ7j6J/XTQOi0d1RjyZ0wdas8bKs2p0x3Ng== dependencies: - is-typedarray "^1.0.0" + call-bind "^1.0.2" + for-each "^0.3.3" + is-typed-array "^1.1.9" typedarray@^0.0.6: version "0.0.6" resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" integrity sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA== -typescript@~4.3.0: - version "4.3.5" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.3.5.tgz#4d1c37cc16e893973c45a06886b7113234f119f4" - integrity sha512-DqQgihaQ9cUrskJo9kIyW/+g0Vxsk8cDtZ52a3NGh0YNTfpUSArXSohyUGnvbPazEPLu398C0UxmKSOrPumUzA== +"typescript@^3 || ^4", typescript@~4.9.4, typescript@~4.9.5: + version "4.9.5" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.9.5.tgz#095979f9bcc0d09da324d58d03ce8f8374cbe65a" + integrity sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g== typical@^4.0.0: version "4.0.0" @@ -10830,24 +11674,16 @@ uglify-es@^3.1.9: source-map "~0.6.1" uglify-js@^3.1.4: - version "3.17.2" - resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.17.2.tgz#f55f668b9a64b213977ae688703b6bbb7ca861c6" - integrity sha512-bbxglRjsGQMchfvXZNusUcYgiB9Hx2K4AHYXQy2DITZ9Rd+JzhX7+hoocE5Winr7z2oHvPsekkBwXtigvxevXg== - -uid-number@0.0.6: - version "0.0.6" - resolved "https://registry.yarnpkg.com/uid-number/-/uid-number-0.0.6.tgz#0ea10e8035e8eb5b8e4449f06da1c730663baa81" - integrity sha512-c461FXIljswCuscZn67xq9PpszkPT6RjheWFQTgCyabJrTUozElanb0YEqv2UGgk247YpcJkFBuSGNvBlpXM9w== - -ultron@1.0.x: - version "1.0.2" - resolved "https://registry.yarnpkg.com/ultron/-/ultron-1.0.2.tgz#ace116ab557cd197386a4e88f4685378c8b2e4fa" - integrity sha512-QMpnpVtYaWEeY+MwKDN/UdKlE/LsFZXM5lO1u7GaZzNgmIbGixHEmVMIKT+vqYOALu3m5GYQy9kz4Xu4IVn7Ow== + version "3.17.4" + resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.17.4.tgz#61678cf5fa3f5b7eb789bb345df29afb8257c22c" + integrity sha512-T9q82TJI9e/C1TAxYvfb16xO120tMVFZrGA3f9/P4424DNu6ypK103y0GPFVa17yotwSyZW5iYXgjYHkGrJW/g== -umask@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/umask/-/umask-1.1.0.tgz#f29cebf01df517912bb58ff9c4e50fde8e33320d" - integrity sha512-lE/rxOhmiScJu9L6RTNVgB/zZbF+vGC0/p6D3xnkAePI2o0sMyFG966iR5Ki50OI/0mNi2yaRnxfLsPmEZF/JA== +uint8arrays@^3.0.0, uint8arrays@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/uint8arrays/-/uint8arrays-3.1.1.tgz#2d8762acce159ccd9936057572dade9459f65ae0" + integrity sha512-+QJa8QRnbdXVpHYjLoTpJIdCTiw9Ir62nocClWuXIq2JIh4Uta0cQsTSpFL678p2CN8B+XSApwcU+pQEqVpKWg== + dependencies: + multiformats "^9.4.2" unbox-primitive@^1.0.2: version "1.0.2" @@ -10872,10 +11708,10 @@ unicode-match-property-ecmascript@^2.0.0: unicode-canonical-property-names-ecmascript "^2.0.0" unicode-property-aliases-ecmascript "^2.0.0" -unicode-match-property-value-ecmascript@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.0.0.tgz#1a01aa57247c14c568b89775a54938788189a714" - integrity sha512-7Yhkc0Ye+t4PNYzOGKedDhXbYIBe1XEQYQxOPyhcXNMJ0WCABqqj6ckydd6pWRZTHV4GuCPKdBAUiMc60tsKVw== +unicode-match-property-value-ecmascript@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.1.0.tgz#cb5fffdcd16a05124f5a4b0bf7c3770208acbbe0" + integrity sha512-qxkjQt6qjg/mYscYMC0XKRn3Rh0wFPlfxB0xkt9CfyTvpX1Ra0+rAmdX2QyAobptSEvuy4RtpPRui6XkV+8wjA== unicode-property-aliases-ecmascript@^2.0.0: version "2.1.0" @@ -10899,6 +11735,20 @@ unique-filename@^1.1.1: dependencies: unique-slug "^2.0.0" +unique-filename@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/unique-filename/-/unique-filename-2.0.1.tgz#e785f8675a9a7589e0ac77e0b5c34d2eaeac6da2" + integrity sha512-ODWHtkkdx3IAR+veKxFV+VBkUMcN+FaqzUUd7IZzt+0zhDZFPFxhlqwPF3YQvMHx1TD0tdgYl+kuPnJ8E6ql7A== + dependencies: + unique-slug "^3.0.0" + +unique-filename@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/unique-filename/-/unique-filename-3.0.0.tgz#48ba7a5a16849f5080d26c760c86cf5cf05770ea" + integrity sha512-afXhuC55wkAmZ0P18QsVE6kp8JaxrEokN2HGIoIVv2ijHQd419H0+6EigAFcIzXeMIkcIkNBpB3L/DXB3cTS/g== + dependencies: + unique-slug "^4.0.0" + unique-slug@^2.0.0: version "2.0.2" resolved "https://registry.yarnpkg.com/unique-slug/-/unique-slug-2.0.2.tgz#baabce91083fc64e945b0f3ad613e264f7cd4e6c" @@ -10906,6 +11756,27 @@ unique-slug@^2.0.0: dependencies: imurmurhash "^0.1.4" +unique-slug@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/unique-slug/-/unique-slug-3.0.0.tgz#6d347cf57c8a7a7a6044aabd0e2d74e4d76dc7c9" + integrity sha512-8EyMynh679x/0gqE9fT9oilG+qEt+ibFyqjuVTsZn1+CMxH+XLlpvr2UZx4nVcCwTpx81nICr2JQFkM+HPLq4w== + dependencies: + imurmurhash "^0.1.4" + +unique-slug@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/unique-slug/-/unique-slug-4.0.0.tgz#6bae6bb16be91351badd24cdce741f892a6532e3" + integrity sha512-WrcA6AyEfqDX5bWige/4NQfPZMtASNVxdmWR76WESYQVAACSgWcR6e9i0mofqqBxYFtL4oAxPIptY73/0YE1DQ== + dependencies: + imurmurhash "^0.1.4" + +unique-string@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/unique-string/-/unique-string-2.0.0.tgz#39c6451f81afb2749de2b233e3f7c5e8843bd89d" + integrity sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg== + dependencies: + crypto-random-string "^2.0.0" + universal-user-agent@^6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/universal-user-agent/-/universal-user-agent-6.0.0.tgz#3381f8503b251c0d9cd21bc1de939ec9df5480ee" @@ -10916,11 +11787,6 @@ universalify@^0.1.0: resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66" integrity sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg== -universalify@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.2.0.tgz#6451760566fa857534745ab1dde952d1b1761be0" - integrity sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg== - universalify@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/universalify/-/universalify-2.0.0.tgz#75a4984efedc4b08975c5aeb73f530d02df25717" @@ -10939,15 +11805,15 @@ unset-value@^1.0.0: has-value "^0.3.1" isobject "^3.0.0" -upath@^2.0.1: +upath@2.0.1, upath@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/upath/-/upath-2.0.1.tgz#50c73dea68d6f6b990f51d279ce6081665d61a8b" integrity sha512-1uEe95xksV1O0CYKXo8vQvN1JEbtJp7lb7C5U9HMsIp6IVwntkH/oNUzyVNQSd4S1sYk2FpSSW44FqMc8qee5w== -update-browserslist-db@^1.0.9: - version "1.0.9" - resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.0.9.tgz#2924d3927367a38d5c555413a7ce138fc95fcb18" - integrity sha512-/xsqn21EGVdXI3EXSum1Yckj3ZVZugqyOZQ/CxYPBD/R+ko9NSUScf8tFF4dOKY+2pvSSJA/S+5B8s4Zr4kyvg== +update-browserslist-db@^1.0.10: + version "1.0.11" + resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.0.11.tgz#9a2a641ad2907ae7b3616506f4b977851db5b940" + integrity sha512-dCwEFf0/oT85M1fHBg4F0jtLwJrutGoHSQXCh7u4o2t1drG+c0a9Flnqww6XUKSfQMPpJBRjU8d4RXB09qtvaA== dependencies: escalade "^3.1.1" picocolors "^1.0.0" @@ -10964,22 +11830,7 @@ urix@^0.1.0: resolved "https://registry.yarnpkg.com/urix/-/urix-0.1.0.tgz#da937f7a62e21fec1fd18d49b35c2935067a6c72" integrity sha512-Am1ousAhSLBeB9cG/7k7r2R0zj50uDRlZHPGbazid5s9rlF1F/QKYObEKSIunSjIOkJZqwRRLpvewjEkM7pSqg== -url-parse@^1.5.3: - version "1.5.10" - resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.5.10.tgz#9d3c2f736c1d75dd3bd2be507dcc111f1e2ea9c1" - integrity sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ== - dependencies: - querystringify "^2.1.1" - requires-port "^1.0.0" - -use-subscription@^1.0.0: - version "1.8.0" - resolved "https://registry.yarnpkg.com/use-subscription/-/use-subscription-1.8.0.tgz#f118938c29d263c2bce12fc5585d3fe694d4dbce" - integrity sha512-LISuG0/TmmoDoCRmV5XAqYkd3UCBNM0ML3gGBndze65WITcsExCD3DTvXXTLyNcOC0heFQZzluW88bN/oC1DQQ== - dependencies: - use-sync-external-store "^1.2.0" - -use-sync-external-store@^1.2.0: +use-sync-external-store@^1.0.0: version "1.2.0" resolved "https://registry.yarnpkg.com/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz#7dbefd6ef3fe4e767a0cf5d7287aacfb5846928a" integrity sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA== @@ -10994,53 +11845,46 @@ utf8@^3.0.0: resolved "https://registry.yarnpkg.com/utf8/-/utf8-3.0.0.tgz#f052eed1364d696e769ef058b183df88c87f69d1" integrity sha512-E8VjFIQ/TyQgp+TZfS6l8yp/xWppSAHzidGiRrqe4bK4XP9pTRyKFgGJpO3SN7zdX4DeomTrwaseCHovfpFcqQ== -util-deprecate@^1.0.1, util-deprecate@~1.0.1: +util-deprecate@^1.0.1, util-deprecate@^1.0.2, util-deprecate@~1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw== -util-promisify@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/util-promisify/-/util-promisify-2.1.0.tgz#3c2236476c4d32c5ff3c47002add7c13b9a82a53" - integrity sha512-K+5eQPYs14b3+E+hmE2J6gCZ4JmMl9DbYS6BeP2CHq6WMuNxErxf5B/n0fz85L8zUuoO6rIzNNmIQDu/j+1OcA== - dependencies: - object.getownpropertydescriptors "^2.0.3" - utils-merge@1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713" integrity sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA== -uuid@^3.3.2: - version "3.4.0" - resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee" - integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A== - -uuid@^8.3.2: +uuid@8.3.2: version "8.3.2" resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2" integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg== +uuid@^9.0.0: + version "9.0.0" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-9.0.0.tgz#592f550650024a38ceb0c562f2f6aa435761efb5" + integrity sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg== + v8-compile-cache-lib@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz#6336e8d71965cb3d35a1bbb7868445a7c05264bf" integrity sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg== -v8-compile-cache@^2.0.3: +v8-compile-cache@2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz#2de19618c66dc247dcfb6f99338035d8245a2cee" integrity sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA== -v8-to-istanbul@^8.1.0: - version "8.1.1" - resolved "https://registry.yarnpkg.com/v8-to-istanbul/-/v8-to-istanbul-8.1.1.tgz#77b752fd3975e31bbcef938f85e9bd1c7a8d60ed" - integrity sha512-FGtKtv3xIpR6BYhvgH8MI/y78oT7d8Au3ww4QIxymrCtZEh5b8gCw2siywE+puhEmuWKDtmfrvF5UlB298ut3w== +v8-to-istanbul@^9.0.1: + version "9.1.0" + resolved "https://registry.yarnpkg.com/v8-to-istanbul/-/v8-to-istanbul-9.1.0.tgz#1b83ed4e397f58c85c266a570fc2558b5feb9265" + integrity sha512-6z3GW9x8G1gd+JIIgQQQxXuiJtCXeAjp6RaPEPLv62mH3iPHPxV6W3robxtCzNErRo6ZwTmzWhsbNvjyEBKzKA== dependencies: + "@jridgewell/trace-mapping" "^0.3.12" "@types/istanbul-lib-coverage" "^2.0.1" convert-source-map "^1.6.0" - source-map "^0.7.3" -validate-npm-package-license@^3.0.1, validate-npm-package-license@^3.0.4: +validate-npm-package-license@3.0.4, validate-npm-package-license@^3.0.1, validate-npm-package-license@^3.0.4: version "3.0.4" resolved "https://registry.yarnpkg.com/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz#fc91f6b9c7ba15c857f4cb2c5defeec39d4f410a" integrity sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew== @@ -11048,6 +11892,13 @@ validate-npm-package-license@^3.0.1, validate-npm-package-license@^3.0.4: spdx-correct "^3.0.0" spdx-expression-parse "^3.0.0" +validate-npm-package-name@4.0.0, validate-npm-package-name@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/validate-npm-package-name/-/validate-npm-package-name-4.0.0.tgz#fe8f1c50ac20afdb86f177da85b3600f0ac0d747" + integrity sha512-mzR0L8ZDktZjpX4OB46KT+56MAhl4EIazWP/+G/HPGuvfdaqg4YsCdtOm6U9+LOFyYDoh4dpnpxZRB9MQQns5Q== + dependencies: + builtins "^5.0.0" + validate-npm-package-name@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/validate-npm-package-name/-/validate-npm-package-name-3.0.0.tgz#5fa912d81eb7d0c74afc140de7317f0ca7df437e" @@ -11055,10 +11906,17 @@ validate-npm-package-name@^3.0.0: dependencies: builtins "^1.0.3" -validator@^13.5.2: - version "13.7.0" - resolved "https://registry.yarnpkg.com/validator/-/validator-13.7.0.tgz#4f9658ba13ba8f3d82ee881d3516489ea85c0857" - integrity sha512-nYXQLCBkpJ8X6ltALua9dRrZDHVYxjJ1wgskNt1lH9fzGjs3tgojGSCBjmEPwkWS1y29+DrizMTW19Pr9uB2nw== +validate-npm-package-name@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/validate-npm-package-name/-/validate-npm-package-name-5.0.0.tgz#f16afd48318e6f90a1ec101377fa0384cfc8c713" + integrity sha512-YuKoXDAhBYxY7SfOKxHBDoSyENFeW5VvIIQp2TGQuit8gpK6MnWaQelBKxso72DoxTZfZdcP3W90LqpSkgPzLQ== + dependencies: + builtins "^5.0.0" + +validator@^13.7.0: + version "13.9.0" + resolved "https://registry.yarnpkg.com/validator/-/validator-13.9.0.tgz#33e7b85b604f3bbce9bb1a05d5c3e22e1c2ff855" + integrity sha512-B+dGG8U3fdtM0/aNK4/X8CXq/EcxU2WPrPEkJGslb47qyHsxmbggTWK0yEA4qnYVNF+nxNlN88o14hIcPmSIEA== varint@^6.0.0: version "6.0.0" @@ -11070,35 +11928,17 @@ vary@^1, vary@~1.1.2: resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" integrity sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg== -verror@1.10.0: - version "1.10.0" - resolved "https://registry.yarnpkg.com/verror/-/verror-1.10.0.tgz#3a105ca17053af55d6e270c1f8288682e18da400" - integrity sha512-ZZKSmDAEFOijERBLkmYfJ+vmk3w+7hOLYDNkRCuRuMJGEmqYNCNLyBBFwWKVMhfwaEF3WOd0Zlw86U/WC/+nYw== - dependencies: - assert-plus "^1.0.0" - core-util-is "1.0.2" - extsprintf "^1.2.0" - vlq@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/vlq/-/vlq-1.0.1.tgz#c003f6e7c0b4c1edd623fd6ee50bbc0d6a1de468" integrity sha512-gQpnTgkubC6hQgdIcRdYGDSDc+SaujOdyesZQMv6JlfQee/9Mp0Qhnys6WxDWvQnL5WZdT7o2Ul187aSt0Rq+w== -w3c-hr-time@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz#0a89cdf5cc15822df9c360543676963e0cc308cd" - integrity sha512-z8P5DvDNjKDoFIHK7q8r8lackT6l+jo/Ye3HOle7l9nICP9lf1Ci25fy9vHd0JOWewkIFzXIEig3TdKT7JQ5fQ== - dependencies: - browser-process-hrtime "^1.0.0" - -w3c-xmlserializer@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/w3c-xmlserializer/-/w3c-xmlserializer-2.0.0.tgz#3e7104a05b75146cc60f564380b7f683acf1020a" - integrity sha512-4tzD0mF8iSiMiNs30BiLO3EpfGLZUT2MSX/G+o7ZywDzliWQ3OPtTZ0PTC3B3ca1UAf4cJMHB+2Bf56EriJuRA== - dependencies: - xml-name-validator "^3.0.0" +walk-up-path@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/walk-up-path/-/walk-up-path-1.0.0.tgz#d4745e893dd5fd0dbb58dd0a4c6a33d9c9fec53e" + integrity sha512-hwj/qMDUEjCU5h0xr90KGCf0tg0/LgJbmOWgrWKYlcJZM7XvquvUJZ0G/HMGr7F7OQMOUuPHWP9JpriinkAlkg== -walker@^1.0.7, walker@~1.0.5: +walker@^1.0.7, walker@^1.0.8: version "1.0.8" resolved "https://registry.yarnpkg.com/walker/-/walker-1.0.8.tgz#bd498db477afe573dc04185f011d3ab8a8d7653f" integrity sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ== @@ -11112,20 +11952,20 @@ wcwidth@^1.0.0, wcwidth@^1.0.1: dependencies: defaults "^1.0.3" -web-did-resolver@^2.0.8: - version "2.0.20" - resolved "https://registry.yarnpkg.com/web-did-resolver/-/web-did-resolver-2.0.20.tgz#22e053b0f8bc1f4ab03da05989ce934852b7623f" - integrity sha512-qGcrm01B+ytCZUYhxH0mGOk0Ldf67kXUXLsNth6F3sx3fhUKNSIE8D+MnMFRugQm7j87mDHqUTDLmW9c90g3nw== +web-did-resolver@^2.0.21: + version "2.0.23" + resolved "https://registry.yarnpkg.com/web-did-resolver/-/web-did-resolver-2.0.23.tgz#59806a8bc6f5709403929a3d2b49c06279580632" + integrity sha512-7yOKnY9E322cVFfVkpV6g2j7QWB3H32aezGn2VagBmTAQr74zf0hxRN0p/PzK/kcgnc/oDCIRuiWUGwJEJAh0w== dependencies: cross-fetch "^3.1.5" did-resolver "^4.0.0" -webcrypto-core@^1.7.4: - version "1.7.5" - resolved "https://registry.yarnpkg.com/webcrypto-core/-/webcrypto-core-1.7.5.tgz#c02104c953ca7107557f9c165d194c6316587ca4" - integrity sha512-gaExY2/3EHQlRNNNVSrbG2Cg94Rutl7fAaKILS1w8ZDhGxdFOaw6EbCfHIxPy9vt/xwp5o0VQAx9aySPF6hU1A== +webcrypto-core@^1.7.7: + version "1.7.7" + resolved "https://registry.yarnpkg.com/webcrypto-core/-/webcrypto-core-1.7.7.tgz#06f24b3498463e570fed64d7cab149e5437b162c" + integrity sha512-7FjigXNsBfopEj+5DV2nhNpfic2vumtjjgPmeDKk45z+MJwXKKfhPB7118Pfzrmh4jqOMST6Ch37iPAHoImg5g== dependencies: - "@peculiar/asn1-schema" "^2.1.6" + "@peculiar/asn1-schema" "^2.3.6" "@peculiar/json-schema" "^1.1.12" asn1js "^3.0.1" pvtsutils "^1.3.2" @@ -11141,33 +11981,11 @@ webidl-conversions@^3.0.0: resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871" integrity sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ== -webidl-conversions@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-5.0.0.tgz#ae59c8a00b121543a2acc65c0434f57b0fc11aff" - integrity sha512-VlZwKPCkYKxQgeSbH5EyngOmRp7Ww7I9rQLERETtf5ofd9pGeswWiOtogpEO850jziPRarreGxn5QIiTqpb2wA== - -webidl-conversions@^6.1.0: - version "6.1.0" - resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-6.1.0.tgz#9111b4d7ea80acd40f5270d666621afa78b69514" - integrity sha512-qBIvFLGiBpLjfwmYAaHPXsn+ho5xZnGvyGvsarywGNc8VyQJUMHJ8OBKGGrPER0okBeMDaan4mNBlgBROxuI8w== - -whatwg-encoding@^1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/whatwg-encoding/-/whatwg-encoding-1.0.5.tgz#5abacf777c32166a51d085d6b4f3e7d27113ddb0" - integrity sha512-b5lim54JOPN9HtzvK9HFXvBma/rnfFeqsic0hSpjtDbVxR3dJKLc+KB4V6GgiGOvl7CY/KNh8rxSo9DKQrnUEw== - dependencies: - iconv-lite "0.4.24" - whatwg-fetch@^3.0.0: version "3.6.2" resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-3.6.2.tgz#dced24f37f2624ed0281725d51d0e2e3fe677f8c" integrity sha512-bJlen0FcuU/0EMLrdbJ7zOnW6ITZLrZMIarMUVmdKtsGvZna8vxKYaexICWPfZ8qwf9fzNq+UEIZrnSaApt6RA== -whatwg-mimetype@^2.3.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz#3d4b1e0312d2079879f826aff18dbeeca5960fbf" - integrity sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g== - whatwg-url@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-5.0.0.tgz#966454e8765462e37644d3626f6742ce8b70965d" @@ -11176,15 +11994,6 @@ whatwg-url@^5.0.0: tr46 "~0.0.3" webidl-conversions "^3.0.0" -whatwg-url@^8.0.0, whatwg-url@^8.4.0, whatwg-url@^8.5.0: - version "8.7.0" - resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-8.7.0.tgz#656a78e510ff8f3937bc0bcbe9f5c0ac35941b77" - integrity sha512-gAojqb/m9Q8a5IV96E3fHJM70AzCkgt4uXYX2O7EmuyOnLrViCQlsEBmF9UQIu3/aeAIp2U17rtbpZWNntQqdg== - dependencies: - lodash "^4.7.0" - tr46 "^2.1.0" - webidl-conversions "^6.1.0" - which-boxed-primitive@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz#13757bc89b209b049fe5d86430e21cf40a89a8e6" @@ -11197,11 +12006,23 @@ which-boxed-primitive@^1.0.2: is-symbol "^1.0.3" which-module@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.0.tgz#d9ef07dce77b9902b8a3a8fa4b31c3e3f7e6e87a" - integrity sha512-B+enWhmw6cjfVC7kS8Pj9pCrKSc5txArRyaYGe088shv/FGWH+0Rjx/xPgtsWfsUtS27FkP697E4DDhgrgoc0Q== + version "2.0.1" + resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.1.tgz#776b1fe35d90aebe99e8ac15eb24093389a4a409" + integrity sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ== + +which-typed-array@^1.1.9: + version "1.1.9" + resolved "https://registry.yarnpkg.com/which-typed-array/-/which-typed-array-1.1.9.tgz#307cf898025848cf995e795e8423c7f337efbde6" + integrity sha512-w9c4xkx6mPidwp7180ckYWfMmvxpjlZuIudNtDf4N/tTAUB8VJbX25qZoAsrtGuYNnGw3pa0AXgbGKRB8/EceA== + dependencies: + available-typed-arrays "^1.0.5" + call-bind "^1.0.2" + for-each "^0.3.3" + gopd "^1.0.1" + has-tostringtag "^1.0.0" + is-typed-array "^1.1.10" -which@^1.2.9, which@^1.3.1: +which@^1.2.9: version "1.3.1" resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a" integrity sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ== @@ -11215,6 +12036,13 @@ which@^2.0.1, which@^2.0.2: dependencies: isexe "^2.0.0" +which@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/which/-/which-3.0.0.tgz#a9efd016db59728758a390d23f1687b6e8f59f8e" + integrity sha512-nla//68K9NU6yRiwDY/Q8aU6siKlSs64aEC7+IV56QoAuyQT2ovsJcgGYGyqMOmI/CGN1BOR6mM5EN0FBO+zyQ== + dependencies: + isexe "^2.0.0" + wide-align@^1.1.0, wide-align@^1.1.2, wide-align@^1.1.5: version "1.1.5" resolved "https://registry.yarnpkg.com/wide-align/-/wide-align-1.1.5.tgz#df1d4c206854369ecf3c9a4898f1b23fbd9d15d3" @@ -11222,7 +12050,7 @@ wide-align@^1.1.0, wide-align@^1.1.2, wide-align@^1.1.5: dependencies: string-width "^1.0.2 || 2 || 3 || 4" -word-wrap@^1.2.3, word-wrap@~1.2.3: +word-wrap@^1.2.3: version "1.2.3" resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c" integrity sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ== @@ -11263,6 +12091,14 @@ wrappy@1: resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== +write-file-atomic@4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-4.0.1.tgz#9faa33a964c1c85ff6f849b80b42a88c2c537c8f" + integrity sha512-nSKUxgAbyioruk6hU87QzVbY279oYT6uiwgDoujth2ju4mJ+TZau7SQBhtbTmUyuNYTuXnSyRn66FV0+eCgcrQ== + dependencies: + imurmurhash "^0.1.4" + signal-exit "^3.0.7" + write-file-atomic@^2.3.0, write-file-atomic@^2.4.2: version "2.4.3" resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-2.4.3.tgz#1fd2e9ae1df3e75b8d8c367443c692d4ca81f481" @@ -11272,15 +12108,21 @@ write-file-atomic@^2.3.0, write-file-atomic@^2.4.2: imurmurhash "^0.1.4" signal-exit "^3.0.2" -write-file-atomic@^3.0.0, write-file-atomic@^3.0.3: - version "3.0.3" - resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-3.0.3.tgz#56bd5c5a5c70481cd19c571bd39ab965a5de56e8" - integrity sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q== +write-file-atomic@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-4.0.2.tgz#a9df01ae5b77858a027fd2e80768ee433555fcfd" + integrity sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg== dependencies: imurmurhash "^0.1.4" - is-typedarray "^1.0.0" - signal-exit "^3.0.2" - typedarray-to-buffer "^3.1.5" + signal-exit "^3.0.7" + +write-file-atomic@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-5.0.0.tgz#54303f117e109bf3d540261125c8ea5a7320fab0" + integrity sha512-R7NYMnHSlV42K54lwY9lvW6MnSm1HSJqZL3xiSgi9E7//FYaI74r2G0rd+/X6VAMkHEdzxQaU5HUOXWUz5kA/w== + dependencies: + imurmurhash "^0.1.4" + signal-exit "^3.0.7" write-json-file@^3.2.0: version "3.2.0" @@ -11294,19 +12136,7 @@ write-json-file@^3.2.0: sort-keys "^2.0.0" write-file-atomic "^2.4.2" -write-json-file@^4.3.0: - version "4.3.0" - resolved "https://registry.yarnpkg.com/write-json-file/-/write-json-file-4.3.0.tgz#908493d6fd23225344af324016e4ca8f702dd12d" - integrity sha512-PxiShnxf0IlnQuMYOPPhPkhExoCQuTUNPOa/2JWCYTmBquU9njyyDuwRKN26IZBlp4yn1nt+Agh2HOOBl+55HQ== - dependencies: - detect-indent "^6.0.0" - graceful-fs "^4.1.15" - is-plain-obj "^2.0.0" - make-dir "^3.0.0" - sort-keys "^4.0.0" - write-file-atomic "^3.0.0" - -write-pkg@^4.0.0: +write-pkg@4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/write-pkg/-/write-pkg-4.0.0.tgz#675cc04ef6c11faacbbc7771b24c0abbf2a20039" integrity sha512-v2UQ+50TNf2rNHJ8NyWttfm/EJUBWMJcx6ZTYZr6Qp52uuegWw/lBkCtCbnYZEmPRNL61m+u67dAmGxo+HTULA== @@ -11315,55 +12145,30 @@ write-pkg@^4.0.0: type-fest "^0.4.1" write-json-file "^3.2.0" -ws@^1.1.0, ws@^1.1.5: - version "1.1.5" - resolved "https://registry.yarnpkg.com/ws/-/ws-1.1.5.tgz#cbd9e6e75e09fc5d2c90015f21f0c40875e0dd51" - integrity sha512-o3KqipXNUdS7wpQzBHSe180lBGO60SoK0yVo3CYJgb2MkobuWuBX6dhkYP5ORCLd55y+SaflMOV5fqAB53ux4w== - dependencies: - options ">=0.0.5" - ultron "1.0.x" - -ws@^6.1.4: +ws@^6.2.2: version "6.2.2" resolved "https://registry.yarnpkg.com/ws/-/ws-6.2.2.tgz#dd5cdbd57a9979916097652d78f1cc5faea0c32e" integrity sha512-zmhltoSR8u1cnDsD43TX59mzoMZsLKqUweyYBAIvTngR3shc0W6aOZylZmq/7hqyVxPdi+5Ud2QInblgyE72fw== dependencies: async-limiter "~1.0.0" -ws@^7, ws@^7.4.6, ws@^7.5.3: +ws@^7, ws@^7.5.1: version "7.5.9" resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.9.tgz#54fa7db29f4c7cec68b1ddd3a89de099942bb591" integrity sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q== -xcode@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/xcode/-/xcode-2.1.0.tgz#bab64a7e954bb50ca8d19da7e09531c65a43ecfe" - integrity sha512-uCrmPITrqTEzhn0TtT57fJaNaw8YJs1aCzs+P/QqxsDbvPZSv7XMPPwXrKvHtD6pLjBM/NaVwraWJm8q83Y4iQ== - dependencies: - simple-plist "^1.0.0" - uuid "^3.3.2" - -xml-name-validator@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/xml-name-validator/-/xml-name-validator-3.0.0.tgz#6ae73e06de4d8c6e47f9fb181f78d648ad457c6a" - integrity sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw== - -xmlbuilder@^15.1.1: - version "15.1.1" - resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-15.1.1.tgz#9dcdce49eea66d8d10b42cae94a79c3c8d0c2ec5" - integrity sha512-yMqGBqtXyeN1e3TGYvgNgDVZ3j84W4cwkOXQswghol6APgZWaff9lnbvN7MHYJOiXsvGPXtjTYJEiC9J2wv9Eg== +ws@^8.13.0: + version "8.13.0" + resolved "https://registry.yarnpkg.com/ws/-/ws-8.13.0.tgz#9a9fb92f93cf41512a0735c8f4dd09b8a1211cd0" + integrity sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA== -xmlchars@^2.2.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/xmlchars/-/xmlchars-2.2.0.tgz#060fe1bcb7f9c76fe2a17db86a9bc3ab894210cb" - integrity sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw== - -xmldoc@^1.1.2: - version "1.2.0" - resolved "https://registry.yarnpkg.com/xmldoc/-/xmldoc-1.2.0.tgz#7554371bfd8c138287cff01841ae4566d26e5541" - integrity sha512-2eN8QhjBsMW2uVj7JHLHkMytpvGHLHxKXBy4J3fAT/HujsEtM6yU84iGjpESYGHg6XwK0Vu4l+KgqQ2dv2cCqg== +xstream@^11.14.0: + version "11.14.0" + resolved "https://registry.yarnpkg.com/xstream/-/xstream-11.14.0.tgz#2c071d26b18310523b6877e86b4e54df068a9ae5" + integrity sha512-1bLb+kKKtKPbgTK6i/BaoAn03g47PpFstlbe1BA+y3pNS/LfvcaghS5BFf9+EE1J+KwSQsEpfJvFN5GqFtiNmw== dependencies: - sax "^1.2.4" + globalthis "^1.0.1" + symbol-observable "^2.0.3" xtend@~4.0.1: version "4.0.2" @@ -11380,7 +12185,7 @@ y18n@^5.0.5: resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55" integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA== -yallist@^3.0.0, yallist@^3.1.1: +yallist@^3.0.0, yallist@^3.0.2, yallist@^3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd" integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g== @@ -11395,15 +12200,20 @@ yaml@^1.10.0: resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.10.2.tgz#2301c5ffbf12b467de8da2333a459e29e7920e4b" integrity sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg== +yaml@^2.1.3: + version "2.2.2" + resolved "https://registry.yarnpkg.com/yaml/-/yaml-2.2.2.tgz#ec551ef37326e6d42872dad1970300f8eb83a073" + integrity sha512-CBKFWExMn46Foo4cldiChEzn7S7SRV+wqiluAb6xmueD/fGyRHIhX8m14vVGgeFWjN540nKCNVj6P21eQjgTuA== + yargs-parser@20.2.4: version "20.2.4" resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.4.tgz#b42890f14566796f85ae8e3a25290d205f154a54" integrity sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA== -yargs-parser@20.x, yargs-parser@^20.2.2, yargs-parser@^20.2.3: - version "20.2.9" - resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.9.tgz#2eb7dc3b0289718fc295f362753845c41a0c94ee" - integrity sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w== +yargs-parser@21.1.1, yargs-parser@^21.0.1, yargs-parser@^21.1.1: + version "21.1.1" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-21.1.1.tgz#9096bceebf990d21bb31fa9516e0ede294a77d35" + integrity sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw== yargs-parser@^18.1.2: version "18.1.3" @@ -11413,7 +12223,25 @@ yargs-parser@^18.1.2: camelcase "^5.0.0" decamelize "^1.2.0" -yargs@^15.1.0, yargs@^15.3.1: +yargs-parser@^20.2.2, yargs-parser@^20.2.3: + version "20.2.9" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.9.tgz#2eb7dc3b0289718fc295f362753845c41a0c94ee" + integrity sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w== + +yargs@16.2.0, yargs@^16.2.0: + version "16.2.0" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-16.2.0.tgz#1c82bf0f6b6a66eafce7ef30e376f49a12477f66" + integrity sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw== + dependencies: + cliui "^7.0.2" + escalade "^3.1.1" + get-caller-file "^2.0.5" + require-directory "^2.1.1" + string-width "^4.2.0" + y18n "^5.0.5" + yargs-parser "^20.2.2" + +yargs@^15.1.0: version "15.4.1" resolved "https://registry.yarnpkg.com/yargs/-/yargs-15.4.1.tgz#0d87a16de01aee9d8bec2bfbf74f67851730f4f8" integrity sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A== @@ -11430,18 +12258,18 @@ yargs@^15.1.0, yargs@^15.3.1: y18n "^4.0.0" yargs-parser "^18.1.2" -yargs@^16.2.0: - version "16.2.0" - resolved "https://registry.yarnpkg.com/yargs/-/yargs-16.2.0.tgz#1c82bf0f6b6a66eafce7ef30e376f49a12477f66" - integrity sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw== +yargs@^17.3.1, yargs@^17.5.1, yargs@^17.6.2: + version "17.7.1" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.7.1.tgz#34a77645201d1a8fc5213ace787c220eabbd0967" + integrity sha512-cwiTb08Xuv5fqF4AovYacTFNxk62th7LKJ6BL9IGUpTJrWoU7/7WdQGTP2SjKf1dUNBGzDd28p/Yfs/GI6JrLw== dependencies: - cliui "^7.0.2" + cliui "^8.0.1" escalade "^3.1.1" get-caller-file "^2.0.5" require-directory "^2.1.1" - string-width "^4.2.0" + string-width "^4.2.3" y18n "^5.0.5" - yargs-parser "^20.2.2" + yargs-parser "^21.1.1" yn@3.1.1: version "3.1.1"