diff --git a/.github/actions/iota-sandbox/tear-down/action.yml b/.github/actions/iota-sandbox/tear-down/action.yml index 8a0da1906e..c8e6225d0b 100644 --- a/.github/actions/iota-sandbox/tear-down/action.yml +++ b/.github/actions/iota-sandbox/tear-down/action.yml @@ -7,6 +7,6 @@ runs: shell: bash run: | cd iota-sandbox/sandbox - docker-compose down + docker compose down cd ../.. sudo rm -rf iota-sandbox diff --git a/.github/actions/publish/publish-wasm/action.yml b/.github/actions/publish/publish-wasm/action.yml index cfaedea939..6bb1171fbd 100644 --- a/.github/actions/publish/publish-wasm/action.yml +++ b/.github/actions/publish/publish-wasm/action.yml @@ -24,7 +24,7 @@ runs: registry-url: 'https://registry.npmjs.org' - name: Download bindings/wasm artifacts - uses: actions/download-artifact@v2 + uses: actions/download-artifact@v4 with: name: ${{ inputs.input-artifact-name }} path: bindings/wasm diff --git a/.github/actions/rust/rust-setup/action.yml b/.github/actions/rust/rust-setup/action.yml index b7b16a352a..5f783a98cc 100644 --- a/.github/actions/rust/rust-setup/action.yml +++ b/.github/actions/rust/rust-setup/action.yml @@ -48,7 +48,16 @@ runs: shell: bash run: | - if ! rustup self update; then + # self update is currently broken on Windows runners: + # https://github.com/rust-lang/rustup/issues/3709 + # so we'll skip self update for windows + OS=${{ inputs.os }} + IS_WINDOWS=false; [[ $OS =~ ^[wW]indows ]] && IS_WINDOWS=true + + if [[ $IS_WINDOWS = true ]] ; + then + echo "skipping self update on windows runner due to https://github.com/rust-lang/rustup/issues/3709" + elif ! rustup self update; then echo "rustup self update failed" fi @@ -57,7 +66,13 @@ runs: rustup target add $TARGET fi - rustup update + if [[ $IS_WINDOWS = true ]] ; + then + echo "skipping self update on windows runner due to https://github.com/rust-lang/rustup/issues/3709" + rustup update --no-self-update + else + rustup update + fi TOOLCHAIN=${{ inputs.toolchain }} if [[ $TOOLCHAIN != 'stable' ]]; then diff --git a/.github/workflows/build-and-test-grpc.yml b/.github/workflows/build-and-test-grpc.yml index 80311728c8..2a561bd952 100644 --- a/.github/workflows/build-and-test-grpc.yml +++ b/.github/workflows/build-and-test-grpc.yml @@ -38,4 +38,4 @@ jobs: context: . file: bindings/grpc/Dockerfile push: false - labels: iotaledger/identity-grpc:latest \ No newline at end of file + tags: iotaledger/identity-grpc:latest \ No newline at end of file diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index a58316790f..c92432e36f 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -82,6 +82,12 @@ jobs: steps: - uses: actions/checkout@v3 + - name: Ensure, OpenSSL is available in Windows + if: matrix.os == 'windows-latest' + run: | + echo "VCPKG_ROOT=$env:VCPKG_INSTALLATION_ROOT" | Out-File -FilePath $env:GITHUB_ENV -Append + vcpkg install openssl:x64-windows-static-md + - name: Setup Rust and cache uses: './.github/actions/rust/rust-setup' with: @@ -120,15 +126,18 @@ jobs: run: cargo clean # Build the library, tests, and examples without running them to avoid recompilation in the run tests step - - name: Build with all features - run: cargo build --workspace --tests --examples --all-features --release + - name: Build with default features + run: cargo build --workspace --tests --examples --release - name: Start iota sandbox if: matrix.os == 'ubuntu-latest' uses: './.github/actions/iota-sandbox/setup' - - name: Run tests - run: cargo test --workspace --all-features --release + - name: Run tests excluding `custom_time` feature + run: cargo test --workspace --release + + - name: Run tests with `custom_time` feature + run: cargo test --test custom_time --features="custom_time" - name: Run Rust examples # run examples only on ubuntu for now @@ -151,7 +160,7 @@ jobs: - name: Tear down iota sandbox if: matrix.os == 'ubuntu-latest' && always() uses: './.github/actions/iota-sandbox/tear-down' - + - name: Stop sccache uses: './.github/actions/rust/sccache/stop-sccache' with: @@ -189,7 +198,7 @@ jobs: working-directory: bindings/wasm - name: Download bindings/wasm artifacts - uses: actions/download-artifact@v2 + uses: actions/download-artifact@v4 with: name: identity-wasm-bindings-build path: bindings/wasm @@ -198,9 +207,105 @@ jobs: uses: './.github/actions/iota-sandbox/setup' - name: Run Wasm examples - run: npm run test:examples + run: npm run test:readme && npm run test:node working-directory: bindings/wasm - name: Tear down iota sandbox if: always() uses: './.github/actions/iota-sandbox/tear-down' + + test-wasm-firefox: + needs: build-wasm + if: ${{ needs.check-for-run-condition.outputs.should-run == 'true' }} + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + os: [ ubuntu-latest ] + include: + - os: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + + - name: Set up Node.js + uses: actions/setup-node@v1 + with: + node-version: 16.x + + - name: Install JS dependencies + run: npm ci + working-directory: bindings/wasm + + - name: Download bindings/wasm artifacts + uses: actions/download-artifact@v4 + with: + name: identity-wasm-bindings-build + path: bindings/wasm + + - name: Start iota sandbox + uses: './.github/actions/iota-sandbox/setup' + + - name: Build Docker image + uses: docker/build-push-action@v6.2.0 + with: + context: bindings/wasm/ + file: bindings/wasm/cypress/Dockerfile + push: false + tags: cypress-test:latest + load: true + + - name: Run cypress + run: docker run --network host cypress-test test:browser:parallel:firefox + + - name: Tear down iota sandbox + if: always() + uses: './.github/actions/iota-sandbox/tear-down' + + test-wasm-chrome: + needs: build-wasm + if: ${{ needs.check-for-run-condition.outputs.should-run == 'true' }} + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + os: [ ubuntu-latest ] + include: + - os: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + + - name: Set up Node.js + uses: actions/setup-node@v1 + with: + node-version: 16.x + + - name: Install JS dependencies + run: npm ci + working-directory: bindings/wasm + + - name: Download bindings/wasm artifacts + uses: actions/download-artifact@v4 + with: + name: identity-wasm-bindings-build + path: bindings/wasm + + - name: Start iota sandbox + uses: './.github/actions/iota-sandbox/setup' + + - name: Build Docker image + uses: docker/build-push-action@v6.2.0 + with: + context: bindings/wasm/ + file: bindings/wasm/cypress/Dockerfile + push: false + tags: cypress-test:latest + load: true + + - name: Run cypress + run: docker run --network host cypress-test test:browser:parallel:chrome + + - name: Tear down iota sandbox + if: always() + uses: './.github/actions/iota-sandbox/tear-down' diff --git a/.github/workflows/grpc-publish-to-dockerhub.yml b/.github/workflows/grpc-publish-to-dockerhub.yml index d72fe20702..348bf8c564 100644 --- a/.github/workflows/grpc-publish-to-dockerhub.yml +++ b/.github/workflows/grpc-publish-to-dockerhub.yml @@ -39,14 +39,15 @@ jobs: context: . file: bindings/grpc/Dockerfile push: ${{ !inputs.dry-run }} - labels: iotaledger/identity-grpc:${{ inputs.tag }} + tags: iotaledger/identity-grpc:${{ inputs.tag }} - name: Docker Hub Description uses: peter-evans/dockerhub-description@e98e4d1628a5f3be2be7c231e50981aee98723ae + if: ${{ !inputs.dry-run }} with: username: ${{ secrets.IOTALEDGER_DOCKER_USERNAME }} password: ${{ secrets.IOTALEDGER_DOCKER_PASSWORD }} repository: iotaledger/identity-grpc - readme-filepath: ./bindigns/grpc/README.md + readme-filepath: ./bindings/grpc/README.md short-description: ${{ github.event.repository.description }} diff --git a/.github/workflows/shared-build-wasm.yml b/.github/workflows/shared-build-wasm.yml index 8f39769dfd..263e1da66c 100644 --- a/.github/workflows/shared-build-wasm.yml +++ b/.github/workflows/shared-build-wasm.yml @@ -82,12 +82,13 @@ jobs: os: ${{matrix.os}} - name: Upload artifact - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v4 with: name: ${{ inputs.output-artifact-name }} path: | bindings/wasm/node bindings/wasm/web bindings/wasm/examples/dist + bindings/wasm/docs if-no-files-found: error retention-days: 1 diff --git a/.github/workflows/upload-docs.yml b/.github/workflows/upload-docs.yml new file mode 100644 index 0000000000..5ce1429767 --- /dev/null +++ b/.github/workflows/upload-docs.yml @@ -0,0 +1,51 @@ +name: Build and upload API docs + +on: + release: + types: [published] + workflow_dispatch: + inputs: + version: + description: 'Version to publish docs under (e.g. `v1.2.3-dev.1`)' + required: true + +permissions: + actions: 'write' + +jobs: + build-wasm: + # owner/repository of workflow has to be static, see https://github.community/t/env-variables-in-uses/17466 + uses: iotaledger/identity.rs/.github/workflows/shared-build-wasm.yml@main + with: + run-unit-tests: false + ref: ${{ inputs.ref }} + output-artifact-name: identity-docs + + upload-docs: + runs-on: ubuntu-latest + needs: build-wasm + steps: + - uses: actions/download-artifact@v4 + with: + name: identity-docs + - name: Get release version + id: get_release_version + run: | + if [ "${{ github.event_name }}" = "release" ]; then + INPUT_VERSION="${{ github.ref }}" + else + INPUT_VERSION="${{ github.event.inputs.version }}" + fi + VERSION=$(echo $INPUT_VERSION | sed -e 's/.*v\([0-9]*\.[0-9]*\).*/\1/') + echo VERSION=$VERSION >> $GITHUB_OUTPUT + - name: Compress generated docs + run: | + tar czvf wasm.tar.gz docs/* + + - name: Upload docs to AWS S3 + env: + AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID_IOTA_WIKI }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY_IOTA_WIKI }} + AWS_DEFAULT_REGION: "eu-central-1" + run: | + aws s3 cp wasm.tar.gz s3://files.iota.org/iota-wiki/iota-identity/${{ steps.get_release_version.outputs.VERSION }}/ --acl public-read diff --git a/.license_template b/.license_template index 30334ddc0c..a437281e00 100644 --- a/.license_template +++ b/.license_template @@ -1,2 +1,2 @@ -// Copyright {20\d{2}(-20\d{2})?} IOTA Stiftung +// Copyright {20\d{2}(-20\d{2})?} IOTA Stiftung{(?:, .+)?} // SPDX-License-Identifier: Apache-2.0 diff --git a/CHANGELOG.md b/CHANGELOG.md index 979805a0d4..ab1b36f03b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,17 +1,57 @@ # Changelog +## [v1.4.0](https://github.com/iotaledger/identity.rs/tree/v1.4.0) (2024-09-23) + +[Full Changelog](https://github.com/iotaledger/identity.rs/compare/v1.3.1...v1.4.0) + +### Added + +- Add feature to support custom `now_utc` implementations [\#1397](https://github.com/iotaledger/identity.rs/pull/1397) +- Add support for `did:jwk` resolution [\#1404](https://github.com/iotaledger/identity.rs/pull/1404) +- Linked Verifiable Presentations [\#1398](https://github.com/iotaledger/identity.rs/pull/1398) +- Add support for custom JWS algorithms [\#1410](https://github.com/iotaledger/identity.rs/pull/1410) + +### Patch + +- Make `bls12_381_plus` dependency more flexible again [\#1393](https://github.com/iotaledger/identity.rs/pull/1393) +- Mark `js-sys` as optional for identity_core [\#1405](https://github.com/iotaledger/identity.rs/pull/1405) +- Remove dependency on `identity_core` default features [\#1408](https://github.com/iotaledger/identity.rs/pull/1408) + +## [v1.3.1](https://github.com/iotaledger/identity.rs/tree/v1.3.1) (2024-06-12) + +[Full Changelog](https://github.com/iotaledger/identity.rs/compare/v1.3.0...v1.3.1) + +### Patch + +- Pin and bump `bls12_381_plus` dependency [\#1378](https://github.com/iotaledger/identity.rs/pull/1378) + +## [v1.3.0](https://github.com/iotaledger/identity.rs/tree/v1.3.0) (2024-05-28) + +[Full Changelog](https://github.com/iotaledger/identity.rs/compare/v1.2.0...v1.3.0) + +### Added + +- Add ZK BBS+-based selectively disclosable credentials \(JPT\) [\#1355](https://github.com/iotaledger/identity.rs/pull/1355) +- Add EcDSA verifier [\#1353](https://github.com/iotaledger/identity.rs/pull/1353) + +### Patch + +- Support for specification-compliant verification method type `JsonWebKey2020` [\#1367](https://github.com/iotaledger/identity.rs/pull/1367) + ## [v1.2.0](https://github.com/iotaledger/identity.rs/tree/v1.2.0) (2024-03-27) [Full Changelog](https://github.com/iotaledger/identity.rs/compare/v1.1.1...v1.2.0) ### Added -- Add `get_public_key` for `StrongholdStorage` [\#1311](https://github.com/iotaledger/identity.rs/pull/1311) -- Support multiple IOTA networks in the Resolver [\#1304](https://github.com/iotaledger/identity.rs/pull/1304) -- Allow setting additional controllers for `IotaDocument` [\#1314](https://github.com/iotaledger/identity.rs/pull/1314) -- use latest release of sd-jwt-payload `IotaDocument` [\#1333](https://github.com/iotaledger/identity.rs/pull/1333) + - Allow arbitrary verification methods [\#1334](https://github.com/iotaledger/identity.rs/pull/1334) +- use latest release of sd-jwt-payload [\#1333](https://github.com/iotaledger/identity.rs/pull/1333) +- Allow setting additional controllers for `IotaDocument` [\#1314](https://github.com/iotaledger/identity.rs/pull/1314) +- Add `get_public_key` for `StrongholdStorage` [\#1311](https://github.com/iotaledger/identity.rs/pull/1311) +- Support multiple IOTA networks in the `Resolver` [\#1304](https://github.com/iotaledger/identity.rs/pull/1304) ### Patch + - Support %-encoded characters in DID method id [\#1303](https://github.com/iotaledger/identity.rs/pull/1303) ## [v1.1.1](https://github.com/iotaledger/identity.rs/tree/v1.1.1) (2024-02-19) diff --git a/Cargo.toml b/Cargo.toml index 96862c133d..bbf08bd310 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,3 +1,11 @@ +[workspace.package] +authors = ["IOTA Stiftung"] +edition = "2021" +homepage = "https://www.iota.org" +license = "Apache-2.0" +repository = "https://github.com/iotaledger/identity.rs" +rust-version = "1.65" + [workspace] resolver = "2" members = [ @@ -12,6 +20,7 @@ members = [ "identity_verification", "identity_stronghold", "identity_jose", + "identity_ecdsa_verifier", "identity_eddsa_verifier", "examples", ] @@ -19,18 +28,13 @@ members = [ exclude = ["bindings/wasm", "bindings/grpc"] [workspace.dependencies] +bls12_381_plus = { version = "0.8.17" } serde = { version = "1.0", default-features = false, features = ["alloc", "derive"] } serde_json = { version = "1.0", default-features = false } strum = { version = "0.25", default-features = false, features = ["std", "derive"] } thiserror = { version = "1.0", default-features = false } - -[workspace.package] -authors = ["IOTA Stiftung"] -edition = "2021" -homepage = "https://www.iota.org" -license = "Apache-2.0" -repository = "https://github.com/iotaledger/identity.rs" -rust-version = "1.65" +json-proof-token = { version = "0.3.5" } +zkryptium = { version = "0.2.2", default-features = false, features = ["bbsplus"] } [workspace.lints.clippy] result_large_err = "allow" diff --git a/README.md b/README.md index 72d0f89c2d..ef2d8cb15e 100644 --- a/README.md +++ b/README.md @@ -54,7 +54,7 @@ If you want to include IOTA Identity in your project, simply add it as a depende ```toml [dependencies] -identity_iota = { version = "1.2.0" } +identity_iota = { version = "1.4.0" } ``` To try out the [examples](https://github.com/iotaledger/identity.rs/blob/HEAD/examples), you can also do this: @@ -88,7 +88,7 @@ version = "1.0.0" edition = "2021" [dependencies] -identity_iota = { version = "1.2.0", features = ["memstore"] } +identity_iota = { version = "1.4.0", features = ["memstore"] } iota-sdk = { version = "1.0.2", default-features = true, features = ["tls", "client", "stronghold"] } tokio = { version = "1", features = ["full"] } anyhow = "1.0.62" diff --git a/bindings/grpc/README.md b/bindings/grpc/README.md index 814e82a7f8..f94f0add17 100644 --- a/bindings/grpc/README.md +++ b/bindings/grpc/README.md @@ -1,7 +1,7 @@ # Identity.rs gRPC Bindings This project provides the functionalities of [Identity.rs](https://github.com/iotaledger/identity.rs) in a language-agnostic way through a [gRPC](https://grpc.io) server. -The server can easily be run with docker using [this dockerfile](https://github.com/iotaledger/identity.rs/blob/grpc-bindings/bindings/grpc/Dockerfile). +The server can easily be run with docker using [this dockerfile](https://github.com/iotaledger/identity.rs/blob/main/bindings/grpc/Dockerfile). ## Build Run `docker build -f bindings/grpc/Dockerfile -t iotaleger/identity-grpc .` from the project root. @@ -17,17 +17,17 @@ Make sure to provide a valid stronghold snapshot at the provided `SNAPSHOT_PATH` ### Available services | Service description | Service Id | Proto File | | ------------------------------------------------------------------------------ | ------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------| -| Credential Revocation Checking | `credentials/CredentialRevocation.check` | [credentials.proto](https://github.com/iotaledger/identity.rs/blob/grpc-bindings/bindings/grpc/proto/credentials.proto) | -| SD-JWT Validation | `sd_jwt/Verification.verify` | [sd_jwt.proto](https://github.com/iotaledger/identity.rs/blob/grpc-bindings/bindings/grpc/proto/sd_jwt.proto) | -| Credential JWT creation | `credentials/Jwt.create` | [credentials.proto](https://github.com/iotaledger/identity.rs/blob/grpc-bindings/bindings/grpc/proto/credentials.proto) | -| Credential JWT validation | `credentials/VcValidation.validate` | [credentials.proto](https://github.com/iotaledger/identity.rs/blob/grpc-bindings/bindings/grpc/proto/credentials.proto) | -| DID Document Creation | `document/DocumentService.create` | [document.proto](https://github.com/iotaledger/identity.rs/blob/grpc-bindings/bindings/grpc/proto/document.proto) | -| Domain Linkage - validate domain, let server fetch did-configuration | `domain_linkage/DomainLinkage.validate_domain` | [domain_linkage.proto](https://github.com/iotaledger/identity.rs/blob/grpc-bindings/bindings/grpc/proto/domain_linkage.proto) | -| Domain Linkage - validate domain, pass did-configuration to service | `domain_linkage/DomainLinkage.validate_domain_against_did_configuration` | [domain_linkage.proto](https://github.com/iotaledger/identity.rs/blob/grpc-bindings/bindings/grpc/proto/domain_linkage.proto) | -| Domain Linkage - validate endpoints in DID, let server fetch did-configuration | `domain_linkage/DomainLinkage.validate_did` | [domain_linkage.proto](https://github.com/iotaledger/identity.rs/blob/grpc-bindings/bindings/grpc/proto/domain_linkage.proto) | -| Domain Linkage - validate endpoints in DID, pass did-configuration to service | `domain_linkage/DomainLinkage.validate_did_against_did_configurations` | [domain_linkage.proto](https://github.com/iotaledger/identity.rs/blob/grpc-bindings/bindings/grpc/proto/domain_linkage.proto) | -| `StatusList2021Credential` creation | `status_list_2021/StatusList2021Svc.create` | [status_list_2021.proto](https://github.com/iotaledger/identity.rs/blob/grpc-bindings/bindings/grpc/proto/status_list_2021.proto) | -| `StatusList2021Credential` update | `status_list_2021/StatusList2021Svc.update` | [status_list_2021.proto](https://github.com/iotaledger/identity.rs/blob/grpc-bindings/bindings/grpc/proto/status_list_2021.proto) | +| Credential Revocation Checking | `credentials/CredentialRevocation.check` | [credentials.proto](https://github.com/iotaledger/identity.rs/blob/main/bindings/grpc/proto/credentials.proto) | +| SD-JWT Validation | `sd_jwt/Verification.verify` | [sd_jwt.proto](https://github.com/iotaledger/identity.rs/blob/main/bindings/grpc/proto/sd_jwt.proto) | +| Credential JWT creation | `credentials/Jwt.create` | [credentials.proto](https://github.com/iotaledger/identity.rs/blob/main/bindings/grpc/proto/credentials.proto) | +| Credential JWT validation | `credentials/VcValidation.validate` | [credentials.proto](https://github.com/iotaledger/identity.rs/blob/main/bindings/grpc/proto/credentials.proto) | +| DID Document Creation | `document/DocumentService.create` | [document.proto](https://github.com/iotaledger/identity.rs/blob/main/bindings/grpc/proto/document.proto) | +| Domain Linkage - validate domain, let server fetch did-configuration | `domain_linkage/DomainLinkage.validate_domain` | [domain_linkage.proto](https://github.com/iotaledger/identity.rs/blob/main/bindings/grpc/proto/domain_linkage.proto) | +| Domain Linkage - validate domain, pass did-configuration to service | `domain_linkage/DomainLinkage.validate_domain_against_did_configuration` | [domain_linkage.proto](https://github.com/iotaledger/identity.rs/blob/main/bindings/grpc/proto/domain_linkage.proto) | +| Domain Linkage - validate endpoints in DID, let server fetch did-configuration | `domain_linkage/DomainLinkage.validate_did` | [domain_linkage.proto](https://github.com/iotaledger/identity.rs/blob/main/bindings/grpc/proto/domain_linkage.proto) | +| Domain Linkage - validate endpoints in DID, pass did-configuration to service | `domain_linkage/DomainLinkage.validate_did_against_did_configurations` | [domain_linkage.proto](https://github.com/iotaledger/identity.rs/blob/main/bindings/grpc/proto/domain_linkage.proto) | +| `StatusList2021Credential` creation | `status_list_2021/StatusList2021Svc.create` | [status_list_2021.proto](https://github.com/iotaledger/identity.rs/blob/main/bindings/grpc/proto/status_list_2021.proto) | +| `StatusList2021Credential` update | `status_list_2021/StatusList2021Svc.update` | [status_list_2021.proto](https://github.com/iotaledger/identity.rs/blob/main/bindings/grpc/proto/status_list_2021.proto) | ## Testing @@ -62,7 +62,7 @@ In order to test domain linkage, you need access to a server that is reachable v 1. for convenience, you can find a script to start the HTTP server, that you can adjust in `tooling/start-http-server.sh`, don't forget to insert your static domain or to remove the `--domain` parameter #### Domain linkage credential -1. copy the public url and insert it into [6_domain_linkage.rs](../../examples/1_advanced/6_domain_linkage.rs) as domain 1, e.g. `let domain_1: Url = Url::parse("https://0d40-2003-d3-2710-e200-485f-e8bb-7431-79a7.ngrok-free.app")?;` +1. copy the public url and insert it into [6_domain_linkage.rs](https://github.com/iotaledger/identity.rs/blob/main/examples/1_advanced/6_domain_linkage.rs) as domain 1, e.g. `let domain_1: Url = Url::parse("https://0d40-2003-d3-2710-e200-485f-e8bb-7431-79a7.ngrok-free.app")?;` .1 run the example with `cargo run --release --example 6_domain_linkage` #### GRPC server diff --git a/bindings/grpc/proto/domain_linkage.proto b/bindings/grpc/proto/domain_linkage.proto index f2fe3426df..5edee5e18a 100644 --- a/bindings/grpc/proto/domain_linkage.proto +++ b/bindings/grpc/proto/domain_linkage.proto @@ -4,6 +4,52 @@ syntax = "proto3"; package domain_linkage; +message ValidateDidResponse { + string did = 1; + message Domains { + message ValidDomain { + string url = 1; + string credential = 2; + string service_id = 3; + } + + repeated ValidDomain valid = 1; + + message InvalidDomain { + string url = 1; + optional string credential = 2; + string service_id = 3; + string error = 4; + } + + repeated InvalidDomain invalid = 2; + } + Domains domains = 2; +} + +message ValidateDomainResponse { + string domain = 1; + message LinkedDids { + message ValidDid { + string did = 1; + string credential = 2; + string service_id = 3; + } + + repeated ValidDid valid = 1; + + message InvalidDid { + optional string did = 1; + optional string credential = 2; + optional string service_id = 3; + string error = 4; + } + + repeated InvalidDid invalid = 2; + } + LinkedDids linked_dids = 2; +} + message ValidateDomainRequest { // domain to validate string domain = 1; @@ -16,27 +62,6 @@ message ValidateDomainAgainstDidConfigurationRequest { string did_configuration = 2; } -message LinkedDidValidationStatus { - // validation succeeded or not, `error` property is added for `false` cases - bool valid = 1; - // credential from `linked_dids` as compact JWT domain linkage credential if it could be retrieved - optional string document = 2; - // an error message, that occurred when validated, omitted if valid - optional string error = 3; -} - -message ValidateDomainResponse { - // list of JWT domain linkage credential, uses the same order as the `did-configuration.json` file for domain - repeated LinkedDidValidationStatus linked_dids = 1; -} - -message LinkedDidEndpointValidationStatus { - // id of service endpoint entry - string id = 1; - // list of JWT domain linkage credential, uses the same order as the `did-configuration.json` file for domain - repeated LinkedDidValidationStatus service_endpoint = 2; -} - message ValidateDidRequest { // DID to validate string did = 1; @@ -49,15 +74,13 @@ message ValidateDidAgainstDidConfigurationsRequest { repeated ValidateDomainAgainstDidConfigurationRequest did_configurations = 2; } -message ValidateDidResponse { - // mapping of service entries from DID with validation status for endpoint URLs - repeated LinkedDidEndpointValidationStatus service = 1; -} - service DomainLinkage { rpc validate_domain(ValidateDomainRequest) returns (ValidateDomainResponse); - rpc validate_domain_against_did_configuration(ValidateDomainAgainstDidConfigurationRequest) returns (ValidateDomainResponse); + rpc validate_domain_against_did_configuration( + ValidateDomainAgainstDidConfigurationRequest) + returns (ValidateDomainResponse); rpc validate_did(ValidateDidRequest) returns (ValidateDidResponse); - rpc validate_did_against_did_configurations(ValidateDidAgainstDidConfigurationsRequest) returns (ValidateDidResponse); + rpc validate_did_against_did_configurations( + ValidateDidAgainstDidConfigurationsRequest) returns (ValidateDidResponse); } \ No newline at end of file diff --git a/bindings/grpc/proto/utils.proto b/bindings/grpc/proto/utils.proto new file mode 100644 index 0000000000..87ea3f7054 --- /dev/null +++ b/bindings/grpc/proto/utils.proto @@ -0,0 +1,23 @@ +// Copyright 2020-2024 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +syntax = "proto3"; +package utils; + +message DataSigningRequest { + // Raw data that will be signed. + bytes data = 1; + // Signing key's ID. + string key_id = 2; +} + +message DataSigningResponse { + // Raw data signature. + bytes signature = 1; +} + +// Service that handles signing operations on raw data. +service Signing { + rpc sign(DataSigningRequest) returns (DataSigningResponse); +} + diff --git a/bindings/grpc/rustfmt.toml b/bindings/grpc/rustfmt.toml new file mode 100644 index 0000000000..c0842c2114 --- /dev/null +++ b/bindings/grpc/rustfmt.toml @@ -0,0 +1,8 @@ +comment_width = 120 +format_code_in_doc_comments = true +max_width = 120 +normalize_comments = false +normalize_doc_attributes = false +tab_spaces = 2 +wrap_comments = true +imports_granularity = "Item" diff --git a/bindings/grpc/src/main.rs b/bindings/grpc/src/main.rs index 7db360aa4c..82650cabfa 100644 --- a/bindings/grpc/src/main.rs +++ b/bindings/grpc/src/main.rs @@ -3,6 +3,7 @@ use std::str::FromStr; +use anyhow::Context; use identity_grpc::server::GRpcServer; use identity_stronghold::StrongholdStorage; use iota_sdk::client::stronghold::StrongholdAdapter; @@ -37,11 +38,18 @@ async fn main() -> anyhow::Result<()> { #[tracing::instrument] fn init_stronghold() -> anyhow::Result { - let stronghold_password = std::env::var("STRONGHOLD_PWD")?; - let snapshot_path = std::env::var("SNAPSHOT_PATH")?; + use std::env; + use std::fs; + let stronghold_password = env::var("STRONGHOLD_PWD_FILE") + .context("Unset \"STRONGHOLD_PWD_FILE\" env variable") + .and_then(|path| fs::read_to_string(&path).context(format!("{path} does not exists"))) + .map(sanitize_pwd) + .or(env::var("STRONGHOLD_PWD")) + .context("No password for stronghold was provided")?; + let snapshot_path = env::var("SNAPSHOT_PATH")?; // Check for snapshot file at specified path - let metadata = std::fs::metadata(&snapshot_path)?; + let metadata = fs::metadata(&snapshot_path)?; if !metadata.is_file() { return Err(anyhow::anyhow!("No snapshot at provided path \"{}\"", &snapshot_path)); } @@ -53,3 +61,11 @@ fn init_stronghold() -> anyhow::Result { .map(StrongholdStorage::new)?, ) } + +/// Remove any trailing whitespace in-place. +fn sanitize_pwd(mut pwd: String) -> String { + let trimmed = pwd.trim_end(); + pwd.truncate(trimmed.len()); + pwd.shrink_to_fit(); + pwd +} diff --git a/bindings/grpc/src/services/document.rs b/bindings/grpc/src/services/document.rs index db50702c4a..31b57f9aab 100644 --- a/bindings/grpc/src/services/document.rs +++ b/bindings/grpc/src/services/document.rs @@ -7,17 +7,21 @@ use _document::CreateDidRequest; use _document::CreateDidResponse; use identity_iota::core::ToJson; use identity_iota::iota::IotaDID; +use identity_iota::iota::IotaDocument; +use identity_iota::iota::StateMetadataDocument; use identity_iota::iota::StateMetadataEncoding; -use identity_iota::iota::{IotaDocument, StateMetadataDocument}; use identity_iota::storage::JwkDocumentExt; use identity_iota::storage::JwkStorageDocumentError; use identity_iota::storage::Storage; use identity_iota::verification::jws::JwsAlgorithm; use identity_iota::verification::MethodScope; -use identity_storage::{KeyId, KeyStorageErrorKind, StorageSigner}; +use identity_storage::KeyId; +use identity_storage::KeyStorageErrorKind; +use identity_storage::StorageSigner; use identity_stronghold::StrongholdStorage; use identity_stronghold::ED25519_KEY_TYPE; -use identity_sui_name_tbd::client::{IdentityClient, IdentityClientReadOnly}; +use identity_sui_name_tbd::client::IdentityClient; +use identity_sui_name_tbd::client::IdentityClientReadOnly; use identity_sui_name_tbd::transaction::Transaction; use tonic::Code; use tonic::Request; diff --git a/bindings/grpc/src/services/domain_linkage.rs b/bindings/grpc/src/services/domain_linkage.rs index 6c094af91e..560495c628 100644 --- a/bindings/grpc/src/services/domain_linkage.rs +++ b/bindings/grpc/src/services/domain_linkage.rs @@ -4,18 +4,20 @@ use std::collections::HashMap; use std::error::Error; -use domain_linkage::domain_linkage_server::DomainLinkage; -use domain_linkage::domain_linkage_server::DomainLinkageServer; -use domain_linkage::LinkedDidEndpointValidationStatus; -use domain_linkage::LinkedDidValidationStatus; -use domain_linkage::ValidateDidAgainstDidConfigurationsRequest; -use domain_linkage::ValidateDidRequest; -use domain_linkage::ValidateDidResponse; -use domain_linkage::ValidateDomainAgainstDidConfigurationRequest; -use domain_linkage::ValidateDomainRequest; -use domain_linkage::ValidateDomainResponse; -use futures::stream::FuturesOrdered; -use futures::TryStreamExt; +use _domain_linkage::domain_linkage_server::DomainLinkage; +use _domain_linkage::domain_linkage_server::DomainLinkageServer; +use _domain_linkage::validate_did_response::domains::InvalidDomain; +use _domain_linkage::validate_did_response::domains::ValidDomain; +use _domain_linkage::validate_did_response::Domains; +use _domain_linkage::validate_domain_response::linked_dids::InvalidDid; +use _domain_linkage::validate_domain_response::linked_dids::ValidDid; +use _domain_linkage::validate_domain_response::LinkedDids; +use _domain_linkage::ValidateDidAgainstDidConfigurationsRequest; +use _domain_linkage::ValidateDidRequest; +use _domain_linkage::ValidateDidResponse; +use _domain_linkage::ValidateDomainAgainstDidConfigurationRequest; +use _domain_linkage::ValidateDomainRequest; +use _domain_linkage::ValidateDomainResponse; use identity_eddsa_verifier::EdDSAJwsVerifier; use identity_iota::core::FromJson; use identity_iota::core::Url; @@ -36,8 +38,7 @@ use tonic::Response; use tonic::Status; use url::Origin; -#[allow(clippy::module_inception)] -mod domain_linkage { +mod _domain_linkage { tonic::include_proto!("domain_linkage"); } @@ -90,11 +91,15 @@ impl DomainValidationConfig { } /// Builds a validation status for a failed validation from an `Error`. -fn get_validation_failed_status(message: &str, err: &impl Error) -> LinkedDidValidationStatus { - LinkedDidValidationStatus { - valid: false, - document: None, - error: Some(format!("{}; {}", message, &err.to_string())), +fn get_linked_did_validation_failed_status(message: &str, err: &impl Error) -> LinkedDids { + LinkedDids { + valid: vec![], + invalid: vec![InvalidDid { + service_id: None, + credential: None, + did: None, + error: format!("{}: {}", message, err), + }], } } @@ -121,8 +126,7 @@ impl DomainLinkageService { &self, did: &IotaDID, did_configurations: Option>, - ) -> Result, DomainLinkageError> { - // fetch DID document for given DID + ) -> Result { let did_document = self .resolver .resolve(did) @@ -144,36 +148,66 @@ impl DomainLinkageService { None => HashMap::new(), }; - // check validation for all services and endpoints in them - let mut service_futures = FuturesOrdered::new(); + let mut futures = vec![]; + for service in services { - let service_id: CoreDID = did.clone().into(); + let service_id = service.id().to_string(); let domains: Vec = service.domains().into(); - let local_config_map = config_map.clone(); - service_futures.push_back(async move { - let mut domain_futures = FuturesOrdered::new(); - for domain in domains { - let config = local_config_map.get(&domain.origin()).map(|value| value.to_owned()); - domain_futures.push_back(self.validate_domains_with_optional_configuration( - domain.clone(), - Some(did.clone().into()), - config, - )); + for domain in domains { + let config = config_map.get(&domain.origin()).cloned(); + let service_id_clone = service_id.clone(); + futures.push({ + let domain = domain.clone(); + async move { + let result = self + .validate_domains_with_optional_configuration(&domain, Some(did.clone().into()), config) + .await; + + (service_id_clone, domain, result) + } + }); + } + } + + let results = futures::future::join_all(futures).await; + + let mut valid_domains = vec![]; + let mut invalid_domains = vec![]; + + for (service_id, domain, result) in results { + match result { + Ok(status) => { + status.valid.iter().for_each(|valid| { + valid_domains.push(ValidDomain { + service_id: service_id.to_string(), + url: domain.to_string(), + credential: valid.credential.clone(), + }); + }); + status.invalid.iter().for_each(|invalid| { + invalid_domains.push(InvalidDomain { + service_id: service_id.to_string(), + credential: invalid.credential.clone(), + url: domain.to_string(), + error: invalid.error.clone(), + }); + }); } - domain_futures - .try_collect::>>() - .await - .map(|value| LinkedDidEndpointValidationStatus { - id: service_id.to_string(), - service_endpoint: value.into_iter().flatten().collect(), - }) - }); + Err(err) => { + invalid_domains.push(InvalidDomain { + service_id: service_id.to_string(), + credential: None, + url: domain.to_string(), + error: err.to_string(), + }); + } + } } - let endpoint_validation_status = service_futures - .try_collect::>() - .await?; - Ok(endpoint_validation_status) + Ok(Domains { + valid: valid_domains, + invalid: invalid_domains, + }) } /// Validates domain linkage for given origin. @@ -186,10 +220,10 @@ impl DomainLinkageService { /// origin async fn validate_domains_with_optional_configuration( &self, - domain: Url, + domain: &Url, did: Option, config: Option, - ) -> Result, DomainLinkageError> { + ) -> Result { // get domain linkage config let domain_linkage_configuration: DomainLinkageConfiguration = if let Some(config_value) = config { config_value @@ -197,10 +231,10 @@ impl DomainLinkageService { match DomainLinkageConfiguration::fetch_configuration(domain.clone()).await { Ok(value) => value, Err(err) => { - return Ok(vec![get_validation_failed_status( + return Ok(get_linked_did_validation_failed_status( "could not get domain linkage config", &err, - )]); + )); } } }; @@ -212,10 +246,10 @@ impl DomainLinkageService { match domain_linkage_configuration.issuers() { Ok(value) => value, Err(err) => { - return Ok(vec![get_validation_failed_status( + return Ok(get_linked_did_validation_failed_status( "could not get issuers from domain linkage config credential", &err, - )]); + )); } } }; @@ -224,40 +258,51 @@ impl DomainLinkageService { let resolved = match self.resolver.resolve_multiple(&linked_dids).await { Ok(value) => value, Err(err) => { - return Ok(vec![get_validation_failed_status( + return Ok(get_linked_did_validation_failed_status( "could not resolve linked DIDs from domain linkage config", &err, - )]); + )); } }; + let mut valid_dids = vec![]; + let mut invalid_dids = vec![]; + // check linked DIDs separately - let errors: Vec> = resolved - .values() - .map(|issuer_did_doc| { - JwtDomainLinkageValidator::with_signature_verifier(EdDSAJwsVerifier::default()) + domain_linkage_configuration + .linked_dids() + .iter() + .zip(resolved.values()) + .for_each(|(credential, issuer_did_doc)| { + let id = issuer_did_doc.id().to_string(); + + if let Err(err) = JwtDomainLinkageValidator::with_signature_verifier(EdDSAJwsVerifier::default()) .validate_linkage( &issuer_did_doc, &domain_linkage_configuration, - &domain.clone(), + &domain, &JwtCredentialValidationOptions::default(), ) - .err() - .map(|err| err.to_string()) - }) - .collect(); + { + invalid_dids.push(InvalidDid { + service_id: Some(id), + credential: Some(credential.as_str().to_string()), + did: Some(issuer_did_doc.to_string()), + error: err.to_string(), + }); + } else { + valid_dids.push(ValidDid { + service_id: id, + did: issuer_did_doc.to_string(), + credential: credential.as_str().to_string(), + }); + } + }); - // collect resolved documents and their validation status into array following the order of `linked_dids` - let status_infos = domain_linkage_configuration - .linked_dids() - .iter() - .zip(errors.iter()) - .map(|(credential, error)| LinkedDidValidationStatus { - valid: error.is_none(), - document: Some(credential.as_str().to_string()), - error: error.clone(), - }) - .collect(); + let status_infos = LinkedDids { + valid: valid_dids, + invalid: invalid_dids, + }; Ok(status_infos) } @@ -282,12 +327,13 @@ impl DomainLinkage for DomainLinkageService { Url::parse(&request_data.domain).map_err(|err| DomainLinkageError::DomainParsing(err.to_string()))?; // get validation status for all issuer dids - let status_infos = self - .validate_domains_with_optional_configuration(domain, None, None) + let linked_dids = self + .validate_domains_with_optional_configuration(&domain, None, None) .await?; Ok(Response::new(ValidateDomainResponse { - linked_dids: status_infos, + domain: domain.to_string(), + linked_dids: Some(linked_dids), })) } @@ -306,18 +352,19 @@ impl DomainLinkage for DomainLinkageService { // parse given domain let domain: Url = Url::parse(&request_data.domain).map_err(|err| DomainLinkageError::DomainParsing(err.to_string()))?; + // parse config let config = DomainLinkageConfiguration::from_json(&request_data.did_configuration.to_string()).map_err(|err| { DomainLinkageError::DidConfigurationParsing(format!("could not parse given DID configuration; {}", &err)) })?; - // get validation status for all issuer dids - let status_infos = self - .validate_domains_with_optional_configuration(domain, None, Some(config)) + let linked_dids = self + .validate_domains_with_optional_configuration(&domain, None, Some(config)) .await?; Ok(Response::new(ValidateDomainResponse { - linked_dids: status_infos, + domain: request_data.domain.clone(), + linked_dids: Some(linked_dids), })) } @@ -332,10 +379,11 @@ impl DomainLinkage for DomainLinkageService { // fetch DID document for given DID let did: IotaDID = IotaDID::parse(req.into_inner().did).map_err(|e| Status::internal(e.to_string()))?; - let endpoint_validation_status = self.validate_did_with_optional_configurations(&did, None).await?; + let domains = self.validate_did_with_optional_configurations(&did, None).await?; let response = ValidateDidResponse { - service: endpoint_validation_status, + did: did.to_string(), + domains: Some(domains), }; Ok(Response::new(response)) @@ -360,12 +408,13 @@ impl DomainLinkage for DomainLinkageService { .map(DomainValidationConfig::try_parse) .collect::, DomainLinkageError>>()?; - let endpoint_validation_status = self + let domains = self .validate_did_with_optional_configurations(&did, Some(did_configurations)) .await?; let response = ValidateDidResponse { - service: endpoint_validation_status, + did: did.to_string(), + domains: Some(domains), }; Ok(Response::new(response)) diff --git a/bindings/grpc/src/services/mod.rs b/bindings/grpc/src/services/mod.rs index 082fb0ada9..d352b8f858 100644 --- a/bindings/grpc/src/services/mod.rs +++ b/bindings/grpc/src/services/mod.rs @@ -7,6 +7,7 @@ pub mod domain_linkage; pub mod health_check; pub mod sd_jwt; pub mod status_list_2021; +pub mod utils; use identity_stronghold::StrongholdStorage; use identity_sui_name_tbd::client::IdentityClientReadOnly; @@ -21,6 +22,7 @@ pub fn routes(client: &IdentityClientReadOnly, stronghold: &StrongholdStorage) - routes.add_service(domain_linkage::service(client)); routes.add_service(document::service(client, stronghold)); routes.add_service(status_list_2021::service()); + routes.add_service(utils::service(stronghold)); routes.routes() } diff --git a/bindings/grpc/src/services/utils.rs b/bindings/grpc/src/services/utils.rs new file mode 100644 index 0000000000..0e7d2fc570 --- /dev/null +++ b/bindings/grpc/src/services/utils.rs @@ -0,0 +1,67 @@ +// Copyright 2020-2024 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +use _utils::signing_server::Signing as SigningSvc; +use _utils::signing_server::SigningServer; +use _utils::DataSigningRequest; +use _utils::DataSigningResponse; +use identity_iota::storage::JwkStorage; +use identity_iota::storage::KeyId; +use identity_iota::storage::KeyStorageError; +use identity_stronghold::StrongholdStorage; +use tonic::Request; +use tonic::Response; +use tonic::Status; + +mod _utils { + tonic::include_proto!("utils"); +} + +#[derive(Debug, thiserror::Error)] +#[error("Key storage error: {0}")] +pub struct Error(#[from] KeyStorageError); + +impl From for Status { + fn from(value: Error) -> Self { + Status::internal(value.to_string()) + } +} + +pub struct SigningService { + storage: StrongholdStorage, +} + +impl SigningService { + pub fn new(stronghold: &StrongholdStorage) -> Self { + Self { + storage: stronghold.clone(), + } + } +} + +#[tonic::async_trait] +impl SigningSvc for SigningService { + #[tracing::instrument( + name = "utils/sign", + skip_all, + fields(request = ?req.get_ref()) + ret, + err, + )] + async fn sign(&self, req: Request) -> Result, Status> { + let DataSigningRequest { data, key_id } = req.into_inner(); + let key_id = KeyId::new(key_id); + let public_key_jwk = self.storage.get_public_key(&key_id).await.map_err(Error)?; + let signature = self + .storage + .sign(&key_id, &data, &public_key_jwk) + .await + .map_err(Error)?; + + Ok(Response::new(DataSigningResponse { signature })) + } +} + +pub fn service(stronghold: &StrongholdStorage) -> SigningServer { + SigningServer::new(SigningService::new(stronghold)) +} diff --git a/bindings/grpc/tests/api/credential_revocation_check.rs b/bindings/grpc/tests/api/credential_revocation_check.rs index 06a0cb6340..a2dc1722d7 100644 --- a/bindings/grpc/tests/api/credential_revocation_check.rs +++ b/bindings/grpc/tests/api/credential_revocation_check.rs @@ -11,8 +11,9 @@ use identity_stronghold::StrongholdStorage; use serde_json::json; use crate::credential_revocation_check::credentials::RevocationCheckRequest; +use crate::helpers::make_stronghold; +use crate::helpers::Entity; use crate::helpers::TestServer; -use crate::helpers::{make_stronghold, Entity}; mod credentials { tonic::include_proto!("credentials"); diff --git a/bindings/grpc/tests/api/domain_linkage.rs b/bindings/grpc/tests/api/domain_linkage.rs index cac09baf3e..05819710df 100644 --- a/bindings/grpc/tests/api/domain_linkage.rs +++ b/bindings/grpc/tests/api/domain_linkage.rs @@ -1,7 +1,14 @@ // Copyright 2020-2024 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 +use _credentials::validate_did_response::Domains; +use _credentials::validate_domain_response::LinkedDids; + +use crate::domain_linkage::_credentials::validate_did_response::domains::InvalidDomain; +use crate::domain_linkage::_credentials::validate_did_response::domains::ValidDomain; +use crate::domain_linkage::_credentials::validate_domain_response::linked_dids::ValidDid; use identity_iota::core::Duration; +use identity_iota::core::FromJson; use identity_iota::core::Object; use identity_iota::core::OrderedSet; use identity_iota::core::Timestamp; @@ -13,13 +20,13 @@ use identity_iota::credential::Jwt; use identity_iota::credential::LinkedDomainService; use identity_iota::did::DIDUrl; use identity_iota::did::DID; +use identity_iota::iota::IotaDocument; use identity_storage::JwkDocumentExt; use identity_storage::JwsSignatureOptions; use identity_stronghold::StrongholdStorage; use crate::domain_linkage::_credentials::domain_linkage_client::DomainLinkageClient; -use crate::domain_linkage::_credentials::LinkedDidEndpointValidationStatus; -use crate::domain_linkage::_credentials::LinkedDidValidationStatus; + use crate::domain_linkage::_credentials::ValidateDidAgainstDidConfigurationsRequest; use crate::domain_linkage::_credentials::ValidateDidResponse; use crate::domain_linkage::_credentials::ValidateDomainAgainstDidConfigurationRequest; @@ -33,7 +40,7 @@ mod _credentials { } /// Prepares basically the same test setup as in test `examples/1_advanced/6_domain_linkage.rs`. -async fn prepare_test() -> anyhow::Result<(TestServer, Url, String, Jwt)> { +async fn prepare_test() -> anyhow::Result<(TestServer, Url, Url, String, Jwt)> { let stronghold = StrongholdStorage::new(make_stronghold()); let server = TestServer::new_with_stronghold(stronghold.clone()).await; let api_client = server.client(); @@ -42,9 +49,9 @@ async fn prepare_test() -> anyhow::Result<(TestServer, Url, String, Jwt)> { issuer.create_did(api_client).await?; let did = issuer .document() - .ok_or_else(|| anyhow::anyhow!("no DID document for issuer"))? - .id(); - let did_string = did.to_string(); + .ok_or_else(|| anyhow::anyhow!("no DID document for issuer"))?; + + let did = did.id().clone(); // ===================================================== // Create Linked Domain service // ===================================================== @@ -70,6 +77,8 @@ async fn prepare_test() -> anyhow::Result<(TestServer, Url, String, Jwt)> { .document() .ok_or_else(|| anyhow::anyhow!("no DID document for issuer"))?; + let did_string = updated_did_document.to_string(); + println!("DID document with linked domain service: {updated_did_document:#}"); // ===================================================== @@ -101,12 +110,12 @@ async fn prepare_test() -> anyhow::Result<(TestServer, Url, String, Jwt)> { ) .await?; - Ok((server, domain_1, did_string, jwt)) + Ok((server, domain_1, domain_2, did_string, jwt)) } #[tokio::test] async fn can_validate_domain() -> anyhow::Result<()> { - let (server, linked_domain, _, jwt) = prepare_test().await?; + let (server, linked_domain, _, did, jwt) = prepare_test().await?; let configuration_resource: DomainLinkageConfiguration = DomainLinkageConfiguration::new(vec![jwt.clone()]); let mut grpc_client = DomainLinkageClient::connect(server.endpoint()).await?; @@ -116,15 +125,20 @@ async fn can_validate_domain() -> anyhow::Result<()> { did_configuration: configuration_resource.to_string(), }) .await?; + let did_id = IotaDocument::from_json(&did)?.id().to_string(); assert_eq!( response.into_inner(), ValidateDomainResponse { - linked_dids: vec![LinkedDidValidationStatus { - valid: true, - document: Some(jwt.as_str().to_string()), - error: None, - }], + linked_dids: Some(LinkedDids { + invalid: vec![], + valid: vec![ValidDid { + service_id: did_id, + did: did.to_string().clone(), + credential: jwt.as_str().to_string(), + }] + }), + domain: linked_domain.to_string(), } ); @@ -133,13 +147,14 @@ async fn can_validate_domain() -> anyhow::Result<()> { #[tokio::test] async fn can_validate_did() -> anyhow::Result<()> { - let (server, linked_domain, issuer_did, jwt) = prepare_test().await?; + let (server, linked_domain, domain2, issuer_did, jwt) = prepare_test().await?; let configuration_resource: DomainLinkageConfiguration = DomainLinkageConfiguration::new(vec![jwt.clone()]); let mut grpc_client = DomainLinkageClient::connect(server.endpoint()).await?; + let did_id = IotaDocument::from_json(&issuer_did)?.id().to_string(); let response = grpc_client .validate_did_against_did_configurations(ValidateDidAgainstDidConfigurationsRequest { - did: issuer_did.clone(), + did: did_id.clone(), did_configurations: vec![ValidateDomainAgainstDidConfigurationRequest { domain: linked_domain.to_string(), did_configuration: configuration_resource.to_string(), @@ -147,26 +162,31 @@ async fn can_validate_did() -> anyhow::Result<()> { }) .await?; + let service_id = format!("{}#domain-linkage", did_id); + + let valid_domain = ValidDomain { + service_id: service_id.clone(), + url: linked_domain.to_string(), + credential: jwt.as_str().to_string(), + }; + + let error = format!("could not get domain linkage config: domain linkage error: error sending request for url ({}.well-known/did-configuration.json): error trying to connect: dns error: failed to lookup address information: nodename nor servname provided, or not known", domain2.to_string()); + + let invalid_domain = InvalidDomain { + service_id: service_id.clone(), + credential: None, + url: domain2.to_string(), + error, + }; + assert_eq!( response.into_inner(), ValidateDidResponse { - service: vec![ - LinkedDidEndpointValidationStatus { - id: issuer_did, - service_endpoint: vec![ - LinkedDidValidationStatus { - valid: true, - document: Some(jwt.as_str().to_string()), - error: None, - }, - LinkedDidValidationStatus { - valid: false, - document: None, - error: Some("could not get domain linkage config; domain linkage error: error sending request for url (https://bar.example.com/.well-known/did-configuration.json): error trying to connect: dns error: failed to lookup address information: nodename nor servname provided, or not known".to_string()), - } - ], - } - ] + did: did_id, + domains: Some(Domains { + invalid: vec![invalid_domain], + valid: vec![valid_domain], + }), } ); diff --git a/bindings/grpc/tests/api/helpers.rs b/bindings/grpc/tests/api/helpers.rs index 5ff66a6326..3d742cb779 100644 --- a/bindings/grpc/tests/api/helpers.rs +++ b/bindings/grpc/tests/api/helpers.rs @@ -12,18 +12,24 @@ use identity_iota::verification::MethodScope; use identity_jose::jwk::Jwk; use identity_storage::key_id_storage::KeyIdMemstore; use identity_storage::key_storage::JwkMemStore; +use identity_storage::JwkDocumentExt; +use identity_storage::JwkStorage; +use identity_storage::KeyId; use identity_storage::KeyIdStorage; +use identity_storage::KeyType; use identity_storage::Storage; -use identity_storage::{JwkDocumentExt, KeyType}; -use identity_storage::{JwkStorage, KeyId, StorageSigner}; +use identity_storage::StorageSigner; use identity_stronghold::StrongholdStorage; -use identity_sui_name_tbd::client::{IdentityClient, IdentityClientReadOnly}; +use identity_sui_name_tbd::client::IdentityClient; +use identity_sui_name_tbd::client::IdentityClientReadOnly; use identity_sui_name_tbd::transaction::Transaction; use iota_sdk::client::secret::stronghold::StrongholdSecretManager; use iota_sdk::client::stronghold::StrongholdAdapter; use iota_sdk::client::Password; -use iota_sdk_move::types::base_types::{IotaAddress, ObjectID}; -use iota_sdk_move::{IotaClient, IotaClientBuilder}; +use iota_sdk_move::types::base_types::IotaAddress; +use iota_sdk_move::types::base_types::ObjectID; +use iota_sdk_move::IotaClient; +use iota_sdk_move::IotaClientBuilder; use jsonpath_rust::JsonPathQuery; use rand::distributions::Alphanumeric; use rand::distributions::DistString; diff --git a/bindings/grpc/tests/api/main.rs b/bindings/grpc/tests/api/main.rs index 1602c15072..d070ba4c04 100644 --- a/bindings/grpc/tests/api/main.rs +++ b/bindings/grpc/tests/api/main.rs @@ -19,3 +19,4 @@ mod jwt; mod sd_jwt_validation; #[cfg(test)] mod status_list_2021; +mod utils; diff --git a/bindings/grpc/tests/api/utils.rs b/bindings/grpc/tests/api/utils.rs new file mode 100644 index 0000000000..9b320bd154 --- /dev/null +++ b/bindings/grpc/tests/api/utils.rs @@ -0,0 +1,51 @@ +// Copyright 2020-2024 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +use _utils::signing_client::SigningClient; +use _utils::DataSigningRequest; +use identity_iota::verification::jws::JwsAlgorithm; +use identity_storage::JwkStorage; +use identity_storage::KeyType; +use identity_stronghold::StrongholdKeyType; +use identity_stronghold::StrongholdStorage; + +use crate::helpers::make_stronghold; +use crate::helpers::TestServer; + +mod _utils { + tonic::include_proto!("utils"); +} + +const SAMPLE_SIGNING_DATA: &'static [u8] = b"I'm just some random data to be signed :)"; + +#[tokio::test] +async fn raw_data_signing_works() -> anyhow::Result<()> { + let stronghold = StrongholdStorage::new(make_stronghold()); + let server = TestServer::new_with_stronghold(stronghold.clone()).await; + + let key_id = stronghold + .generate(KeyType::from_static_str("Ed25519"), JwsAlgorithm::EdDSA) + .await? + .key_id; + + let expected_signature = { + let public_key_jwk = stronghold + .get_public_key_with_type(&key_id, StrongholdKeyType::Ed25519) + .await?; + stronghold.sign(&key_id, SAMPLE_SIGNING_DATA, &public_key_jwk).await? + }; + + let mut grpc_client = SigningClient::connect(server.endpoint()).await?; + let signature = grpc_client + .sign(DataSigningRequest { + data: SAMPLE_SIGNING_DATA.to_owned(), + key_id: key_id.to_string(), + }) + .await? + .into_inner() + .signature; + + assert_eq!(signature, expected_signature); + + Ok(()) +} diff --git a/bindings/grpc/tooling/domain-linkage-test-server/.well-known/did-configuration.json b/bindings/grpc/tooling/domain-linkage-test-server/.well-known/did-configuration.json index 802f453e3e..579b9ee575 100644 --- a/bindings/grpc/tooling/domain-linkage-test-server/.well-known/did-configuration.json +++ b/bindings/grpc/tooling/domain-linkage-test-server/.well-known/did-configuration.json @@ -3,4 +3,4 @@ "linked_dids": [ "add your domain linkage credential here" ] -} \ No newline at end of file +} diff --git a/bindings/wasm/.dockerignore b/bindings/wasm/.dockerignore new file mode 100644 index 0000000000..2c085d1d2f --- /dev/null +++ b/bindings/wasm/.dockerignore @@ -0,0 +1,2 @@ +node_modules +target diff --git a/bindings/wasm/CHANGELOG.md b/bindings/wasm/CHANGELOG.md index fb0c4f8f46..55ab6c5e4e 100644 --- a/bindings/wasm/CHANGELOG.md +++ b/bindings/wasm/CHANGELOG.md @@ -1,32 +1,65 @@ # Changelog +## [wasm-v1.4.0](https://github.com/iotaledger/identity.rs/tree/wasm-v1.4.0) (2024-09-23) + +[Full Changelog](https://github.com/iotaledger/identity.rs/compare/wasm-v1.3.1...wasm-v1.4.0) + +### Added + +- Add support for `did:jwk` resolution [\#1404](https://github.com/iotaledger/identity.rs/pull/1404) +- Linked Verifiable Presentations [\#1398](https://github.com/iotaledger/identity.rs/pull/1398) +- Add WASM bindings for EcDSA JWS Verifier [\#1396](https://github.com/iotaledger/identity.rs/pull/1396) + +## [wasm-v1.3.1](https://github.com/iotaledger/identity.rs/tree/wasm-v1.3.1) (2024-06-28) + +[Full Changelog](https://github.com/iotaledger/identity.rs/compare/wasm-v1.3.0...wasm-v1.3.1) + +### Patch + +- Make base64 encoding target independent in `KeyIdMemStore` in wasm bindings [\#1386](https://github.com/iotaledger/identity.rs/pull/1386) + +## [wasm-v1.3.0](https://github.com/iotaledger/identity.rs/tree/wasm-v1.3.0) (2024-05-28) + +[Full Changelog](https://github.com/iotaledger/identity.rs/compare/wasm-v1.2.0...wasm-v1.3.0) + +### Added + +- Add ZK BBS+-based selectively disclosable credentials \(JPT\) [\#1355](https://github.com/iotaledger/identity.rs/pull/1355) + +### Patch + +- Support for specification-compliant verification method type `JsonWebKey2020` [\#1367](https://github.com/iotaledger/identity.rs/pull/1367) + ## [wasm-v1.2.0](https://github.com/iotaledger/identity.rs/tree/wasm-v1.2.0) (2024-03-27) [Full Changelog](https://github.com/iotaledger/identity.rs/compare/wasm-v1.1.0...wasm-v1.2.0) ### Added -- Allow setting additional controllers for `IotaDocument` [\#1314](https://github.com/iotaledger/identity.rs/pull/1314) -- use latest release of sd-jwt-payload `IotaDocument` [\#1333](https://github.com/iotaledger/identity.rs/pull/1333) + - Allow arbitrary verification methods [\#1334](https://github.com/iotaledger/identity.rs/pull/1334) +- use latest release of sd-jwt-payload [\#1333](https://github.com/iotaledger/identity.rs/pull/1333) +- Add constructor for `VerificationMethod` in TS [\#1321](https://github.com/iotaledger/identity.rs/pull/1321) +- Allow setting additional controllers for `IotaDocument` [\#1314](https://github.com/iotaledger/identity.rs/pull/1314) ### Patch + - Support %-encoded characters in DID method id [\#1303](https://github.com/iotaledger/identity.rs/pull/1303) -## [wasm-v1.1.0](https://github.com/iotaledger/identity.rs/tree/wasm-v1.1.0) (2024-02-06) +## [wasm-v1.1.0](https://github.com/iotaledger/identity.rs/tree/wasm-v1.1.0) (2024-02-07) [Full Changelog](https://github.com/iotaledger/identity.rs/compare/wasm-v1.0.0...wasm-v1.1.0) ### Added -- Support Selective Disclosure SD-JWT [\#1268](https://github.com/iotaledger/identity.rs/pull/1268) +- Update `sd-jwt-payload` dependency [\#1296](https://github.com/iotaledger/identity.rs/pull/1296) - Add support for StatusList2021 [\#1273](https://github.com/iotaledger/identity.rs/pull/1273) -- Update sd-jwt-payload dependency [\#1296](https://github.com/iotaledger/identity.rs/pull/1296) +- Support Selective Disclosure SD-JWT [\#1268](https://github.com/iotaledger/identity.rs/pull/1268) ### Patch -- Validate domain-linkage URL making sure they only include an origin [\#1267](https://github.com/iotaledger/identity.rs/pull/1267) -- Credentials cannot be unrevoked with StatusList2021 [\#1284](https://github.com/iotaledger/identity.rs/pull/1284) - Fix RevocationBitmap2022 encoding bug [\#1292](https://github.com/iotaledger/identity.rs/pull/1292) +- Credentials cannot be unrevoked with StatusList2021 [\#1284](https://github.com/iotaledger/identity.rs/pull/1284) +- Validate domain-linkage URL making sure they only include an origin [\#1267](https://github.com/iotaledger/identity.rs/pull/1267) ## [wasm-v1.0.0](https://github.com/iotaledger/identity.rs/tree/wasm-v1.0.0) (2023-11-02) diff --git a/bindings/wasm/Cargo.toml b/bindings/wasm/Cargo.toml index 75c4df419a..8406b386b2 100644 --- a/bindings/wasm/Cargo.toml +++ b/bindings/wasm/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "identity_wasm" -version = "1.2.0" +version = "1.4.0" authors = ["IOTA Stiftung"] edition = "2021" homepage = "https://www.iota.org" @@ -17,10 +17,13 @@ crate-type = ["cdylib", "rlib"] [dependencies] async-trait = { version = "0.1", default-features = false } +bls12_381_plus = "0.8.17" console_error_panic_hook = { version = "0.1" } futures = { version = "0.3" } +identity_ecdsa_verifier = { path = "../../identity_ecdsa_verifier", default-features = false, features = ["es256", "es256k"] } identity_eddsa_verifier = { path = "../../identity_eddsa_verifier", default-features = false, features = ["ed25519"] } js-sys = { version = "0.3.61" } +json-proof-token = "0.3.4" proc_typescript = { version = "0.1.0", path = "./proc_typescript" } serde = { version = "1.0", features = ["derive"] } serde_json = { version = "1.0", default-features = false } @@ -29,11 +32,12 @@ serde_repr = { version = "0.1", default-features = false } tokio = { version = "1.29", default-features = false, features = ["sync"] } wasm-bindgen = { version = "0.2.85", features = ["serde-serialize"] } wasm-bindgen-futures = { version = "0.4", default-features = false } +zkryptium = "0.2.2" [dependencies.identity_iota] path = "../../identity_iota" default-features = false -features = ["client", "revocation-bitmap", "resolver", "domain-linkage", "sd-jwt", "status-list-2021"] +features = ["client", "revocation-bitmap", "resolver", "domain-linkage", "sd-jwt", "status-list-2021", "jpt-bbs-plus"] [dev-dependencies] rand = "0.8.5" @@ -45,3 +49,8 @@ instant = { version = "0.1", default-features = false, features = ["wasm-bindgen [profile.release] opt-level = 's' lto = true + +[lints.clippy] +# can be removed as soon as fix has been added to clippy +# see https://github.com/rust-lang/rust-clippy/issues/12377 +empty_docs = "allow" diff --git a/bindings/wasm/build/docs.js b/bindings/wasm/build/docs.js deleted file mode 100644 index b496ccd7d0..0000000000 --- a/bindings/wasm/build/docs.js +++ /dev/null @@ -1,15 +0,0 @@ -const fs = require("fs"); -const path = require("path"); -const jsdoc2md = require("jsdoc-to-markdown"); - -const importFile = path.join(__dirname, "../node/identity_wasm.js"); -const exportFile = path.join(__dirname, "../docs/api-reference.md"); - -const docsRoot = path.join(__dirname, "../docs"); -const docsData = jsdoc2md.renderSync({ files: importFile }); - -if (!fs.existsSync(docsRoot)) { - fs.mkdirSync(docsRoot); -} - -fs.writeFileSync(exportFile, docsData); diff --git a/bindings/wasm/cypress.config.ts b/bindings/wasm/cypress.config.ts index 1ad8692bba..c59929872a 100644 --- a/bindings/wasm/cypress.config.ts +++ b/bindings/wasm/cypress.config.ts @@ -10,5 +10,23 @@ export default defineConfig({ }, e2e: { supportFile: false, + setupNodeEvents(on, config) { + on("before:browser:launch", (browser, launchOptions) => { + if (browser.family === "firefox") { + // Fix to make subtle crypto work in cypress firefox + // https://github.com/cypress-io/cypress/issues/18217 + launchOptions.preferences[ + "network.proxy.testing_localhost_is_secure_when_hijacked" + ] = true; + // Temporary fix to allow cypress to control Firefox via CDP + // https://github.com/cypress-io/cypress/issues/29713 + // https://fxdx.dev/deprecating-cdp-support-in-firefox-embracing-the-future-with-webdriver-bidi/ + launchOptions.preferences[ + "remote.active-protocols" + ] = 3; + } + return launchOptions; + }); + }, }, }); diff --git a/bindings/wasm/cypress/Dockerfile b/bindings/wasm/cypress/Dockerfile new file mode 100644 index 0000000000..5ea3f7433b --- /dev/null +++ b/bindings/wasm/cypress/Dockerfile @@ -0,0 +1,11 @@ +FROM cypress/browsers:latest + +COPY ./ /e2e + +WORKDIR /e2e + +RUN npm ci + +RUN npm run build:examples:web + +ENTRYPOINT [ "npm", "run" ] \ No newline at end of file diff --git a/bindings/wasm/docs/api-reference.md b/bindings/wasm/docs/api-reference.md deleted file mode 100644 index 2f50e4ed3d..0000000000 --- a/bindings/wasm/docs/api-reference.md +++ /dev/null @@ -1,6342 +0,0 @@ -## Classes - -
-
CoreDID
-

A method-agnostic Decentralized Identifier (DID).

-
-
CoreDocument
-

A method-agnostic DID Document.

-

Note: All methods that involve reading from this class may potentially raise an error -if the object is being concurrently modified.

-
-
Credential
-
-
CustomMethodData
-

A custom verification method data format.

-
-
DIDUrl
-

A method agnostic DID Url.

-
-
DecodedJws
-

A cryptographically verified decoded token from a JWS.

-

Contains the decoded headers and the raw claims.

-
-
DecodedJwtCredential
-

A cryptographically verified and decoded Credential.

-

Note that having an instance of this type only means the JWS it was constructed from was verified. -It does not imply anything about a potentially present proof property on the credential itself.

-
-
DecodedJwtPresentation
-

A cryptographically verified and decoded presentation.

-

Note that having an instance of this type only means the JWS it was constructed from was verified. -It does not imply anything about a potentially present proof property on the presentation itself.

-
-
Disclosure
-

Represents an elements constructing a disclosure. -Object properties and array elements disclosures are supported.

-

See: https://www.ietf.org/archive/id/draft-ietf-oauth-selective-disclosure-jwt-07.html#name-disclosures

-
-
DomainLinkageConfiguration
-

DID Configuration Resource which contains Domain Linkage Credentials. -It can be placed in an origin's .well-known directory to prove linkage between the origin and a DID. -See: https://identity.foundation/.well-known/resources/did-configuration/#did-configuration-resource

-

Note:

- -
-
Duration
-

A span of time.

-
-
EdDSAJwsVerifier
-

An implementor of IJwsVerifier that can handle the -EdDSA algorithm.

-
-
IotaDID
-

A DID conforming to the IOTA DID method specification.

-
-
IotaDocument
-

A DID Document adhering to the IOTA DID method specification.

-

Note: All methods that involve reading from this class may potentially raise an error -if the object is being concurrently modified.

-
-
IotaDocumentMetadata
-

Additional attributes related to an IOTA DID Document.

-
-
IotaIdentityClientExt
-

An extension interface that provides helper functions for publication -and resolution of DID documents in Alias Outputs.

-
-
Jwk
-
-
JwkGenOutput
-

The result of a key generation in JwkStorage.

-
-
Jws
-

A wrapper around a JSON Web Signature (JWS).

-
-
JwsHeader
-
-
JwsSignatureOptions
-
-
JwsVerificationOptions
-
-
Jwt
-

A wrapper around a JSON Web Token (JWK).

-
-
JwtCredentialValidationOptions
-

Options to declare validation criteria when validating credentials.

-
-
JwtCredentialValidator
-

A type for decoding and validating Credential.

-
-
JwtDomainLinkageValidator
-

A validator for a Domain Linkage Configuration and Credentials.

-
-
JwtPresentationOptions
-
-
JwtPresentationValidationOptions
-

Options to declare validation criteria when validating presentation.

-
-
JwtPresentationValidator
-
-
KeyBindingJWTValidationOptions
-

Options to declare validation criteria when validating credentials.

-
-
KeyBindingJwtClaims
-

Claims set for key binding JWT.

-
-
LinkedDomainService
-
-
MethodData
-

Supported verification method data formats.

-
-
MethodDigest
-

Unique identifier of a VerificationMethod.

-

NOTE: -This class does not have a JSON representation, -use the methods pack and unpack instead.

-
-
MethodScope
-

Supported verification method types.

-
-
MethodType
-

Supported verification method types.

-
-
Presentation
-
-
Proof
-

Represents a cryptographic proof that can be used to validate verifiable credentials and -presentations.

-

This representation does not inherently implement any standard; instead, it -can be utilized to implement standards or user-defined proofs. The presence of the -type field is necessary to accommodate different types of cryptographic proofs.

-

Note that this proof is not related to JWT and can be used in combination or as an alternative -to it.

-
-
Resolver
-

Convenience type for resolving DID documents from different DID methods.

-

Also provides methods for resolving DID Documents associated with -verifiable Credentials and Presentations.

-

Configuration

-

The resolver will only be able to resolve DID documents for methods it has been configured for in the constructor.

-
-
RevocationBitmap
-

A compressed bitmap for managing credential revocation.

-
-
SdJwt
-

Representation of an SD-JWT of the format -<Issuer-signed JWT>~<Disclosure 1>~<Disclosure 2>~...~<Disclosure N>~<optional KB-JWT>.

-
-
SdJwtCredentialValidator
-

A type for decoding and validating Credential.

-
-
SdObjectDecoder
-

Substitutes digests in an SD-JWT object by their corresponding plaintext values provided by disclosures.

-
-
SdObjectEncoder
-

Transforms a JSON object into an SD-JWT object by substituting selected values -with their corresponding disclosure digests.

-

Note: digests are created using the sha-256 algorithm.

-
-
Service
-

A DID Document Service used to enable trusted interactions associated with a DID subject.

-
-
StatusList2021
-

StatusList2021 data structure as described in W3C's VC status list 2021.

-
-
StatusList2021Credential
-

A parsed StatusList2021Credential.

-
-
StatusList2021CredentialBuilder
-

Builder type to construct valid StatusList2021Credential istances.

-
-
StatusList2021Entry
-

StatusList2021Entry implementation.

-
-
Storage
-

A type wrapping a JwkStorage and KeyIdStorage that should always be used together when -working with storage backed DID documents.

-
-
Timestamp
-
-
UnknownCredential
-
-
VerificationMethod
-

A DID Document Verification Method.

-
-
- -## Members - -
-
StatusPurpose
-

Purpose of a StatusList2021.

-
-
SubjectHolderRelationship
-

Declares how credential subjects must relate to the presentation holder.

-

See also the Subject-Holder Relationship section of the specification.

-
-
AlwaysSubject
-

The holder must always match the subject on all credentials, regardless of their nonTransferable property. -This variant is the default.

-
-
SubjectOnNonTransferable
-

The holder must match the subject only for credentials where the nonTransferable property is true.

-
-
Any
-

The holder is not required to have any kind of relationship to any credential subject.

-
-
StateMetadataEncoding
-
-
FailFast
-

Declares when validation should return if an error occurs.

-
-
AllErrors
-

Return all errors that occur during validation.

-
-
FirstError
-

Return after the first error occurs.

-
-
MethodRelationship
-
-
CredentialStatus
-
-
StatusCheck
-

Controls validation behaviour when checking whether or not a credential has been revoked by its -credentialStatus.

-
-
Strict
-

Validate the status if supported, reject any unsupported -credentialStatus types.

-

Only RevocationBitmap2022 is currently supported.

-

This is the default.

-
-
SkipUnsupported
-

Validate the status if supported, skip any unsupported -credentialStatus types.

-
-
SkipAll
-

Skip all status checks.

-
-
- -## Functions - -
-
verifyEd25519(alg, signingInput, decodedSignature, publicKey)
-

Verify a JWS signature secured with the EdDSA algorithm and curve Ed25519.

-

This function is useful when one is composing a IJwsVerifier that delegates -EdDSA verification with curve Ed25519 to this function.

-

Warning

-

This function does not check whether alg = EdDSA in the protected header. Callers are expected to assert this -prior to calling the function.

-
-
encodeB64(data) ⇒ string
-

Encode the given bytes in url-safe base64.

-
-
decodeB64(data) ⇒ Uint8Array
-

Decode the given url-safe base64-encoded slice into its raw bytes.

-
-
start()
-

Initializes the console error panic hook for better error messages

-
-
- - - -## CoreDID -A method-agnostic Decentralized Identifier (DID). - -**Kind**: global class - -* [CoreDID](#CoreDID) - * _instance_ - * [.setMethodName(value)](#CoreDID+setMethodName) - * [.setMethodId(value)](#CoreDID+setMethodId) - * [.scheme()](#CoreDID+scheme) ⇒ string - * [.authority()](#CoreDID+authority) ⇒ string - * [.method()](#CoreDID+method) ⇒ string - * [.methodId()](#CoreDID+methodId) ⇒ string - * [.join(segment)](#CoreDID+join) ⇒ [DIDUrl](#DIDUrl) - * [.toUrl()](#CoreDID+toUrl) ⇒ [DIDUrl](#DIDUrl) - * [.intoUrl()](#CoreDID+intoUrl) ⇒ [DIDUrl](#DIDUrl) - * [.toString()](#CoreDID+toString) ⇒ string - * [.toCoreDid()](#CoreDID+toCoreDid) ⇒ [CoreDID](#CoreDID) - * [.toJSON()](#CoreDID+toJSON) ⇒ any - * [.clone()](#CoreDID+clone) ⇒ [CoreDID](#CoreDID) - * _static_ - * [.parse(input)](#CoreDID.parse) ⇒ [CoreDID](#CoreDID) - * [.validMethodName(value)](#CoreDID.validMethodName) ⇒ boolean - * [.validMethodId(value)](#CoreDID.validMethodId) ⇒ boolean - * [.fromJSON(json)](#CoreDID.fromJSON) ⇒ [CoreDID](#CoreDID) - - - -### coreDID.setMethodName(value) -Set the method name of the [CoreDID](#CoreDID). - -**Kind**: instance method of [CoreDID](#CoreDID) - -| Param | Type | -| --- | --- | -| value | string | - - - -### coreDID.setMethodId(value) -Set the method-specific-id of the `DID`. - -**Kind**: instance method of [CoreDID](#CoreDID) - -| Param | Type | -| --- | --- | -| value | string | - - - -### coreDID.scheme() ⇒ string -Returns the [CoreDID](#CoreDID) scheme. - -E.g. -- `"did:example:12345678" -> "did"` -- `"did:iota:smr:12345678" -> "did"` - -**Kind**: instance method of [CoreDID](#CoreDID) - - -### coreDID.authority() ⇒ string -Returns the [CoreDID](#CoreDID) authority: the method name and method-id. - -E.g. -- `"did:example:12345678" -> "example:12345678"` -- `"did:iota:smr:12345678" -> "iota:smr:12345678"` - -**Kind**: instance method of [CoreDID](#CoreDID) - - -### coreDID.method() ⇒ string -Returns the [CoreDID](#CoreDID) method name. - -E.g. -- `"did:example:12345678" -> "example"` -- `"did:iota:smr:12345678" -> "iota"` - -**Kind**: instance method of [CoreDID](#CoreDID) - - -### coreDID.methodId() ⇒ string -Returns the [CoreDID](#CoreDID) method-specific ID. - -E.g. -- `"did:example:12345678" -> "12345678"` -- `"did:iota:smr:12345678" -> "smr:12345678"` - -**Kind**: instance method of [CoreDID](#CoreDID) - - -### coreDID.join(segment) ⇒ [DIDUrl](#DIDUrl) -Construct a new [DIDUrl](#DIDUrl) by joining with a relative DID Url string. - -**Kind**: instance method of [CoreDID](#CoreDID) - -| Param | Type | -| --- | --- | -| segment | string | - - - -### coreDID.toUrl() ⇒ [DIDUrl](#DIDUrl) -Clones the [CoreDID](#CoreDID) into a [DIDUrl](#DIDUrl). - -**Kind**: instance method of [CoreDID](#CoreDID) - - -### coreDID.intoUrl() ⇒ [DIDUrl](#DIDUrl) -Converts the [CoreDID](#CoreDID) into a [DIDUrl](#DIDUrl), consuming it. - -**Kind**: instance method of [CoreDID](#CoreDID) - - -### coreDID.toString() ⇒ string -Returns the [CoreDID](#CoreDID) as a string. - -**Kind**: instance method of [CoreDID](#CoreDID) - - -### coreDID.toCoreDid() ⇒ [CoreDID](#CoreDID) -**Kind**: instance method of [CoreDID](#CoreDID) - - -### coreDID.toJSON() ⇒ any -Serializes this to a JSON object. - -**Kind**: instance method of [CoreDID](#CoreDID) - - -### coreDID.clone() ⇒ [CoreDID](#CoreDID) -Deep clones the object. - -**Kind**: instance method of [CoreDID](#CoreDID) - - -### CoreDID.parse(input) ⇒ [CoreDID](#CoreDID) -Parses a [CoreDID](#CoreDID) from the given `input`. - -### Errors - -Throws an error if the input is not a valid [CoreDID](#CoreDID). - -**Kind**: static method of [CoreDID](#CoreDID) - -| Param | Type | -| --- | --- | -| input | string | - - - -### CoreDID.validMethodName(value) ⇒ boolean -Validates whether a string is a valid DID method name. - -**Kind**: static method of [CoreDID](#CoreDID) - -| Param | Type | -| --- | --- | -| value | string | - - - -### CoreDID.validMethodId(value) ⇒ boolean -Validates whether a string is a valid `DID` method-id. - -**Kind**: static method of [CoreDID](#CoreDID) - -| Param | Type | -| --- | --- | -| value | string | - - - -### CoreDID.fromJSON(json) ⇒ [CoreDID](#CoreDID) -Deserializes an instance from a JSON object. - -**Kind**: static method of [CoreDID](#CoreDID) - -| Param | Type | -| --- | --- | -| json | any | - - - -## CoreDocument -A method-agnostic DID Document. - -Note: All methods that involve reading from this class may potentially raise an error -if the object is being concurrently modified. - -**Kind**: global class - -* [CoreDocument](#CoreDocument) - * [new CoreDocument(values)](#new_CoreDocument_new) - * _instance_ - * [.id()](#CoreDocument+id) ⇒ [CoreDID](#CoreDID) - * [.setId(id)](#CoreDocument+setId) - * [.controller()](#CoreDocument+controller) ⇒ [Array.<CoreDID>](#CoreDID) - * [.setController(controllers)](#CoreDocument+setController) - * [.alsoKnownAs()](#CoreDocument+alsoKnownAs) ⇒ Array.<string> - * [.setAlsoKnownAs(urls)](#CoreDocument+setAlsoKnownAs) - * [.verificationMethod()](#CoreDocument+verificationMethod) ⇒ [Array.<VerificationMethod>](#VerificationMethod) - * [.authentication()](#CoreDocument+authentication) ⇒ Array.<(DIDUrl\|VerificationMethod)> - * [.assertionMethod()](#CoreDocument+assertionMethod) ⇒ Array.<(DIDUrl\|VerificationMethod)> - * [.keyAgreement()](#CoreDocument+keyAgreement) ⇒ Array.<(DIDUrl\|VerificationMethod)> - * [.capabilityDelegation()](#CoreDocument+capabilityDelegation) ⇒ Array.<(DIDUrl\|VerificationMethod)> - * [.capabilityInvocation()](#CoreDocument+capabilityInvocation) ⇒ Array.<(DIDUrl\|VerificationMethod)> - * [.properties()](#CoreDocument+properties) ⇒ Map.<string, any> - * [.setPropertyUnchecked(key, value)](#CoreDocument+setPropertyUnchecked) - * [.service()](#CoreDocument+service) ⇒ [Array.<Service>](#Service) - * [.insertService(service)](#CoreDocument+insertService) - * [.removeService(didUrl)](#CoreDocument+removeService) ⇒ [Service](#Service) \| undefined - * [.resolveService(query)](#CoreDocument+resolveService) ⇒ [Service](#Service) \| undefined - * [.methods([scope])](#CoreDocument+methods) ⇒ [Array.<VerificationMethod>](#VerificationMethod) - * [.verificationRelationships()](#CoreDocument+verificationRelationships) ⇒ Array.<(DIDUrl\|VerificationMethod)> - * [.insertMethod(method, scope)](#CoreDocument+insertMethod) - * [.removeMethod(did)](#CoreDocument+removeMethod) ⇒ [VerificationMethod](#VerificationMethod) \| undefined - * [.resolveMethod(query, [scope])](#CoreDocument+resolveMethod) ⇒ [VerificationMethod](#VerificationMethod) \| undefined - * [.attachMethodRelationship(didUrl, relationship)](#CoreDocument+attachMethodRelationship) ⇒ boolean - * [.detachMethodRelationship(didUrl, relationship)](#CoreDocument+detachMethodRelationship) ⇒ boolean - * [.verifyJws(jws, options, signatureVerifier, [detachedPayload])](#CoreDocument+verifyJws) ⇒ [DecodedJws](#DecodedJws) - * [.revokeCredentials(serviceQuery, indices)](#CoreDocument+revokeCredentials) - * [.unrevokeCredentials(serviceQuery, indices)](#CoreDocument+unrevokeCredentials) - * [.clone()](#CoreDocument+clone) ⇒ [CoreDocument](#CoreDocument) - * [._shallowCloneInternal()](#CoreDocument+_shallowCloneInternal) ⇒ [CoreDocument](#CoreDocument) - * [._strongCountInternal()](#CoreDocument+_strongCountInternal) ⇒ number - * [.toJSON()](#CoreDocument+toJSON) ⇒ any - * [.generateMethod(storage, keyType, alg, fragment, scope)](#CoreDocument+generateMethod) ⇒ Promise.<string> - * [.purgeMethod(storage, id)](#CoreDocument+purgeMethod) ⇒ Promise.<void> - * [.createJws(storage, fragment, payload, options)](#CoreDocument+createJws) ⇒ [Promise.<Jws>](#Jws) - * [.createCredentialJwt(storage, fragment, credential, options, [custom_claims])](#CoreDocument+createCredentialJwt) ⇒ [Promise.<Jwt>](#Jwt) - * [.createPresentationJwt(storage, fragment, presentation, signature_options, presentation_options)](#CoreDocument+createPresentationJwt) ⇒ [Promise.<Jwt>](#Jwt) - * _static_ - * [.fromJSON(json)](#CoreDocument.fromJSON) ⇒ [CoreDocument](#CoreDocument) - - - -### new CoreDocument(values) -Creates a new [CoreDocument](#CoreDocument) with the given properties. - - -| Param | Type | -| --- | --- | -| values | ICoreDocument | - - - -### coreDocument.id() ⇒ [CoreDID](#CoreDID) -Returns a copy of the DID Document `id`. - -**Kind**: instance method of [CoreDocument](#CoreDocument) - - -### coreDocument.setId(id) -Sets the DID of the document. - -### Warning - -Changing the identifier can drastically alter the results of -`resolve_method`, `resolve_service` and the related -[DID URL dereferencing](https://w3c-ccg.github.io/did-resolution/#dereferencing) algorithm. - -**Kind**: instance method of [CoreDocument](#CoreDocument) - -| Param | Type | -| --- | --- | -| id | [CoreDID](#CoreDID) | - - - -### coreDocument.controller() ⇒ [Array.<CoreDID>](#CoreDID) -Returns a copy of the document controllers. - -**Kind**: instance method of [CoreDocument](#CoreDocument) - - -### coreDocument.setController(controllers) -Sets the controllers of the DID Document. - -Note: Duplicates will be ignored. -Use `null` to remove all controllers. - -**Kind**: instance method of [CoreDocument](#CoreDocument) - -| Param | Type | -| --- | --- | -| controllers | [CoreDID](#CoreDID) \| [Array.<CoreDID>](#CoreDID) \| null | - - - -### coreDocument.alsoKnownAs() ⇒ Array.<string> -Returns a copy of the document's `alsoKnownAs` set. - -**Kind**: instance method of [CoreDocument](#CoreDocument) - - -### coreDocument.setAlsoKnownAs(urls) -Sets the `alsoKnownAs` property in the DID document. - -**Kind**: instance method of [CoreDocument](#CoreDocument) - -| Param | Type | -| --- | --- | -| urls | string \| Array.<string> \| null | - - - -### coreDocument.verificationMethod() ⇒ [Array.<VerificationMethod>](#VerificationMethod) -Returns a copy of the document's `verificationMethod` set. - -**Kind**: instance method of [CoreDocument](#CoreDocument) - - -### coreDocument.authentication() ⇒ Array.<(DIDUrl\|VerificationMethod)> -Returns a copy of the document's `authentication` set. - -**Kind**: instance method of [CoreDocument](#CoreDocument) - - -### coreDocument.assertionMethod() ⇒ Array.<(DIDUrl\|VerificationMethod)> -Returns a copy of the document's `assertionMethod` set. - -**Kind**: instance method of [CoreDocument](#CoreDocument) - - -### coreDocument.keyAgreement() ⇒ Array.<(DIDUrl\|VerificationMethod)> -Returns a copy of the document's `keyAgreement` set. - -**Kind**: instance method of [CoreDocument](#CoreDocument) - - -### coreDocument.capabilityDelegation() ⇒ Array.<(DIDUrl\|VerificationMethod)> -Returns a copy of the document's `capabilityDelegation` set. - -**Kind**: instance method of [CoreDocument](#CoreDocument) - - -### coreDocument.capabilityInvocation() ⇒ Array.<(DIDUrl\|VerificationMethod)> -Returns a copy of the document's `capabilityInvocation` set. - -**Kind**: instance method of [CoreDocument](#CoreDocument) - - -### coreDocument.properties() ⇒ Map.<string, any> -Returns a copy of the custom DID Document properties. - -**Kind**: instance method of [CoreDocument](#CoreDocument) - - -### coreDocument.setPropertyUnchecked(key, value) -Sets a custom property in the DID Document. -If the value is set to `null`, the custom property will be removed. - -### WARNING - -This method can overwrite existing properties like `id` and result in an invalid document. - -**Kind**: instance method of [CoreDocument](#CoreDocument) - -| Param | Type | -| --- | --- | -| key | string | -| value | any | - - - -### coreDocument.service() ⇒ [Array.<Service>](#Service) -Returns a set of all [Service](#Service) in the document. - -**Kind**: instance method of [CoreDocument](#CoreDocument) - - -### coreDocument.insertService(service) -Add a new [Service](#Service) to the document. - -Errors if there already exists a service or verification method with the same id. - -**Kind**: instance method of [CoreDocument](#CoreDocument) - -| Param | Type | -| --- | --- | -| service | [Service](#Service) | - - - -### coreDocument.removeService(didUrl) ⇒ [Service](#Service) \| undefined -Remove a [Service](#Service) identified by the given [DIDUrl](#DIDUrl) from the document. - -Returns `true` if the service was removed. - -**Kind**: instance method of [CoreDocument](#CoreDocument) - -| Param | Type | -| --- | --- | -| didUrl | [DIDUrl](#DIDUrl) | - - - -### coreDocument.resolveService(query) ⇒ [Service](#Service) \| undefined -Returns the first [Service](#Service) with an `id` property matching the provided `query`, -if present. - -**Kind**: instance method of [CoreDocument](#CoreDocument) - -| Param | Type | -| --- | --- | -| query | [DIDUrl](#DIDUrl) \| string | - - - -### coreDocument.methods([scope]) ⇒ [Array.<VerificationMethod>](#VerificationMethod) -Returns a list of all [VerificationMethod](#VerificationMethod) in the DID Document, -whose verification relationship matches `scope`. - -If `scope` is not set, a list over the **embedded** methods is returned. - -**Kind**: instance method of [CoreDocument](#CoreDocument) - -| Param | Type | -| --- | --- | -| [scope] | [MethodScope](#MethodScope) \| undefined | - - - -### coreDocument.verificationRelationships() ⇒ Array.<(DIDUrl\|VerificationMethod)> -Returns an array of all verification relationships. - -**Kind**: instance method of [CoreDocument](#CoreDocument) - - -### coreDocument.insertMethod(method, scope) -Adds a new `method` to the document in the given `scope`. - -**Kind**: instance method of [CoreDocument](#CoreDocument) - -| Param | Type | -| --- | --- | -| method | [VerificationMethod](#VerificationMethod) | -| scope | [MethodScope](#MethodScope) | - - - -### coreDocument.removeMethod(did) ⇒ [VerificationMethod](#VerificationMethod) \| undefined -Removes all references to the specified Verification Method. - -**Kind**: instance method of [CoreDocument](#CoreDocument) - -| Param | Type | -| --- | --- | -| did | [DIDUrl](#DIDUrl) | - - - -### coreDocument.resolveMethod(query, [scope]) ⇒ [VerificationMethod](#VerificationMethod) \| undefined -Returns a copy of the first verification method with an `id` property -matching the provided `query` and the verification relationship -specified by `scope`, if present. - -**Kind**: instance method of [CoreDocument](#CoreDocument) - -| Param | Type | -| --- | --- | -| query | [DIDUrl](#DIDUrl) \| string | -| [scope] | [MethodScope](#MethodScope) \| undefined | - - - -### coreDocument.attachMethodRelationship(didUrl, relationship) ⇒ boolean -Attaches the relationship to the given method, if the method exists. - -Note: The method needs to be in the set of verification methods, -so it cannot be an embedded one. - -**Kind**: instance method of [CoreDocument](#CoreDocument) - -| Param | Type | -| --- | --- | -| didUrl | [DIDUrl](#DIDUrl) | -| relationship | [MethodRelationship](#MethodRelationship) | - - - -### coreDocument.detachMethodRelationship(didUrl, relationship) ⇒ boolean -Detaches the given relationship from the given method, if the method exists. - -**Kind**: instance method of [CoreDocument](#CoreDocument) - -| Param | Type | -| --- | --- | -| didUrl | [DIDUrl](#DIDUrl) | -| relationship | [MethodRelationship](#MethodRelationship) | - - - -### coreDocument.verifyJws(jws, options, signatureVerifier, [detachedPayload]) ⇒ [DecodedJws](#DecodedJws) -Decodes and verifies the provided JWS according to the passed `options` and `signatureVerifier`. - If no `signatureVerifier` argument is provided a default verifier will be used that is (only) capable of -verifying EdDSA signatures. - -Regardless of which options are passed the following conditions must be met in order for a verification attempt to -take place. -- The JWS must be encoded according to the JWS compact serialization. -- The `kid` value in the protected header must be an identifier of a verification method in this DID document, -or set explicitly in the `options`. - -**Kind**: instance method of [CoreDocument](#CoreDocument) - -| Param | Type | -| --- | --- | -| jws | [Jws](#Jws) | -| options | [JwsVerificationOptions](#JwsVerificationOptions) | -| signatureVerifier | IJwsVerifier | -| [detachedPayload] | string \| undefined | - - - -### coreDocument.revokeCredentials(serviceQuery, indices) -If the document has a [RevocationBitmap](#RevocationBitmap) service identified by `serviceQuery`, -revoke all specified `indices`. - -**Kind**: instance method of [CoreDocument](#CoreDocument) - -| Param | Type | -| --- | --- | -| serviceQuery | [DIDUrl](#DIDUrl) \| string | -| indices | number \| Array.<number> | - - - -### coreDocument.unrevokeCredentials(serviceQuery, indices) -If the document has a [RevocationBitmap](#RevocationBitmap) service identified by `serviceQuery`, -unrevoke all specified `indices`. - -**Kind**: instance method of [CoreDocument](#CoreDocument) - -| Param | Type | -| --- | --- | -| serviceQuery | [DIDUrl](#DIDUrl) \| string | -| indices | number \| Array.<number> | - - - -### coreDocument.clone() ⇒ [CoreDocument](#CoreDocument) -Deep clones the [CoreDocument](#CoreDocument). - -**Kind**: instance method of [CoreDocument](#CoreDocument) - - -### coreDocument.\_shallowCloneInternal() ⇒ [CoreDocument](#CoreDocument) -### Warning -This is for internal use only. Do not rely on or call this method. - -**Kind**: instance method of [CoreDocument](#CoreDocument) - - -### coreDocument.\_strongCountInternal() ⇒ number -### Warning -This is for internal use only. Do not rely on or call this method. - -**Kind**: instance method of [CoreDocument](#CoreDocument) - - -### coreDocument.toJSON() ⇒ any -Serializes to a plain JS representation. - -**Kind**: instance method of [CoreDocument](#CoreDocument) - - -### coreDocument.generateMethod(storage, keyType, alg, fragment, scope) ⇒ Promise.<string> -Generate new key material in the given `storage` and insert a new verification method with the corresponding -public key material into the DID document. - -- If no fragment is given the `kid` of the generated JWK is used, if it is set, otherwise an error is returned. -- The `keyType` must be compatible with the given `storage`. `Storage`s are expected to export key type constants -for that use case. - -The fragment of the generated method is returned. - -**Kind**: instance method of [CoreDocument](#CoreDocument) - -| Param | Type | -| --- | --- | -| storage | [Storage](#Storage) | -| keyType | string | -| alg | JwsAlgorithm | -| fragment | string \| undefined | -| scope | [MethodScope](#MethodScope) | - - - -### coreDocument.purgeMethod(storage, id) ⇒ Promise.<void> -Remove the method identified by the `fragment` from the document and delete the corresponding key material in -the `storage`. - -**Kind**: instance method of [CoreDocument](#CoreDocument) - -| Param | Type | -| --- | --- | -| storage | [Storage](#Storage) | -| id | [DIDUrl](#DIDUrl) | - - - -### coreDocument.createJws(storage, fragment, payload, options) ⇒ [Promise.<Jws>](#Jws) -Sign the `payload` according to `options` with the storage backed private key corresponding to the public key -material in the verification method identified by the given `fragment. - -Upon success a string representing a JWS encoded according to the Compact JWS Serialization format is returned. -See [RFC7515 section 3.1](https://www.rfc-editor.org/rfc/rfc7515#section-3.1). - -**Kind**: instance method of [CoreDocument](#CoreDocument) - -| Param | Type | -| --- | --- | -| storage | [Storage](#Storage) | -| fragment | string | -| payload | string | -| options | [JwsSignatureOptions](#JwsSignatureOptions) | - - - -### coreDocument.createCredentialJwt(storage, fragment, credential, options, [custom_claims]) ⇒ [Promise.<Jwt>](#Jwt) -Produces a JWT where the payload is produced from the given `credential` -in accordance with [VC Data Model v1.1](https://www.w3.org/TR/vc-data-model/#json-web-token). - -Unless the `kid` is explicitly set in the options, the `kid` in the protected header is the `id` -of the method identified by `fragment` and the JWS signature will be produced by the corresponding -private key backed by the `storage` in accordance with the passed `options`. - -The `custom_claims` can be used to set additional claims on the resulting JWT. - -**Kind**: instance method of [CoreDocument](#CoreDocument) - -| Param | Type | -| --- | --- | -| storage | [Storage](#Storage) | -| fragment | string | -| credential | [Credential](#Credential) | -| options | [JwsSignatureOptions](#JwsSignatureOptions) | -| [custom_claims] | Record.<string, any> \| undefined | - - - -### coreDocument.createPresentationJwt(storage, fragment, presentation, signature_options, presentation_options) ⇒ [Promise.<Jwt>](#Jwt) -Produces a JWT where the payload is produced from the given presentation. -in accordance with [VC Data Model v1.1](https://www.w3.org/TR/vc-data-model/#json-web-token). - -Unless the `kid` is explicitly set in the options, the `kid` in the protected header is the `id` -of the method identified by `fragment` and the JWS signature will be produced by the corresponding -private key backed by the `storage` in accordance with the passed `options`. - -**Kind**: instance method of [CoreDocument](#CoreDocument) - -| Param | Type | -| --- | --- | -| storage | [Storage](#Storage) | -| fragment | string | -| presentation | [Presentation](#Presentation) | -| signature_options | [JwsSignatureOptions](#JwsSignatureOptions) | -| presentation_options | [JwtPresentationOptions](#JwtPresentationOptions) | - - - -### CoreDocument.fromJSON(json) ⇒ [CoreDocument](#CoreDocument) -Deserializes an instance from a plain JS representation. - -**Kind**: static method of [CoreDocument](#CoreDocument) - -| Param | Type | -| --- | --- | -| json | any | - - - -## Credential -**Kind**: global class - -* [Credential](#Credential) - * [new Credential(values)](#new_Credential_new) - * _instance_ - * [.context()](#Credential+context) ⇒ Array.<(string\|Record.<string, any>)> - * [.id()](#Credential+id) ⇒ string \| undefined - * [.type()](#Credential+type) ⇒ Array.<string> - * [.credentialSubject()](#Credential+credentialSubject) ⇒ Array.<Subject> - * [.issuer()](#Credential+issuer) ⇒ string \| Issuer - * [.issuanceDate()](#Credential+issuanceDate) ⇒ [Timestamp](#Timestamp) - * [.expirationDate()](#Credential+expirationDate) ⇒ [Timestamp](#Timestamp) \| undefined - * [.credentialStatus()](#Credential+credentialStatus) ⇒ Array.<Status> - * [.credentialSchema()](#Credential+credentialSchema) ⇒ Array.<Schema> - * [.refreshService()](#Credential+refreshService) ⇒ Array.<RefreshService> - * [.termsOfUse()](#Credential+termsOfUse) ⇒ Array.<Policy> - * [.evidence()](#Credential+evidence) ⇒ Array.<Evidence> - * [.nonTransferable()](#Credential+nonTransferable) ⇒ boolean \| undefined - * [.proof()](#Credential+proof) ⇒ [Proof](#Proof) \| undefined - * [.properties()](#Credential+properties) ⇒ Map.<string, any> - * [.setProof([proof])](#Credential+setProof) - * [.toJwtClaims([custom_claims])](#Credential+toJwtClaims) ⇒ Record.<string, any> - * [.toJSON()](#Credential+toJSON) ⇒ any - * [.clone()](#Credential+clone) ⇒ [Credential](#Credential) - * _static_ - * [.BaseContext()](#Credential.BaseContext) ⇒ string - * [.BaseType()](#Credential.BaseType) ⇒ string - * [.createDomainLinkageCredential(values)](#Credential.createDomainLinkageCredential) ⇒ [Credential](#Credential) - * [.fromJSON(json)](#Credential.fromJSON) ⇒ [Credential](#Credential) - - - -### new Credential(values) -Constructs a new [Credential](#Credential). - - -| Param | Type | -| --- | --- | -| values | ICredential | - - - -### credential.context() ⇒ Array.<(string\|Record.<string, any>)> -Returns a copy of the JSON-LD context(s) applicable to the [Credential](#Credential). - -**Kind**: instance method of [Credential](#Credential) - - -### credential.id() ⇒ string \| undefined -Returns a copy of the unique `URI` identifying the [Credential](#Credential) . - -**Kind**: instance method of [Credential](#Credential) - - -### credential.type() ⇒ Array.<string> -Returns a copy of the URIs defining the type of the [Credential](#Credential). - -**Kind**: instance method of [Credential](#Credential) - - -### credential.credentialSubject() ⇒ Array.<Subject> -Returns a copy of the [Credential](#Credential) subject(s). - -**Kind**: instance method of [Credential](#Credential) - - -### credential.issuer() ⇒ string \| Issuer -Returns a copy of the issuer of the [Credential](#Credential). - -**Kind**: instance method of [Credential](#Credential) - - -### credential.issuanceDate() ⇒ [Timestamp](#Timestamp) -Returns a copy of the timestamp of when the [Credential](#Credential) becomes valid. - -**Kind**: instance method of [Credential](#Credential) - - -### credential.expirationDate() ⇒ [Timestamp](#Timestamp) \| undefined -Returns a copy of the timestamp of when the [Credential](#Credential) should no longer be considered valid. - -**Kind**: instance method of [Credential](#Credential) - - -### credential.credentialStatus() ⇒ Array.<Status> -Returns a copy of the information used to determine the current status of the [Credential](#Credential). - -**Kind**: instance method of [Credential](#Credential) - - -### credential.credentialSchema() ⇒ Array.<Schema> -Returns a copy of the information used to assist in the enforcement of a specific [Credential](#Credential) structure. - -**Kind**: instance method of [Credential](#Credential) - - -### credential.refreshService() ⇒ Array.<RefreshService> -Returns a copy of the service(s) used to refresh an expired [Credential](#Credential). - -**Kind**: instance method of [Credential](#Credential) - - -### credential.termsOfUse() ⇒ Array.<Policy> -Returns a copy of the terms-of-use specified by the [Credential](#Credential) issuer. - -**Kind**: instance method of [Credential](#Credential) - - -### credential.evidence() ⇒ Array.<Evidence> -Returns a copy of the human-readable evidence used to support the claims within the [Credential](#Credential). - -**Kind**: instance method of [Credential](#Credential) - - -### credential.nonTransferable() ⇒ boolean \| undefined -Returns whether or not the [Credential](#Credential) must only be contained within a [Presentation](#Presentation) -with a proof issued from the [Credential](#Credential) subject. - -**Kind**: instance method of [Credential](#Credential) - - -### credential.proof() ⇒ [Proof](#Proof) \| undefined -Optional cryptographic proof, unrelated to JWT. - -**Kind**: instance method of [Credential](#Credential) - - -### credential.properties() ⇒ Map.<string, any> -Returns a copy of the miscellaneous properties on the [Credential](#Credential). - -**Kind**: instance method of [Credential](#Credential) - - -### credential.setProof([proof]) -Sets the `proof` property of the [Credential](#Credential). - -Note that this proof is not related to JWT. - -**Kind**: instance method of [Credential](#Credential) - -| Param | Type | -| --- | --- | -| [proof] | [Proof](#Proof) \| undefined | - - - -### credential.toJwtClaims([custom_claims]) ⇒ Record.<string, any> -Serializes the `Credential` as a JWT claims set -in accordance with [VC Data Model v1.1](https://www.w3.org/TR/vc-data-model/#json-web-token). - -The resulting object can be used as the payload of a JWS when issuing the credential. - -**Kind**: instance method of [Credential](#Credential) - -| Param | Type | -| --- | --- | -| [custom_claims] | Record.<string, any> \| undefined | - - - -### credential.toJSON() ⇒ any -Serializes this to a JSON object. - -**Kind**: instance method of [Credential](#Credential) - - -### credential.clone() ⇒ [Credential](#Credential) -Deep clones the object. - -**Kind**: instance method of [Credential](#Credential) - - -### Credential.BaseContext() ⇒ string -Returns the base JSON-LD context. - -**Kind**: static method of [Credential](#Credential) - - -### Credential.BaseType() ⇒ string -Returns the base type. - -**Kind**: static method of [Credential](#Credential) - - -### Credential.createDomainLinkageCredential(values) ⇒ [Credential](#Credential) -**Kind**: static method of [Credential](#Credential) - -| Param | Type | -| --- | --- | -| values | IDomainLinkageCredential | - - - -### Credential.fromJSON(json) ⇒ [Credential](#Credential) -Deserializes an instance from a JSON object. - -**Kind**: static method of [Credential](#Credential) - -| Param | Type | -| --- | --- | -| json | any | - - - -## CustomMethodData -A custom verification method data format. - -**Kind**: global class - -* [CustomMethodData](#CustomMethodData) - * [new CustomMethodData(name, data)](#new_CustomMethodData_new) - * _instance_ - * [.clone()](#CustomMethodData+clone) ⇒ [CustomMethodData](#CustomMethodData) - * [.toJSON()](#CustomMethodData+toJSON) ⇒ any - * _static_ - * [.fromJSON(json)](#CustomMethodData.fromJSON) ⇒ [CustomMethodData](#CustomMethodData) - - - -### new CustomMethodData(name, data) - -| Param | Type | -| --- | --- | -| name | string | -| data | any | - - - -### customMethodData.clone() ⇒ [CustomMethodData](#CustomMethodData) -Deep clones the object. - -**Kind**: instance method of [CustomMethodData](#CustomMethodData) - - -### customMethodData.toJSON() ⇒ any -Serializes this to a JSON object. - -**Kind**: instance method of [CustomMethodData](#CustomMethodData) - - -### CustomMethodData.fromJSON(json) ⇒ [CustomMethodData](#CustomMethodData) -Deserializes an instance from a JSON object. - -**Kind**: static method of [CustomMethodData](#CustomMethodData) - -| Param | Type | -| --- | --- | -| json | any | - - - -## DIDUrl -A method agnostic DID Url. - -**Kind**: global class - -* [DIDUrl](#DIDUrl) - * _instance_ - * [.did()](#DIDUrl+did) ⇒ [CoreDID](#CoreDID) - * [.urlStr()](#DIDUrl+urlStr) ⇒ string - * [.fragment()](#DIDUrl+fragment) ⇒ string \| undefined - * [.setFragment([value])](#DIDUrl+setFragment) - * [.path()](#DIDUrl+path) ⇒ string \| undefined - * [.setPath([value])](#DIDUrl+setPath) - * [.query()](#DIDUrl+query) ⇒ string \| undefined - * [.setQuery([value])](#DIDUrl+setQuery) - * [.join(segment)](#DIDUrl+join) ⇒ [DIDUrl](#DIDUrl) - * [.toString()](#DIDUrl+toString) ⇒ string - * [.toJSON()](#DIDUrl+toJSON) ⇒ any - * [.clone()](#DIDUrl+clone) ⇒ [DIDUrl](#DIDUrl) - * _static_ - * [.parse(input)](#DIDUrl.parse) ⇒ [DIDUrl](#DIDUrl) - * [.fromJSON(json)](#DIDUrl.fromJSON) ⇒ [DIDUrl](#DIDUrl) - - - -### didUrl.did() ⇒ [CoreDID](#CoreDID) -Return a copy of the [CoreDID](#CoreDID) section of the [DIDUrl](#DIDUrl). - -**Kind**: instance method of [DIDUrl](#DIDUrl) - - -### didUrl.urlStr() ⇒ string -Return a copy of the relative DID Url as a string, including only the path, query, and fragment. - -**Kind**: instance method of [DIDUrl](#DIDUrl) - - -### didUrl.fragment() ⇒ string \| undefined -Returns a copy of the [DIDUrl](#DIDUrl) method fragment, if any. Excludes the leading '#'. - -**Kind**: instance method of [DIDUrl](#DIDUrl) - - -### didUrl.setFragment([value]) -Sets the `fragment` component of the [DIDUrl](#DIDUrl). - -**Kind**: instance method of [DIDUrl](#DIDUrl) - -| Param | Type | -| --- | --- | -| [value] | string \| undefined | - - - -### didUrl.path() ⇒ string \| undefined -Returns a copy of the [DIDUrl](#DIDUrl) path. - -**Kind**: instance method of [DIDUrl](#DIDUrl) - - -### didUrl.setPath([value]) -Sets the `path` component of the [DIDUrl](#DIDUrl). - -**Kind**: instance method of [DIDUrl](#DIDUrl) - -| Param | Type | -| --- | --- | -| [value] | string \| undefined | - - - -### didUrl.query() ⇒ string \| undefined -Returns a copy of the [DIDUrl](#DIDUrl) method query, if any. Excludes the leading '?'. - -**Kind**: instance method of [DIDUrl](#DIDUrl) - - -### didUrl.setQuery([value]) -Sets the `query` component of the [DIDUrl](#DIDUrl). - -**Kind**: instance method of [DIDUrl](#DIDUrl) - -| Param | Type | -| --- | --- | -| [value] | string \| undefined | - - - -### didUrl.join(segment) ⇒ [DIDUrl](#DIDUrl) -Append a string representing a path, query, and/or fragment, returning a new [DIDUrl](#DIDUrl). - -Must begin with a valid delimiter character: '/', '?', '#'. Overwrites the existing URL -segment and any following segments in order of path, query, then fragment. - -I.e. -- joining a path will clear the query and fragment. -- joining a query will clear the fragment. -- joining a fragment will only overwrite the fragment. - -**Kind**: instance method of [DIDUrl](#DIDUrl) - -| Param | Type | -| --- | --- | -| segment | string | - - - -### didUrl.toString() ⇒ string -Returns the [DIDUrl](#DIDUrl) as a string. - -**Kind**: instance method of [DIDUrl](#DIDUrl) - - -### didUrl.toJSON() ⇒ any -Serializes this to a JSON object. - -**Kind**: instance method of [DIDUrl](#DIDUrl) - - -### didUrl.clone() ⇒ [DIDUrl](#DIDUrl) -Deep clones the object. - -**Kind**: instance method of [DIDUrl](#DIDUrl) - - -### DIDUrl.parse(input) ⇒ [DIDUrl](#DIDUrl) -Parses a [DIDUrl](#DIDUrl) from the input string. - -**Kind**: static method of [DIDUrl](#DIDUrl) - -| Param | Type | -| --- | --- | -| input | string | - - - -### DIDUrl.fromJSON(json) ⇒ [DIDUrl](#DIDUrl) -Deserializes an instance from a JSON object. - -**Kind**: static method of [DIDUrl](#DIDUrl) - -| Param | Type | -| --- | --- | -| json | any | - - - -## DecodedJws -A cryptographically verified decoded token from a JWS. - -Contains the decoded headers and the raw claims. - -**Kind**: global class - -* [DecodedJws](#DecodedJws) - * [.claims()](#DecodedJws+claims) ⇒ string - * [.claimsBytes()](#DecodedJws+claimsBytes) ⇒ Uint8Array - * [.protectedHeader()](#DecodedJws+protectedHeader) ⇒ [JwsHeader](#JwsHeader) - * [.clone()](#DecodedJws+clone) ⇒ [DecodedJws](#DecodedJws) - * [.toJSON()](#DecodedJws+toJSON) ⇒ any - - - -### decodedJws.claims() ⇒ string -Returns a copy of the parsed claims represented as a string. - -# Errors -An error is thrown if the claims cannot be represented as a string. - -This error can only occur if the Token was decoded from a detached payload. - -**Kind**: instance method of [DecodedJws](#DecodedJws) - - -### decodedJws.claimsBytes() ⇒ Uint8Array -Return a copy of the parsed claims represented as an array of bytes. - -**Kind**: instance method of [DecodedJws](#DecodedJws) - - -### decodedJws.protectedHeader() ⇒ [JwsHeader](#JwsHeader) -Returns a copy of the protected header. - -**Kind**: instance method of [DecodedJws](#DecodedJws) - - -### decodedJws.clone() ⇒ [DecodedJws](#DecodedJws) -Deep clones the object. - -**Kind**: instance method of [DecodedJws](#DecodedJws) - - -### decodedJws.toJSON() ⇒ any -Serializes this to a JSON object. - -**Kind**: instance method of [DecodedJws](#DecodedJws) - - -## DecodedJwtCredential -A cryptographically verified and decoded Credential. - -Note that having an instance of this type only means the JWS it was constructed from was verified. -It does not imply anything about a potentially present proof property on the credential itself. - -**Kind**: global class - -* [DecodedJwtCredential](#DecodedJwtCredential) - * [.credential()](#DecodedJwtCredential+credential) ⇒ [Credential](#Credential) - * [.protectedHeader()](#DecodedJwtCredential+protectedHeader) ⇒ [JwsHeader](#JwsHeader) - * [.customClaims()](#DecodedJwtCredential+customClaims) ⇒ Record.<string, any> \| undefined - * [.intoCredential()](#DecodedJwtCredential+intoCredential) ⇒ [Credential](#Credential) - - - -### decodedJwtCredential.credential() ⇒ [Credential](#Credential) -Returns a copy of the credential parsed to the [Verifiable Credentials Data model](https://www.w3.org/TR/vc-data-model/). - -**Kind**: instance method of [DecodedJwtCredential](#DecodedJwtCredential) - - -### decodedJwtCredential.protectedHeader() ⇒ [JwsHeader](#JwsHeader) -Returns a copy of the protected header parsed from the decoded JWS. - -**Kind**: instance method of [DecodedJwtCredential](#DecodedJwtCredential) - - -### decodedJwtCredential.customClaims() ⇒ Record.<string, any> \| undefined -The custom claims parsed from the JWT. - -**Kind**: instance method of [DecodedJwtCredential](#DecodedJwtCredential) - - -### decodedJwtCredential.intoCredential() ⇒ [Credential](#Credential) -Consumes the object and returns the decoded credential. - -### Warning - -This destroys the [DecodedJwtCredential](#DecodedJwtCredential) object. - -**Kind**: instance method of [DecodedJwtCredential](#DecodedJwtCredential) - - -## DecodedJwtPresentation -A cryptographically verified and decoded presentation. - -Note that having an instance of this type only means the JWS it was constructed from was verified. -It does not imply anything about a potentially present proof property on the presentation itself. - -**Kind**: global class - -* [DecodedJwtPresentation](#DecodedJwtPresentation) - * [.presentation()](#DecodedJwtPresentation+presentation) ⇒ [Presentation](#Presentation) - * [.protectedHeader()](#DecodedJwtPresentation+protectedHeader) ⇒ [JwsHeader](#JwsHeader) - * [.intoPresentation()](#DecodedJwtPresentation+intoPresentation) ⇒ [Presentation](#Presentation) - * [.expirationDate()](#DecodedJwtPresentation+expirationDate) ⇒ [Timestamp](#Timestamp) \| undefined - * [.issuanceDate()](#DecodedJwtPresentation+issuanceDate) ⇒ [Timestamp](#Timestamp) \| undefined - * [.audience()](#DecodedJwtPresentation+audience) ⇒ string \| undefined - * [.customClaims()](#DecodedJwtPresentation+customClaims) ⇒ Record.<string, any> \| undefined - - - -### decodedJwtPresentation.presentation() ⇒ [Presentation](#Presentation) -**Kind**: instance method of [DecodedJwtPresentation](#DecodedJwtPresentation) - - -### decodedJwtPresentation.protectedHeader() ⇒ [JwsHeader](#JwsHeader) -Returns a copy of the protected header parsed from the decoded JWS. - -**Kind**: instance method of [DecodedJwtPresentation](#DecodedJwtPresentation) - - -### decodedJwtPresentation.intoPresentation() ⇒ [Presentation](#Presentation) -Consumes the object and returns the decoded presentation. - -### Warning -This destroys the [DecodedJwtPresentation](#DecodedJwtPresentation) object. - -**Kind**: instance method of [DecodedJwtPresentation](#DecodedJwtPresentation) - - -### decodedJwtPresentation.expirationDate() ⇒ [Timestamp](#Timestamp) \| undefined -The expiration date parsed from the JWT claims. - -**Kind**: instance method of [DecodedJwtPresentation](#DecodedJwtPresentation) - - -### decodedJwtPresentation.issuanceDate() ⇒ [Timestamp](#Timestamp) \| undefined -The issuance date parsed from the JWT claims. - -**Kind**: instance method of [DecodedJwtPresentation](#DecodedJwtPresentation) - - -### decodedJwtPresentation.audience() ⇒ string \| undefined -The `aud` property parsed from JWT claims. - -**Kind**: instance method of [DecodedJwtPresentation](#DecodedJwtPresentation) - - -### decodedJwtPresentation.customClaims() ⇒ Record.<string, any> \| undefined -The custom claims parsed from the JWT. - -**Kind**: instance method of [DecodedJwtPresentation](#DecodedJwtPresentation) - - -## Disclosure -Represents an elements constructing a disclosure. -Object properties and array elements disclosures are supported. - -See: https://www.ietf.org/archive/id/draft-ietf-oauth-selective-disclosure-jwt-07.html#name-disclosures - -**Kind**: global class - -* [Disclosure](#Disclosure) - * [new Disclosure(salt, claim_name, claim_value)](#new_Disclosure_new) - * _instance_ - * [.disclosure()](#Disclosure+disclosure) ⇒ string - * [.toEncodedString()](#Disclosure+toEncodedString) ⇒ string - * [.toString()](#Disclosure+toString) ⇒ string - * [.salt()](#Disclosure+salt) ⇒ string - * [.claimName()](#Disclosure+claimName) ⇒ string \| undefined - * [.claimValue()](#Disclosure+claimValue) ⇒ any - * [.toJSON()](#Disclosure+toJSON) ⇒ any - * _static_ - * [.parse(disclosure)](#Disclosure.parse) ⇒ [Disclosure](#Disclosure) - * [.fromJSON(json)](#Disclosure.fromJSON) ⇒ [Disclosure](#Disclosure) - - - -### new Disclosure(salt, claim_name, claim_value) - -| Param | Type | -| --- | --- | -| salt | string | -| claim_name | string \| undefined | -| claim_value | any | - - - -### disclosure.disclosure() ⇒ string -Returns a copy of the base64url-encoded string. - -**Kind**: instance method of [Disclosure](#Disclosure) - - -### disclosure.toEncodedString() ⇒ string -Returns a copy of the base64url-encoded string. - -**Kind**: instance method of [Disclosure](#Disclosure) - - -### disclosure.toString() ⇒ string -Returns a copy of the base64url-encoded string. - -**Kind**: instance method of [Disclosure](#Disclosure) - - -### disclosure.salt() ⇒ string -Returns a copy of the salt value. - -**Kind**: instance method of [Disclosure](#Disclosure) - - -### disclosure.claimName() ⇒ string \| undefined -Returns a copy of the claim name, optional for array elements. - -**Kind**: instance method of [Disclosure](#Disclosure) - - -### disclosure.claimValue() ⇒ any -Returns a copy of the claim Value which can be of any type. - -**Kind**: instance method of [Disclosure](#Disclosure) - - -### disclosure.toJSON() ⇒ any -Serializes this to a JSON object. - -**Kind**: instance method of [Disclosure](#Disclosure) - - -### Disclosure.parse(disclosure) ⇒ [Disclosure](#Disclosure) -Parses a Base64 encoded disclosure into a `Disclosure`. - -## Error - -Returns an `InvalidDisclosure` if input is not a valid disclosure. - -**Kind**: static method of [Disclosure](#Disclosure) - -| Param | Type | -| --- | --- | -| disclosure | string | - - - -### Disclosure.fromJSON(json) ⇒ [Disclosure](#Disclosure) -Deserializes an instance from a JSON object. - -**Kind**: static method of [Disclosure](#Disclosure) - -| Param | Type | -| --- | --- | -| json | any | - - - -## DomainLinkageConfiguration -DID Configuration Resource which contains Domain Linkage Credentials. -It can be placed in an origin's `.well-known` directory to prove linkage between the origin and a DID. -See: - -Note: -- Only the [JSON Web Token Proof Format](https://identity.foundation/.well-known/resources/did-configuration/#json-web-token-proof-format) - -**Kind**: global class - -* [DomainLinkageConfiguration](#DomainLinkageConfiguration) - * [new DomainLinkageConfiguration(linkedDids)](#new_DomainLinkageConfiguration_new) - * _instance_ - * [.linkedDids()](#DomainLinkageConfiguration+linkedDids) ⇒ [Array.<Jwt>](#Jwt) - * [.issuers()](#DomainLinkageConfiguration+issuers) ⇒ [Array.<CoreDID>](#CoreDID) - * [.toJSON()](#DomainLinkageConfiguration+toJSON) ⇒ any - * [.clone()](#DomainLinkageConfiguration+clone) ⇒ [DomainLinkageConfiguration](#DomainLinkageConfiguration) - * _static_ - * [.fromJSON(json)](#DomainLinkageConfiguration.fromJSON) ⇒ [DomainLinkageConfiguration](#DomainLinkageConfiguration) - - - -### new DomainLinkageConfiguration(linkedDids) -Constructs a new [DomainLinkageConfiguration](#DomainLinkageConfiguration). - - -| Param | Type | -| --- | --- | -| linkedDids | [Array.<Jwt>](#Jwt) | - - - -### domainLinkageConfiguration.linkedDids() ⇒ [Array.<Jwt>](#Jwt) -List of the Domain Linkage Credentials. - -**Kind**: instance method of [DomainLinkageConfiguration](#DomainLinkageConfiguration) - - -### domainLinkageConfiguration.issuers() ⇒ [Array.<CoreDID>](#CoreDID) -List of the issuers of the Domain Linkage Credentials. - -**Kind**: instance method of [DomainLinkageConfiguration](#DomainLinkageConfiguration) - - -### domainLinkageConfiguration.toJSON() ⇒ any -Serializes this to a JSON object. - -**Kind**: instance method of [DomainLinkageConfiguration](#DomainLinkageConfiguration) - - -### domainLinkageConfiguration.clone() ⇒ [DomainLinkageConfiguration](#DomainLinkageConfiguration) -Deep clones the object. - -**Kind**: instance method of [DomainLinkageConfiguration](#DomainLinkageConfiguration) - - -### DomainLinkageConfiguration.fromJSON(json) ⇒ [DomainLinkageConfiguration](#DomainLinkageConfiguration) -Deserializes an instance from a JSON object. - -**Kind**: static method of [DomainLinkageConfiguration](#DomainLinkageConfiguration) - -| Param | Type | -| --- | --- | -| json | any | - - - -## Duration -A span of time. - -**Kind**: global class - -* [Duration](#Duration) - * _instance_ - * [.toJSON()](#Duration+toJSON) ⇒ any - * _static_ - * [.seconds(seconds)](#Duration.seconds) ⇒ [Duration](#Duration) - * [.minutes(minutes)](#Duration.minutes) ⇒ [Duration](#Duration) - * [.hours(hours)](#Duration.hours) ⇒ [Duration](#Duration) - * [.days(days)](#Duration.days) ⇒ [Duration](#Duration) - * [.weeks(weeks)](#Duration.weeks) ⇒ [Duration](#Duration) - * [.fromJSON(json)](#Duration.fromJSON) ⇒ [Duration](#Duration) - - - -### duration.toJSON() ⇒ any -Serializes this to a JSON object. - -**Kind**: instance method of [Duration](#Duration) - - -### Duration.seconds(seconds) ⇒ [Duration](#Duration) -Create a new [Duration](#Duration) with the given number of seconds. - -**Kind**: static method of [Duration](#Duration) - -| Param | Type | -| --- | --- | -| seconds | number | - - - -### Duration.minutes(minutes) ⇒ [Duration](#Duration) -Create a new [Duration](#Duration) with the given number of minutes. - -**Kind**: static method of [Duration](#Duration) - -| Param | Type | -| --- | --- | -| minutes | number | - - - -### Duration.hours(hours) ⇒ [Duration](#Duration) -Create a new [Duration](#Duration) with the given number of hours. - -**Kind**: static method of [Duration](#Duration) - -| Param | Type | -| --- | --- | -| hours | number | - - - -### Duration.days(days) ⇒ [Duration](#Duration) -Create a new [Duration](#Duration) with the given number of days. - -**Kind**: static method of [Duration](#Duration) - -| Param | Type | -| --- | --- | -| days | number | - - - -### Duration.weeks(weeks) ⇒ [Duration](#Duration) -Create a new [Duration](#Duration) with the given number of weeks. - -**Kind**: static method of [Duration](#Duration) - -| Param | Type | -| --- | --- | -| weeks | number | - - - -### Duration.fromJSON(json) ⇒ [Duration](#Duration) -Deserializes an instance from a JSON object. - -**Kind**: static method of [Duration](#Duration) - -| Param | Type | -| --- | --- | -| json | any | - - - -## EdDSAJwsVerifier -An implementor of `IJwsVerifier` that can handle the -`EdDSA` algorithm. - -**Kind**: global class - -* [EdDSAJwsVerifier](#EdDSAJwsVerifier) - * [new EdDSAJwsVerifier()](#new_EdDSAJwsVerifier_new) - * [.verify(alg, signingInput, decodedSignature, publicKey)](#EdDSAJwsVerifier+verify) - - - -### new EdDSAJwsVerifier() -Constructs an EdDSAJwsVerifier. - - - -### edDSAJwsVerifier.verify(alg, signingInput, decodedSignature, publicKey) -Verify a JWS signature secured with the `EdDSA` algorithm. -Only the `Ed25519` curve is supported for now. - -This function is useful when one is building an `IJwsVerifier` that extends the default provided by -the IOTA Identity Framework. - -# Warning - -This function does not check whether `alg = EdDSA` in the protected header. Callers are expected to assert this -prior to calling the function. - -**Kind**: instance method of [EdDSAJwsVerifier](#EdDSAJwsVerifier) - -| Param | Type | -| --- | --- | -| alg | JwsAlgorithm | -| signingInput | Uint8Array | -| decodedSignature | Uint8Array | -| publicKey | [Jwk](#Jwk) | - - - -## IotaDID -A DID conforming to the IOTA DID method specification. - -**Kind**: global class - -* [IotaDID](#IotaDID) - * [new IotaDID(bytes, network)](#new_IotaDID_new) - * _instance_ - * [.network()](#IotaDID+network) ⇒ string - * [.tag()](#IotaDID+tag) ⇒ string - * [.toCoreDid()](#IotaDID+toCoreDid) ⇒ [CoreDID](#CoreDID) - * [.scheme()](#IotaDID+scheme) ⇒ string - * [.authority()](#IotaDID+authority) ⇒ string - * [.method()](#IotaDID+method) ⇒ string - * [.methodId()](#IotaDID+methodId) ⇒ string - * [.join(segment)](#IotaDID+join) ⇒ [DIDUrl](#DIDUrl) - * [.toUrl()](#IotaDID+toUrl) ⇒ [DIDUrl](#DIDUrl) - * [.toAliasId()](#IotaDID+toAliasId) ⇒ string - * [.intoUrl()](#IotaDID+intoUrl) ⇒ [DIDUrl](#DIDUrl) - * [.toString()](#IotaDID+toString) ⇒ string - * [.toJSON()](#IotaDID+toJSON) ⇒ any - * [.clone()](#IotaDID+clone) ⇒ [IotaDID](#IotaDID) - * _static_ - * [.METHOD](#IotaDID.METHOD) ⇒ string - * [.DEFAULT_NETWORK](#IotaDID.DEFAULT_NETWORK) ⇒ string - * [.fromAliasId(aliasId, network)](#IotaDID.fromAliasId) ⇒ [IotaDID](#IotaDID) - * [.placeholder(network)](#IotaDID.placeholder) ⇒ [IotaDID](#IotaDID) - * [.parse(input)](#IotaDID.parse) ⇒ [IotaDID](#IotaDID) - * [.fromJSON(json)](#IotaDID.fromJSON) ⇒ [IotaDID](#IotaDID) - - - -### new IotaDID(bytes, network) -Constructs a new [IotaDID](#IotaDID) from a byte representation of the tag and the given -network name. - -See also [placeholder](#IotaDID.placeholder). - - -| Param | Type | -| --- | --- | -| bytes | Uint8Array | -| network | string | - - - -### did.network() ⇒ string -Returns the Tangle network name of the [IotaDID](#IotaDID). - -**Kind**: instance method of [IotaDID](#IotaDID) - - -### did.tag() ⇒ string -Returns a copy of the unique tag of the [IotaDID](#IotaDID). - -**Kind**: instance method of [IotaDID](#IotaDID) - - -### did.toCoreDid() ⇒ [CoreDID](#CoreDID) -Returns the DID represented as a [CoreDID](#CoreDID). - -**Kind**: instance method of [IotaDID](#IotaDID) - - -### did.scheme() ⇒ string -Returns the `DID` scheme. - -E.g. -- `"did:example:12345678" -> "did"` -- `"did:iota:main:12345678" -> "did"` - -**Kind**: instance method of [IotaDID](#IotaDID) - - -### did.authority() ⇒ string -Returns the `DID` authority: the method name and method-id. - -E.g. -- `"did:example:12345678" -> "example:12345678"` -- `"did:iota:main:12345678" -> "iota:main:12345678"` - -**Kind**: instance method of [IotaDID](#IotaDID) - - -### did.method() ⇒ string -Returns the `DID` method name. - -E.g. -- `"did:example:12345678" -> "example"` -- `"did:iota:main:12345678" -> "iota"` - -**Kind**: instance method of [IotaDID](#IotaDID) - - -### did.methodId() ⇒ string -Returns the `DID` method-specific ID. - -E.g. -- `"did:example:12345678" -> "12345678"` -- `"did:iota:main:12345678" -> "main:12345678"` - -**Kind**: instance method of [IotaDID](#IotaDID) - - -### did.join(segment) ⇒ [DIDUrl](#DIDUrl) -Construct a new [DIDUrl](#DIDUrl) by joining with a relative DID Url string. - -**Kind**: instance method of [IotaDID](#IotaDID) - -| Param | Type | -| --- | --- | -| segment | string | - - - -### did.toUrl() ⇒ [DIDUrl](#DIDUrl) -Clones the `DID` into a [DIDUrl](#DIDUrl). - -**Kind**: instance method of [IotaDID](#IotaDID) - - -### did.toAliasId() ⇒ string -Returns the hex-encoded AliasId with a '0x' prefix, from the DID tag. - -**Kind**: instance method of [IotaDID](#IotaDID) - - -### did.intoUrl() ⇒ [DIDUrl](#DIDUrl) -Converts the `DID` into a [DIDUrl](#DIDUrl), consuming it. - -**Kind**: instance method of [IotaDID](#IotaDID) - - -### did.toString() ⇒ string -Returns the `DID` as a string. - -**Kind**: instance method of [IotaDID](#IotaDID) - - -### did.toJSON() ⇒ any -Serializes this to a JSON object. - -**Kind**: instance method of [IotaDID](#IotaDID) - - -### did.clone() ⇒ [IotaDID](#IotaDID) -Deep clones the object. - -**Kind**: instance method of [IotaDID](#IotaDID) - - -### IotaDID.METHOD ⇒ string -The IOTA DID method name (`"iota"`). - -**Kind**: static property of [IotaDID](#IotaDID) - - -### IotaDID.DEFAULT\_NETWORK ⇒ string -The default Tangle network (`"iota"`). - -**Kind**: static property of [IotaDID](#IotaDID) - - -### IotaDID.fromAliasId(aliasId, network) ⇒ [IotaDID](#IotaDID) -Constructs a new [IotaDID](#IotaDID) from a hex representation of an Alias Id and the given -network name. - -**Kind**: static method of [IotaDID](#IotaDID) - -| Param | Type | -| --- | --- | -| aliasId | string | -| network | string | - - - -### IotaDID.placeholder(network) ⇒ [IotaDID](#IotaDID) -Creates a new placeholder [IotaDID](#IotaDID) with the given network name. - -E.g. `did:iota:smr:0x0000000000000000000000000000000000000000000000000000000000000000`. - -**Kind**: static method of [IotaDID](#IotaDID) - -| Param | Type | -| --- | --- | -| network | string | - - - -### IotaDID.parse(input) ⇒ [IotaDID](#IotaDID) -Parses a [IotaDID](#IotaDID) from the input string. - -**Kind**: static method of [IotaDID](#IotaDID) - -| Param | Type | -| --- | --- | -| input | string | - - - -### IotaDID.fromJSON(json) ⇒ [IotaDID](#IotaDID) -Deserializes an instance from a JSON object. - -**Kind**: static method of [IotaDID](#IotaDID) - -| Param | Type | -| --- | --- | -| json | any | - - - -## IotaDocument -A DID Document adhering to the IOTA DID method specification. - -Note: All methods that involve reading from this class may potentially raise an error -if the object is being concurrently modified. - -**Kind**: global class - -* [IotaDocument](#IotaDocument) - * [new IotaDocument(network)](#new_IotaDocument_new) - * _instance_ - * [.id()](#IotaDocument+id) ⇒ [IotaDID](#IotaDID) - * [.controller()](#IotaDocument+controller) ⇒ [Array.<IotaDID>](#IotaDID) - * [.setController(controller)](#IotaDocument+setController) - * [.alsoKnownAs()](#IotaDocument+alsoKnownAs) ⇒ Array.<string> - * [.setAlsoKnownAs(urls)](#IotaDocument+setAlsoKnownAs) - * [.properties()](#IotaDocument+properties) ⇒ Map.<string, any> - * [.setPropertyUnchecked(key, value)](#IotaDocument+setPropertyUnchecked) - * [.service()](#IotaDocument+service) ⇒ [Array.<Service>](#Service) - * [.insertService(service)](#IotaDocument+insertService) - * [.removeService(did)](#IotaDocument+removeService) ⇒ [Service](#Service) \| undefined - * [.resolveService(query)](#IotaDocument+resolveService) ⇒ [Service](#Service) \| undefined - * [.methods([scope])](#IotaDocument+methods) ⇒ [Array.<VerificationMethod>](#VerificationMethod) - * [.insertMethod(method, scope)](#IotaDocument+insertMethod) - * [.removeMethod(did)](#IotaDocument+removeMethod) ⇒ [VerificationMethod](#VerificationMethod) \| undefined - * [.resolveMethod(query, [scope])](#IotaDocument+resolveMethod) ⇒ [VerificationMethod](#VerificationMethod) \| undefined - * [.attachMethodRelationship(didUrl, relationship)](#IotaDocument+attachMethodRelationship) ⇒ boolean - * [.detachMethodRelationship(didUrl, relationship)](#IotaDocument+detachMethodRelationship) ⇒ boolean - * [.verifyJws(jws, options, signatureVerifier, [detachedPayload])](#IotaDocument+verifyJws) ⇒ [DecodedJws](#DecodedJws) - * [.pack()](#IotaDocument+pack) ⇒ Uint8Array - * [.packWithEncoding(encoding)](#IotaDocument+packWithEncoding) ⇒ Uint8Array - * [.metadata()](#IotaDocument+metadata) ⇒ [IotaDocumentMetadata](#IotaDocumentMetadata) - * [.metadataCreated()](#IotaDocument+metadataCreated) ⇒ [Timestamp](#Timestamp) \| undefined - * [.setMetadataCreated(timestamp)](#IotaDocument+setMetadataCreated) - * [.metadataUpdated()](#IotaDocument+metadataUpdated) ⇒ [Timestamp](#Timestamp) \| undefined - * [.setMetadataUpdated(timestamp)](#IotaDocument+setMetadataUpdated) - * [.metadataDeactivated()](#IotaDocument+metadataDeactivated) ⇒ boolean \| undefined - * [.setMetadataDeactivated([deactivated])](#IotaDocument+setMetadataDeactivated) - * [.metadataStateControllerAddress()](#IotaDocument+metadataStateControllerAddress) ⇒ string \| undefined - * [.metadataGovernorAddress()](#IotaDocument+metadataGovernorAddress) ⇒ string \| undefined - * [.setMetadataPropertyUnchecked(key, value)](#IotaDocument+setMetadataPropertyUnchecked) - * [.revokeCredentials(serviceQuery, indices)](#IotaDocument+revokeCredentials) - * [.unrevokeCredentials(serviceQuery, indices)](#IotaDocument+unrevokeCredentials) - * [.clone()](#IotaDocument+clone) ⇒ [IotaDocument](#IotaDocument) - * [._shallowCloneInternal()](#IotaDocument+_shallowCloneInternal) ⇒ [IotaDocument](#IotaDocument) - * [._strongCountInternal()](#IotaDocument+_strongCountInternal) ⇒ number - * [.toJSON()](#IotaDocument+toJSON) ⇒ any - * [.toCoreDocument()](#IotaDocument+toCoreDocument) ⇒ [CoreDocument](#CoreDocument) - * [.generateMethod(storage, keyType, alg, fragment, scope)](#IotaDocument+generateMethod) ⇒ Promise.<string> - * [.purgeMethod(storage, id)](#IotaDocument+purgeMethod) ⇒ Promise.<void> - * ~~[.createJwt(storage, fragment, payload, options)](#IotaDocument+createJwt) ⇒ [Promise.<Jws>](#Jws)~~ - * [.createJws(storage, fragment, payload, options)](#IotaDocument+createJws) ⇒ [Promise.<Jws>](#Jws) - * [.createCredentialJwt(storage, fragment, credential, options, [custom_claims])](#IotaDocument+createCredentialJwt) ⇒ [Promise.<Jwt>](#Jwt) - * [.createPresentationJwt(storage, fragment, presentation, signature_options, presentation_options)](#IotaDocument+createPresentationJwt) ⇒ [Promise.<Jwt>](#Jwt) - * _static_ - * [.newWithId(id)](#IotaDocument.newWithId) ⇒ [IotaDocument](#IotaDocument) - * [.unpackFromOutput(did, aliasOutput, allowEmpty)](#IotaDocument.unpackFromOutput) ⇒ [IotaDocument](#IotaDocument) - * [.unpackFromBlock(network, block)](#IotaDocument.unpackFromBlock) ⇒ [Array.<IotaDocument>](#IotaDocument) - * [.fromJSON(json)](#IotaDocument.fromJSON) ⇒ [IotaDocument](#IotaDocument) - - - -### new IotaDocument(network) -Constructs an empty IOTA DID Document with a [placeholder](#IotaDID.placeholder) identifier -for the given `network`. - - -| Param | Type | -| --- | --- | -| network | string | - - - -### iotaDocument.id() ⇒ [IotaDID](#IotaDID) -Returns a copy of the DID Document `id`. - -**Kind**: instance method of [IotaDocument](#IotaDocument) - - -### iotaDocument.controller() ⇒ [Array.<IotaDID>](#IotaDID) -Returns a copy of the list of document controllers. - -NOTE: controllers are determined by the `state_controller` unlock condition of the output -during resolution and are omitted when publishing. - -**Kind**: instance method of [IotaDocument](#IotaDocument) - - -### iotaDocument.setController(controller) -Sets the controllers of the document. - -Note: Duplicates will be ignored. -Use `null` to remove all controllers. - -**Kind**: instance method of [IotaDocument](#IotaDocument) - -| Param | Type | -| --- | --- | -| controller | [Array.<IotaDID>](#IotaDID) \| null | - - - -### iotaDocument.alsoKnownAs() ⇒ Array.<string> -Returns a copy of the document's `alsoKnownAs` set. - -**Kind**: instance method of [IotaDocument](#IotaDocument) - - -### iotaDocument.setAlsoKnownAs(urls) -Sets the `alsoKnownAs` property in the DID document. - -**Kind**: instance method of [IotaDocument](#IotaDocument) - -| Param | Type | -| --- | --- | -| urls | string \| Array.<string> \| null | - - - -### iotaDocument.properties() ⇒ Map.<string, any> -Returns a copy of the custom DID Document properties. - -**Kind**: instance method of [IotaDocument](#IotaDocument) - - -### iotaDocument.setPropertyUnchecked(key, value) -Sets a custom property in the DID Document. -If the value is set to `null`, the custom property will be removed. - -### WARNING - -This method can overwrite existing properties like `id` and result in an invalid document. - -**Kind**: instance method of [IotaDocument](#IotaDocument) - -| Param | Type | -| --- | --- | -| key | string | -| value | any | - - - -### iotaDocument.service() ⇒ [Array.<Service>](#Service) -Return a set of all [Service](#Service) in the document. - -**Kind**: instance method of [IotaDocument](#IotaDocument) - - -### iotaDocument.insertService(service) -Add a new [Service](#Service) to the document. - -Returns `true` if the service was added. - -**Kind**: instance method of [IotaDocument](#IotaDocument) - -| Param | Type | -| --- | --- | -| service | [Service](#Service) | - - - -### iotaDocument.removeService(did) ⇒ [Service](#Service) \| undefined -Remove a [Service](#Service) identified by the given [DIDUrl](#DIDUrl) from the document. - -Returns `true` if a service was removed. - -**Kind**: instance method of [IotaDocument](#IotaDocument) - -| Param | Type | -| --- | --- | -| did | [DIDUrl](#DIDUrl) | - - - -### iotaDocument.resolveService(query) ⇒ [Service](#Service) \| undefined -Returns the first [Service](#Service) with an `id` property matching the provided `query`, -if present. - -**Kind**: instance method of [IotaDocument](#IotaDocument) - -| Param | Type | -| --- | --- | -| query | [DIDUrl](#DIDUrl) \| string | - - - -### iotaDocument.methods([scope]) ⇒ [Array.<VerificationMethod>](#VerificationMethod) -Returns a list of all [VerificationMethod](#VerificationMethod) in the DID Document, -whose verification relationship matches `scope`. - -If `scope` is not set, a list over the **embedded** methods is returned. - -**Kind**: instance method of [IotaDocument](#IotaDocument) - -| Param | Type | -| --- | --- | -| [scope] | [MethodScope](#MethodScope) \| undefined | - - - -### iotaDocument.insertMethod(method, scope) -Adds a new `method` to the document in the given `scope`. - -**Kind**: instance method of [IotaDocument](#IotaDocument) - -| Param | Type | -| --- | --- | -| method | [VerificationMethod](#VerificationMethod) | -| scope | [MethodScope](#MethodScope) | - - - -### iotaDocument.removeMethod(did) ⇒ [VerificationMethod](#VerificationMethod) \| undefined -Removes all references to the specified Verification Method. - -**Kind**: instance method of [IotaDocument](#IotaDocument) - -| Param | Type | -| --- | --- | -| did | [DIDUrl](#DIDUrl) | - - - -### iotaDocument.resolveMethod(query, [scope]) ⇒ [VerificationMethod](#VerificationMethod) \| undefined -Returns a copy of the first verification method with an `id` property -matching the provided `query` and the verification relationship -specified by `scope`, if present. - -**Kind**: instance method of [IotaDocument](#IotaDocument) - -| Param | Type | -| --- | --- | -| query | [DIDUrl](#DIDUrl) \| string | -| [scope] | [MethodScope](#MethodScope) \| undefined | - - - -### iotaDocument.attachMethodRelationship(didUrl, relationship) ⇒ boolean -Attaches the relationship to the given method, if the method exists. - -Note: The method needs to be in the set of verification methods, -so it cannot be an embedded one. - -**Kind**: instance method of [IotaDocument](#IotaDocument) - -| Param | Type | -| --- | --- | -| didUrl | [DIDUrl](#DIDUrl) | -| relationship | [MethodRelationship](#MethodRelationship) | - - - -### iotaDocument.detachMethodRelationship(didUrl, relationship) ⇒ boolean -Detaches the given relationship from the given method, if the method exists. - -**Kind**: instance method of [IotaDocument](#IotaDocument) - -| Param | Type | -| --- | --- | -| didUrl | [DIDUrl](#DIDUrl) | -| relationship | [MethodRelationship](#MethodRelationship) | - - - -### iotaDocument.verifyJws(jws, options, signatureVerifier, [detachedPayload]) ⇒ [DecodedJws](#DecodedJws) -Decodes and verifies the provided JWS according to the passed `options` and `signatureVerifier`. - If no `signatureVerifier` argument is provided a default verifier will be used that is (only) capable of -verifying EdDSA signatures. - -Regardless of which options are passed the following conditions must be met in order for a verification attempt to -take place. -- The JWS must be encoded according to the JWS compact serialization. -- The `kid` value in the protected header must be an identifier of a verification method in this DID document. - -**Kind**: instance method of [IotaDocument](#IotaDocument) - -| Param | Type | -| --- | --- | -| jws | [Jws](#Jws) | -| options | [JwsVerificationOptions](#JwsVerificationOptions) | -| signatureVerifier | IJwsVerifier | -| [detachedPayload] | string \| undefined | - - - -### iotaDocument.pack() ⇒ Uint8Array -Serializes the document for inclusion in an Alias Output's state metadata -with the default [StateMetadataEncoding](#StateMetadataEncoding). - -**Kind**: instance method of [IotaDocument](#IotaDocument) - - -### iotaDocument.packWithEncoding(encoding) ⇒ Uint8Array -Serializes the document for inclusion in an Alias Output's state metadata. - -**Kind**: instance method of [IotaDocument](#IotaDocument) - -| Param | Type | -| --- | --- | -| encoding | [StateMetadataEncoding](#StateMetadataEncoding) | - - - -### iotaDocument.metadata() ⇒ [IotaDocumentMetadata](#IotaDocumentMetadata) -Returns a copy of the metadata associated with this document. - -NOTE: Copies all the metadata. See also `metadataCreated`, `metadataUpdated`, -`metadataPreviousMessageId`, `metadataProof` if only a subset of the metadata required. - -**Kind**: instance method of [IotaDocument](#IotaDocument) - - -### iotaDocument.metadataCreated() ⇒ [Timestamp](#Timestamp) \| undefined -Returns a copy of the timestamp of when the DID document was created. - -**Kind**: instance method of [IotaDocument](#IotaDocument) - - -### iotaDocument.setMetadataCreated(timestamp) -Sets the timestamp of when the DID document was created. - -**Kind**: instance method of [IotaDocument](#IotaDocument) - -| Param | Type | -| --- | --- | -| timestamp | [Timestamp](#Timestamp) \| undefined | - - - -### iotaDocument.metadataUpdated() ⇒ [Timestamp](#Timestamp) \| undefined -Returns a copy of the timestamp of the last DID document update. - -**Kind**: instance method of [IotaDocument](#IotaDocument) - - -### iotaDocument.setMetadataUpdated(timestamp) -Sets the timestamp of the last DID document update. - -**Kind**: instance method of [IotaDocument](#IotaDocument) - -| Param | Type | -| --- | --- | -| timestamp | [Timestamp](#Timestamp) \| undefined | - - - -### iotaDocument.metadataDeactivated() ⇒ boolean \| undefined -Returns a copy of the deactivated status of the DID document. - -**Kind**: instance method of [IotaDocument](#IotaDocument) - - -### iotaDocument.setMetadataDeactivated([deactivated]) -Sets the deactivated status of the DID document. - -**Kind**: instance method of [IotaDocument](#IotaDocument) - -| Param | Type | -| --- | --- | -| [deactivated] | boolean \| undefined | - - - -### iotaDocument.metadataStateControllerAddress() ⇒ string \| undefined -Returns a copy of the Bech32-encoded state controller address, if present. - -**Kind**: instance method of [IotaDocument](#IotaDocument) - - -### iotaDocument.metadataGovernorAddress() ⇒ string \| undefined -Returns a copy of the Bech32-encoded governor address, if present. - -**Kind**: instance method of [IotaDocument](#IotaDocument) - - -### iotaDocument.setMetadataPropertyUnchecked(key, value) -Sets a custom property in the document metadata. -If the value is set to `null`, the custom property will be removed. - -**Kind**: instance method of [IotaDocument](#IotaDocument) - -| Param | Type | -| --- | --- | -| key | string | -| value | any | - - - -### iotaDocument.revokeCredentials(serviceQuery, indices) -If the document has a [RevocationBitmap](#RevocationBitmap) service identified by `serviceQuery`, -revoke all specified `indices`. - -**Kind**: instance method of [IotaDocument](#IotaDocument) - -| Param | Type | -| --- | --- | -| serviceQuery | [DIDUrl](#DIDUrl) \| string | -| indices | number \| Array.<number> | - - - -### iotaDocument.unrevokeCredentials(serviceQuery, indices) -If the document has a [RevocationBitmap](#RevocationBitmap) service identified by `serviceQuery`, -unrevoke all specified `indices`. - -**Kind**: instance method of [IotaDocument](#IotaDocument) - -| Param | Type | -| --- | --- | -| serviceQuery | [DIDUrl](#DIDUrl) \| string | -| indices | number \| Array.<number> | - - - -### iotaDocument.clone() ⇒ [IotaDocument](#IotaDocument) -Returns a deep clone of the [IotaDocument](#IotaDocument). - -**Kind**: instance method of [IotaDocument](#IotaDocument) - - -### iotaDocument.\_shallowCloneInternal() ⇒ [IotaDocument](#IotaDocument) -### Warning -This is for internal use only. Do not rely on or call this method. - -**Kind**: instance method of [IotaDocument](#IotaDocument) - - -### iotaDocument.\_strongCountInternal() ⇒ number -### Warning -This is for internal use only. Do not rely on or call this method. - -**Kind**: instance method of [IotaDocument](#IotaDocument) - - -### iotaDocument.toJSON() ⇒ any -Serializes to a plain JS representation. - -**Kind**: instance method of [IotaDocument](#IotaDocument) - - -### iotaDocument.toCoreDocument() ⇒ [CoreDocument](#CoreDocument) -Transforms the [IotaDocument](#IotaDocument) to its [CoreDocument](#CoreDocument) representation. - -**Kind**: instance method of [IotaDocument](#IotaDocument) - - -### iotaDocument.generateMethod(storage, keyType, alg, fragment, scope) ⇒ Promise.<string> -Generate new key material in the given `storage` and insert a new verification method with the corresponding -public key material into the DID document. - -- If no fragment is given the `kid` of the generated JWK is used, if it is set, otherwise an error is returned. -- The `keyType` must be compatible with the given `storage`. `Storage`s are expected to export key type constants -for that use case. - -The fragment of the generated method is returned. - -**Kind**: instance method of [IotaDocument](#IotaDocument) - -| Param | Type | -| --- | --- | -| storage | [Storage](#Storage) | -| keyType | string | -| alg | JwsAlgorithm | -| fragment | string \| undefined | -| scope | [MethodScope](#MethodScope) | - - - -### iotaDocument.purgeMethod(storage, id) ⇒ Promise.<void> -Remove the method identified by the given fragment from the document and delete the corresponding key material in -the given `storage`. - -**Kind**: instance method of [IotaDocument](#IotaDocument) - -| Param | Type | -| --- | --- | -| storage | [Storage](#Storage) | -| id | [DIDUrl](#DIDUrl) | - - - -### ~~iotaDocument.createJwt(storage, fragment, payload, options) ⇒ [Promise.<Jws>](#Jws)~~ -***Deprecated*** - -Sign the `payload` according to `options` with the storage backed private key corresponding to the public key -material in the verification method identified by the given `fragment. - -Upon success a string representing a JWS encoded according to the Compact JWS Serialization format is returned. -See [RFC7515 section 3.1](https://www.rfc-editor.org/rfc/rfc7515#section-3.1). - -**Kind**: instance method of [IotaDocument](#IotaDocument) - -| Param | Type | -| --- | --- | -| storage | [Storage](#Storage) | -| fragment | string | -| payload | string | -| options | [JwsSignatureOptions](#JwsSignatureOptions) | - - - -### iotaDocument.createJws(storage, fragment, payload, options) ⇒ [Promise.<Jws>](#Jws) -Sign the `payload` according to `options` with the storage backed private key corresponding to the public key -material in the verification method identified by the given `fragment. - -Upon success a string representing a JWS encoded according to the Compact JWS Serialization format is returned. -See [RFC7515 section 3.1](https://www.rfc-editor.org/rfc/rfc7515#section-3.1). - -**Kind**: instance method of [IotaDocument](#IotaDocument) - -| Param | Type | -| --- | --- | -| storage | [Storage](#Storage) | -| fragment | string | -| payload | string | -| options | [JwsSignatureOptions](#JwsSignatureOptions) | - - - -### iotaDocument.createCredentialJwt(storage, fragment, credential, options, [custom_claims]) ⇒ [Promise.<Jwt>](#Jwt) -Produces a JWS where the payload is produced from the given `credential` -in accordance with [VC Data Model v1.1](https://www.w3.org/TR/vc-data-model/#json-web-token). - -Unless the `kid` is explicitly set in the options, the `kid` in the protected header is the `id` -of the method identified by `fragment` and the JWS signature will be produced by the corresponding -private key backed by the `storage` in accordance with the passed `options`. - -The `custom_claims` can be used to set additional claims on the resulting JWT. - -**Kind**: instance method of [IotaDocument](#IotaDocument) - -| Param | Type | -| --- | --- | -| storage | [Storage](#Storage) | -| fragment | string | -| credential | [Credential](#Credential) | -| options | [JwsSignatureOptions](#JwsSignatureOptions) | -| [custom_claims] | Record.<string, any> \| undefined | - - - -### iotaDocument.createPresentationJwt(storage, fragment, presentation, signature_options, presentation_options) ⇒ [Promise.<Jwt>](#Jwt) -Produces a JWT where the payload is produced from the given presentation. -in accordance with [VC Data Model v1.1](https://www.w3.org/TR/vc-data-model/#json-web-token). - -Unless the `kid` is explicitly set in the options, the `kid` in the protected header is the `id` -of the method identified by `fragment` and the JWS signature will be produced by the corresponding -private key backed by the `storage` in accordance with the passed `options`. - -**Kind**: instance method of [IotaDocument](#IotaDocument) - -| Param | Type | -| --- | --- | -| storage | [Storage](#Storage) | -| fragment | string | -| presentation | [Presentation](#Presentation) | -| signature_options | [JwsSignatureOptions](#JwsSignatureOptions) | -| presentation_options | [JwtPresentationOptions](#JwtPresentationOptions) | - - - -### IotaDocument.newWithId(id) ⇒ [IotaDocument](#IotaDocument) -Constructs an empty DID Document with the given identifier. - -**Kind**: static method of [IotaDocument](#IotaDocument) - -| Param | Type | -| --- | --- | -| id | [IotaDID](#IotaDID) | - - - -### IotaDocument.unpackFromOutput(did, aliasOutput, allowEmpty) ⇒ [IotaDocument](#IotaDocument) -Deserializes the document from an Alias Output. - -If `allowEmpty` is true, this will return an empty DID document marked as `deactivated` -if `stateMetadata` is empty. - -The `tokenSupply` must be equal to the token supply of the network the DID is associated with. - -NOTE: `did` is required since it is omitted from the serialized DID Document and -cannot be inferred from the state metadata. It also indicates the network, which is not -encoded in the `AliasId` alone. - -**Kind**: static method of [IotaDocument](#IotaDocument) - -| Param | Type | -| --- | --- | -| did | [IotaDID](#IotaDID) | -| aliasOutput | AliasOutputBuilderParams | -| allowEmpty | boolean | - - - -### IotaDocument.unpackFromBlock(network, block) ⇒ [Array.<IotaDocument>](#IotaDocument) -Returns all DID documents of the Alias Outputs contained in the block's transaction payload -outputs, if any. - -Errors if any Alias Output does not contain a valid or empty DID Document. - -**Kind**: static method of [IotaDocument](#IotaDocument) - -| Param | Type | -| --- | --- | -| network | string | -| block | Block | - - - -### IotaDocument.fromJSON(json) ⇒ [IotaDocument](#IotaDocument) -Deserializes an instance from a plain JS representation. - -**Kind**: static method of [IotaDocument](#IotaDocument) - -| Param | Type | -| --- | --- | -| json | any | - - - -## IotaDocumentMetadata -Additional attributes related to an IOTA DID Document. - -**Kind**: global class - -* [IotaDocumentMetadata](#IotaDocumentMetadata) - * _instance_ - * [.created()](#IotaDocumentMetadata+created) ⇒ [Timestamp](#Timestamp) \| undefined - * [.updated()](#IotaDocumentMetadata+updated) ⇒ [Timestamp](#Timestamp) \| undefined - * [.deactivated()](#IotaDocumentMetadata+deactivated) ⇒ boolean \| undefined - * [.stateControllerAddress()](#IotaDocumentMetadata+stateControllerAddress) ⇒ string \| undefined - * [.governorAddress()](#IotaDocumentMetadata+governorAddress) ⇒ string \| undefined - * [.properties()](#IotaDocumentMetadata+properties) ⇒ Map.<string, any> - * [.toJSON()](#IotaDocumentMetadata+toJSON) ⇒ any - * [.clone()](#IotaDocumentMetadata+clone) ⇒ [IotaDocumentMetadata](#IotaDocumentMetadata) - * _static_ - * [.fromJSON(json)](#IotaDocumentMetadata.fromJSON) ⇒ [IotaDocumentMetadata](#IotaDocumentMetadata) - - - -### iotaDocumentMetadata.created() ⇒ [Timestamp](#Timestamp) \| undefined -Returns a copy of the timestamp of when the DID document was created. - -**Kind**: instance method of [IotaDocumentMetadata](#IotaDocumentMetadata) - - -### iotaDocumentMetadata.updated() ⇒ [Timestamp](#Timestamp) \| undefined -Returns a copy of the timestamp of the last DID document update. - -**Kind**: instance method of [IotaDocumentMetadata](#IotaDocumentMetadata) - - -### iotaDocumentMetadata.deactivated() ⇒ boolean \| undefined -Returns a copy of the deactivated status of the DID document. - -**Kind**: instance method of [IotaDocumentMetadata](#IotaDocumentMetadata) - - -### iotaDocumentMetadata.stateControllerAddress() ⇒ string \| undefined -Returns a copy of the Bech32-encoded state controller address, if present. - -**Kind**: instance method of [IotaDocumentMetadata](#IotaDocumentMetadata) - - -### iotaDocumentMetadata.governorAddress() ⇒ string \| undefined -Returns a copy of the Bech32-encoded governor address, if present. - -**Kind**: instance method of [IotaDocumentMetadata](#IotaDocumentMetadata) - - -### iotaDocumentMetadata.properties() ⇒ Map.<string, any> -Returns a copy of the custom metadata properties. - -**Kind**: instance method of [IotaDocumentMetadata](#IotaDocumentMetadata) - - -### iotaDocumentMetadata.toJSON() ⇒ any -Serializes this to a JSON object. - -**Kind**: instance method of [IotaDocumentMetadata](#IotaDocumentMetadata) - - -### iotaDocumentMetadata.clone() ⇒ [IotaDocumentMetadata](#IotaDocumentMetadata) -Deep clones the object. - -**Kind**: instance method of [IotaDocumentMetadata](#IotaDocumentMetadata) - - -### IotaDocumentMetadata.fromJSON(json) ⇒ [IotaDocumentMetadata](#IotaDocumentMetadata) -Deserializes an instance from a JSON object. - -**Kind**: static method of [IotaDocumentMetadata](#IotaDocumentMetadata) - -| Param | Type | -| --- | --- | -| json | any | - - - -## IotaIdentityClientExt -An extension interface that provides helper functions for publication -and resolution of DID documents in Alias Outputs. - -**Kind**: global class - -* [IotaIdentityClientExt](#IotaIdentityClientExt) - * [.newDidOutput(client, address, document, [rentStructure])](#IotaIdentityClientExt.newDidOutput) ⇒ Promise.<AliasOutputBuilderParams> - * [.updateDidOutput(client, document)](#IotaIdentityClientExt.updateDidOutput) ⇒ Promise.<AliasOutputBuilderParams> - * [.deactivateDidOutput(client, did)](#IotaIdentityClientExt.deactivateDidOutput) ⇒ Promise.<AliasOutputBuilderParams> - * [.resolveDid(client, did)](#IotaIdentityClientExt.resolveDid) ⇒ [Promise.<IotaDocument>](#IotaDocument) - * [.resolveDidOutput(client, did)](#IotaIdentityClientExt.resolveDidOutput) ⇒ Promise.<AliasOutputBuilderParams> - - - -### IotaIdentityClientExt.newDidOutput(client, address, document, [rentStructure]) ⇒ Promise.<AliasOutputBuilderParams> -Create a DID with a new Alias Output containing the given `document`. - -The `address` will be set as the state controller and governor unlock conditions. -The minimum required token deposit amount will be set according to the given -`rent_structure`, which will be fetched from the node if not provided. -The returned Alias Output can be further customised before publication, if desired. - -NOTE: this does *not* publish the Alias Output. - -**Kind**: static method of [IotaIdentityClientExt](#IotaIdentityClientExt) - -| Param | Type | -| --- | --- | -| client | IIotaIdentityClient | -| address | Address | -| document | [IotaDocument](#IotaDocument) | -| [rentStructure] | IRent \| undefined | - - - -### IotaIdentityClientExt.updateDidOutput(client, document) ⇒ Promise.<AliasOutputBuilderParams> -Fetches the associated Alias Output and updates it with `document` in its state metadata. -The storage deposit on the output is left unchanged. If the size of the document increased, -the amount should be increased manually. - -NOTE: this does *not* publish the updated Alias Output. - -**Kind**: static method of [IotaIdentityClientExt](#IotaIdentityClientExt) - -| Param | Type | -| --- | --- | -| client | IIotaIdentityClient | -| document | [IotaDocument](#IotaDocument) | - - - -### IotaIdentityClientExt.deactivateDidOutput(client, did) ⇒ Promise.<AliasOutputBuilderParams> -Removes the DID document from the state metadata of its Alias Output, -effectively deactivating it. The storage deposit on the output is left unchanged, -and should be reallocated manually. - -Deactivating does not destroy the output. Hence, it can be re-activated by publishing -an update containing a DID document. - -NOTE: this does *not* publish the updated Alias Output. - -**Kind**: static method of [IotaIdentityClientExt](#IotaIdentityClientExt) - -| Param | Type | -| --- | --- | -| client | IIotaIdentityClient | -| did | [IotaDID](#IotaDID) | - - - -### IotaIdentityClientExt.resolveDid(client, did) ⇒ [Promise.<IotaDocument>](#IotaDocument) -Resolve a [IotaDocument](#IotaDocument). Returns an empty, deactivated document if the state metadata -of the Alias Output is empty. - -**Kind**: static method of [IotaIdentityClientExt](#IotaIdentityClientExt) - -| Param | Type | -| --- | --- | -| client | IIotaIdentityClient | -| did | [IotaDID](#IotaDID) | - - - -### IotaIdentityClientExt.resolveDidOutput(client, did) ⇒ Promise.<AliasOutputBuilderParams> -Fetches the `IAliasOutput` associated with the given DID. - -**Kind**: static method of [IotaIdentityClientExt](#IotaIdentityClientExt) - -| Param | Type | -| --- | --- | -| client | IIotaIdentityClient | -| did | [IotaDID](#IotaDID) | - - - -## Jwk -**Kind**: global class - -* [Jwk](#Jwk) - * [new Jwk(jwk)](#new_Jwk_new) - * _instance_ - * [.kty()](#Jwk+kty) ⇒ JwkType - * [.use()](#Jwk+use) ⇒ JwkUse \| undefined - * [.keyOps()](#Jwk+keyOps) ⇒ Array.<JwkOperation> - * [.alg()](#Jwk+alg) ⇒ JwsAlgorithm \| undefined - * [.kid()](#Jwk+kid) ⇒ string \| undefined - * [.x5u()](#Jwk+x5u) ⇒ string \| undefined - * [.x5c()](#Jwk+x5c) ⇒ Array.<string> - * [.x5t()](#Jwk+x5t) ⇒ string \| undefined - * [.x5t256()](#Jwk+x5t256) ⇒ string \| undefined - * [.paramsEc()](#Jwk+paramsEc) ⇒ JwkParamsEc \| undefined - * [.paramsOkp()](#Jwk+paramsOkp) ⇒ JwkParamsOkp \| undefined - * [.paramsOct()](#Jwk+paramsOct) ⇒ JwkParamsOct \| undefined - * [.paramsRsa()](#Jwk+paramsRsa) ⇒ JwkParamsRsa \| undefined - * [.toPublic()](#Jwk+toPublic) ⇒ [Jwk](#Jwk) \| undefined - * [.isPublic()](#Jwk+isPublic) ⇒ boolean - * [.isPrivate()](#Jwk+isPrivate) ⇒ boolean - * [.toJSON()](#Jwk+toJSON) ⇒ any - * [.clone()](#Jwk+clone) ⇒ [Jwk](#Jwk) - * _static_ - * [.fromJSON(json)](#Jwk.fromJSON) ⇒ [Jwk](#Jwk) - - - -### new Jwk(jwk) - -| Param | Type | -| --- | --- | -| jwk | IJwkParams | - - - -### jwk.kty() ⇒ JwkType -Returns the value for the key type parameter (kty). - -**Kind**: instance method of [Jwk](#Jwk) - - -### jwk.use() ⇒ JwkUse \| undefined -Returns the value for the use property (use). - -**Kind**: instance method of [Jwk](#Jwk) - - -### jwk.keyOps() ⇒ Array.<JwkOperation> -**Kind**: instance method of [Jwk](#Jwk) - - -### jwk.alg() ⇒ JwsAlgorithm \| undefined -Returns the value for the algorithm property (alg). - -**Kind**: instance method of [Jwk](#Jwk) - - -### jwk.kid() ⇒ string \| undefined -Returns the value of the key ID property (kid). - -**Kind**: instance method of [Jwk](#Jwk) - - -### jwk.x5u() ⇒ string \| undefined -Returns the value of the X.509 URL property (x5u). - -**Kind**: instance method of [Jwk](#Jwk) - - -### jwk.x5c() ⇒ Array.<string> -Returns the value of the X.509 certificate chain property (x5c). - -**Kind**: instance method of [Jwk](#Jwk) - - -### jwk.x5t() ⇒ string \| undefined -Returns the value of the X.509 certificate SHA-1 thumbprint property (x5t). - -**Kind**: instance method of [Jwk](#Jwk) - - -### jwk.x5t256() ⇒ string \| undefined -Returns the value of the X.509 certificate SHA-256 thumbprint property (x5t#S256). - -**Kind**: instance method of [Jwk](#Jwk) - - -### jwk.paramsEc() ⇒ JwkParamsEc \| undefined -If this JWK is of kty EC, returns those parameters. - -**Kind**: instance method of [Jwk](#Jwk) - - -### jwk.paramsOkp() ⇒ JwkParamsOkp \| undefined -If this JWK is of kty OKP, returns those parameters. - -**Kind**: instance method of [Jwk](#Jwk) - - -### jwk.paramsOct() ⇒ JwkParamsOct \| undefined -If this JWK is of kty OCT, returns those parameters. - -**Kind**: instance method of [Jwk](#Jwk) - - -### jwk.paramsRsa() ⇒ JwkParamsRsa \| undefined -If this JWK is of kty RSA, returns those parameters. - -**Kind**: instance method of [Jwk](#Jwk) - - -### jwk.toPublic() ⇒ [Jwk](#Jwk) \| undefined -Returns a clone of the [Jwk](#Jwk) with _all_ private key components unset. -Nothing is returned when `kty = oct` as this key type is not considered public by this library. - -**Kind**: instance method of [Jwk](#Jwk) - - -### jwk.isPublic() ⇒ boolean -Returns `true` if _all_ private key components of the key are unset, `false` otherwise. - -**Kind**: instance method of [Jwk](#Jwk) - - -### jwk.isPrivate() ⇒ boolean -Returns `true` if _all_ private key components of the key are set, `false` otherwise. - -**Kind**: instance method of [Jwk](#Jwk) - - -### jwk.toJSON() ⇒ any -Serializes this to a JSON object. - -**Kind**: instance method of [Jwk](#Jwk) - - -### jwk.clone() ⇒ [Jwk](#Jwk) -Deep clones the object. - -**Kind**: instance method of [Jwk](#Jwk) - - -### Jwk.fromJSON(json) ⇒ [Jwk](#Jwk) -Deserializes an instance from a JSON object. - -**Kind**: static method of [Jwk](#Jwk) - -| Param | Type | -| --- | --- | -| json | any | - - - -## JwkGenOutput -The result of a key generation in `JwkStorage`. - -**Kind**: global class - -* [JwkGenOutput](#JwkGenOutput) - * [new JwkGenOutput(key_id, jwk)](#new_JwkGenOutput_new) - * _instance_ - * [.jwk()](#JwkGenOutput+jwk) ⇒ [Jwk](#Jwk) - * [.keyId()](#JwkGenOutput+keyId) ⇒ string - * [.toJSON()](#JwkGenOutput+toJSON) ⇒ any - * [.clone()](#JwkGenOutput+clone) ⇒ [JwkGenOutput](#JwkGenOutput) - * _static_ - * [.fromJSON(json)](#JwkGenOutput.fromJSON) ⇒ [JwkGenOutput](#JwkGenOutput) - - - -### new JwkGenOutput(key_id, jwk) - -| Param | Type | -| --- | --- | -| key_id | string | -| jwk | [Jwk](#Jwk) | - - - -### jwkGenOutput.jwk() ⇒ [Jwk](#Jwk) -Returns the generated public [Jwk](#Jwk). - -**Kind**: instance method of [JwkGenOutput](#JwkGenOutput) - - -### jwkGenOutput.keyId() ⇒ string -Returns the key id of the generated [Jwk](#Jwk). - -**Kind**: instance method of [JwkGenOutput](#JwkGenOutput) - - -### jwkGenOutput.toJSON() ⇒ any -Serializes this to a JSON object. - -**Kind**: instance method of [JwkGenOutput](#JwkGenOutput) - - -### jwkGenOutput.clone() ⇒ [JwkGenOutput](#JwkGenOutput) -Deep clones the object. - -**Kind**: instance method of [JwkGenOutput](#JwkGenOutput) - - -### JwkGenOutput.fromJSON(json) ⇒ [JwkGenOutput](#JwkGenOutput) -Deserializes an instance from a JSON object. - -**Kind**: static method of [JwkGenOutput](#JwkGenOutput) - -| Param | Type | -| --- | --- | -| json | any | - - - -## Jws -A wrapper around a JSON Web Signature (JWS). - -**Kind**: global class - -* [Jws](#Jws) - * [new Jws(jws_string)](#new_Jws_new) - * [.toString()](#Jws+toString) ⇒ string - - - -### new Jws(jws_string) -Creates a new [Jws](#Jws) from the given string. - - -| Param | Type | -| --- | --- | -| jws_string | string | - - - -### jws.toString() ⇒ string -Returns a clone of the JWS string. - -**Kind**: instance method of [Jws](#Jws) - - -## JwsHeader -**Kind**: global class - -* [JwsHeader](#JwsHeader) - * [new JwsHeader()](#new_JwsHeader_new) - * _instance_ - * [.alg()](#JwsHeader+alg) ⇒ JwsAlgorithm \| undefined - * [.setAlg(value)](#JwsHeader+setAlg) - * [.b64()](#JwsHeader+b64) ⇒ boolean \| undefined - * [.setB64(value)](#JwsHeader+setB64) - * [.custom()](#JwsHeader+custom) ⇒ Record.<string, any> \| undefined - * [.has(claim)](#JwsHeader+has) ⇒ boolean - * [.isDisjoint(other)](#JwsHeader+isDisjoint) ⇒ boolean - * [.jku()](#JwsHeader+jku) ⇒ string \| undefined - * [.setJku(value)](#JwsHeader+setJku) - * [.jwk()](#JwsHeader+jwk) ⇒ [Jwk](#Jwk) \| undefined - * [.setJwk(value)](#JwsHeader+setJwk) - * [.kid()](#JwsHeader+kid) ⇒ string \| undefined - * [.setKid(value)](#JwsHeader+setKid) - * [.x5u()](#JwsHeader+x5u) ⇒ string \| undefined - * [.setX5u(value)](#JwsHeader+setX5u) - * [.x5c()](#JwsHeader+x5c) ⇒ Array.<string> - * [.setX5c(value)](#JwsHeader+setX5c) - * [.x5t()](#JwsHeader+x5t) ⇒ string \| undefined - * [.setX5t(value)](#JwsHeader+setX5t) - * [.x5tS256()](#JwsHeader+x5tS256) ⇒ string \| undefined - * [.setX5tS256(value)](#JwsHeader+setX5tS256) - * [.typ()](#JwsHeader+typ) ⇒ string \| undefined - * [.setTyp(value)](#JwsHeader+setTyp) - * [.cty()](#JwsHeader+cty) ⇒ string \| undefined - * [.setCty(value)](#JwsHeader+setCty) - * [.crit()](#JwsHeader+crit) ⇒ Array.<string> - * [.setCrit(value)](#JwsHeader+setCrit) - * [.url()](#JwsHeader+url) ⇒ string \| undefined - * [.setUrl(value)](#JwsHeader+setUrl) - * [.nonce()](#JwsHeader+nonce) ⇒ string \| undefined - * [.setNonce(value)](#JwsHeader+setNonce) - * [.toJSON()](#JwsHeader+toJSON) ⇒ any - * [.clone()](#JwsHeader+clone) ⇒ [JwsHeader](#JwsHeader) - * _static_ - * [.fromJSON(json)](#JwsHeader.fromJSON) ⇒ [JwsHeader](#JwsHeader) - - - -### new JwsHeader() -Create a new empty [JwsHeader](#JwsHeader). - - - -### jwsHeader.alg() ⇒ JwsAlgorithm \| undefined -Returns the value for the algorithm claim (alg). - -**Kind**: instance method of [JwsHeader](#JwsHeader) - - -### jwsHeader.setAlg(value) -Sets a value for the algorithm claim (alg). - -**Kind**: instance method of [JwsHeader](#JwsHeader) - -| Param | Type | -| --- | --- | -| value | JwsAlgorithm | - - - -### jwsHeader.b64() ⇒ boolean \| undefined -Returns the value of the base64url-encode payload claim (b64). - -**Kind**: instance method of [JwsHeader](#JwsHeader) - - -### jwsHeader.setB64(value) -Sets a value for the base64url-encode payload claim (b64). - -**Kind**: instance method of [JwsHeader](#JwsHeader) - -| Param | Type | -| --- | --- | -| value | boolean | - - - -### jwsHeader.custom() ⇒ Record.<string, any> \| undefined -Additional header parameters. - -**Kind**: instance method of [JwsHeader](#JwsHeader) - - -### jwsHeader.has(claim) ⇒ boolean -**Kind**: instance method of [JwsHeader](#JwsHeader) - -| Param | Type | -| --- | --- | -| claim | string | - - - -### jwsHeader.isDisjoint(other) ⇒ boolean -Returns `true` if none of the fields are set in both `self` and `other`. - -**Kind**: instance method of [JwsHeader](#JwsHeader) - -| Param | Type | -| --- | --- | -| other | [JwsHeader](#JwsHeader) | - - - -### jwsHeader.jku() ⇒ string \| undefined -Returns the value of the JWK Set URL claim (jku). - -**Kind**: instance method of [JwsHeader](#JwsHeader) - - -### jwsHeader.setJku(value) -Sets a value for the JWK Set URL claim (jku). - -**Kind**: instance method of [JwsHeader](#JwsHeader) - -| Param | Type | -| --- | --- | -| value | string | - - - -### jwsHeader.jwk() ⇒ [Jwk](#Jwk) \| undefined -Returns the value of the JWK claim (jwk). - -**Kind**: instance method of [JwsHeader](#JwsHeader) - - -### jwsHeader.setJwk(value) -Sets a value for the JWK claim (jwk). - -**Kind**: instance method of [JwsHeader](#JwsHeader) - -| Param | Type | -| --- | --- | -| value | [Jwk](#Jwk) | - - - -### jwsHeader.kid() ⇒ string \| undefined -Returns the value of the key ID claim (kid). - -**Kind**: instance method of [JwsHeader](#JwsHeader) - - -### jwsHeader.setKid(value) -Sets a value for the key ID claim (kid). - -**Kind**: instance method of [JwsHeader](#JwsHeader) - -| Param | Type | -| --- | --- | -| value | string | - - - -### jwsHeader.x5u() ⇒ string \| undefined -Returns the value of the X.509 URL claim (x5u). - -**Kind**: instance method of [JwsHeader](#JwsHeader) - - -### jwsHeader.setX5u(value) -Sets a value for the X.509 URL claim (x5u). - -**Kind**: instance method of [JwsHeader](#JwsHeader) - -| Param | Type | -| --- | --- | -| value | string | - - - -### jwsHeader.x5c() ⇒ Array.<string> -Returns the value of the X.509 certificate chain claim (x5c). - -**Kind**: instance method of [JwsHeader](#JwsHeader) - - -### jwsHeader.setX5c(value) -Sets values for the X.509 certificate chain claim (x5c). - -**Kind**: instance method of [JwsHeader](#JwsHeader) - -| Param | Type | -| --- | --- | -| value | Array.<string> | - - - -### jwsHeader.x5t() ⇒ string \| undefined -Returns the value of the X.509 certificate SHA-1 thumbprint claim (x5t). - -**Kind**: instance method of [JwsHeader](#JwsHeader) - - -### jwsHeader.setX5t(value) -Sets a value for the X.509 certificate SHA-1 thumbprint claim (x5t). - -**Kind**: instance method of [JwsHeader](#JwsHeader) - -| Param | Type | -| --- | --- | -| value | string | - - - -### jwsHeader.x5tS256() ⇒ string \| undefined -Returns the value of the X.509 certificate SHA-256 thumbprint claim -(x5t#S256). - -**Kind**: instance method of [JwsHeader](#JwsHeader) - - -### jwsHeader.setX5tS256(value) -Sets a value for the X.509 certificate SHA-256 thumbprint claim -(x5t#S256). - -**Kind**: instance method of [JwsHeader](#JwsHeader) - -| Param | Type | -| --- | --- | -| value | string | - - - -### jwsHeader.typ() ⇒ string \| undefined -Returns the value of the token type claim (typ). - -**Kind**: instance method of [JwsHeader](#JwsHeader) - - -### jwsHeader.setTyp(value) -Sets a value for the token type claim (typ). - -**Kind**: instance method of [JwsHeader](#JwsHeader) - -| Param | Type | -| --- | --- | -| value | string | - - - -### jwsHeader.cty() ⇒ string \| undefined -Returns the value of the content type claim (cty). - -**Kind**: instance method of [JwsHeader](#JwsHeader) - - -### jwsHeader.setCty(value) -Sets a value for the content type claim (cty). - -**Kind**: instance method of [JwsHeader](#JwsHeader) - -| Param | Type | -| --- | --- | -| value | string | - - - -### jwsHeader.crit() ⇒ Array.<string> -Returns the value of the critical claim (crit). - -**Kind**: instance method of [JwsHeader](#JwsHeader) - - -### jwsHeader.setCrit(value) -Sets values for the critical claim (crit). - -**Kind**: instance method of [JwsHeader](#JwsHeader) - -| Param | Type | -| --- | --- | -| value | Array.<string> | - - - -### jwsHeader.url() ⇒ string \| undefined -Returns the value of the url claim (url). - -**Kind**: instance method of [JwsHeader](#JwsHeader) - - -### jwsHeader.setUrl(value) -Sets a value for the url claim (url). - -**Kind**: instance method of [JwsHeader](#JwsHeader) - -| Param | Type | -| --- | --- | -| value | string | - - - -### jwsHeader.nonce() ⇒ string \| undefined -Returns the value of the nonce claim (nonce). - -**Kind**: instance method of [JwsHeader](#JwsHeader) - - -### jwsHeader.setNonce(value) -Sets a value for the nonce claim (nonce). - -**Kind**: instance method of [JwsHeader](#JwsHeader) - -| Param | Type | -| --- | --- | -| value | string | - - - -### jwsHeader.toJSON() ⇒ any -Serializes this to a JSON object. - -**Kind**: instance method of [JwsHeader](#JwsHeader) - - -### jwsHeader.clone() ⇒ [JwsHeader](#JwsHeader) -Deep clones the object. - -**Kind**: instance method of [JwsHeader](#JwsHeader) - - -### JwsHeader.fromJSON(json) ⇒ [JwsHeader](#JwsHeader) -Deserializes an instance from a JSON object. - -**Kind**: static method of [JwsHeader](#JwsHeader) - -| Param | Type | -| --- | --- | -| json | any | - - - -## JwsSignatureOptions -**Kind**: global class - -* [JwsSignatureOptions](#JwsSignatureOptions) - * [new JwsSignatureOptions([options])](#new_JwsSignatureOptions_new) - * _instance_ - * [.setAttachJwk(value)](#JwsSignatureOptions+setAttachJwk) - * [.setB64(value)](#JwsSignatureOptions+setB64) - * [.setTyp(value)](#JwsSignatureOptions+setTyp) - * [.setCty(value)](#JwsSignatureOptions+setCty) - * [.serUrl(value)](#JwsSignatureOptions+serUrl) - * [.setNonce(value)](#JwsSignatureOptions+setNonce) - * [.setKid(value)](#JwsSignatureOptions+setKid) - * [.setDetachedPayload(value)](#JwsSignatureOptions+setDetachedPayload) - * [.setCustomHeaderParameters(value)](#JwsSignatureOptions+setCustomHeaderParameters) - * [.toJSON()](#JwsSignatureOptions+toJSON) ⇒ any - * [.clone()](#JwsSignatureOptions+clone) ⇒ [JwsSignatureOptions](#JwsSignatureOptions) - * _static_ - * [.fromJSON(json)](#JwsSignatureOptions.fromJSON) ⇒ [JwsSignatureOptions](#JwsSignatureOptions) - - - -### new JwsSignatureOptions([options]) - -| Param | Type | -| --- | --- | -| [options] | IJwsSignatureOptions \| undefined | - - - -### jwsSignatureOptions.setAttachJwk(value) -Replace the value of the `attachJwk` field. - -**Kind**: instance method of [JwsSignatureOptions](#JwsSignatureOptions) - -| Param | Type | -| --- | --- | -| value | boolean | - - - -### jwsSignatureOptions.setB64(value) -Replace the value of the `b64` field. - -**Kind**: instance method of [JwsSignatureOptions](#JwsSignatureOptions) - -| Param | Type | -| --- | --- | -| value | boolean | - - - -### jwsSignatureOptions.setTyp(value) -Replace the value of the `typ` field. - -**Kind**: instance method of [JwsSignatureOptions](#JwsSignatureOptions) - -| Param | Type | -| --- | --- | -| value | string | - - - -### jwsSignatureOptions.setCty(value) -Replace the value of the `cty` field. - -**Kind**: instance method of [JwsSignatureOptions](#JwsSignatureOptions) - -| Param | Type | -| --- | --- | -| value | string | - - - -### jwsSignatureOptions.serUrl(value) -Replace the value of the `url` field. - -**Kind**: instance method of [JwsSignatureOptions](#JwsSignatureOptions) - -| Param | Type | -| --- | --- | -| value | string | - - - -### jwsSignatureOptions.setNonce(value) -Replace the value of the `nonce` field. - -**Kind**: instance method of [JwsSignatureOptions](#JwsSignatureOptions) - -| Param | Type | -| --- | --- | -| value | string | - - - -### jwsSignatureOptions.setKid(value) -Replace the value of the `kid` field. - -**Kind**: instance method of [JwsSignatureOptions](#JwsSignatureOptions) - -| Param | Type | -| --- | --- | -| value | string | - - - -### jwsSignatureOptions.setDetachedPayload(value) -Replace the value of the `detached_payload` field. - -**Kind**: instance method of [JwsSignatureOptions](#JwsSignatureOptions) - -| Param | Type | -| --- | --- | -| value | boolean | - - - -### jwsSignatureOptions.setCustomHeaderParameters(value) -Add additional header parameters. - -**Kind**: instance method of [JwsSignatureOptions](#JwsSignatureOptions) - -| Param | Type | -| --- | --- | -| value | Record.<string, any> | - - - -### jwsSignatureOptions.toJSON() ⇒ any -Serializes this to a JSON object. - -**Kind**: instance method of [JwsSignatureOptions](#JwsSignatureOptions) - - -### jwsSignatureOptions.clone() ⇒ [JwsSignatureOptions](#JwsSignatureOptions) -Deep clones the object. - -**Kind**: instance method of [JwsSignatureOptions](#JwsSignatureOptions) - - -### JwsSignatureOptions.fromJSON(json) ⇒ [JwsSignatureOptions](#JwsSignatureOptions) -Deserializes an instance from a JSON object. - -**Kind**: static method of [JwsSignatureOptions](#JwsSignatureOptions) - -| Param | Type | -| --- | --- | -| json | any | - - - -## JwsVerificationOptions -**Kind**: global class - -* [JwsVerificationOptions](#JwsVerificationOptions) - * [new JwsVerificationOptions([options])](#new_JwsVerificationOptions_new) - * _instance_ - * [.setNonce(value)](#JwsVerificationOptions+setNonce) - * [.setMethodScope(value)](#JwsVerificationOptions+setMethodScope) - * [.setMethodId(value)](#JwsVerificationOptions+setMethodId) - * [.toJSON()](#JwsVerificationOptions+toJSON) ⇒ any - * [.clone()](#JwsVerificationOptions+clone) ⇒ [JwsVerificationOptions](#JwsVerificationOptions) - * _static_ - * [.fromJSON(json)](#JwsVerificationOptions.fromJSON) ⇒ [JwsVerificationOptions](#JwsVerificationOptions) - - - -### new JwsVerificationOptions([options]) -Creates a new [JwsVerificationOptions](#JwsVerificationOptions) from the given fields. - - -| Param | Type | -| --- | --- | -| [options] | IJwsVerificationOptions \| undefined | - - - -### jwsVerificationOptions.setNonce(value) -Set the expected value for the `nonce` parameter of the protected header. - -**Kind**: instance method of [JwsVerificationOptions](#JwsVerificationOptions) - -| Param | Type | -| --- | --- | -| value | string | - - - -### jwsVerificationOptions.setMethodScope(value) -Set the scope of the verification methods that may be used to verify the given JWS. - -**Kind**: instance method of [JwsVerificationOptions](#JwsVerificationOptions) - -| Param | Type | -| --- | --- | -| value | [MethodScope](#MethodScope) | - - - -### jwsVerificationOptions.setMethodId(value) -Set the DID URl of the method, whose JWK should be used to verify the JWS. - -**Kind**: instance method of [JwsVerificationOptions](#JwsVerificationOptions) - -| Param | Type | -| --- | --- | -| value | [DIDUrl](#DIDUrl) | - - - -### jwsVerificationOptions.toJSON() ⇒ any -Serializes this to a JSON object. - -**Kind**: instance method of [JwsVerificationOptions](#JwsVerificationOptions) - - -### jwsVerificationOptions.clone() ⇒ [JwsVerificationOptions](#JwsVerificationOptions) -Deep clones the object. - -**Kind**: instance method of [JwsVerificationOptions](#JwsVerificationOptions) - - -### JwsVerificationOptions.fromJSON(json) ⇒ [JwsVerificationOptions](#JwsVerificationOptions) -Deserializes an instance from a JSON object. - -**Kind**: static method of [JwsVerificationOptions](#JwsVerificationOptions) - -| Param | Type | -| --- | --- | -| json | any | - - - -## Jwt -A wrapper around a JSON Web Token (JWK). - -**Kind**: global class - -* [Jwt](#Jwt) - * [new Jwt(jwt_string)](#new_Jwt_new) - * _instance_ - * [.toString()](#Jwt+toString) ⇒ string - * [.toJSON()](#Jwt+toJSON) ⇒ any - * [.clone()](#Jwt+clone) ⇒ [Jwt](#Jwt) - * _static_ - * [.fromJSON(json)](#Jwt.fromJSON) ⇒ [Jwt](#Jwt) - - - -### new Jwt(jwt_string) -Creates a new [Jwt](#Jwt) from the given string. - - -| Param | Type | -| --- | --- | -| jwt_string | string | - - - -### jwt.toString() ⇒ string -Returns a clone of the JWT string. - -**Kind**: instance method of [Jwt](#Jwt) - - -### jwt.toJSON() ⇒ any -Serializes this to a JSON object. - -**Kind**: instance method of [Jwt](#Jwt) - - -### jwt.clone() ⇒ [Jwt](#Jwt) -Deep clones the object. - -**Kind**: instance method of [Jwt](#Jwt) - - -### Jwt.fromJSON(json) ⇒ [Jwt](#Jwt) -Deserializes an instance from a JSON object. - -**Kind**: static method of [Jwt](#Jwt) - -| Param | Type | -| --- | --- | -| json | any | - - - -## JwtCredentialValidationOptions -Options to declare validation criteria when validating credentials. - -**Kind**: global class - -* [JwtCredentialValidationOptions](#JwtCredentialValidationOptions) - * [new JwtCredentialValidationOptions([options])](#new_JwtCredentialValidationOptions_new) - * _instance_ - * [.toJSON()](#JwtCredentialValidationOptions+toJSON) ⇒ any - * [.clone()](#JwtCredentialValidationOptions+clone) ⇒ [JwtCredentialValidationOptions](#JwtCredentialValidationOptions) - * _static_ - * [.fromJSON(json)](#JwtCredentialValidationOptions.fromJSON) ⇒ [JwtCredentialValidationOptions](#JwtCredentialValidationOptions) - - - -### new JwtCredentialValidationOptions([options]) - -| Param | Type | -| --- | --- | -| [options] | IJwtCredentialValidationOptions \| undefined | - - - -### jwtCredentialValidationOptions.toJSON() ⇒ any -Serializes this to a JSON object. - -**Kind**: instance method of [JwtCredentialValidationOptions](#JwtCredentialValidationOptions) - - -### jwtCredentialValidationOptions.clone() ⇒ [JwtCredentialValidationOptions](#JwtCredentialValidationOptions) -Deep clones the object. - -**Kind**: instance method of [JwtCredentialValidationOptions](#JwtCredentialValidationOptions) - - -### JwtCredentialValidationOptions.fromJSON(json) ⇒ [JwtCredentialValidationOptions](#JwtCredentialValidationOptions) -Deserializes an instance from a JSON object. - -**Kind**: static method of [JwtCredentialValidationOptions](#JwtCredentialValidationOptions) - -| Param | Type | -| --- | --- | -| json | any | - - - -## JwtCredentialValidator -A type for decoding and validating [Credential](#Credential). - -**Kind**: global class - -* [JwtCredentialValidator](#JwtCredentialValidator) - * [new JwtCredentialValidator(signatureVerifier)](#new_JwtCredentialValidator_new) - * _instance_ - * [.validate(credential_jwt, issuer, options, fail_fast)](#JwtCredentialValidator+validate) ⇒ [DecodedJwtCredential](#DecodedJwtCredential) - * [.verifySignature(credential, trustedIssuers, options)](#JwtCredentialValidator+verifySignature) ⇒ [DecodedJwtCredential](#DecodedJwtCredential) - * _static_ - * [.checkExpiresOnOrAfter(credential, timestamp)](#JwtCredentialValidator.checkExpiresOnOrAfter) - * [.checkIssuedOnOrBefore(credential, timestamp)](#JwtCredentialValidator.checkIssuedOnOrBefore) - * [.checkSubjectHolderRelationship(credential, holder, relationship)](#JwtCredentialValidator.checkSubjectHolderRelationship) - * [.checkStatus(credential, trustedIssuers, statusCheck)](#JwtCredentialValidator.checkStatus) - * [.checkStatusWithStatusList2021(credential, status_list, status_check)](#JwtCredentialValidator.checkStatusWithStatusList2021) - * [.extractIssuer(credential)](#JwtCredentialValidator.extractIssuer) ⇒ [CoreDID](#CoreDID) - * [.extractIssuerFromJwt(credential)](#JwtCredentialValidator.extractIssuerFromJwt) ⇒ [CoreDID](#CoreDID) - - - -### new JwtCredentialValidator(signatureVerifier) -Creates a new [JwtCredentialValidator](#JwtCredentialValidator). If a `signatureVerifier` is provided it will be used when -verifying decoded JWS signatures, otherwise the default which is only capable of handling the `EdDSA` -algorithm will be used. - - -| Param | Type | -| --- | --- | -| signatureVerifier | IJwsVerifier | - - - -### jwtCredentialValidator.validate(credential_jwt, issuer, options, fail_fast) ⇒ [DecodedJwtCredential](#DecodedJwtCredential) -Decodes and validates a [Credential](#Credential) issued as a JWS. A [DecodedJwtCredential](#DecodedJwtCredential) is returned upon -success. - -The following properties are validated according to `options`: -- the issuer's signature on the JWS, -- the expiration date, -- the issuance date, -- the semantic structure. - -# Warning -The lack of an error returned from this method is in of itself not enough to conclude that the credential can be -trusted. This section contains more information on additional checks that should be carried out before and after -calling this method. - -## The state of the issuer's DID Document -The caller must ensure that `issuer` represents an up-to-date DID Document. - -## Properties that are not validated - There are many properties defined in [The Verifiable Credentials Data Model](https://www.w3.org/TR/vc-data-model/) that are **not** validated, such as: -`proof`, `credentialStatus`, `type`, `credentialSchema`, `refreshService` **and more**. -These should be manually checked after validation, according to your requirements. - -# Errors -An error is returned whenever a validated condition is not satisfied. - -**Kind**: instance method of [JwtCredentialValidator](#JwtCredentialValidator) - -| Param | Type | -| --- | --- | -| credential_jwt | [Jwt](#Jwt) | -| issuer | [CoreDocument](#CoreDocument) \| IToCoreDocument | -| options | [JwtCredentialValidationOptions](#JwtCredentialValidationOptions) | -| fail_fast | [FailFast](#FailFast) | - - - -### jwtCredentialValidator.verifySignature(credential, trustedIssuers, options) ⇒ [DecodedJwtCredential](#DecodedJwtCredential) -Decode and verify the JWS signature of a [Credential](#Credential) issued as a JWT using the DID Document of a trusted -issuer. - -A [DecodedJwtCredential](#DecodedJwtCredential) is returned upon success. - -# Warning -The caller must ensure that the DID Documents of the trusted issuers are up-to-date. - -## Proofs - Only the JWS signature is verified. If the [Credential](#Credential) contains a `proof` property this will not be -verified by this method. - -# Errors -This method immediately returns an error if -the credential issuer' url cannot be parsed to a DID belonging to one of the trusted issuers. Otherwise an attempt -to verify the credential's signature will be made and an error is returned upon failure. - -**Kind**: instance method of [JwtCredentialValidator](#JwtCredentialValidator) - -| Param | Type | -| --- | --- | -| credential | [Jwt](#Jwt) | -| trustedIssuers | Array.<(CoreDocument\|IToCoreDocument)> | -| options | [JwsVerificationOptions](#JwsVerificationOptions) | - - - -### JwtCredentialValidator.checkExpiresOnOrAfter(credential, timestamp) -Validate that the credential expires on or after the specified timestamp. - -**Kind**: static method of [JwtCredentialValidator](#JwtCredentialValidator) - -| Param | Type | -| --- | --- | -| credential | [Credential](#Credential) | -| timestamp | [Timestamp](#Timestamp) | - - - -### JwtCredentialValidator.checkIssuedOnOrBefore(credential, timestamp) -Validate that the credential is issued on or before the specified timestamp. - -**Kind**: static method of [JwtCredentialValidator](#JwtCredentialValidator) - -| Param | Type | -| --- | --- | -| credential | [Credential](#Credential) | -| timestamp | [Timestamp](#Timestamp) | - - - -### JwtCredentialValidator.checkSubjectHolderRelationship(credential, holder, relationship) -Validate that the relationship between the `holder` and the credential subjects is in accordance with -`relationship`. The `holder` parameter is expected to be the URL of the holder. - -**Kind**: static method of [JwtCredentialValidator](#JwtCredentialValidator) - -| Param | Type | -| --- | --- | -| credential | [Credential](#Credential) | -| holder | string | -| relationship | [SubjectHolderRelationship](#SubjectHolderRelationship) | - - - -### JwtCredentialValidator.checkStatus(credential, trustedIssuers, statusCheck) -Checks whether the credential status has been revoked. - -Only supports `RevocationBitmap2022`. - -**Kind**: static method of [JwtCredentialValidator](#JwtCredentialValidator) - -| Param | Type | -| --- | --- | -| credential | [Credential](#Credential) | -| trustedIssuers | Array.<(CoreDocument\|IToCoreDocument)> | -| statusCheck | [StatusCheck](#StatusCheck) | - - - -### JwtCredentialValidator.checkStatusWithStatusList2021(credential, status_list, status_check) -Checks wheter the credential status has been revoked using `StatusList2021`. - -**Kind**: static method of [JwtCredentialValidator](#JwtCredentialValidator) - -| Param | Type | -| --- | --- | -| credential | [Credential](#Credential) | -| status_list | [StatusList2021Credential](#StatusList2021Credential) | -| status_check | [StatusCheck](#StatusCheck) | - - - -### JwtCredentialValidator.extractIssuer(credential) ⇒ [CoreDID](#CoreDID) -Utility for extracting the issuer field of a [Credential](#Credential) as a DID. - -### Errors - -Fails if the issuer field is not a valid DID. - -**Kind**: static method of [JwtCredentialValidator](#JwtCredentialValidator) - -| Param | Type | -| --- | --- | -| credential | [Credential](#Credential) | - - - -### JwtCredentialValidator.extractIssuerFromJwt(credential) ⇒ [CoreDID](#CoreDID) -Utility for extracting the issuer field of a credential in JWT representation as DID. - -# Errors - -If the JWT decoding fails or the issuer field is not a valid DID. - -**Kind**: static method of [JwtCredentialValidator](#JwtCredentialValidator) - -| Param | Type | -| --- | --- | -| credential | [Jwt](#Jwt) | - - - -## JwtDomainLinkageValidator -A validator for a Domain Linkage Configuration and Credentials. - -**Kind**: global class - -* [JwtDomainLinkageValidator](#JwtDomainLinkageValidator) - * [new JwtDomainLinkageValidator(signatureVerifier)](#new_JwtDomainLinkageValidator_new) - * [.validateLinkage(issuer, configuration, domain, options)](#JwtDomainLinkageValidator+validateLinkage) - * [.validateCredential(issuer, credentialJwt, domain, options)](#JwtDomainLinkageValidator+validateCredential) - - - -### new JwtDomainLinkageValidator(signatureVerifier) -Creates a new [JwtDomainLinkageValidator](#JwtDomainLinkageValidator). If a `signatureVerifier` is provided it will be used when -verifying decoded JWS signatures, otherwise the default which is only capable of handling the `EdDSA` -algorithm will be used. - - -| Param | Type | -| --- | --- | -| signatureVerifier | IJwsVerifier | - - - -### jwtDomainLinkageValidator.validateLinkage(issuer, configuration, domain, options) -Validates the linkage between a domain and a DID. -[DomainLinkageConfiguration](#DomainLinkageConfiguration) is validated according to [DID Configuration Resource Verification](https://identity.foundation/.well-known/resources/did-configuration/#did-configuration-resource-verification). - -Linkage is valid if no error is thrown. - -# Note: -- Only the [JSON Web Token Proof Format](https://identity.foundation/.well-known/resources/did-configuration/#json-web-token-proof-format) - is supported. -- Only the Credential issued by `issuer` is verified. - -# Errors - - - Semantic structure of `configuration` is invalid. - - `configuration` includes multiple credentials issued by `issuer`. - - Validation of the matched Domain Linkage Credential fails. - -**Kind**: instance method of [JwtDomainLinkageValidator](#JwtDomainLinkageValidator) - -| Param | Type | -| --- | --- | -| issuer | [CoreDocument](#CoreDocument) \| IToCoreDocument | -| configuration | [DomainLinkageConfiguration](#DomainLinkageConfiguration) | -| domain | string | -| options | [JwtCredentialValidationOptions](#JwtCredentialValidationOptions) | - - - -### jwtDomainLinkageValidator.validateCredential(issuer, credentialJwt, domain, options) -Validates a [Domain Linkage Credential](https://identity.foundation/.well-known/resources/did-configuration/#domain-linkage-credential). - -Error will be thrown in case the validation fails. - -**Kind**: instance method of [JwtDomainLinkageValidator](#JwtDomainLinkageValidator) - -| Param | Type | -| --- | --- | -| issuer | [CoreDocument](#CoreDocument) \| IToCoreDocument | -| credentialJwt | [Jwt](#Jwt) | -| domain | string | -| options | [JwtCredentialValidationOptions](#JwtCredentialValidationOptions) | - - - -## JwtPresentationOptions -**Kind**: global class - -* [JwtPresentationOptions](#JwtPresentationOptions) - * [new JwtPresentationOptions([options])](#new_JwtPresentationOptions_new) - * _instance_ - * [.toJSON()](#JwtPresentationOptions+toJSON) ⇒ any - * [.clone()](#JwtPresentationOptions+clone) ⇒ [JwtPresentationOptions](#JwtPresentationOptions) - * _static_ - * [.fromJSON(json)](#JwtPresentationOptions.fromJSON) ⇒ [JwtPresentationOptions](#JwtPresentationOptions) - - - -### new JwtPresentationOptions([options]) -Creates a new [JwtPresentationOptions](#JwtPresentationOptions) from the given fields. - -Throws an error if any of the options are invalid. - - -| Param | Type | -| --- | --- | -| [options] | IJwtPresentationOptions \| undefined | - - - -### jwtPresentationOptions.toJSON() ⇒ any -Serializes this to a JSON object. - -**Kind**: instance method of [JwtPresentationOptions](#JwtPresentationOptions) - - -### jwtPresentationOptions.clone() ⇒ [JwtPresentationOptions](#JwtPresentationOptions) -Deep clones the object. - -**Kind**: instance method of [JwtPresentationOptions](#JwtPresentationOptions) - - -### JwtPresentationOptions.fromJSON(json) ⇒ [JwtPresentationOptions](#JwtPresentationOptions) -Deserializes an instance from a JSON object. - -**Kind**: static method of [JwtPresentationOptions](#JwtPresentationOptions) - -| Param | Type | -| --- | --- | -| json | any | - - - -## JwtPresentationValidationOptions -Options to declare validation criteria when validating presentation. - -**Kind**: global class - -* [JwtPresentationValidationOptions](#JwtPresentationValidationOptions) - * [new JwtPresentationValidationOptions([options])](#new_JwtPresentationValidationOptions_new) - * _instance_ - * [.toJSON()](#JwtPresentationValidationOptions+toJSON) ⇒ any - * [.clone()](#JwtPresentationValidationOptions+clone) ⇒ [JwtPresentationValidationOptions](#JwtPresentationValidationOptions) - * _static_ - * [.fromJSON(json)](#JwtPresentationValidationOptions.fromJSON) ⇒ [JwtPresentationValidationOptions](#JwtPresentationValidationOptions) - - - -### new JwtPresentationValidationOptions([options]) -Creates a new [JwtPresentationValidationOptions](#JwtPresentationValidationOptions) from the given fields. - -Throws an error if any of the options are invalid. - - -| Param | Type | -| --- | --- | -| [options] | IJwtPresentationValidationOptions \| undefined | - - - -### jwtPresentationValidationOptions.toJSON() ⇒ any -Serializes this to a JSON object. - -**Kind**: instance method of [JwtPresentationValidationOptions](#JwtPresentationValidationOptions) - - -### jwtPresentationValidationOptions.clone() ⇒ [JwtPresentationValidationOptions](#JwtPresentationValidationOptions) -Deep clones the object. - -**Kind**: instance method of [JwtPresentationValidationOptions](#JwtPresentationValidationOptions) - - -### JwtPresentationValidationOptions.fromJSON(json) ⇒ [JwtPresentationValidationOptions](#JwtPresentationValidationOptions) -Deserializes an instance from a JSON object. - -**Kind**: static method of [JwtPresentationValidationOptions](#JwtPresentationValidationOptions) - -| Param | Type | -| --- | --- | -| json | any | - - - -## JwtPresentationValidator -**Kind**: global class - -* [JwtPresentationValidator](#JwtPresentationValidator) - * [new JwtPresentationValidator(signatureVerifier)](#new_JwtPresentationValidator_new) - * _instance_ - * [.validate(presentationJwt, holder, validation_options)](#JwtPresentationValidator+validate) ⇒ [DecodedJwtPresentation](#DecodedJwtPresentation) - * _static_ - * [.checkStructure(presentation)](#JwtPresentationValidator.checkStructure) - * [.extractHolder(presentation)](#JwtPresentationValidator.extractHolder) ⇒ [CoreDID](#CoreDID) - - - -### new JwtPresentationValidator(signatureVerifier) -Creates a new [JwtPresentationValidator](#JwtPresentationValidator). If a `signatureVerifier` is provided it will be used when -verifying decoded JWS signatures, otherwise the default which is only capable of handling the `EdDSA` -algorithm will be used. - - -| Param | Type | -| --- | --- | -| signatureVerifier | IJwsVerifier | - - - -### jwtPresentationValidator.validate(presentationJwt, holder, validation_options) ⇒ [DecodedJwtPresentation](#DecodedJwtPresentation) -Validates a [Presentation](#Presentation) encoded as a [Jwt](#Jwt). - -The following properties are validated according to `options`: -- the JWT can be decoded into a semantically valid presentation. -- the expiration and issuance date contained in the JWT claims. -- the holder's signature. - -Validation is done with respect to the properties set in `options`. - -# Warning - -* This method does NOT validate the constituent credentials and therefore also not the relationship between the -credentials' subjects and the presentation holder. This can be done with [JwtCredentialValidationOptions](#JwtCredentialValidationOptions). -* The lack of an error returned from this method is in of itself not enough to conclude that the presentation can -be trusted. This section contains more information on additional checks that should be carried out before and -after calling this method. - -## The state of the supplied DID Documents. - -The caller must ensure that the DID Documents in `holder` are up-to-date. - -# Errors - -An error is returned whenever a validated condition is not satisfied or when decoding fails. - -**Kind**: instance method of [JwtPresentationValidator](#JwtPresentationValidator) - -| Param | Type | -| --- | --- | -| presentationJwt | [Jwt](#Jwt) | -| holder | [CoreDocument](#CoreDocument) \| IToCoreDocument | -| validation_options | [JwtPresentationValidationOptions](#JwtPresentationValidationOptions) | - - - -### JwtPresentationValidator.checkStructure(presentation) -Validates the semantic structure of the [Presentation](#Presentation). - -**Kind**: static method of [JwtPresentationValidator](#JwtPresentationValidator) - -| Param | Type | -| --- | --- | -| presentation | [Presentation](#Presentation) | - - - -### JwtPresentationValidator.extractHolder(presentation) ⇒ [CoreDID](#CoreDID) -Attempt to extract the holder of the presentation. - -# Errors: -* If deserialization/decoding of the presentation fails. -* If the holder can't be parsed as DIDs. - -**Kind**: static method of [JwtPresentationValidator](#JwtPresentationValidator) - -| Param | Type | -| --- | --- | -| presentation | [Jwt](#Jwt) | - - - -## KeyBindingJWTValidationOptions -Options to declare validation criteria when validating credentials. - -**Kind**: global class - -* [KeyBindingJWTValidationOptions](#KeyBindingJWTValidationOptions) - * [new KeyBindingJWTValidationOptions([options])](#new_KeyBindingJWTValidationOptions_new) - * _instance_ - * [.toJSON()](#KeyBindingJWTValidationOptions+toJSON) ⇒ any - * [.clone()](#KeyBindingJWTValidationOptions+clone) ⇒ [KeyBindingJWTValidationOptions](#KeyBindingJWTValidationOptions) - * _static_ - * [.fromJSON(json)](#KeyBindingJWTValidationOptions.fromJSON) ⇒ [KeyBindingJWTValidationOptions](#KeyBindingJWTValidationOptions) - - - -### new KeyBindingJWTValidationOptions([options]) - -| Param | Type | -| --- | --- | -| [options] | IKeyBindingJWTValidationOptions \| undefined | - - - -### keyBindingJWTValidationOptions.toJSON() ⇒ any -Serializes this to a JSON object. - -**Kind**: instance method of [KeyBindingJWTValidationOptions](#KeyBindingJWTValidationOptions) - - -### keyBindingJWTValidationOptions.clone() ⇒ [KeyBindingJWTValidationOptions](#KeyBindingJWTValidationOptions) -Deep clones the object. - -**Kind**: instance method of [KeyBindingJWTValidationOptions](#KeyBindingJWTValidationOptions) - - -### KeyBindingJWTValidationOptions.fromJSON(json) ⇒ [KeyBindingJWTValidationOptions](#KeyBindingJWTValidationOptions) -Deserializes an instance from a JSON object. - -**Kind**: static method of [KeyBindingJWTValidationOptions](#KeyBindingJWTValidationOptions) - -| Param | Type | -| --- | --- | -| json | any | - - - -## KeyBindingJwtClaims -Claims set for key binding JWT. - -**Kind**: global class - -* [KeyBindingJwtClaims](#KeyBindingJwtClaims) - * [new KeyBindingJwtClaims(jwt, disclosures, nonce, aud, [issued_at], [custom_properties])](#new_KeyBindingJwtClaims_new) - * _instance_ - * [.toString()](#KeyBindingJwtClaims+toString) ⇒ string - * [.iat()](#KeyBindingJwtClaims+iat) ⇒ bigint - * [.aud()](#KeyBindingJwtClaims+aud) ⇒ string - * [.nonce()](#KeyBindingJwtClaims+nonce) ⇒ string - * [.sdHash()](#KeyBindingJwtClaims+sdHash) ⇒ string - * [.customProperties()](#KeyBindingJwtClaims+customProperties) ⇒ Record.<string, any> - * [.toJSON()](#KeyBindingJwtClaims+toJSON) ⇒ any - * [.clone()](#KeyBindingJwtClaims+clone) ⇒ [KeyBindingJwtClaims](#KeyBindingJwtClaims) - * _static_ - * [.keyBindingJwtHeaderTyp()](#KeyBindingJwtClaims.keyBindingJwtHeaderTyp) ⇒ string - * [.fromJSON(json)](#KeyBindingJwtClaims.fromJSON) ⇒ [KeyBindingJwtClaims](#KeyBindingJwtClaims) - - - -### new KeyBindingJwtClaims(jwt, disclosures, nonce, aud, [issued_at], [custom_properties]) -Creates a new [`KeyBindingJwtClaims`]. -When `issued_at` is left as None, it will automatically default to the current time. - -# Error -When `issued_at` is set to `None` and the system returns time earlier than `SystemTime::UNIX_EPOCH`. - - -| Param | Type | -| --- | --- | -| jwt | string | -| disclosures | Array.<string> | -| nonce | string | -| aud | string | -| [issued_at] | [Timestamp](#Timestamp) \| undefined | -| [custom_properties] | Record.<string, any> \| undefined | - - - -### keyBindingJwtClaims.toString() ⇒ string -Returns a string representation of the claims. - -**Kind**: instance method of [KeyBindingJwtClaims](#KeyBindingJwtClaims) - - -### keyBindingJwtClaims.iat() ⇒ bigint -Returns a copy of the issued at `iat` property. - -**Kind**: instance method of [KeyBindingJwtClaims](#KeyBindingJwtClaims) - - -### keyBindingJwtClaims.aud() ⇒ string -Returns a copy of the audience `aud` property. - -**Kind**: instance method of [KeyBindingJwtClaims](#KeyBindingJwtClaims) - - -### keyBindingJwtClaims.nonce() ⇒ string -Returns a copy of the `nonce` property. - -**Kind**: instance method of [KeyBindingJwtClaims](#KeyBindingJwtClaims) - - -### keyBindingJwtClaims.sdHash() ⇒ string -Returns a copy of the `sd_hash` property. - -**Kind**: instance method of [KeyBindingJwtClaims](#KeyBindingJwtClaims) - - -### keyBindingJwtClaims.customProperties() ⇒ Record.<string, any> -Returns a copy of the custom properties. - -**Kind**: instance method of [KeyBindingJwtClaims](#KeyBindingJwtClaims) - - -### keyBindingJwtClaims.toJSON() ⇒ any -Serializes this to a JSON object. - -**Kind**: instance method of [KeyBindingJwtClaims](#KeyBindingJwtClaims) - - -### keyBindingJwtClaims.clone() ⇒ [KeyBindingJwtClaims](#KeyBindingJwtClaims) -Deep clones the object. - -**Kind**: instance method of [KeyBindingJwtClaims](#KeyBindingJwtClaims) - - -### KeyBindingJwtClaims.keyBindingJwtHeaderTyp() ⇒ string -Returns the value of the `typ` property of the JWT header according to -https://www.ietf.org/archive/id/draft-ietf-oauth-selective-disclosure-jwt-07.html#name-key-binding-jwt - -**Kind**: static method of [KeyBindingJwtClaims](#KeyBindingJwtClaims) - - -### KeyBindingJwtClaims.fromJSON(json) ⇒ [KeyBindingJwtClaims](#KeyBindingJwtClaims) -Deserializes an instance from a JSON object. - -**Kind**: static method of [KeyBindingJwtClaims](#KeyBindingJwtClaims) - -| Param | Type | -| --- | --- | -| json | any | - - - -## LinkedDomainService -**Kind**: global class - -* [LinkedDomainService](#LinkedDomainService) - * [new LinkedDomainService(options)](#new_LinkedDomainService_new) - * _instance_ - * [.domains()](#LinkedDomainService+domains) ⇒ Array.<string> - * [.toService()](#LinkedDomainService+toService) ⇒ [Service](#Service) - * [.clone()](#LinkedDomainService+clone) ⇒ [LinkedDomainService](#LinkedDomainService) - * _static_ - * [.fromService(service)](#LinkedDomainService.fromService) ⇒ [LinkedDomainService](#LinkedDomainService) - * [.isValid(service)](#LinkedDomainService.isValid) ⇒ boolean - - - -### new LinkedDomainService(options) -Constructs a new [LinkedDomainService](#LinkedDomainService) that wraps a spec compliant [Linked Domain Service Endpoint](https://identity.foundation/.well-known/resources/did-configuration/#linked-domain-service-endpoint). - -Domain URLs must include the `https` scheme in order to pass the domain linkage validation. - - -| Param | Type | -| --- | --- | -| options | ILinkedDomainService | - - - -### linkedDomainService.domains() ⇒ Array.<string> -Returns the domains contained in the Linked Domain Service. - -**Kind**: instance method of [LinkedDomainService](#LinkedDomainService) - - -### linkedDomainService.toService() ⇒ [Service](#Service) -Returns the inner service which can be added to a DID Document. - -**Kind**: instance method of [LinkedDomainService](#LinkedDomainService) - - -### linkedDomainService.clone() ⇒ [LinkedDomainService](#LinkedDomainService) -Deep clones the object. - -**Kind**: instance method of [LinkedDomainService](#LinkedDomainService) - - -### LinkedDomainService.fromService(service) ⇒ [LinkedDomainService](#LinkedDomainService) -Creates a new [LinkedDomainService](#LinkedDomainService) from a [Service](#Service). - -# Error - -Errors if `service` is not a valid Linked Domain Service. - -**Kind**: static method of [LinkedDomainService](#LinkedDomainService) - -| Param | Type | -| --- | --- | -| service | [Service](#Service) | - - - -### LinkedDomainService.isValid(service) ⇒ boolean -Returns `true` if a [Service](#Service) is a valid Linked Domain Service. - -**Kind**: static method of [LinkedDomainService](#LinkedDomainService) - -| Param | Type | -| --- | --- | -| service | [Service](#Service) | - - - -## MethodData -Supported verification method data formats. - -**Kind**: global class - -* [MethodData](#MethodData) - * _instance_ - * [.tryCustom()](#MethodData+tryCustom) ⇒ [CustomMethodData](#CustomMethodData) - * [.tryDecode()](#MethodData+tryDecode) ⇒ Uint8Array - * [.tryPublicKeyJwk()](#MethodData+tryPublicKeyJwk) ⇒ [Jwk](#Jwk) - * [.toJSON()](#MethodData+toJSON) ⇒ any - * [.clone()](#MethodData+clone) ⇒ [MethodData](#MethodData) - * _static_ - * [.newBase58(data)](#MethodData.newBase58) ⇒ [MethodData](#MethodData) - * [.newMultibase(data)](#MethodData.newMultibase) ⇒ [MethodData](#MethodData) - * [.newJwk(key)](#MethodData.newJwk) ⇒ [MethodData](#MethodData) - * [.newCustom(name, data)](#MethodData.newCustom) ⇒ [MethodData](#MethodData) - * [.fromJSON(json)](#MethodData.fromJSON) ⇒ [MethodData](#MethodData) - - - -### methodData.tryCustom() ⇒ [CustomMethodData](#CustomMethodData) -Returns the wrapped custom method data format is `Custom`. - -**Kind**: instance method of [MethodData](#MethodData) - - -### methodData.tryDecode() ⇒ Uint8Array -Returns a `Uint8Array` containing the decoded bytes of the [MethodData](#MethodData). - -This is generally a public key identified by a [MethodData](#MethodData) value. - -### Errors -Decoding can fail if [MethodData](#MethodData) has invalid content or cannot be -represented as a vector of bytes. - -**Kind**: instance method of [MethodData](#MethodData) - - -### methodData.tryPublicKeyJwk() ⇒ [Jwk](#Jwk) -Returns the wrapped [Jwk](#Jwk) if the format is `PublicKeyJwk`. - -**Kind**: instance method of [MethodData](#MethodData) - - -### methodData.toJSON() ⇒ any -Serializes this to a JSON object. - -**Kind**: instance method of [MethodData](#MethodData) - - -### methodData.clone() ⇒ [MethodData](#MethodData) -Deep clones the object. - -**Kind**: instance method of [MethodData](#MethodData) - - -### MethodData.newBase58(data) ⇒ [MethodData](#MethodData) -Creates a new [MethodData](#MethodData) variant with Base58-BTC encoded content. - -**Kind**: static method of [MethodData](#MethodData) - -| Param | Type | -| --- | --- | -| data | Uint8Array | - - - -### MethodData.newMultibase(data) ⇒ [MethodData](#MethodData) -Creates a new [MethodData](#MethodData) variant with Multibase-encoded content. - -**Kind**: static method of [MethodData](#MethodData) - -| Param | Type | -| --- | --- | -| data | Uint8Array | - - - -### MethodData.newJwk(key) ⇒ [MethodData](#MethodData) -Creates a new [MethodData](#MethodData) variant consisting of the given `key`. - -### Errors -An error is thrown if the given `key` contains any private components. - -**Kind**: static method of [MethodData](#MethodData) - -| Param | Type | -| --- | --- | -| key | [Jwk](#Jwk) | - - - -### MethodData.newCustom(name, data) ⇒ [MethodData](#MethodData) -Creates a new custom [MethodData](#MethodData). - -**Kind**: static method of [MethodData](#MethodData) - -| Param | Type | -| --- | --- | -| name | string | -| data | any | - - - -### MethodData.fromJSON(json) ⇒ [MethodData](#MethodData) -Deserializes an instance from a JSON object. - -**Kind**: static method of [MethodData](#MethodData) - -| Param | Type | -| --- | --- | -| json | any | - - - -## MethodDigest -Unique identifier of a [VerificationMethod](#VerificationMethod). - -NOTE: -This class does not have a JSON representation, -use the methods `pack` and `unpack` instead. - -**Kind**: global class - -* [MethodDigest](#MethodDigest) - * [new MethodDigest(verification_method)](#new_MethodDigest_new) - * _instance_ - * [.pack()](#MethodDigest+pack) ⇒ Uint8Array - * [.clone()](#MethodDigest+clone) ⇒ [MethodDigest](#MethodDigest) - * _static_ - * [.unpack(bytes)](#MethodDigest.unpack) ⇒ [MethodDigest](#MethodDigest) - - - -### new MethodDigest(verification_method) - -| Param | Type | -| --- | --- | -| verification_method | [VerificationMethod](#VerificationMethod) | - - - -### methodDigest.pack() ⇒ Uint8Array -Packs [MethodDigest](#MethodDigest) into bytes. - -**Kind**: instance method of [MethodDigest](#MethodDigest) - - -### methodDigest.clone() ⇒ [MethodDigest](#MethodDigest) -Deep clones the object. - -**Kind**: instance method of [MethodDigest](#MethodDigest) - - -### MethodDigest.unpack(bytes) ⇒ [MethodDigest](#MethodDigest) -Unpacks bytes into [MethodDigest](#MethodDigest). - -**Kind**: static method of [MethodDigest](#MethodDigest) - -| Param | Type | -| --- | --- | -| bytes | Uint8Array | - - - -## MethodScope -Supported verification method types. - -**Kind**: global class - -* [MethodScope](#MethodScope) - * _instance_ - * [.toString()](#MethodScope+toString) ⇒ string - * [.toJSON()](#MethodScope+toJSON) ⇒ any - * [.clone()](#MethodScope+clone) ⇒ [MethodScope](#MethodScope) - * _static_ - * [.VerificationMethod()](#MethodScope.VerificationMethod) ⇒ [MethodScope](#MethodScope) - * [.Authentication()](#MethodScope.Authentication) ⇒ [MethodScope](#MethodScope) - * [.AssertionMethod()](#MethodScope.AssertionMethod) ⇒ [MethodScope](#MethodScope) - * [.KeyAgreement()](#MethodScope.KeyAgreement) ⇒ [MethodScope](#MethodScope) - * [.CapabilityDelegation()](#MethodScope.CapabilityDelegation) ⇒ [MethodScope](#MethodScope) - * [.CapabilityInvocation()](#MethodScope.CapabilityInvocation) ⇒ [MethodScope](#MethodScope) - * [.fromJSON(json)](#MethodScope.fromJSON) ⇒ [MethodScope](#MethodScope) - - - -### methodScope.toString() ⇒ string -Returns the [MethodScope](#MethodScope) as a string. - -**Kind**: instance method of [MethodScope](#MethodScope) - - -### methodScope.toJSON() ⇒ any -Serializes this to a JSON object. - -**Kind**: instance method of [MethodScope](#MethodScope) - - -### methodScope.clone() ⇒ [MethodScope](#MethodScope) -Deep clones the object. - -**Kind**: instance method of [MethodScope](#MethodScope) - - -### MethodScope.VerificationMethod() ⇒ [MethodScope](#MethodScope) -**Kind**: static method of [MethodScope](#MethodScope) - - -### MethodScope.Authentication() ⇒ [MethodScope](#MethodScope) -**Kind**: static method of [MethodScope](#MethodScope) - - -### MethodScope.AssertionMethod() ⇒ [MethodScope](#MethodScope) -**Kind**: static method of [MethodScope](#MethodScope) - - -### MethodScope.KeyAgreement() ⇒ [MethodScope](#MethodScope) -**Kind**: static method of [MethodScope](#MethodScope) - - -### MethodScope.CapabilityDelegation() ⇒ [MethodScope](#MethodScope) -**Kind**: static method of [MethodScope](#MethodScope) - - -### MethodScope.CapabilityInvocation() ⇒ [MethodScope](#MethodScope) -**Kind**: static method of [MethodScope](#MethodScope) - - -### MethodScope.fromJSON(json) ⇒ [MethodScope](#MethodScope) -Deserializes an instance from a JSON object. - -**Kind**: static method of [MethodScope](#MethodScope) - -| Param | Type | -| --- | --- | -| json | any | - - - -## MethodType -Supported verification method types. - -**Kind**: global class - -* [MethodType](#MethodType) - * _instance_ - * [.toString()](#MethodType+toString) ⇒ string - * [.toJSON()](#MethodType+toJSON) ⇒ any - * [.clone()](#MethodType+clone) ⇒ [MethodType](#MethodType) - * _static_ - * [.Ed25519VerificationKey2018()](#MethodType.Ed25519VerificationKey2018) ⇒ [MethodType](#MethodType) - * [.X25519KeyAgreementKey2019()](#MethodType.X25519KeyAgreementKey2019) ⇒ [MethodType](#MethodType) - * [.JsonWebKey()](#MethodType.JsonWebKey) ⇒ [MethodType](#MethodType) - * [.custom(type_)](#MethodType.custom) ⇒ [MethodType](#MethodType) - * [.fromJSON(json)](#MethodType.fromJSON) ⇒ [MethodType](#MethodType) - - - -### methodType.toString() ⇒ string -Returns the [MethodType](#MethodType) as a string. - -**Kind**: instance method of [MethodType](#MethodType) - - -### methodType.toJSON() ⇒ any -Serializes this to a JSON object. - -**Kind**: instance method of [MethodType](#MethodType) - - -### methodType.clone() ⇒ [MethodType](#MethodType) -Deep clones the object. - -**Kind**: instance method of [MethodType](#MethodType) - - -### MethodType.Ed25519VerificationKey2018() ⇒ [MethodType](#MethodType) -**Kind**: static method of [MethodType](#MethodType) - - -### MethodType.X25519KeyAgreementKey2019() ⇒ [MethodType](#MethodType) -**Kind**: static method of [MethodType](#MethodType) - - -### MethodType.JsonWebKey() ⇒ [MethodType](#MethodType) -A verification method for use with JWT verification as prescribed by the [Jwk](#Jwk) -in the `publicKeyJwk` entry. - -**Kind**: static method of [MethodType](#MethodType) - - -### MethodType.custom(type_) ⇒ [MethodType](#MethodType) -A custom method. - -**Kind**: static method of [MethodType](#MethodType) - -| Param | Type | -| --- | --- | -| type_ | string | - - - -### MethodType.fromJSON(json) ⇒ [MethodType](#MethodType) -Deserializes an instance from a JSON object. - -**Kind**: static method of [MethodType](#MethodType) - -| Param | Type | -| --- | --- | -| json | any | - - - -## Presentation -**Kind**: global class - -* [Presentation](#Presentation) - * [new Presentation(values)](#new_Presentation_new) - * _instance_ - * [.context()](#Presentation+context) ⇒ Array.<(string\|Record.<string, any>)> - * [.id()](#Presentation+id) ⇒ string \| undefined - * [.type()](#Presentation+type) ⇒ Array.<string> - * [.verifiableCredential()](#Presentation+verifiableCredential) ⇒ [Array.<UnknownCredential>](#UnknownCredential) - * [.holder()](#Presentation+holder) ⇒ string - * [.refreshService()](#Presentation+refreshService) ⇒ Array.<RefreshService> - * [.termsOfUse()](#Presentation+termsOfUse) ⇒ Array.<Policy> - * [.proof()](#Presentation+proof) ⇒ [Proof](#Proof) \| undefined - * [.setProof([proof])](#Presentation+setProof) - * [.properties()](#Presentation+properties) ⇒ Map.<string, any> - * [.toJSON()](#Presentation+toJSON) ⇒ any - * [.clone()](#Presentation+clone) ⇒ [Presentation](#Presentation) - * _static_ - * [.BaseContext()](#Presentation.BaseContext) ⇒ string - * [.BaseType()](#Presentation.BaseType) ⇒ string - * [.fromJSON(json)](#Presentation.fromJSON) ⇒ [Presentation](#Presentation) - - - -### new Presentation(values) -Constructs a new presentation. - - -| Param | Type | -| --- | --- | -| values | IPresentation | - - - -### presentation.context() ⇒ Array.<(string\|Record.<string, any>)> -Returns a copy of the JSON-LD context(s) applicable to the presentation. - -**Kind**: instance method of [Presentation](#Presentation) - - -### presentation.id() ⇒ string \| undefined -Returns a copy of the unique `URI` identifying the presentation. - -**Kind**: instance method of [Presentation](#Presentation) - - -### presentation.type() ⇒ Array.<string> -Returns a copy of the URIs defining the type of the presentation. - -**Kind**: instance method of [Presentation](#Presentation) - - -### presentation.verifiableCredential() ⇒ [Array.<UnknownCredential>](#UnknownCredential) -Returns the JWT credentials expressing the claims of the presentation. - -**Kind**: instance method of [Presentation](#Presentation) - - -### presentation.holder() ⇒ string -Returns a copy of the URI of the entity that generated the presentation. - -**Kind**: instance method of [Presentation](#Presentation) - - -### presentation.refreshService() ⇒ Array.<RefreshService> -Returns a copy of the service(s) used to refresh an expired [Credential](#Credential) in the presentation. - -**Kind**: instance method of [Presentation](#Presentation) - - -### presentation.termsOfUse() ⇒ Array.<Policy> -Returns a copy of the terms-of-use specified by the presentation holder - -**Kind**: instance method of [Presentation](#Presentation) - - -### presentation.proof() ⇒ [Proof](#Proof) \| undefined -Optional cryptographic proof, unrelated to JWT. - -**Kind**: instance method of [Presentation](#Presentation) - - -### presentation.setProof([proof]) -Sets the proof property of the [Presentation](#Presentation). - -Note that this proof is not related to JWT. - -**Kind**: instance method of [Presentation](#Presentation) - -| Param | Type | -| --- | --- | -| [proof] | [Proof](#Proof) \| undefined | - - - -### presentation.properties() ⇒ Map.<string, any> -Returns a copy of the miscellaneous properties on the presentation. - -**Kind**: instance method of [Presentation](#Presentation) - - -### presentation.toJSON() ⇒ any -Serializes this to a JSON object. - -**Kind**: instance method of [Presentation](#Presentation) - - -### presentation.clone() ⇒ [Presentation](#Presentation) -Deep clones the object. - -**Kind**: instance method of [Presentation](#Presentation) - - -### Presentation.BaseContext() ⇒ string -Returns the base JSON-LD context. - -**Kind**: static method of [Presentation](#Presentation) - - -### Presentation.BaseType() ⇒ string -Returns the base type. - -**Kind**: static method of [Presentation](#Presentation) - - -### Presentation.fromJSON(json) ⇒ [Presentation](#Presentation) -Deserializes an instance from a JSON object. - -**Kind**: static method of [Presentation](#Presentation) - -| Param | Type | -| --- | --- | -| json | any | - - - -## Proof -Represents a cryptographic proof that can be used to validate verifiable credentials and -presentations. - -This representation does not inherently implement any standard; instead, it -can be utilized to implement standards or user-defined proofs. The presence of the -`type` field is necessary to accommodate different types of cryptographic proofs. - -Note that this proof is not related to JWT and can be used in combination or as an alternative -to it. - -**Kind**: global class - -* [Proof](#Proof) - * [new Proof(type_, properties)](#new_Proof_new) - * _instance_ - * [.type()](#Proof+type) ⇒ string - * [.properties()](#Proof+properties) ⇒ any - * [.toJSON()](#Proof+toJSON) ⇒ any - * [.clone()](#Proof+clone) ⇒ [Proof](#Proof) - * _static_ - * [.fromJSON(json)](#Proof.fromJSON) ⇒ [Proof](#Proof) - - - -### new Proof(type_, properties) - -| Param | Type | -| --- | --- | -| type_ | string | -| properties | any | - - - -### proof.type() ⇒ string -Returns the type of proof. - -**Kind**: instance method of [Proof](#Proof) - - -### proof.properties() ⇒ any -Returns the properties of the proof. - -**Kind**: instance method of [Proof](#Proof) - - -### proof.toJSON() ⇒ any -Serializes this to a JSON object. - -**Kind**: instance method of [Proof](#Proof) - - -### proof.clone() ⇒ [Proof](#Proof) -Deep clones the object. - -**Kind**: instance method of [Proof](#Proof) - - -### Proof.fromJSON(json) ⇒ [Proof](#Proof) -Deserializes an instance from a JSON object. - -**Kind**: static method of [Proof](#Proof) - -| Param | Type | -| --- | --- | -| json | any | - - - -## Resolver -Convenience type for resolving DID documents from different DID methods. - -Also provides methods for resolving DID Documents associated with -verifiable [Credential](#Credential)s and [Presentation](#Presentation)s. - -# Configuration - -The resolver will only be able to resolve DID documents for methods it has been configured for in the constructor. - -**Kind**: global class - -* [Resolver](#Resolver) - * [new Resolver(config)](#new_Resolver_new) - * [.resolve(did)](#Resolver+resolve) ⇒ Promise.<(CoreDocument\|IToCoreDocument)> - * [.resolveMultiple(dids)](#Resolver+resolveMultiple) ⇒ Promise.<Array.<(CoreDocument\|IToCoreDocument)>> - - - -### new Resolver(config) -Constructs a new [Resolver](#Resolver). - -# Errors -If both a `client` is given and the `handlers` map contains the "iota" key the construction process -will throw an error because the handler for the "iota" method then becomes ambiguous. - - -| Param | Type | -| --- | --- | -| config | ResolverConfig | - - - -### resolver.resolve(did) ⇒ Promise.<(CoreDocument\|IToCoreDocument)> -Fetches the DID Document of the given DID. - -### Errors - -Errors if the resolver has not been configured to handle the method -corresponding to the given DID or the resolution process itself fails. - -**Kind**: instance method of [Resolver](#Resolver) - -| Param | Type | -| --- | --- | -| did | string | - - - -### resolver.resolveMultiple(dids) ⇒ Promise.<Array.<(CoreDocument\|IToCoreDocument)>> -Concurrently fetches the DID Documents of the multiple given DIDs. - -# Errors -* If the resolver has not been configured to handle the method of any of the given DIDs. -* If the resolution process of any DID fails. - -## Note -* The order of the documents in the returned array matches that in `dids`. -* If `dids` contains duplicates, these will be resolved only once and the resolved document -is copied into the returned array to match the order of `dids`. - -**Kind**: instance method of [Resolver](#Resolver) - -| Param | Type | -| --- | --- | -| dids | Array.<string> | - - - -## RevocationBitmap -A compressed bitmap for managing credential revocation. - -**Kind**: global class - -* [RevocationBitmap](#RevocationBitmap) - * [new RevocationBitmap()](#new_RevocationBitmap_new) - * _instance_ - * [.isRevoked(index)](#RevocationBitmap+isRevoked) ⇒ boolean - * [.revoke(index)](#RevocationBitmap+revoke) ⇒ boolean - * [.unrevoke(index)](#RevocationBitmap+unrevoke) ⇒ boolean - * [.len()](#RevocationBitmap+len) ⇒ number - * [.toService(serviceId)](#RevocationBitmap+toService) ⇒ [Service](#Service) - * _static_ - * [.type()](#RevocationBitmap.type) ⇒ string - * [.fromEndpoint(service)](#RevocationBitmap.fromEndpoint) ⇒ [RevocationBitmap](#RevocationBitmap) - - - -### new RevocationBitmap() -Creates a new [RevocationBitmap](#RevocationBitmap) instance. - - - -### revocationBitmap.isRevoked(index) ⇒ boolean -Returns `true` if the credential at the given `index` is revoked. - -**Kind**: instance method of [RevocationBitmap](#RevocationBitmap) - -| Param | Type | -| --- | --- | -| index | number | - - - -### revocationBitmap.revoke(index) ⇒ boolean -Mark the given index as revoked. - -Returns true if the index was absent from the set. - -**Kind**: instance method of [RevocationBitmap](#RevocationBitmap) - -| Param | Type | -| --- | --- | -| index | number | - - - -### revocationBitmap.unrevoke(index) ⇒ boolean -Mark the index as not revoked. - -Returns true if the index was present in the set. - -**Kind**: instance method of [RevocationBitmap](#RevocationBitmap) - -| Param | Type | -| --- | --- | -| index | number | - - - -### revocationBitmap.len() ⇒ number -Returns the number of revoked credentials. - -**Kind**: instance method of [RevocationBitmap](#RevocationBitmap) - - -### revocationBitmap.toService(serviceId) ⇒ [Service](#Service) -Return a `Service` with: -- the service's id set to `serviceId`, -- of type `RevocationBitmap2022`, -- and with the bitmap embedded in a data url in the service's endpoint. - -**Kind**: instance method of [RevocationBitmap](#RevocationBitmap) - -| Param | Type | -| --- | --- | -| serviceId | [DIDUrl](#DIDUrl) | - - - -### RevocationBitmap.type() ⇒ string -The name of the service type. - -**Kind**: static method of [RevocationBitmap](#RevocationBitmap) - - -### RevocationBitmap.fromEndpoint(service) ⇒ [RevocationBitmap](#RevocationBitmap) -Try to construct a [RevocationBitmap](#RevocationBitmap) from a service -if it is a valid Revocation Bitmap Service. - -**Kind**: static method of [RevocationBitmap](#RevocationBitmap) - -| Param | Type | -| --- | --- | -| service | [Service](#Service) | - - - -## SdJwt -Representation of an SD-JWT of the format -`~~~...~~`. - -**Kind**: global class - -* [SdJwt](#SdJwt) - * [new SdJwt(jwt, disclosures, [key_binding_jwt])](#new_SdJwt_new) - * _instance_ - * [.presentation()](#SdJwt+presentation) ⇒ string - * [.toString()](#SdJwt+toString) ⇒ string - * [.jwt()](#SdJwt+jwt) ⇒ string - * [.disclosures()](#SdJwt+disclosures) ⇒ Array.<string> - * [.keyBindingJwt()](#SdJwt+keyBindingJwt) ⇒ string \| undefined - * [.clone()](#SdJwt+clone) ⇒ [SdJwt](#SdJwt) - * _static_ - * [.parse(sd_jwt)](#SdJwt.parse) ⇒ [SdJwt](#SdJwt) - - - -### new SdJwt(jwt, disclosures, [key_binding_jwt]) -Creates a new `SdJwt` from its components. - - -| Param | Type | -| --- | --- | -| jwt | string | -| disclosures | Array.<string> | -| [key_binding_jwt] | string \| undefined | - - - -### sdJwt.presentation() ⇒ string -Serializes the components into the final SD-JWT. - -**Kind**: instance method of [SdJwt](#SdJwt) - - -### sdJwt.toString() ⇒ string -Serializes the components into the final SD-JWT. - -**Kind**: instance method of [SdJwt](#SdJwt) - - -### sdJwt.jwt() ⇒ string -The JWT part. - -**Kind**: instance method of [SdJwt](#SdJwt) - - -### sdJwt.disclosures() ⇒ Array.<string> -The disclosures part. - -**Kind**: instance method of [SdJwt](#SdJwt) - - -### sdJwt.keyBindingJwt() ⇒ string \| undefined -The optional key binding JWT. - -**Kind**: instance method of [SdJwt](#SdJwt) - - -### sdJwt.clone() ⇒ [SdJwt](#SdJwt) -Deep clones the object. - -**Kind**: instance method of [SdJwt](#SdJwt) - - -### SdJwt.parse(sd_jwt) ⇒ [SdJwt](#SdJwt) -Parses an SD-JWT into its components as [`SdJwt`]. - -## Error -Returns `DeserializationError` if parsing fails. - -**Kind**: static method of [SdJwt](#SdJwt) - -| Param | Type | -| --- | --- | -| sd_jwt | string | - - - -## SdJwtCredentialValidator -A type for decoding and validating [Credential](#Credential). - -**Kind**: global class - -* [SdJwtCredentialValidator](#SdJwtCredentialValidator) - * [new SdJwtCredentialValidator(signatureVerifier)](#new_SdJwtCredentialValidator_new) - * [.validateCredential(sd_jwt, issuer, options, fail_fast)](#SdJwtCredentialValidator+validateCredential) ⇒ [DecodedJwtCredential](#DecodedJwtCredential) - * [.verifySignature(credential, trustedIssuers, options)](#SdJwtCredentialValidator+verifySignature) ⇒ [DecodedJwtCredential](#DecodedJwtCredential) - * [.validateKeyBindingJwt(sdJwt, holder, options)](#SdJwtCredentialValidator+validateKeyBindingJwt) ⇒ [KeyBindingJwtClaims](#KeyBindingJwtClaims) - - - -### new SdJwtCredentialValidator(signatureVerifier) -Creates a new `SdJwtCredentialValidator`. If a `signatureVerifier` is provided it will be used when -verifying decoded JWS signatures, otherwise the default which is only capable of handling the `EdDSA` -algorithm will be used. - - -| Param | Type | -| --- | --- | -| signatureVerifier | IJwsVerifier | - - - -### sdJwtCredentialValidator.validateCredential(sd_jwt, issuer, options, fail_fast) ⇒ [DecodedJwtCredential](#DecodedJwtCredential) -Decodes and validates a `Credential` issued as an SD-JWT. A `DecodedJwtCredential` is returned upon success. -The credential is constructed by replacing disclosures following the -[`Selective Disclosure for JWTs (SD-JWT)`](https://www.ietf.org/archive/id/draft-ietf-oauth-selective-disclosure-jwt-07.html) standard. - -The following properties are validated according to `options`: -- the issuer's signature on the JWS, -- the expiration date, -- the issuance date, -- the semantic structure. - -# Warning -* The key binding JWT is not validated. If needed, it must be validated separately using -`SdJwtValidator::validate_key_binding_jwt`. -* The lack of an error returned from this method is in of itself not enough to conclude that the credential can be -trusted. This section contains more information on additional checks that should be carried out before and after -calling this method. - -## The state of the issuer's DID Document -The caller must ensure that `issuer` represents an up-to-date DID Document. - -## Properties that are not validated - There are many properties defined in [The Verifiable Credentials Data Model](https://www.w3.org/TR/vc-data-model/) that are **not** validated, such as: -`proof`, `credentialStatus`, `type`, `credentialSchema`, `refreshService` **and more**. -These should be manually checked after validation, according to your requirements. - -# Errors -An error is returned whenever a validated condition is not satisfied. - -**Kind**: instance method of [SdJwtCredentialValidator](#SdJwtCredentialValidator) - -| Param | Type | -| --- | --- | -| sd_jwt | [SdJwt](#SdJwt) | -| issuer | [CoreDocument](#CoreDocument) \| IToCoreDocument | -| options | [JwtCredentialValidationOptions](#JwtCredentialValidationOptions) | -| fail_fast | [FailFast](#FailFast) | - - - -### sdJwtCredentialValidator.verifySignature(credential, trustedIssuers, options) ⇒ [DecodedJwtCredential](#DecodedJwtCredential) -Decode and verify the JWS signature of a `Credential` issued as an SD-JWT using the DID Document of a trusted -issuer and replaces the disclosures. - -A `DecodedJwtCredential` is returned upon success. - -# Warning -The caller must ensure that the DID Documents of the trusted issuers are up-to-date. - -## Proofs - Only the JWS signature is verified. If the `Credential` contains a `proof` property this will not be verified -by this method. - -# Errors -* If the issuer' URL cannot be parsed. -* If Signature verification fails. -* If SD decoding fails. - -**Kind**: instance method of [SdJwtCredentialValidator](#SdJwtCredentialValidator) - -| Param | Type | -| --- | --- | -| credential | [SdJwt](#SdJwt) | -| trustedIssuers | Array.<(CoreDocument\|IToCoreDocument)> | -| options | [JwsVerificationOptions](#JwsVerificationOptions) | - - - -### sdJwtCredentialValidator.validateKeyBindingJwt(sdJwt, holder, options) ⇒ [KeyBindingJwtClaims](#KeyBindingJwtClaims) -Validates a Key Binding JWT (KB-JWT) according to `https://www.ietf.org/archive/id/draft-ietf-oauth-selective-disclosure-jwt-07.html#name-key-binding-jwt`. -The Validation process includes: - * Signature validation using public key materials defined in the `holder` document. - * `typ` value in KB-JWT header. - * `sd_hash` claim value in the KB-JWT claim. - * Optional `nonce`, `aud` and issuance date validation. - -**Kind**: instance method of [SdJwtCredentialValidator](#SdJwtCredentialValidator) - -| Param | Type | -| --- | --- | -| sdJwt | [SdJwt](#SdJwt) | -| holder | [CoreDocument](#CoreDocument) \| IToCoreDocument | -| options | [KeyBindingJWTValidationOptions](#KeyBindingJWTValidationOptions) | - - - -## SdObjectDecoder -Substitutes digests in an SD-JWT object by their corresponding plaintext values provided by disclosures. - -**Kind**: global class - -* [SdObjectDecoder](#SdObjectDecoder) - * [new SdObjectDecoder()](#new_SdObjectDecoder_new) - * [.decode(object, disclosures)](#SdObjectDecoder+decode) ⇒ Record.<string, any> - - - -### new SdObjectDecoder() -Creates a new `SdObjectDecoder` with `sha-256` hasher. - - - -### sdObjectDecoder.decode(object, disclosures) ⇒ Record.<string, any> -Decodes an SD-JWT `object` containing by Substituting the digests with their corresponding -plaintext values provided by `disclosures`. - -## Notes -* Claims like `exp` or `iat` are not validated in the process of decoding. -* `_sd_alg` property will be removed if present. - -**Kind**: instance method of [SdObjectDecoder](#SdObjectDecoder) - -| Param | Type | -| --- | --- | -| object | Record.<string, any> | -| disclosures | Array.<string> | - - - -## SdObjectEncoder -Transforms a JSON object into an SD-JWT object by substituting selected values -with their corresponding disclosure digests. - -Note: digests are created using the sha-256 algorithm. - -**Kind**: global class - -* [SdObjectEncoder](#SdObjectEncoder) - * [new SdObjectEncoder(object)](#new_SdObjectEncoder_new) - * [.conceal(path, [salt])](#SdObjectEncoder+conceal) ⇒ [Disclosure](#Disclosure) - * [.addSdAlgProperty()](#SdObjectEncoder+addSdAlgProperty) - * [.encodeToString()](#SdObjectEncoder+encodeToString) ⇒ string - * [.toString()](#SdObjectEncoder+toString) ⇒ string - * [.encodeToObject()](#SdObjectEncoder+encodeToObject) ⇒ Record.<string, any> - * [.toJSON()](#SdObjectEncoder+toJSON) ⇒ any - * [.addDecoys(path, number_of_decoys)](#SdObjectEncoder+addDecoys) - - - -### new SdObjectEncoder(object) -Creates a new `SdObjectEncoder` with `sha-256` hash function. - - -| Param | Type | -| --- | --- | -| object | any | - - - -### sdObjectEncoder.conceal(path, [salt]) ⇒ [Disclosure](#Disclosure) -Substitutes a value with the digest of its disclosure. -If no salt is provided, the disclosure will be created with a random salt value. - -`path` indicates the pointer to the value that will be concealed using the syntax of -[JSON pointer](https://datatracker.ietf.org/doc/html/rfc6901). - -For the following object: - - ``` -{ - "id": "did:value", - "claim1": { - "abc": true - }, - "claim2": ["val_1", "val_2"] -} -``` - -Path "/id" conceals `"id": "did:value"` -Path "/claim1/abc" conceals `"abc": true` -Path "/claim2/0" conceals `val_1` -``` - -## Errors -* `InvalidPath` if pointer is invalid. -* `DataTypeMismatch` if existing SD format is invalid. - -**Kind**: instance method of [SdObjectEncoder](#SdObjectEncoder) - -| Param | Type | -| --- | --- | -| path | string | -| [salt] | string \| undefined | - - - -### sdObjectEncoder.addSdAlgProperty() -Adds the `_sd_alg` property to the top level of the object, with -its value set to "sha-256". - -**Kind**: instance method of [SdObjectEncoder](#SdObjectEncoder) - - -### sdObjectEncoder.encodeToString() ⇒ string -Returns the modified object as a string. - -**Kind**: instance method of [SdObjectEncoder](#SdObjectEncoder) - - -### sdObjectEncoder.toString() ⇒ string -Returns the modified object as a string. - -**Kind**: instance method of [SdObjectEncoder](#SdObjectEncoder) - - -### sdObjectEncoder.encodeToObject() ⇒ Record.<string, any> -Returns the modified object. - -**Kind**: instance method of [SdObjectEncoder](#SdObjectEncoder) - - -### sdObjectEncoder.toJSON() ⇒ any -Returns the modified object. - -**Kind**: instance method of [SdObjectEncoder](#SdObjectEncoder) - - -### sdObjectEncoder.addDecoys(path, number_of_decoys) -Adds a decoy digest to the specified path. -If path is an empty slice, decoys will be added to the top level. - -**Kind**: instance method of [SdObjectEncoder](#SdObjectEncoder) - -| Param | Type | -| --- | --- | -| path | string | -| number_of_decoys | number | - - - -## Service -A DID Document Service used to enable trusted interactions associated with a DID subject. - -**Kind**: global class - -* [Service](#Service) - * [new Service(service)](#new_Service_new) - * _instance_ - * [.id()](#Service+id) ⇒ [DIDUrl](#DIDUrl) - * [.type()](#Service+type) ⇒ Array.<string> - * [.serviceEndpoint()](#Service+serviceEndpoint) ⇒ string \| Array.<string> \| Map.<string, Array.<string>> - * [.properties()](#Service+properties) ⇒ Map.<string, any> - * [.toJSON()](#Service+toJSON) ⇒ any - * [.clone()](#Service+clone) ⇒ [Service](#Service) - * _static_ - * [.fromJSON(json)](#Service.fromJSON) ⇒ [Service](#Service) - - - -### new Service(service) - -| Param | Type | -| --- | --- | -| service | IService | - - - -### service.id() ⇒ [DIDUrl](#DIDUrl) -Returns a copy of the [Service](#Service) id. - -**Kind**: instance method of [Service](#Service) - - -### service.type() ⇒ Array.<string> -Returns a copy of the [Service](#Service) type. - -**Kind**: instance method of [Service](#Service) - - -### service.serviceEndpoint() ⇒ string \| Array.<string> \| Map.<string, Array.<string>> -Returns a copy of the [Service](#Service) endpoint. - -**Kind**: instance method of [Service](#Service) - - -### service.properties() ⇒ Map.<string, any> -Returns a copy of the custom properties on the [Service](#Service). - -**Kind**: instance method of [Service](#Service) - - -### service.toJSON() ⇒ any -Serializes this to a JSON object. - -**Kind**: instance method of [Service](#Service) - - -### service.clone() ⇒ [Service](#Service) -Deep clones the object. - -**Kind**: instance method of [Service](#Service) - - -### Service.fromJSON(json) ⇒ [Service](#Service) -Deserializes an instance from a JSON object. - -**Kind**: static method of [Service](#Service) - -| Param | Type | -| --- | --- | -| json | any | - - - -## StatusList2021 -StatusList2021 data structure as described in [W3C's VC status list 2021](https://www.w3.org/TR/2023/WD-vc-status-list-20230427/). - -**Kind**: global class - -* [StatusList2021](#StatusList2021) - * [new StatusList2021([size])](#new_StatusList2021_new) - * _instance_ - * [.clone()](#StatusList2021+clone) ⇒ [StatusList2021](#StatusList2021) - * [.len()](#StatusList2021+len) ⇒ number - * [.get(index)](#StatusList2021+get) ⇒ boolean - * [.set(index, value)](#StatusList2021+set) - * [.intoEncodedStr()](#StatusList2021+intoEncodedStr) ⇒ string - * _static_ - * [.fromEncodedStr(s)](#StatusList2021.fromEncodedStr) ⇒ [StatusList2021](#StatusList2021) - - - -### new StatusList2021([size]) -Creates a new [StatusList2021](#StatusList2021) of `size` entries. - - -| Param | Type | -| --- | --- | -| [size] | number \| undefined | - - - -### statusList2021.clone() ⇒ [StatusList2021](#StatusList2021) -Deep clones the object. - -**Kind**: instance method of [StatusList2021](#StatusList2021) - - -### statusList2021.len() ⇒ number -Returns the number of entries in this [StatusList2021](#StatusList2021). - -**Kind**: instance method of [StatusList2021](#StatusList2021) - - -### statusList2021.get(index) ⇒ boolean -Returns whether the entry at `index` is set. - -**Kind**: instance method of [StatusList2021](#StatusList2021) - -| Param | Type | -| --- | --- | -| index | number | - - - -### statusList2021.set(index, value) -Sets the value of the `index`-th entry. - -**Kind**: instance method of [StatusList2021](#StatusList2021) - -| Param | Type | -| --- | --- | -| index | number | -| value | boolean | - - - -### statusList2021.intoEncodedStr() ⇒ string -Encodes this [StatusList2021](#StatusList2021) into its compressed -base64 string representation. - -**Kind**: instance method of [StatusList2021](#StatusList2021) - - -### StatusList2021.fromEncodedStr(s) ⇒ [StatusList2021](#StatusList2021) -Attempts to decode a [StatusList2021](#StatusList2021) from a string. - -**Kind**: static method of [StatusList2021](#StatusList2021) - -| Param | Type | -| --- | --- | -| s | string | - - - -## StatusList2021Credential -A parsed [StatusList2021Credential](https://www.w3.org/TR/2023/WD-vc-status-list-20230427/#statuslist2021credential). - -**Kind**: global class - -* [StatusList2021Credential](#StatusList2021Credential) - * [new StatusList2021Credential(credential)](#new_StatusList2021Credential_new) - * _instance_ - * [.id()](#StatusList2021Credential+id) ⇒ string - * [.setCredentialStatus(credential, index, revoked_or_suspended)](#StatusList2021Credential+setCredentialStatus) ⇒ [StatusList2021Entry](#StatusList2021Entry) - * [.purpose()](#StatusList2021Credential+purpose) ⇒ [StatusPurpose](#StatusPurpose) - * [.entry(index)](#StatusList2021Credential+entry) ⇒ [CredentialStatus](#CredentialStatus) - * [.clone()](#StatusList2021Credential+clone) ⇒ [StatusList2021Credential](#StatusList2021Credential) - * [.toJSON()](#StatusList2021Credential+toJSON) ⇒ any - * _static_ - * [.fromJSON(json)](#StatusList2021Credential.fromJSON) ⇒ [StatusList2021Credential](#StatusList2021Credential) - - - -### new StatusList2021Credential(credential) -Creates a new [StatusList2021Credential](#StatusList2021Credential). - - -| Param | Type | -| --- | --- | -| credential | [Credential](#Credential) | - - - -### statusList2021Credential.id() ⇒ string -**Kind**: instance method of [StatusList2021Credential](#StatusList2021Credential) - - -### statusList2021Credential.setCredentialStatus(credential, index, revoked_or_suspended) ⇒ [StatusList2021Entry](#StatusList2021Entry) -Sets the given credential's status using the `index`-th entry of this status list. -Returns the created `credentialStatus`. - -**Kind**: instance method of [StatusList2021Credential](#StatusList2021Credential) - -| Param | Type | -| --- | --- | -| credential | [Credential](#Credential) | -| index | number | -| revoked_or_suspended | boolean | - - - -### statusList2021Credential.purpose() ⇒ [StatusPurpose](#StatusPurpose) -Returns the [StatusPurpose](#StatusPurpose) of this [StatusList2021Credential](#StatusList2021Credential). - -**Kind**: instance method of [StatusList2021Credential](#StatusList2021Credential) - - -### statusList2021Credential.entry(index) ⇒ [CredentialStatus](#CredentialStatus) -Returns the state of the `index`-th entry, if any. - -**Kind**: instance method of [StatusList2021Credential](#StatusList2021Credential) - -| Param | Type | -| --- | --- | -| index | number | - - - -### statusList2021Credential.clone() ⇒ [StatusList2021Credential](#StatusList2021Credential) -**Kind**: instance method of [StatusList2021Credential](#StatusList2021Credential) - - -### statusList2021Credential.toJSON() ⇒ any -**Kind**: instance method of [StatusList2021Credential](#StatusList2021Credential) - - -### StatusList2021Credential.fromJSON(json) ⇒ [StatusList2021Credential](#StatusList2021Credential) -**Kind**: static method of [StatusList2021Credential](#StatusList2021Credential) - -| Param | Type | -| --- | --- | -| json | any | - - - -## StatusList2021CredentialBuilder -Builder type to construct valid [StatusList2021Credential](#StatusList2021Credential) istances. - -**Kind**: global class - -* [StatusList2021CredentialBuilder](#StatusList2021CredentialBuilder) - * [new StatusList2021CredentialBuilder([status_list])](#new_StatusList2021CredentialBuilder_new) - * [.purpose(purpose)](#StatusList2021CredentialBuilder+purpose) ⇒ [StatusList2021CredentialBuilder](#StatusList2021CredentialBuilder) - * [.subjectId(id)](#StatusList2021CredentialBuilder+subjectId) ⇒ [StatusList2021CredentialBuilder](#StatusList2021CredentialBuilder) - * [.expirationDate(time)](#StatusList2021CredentialBuilder+expirationDate) ⇒ [StatusList2021CredentialBuilder](#StatusList2021CredentialBuilder) - * [.issuer(issuer)](#StatusList2021CredentialBuilder+issuer) ⇒ [StatusList2021CredentialBuilder](#StatusList2021CredentialBuilder) - * [.context(context)](#StatusList2021CredentialBuilder+context) ⇒ [StatusList2021CredentialBuilder](#StatusList2021CredentialBuilder) - * [.type(t)](#StatusList2021CredentialBuilder+type) ⇒ [StatusList2021CredentialBuilder](#StatusList2021CredentialBuilder) - * [.proof(proof)](#StatusList2021CredentialBuilder+proof) ⇒ [StatusList2021CredentialBuilder](#StatusList2021CredentialBuilder) - * [.build()](#StatusList2021CredentialBuilder+build) ⇒ [StatusList2021Credential](#StatusList2021Credential) - - - -### new StatusList2021CredentialBuilder([status_list]) -Creates a new [StatusList2021CredentialBuilder](#StatusList2021CredentialBuilder). - - -| Param | Type | -| --- | --- | -| [status_list] | [StatusList2021](#StatusList2021) \| undefined | - - - -### statusList2021CredentialBuilder.purpose(purpose) ⇒ [StatusList2021CredentialBuilder](#StatusList2021CredentialBuilder) -Sets the purpose of the [StatusList2021Credential](#StatusList2021Credential) that is being created. - -**Kind**: instance method of [StatusList2021CredentialBuilder](#StatusList2021CredentialBuilder) - -| Param | Type | -| --- | --- | -| purpose | [StatusPurpose](#StatusPurpose) | - - - -### statusList2021CredentialBuilder.subjectId(id) ⇒ [StatusList2021CredentialBuilder](#StatusList2021CredentialBuilder) -Sets `credentialSubject.id`. - -**Kind**: instance method of [StatusList2021CredentialBuilder](#StatusList2021CredentialBuilder) - -| Param | Type | -| --- | --- | -| id | string | - - - -### statusList2021CredentialBuilder.expirationDate(time) ⇒ [StatusList2021CredentialBuilder](#StatusList2021CredentialBuilder) -Sets the expiration date of the credential. - -**Kind**: instance method of [StatusList2021CredentialBuilder](#StatusList2021CredentialBuilder) - -| Param | Type | -| --- | --- | -| time | [Timestamp](#Timestamp) | - - - -### statusList2021CredentialBuilder.issuer(issuer) ⇒ [StatusList2021CredentialBuilder](#StatusList2021CredentialBuilder) -Sets the issuer of the credential. - -**Kind**: instance method of [StatusList2021CredentialBuilder](#StatusList2021CredentialBuilder) - -| Param | Type | -| --- | --- | -| issuer | string | - - - -### statusList2021CredentialBuilder.context(context) ⇒ [StatusList2021CredentialBuilder](#StatusList2021CredentialBuilder) -Sets the context of the credential. - -**Kind**: instance method of [StatusList2021CredentialBuilder](#StatusList2021CredentialBuilder) - -| Param | Type | -| --- | --- | -| context | string | - - - -### statusList2021CredentialBuilder.type(t) ⇒ [StatusList2021CredentialBuilder](#StatusList2021CredentialBuilder) -Adds a credential type. - -**Kind**: instance method of [StatusList2021CredentialBuilder](#StatusList2021CredentialBuilder) - -| Param | Type | -| --- | --- | -| t | string | - - - -### statusList2021CredentialBuilder.proof(proof) ⇒ [StatusList2021CredentialBuilder](#StatusList2021CredentialBuilder) -Adds a credential's proof. - -**Kind**: instance method of [StatusList2021CredentialBuilder](#StatusList2021CredentialBuilder) - -| Param | Type | -| --- | --- | -| proof | [Proof](#Proof) | - - - -### statusList2021CredentialBuilder.build() ⇒ [StatusList2021Credential](#StatusList2021Credential) -Attempts to build a valid [StatusList2021Credential](#StatusList2021Credential) with the previously provided data. - -**Kind**: instance method of [StatusList2021CredentialBuilder](#StatusList2021CredentialBuilder) - - -## StatusList2021Entry -[StatusList2021Entry](https://www.w3.org/TR/2023/WD-vc-status-list-20230427/#statuslist2021entry) implementation. - -**Kind**: global class - -* [StatusList2021Entry](#StatusList2021Entry) - * [new StatusList2021Entry(status_list, purpose, index, [id])](#new_StatusList2021Entry_new) - * _instance_ - * [.id()](#StatusList2021Entry+id) ⇒ string - * [.purpose()](#StatusList2021Entry+purpose) ⇒ [StatusPurpose](#StatusPurpose) - * [.index()](#StatusList2021Entry+index) ⇒ number - * [.statusListCredential()](#StatusList2021Entry+statusListCredential) ⇒ string - * [.toStatus()](#StatusList2021Entry+toStatus) ⇒ Status - * [.clone()](#StatusList2021Entry+clone) ⇒ [StatusList2021Entry](#StatusList2021Entry) - * [.toJSON()](#StatusList2021Entry+toJSON) ⇒ any - * _static_ - * [.fromJSON(json)](#StatusList2021Entry.fromJSON) ⇒ [StatusList2021Entry](#StatusList2021Entry) - - - -### new StatusList2021Entry(status_list, purpose, index, [id]) -Creates a new [StatusList2021Entry](#StatusList2021Entry). - - -| Param | Type | -| --- | --- | -| status_list | string | -| purpose | [StatusPurpose](#StatusPurpose) | -| index | number | -| [id] | string \| undefined | - - - -### statusList2021Entry.id() ⇒ string -Returns this `credentialStatus`'s `id`. - -**Kind**: instance method of [StatusList2021Entry](#StatusList2021Entry) - - -### statusList2021Entry.purpose() ⇒ [StatusPurpose](#StatusPurpose) -Returns the purpose of this entry. - -**Kind**: instance method of [StatusList2021Entry](#StatusList2021Entry) - - -### statusList2021Entry.index() ⇒ number -Returns the index of this entry. - -**Kind**: instance method of [StatusList2021Entry](#StatusList2021Entry) - - -### statusList2021Entry.statusListCredential() ⇒ string -Returns the referenced [StatusList2021Credential](#StatusList2021Credential)'s url. - -**Kind**: instance method of [StatusList2021Entry](#StatusList2021Entry) - - -### statusList2021Entry.toStatus() ⇒ Status -Downcasts [this](this) to [Status](Status) - -**Kind**: instance method of [StatusList2021Entry](#StatusList2021Entry) - - -### statusList2021Entry.clone() ⇒ [StatusList2021Entry](#StatusList2021Entry) -Deep clones the object. - -**Kind**: instance method of [StatusList2021Entry](#StatusList2021Entry) - - -### statusList2021Entry.toJSON() ⇒ any -Serializes this to a JSON object. - -**Kind**: instance method of [StatusList2021Entry](#StatusList2021Entry) - - -### StatusList2021Entry.fromJSON(json) ⇒ [StatusList2021Entry](#StatusList2021Entry) -Deserializes an instance from a JSON object. - -**Kind**: static method of [StatusList2021Entry](#StatusList2021Entry) - -| Param | Type | -| --- | --- | -| json | any | - - - -## Storage -A type wrapping a `JwkStorage` and `KeyIdStorage` that should always be used together when -working with storage backed DID documents. - -**Kind**: global class - -* [Storage](#Storage) - * [new Storage(jwkStorage, keyIdStorage)](#new_Storage_new) - * [.keyIdStorage()](#Storage+keyIdStorage) ⇒ KeyIdStorage - * [.keyStorage()](#Storage+keyStorage) ⇒ JwkStorage - - - -### new Storage(jwkStorage, keyIdStorage) -Constructs a new `Storage`. - - -| Param | Type | -| --- | --- | -| jwkStorage | JwkStorage | -| keyIdStorage | KeyIdStorage | - - - -### storage.keyIdStorage() ⇒ KeyIdStorage -Obtain the wrapped `KeyIdStorage`. - -**Kind**: instance method of [Storage](#Storage) - - -### storage.keyStorage() ⇒ JwkStorage -Obtain the wrapped `JwkStorage`. - -**Kind**: instance method of [Storage](#Storage) - - -## Timestamp -**Kind**: global class - -* [Timestamp](#Timestamp) - * [new Timestamp()](#new_Timestamp_new) - * _instance_ - * [.toRFC3339()](#Timestamp+toRFC3339) ⇒ string - * [.checkedAdd(duration)](#Timestamp+checkedAdd) ⇒ [Timestamp](#Timestamp) \| undefined - * [.checkedSub(duration)](#Timestamp+checkedSub) ⇒ [Timestamp](#Timestamp) \| undefined - * [.toJSON()](#Timestamp+toJSON) ⇒ any - * _static_ - * [.parse(input)](#Timestamp.parse) ⇒ [Timestamp](#Timestamp) - * [.nowUTC()](#Timestamp.nowUTC) ⇒ [Timestamp](#Timestamp) - * [.fromJSON(json)](#Timestamp.fromJSON) ⇒ [Timestamp](#Timestamp) - - - -### new Timestamp() -Creates a new [Timestamp](#Timestamp) with the current date and time. - - - -### timestamp.toRFC3339() ⇒ string -Returns the [Timestamp](#Timestamp) as an RFC 3339 `String`. - -**Kind**: instance method of [Timestamp](#Timestamp) - - -### timestamp.checkedAdd(duration) ⇒ [Timestamp](#Timestamp) \| undefined -Computes `self + duration` - -Returns `null` if the operation leads to a timestamp not in the valid range for [RFC 3339](https://tools.ietf.org/html/rfc3339). - -**Kind**: instance method of [Timestamp](#Timestamp) - -| Param | Type | -| --- | --- | -| duration | [Duration](#Duration) | - - - -### timestamp.checkedSub(duration) ⇒ [Timestamp](#Timestamp) \| undefined -Computes `self - duration` - -Returns `null` if the operation leads to a timestamp not in the valid range for [RFC 3339](https://tools.ietf.org/html/rfc3339). - -**Kind**: instance method of [Timestamp](#Timestamp) - -| Param | Type | -| --- | --- | -| duration | [Duration](#Duration) | - - - -### timestamp.toJSON() ⇒ any -Serializes this to a JSON object. - -**Kind**: instance method of [Timestamp](#Timestamp) - - -### Timestamp.parse(input) ⇒ [Timestamp](#Timestamp) -Parses a [Timestamp](#Timestamp) from the provided input string. - -**Kind**: static method of [Timestamp](#Timestamp) - -| Param | Type | -| --- | --- | -| input | string | - - - -### Timestamp.nowUTC() ⇒ [Timestamp](#Timestamp) -Creates a new [Timestamp](#Timestamp) with the current date and time. - -**Kind**: static method of [Timestamp](#Timestamp) - - -### Timestamp.fromJSON(json) ⇒ [Timestamp](#Timestamp) -Deserializes an instance from a JSON object. - -**Kind**: static method of [Timestamp](#Timestamp) - -| Param | Type | -| --- | --- | -| json | any | - - - -## UnknownCredential -**Kind**: global class - -* [UnknownCredential](#UnknownCredential) - * _instance_ - * [.tryIntoJwt()](#UnknownCredential+tryIntoJwt) ⇒ [Jwt](#Jwt) \| undefined - * [.tryIntoCredential()](#UnknownCredential+tryIntoCredential) ⇒ [Credential](#Credential) \| undefined - * [.tryIntoRaw()](#UnknownCredential+tryIntoRaw) ⇒ Record.<string, any> \| undefined - * [.toJSON()](#UnknownCredential+toJSON) ⇒ any - * [.clone()](#UnknownCredential+clone) ⇒ [UnknownCredential](#UnknownCredential) - * _static_ - * [.fromJSON(json)](#UnknownCredential.fromJSON) ⇒ [UnknownCredential](#UnknownCredential) - - - -### unknownCredential.tryIntoJwt() ⇒ [Jwt](#Jwt) \| undefined -Returns a [Jwt](#Jwt) if the credential is of type string, `undefined` otherwise. - -**Kind**: instance method of [UnknownCredential](#UnknownCredential) - - -### unknownCredential.tryIntoCredential() ⇒ [Credential](#Credential) \| undefined -Returns a [Credential](#Credential) if the credential is of said type, `undefined` otherwise. - -**Kind**: instance method of [UnknownCredential](#UnknownCredential) - - -### unknownCredential.tryIntoRaw() ⇒ Record.<string, any> \| undefined -Returns the contained value as an Object, if it can be converted, `undefined` otherwise. - -**Kind**: instance method of [UnknownCredential](#UnknownCredential) - - -### unknownCredential.toJSON() ⇒ any -Serializes this to a JSON object. - -**Kind**: instance method of [UnknownCredential](#UnknownCredential) - - -### unknownCredential.clone() ⇒ [UnknownCredential](#UnknownCredential) -Deep clones the object. - -**Kind**: instance method of [UnknownCredential](#UnknownCredential) - - -### UnknownCredential.fromJSON(json) ⇒ [UnknownCredential](#UnknownCredential) -Deserializes an instance from a JSON object. - -**Kind**: static method of [UnknownCredential](#UnknownCredential) - -| Param | Type | -| --- | --- | -| json | any | - - - -## VerificationMethod -A DID Document Verification Method. - -**Kind**: global class - -* [VerificationMethod](#VerificationMethod) - * [new VerificationMethod(id, controller, type_, data)](#new_VerificationMethod_new) - * _instance_ - * [.id()](#VerificationMethod+id) ⇒ [DIDUrl](#DIDUrl) - * [.setId(id)](#VerificationMethod+setId) - * [.controller()](#VerificationMethod+controller) ⇒ [CoreDID](#CoreDID) - * [.setController(did)](#VerificationMethod+setController) - * [.type()](#VerificationMethod+type) ⇒ [MethodType](#MethodType) - * [.setType(type_)](#VerificationMethod+setType) - * [.data()](#VerificationMethod+data) ⇒ [MethodData](#MethodData) - * [.setData(data)](#VerificationMethod+setData) - * [.properties()](#VerificationMethod+properties) ⇒ Map.<string, any> - * [.setPropertyUnchecked(key, value)](#VerificationMethod+setPropertyUnchecked) - * [.toJSON()](#VerificationMethod+toJSON) ⇒ any - * [.clone()](#VerificationMethod+clone) ⇒ [VerificationMethod](#VerificationMethod) - * _static_ - * [.newFromJwk(did, key, [fragment])](#VerificationMethod.newFromJwk) ⇒ [VerificationMethod](#VerificationMethod) - * [.fromJSON(json)](#VerificationMethod.fromJSON) ⇒ [VerificationMethod](#VerificationMethod) - - - -### new VerificationMethod(id, controller, type_, data) -Create a custom [VerificationMethod](#VerificationMethod). - - -| Param | Type | -| --- | --- | -| id | [DIDUrl](#DIDUrl) | -| controller | [CoreDID](#CoreDID) | -| type_ | [MethodType](#MethodType) | -| data | [MethodData](#MethodData) | - - - -### verificationMethod.id() ⇒ [DIDUrl](#DIDUrl) -Returns a copy of the [DIDUrl](#DIDUrl) of the [VerificationMethod](#VerificationMethod)'s `id`. - -**Kind**: instance method of [VerificationMethod](#VerificationMethod) - - -### verificationMethod.setId(id) -Sets the id of the [VerificationMethod](#VerificationMethod). - -**Kind**: instance method of [VerificationMethod](#VerificationMethod) - -| Param | Type | -| --- | --- | -| id | [DIDUrl](#DIDUrl) | - - - -### verificationMethod.controller() ⇒ [CoreDID](#CoreDID) -Returns a copy of the `controller` `DID` of the [VerificationMethod](#VerificationMethod). - -**Kind**: instance method of [VerificationMethod](#VerificationMethod) - - -### verificationMethod.setController(did) -Sets the `controller` `DID` of the [VerificationMethod](#VerificationMethod) object. - -**Kind**: instance method of [VerificationMethod](#VerificationMethod) - -| Param | Type | -| --- | --- | -| did | [CoreDID](#CoreDID) | - - - -### verificationMethod.type() ⇒ [MethodType](#MethodType) -Returns a copy of the [VerificationMethod](#VerificationMethod) type. - -**Kind**: instance method of [VerificationMethod](#VerificationMethod) - - -### verificationMethod.setType(type_) -Sets the [VerificationMethod](#VerificationMethod) type. - -**Kind**: instance method of [VerificationMethod](#VerificationMethod) - -| Param | Type | -| --- | --- | -| type_ | [MethodType](#MethodType) | - - - -### verificationMethod.data() ⇒ [MethodData](#MethodData) -Returns a copy of the [VerificationMethod](#VerificationMethod) public key data. - -**Kind**: instance method of [VerificationMethod](#VerificationMethod) - - -### verificationMethod.setData(data) -Sets [VerificationMethod](#VerificationMethod) public key data. - -**Kind**: instance method of [VerificationMethod](#VerificationMethod) - -| Param | Type | -| --- | --- | -| data | [MethodData](#MethodData) | - - - -### verificationMethod.properties() ⇒ Map.<string, any> -Get custom properties of the Verification Method. - -**Kind**: instance method of [VerificationMethod](#VerificationMethod) - - -### verificationMethod.setPropertyUnchecked(key, value) -Adds a custom property to the Verification Method. -If the value is set to `null`, the custom property will be removed. - -### WARNING -This method can overwrite existing properties like `id` and result -in an invalid Verification Method. - -**Kind**: instance method of [VerificationMethod](#VerificationMethod) - -| Param | Type | -| --- | --- | -| key | string | -| value | any | - - - -### verificationMethod.toJSON() ⇒ any -Serializes this to a JSON object. - -**Kind**: instance method of [VerificationMethod](#VerificationMethod) - - -### verificationMethod.clone() ⇒ [VerificationMethod](#VerificationMethod) -Deep clones the object. - -**Kind**: instance method of [VerificationMethod](#VerificationMethod) - - -### VerificationMethod.newFromJwk(did, key, [fragment]) ⇒ [VerificationMethod](#VerificationMethod) -Creates a new [VerificationMethod](#VerificationMethod) from the given `did` and [Jwk](#Jwk). If `fragment` is not given -the `kid` value of the given `key` will be used, if present, otherwise an error is returned. - -### Recommendations -The following recommendations are essentially taken from the `publicKeyJwk` description from the [DID specification](https://www.w3.org/TR/did-core/#dfn-publickeyjwk): -- It is recommended that verification methods that use `Jwks` to represent their public keys use the value of - `kid` as their fragment identifier. This is -done automatically if `None` is passed in as the fragment. -- It is recommended that [Jwk](#Jwk) kid values are set to the public key fingerprint. - -**Kind**: static method of [VerificationMethod](#VerificationMethod) - -| Param | Type | -| --- | --- | -| did | [CoreDID](#CoreDID) \| IToCoreDID | -| key | [Jwk](#Jwk) | -| [fragment] | string \| undefined | - - - -### VerificationMethod.fromJSON(json) ⇒ [VerificationMethod](#VerificationMethod) -Deserializes an instance from a JSON object. - -**Kind**: static method of [VerificationMethod](#VerificationMethod) - -| Param | Type | -| --- | --- | -| json | any | - - - -## StatusPurpose -Purpose of a [StatusList2021](#StatusList2021). - -**Kind**: global variable - - -## SubjectHolderRelationship -Declares how credential subjects must relate to the presentation holder. - -See also the [Subject-Holder Relationship](https://www.w3.org/TR/vc-data-model/#subject-holder-relationships) section of the specification. - -**Kind**: global variable - - -## AlwaysSubject -The holder must always match the subject on all credentials, regardless of their [`nonTransferable`](https://www.w3.org/TR/vc-data-model/#nontransferable-property) property. -This variant is the default. - -**Kind**: global variable - - -## SubjectOnNonTransferable -The holder must match the subject only for credentials where the [`nonTransferable`](https://www.w3.org/TR/vc-data-model/#nontransferable-property) property is `true`. - -**Kind**: global variable - - -## Any -The holder is not required to have any kind of relationship to any credential subject. - -**Kind**: global variable - - -## StateMetadataEncoding -**Kind**: global variable - - -## FailFast -Declares when validation should return if an error occurs. - -**Kind**: global variable - - -## AllErrors -Return all errors that occur during validation. - -**Kind**: global variable - - -## FirstError -Return after the first error occurs. - -**Kind**: global variable - - -## MethodRelationship -**Kind**: global variable - - -## CredentialStatus -**Kind**: global variable - - -## StatusCheck -Controls validation behaviour when checking whether or not a credential has been revoked by its -[`credentialStatus`](https://www.w3.org/TR/vc-data-model/#status). - -**Kind**: global variable - - -## Strict -Validate the status if supported, reject any unsupported -[`credentialStatus`](https://www.w3.org/TR/vc-data-model/#status) types. - -Only `RevocationBitmap2022` is currently supported. - -This is the default. - -**Kind**: global variable - - -## SkipUnsupported -Validate the status if supported, skip any unsupported -[`credentialStatus`](https://www.w3.org/TR/vc-data-model/#status) types. - -**Kind**: global variable - - -## SkipAll -Skip all status checks. - -**Kind**: global variable - - -## verifyEd25519(alg, signingInput, decodedSignature, publicKey) -Verify a JWS signature secured with the `EdDSA` algorithm and curve `Ed25519`. - -This function is useful when one is composing a `IJwsVerifier` that delegates -`EdDSA` verification with curve `Ed25519` to this function. - -# Warning - -This function does not check whether `alg = EdDSA` in the protected header. Callers are expected to assert this -prior to calling the function. - -**Kind**: global function - -| Param | Type | -| --- | --- | -| alg | JwsAlgorithm | -| signingInput | Uint8Array | -| decodedSignature | Uint8Array | -| publicKey | [Jwk](#Jwk) | - - - -## encodeB64(data) ⇒ string -Encode the given bytes in url-safe base64. - -**Kind**: global function - -| Param | Type | -| --- | --- | -| data | Uint8Array | - - - -## decodeB64(data) ⇒ Uint8Array -Decode the given url-safe base64-encoded slice into its raw bytes. - -**Kind**: global function - -| Param | Type | -| --- | --- | -| data | Uint8Array | - - - -## start() -Initializes the console error panic hook for better error messages - -**Kind**: global function diff --git a/bindings/wasm/examples/src/0_basic/2_resolve_did.ts b/bindings/wasm/examples/src/0_basic/2_resolve_did.ts index 58bc205b6a..ce8ea7c3e1 100644 --- a/bindings/wasm/examples/src/0_basic/2_resolve_did.ts +++ b/bindings/wasm/examples/src/0_basic/2_resolve_did.ts @@ -1,10 +1,23 @@ // Copyright 2020-2023 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -import { IotaDocument, IotaIdentityClient, JwkMemStore, KeyIdMemStore, Storage } from "@iota/identity-wasm/node"; +import { + CoreDocument, + DIDJwk, + IotaDocument, + IotaIdentityClient, + IToCoreDocument, + JwkMemStore, + KeyIdMemStore, + Resolver, + Storage, +} from "@iota/identity-wasm/node"; import { AliasOutput, Client, MnemonicSecretManager, Utils } from "@iota/sdk-wasm/node"; import { API_ENDPOINT, createDid } from "../util"; +const DID_JWK: string = + "did:jwk:eyJjcnYiOiJQLTI1NiIsImt0eSI6IkVDIiwieCI6ImFjYklRaXVNczNpOF91c3pFakoydHBUdFJNNEVVM3l6OTFQSDZDZEgyVjAiLCJ5IjoiX0tjeUxqOXZXTXB0bm1LdG00NkdxRHo4d2Y3NEk1TEtncmwyR3pIM25TRSJ9"; + /** Demonstrates how to resolve an existing DID in an Alias Output. */ export async function resolveIdentity() { const client = new Client({ @@ -34,4 +47,16 @@ export async function resolveIdentity() { // We can also resolve the Alias Output directly. const aliasOutput: AliasOutput = await didClient.resolveDidOutput(did); console.log("The Alias Output holds " + aliasOutput.getAmount() + " tokens"); + + // did:jwk can be resolved as well. + const handlers = new Map Promise>(); + handlers.set("jwk", didJwkHandler); + const resolver = new Resolver({ handlers }); + const did_jwk_resolved_doc = await resolver.resolve(DID_JWK); + console.log(`DID ${DID_JWK} resolves to:\n ${JSON.stringify(did_jwk_resolved_doc, null, 2)}`); } + +const didJwkHandler = async (did: string) => { + let did_jwk = DIDJwk.parse(did); + return CoreDocument.expandDIDJwk(did_jwk); +}; diff --git a/bindings/wasm/examples/src/1_advanced/8_zkp.ts b/bindings/wasm/examples/src/1_advanced/8_zkp.ts new file mode 100644 index 0000000000..55d0c82fca --- /dev/null +++ b/bindings/wasm/examples/src/1_advanced/8_zkp.ts @@ -0,0 +1,226 @@ +import { + Credential, + FailFast, + IotaDID, + IotaDocument, + IotaIdentityClient, + JptCredentialValidationOptions, + JptCredentialValidator, + JptCredentialValidatorUtils, + JptPresentationValidationOptions, + JptPresentationValidator, + JptPresentationValidatorUtils, + JwkMemStore, + JwpCredentialOptions, + JwpPresentationOptions, + KeyIdMemStore, + MethodScope, + ProofAlgorithm, + SelectiveDisclosurePresentation, + Storage, +} from "@iota/identity-wasm/node"; +import { + type Address, + AliasOutput, + Client, + MnemonicSecretManager, + SecretManager, + SecretManagerType, + Utils, +} from "@iota/sdk-wasm/node"; +import { API_ENDPOINT, ensureAddressHasFunds } from "../util"; + +/** Creates a DID Document and publishes it in a new Alias Output. + +Its functionality is equivalent to the "create DID" example +and exists for convenient calling from the other examples. */ +export async function createDid(client: Client, secretManager: SecretManagerType, storage: Storage): Promise<{ + address: Address; + document: IotaDocument; + fragment: string; +}> { + const didClient = new IotaIdentityClient(client); + const networkHrp: string = await didClient.getNetworkHrp(); + + const secretManagerInstance = new SecretManager(secretManager); + const walletAddressBech32 = (await secretManagerInstance.generateEd25519Addresses({ + accountIndex: 0, + range: { + start: 0, + end: 1, + }, + bech32Hrp: networkHrp, + }))[0]; + + console.log("Wallet address Bech32:", walletAddressBech32); + + await ensureAddressHasFunds(client, walletAddressBech32); + + const address: Address = Utils.parseBech32Address(walletAddressBech32); + + // Create a new DID document with a placeholder DID. + // The DID will be derived from the Alias Id of the Alias Output after publishing. + const document = new IotaDocument(networkHrp); + + const fragment = await document.generateMethodJwp( + storage, + ProofAlgorithm.BLS12381_SHA256, + undefined, + MethodScope.VerificationMethod(), + ); + // Construct an Alias Output containing the DID document, with the wallet address + // set as both the state controller and governor. + const aliasOutput: AliasOutput = await didClient.newDidOutput(address, document); + + // Publish the Alias Output and get the published DID document. + const published = await didClient.publishDidOutput(secretManager, aliasOutput); + + return { address, document: published, fragment }; +} +export async function zkp() { + // =========================================================================== + // Step 1: Create identity for the issuer. + // =========================================================================== + + // Create a new client to interact with the IOTA ledger. + const client = new Client({ + primaryNode: API_ENDPOINT, + localPow: true, + }); + + // Creates a new wallet and identity (see "0_create_did" example). + const issuerSecretManager: MnemonicSecretManager = { + mnemonic: Utils.generateMnemonic(), + }; + const issuerStorage: Storage = new Storage( + new JwkMemStore(), + new KeyIdMemStore(), + ); + let { document: issuerDocument, fragment: issuerFragment } = await createDid( + client, + issuerSecretManager, + issuerStorage, + ); + + // =========================================================================== + // Step 2: Issuer creates and signs a Verifiable Credential with BBS algorithm. + // =========================================================================== + + // Create a credential subject indicating the degree earned by Alice. + const subject = { + name: "Alice", + mainCourses: ["Object-oriented Programming", "Mathematics"], + degree: { + type: "BachelorDegree", + name: "Bachelor of Science and Arts", + }, + GPA: 4.0, + }; + + // Build credential using the above subject and issuer. + const credential = new Credential({ + id: "https:/example.edu/credentials/3732", + issuer: issuerDocument.id(), + type: "UniversityDegreeCredential", + credentialSubject: subject, + }); + const credentialJpt = await issuerDocument + .createCredentialJpt( + credential, + issuerStorage, + issuerFragment, + new JwpCredentialOptions(), + ); + // Validate the credential's proof using the issuer's DID Document, the credential's semantic structure, + // that the issuance date is not in the future and that the expiration date is not in the past: + const decodedJpt = JptCredentialValidator.validate( + credentialJpt, + issuerDocument, + new JptCredentialValidationOptions(), + FailFast.FirstError, + ); + + // =========================================================================== + // Step 3: Issuer sends the Verifiable Credential to the holder. + // =========================================================================== + console.log("Sending credential (as JPT) to the holder: " + credentialJpt.toString()); + + // ============================================================================================ + // Step 4: Holder resolve Issuer's DID, retrieve Issuer's document and validate the Credential + // ============================================================================================ + const identityClient = new IotaIdentityClient(client); + + // Holder resolves issuer's DID. + let issuerDid = IotaDID.parse(JptCredentialValidatorUtils.extractIssuerFromIssuedJpt(credentialJpt).toString()); + let issuerDoc = await identityClient.resolveDid(issuerDid); + + // Holder validates the credential and retrieve the JwpIssued, needed to construct the JwpPresented + let decodedCredential = JptCredentialValidator.validate( + credentialJpt, + issuerDoc, + new JptCredentialValidationOptions(), + FailFast.FirstError, + ); + + // =========================================================================== + // Step 5: Verifier sends the holder a challenge and requests a Presentation. + // + // Please be aware that when we mention "Presentation," we are not alluding to the Verifiable Presentation standard as defined by W3C (https://www.w3.org/TR/vc-data-model/#presentations). + // Instead, our reference is to a JWP Presentation (https://datatracker.ietf.org/doc/html/draft-ietf-jose-json-web-proof#name-presented-form), which differs from the W3C standard. + // =========================================================================== + + // A unique random challenge generated by the requester per presentation can mitigate replay attacks. + const challenge = "475a7984-1bb5-4c4c-a56f-822bccd46440"; + + // ========================================================================================================= + // Step 6: Holder engages in the Selective Disclosure of credential's attributes. + // ========================================================================================================= + const methodId = decodedCredential + .decodedJwp() + .getIssuerProtectedHeader() + .kid!; + const selectiveDisclosurePresentation = new SelectiveDisclosurePresentation(decodedCredential.decodedJwp()); + selectiveDisclosurePresentation.concealInSubject("mainCourses[1]"); + selectiveDisclosurePresentation.concealInSubject("degree.name"); + + // ======================================================================================================================================= + // Step 7: Holder needs Issuer's Public Key to compute the Signature Proof of Knowledge and construct the Presentation + // JPT. + // ======================================================================================================================================= + + // Construct a JPT(JWP in the Presentation form) representing the Selectively Disclosed Verifiable Credential + const presentationOptions = new JwpPresentationOptions(); + presentationOptions.nonce = challenge; + const presentationJpt = await issuerDoc + .createPresentationJpt( + selectiveDisclosurePresentation, + methodId, + presentationOptions, + ); + + // =========================================================================== + // Step 8: Holder sends a Presentation JPT to the Verifier. + // =========================================================================== + + console.log("Sending presentation (as JPT) to the verifier: " + presentationJpt.toString()); + + // =========================================================================== + // Step 9: Verifier receives the Presentation and verifies it. + // =========================================================================== + + // Verifier resolve Issuer DID + const issuerDidV = IotaDID.parse( + JptPresentationValidatorUtils.extractIssuerFromPresentedJpt(presentationJpt).toString(), + ); + const issuerDocV = await identityClient.resolveDid(issuerDidV); + + const presentationValidationOptions = new JptPresentationValidationOptions({ nonce: challenge }); + const decodedPresentedCredential = JptPresentationValidator.validate( + presentationJpt, + issuerDocV, + presentationValidationOptions, + FailFast.FirstError, + ); + + console.log("Presented credential successfully validated: " + decodedPresentedCredential.credential()); +} diff --git a/bindings/wasm/examples/src/1_advanced/9_zkp_revocation.ts b/bindings/wasm/examples/src/1_advanced/9_zkp_revocation.ts new file mode 100644 index 0000000000..e8c3d586a1 --- /dev/null +++ b/bindings/wasm/examples/src/1_advanced/9_zkp_revocation.ts @@ -0,0 +1,281 @@ +import { + Credential, + Duration, + FailFast, + IotaDID, + IotaDocument, + IotaIdentityClient, + JptCredentialValidationOptions, + JptCredentialValidator, + JptCredentialValidatorUtils, + JptPresentationValidationOptions, + JptPresentationValidator, + JptPresentationValidatorUtils, + JwkMemStore, + JwpCredentialOptions, + JwpPresentationOptions, + KeyIdMemStore, + MethodScope, + ProofAlgorithm, + RevocationBitmap, + RevocationTimeframeStatus, + SelectiveDisclosurePresentation, + Status, + StatusCheck, + Storage, + Timestamp, +} from "@iota/identity-wasm/node"; +import { + type Address, + AliasOutput, + Client, + MnemonicSecretManager, + SecretManager, + SecretManagerType, + Utils, +} from "@iota/sdk-wasm/node"; +import { API_ENDPOINT, ensureAddressHasFunds } from "../util"; + +/** Creates a DID Document and publishes it in a new Alias Output. + +Its functionality is equivalent to the "create DID" example +and exists for convenient calling from the other examples. */ +export async function createDid(client: Client, secretManager: SecretManagerType, storage: Storage): Promise<{ + address: Address; + document: IotaDocument; + fragment: string; +}> { + const didClient = new IotaIdentityClient(client); + const networkHrp: string = await didClient.getNetworkHrp(); + + const secretManagerInstance = new SecretManager(secretManager); + const walletAddressBech32 = (await secretManagerInstance.generateEd25519Addresses({ + accountIndex: 0, + range: { + start: 0, + end: 1, + }, + bech32Hrp: networkHrp, + }))[0]; + + console.log("Wallet address Bech32:", walletAddressBech32); + + await ensureAddressHasFunds(client, walletAddressBech32); + + const address: Address = Utils.parseBech32Address(walletAddressBech32); + + // Create a new DID document with a placeholder DID. + // The DID will be derived from the Alias Id of the Alias Output after publishing. + const document = new IotaDocument(networkHrp); + + const fragment = await document.generateMethodJwp( + storage, + ProofAlgorithm.BLS12381_SHA256, + undefined, + MethodScope.VerificationMethod(), + ); + const revocationBitmap = new RevocationBitmap(); + const serviceId = document.id().toUrl().join("#my-revocation-service"); + const service = revocationBitmap.toService(serviceId); + + document.insertService(service); + // Construct an Alias Output containing the DID document, with the wallet address + // set as both the state controller and governor. + const aliasOutput: AliasOutput = await didClient.newDidOutput(address, document); + + // Publish the Alias Output and get the published DID document. + const published = await didClient.publishDidOutput(secretManager, aliasOutput); + + return { address, document: published, fragment }; +} +export async function zkp_revocation() { + // Create a new client to interact with the IOTA ledger. + const client = new Client({ + primaryNode: API_ENDPOINT, + localPow: true, + }); + + // Creates a new wallet and identity (see "0_create_did" example). + const issuerSecretManager: MnemonicSecretManager = { + mnemonic: Utils.generateMnemonic(), + }; + const issuerStorage: Storage = new Storage( + new JwkMemStore(), + new KeyIdMemStore(), + ); + let { document: issuerDocument, fragment: issuerFragment } = await createDid( + client, + issuerSecretManager, + issuerStorage, + ); + const holderSecretManager: MnemonicSecretManager = { + mnemonic: Utils.generateMnemonic(), + }; + const holderStorage: Storage = new Storage( + new JwkMemStore(), + new KeyIdMemStore(), + ); + let { document: holderDocument, fragment: holderFragment } = await createDid( + client, + holderSecretManager, + holderStorage, + ); + // ========================================================================================= + // Step 1: Create a new RevocationTimeframeStatus containing the current validityTimeframe + // ======================================================================================= + + const timeframeId = issuerDocument.id().toUrl().join("#my-revocation-service"); + let revocationTimeframeStatus = new RevocationTimeframeStatus( + timeframeId.toString(), + 5, + Duration.minutes(1), + Timestamp.nowUTC(), + ); + + // Create a credential subject indicating the degree earned by Alice. + const subject = { + name: "Alice", + mainCourses: ["Object-oriented Programming", "Mathematics"], + degree: { + type: "BachelorDegree", + name: "Bachelor of Science and Arts", + }, + GPA: 4.0, + }; + + // Build credential using the above subject and issuer. + const credential = new Credential({ + id: "https:/example.edu/credentials/3732", + issuer: issuerDocument.id(), + type: "UniversityDegreeCredential", + credentialSubject: subject, + credentialStatus: revocationTimeframeStatus as any as Status, + }); + const credentialJpt = await issuerDocument + .createCredentialJpt( + credential, + issuerStorage, + issuerFragment, + new JwpCredentialOptions(), + ); + // Validate the credential's proof using the issuer's DID Document, the credential's semantic structure, + // that the issuance date is not in the future and that the expiration date is not in the past: + const decodedJpt = JptCredentialValidator.validate( + credentialJpt, + issuerDocument, + new JptCredentialValidationOptions(), + FailFast.FirstError, + ); + + console.log("Sending credential (as JPT) to the holder: " + credentialJpt.toString()); + + // Holder validates the credential and retrieve the JwpIssued, needed to construct the JwpPresented + let decodedCredential = JptCredentialValidator.validate( + credentialJpt, + issuerDocument, + new JptCredentialValidationOptions(), + FailFast.FirstError, + ); + + // =========================================================================== + // Credential's Status check + // =========================================================================== + JptCredentialValidatorUtils.checkTimeframesAndRevocationWithValidityTimeframe2024( + decodedCredential.credential(), + issuerDocument, + undefined, + StatusCheck.Strict, + ); + + // A unique random challenge generated by the requester per presentation can mitigate replay attacks. + const challenge = "475a7984-1bb5-4c4c-a56f-822bccd46440"; + + const methodId = decodedCredential + .decodedJwp() + .getIssuerProtectedHeader() + .kid!; + + const selectiveDisclosurePresentation = new SelectiveDisclosurePresentation(decodedCredential.decodedJwp()); + selectiveDisclosurePresentation.concealInSubject("mainCourses[1]"); + selectiveDisclosurePresentation.concealInSubject("degree.name"); + + // Construct a JPT(JWP in the Presentation form) representing the Selectively Disclosed Verifiable Credential + const presentationOptions = new JwpPresentationOptions(); + presentationOptions.nonce = challenge; + const presentationJpt = await issuerDocument + .createPresentationJpt( + selectiveDisclosurePresentation, + methodId, + presentationOptions, + ); + + console.log("Sending presentation (as JPT) to the verifier: " + presentationJpt.toString()); + + // =========================================================================== + // Step 2: Verifier receives the Presentation and verifies it. + // =========================================================================== + + const presentationValidationOptions = new JptPresentationValidationOptions({ nonce: challenge }); + const decodedPresentedCredential = JptPresentationValidator.validate( + presentationJpt, + issuerDocument, + presentationValidationOptions, + FailFast.FirstError, + ); + + JptPresentationValidatorUtils.checkTimeframesWithValidityTimeframe2024( + decodedPresentedCredential.credential(), + undefined, + StatusCheck.Strict, + ); + + console.log("Presented credential successfully validated: " + decodedPresentedCredential.credential()); + + // =========================================================================== + // Step 2b: Waiting for the next validityTimeframe, will result in the Credential timeframe interval NOT valid + // =========================================================================== + + try { + const now = new Date(); + const timeInTwoMinutes = new Date(now.setMinutes(now.getMinutes() + 2)); + JptPresentationValidatorUtils.checkTimeframesWithValidityTimeframe2024( + decodedPresentedCredential.credential(), + Timestamp.parse(timeInTwoMinutes.toISOString()), + StatusCheck.Strict, + ); + } catch (_) { + console.log("successfully expired!"); + } + + // =========================================================================== + // Issuer decides to Revoke Holder's Credential + // =========================================================================== + + console.log("Issuer decides to revoke the Credential"); + + const identityClient = new IotaIdentityClient(client); + + // Update the RevocationBitmap service in the issuer's DID Document. + // This revokes the credential's unique index. + issuerDocument.revokeCredentials("my-revocation-service", 5); + let aliasOutput = await identityClient.updateDidOutput(issuerDocument); + const rent = await identityClient.getRentStructure(); + aliasOutput = await client.buildAliasOutput({ + ...aliasOutput, + amount: Utils.computeStorageDeposit(aliasOutput, rent), + aliasId: aliasOutput.getAliasId(), + unlockConditions: aliasOutput.getUnlockConditions(), + }); + issuerDocument = await identityClient.publishDidOutput(issuerSecretManager, aliasOutput); + + // Holder checks if his credential has been revoked by the Issuer + try { + JptCredentialValidatorUtils.checkRevocationWithValidityTimeframe2024( + decodedCredential.credential(), + issuerDocument, + StatusCheck.Strict, + ); + } catch (_) { + console.log("Credential revoked!"); + } +} diff --git a/bindings/wasm/examples/src/main.ts b/bindings/wasm/examples/src/main.ts index 145980e649..0a074d3fd2 100644 --- a/bindings/wasm/examples/src/main.ts +++ b/bindings/wasm/examples/src/main.ts @@ -17,6 +17,8 @@ import { customResolution } from "./1_advanced/4_custom_resolution"; import { domainLinkage } from "./1_advanced/5_domain_linkage"; import { sdJwt } from "./1_advanced/6_sd_jwt"; import { statusList2021 } from "./1_advanced/7_status_list_2021"; +import { zkp } from "./1_advanced/8_zkp"; +import { zkp_revocation } from "./1_advanced/9_zkp_revocation"; async function main() { // Extract example name. @@ -58,6 +60,10 @@ async function main() { return await sdJwt(); case "7_status_list_2021": return await statusList2021(); + case "8_zkp": + return await zkp(); + case "9_zkp_revocation": + return await zkp_revocation(); default: throw "Unknown example name: '" + argument + "'"; } diff --git a/bindings/wasm/examples/src/tests/8_zkp.ts b/bindings/wasm/examples/src/tests/8_zkp.ts new file mode 100644 index 0000000000..52d5b72bc4 --- /dev/null +++ b/bindings/wasm/examples/src/tests/8_zkp.ts @@ -0,0 +1,8 @@ +import { zkp } from "../1_advanced/8_zkp"; + +// Only verifies that no uncaught exceptions are thrown, including syntax errors etc. +describe("Test node examples", function() { + it("zkp", async () => { + await zkp(); + }); +}); diff --git a/bindings/wasm/examples/src/tests/9_zkp_revocation.ts b/bindings/wasm/examples/src/tests/9_zkp_revocation.ts new file mode 100644 index 0000000000..96075765f3 --- /dev/null +++ b/bindings/wasm/examples/src/tests/9_zkp_revocation.ts @@ -0,0 +1,8 @@ +import { zkp_revocation } from "../1_advanced/9_zkp_revocation"; + +// Only verifies that no uncaught exceptions are thrown, including syntax errors etc. +describe("Test node examples", function() { + it("zkp_revocation", async () => { + await zkp_revocation(); + }); +}); diff --git a/bindings/wasm/lib/jwk_storage.ts b/bindings/wasm/lib/jwk_storage.ts index 2c1156e5ac..235abcc8ce 100644 --- a/bindings/wasm/lib/jwk_storage.ts +++ b/bindings/wasm/lib/jwk_storage.ts @@ -1,5 +1,5 @@ import * as ed from "@noble/ed25519"; -import { decodeB64, encodeB64, Jwk, JwkGenOutput, JwkStorage } from "~identity_wasm"; +import { decodeB64, encodeB64, Jwk, JwkGenOutput, JwkStorage, ProofAlgorithm, ProofUpdateCtx } from "~identity_wasm"; import { EdCurve, JwkType, JwsAlgorithm } from "./jose"; type Ed25519PrivateKey = Uint8Array; @@ -18,6 +18,10 @@ export class JwkMemStore implements JwkStorage { return "Ed25519"; } + private _get_key(keyId: string): Jwk | undefined { + return this._keys.get(keyId); + } + public async generate(keyType: string, algorithm: JwsAlgorithm): Promise { if (keyType !== JwkMemStore.ed25519KeyType()) { throw new Error(`unsupported key type ${keyType}`); @@ -126,6 +130,23 @@ function decodeJwk(jwk: Jwk): [Ed25519PrivateKey, Ed25519PublicKey] { } } +export interface JwkStorageBBSPlusExt { + // Generate a new BLS12381 key represented as a JSON Web Key. + generateBBS: (algorithm: ProofAlgorithm) => Promise; + /** Signs a chunk of data together with an optional header + * using the private key corresponding to the given `keyId` and according + * to `publicKey`'s requirements. + */ + signBBS: (keyId: string, data: Uint8Array[], publicKey: Jwk, header?: Uint8Array) => Promise; + // Updates the timeframe validity period information of a given signature. + updateBBSSignature: ( + keyId: string, + publicKey: Jwk, + signature: Uint8Array, + proofCtx: ProofUpdateCtx, + ) => Promise; +} + // Returns a random number between `min` and `max` (inclusive). // SAFETY NOTE: This is not cryptographically secure randomness and thus not suitable for production use. // It suffices for our testing implementation however and avoids an external dependency. diff --git a/bindings/wasm/lib/key_id_storage.ts b/bindings/wasm/lib/key_id_storage.ts index 1ceeb9eae2..0ed977a5c5 100644 --- a/bindings/wasm/lib/key_id_storage.ts +++ b/bindings/wasm/lib/key_id_storage.ts @@ -1,3 +1,4 @@ +import { encode as base64Encode } from "base64-arraybuffer"; import type { KeyIdStorage, MethodDigest } from "~identity_wasm"; export class KeyIdMemStore implements KeyIdStorage { @@ -48,6 +49,5 @@ export class KeyIdMemStore implements KeyIdStorage { */ function methodDigestToString(methodDigest: MethodDigest): string { let arrayBuffer = methodDigest.pack().buffer; - let buffer = Buffer.from(arrayBuffer); - return buffer.toString("base64"); + return base64Encode(arrayBuffer); } diff --git a/bindings/wasm/package-lock.json b/bindings/wasm/package-lock.json index e84427a5f0..c6afb8ec91 100644 --- a/bindings/wasm/package-lock.json +++ b/bindings/wasm/package-lock.json @@ -1,26 +1,26 @@ { "name": "@iota/identity-wasm", - "version": "1.2.0", + "version": "1.4.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@iota/identity-wasm", - "version": "1.2.0", + "version": "1.4.0", "license": "Apache-2.0", "dependencies": { "@noble/ed25519": "^1.7.3", "@types/node-fetch": "^2.6.2", + "base64-arraybuffer": "^1.0.2", "node-fetch": "^2.6.7" }, "devDependencies": { "@transmute/did-key-ed25519": "0.3.0-unstable.9", "@types/mocha": "^9.1.0", "big-integer": "^1.6.51", - "concurrently": "^7.6.0", "copy-webpack-plugin": "^7.0.0", - "cypress": "^10.11.0", - "cypress-parallel": "^0.9.1", + "cypress": "^13.12.0", + "cypress-parallel": "^0.14.0", "dprint": "^0.33.0", "fs-extra": "^10.1.0", "jsdoc-to-markdown": "^7.1.1", @@ -29,6 +29,8 @@ "ts-node": "^10.9.1", "tsconfig-paths": "^4.1.0", "txm": "^8.1.0", + "typedoc": "^0.24.6", + "typedoc-plugin-markdown": "^3.14.0", "typescript": "^4.7.2", "wasm-opt": "^1.3.0" }, @@ -56,7 +58,6 @@ "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", "integrity": "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==", "dev": true, - "optional": true, "engines": { "node": ">=0.1.90" } @@ -74,9 +75,9 @@ } }, "node_modules/@cypress/request": { - "version": "2.88.10", - "resolved": "https://registry.npmjs.org/@cypress/request/-/request-2.88.10.tgz", - "integrity": "sha512-Zp7F+R93N0yZyG34GutyTNr+okam7s/Fzc1+i3kcqOP8vk6OuajuE9qZJ6Rs+10/1JFtXFYMdyarnU1rZuJesg==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@cypress/request/-/request-3.0.1.tgz", + "integrity": "sha512-TWivJlJi8ZDx2wGOw1dbLuHJKUYX7bWySw377nlnGOW3hP9/MUKIsEdXT/YngWxVdgNCHRBmFlBipE+5/2ZZlQ==", "dev": true, "dependencies": { "aws-sign2": "~0.7.0", @@ -92,9 +93,9 @@ "json-stringify-safe": "~5.0.1", "mime-types": "~2.1.19", "performance-now": "^2.1.0", - "qs": "~6.5.2", + "qs": "6.10.4", "safe-buffer": "^5.1.2", - "tough-cookie": "~2.5.0", + "tough-cookie": "^4.1.3", "tunnel-agent": "^0.6.0", "uuid": "^8.3.2" }, @@ -614,9 +615,9 @@ "dev": true }, "node_modules/@types/sizzle": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/@types/sizzle/-/sizzle-2.3.3.tgz", - "integrity": "sha512-JYM8x9EGF163bEyhdJBpR2QX1R5naCJHC8ucJylJ3w9/CVBaskdQ8WqBf8MmQrd1kRvp/a4TS8HJ+bxzR7ZJYQ==", + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/@types/sizzle/-/sizzle-2.3.8.tgz", + "integrity": "sha512-0vWLNK2D5MT9dg0iOo8GlKguPAU02QjmZitPEsXRuJXU/OGIOt9vT9Fc26wtYuavLxtO45v9PGleoL9Z0k1LHg==", "dev": true }, "node_modules/@types/unist": { @@ -626,9 +627,9 @@ "dev": true }, "node_modules/@types/yauzl": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.0.tgz", - "integrity": "sha512-Cn6WYCm0tXv8p6k+A8PvbDG763EDpBoTzHdA+Q/MF6H3sapGjCm9NzoaJncJS9tUKSuCoDs9XHxYYsQDgxR6kw==", + "version": "2.10.3", + "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.3.tgz", + "integrity": "sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==", "dev": true, "optional": true, "dependencies": { @@ -951,6 +952,13 @@ "node": ">=8" } }, + "node_modules/ansi-sequence-parser": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ansi-sequence-parser/-/ansi-sequence-parser-1.1.1.tgz", + "integrity": "sha512-vJXt3yiaUL4UU546s3rPXlsry/RnM730G1+HkpKE012AN0sx1eOrxSu95oKDIonskeLTijMgqWZ3uDEe3NFvyg==", + "dev": true, + "license": "MIT" + }, "node_modules/ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", @@ -1095,9 +1103,9 @@ } }, "node_modules/aws4": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.11.0.tgz", - "integrity": "sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA==", + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.13.0.tgz", + "integrity": "sha512-3AungXC4I8kKsS9PuS4JH2nc+0bVY/mjgrephHTIi8fpEeGsTHBUJeosp0Wc1myYMElmD0B3Oc4XL/HVJ4PV2g==", "dev": true }, "node_modules/bail": { @@ -1116,6 +1124,14 @@ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "dev": true }, + "node_modules/base64-arraybuffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-1.0.2.tgz", + "integrity": "sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ==", + "engines": { + "node": ">= 0.6.0" + } + }, "node_modules/base64-js": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", @@ -1304,9 +1320,9 @@ } }, "node_modules/cachedir": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/cachedir/-/cachedir-2.3.0.tgz", - "integrity": "sha512-A+Fezp4zxnit6FanDmv9EqXNAi3vt9DWp51/71UEhXukb7QUuvtv9344h91dyAxuTLoSYJFU299qzR3tzwPAhw==", + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/cachedir/-/cachedir-2.4.0.tgz", + "integrity": "sha512-9EtFOZR8g22CL7BWjJ9BUx1+A/djkofnyW3aOXZORNW2kxoUpx2h+uN2cOqwPmFhnpVmxg+KW2OjOSgChTEvsQ==", "dev": true, "engines": { "node": ">=6" @@ -1316,7 +1332,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", - "peer": true, "dependencies": { "function-bind": "^1.1.1", "get-intrinsic": "^1.0.2" @@ -1472,10 +1487,19 @@ } }, "node_modules/ci-info": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.4.0.tgz", - "integrity": "sha512-t5QdPT5jq3o262DOQ8zA6E1tlH2upmUc4Hlvrbx1pGYJuiiHl7O7rvVNI+l8HTVhd/q3Qc9vqimkNk5yiXsAug==", - "dev": true + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", + "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "engines": { + "node": ">=8" + } }, "node_modules/class-transformer": { "version": "0.5.1", @@ -1592,20 +1616,11 @@ "dev": true }, "node_modules/colorette": { - "version": "2.0.19", - "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.19.tgz", - "integrity": "sha512-3tlv/dIP7FWvj3BsbHrGLJ6l/oKh1O3TcgBqMn+yyCagOxc23fyzDS6HypQbgxWbkpDnf52p1LuR4eWDQ/K9WQ==", + "version": "2.0.20", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", + "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", "dev": true }, - "node_modules/colors": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz", - "integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==", - "dev": true, - "engines": { - "node": ">=0.1.90" - } - }, "node_modules/combined-stream": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", @@ -1706,9 +1721,9 @@ } }, "node_modules/commander": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-5.1.0.tgz", - "integrity": "sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==", + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-6.2.1.tgz", + "integrity": "sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA==", "dev": true, "engines": { "node": ">= 6" @@ -1738,33 +1753,6 @@ "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", "dev": true }, - "node_modules/concurrently": { - "version": "7.6.0", - "resolved": "https://registry.npmjs.org/concurrently/-/concurrently-7.6.0.tgz", - "integrity": "sha512-BKtRgvcJGeZ4XttiDiNcFiRlxoAeZOseqUvyYRUp/Vtd+9p1ULmeoSqGsDA+2ivdeDFpqrJvGvmI+StKfKl5hw==", - "dev": true, - "dependencies": { - "chalk": "^4.1.0", - "date-fns": "^2.29.1", - "lodash": "^4.17.21", - "rxjs": "^7.0.0", - "shell-quote": "^1.7.3", - "spawn-command": "^0.0.2-1", - "supports-color": "^8.1.0", - "tree-kill": "^1.2.2", - "yargs": "^17.3.1" - }, - "bin": { - "conc": "dist/bin/concurrently.js", - "concurrently": "dist/bin/concurrently.js" - }, - "engines": { - "node": "^12.20.0 || ^14.13.0 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/open-cli-tools/concurrently?sponsor=1" - } - }, "node_modules/config-master": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/config-master/-/config-master-3.1.0.tgz", @@ -1836,30 +1824,29 @@ } }, "node_modules/cypress": { - "version": "10.11.0", - "resolved": "https://registry.npmjs.org/cypress/-/cypress-10.11.0.tgz", - "integrity": "sha512-lsaE7dprw5DoXM00skni6W5ElVVLGAdRUUdZjX2dYsGjbY/QnpzWZ95Zom1mkGg0hAaO/QVTZoFVS7Jgr/GUPA==", + "version": "13.12.0", + "resolved": "https://registry.npmjs.org/cypress/-/cypress-13.12.0.tgz", + "integrity": "sha512-udzS2JilmI9ApO/UuqurEwOvThclin5ntz7K0BtnHBs+tg2Bl9QShLISXpSEMDv/u8b6mqdoAdyKeZiSqKWL8g==", "dev": true, "hasInstallScript": true, "dependencies": { - "@cypress/request": "^2.88.10", + "@cypress/request": "^3.0.0", "@cypress/xvfb": "^1.2.4", - "@types/node": "^14.14.31", "@types/sinonjs__fake-timers": "8.1.1", "@types/sizzle": "^2.3.2", "arch": "^2.2.0", "blob-util": "^2.0.2", "bluebird": "^3.7.2", - "buffer": "^5.6.0", + "buffer": "^5.7.1", "cachedir": "^2.3.0", "chalk": "^4.1.0", "check-more-types": "^2.24.0", "cli-cursor": "^3.1.0", "cli-table3": "~0.6.1", - "commander": "^5.1.0", + "commander": "^6.2.1", "common-tags": "^1.8.0", "dayjs": "^1.10.4", - "debug": "^4.3.2", + "debug": "^4.3.4", "enquirer": "^2.3.6", "eventemitter2": "6.4.7", "execa": "4.1.0", @@ -1868,18 +1855,19 @@ "figures": "^3.2.0", "fs-extra": "^9.1.0", "getos": "^3.2.1", - "is-ci": "^3.0.0", + "is-ci": "^3.0.1", "is-installed-globally": "~0.4.0", "lazy-ass": "^1.6.0", "listr2": "^3.8.3", "lodash": "^4.17.21", "log-symbols": "^4.0.0", - "minimist": "^1.2.6", + "minimist": "^1.2.8", "ospath": "^1.2.2", "pretty-bytes": "^5.6.0", + "process": "^0.11.10", "proxy-from-env": "1.0.0", "request-progress": "^3.0.0", - "semver": "^7.3.2", + "semver": "^7.5.3", "supports-color": "^8.1.1", "tmp": "~0.2.1", "untildify": "^4.0.0", @@ -1889,18 +1877,18 @@ "cypress": "bin/cypress" }, "engines": { - "node": ">=12.0.0" + "node": "^16.0.0 || ^18.0.0 || >=20.0.0" } }, "node_modules/cypress-multi-reporters": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/cypress-multi-reporters/-/cypress-multi-reporters-1.6.1.tgz", - "integrity": "sha512-FPeC0xWF1N6Myrwc2m7KC0xxlrtG8+x4hlsPFBDRWP8u/veR2x90pGaH3BuJfweV7xoQ4Zo85Qjhu3fgZGrBQQ==", + "version": "1.6.4", + "resolved": "https://registry.npmjs.org/cypress-multi-reporters/-/cypress-multi-reporters-1.6.4.tgz", + "integrity": "sha512-3xU2t6pZjZy/ORHaCvci5OT1DAboS4UuMMM8NBAizeb2C9qmHt+cgAjXgurazkwkPRdO7ccK39M5ZaPCju0r6A==", "dev": true, "peer": true, "dependencies": { - "debug": "^4.1.1", - "lodash": "^4.17.15" + "debug": "^4.3.4", + "lodash": "^4.17.21" }, "engines": { "node": ">=6.0.0" @@ -1910,19 +1898,19 @@ } }, "node_modules/cypress-parallel": { - "version": "0.9.1", - "resolved": "https://registry.npmjs.org/cypress-parallel/-/cypress-parallel-0.9.1.tgz", - "integrity": "sha512-7VSfFr8HEEN6zkgo6SkG7pPoHK7VakFhEH1jbM4+Ire/I+O2jNzyd1bRUA+O3V2DIMow64ECDJKf13YHBon+BQ==", + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/cypress-parallel/-/cypress-parallel-0.14.0.tgz", + "integrity": "sha512-Lsh28G70vxjL0cjR820BdaVQHnGc17Vvb+tYmjbRPmfC+XEzwvUzhcaD0E1zCztBSYhw+b1/1JLmW4Y0qE/EDA==", "dev": true, "dependencies": { + "@colors/colors": "^1.5.0", "cli-table3": "^0.6.0", - "colors": "^1.4.0", "cross-spawn": "^7.0.3", "fs-extra": "^10.0.0", "glob-escape": "^0.0.2", "is-npm": "^5.0.0", "lodash.camelcase": "^4.3.0", - "mocha": "^8.2.1", + "mocha": "~9.2.0", "yargs": "15.3.1" }, "bin": { @@ -1932,15 +1920,6 @@ "cypress-multi-reporters": "^1.5.0" } }, - "node_modules/cypress-parallel/node_modules/ansi-colors": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", - "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", - "dev": true, - "engines": { - "node": ">=6" - } - }, "node_modules/cypress-parallel/node_modules/camelcase": { "version": "5.3.1", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", @@ -1950,42 +1929,15 @@ "node": ">=6" } }, - "node_modules/cypress-parallel/node_modules/chokidar": { - "version": "3.5.1", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.1.tgz", - "integrity": "sha512-9+s+Od+W0VJJzawDma/gvBNQqkTiqYTWLuZoyAsivsI4AaWTCzHG06/TMjsf1cYe9Cb97UCEhjz7HvnPk2p/tw==", - "dev": true, - "dependencies": { - "anymatch": "~3.1.1", - "braces": "~3.0.2", - "glob-parent": "~5.1.0", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.5.0" - }, - "engines": { - "node": ">= 8.10.0" - }, - "optionalDependencies": { - "fsevents": "~2.3.1" - } - }, - "node_modules/cypress-parallel/node_modules/debug": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", - "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "node_modules/cypress-parallel/node_modules/cliui": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", + "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", "dev": true, "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^6.2.0" } }, "node_modules/cypress-parallel/node_modules/decamelize": { @@ -1997,48 +1949,17 @@ "node": ">=0.10.0" } }, - "node_modules/cypress-parallel/node_modules/escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/cypress-parallel/node_modules/glob": { - "version": "7.1.6", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", - "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", + "node_modules/cypress-parallel/node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", "dev": true, "dependencies": { - "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" + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" }, "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/cypress-parallel/node_modules/js-yaml": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.0.0.tgz", - "integrity": "sha512-pqon0s+4ScYUvX30wxQi3PogGFAlUyH0awepWvwkj4jD4v+ova3RiYw8bmA6x2rDrEaj8i/oWKoRxpVNW+Re8Q==", - "dev": true, - "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" + "node": ">=8" } }, "node_modules/cypress-parallel/node_modules/locate-path": { @@ -2053,110 +1974,6 @@ "node": ">=8" } }, - "node_modules/cypress-parallel/node_modules/log-symbols": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.0.0.tgz", - "integrity": "sha512-FN8JBzLx6CzeMrB0tg6pqlGU1wCrXW+ZXGH481kfsBqer0hToTIiHdjH4Mq8xJUbvATujKCvaREGWpGUionraA==", - "dev": true, - "dependencies": { - "chalk": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/cypress-parallel/node_modules/minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", - "dev": true, - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/cypress-parallel/node_modules/mocha": { - "version": "8.4.0", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-8.4.0.tgz", - "integrity": "sha512-hJaO0mwDXmZS4ghXsvPVriOhsxQ7ofcpQdm8dE+jISUOKopitvnXFQmpRR7jd2K6VBG6E26gU3IAbXXGIbu4sQ==", - "dev": true, - "dependencies": { - "@ungap/promise-all-settled": "1.1.2", - "ansi-colors": "4.1.1", - "browser-stdout": "1.3.1", - "chokidar": "3.5.1", - "debug": "4.3.1", - "diff": "5.0.0", - "escape-string-regexp": "4.0.0", - "find-up": "5.0.0", - "glob": "7.1.6", - "growl": "1.10.5", - "he": "1.2.0", - "js-yaml": "4.0.0", - "log-symbols": "4.0.0", - "minimatch": "3.0.4", - "ms": "2.1.3", - "nanoid": "3.1.20", - "serialize-javascript": "5.0.1", - "strip-json-comments": "3.1.1", - "supports-color": "8.1.1", - "which": "2.0.2", - "wide-align": "1.1.3", - "workerpool": "6.1.0", - "yargs": "16.2.0", - "yargs-parser": "20.2.4", - "yargs-unparser": "2.0.0" - }, - "bin": { - "_mocha": "bin/_mocha", - "mocha": "bin/mocha" - }, - "engines": { - "node": ">= 10.12.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/mochajs" - } - }, - "node_modules/cypress-parallel/node_modules/mocha/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true - }, - "node_modules/cypress-parallel/node_modules/mocha/node_modules/yargs": { - "version": "16.2.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", - "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", - "dev": true, - "dependencies": { - "cliui": "^7.0.2", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.0", - "y18n": "^5.0.5", - "yargs-parser": "^20.2.2" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/cypress-parallel/node_modules/nanoid": { - "version": "3.1.20", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.20.tgz", - "integrity": "sha512-a1cQNyczgKbLX9jwbS/+d7W8fX/RfgYR7lVWwWOGIPNgK2m0MWvrGF6/m4kk6U3QcFMnZf3RIhL0v2Jgh/0Uxw==", - "dev": true, - "bin": { - "nanoid": "bin/nanoid.cjs" - }, - "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" - } - }, "node_modules/cypress-parallel/node_modules/p-limit": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", @@ -2184,24 +2001,6 @@ "node": ">=8" } }, - "node_modules/cypress-parallel/node_modules/readdirp": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.5.0.tgz", - "integrity": "sha512-cMhu7c/8rdhkHXWsY+osBhfSy0JikwpHK/5+imo+LpeasTF8ouErHrlYkwT0++njiyuDvc7OFY5T3ukvZ8qmFQ==", - "dev": true, - "dependencies": { - "picomatch": "^2.2.1" - }, - "engines": { - "node": ">=8.10.0" - } - }, - "node_modules/cypress-parallel/node_modules/workerpool": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.1.0.tgz", - "integrity": "sha512-toV7q9rWNYha963Pl/qyeZ6wG+3nnsyvolaNUS8+R5Wtw6qJPTxIlOP1ZSvcGhEJw+l3HMMmtiNo9Gl61G4GVg==", - "dev": true - }, "node_modules/cypress-parallel/node_modules/wrap-ansi": { "version": "6.2.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", @@ -2216,6 +2015,12 @@ "node": ">=8" } }, + "node_modules/cypress-parallel/node_modules/y18n": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", + "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==", + "dev": true + }, "node_modules/cypress-parallel/node_modules/yargs": { "version": "15.3.1", "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.3.1.tgz", @@ -2238,37 +2043,7 @@ "node": ">=8" } }, - "node_modules/cypress-parallel/node_modules/yargs/node_modules/cliui": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", - "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", - "dev": true, - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^6.2.0" - } - }, - "node_modules/cypress-parallel/node_modules/yargs/node_modules/find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dev": true, - "dependencies": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/cypress-parallel/node_modules/yargs/node_modules/y18n": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", - "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==", - "dev": true - }, - "node_modules/cypress-parallel/node_modules/yargs/node_modules/yargs-parser": { + "node_modules/cypress-parallel/node_modules/yargs-parser": { "version": "18.1.3", "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", @@ -2281,12 +2056,6 @@ "node": ">=6" } }, - "node_modules/cypress/node_modules/@types/node": { - "version": "14.18.29", - "resolved": "https://registry.npmjs.org/@types/node/-/node-14.18.29.tgz", - "integrity": "sha512-LhF+9fbIX4iPzhsRLpK5H7iPdvW8L4IwGciXQIOEcuF62+9nw/VQVsOViAOOGxY3OlOKGLFv0sWwJXdwQeTn6A==", - "dev": true - }, "node_modules/cypress/node_modules/fs-extra": { "version": "9.1.0", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", @@ -2323,23 +2092,10 @@ "node": ">= 6" } }, - "node_modules/date-fns": { - "version": "2.29.3", - "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.29.3.tgz", - "integrity": "sha512-dDCnyH2WnnKusqvZZ6+jA1O51Ibt8ZMRNkDZdyAyK4YfbDwa/cEmuztzG5pk6hqlp9aSBPYcjOlktquahGwGeA==", - "dev": true, - "engines": { - "node": ">=0.11" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/date-fns" - } - }, "node_modules/dayjs": { - "version": "1.11.5", - "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.5.tgz", - "integrity": "sha512-CAdX5Q3YW3Gclyo5Vpqkgpj8fSdLQcRuzfX6mC6Phy0nfJ0eGYOeS7m4mt2plDWLAtA4TqTakvbboHvUxfe4iA==", + "version": "1.11.11", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.11.tgz", + "integrity": "sha512-okzr3f11N6WuqYtZSvm+F776mB41wRZMhKP+hc34YdW+KmtYYK9iqvHSwo2k9FEH3fhGXvOPV6yz2IcSrfRUDg==", "dev": true }, "node_modules/debug": { @@ -2529,12 +2285,13 @@ } }, "node_modules/enquirer": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz", - "integrity": "sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==", + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.4.1.tgz", + "integrity": "sha512-rRqJg/6gd538VHvR3PSrdRBb/1Vy2YfzHqzvbhGIQpDRKIa4FgV/54b5Q1xYSxOOwKvjXweS26E0Q+nAMwp2pQ==", "dev": true, "dependencies": { - "ansi-colors": "^4.1.1" + "ansi-colors": "^4.1.1", + "strip-ansi": "^6.0.1" }, "engines": { "node": ">=8.6" @@ -2972,8 +2729,7 @@ "node_modules/function-bind": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", - "peer": true + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" }, "node_modules/get-caller-file": { "version": "2.0.5", @@ -2988,7 +2744,6 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.1.tgz", "integrity": "sha512-2DcsyfABl+gVHEfCOaTrWgyt+tb6MSEGmKq+kI5HwLbIYgjgmMcV8KQ41uaKz1xxUcn9tJtgFbQUEVcEbd0FYw==", - "peer": true, "dependencies": { "function-bind": "^1.1.1", "has": "^1.0.3", @@ -3081,9 +2836,9 @@ "peer": true }, "node_modules/global-dirs": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/global-dirs/-/global-dirs-3.0.0.tgz", - "integrity": "sha512-v8ho2DS5RiCjftj1nD9NmnfaOzTdud7RRnVd9kFNOjqZbISlx5DQ+OrTkywgd0dIt7oFCvKetZSHoHcP3sDdiA==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/global-dirs/-/global-dirs-3.0.1.tgz", + "integrity": "sha512-NBcGGFbBA9s1VzD41QXDG+3++t9Mn5t1FpLdhESY6oKY4gYTFpX4wO3sqGUa0Srjtbfj3szX0RnemmrVRUdULA==", "dev": true, "dependencies": { "ini": "2.0.0" @@ -3155,7 +2910,6 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", - "peer": true, "dependencies": { "function-bind": "^1.1.1" }, @@ -3176,7 +2930,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz", "integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==", - "peer": true, "engines": { "node": ">= 0.4" }, @@ -3188,7 +2941,6 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", - "peer": true, "engines": { "node": ">= 0.4" }, @@ -3664,6 +3416,13 @@ "node": ">=6" } }, + "node_modules/jsonc-parser": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.3.1.tgz", + "integrity": "sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ==", + "dev": true, + "license": "MIT" + }, "node_modules/jsonfile": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", @@ -3996,6 +3755,13 @@ "node": ">=10" } }, + "node_modules/lunr": { + "version": "2.3.9", + "resolved": "https://registry.npmjs.org/lunr/-/lunr-2.3.9.tgz", + "integrity": "sha512-zTU3DaZaF3Rt9rhN3uBMGQD3dD2/vFQqnvZCDv4dl5iOzq2IZQqTxu90r4E5J+nP70J3ilqVCrbho2eWaeW8Ow==", + "dev": true, + "license": "MIT" + }, "node_modules/make-error": { "version": "1.3.6", "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", @@ -4029,10 +3795,11 @@ } }, "node_modules/marked": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/marked/-/marked-4.1.0.tgz", - "integrity": "sha512-+Z6KDjSPa6/723PQYyc1axYZpYYpDnECDaU6hkaf5gqBieBkMKYReL5hteF2QizhlMbgbo8umXl/clZ67+GlsA==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/marked/-/marked-4.3.0.tgz", + "integrity": "sha512-PRsaiG84bK+AMvxziE/lCFss8juXjNaWzVbN5tXAm4XjeaS9NAHhop+PjQxz2A9h8Q4M/xGmzP8vqNwy6JeK0A==", "dev": true, + "license": "MIT", "bin": { "marked": "bin/marked.js" }, @@ -4592,10 +4359,13 @@ } }, "node_modules/minimist": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", - "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==", - "dev": true + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } }, "node_modules/minipass": { "version": "3.3.4", @@ -4894,7 +4664,6 @@ "version": "1.12.3", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz", "integrity": "sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==", - "peer": true, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -5080,6 +4849,15 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/process": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", + "dev": true, + "engines": { + "node": ">= 0.6.0" + } + }, "node_modules/proxy-from-env": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.0.0.tgz", @@ -5112,14 +4890,26 @@ } }, "node_modules/qs": { - "version": "6.5.3", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.3.tgz", - "integrity": "sha512-qxXIEh4pCGfHICj1mAJQ2/2XVZkjCDTcEgfoSQxc/fYivUZxTkk7L3bDBJSoNrEzXI17oUO5Dp07ktqE5KzczA==", + "version": "6.10.4", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.10.4.tgz", + "integrity": "sha512-OQiU+C+Ds5qiH91qh/mg0w+8nwQuLjM4F4M/PbmhDOoYehPh+Fb0bDjtR1sOvy7YKxvj28Y/M0PhP5uVX0kB+g==", "dev": true, + "dependencies": { + "side-channel": "^1.0.4" + }, "engines": { "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/querystringify": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", + "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==", + "dev": true + }, "node_modules/queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", @@ -5310,6 +5100,12 @@ "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", "dev": true }, + "node_modules/requires-port": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", + "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", + "dev": true + }, "node_modules/requizzle": { "version": "0.2.3", "resolved": "https://registry.npmjs.org/requizzle/-/requizzle-0.2.3.tgz", @@ -5343,26 +5139,11 @@ } }, "node_modules/rfdc": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.3.0.tgz", - "integrity": "sha512-V2hovdzFbOi77/WajaSMXk2OLm+xNIeQdMMuB7icj7bk6zi2F8GGAxigcnDFpJHbNyNcgyJDiP+8nOrY5cZGrA==", + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz", + "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==", "dev": true }, - "node_modules/rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "dev": true, - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/run-parallel": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", @@ -5519,17 +5300,23 @@ "node": ">=8" } }, - "node_modules/shell-quote": { - "version": "1.7.3", - "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.7.3.tgz", - "integrity": "sha512-Vpfqwm4EnqGdlsBFNmHhxhElJYrdfcxPThu+ryKS5J8L/fhAwLazFZtq+S+TWZ9ANj2piSQLGj6NQg+lKPmxrw==", - "dev": true + "node_modules/shiki": { + "version": "0.14.7", + "resolved": "https://registry.npmjs.org/shiki/-/shiki-0.14.7.tgz", + "integrity": "sha512-dNPAPrxSc87ua2sKJ3H5dQ/6ZaY8RNnaAqK+t0eG7p0Soi2ydiqbGOTaZCqaYvA/uZYfS1LJnemt3Q+mSfcPCg==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-sequence-parser": "^1.1.0", + "jsonc-parser": "^3.2.0", + "vscode-oniguruma": "^1.7.0", + "vscode-textmate": "^8.0.0" + } }, "node_modules/side-channel": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", - "peer": true, "dependencies": { "call-bind": "^1.0.0", "get-intrinsic": "^1.0.2", @@ -5618,16 +5405,10 @@ "source-map": "^0.6.0" } }, - "node_modules/spawn-command": { - "version": "0.0.2-1", - "resolved": "https://registry.npmjs.org/spawn-command/-/spawn-command-0.0.2-1.tgz", - "integrity": "sha512-n98l9E2RMSJ9ON1AKisHzz7V42VDiBQGY6PB1BwRglz99wpVsSuGzQ+jOi6lFXBGVTCrRpltvjm+/XA+tpeJrg==", - "dev": true - }, "node_modules/sshpk": { - "version": "1.17.0", - "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.17.0.tgz", - "integrity": "sha512-/9HIEs1ZXGhSPE8X6Ccm7Nam1z8KcoCqPdI7ecm1N33EzAetWahvQWVqLZtaZQ+IDKX4IyA2o0gBzqIMkAagHQ==", + "version": "1.18.0", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.18.0.tgz", + "integrity": "sha512-2p2KJZTSqQ/I3+HX42EpYOa2l3f8Erv8MWKsy2I9uf4wA7yFIkXRffYdsx86y6z4vHtV8u7g+pPlr8/4ouAxsQ==", "dev": true, "dependencies": { "asn1": "~0.2.3", @@ -5935,10 +5716,13 @@ "peer": true }, "node_modules/throttleit": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/throttleit/-/throttleit-1.0.0.tgz", - "integrity": "sha512-rkTVqu6IjfQ/6+uNuuc3sZek4CEYxTJom3IktzgdSxcZqdARuebbA/f4QmAxMQIxqq9ZLEUkSYqvuk1I6VKq4g==", - "dev": true + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/throttleit/-/throttleit-1.0.1.tgz", + "integrity": "sha512-vDZpf9Chs9mAdfY046mcPt8fg5QSZr37hEH4TXYBnDF+izxgrbRGUAAaBvIk/fJm9aOFCGFd1EsNg5AZCbnQCQ==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } }, "node_modules/through": { "version": "2.3.8", @@ -5947,15 +5731,12 @@ "dev": true }, "node_modules/tmp": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.1.tgz", - "integrity": "sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ==", + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.3.tgz", + "integrity": "sha512-nZD7m9iCPC5g0pYmcaxogYKggSfLsdxl8of3Q/oIbqCqLLIO9IAF0GWjX1z9NZRHPiXv8Wex4yDCaZsgEw0Y8w==", "dev": true, - "dependencies": { - "rimraf": "^3.0.0" - }, "engines": { - "node": ">=8.17.0" + "node": ">=14.14" } }, "node_modules/to-regex-range": { @@ -5971,16 +5752,27 @@ } }, "node_modules/tough-cookie": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", - "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.4.tgz", + "integrity": "sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==", "dev": true, "dependencies": { - "psl": "^1.1.28", - "punycode": "^2.1.1" + "psl": "^1.1.33", + "punycode": "^2.1.1", + "universalify": "^0.2.0", + "url-parse": "^1.5.3" }, "engines": { - "node": ">=0.8" + "node": ">=6" + } + }, + "node_modules/tough-cookie/node_modules/universalify": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz", + "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==", + "dev": true, + "engines": { + "node": ">= 4.0.0" } }, "node_modules/tr46": { @@ -5988,15 +5780,6 @@ "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" }, - "node_modules/tree-kill": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", - "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==", - "dev": true, - "bin": { - "tree-kill": "cli.js" - } - }, "node_modules/trough": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/trough/-/trough-2.1.0.tgz", @@ -6240,6 +6023,67 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/typedoc": { + "version": "0.24.8", + "resolved": "https://registry.npmjs.org/typedoc/-/typedoc-0.24.8.tgz", + "integrity": "sha512-ahJ6Cpcvxwaxfu4KtjA8qZNqS43wYt6JL27wYiIgl1vd38WW/KWX11YuAeZhuz9v+ttrutSsgK+XO1CjL1kA3w==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "lunr": "^2.3.9", + "marked": "^4.3.0", + "minimatch": "^9.0.0", + "shiki": "^0.14.1" + }, + "bin": { + "typedoc": "bin/typedoc" + }, + "engines": { + "node": ">= 14.14" + }, + "peerDependencies": { + "typescript": "4.6.x || 4.7.x || 4.8.x || 4.9.x || 5.0.x || 5.1.x" + } + }, + "node_modules/typedoc-plugin-markdown": { + "version": "3.17.1", + "resolved": "https://registry.npmjs.org/typedoc-plugin-markdown/-/typedoc-plugin-markdown-3.17.1.tgz", + "integrity": "sha512-QzdU3fj0Kzw2XSdoL15ExLASt2WPqD7FbLeaqwT70+XjKyTshBnUlQA5nNREO1C2P8Uen0CDjsBLMsCQ+zd0lw==", + "dev": true, + "license": "MIT", + "dependencies": { + "handlebars": "^4.7.7" + }, + "peerDependencies": { + "typedoc": ">=0.24.0" + } + }, + "node_modules/typedoc/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/typedoc/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/typescript": { "version": "4.8.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.8.3.tgz", @@ -6370,6 +6214,16 @@ "punycode": "^2.1.0" } }, + "node_modules/url-parse": { + "version": "1.5.10", + "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", + "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", + "dev": true, + "dependencies": { + "querystringify": "^2.1.1", + "requires-port": "^1.0.0" + } + }, "node_modules/uuid": { "version": "8.3.2", "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", @@ -6447,6 +6301,20 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/vscode-oniguruma": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/vscode-oniguruma/-/vscode-oniguruma-1.7.0.tgz", + "integrity": "sha512-L9WMGRfrjOhgHSdOYgCt/yRMsXzLDJSL7BPrOZt73gU0iWO4mpqzqQzOz5srxqTvMBaR0XZTSrVWo4j55Rc6cA==", + "dev": true, + "license": "MIT" + }, + "node_modules/vscode-textmate": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/vscode-textmate/-/vscode-textmate-8.0.0.tgz", + "integrity": "sha512-AFbieoL7a5LMqcnOF04ji+rpXadgOXnZsxQr//r83kLPr7biP7am3g9zbaZIaBGwBRWeSvoMD4mgPdX3e4NWBg==", + "dev": true, + "license": "MIT" + }, "node_modules/walk-back": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/walk-back/-/walk-back-5.1.0.tgz", @@ -6572,63 +6440,11 @@ } }, "node_modules/which-module": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", - "integrity": "sha512-B+enWhmw6cjfVC7kS8Pj9pCrKSc5txArRyaYGe088shv/FGWH+0Rjx/xPgtsWfsUtS27FkP697E4DDhgrgoc0Q==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.1.tgz", + "integrity": "sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==", "dev": true }, - "node_modules/wide-align": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", - "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", - "dev": true, - "dependencies": { - "string-width": "^1.0.2 || 2" - } - }, - "node_modules/wide-align/node_modules/ansi-regex": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.1.tgz", - "integrity": "sha512-+O9Jct8wf++lXxxFc4hc8LsjaSq0HFzzL7cVsw8pRDIPdjKD2mT4ytDZlLuSBZ4cLKZFXIrMGO7DbQCtMJJMKw==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/wide-align/node_modules/is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/wide-align/node_modules/string-width": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", - "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", - "dev": true, - "dependencies": { - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^4.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/wide-align/node_modules/strip-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", - "integrity": "sha512-4XaJ2zQdCzROZDivEVIDPkcQn8LMFSa8kj8Gxb/Lnwzv9A8VctNZ+lfivC/sV3ivW8ElJTERXZoPBRrZKkNKow==", - "dev": true, - "dependencies": { - "ansi-regex": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/wordwrap": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", @@ -6698,32 +6514,14 @@ "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", "dev": true, "engines": { - "node": ">=10" - } - }, - "node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" - }, - "node_modules/yargs": { - "version": "17.5.1", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.5.1.tgz", - "integrity": "sha512-t6YAJcxDkNX7NFYiVtKvWUz8l+PaKTLiL63mJYWR2GnHq2gjEWISzsLp9wg3aY36dY1j+gfIEL3pIF+XlJJfbA==", - "dev": true, - "dependencies": { - "cliui": "^7.0.2", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.3", - "y18n": "^5.0.5", - "yargs-parser": "^21.0.0" - }, - "engines": { - "node": ">=12" + "node": ">=10" } }, + "node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + }, "node_modules/yargs-parser": { "version": "20.2.4", "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.4.tgz", @@ -6757,15 +6555,6 @@ "node": ">=8" } }, - "node_modules/yargs/node_modules/yargs-parser": { - "version": "21.1.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", - "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", - "dev": true, - "engines": { - "node": ">=12" - } - }, "node_modules/yauzl": { "version": "2.10.0", "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", @@ -6809,8 +6598,7 @@ "version": "1.5.0", "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", "integrity": "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==", - "dev": true, - "optional": true + "dev": true }, "@cspotcode/source-map-support": { "version": "0.8.1", @@ -6822,9 +6610,9 @@ } }, "@cypress/request": { - "version": "2.88.10", - "resolved": "https://registry.npmjs.org/@cypress/request/-/request-2.88.10.tgz", - "integrity": "sha512-Zp7F+R93N0yZyG34GutyTNr+okam7s/Fzc1+i3kcqOP8vk6OuajuE9qZJ6Rs+10/1JFtXFYMdyarnU1rZuJesg==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@cypress/request/-/request-3.0.1.tgz", + "integrity": "sha512-TWivJlJi8ZDx2wGOw1dbLuHJKUYX7bWySw377nlnGOW3hP9/MUKIsEdXT/YngWxVdgNCHRBmFlBipE+5/2ZZlQ==", "dev": true, "requires": { "aws-sign2": "~0.7.0", @@ -6840,9 +6628,9 @@ "json-stringify-safe": "~5.0.1", "mime-types": "~2.1.19", "performance-now": "^2.1.0", - "qs": "~6.5.2", + "qs": "6.10.4", "safe-buffer": "^5.1.2", - "tough-cookie": "~2.5.0", + "tough-cookie": "^4.1.3", "tunnel-agent": "^0.6.0", "uuid": "^8.3.2" }, @@ -7306,9 +7094,9 @@ "dev": true }, "@types/sizzle": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/@types/sizzle/-/sizzle-2.3.3.tgz", - "integrity": "sha512-JYM8x9EGF163bEyhdJBpR2QX1R5naCJHC8ucJylJ3w9/CVBaskdQ8WqBf8MmQrd1kRvp/a4TS8HJ+bxzR7ZJYQ==", + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/@types/sizzle/-/sizzle-2.3.8.tgz", + "integrity": "sha512-0vWLNK2D5MT9dg0iOo8GlKguPAU02QjmZitPEsXRuJXU/OGIOt9vT9Fc26wtYuavLxtO45v9PGleoL9Z0k1LHg==", "dev": true }, "@types/unist": { @@ -7318,9 +7106,9 @@ "dev": true }, "@types/yauzl": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.0.tgz", - "integrity": "sha512-Cn6WYCm0tXv8p6k+A8PvbDG763EDpBoTzHdA+Q/MF6H3sapGjCm9NzoaJncJS9tUKSuCoDs9XHxYYsQDgxR6kw==", + "version": "2.10.3", + "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.3.tgz", + "integrity": "sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==", "dev": true, "optional": true, "requires": { @@ -7604,6 +7392,12 @@ "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", "dev": true }, + "ansi-sequence-parser": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ansi-sequence-parser/-/ansi-sequence-parser-1.1.1.tgz", + "integrity": "sha512-vJXt3yiaUL4UU546s3rPXlsry/RnM730G1+HkpKE012AN0sx1eOrxSu95oKDIonskeLTijMgqWZ3uDEe3NFvyg==", + "dev": true + }, "ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", @@ -7704,9 +7498,9 @@ "dev": true }, "aws4": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.11.0.tgz", - "integrity": "sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA==", + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.13.0.tgz", + "integrity": "sha512-3AungXC4I8kKsS9PuS4JH2nc+0bVY/mjgrephHTIi8fpEeGsTHBUJeosp0Wc1myYMElmD0B3Oc4XL/HVJ4PV2g==", "dev": true }, "bail": { @@ -7721,6 +7515,11 @@ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "dev": true }, + "base64-arraybuffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-1.0.2.tgz", + "integrity": "sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ==" + }, "base64-js": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", @@ -7846,16 +7645,15 @@ } }, "cachedir": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/cachedir/-/cachedir-2.3.0.tgz", - "integrity": "sha512-A+Fezp4zxnit6FanDmv9EqXNAi3vt9DWp51/71UEhXukb7QUuvtv9344h91dyAxuTLoSYJFU299qzR3tzwPAhw==", + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/cachedir/-/cachedir-2.4.0.tgz", + "integrity": "sha512-9EtFOZR8g22CL7BWjJ9BUx1+A/djkofnyW3aOXZORNW2kxoUpx2h+uN2cOqwPmFhnpVmxg+KW2OjOSgChTEvsQ==", "dev": true }, "call-bind": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", - "peer": true, "requires": { "function-bind": "^1.1.1", "get-intrinsic": "^1.0.2" @@ -7958,9 +7756,9 @@ "peer": true }, "ci-info": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.4.0.tgz", - "integrity": "sha512-t5QdPT5jq3o262DOQ8zA6E1tlH2upmUc4Hlvrbx1pGYJuiiHl7O7rvVNI+l8HTVhd/q3Qc9vqimkNk5yiXsAug==", + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", + "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", "dev": true }, "class-transformer": { @@ -8052,15 +7850,9 @@ "dev": true }, "colorette": { - "version": "2.0.19", - "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.19.tgz", - "integrity": "sha512-3tlv/dIP7FWvj3BsbHrGLJ6l/oKh1O3TcgBqMn+yyCagOxc23fyzDS6HypQbgxWbkpDnf52p1LuR4eWDQ/K9WQ==", - "dev": true - }, - "colors": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz", - "integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==", + "version": "2.0.20", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", + "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", "dev": true }, "combined-stream": { @@ -8145,9 +7937,9 @@ } }, "commander": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-5.1.0.tgz", - "integrity": "sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==", + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-6.2.1.tgz", + "integrity": "sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA==", "dev": true }, "common-sequence": { @@ -8168,23 +7960,6 @@ "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", "dev": true }, - "concurrently": { - "version": "7.6.0", - "resolved": "https://registry.npmjs.org/concurrently/-/concurrently-7.6.0.tgz", - "integrity": "sha512-BKtRgvcJGeZ4XttiDiNcFiRlxoAeZOseqUvyYRUp/Vtd+9p1ULmeoSqGsDA+2ivdeDFpqrJvGvmI+StKfKl5hw==", - "dev": true, - "requires": { - "chalk": "^4.1.0", - "date-fns": "^2.29.1", - "lodash": "^4.17.21", - "rxjs": "^7.0.0", - "shell-quote": "^1.7.3", - "spawn-command": "^0.0.2-1", - "supports-color": "^8.1.0", - "tree-kill": "^1.2.2", - "yargs": "^17.3.1" - } - }, "config-master": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/config-master/-/config-master-3.1.0.tgz", @@ -8242,29 +8017,28 @@ } }, "cypress": { - "version": "10.11.0", - "resolved": "https://registry.npmjs.org/cypress/-/cypress-10.11.0.tgz", - "integrity": "sha512-lsaE7dprw5DoXM00skni6W5ElVVLGAdRUUdZjX2dYsGjbY/QnpzWZ95Zom1mkGg0hAaO/QVTZoFVS7Jgr/GUPA==", + "version": "13.12.0", + "resolved": "https://registry.npmjs.org/cypress/-/cypress-13.12.0.tgz", + "integrity": "sha512-udzS2JilmI9ApO/UuqurEwOvThclin5ntz7K0BtnHBs+tg2Bl9QShLISXpSEMDv/u8b6mqdoAdyKeZiSqKWL8g==", "dev": true, "requires": { - "@cypress/request": "^2.88.10", + "@cypress/request": "^3.0.0", "@cypress/xvfb": "^1.2.4", - "@types/node": "^14.14.31", "@types/sinonjs__fake-timers": "8.1.1", "@types/sizzle": "^2.3.2", "arch": "^2.2.0", "blob-util": "^2.0.2", "bluebird": "^3.7.2", - "buffer": "^5.6.0", + "buffer": "^5.7.1", "cachedir": "^2.3.0", "chalk": "^4.1.0", "check-more-types": "^2.24.0", "cli-cursor": "^3.1.0", "cli-table3": "~0.6.1", - "commander": "^5.1.0", + "commander": "^6.2.1", "common-tags": "^1.8.0", "dayjs": "^1.10.4", - "debug": "^4.3.2", + "debug": "^4.3.4", "enquirer": "^2.3.6", "eventemitter2": "6.4.7", "execa": "4.1.0", @@ -8273,30 +8047,25 @@ "figures": "^3.2.0", "fs-extra": "^9.1.0", "getos": "^3.2.1", - "is-ci": "^3.0.0", + "is-ci": "^3.0.1", "is-installed-globally": "~0.4.0", "lazy-ass": "^1.6.0", "listr2": "^3.8.3", "lodash": "^4.17.21", "log-symbols": "^4.0.0", - "minimist": "^1.2.6", + "minimist": "^1.2.8", "ospath": "^1.2.2", "pretty-bytes": "^5.6.0", + "process": "^0.11.10", "proxy-from-env": "1.0.0", "request-progress": "^3.0.0", - "semver": "^7.3.2", + "semver": "^7.5.3", "supports-color": "^8.1.1", "tmp": "~0.2.1", "untildify": "^4.0.0", "yauzl": "^2.10.0" }, "dependencies": { - "@types/node": { - "version": "14.18.29", - "resolved": "https://registry.npmjs.org/@types/node/-/node-14.18.29.tgz", - "integrity": "sha512-LhF+9fbIX4iPzhsRLpK5H7iPdvW8L4IwGciXQIOEcuF62+9nw/VQVsOViAOOGxY3OlOKGLFv0sWwJXdwQeTn6A==", - "dev": true - }, "fs-extra": { "version": "9.1.0", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", @@ -8312,68 +8081,48 @@ } }, "cypress-multi-reporters": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/cypress-multi-reporters/-/cypress-multi-reporters-1.6.1.tgz", - "integrity": "sha512-FPeC0xWF1N6Myrwc2m7KC0xxlrtG8+x4hlsPFBDRWP8u/veR2x90pGaH3BuJfweV7xoQ4Zo85Qjhu3fgZGrBQQ==", + "version": "1.6.4", + "resolved": "https://registry.npmjs.org/cypress-multi-reporters/-/cypress-multi-reporters-1.6.4.tgz", + "integrity": "sha512-3xU2t6pZjZy/ORHaCvci5OT1DAboS4UuMMM8NBAizeb2C9qmHt+cgAjXgurazkwkPRdO7ccK39M5ZaPCju0r6A==", "dev": true, "peer": true, "requires": { - "debug": "^4.1.1", - "lodash": "^4.17.15" + "debug": "^4.3.4", + "lodash": "^4.17.21" } }, "cypress-parallel": { - "version": "0.9.1", - "resolved": "https://registry.npmjs.org/cypress-parallel/-/cypress-parallel-0.9.1.tgz", - "integrity": "sha512-7VSfFr8HEEN6zkgo6SkG7pPoHK7VakFhEH1jbM4+Ire/I+O2jNzyd1bRUA+O3V2DIMow64ECDJKf13YHBon+BQ==", + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/cypress-parallel/-/cypress-parallel-0.14.0.tgz", + "integrity": "sha512-Lsh28G70vxjL0cjR820BdaVQHnGc17Vvb+tYmjbRPmfC+XEzwvUzhcaD0E1zCztBSYhw+b1/1JLmW4Y0qE/EDA==", "dev": true, "requires": { + "@colors/colors": "^1.5.0", "cli-table3": "^0.6.0", - "colors": "^1.4.0", "cross-spawn": "^7.0.3", "fs-extra": "^10.0.0", "glob-escape": "^0.0.2", "is-npm": "^5.0.0", "lodash.camelcase": "^4.3.0", - "mocha": "^8.2.1", + "mocha": "~9.2.0", "yargs": "15.3.1" }, "dependencies": { - "ansi-colors": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", - "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", - "dev": true - }, "camelcase": { "version": "5.3.1", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", "dev": true }, - "chokidar": { - "version": "3.5.1", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.1.tgz", - "integrity": "sha512-9+s+Od+W0VJJzawDma/gvBNQqkTiqYTWLuZoyAsivsI4AaWTCzHG06/TMjsf1cYe9Cb97UCEhjz7HvnPk2p/tw==", - "dev": true, - "requires": { - "anymatch": "~3.1.1", - "braces": "~3.0.2", - "fsevents": "~2.3.1", - "glob-parent": "~5.1.0", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.5.0" - } - }, - "debug": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", - "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "cliui": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", + "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", "dev": true, "requires": { - "ms": "2.1.2" + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^6.2.0" } }, "decamelize": { @@ -8382,33 +8131,14 @@ "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", "dev": true }, - "escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true - }, - "glob": { - "version": "7.1.6", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", - "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", - "dev": true, - "requires": { - "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" - } - }, - "js-yaml": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.0.0.tgz", - "integrity": "sha512-pqon0s+4ScYUvX30wxQi3PogGFAlUyH0awepWvwkj4jD4v+ova3RiYw8bmA6x2rDrEaj8i/oWKoRxpVNW+Re8Q==", + "find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", "dev": true, "requires": { - "argparse": "^2.0.1" + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" } }, "locate-path": { @@ -8420,86 +8150,6 @@ "p-locate": "^4.1.0" } }, - "log-symbols": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.0.0.tgz", - "integrity": "sha512-FN8JBzLx6CzeMrB0tg6pqlGU1wCrXW+ZXGH481kfsBqer0hToTIiHdjH4Mq8xJUbvATujKCvaREGWpGUionraA==", - "dev": true, - "requires": { - "chalk": "^4.0.0" - } - }, - "minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", - "dev": true, - "requires": { - "brace-expansion": "^1.1.7" - } - }, - "mocha": { - "version": "8.4.0", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-8.4.0.tgz", - "integrity": "sha512-hJaO0mwDXmZS4ghXsvPVriOhsxQ7ofcpQdm8dE+jISUOKopitvnXFQmpRR7jd2K6VBG6E26gU3IAbXXGIbu4sQ==", - "dev": true, - "requires": { - "@ungap/promise-all-settled": "1.1.2", - "ansi-colors": "4.1.1", - "browser-stdout": "1.3.1", - "chokidar": "3.5.1", - "debug": "4.3.1", - "diff": "5.0.0", - "escape-string-regexp": "4.0.0", - "find-up": "5.0.0", - "glob": "7.1.6", - "growl": "1.10.5", - "he": "1.2.0", - "js-yaml": "4.0.0", - "log-symbols": "4.0.0", - "minimatch": "3.0.4", - "ms": "2.1.3", - "nanoid": "3.1.20", - "serialize-javascript": "5.0.1", - "strip-json-comments": "3.1.1", - "supports-color": "8.1.1", - "which": "2.0.2", - "wide-align": "1.1.3", - "workerpool": "6.1.0", - "yargs": "16.2.0", - "yargs-parser": "20.2.4", - "yargs-unparser": "2.0.0" - }, - "dependencies": { - "ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true - }, - "yargs": { - "version": "16.2.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", - "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", - "dev": true, - "requires": { - "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" - } - } - } - }, - "nanoid": { - "version": "3.1.20", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.20.tgz", - "integrity": "sha512-a1cQNyczgKbLX9jwbS/+d7W8fX/RfgYR7lVWwWOGIPNgK2m0MWvrGF6/m4kk6U3QcFMnZf3RIhL0v2Jgh/0Uxw==", - "dev": true - }, "p-limit": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", @@ -8518,21 +8168,6 @@ "p-limit": "^2.2.0" } }, - "readdirp": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.5.0.tgz", - "integrity": "sha512-cMhu7c/8rdhkHXWsY+osBhfSy0JikwpHK/5+imo+LpeasTF8ouErHrlYkwT0++njiyuDvc7OFY5T3ukvZ8qmFQ==", - "dev": true, - "requires": { - "picomatch": "^2.2.1" - } - }, - "workerpool": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.1.0.tgz", - "integrity": "sha512-toV7q9rWNYha963Pl/qyeZ6wG+3nnsyvolaNUS8+R5Wtw6qJPTxIlOP1ZSvcGhEJw+l3HMMmtiNo9Gl61G4GVg==", - "dev": true - }, "wrap-ansi": { "version": "6.2.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", @@ -8544,6 +8179,12 @@ "strip-ansi": "^6.0.0" } }, + "y18n": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", + "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==", + "dev": true + }, "yargs": { "version": "15.3.1", "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.3.1.tgz", @@ -8561,45 +8202,16 @@ "which-module": "^2.0.0", "y18n": "^4.0.0", "yargs-parser": "^18.1.1" - }, - "dependencies": { - "cliui": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", - "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", - "dev": true, - "requires": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^6.2.0" - } - }, - "find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dev": true, - "requires": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - } - }, - "y18n": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", - "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==", - "dev": true - }, - "yargs-parser": { - "version": "18.1.3", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", - "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", - "dev": true, - "requires": { - "camelcase": "^5.0.0", - "decamelize": "^1.2.0" - } - } + } + }, + "yargs-parser": { + "version": "18.1.3", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", + "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", + "dev": true, + "requires": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" } } } @@ -8619,16 +8231,10 @@ "integrity": "sha512-WboRycPNsVw3B3TL559F7kuBUM4d8CgMEvk6xEJlOp7OBPjt6G7z8WMWlD2rOFZLk6OYfFIUGsCOWzcQH9K2og==", "dev": true }, - "date-fns": { - "version": "2.29.3", - "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.29.3.tgz", - "integrity": "sha512-dDCnyH2WnnKusqvZZ6+jA1O51Ibt8ZMRNkDZdyAyK4YfbDwa/cEmuztzG5pk6hqlp9aSBPYcjOlktquahGwGeA==", - "dev": true - }, "dayjs": { - "version": "1.11.5", - "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.5.tgz", - "integrity": "sha512-CAdX5Q3YW3Gclyo5Vpqkgpj8fSdLQcRuzfX6mC6Phy0nfJ0eGYOeS7m4mt2plDWLAtA4TqTakvbboHvUxfe4iA==", + "version": "1.11.11", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.11.tgz", + "integrity": "sha512-okzr3f11N6WuqYtZSvm+F776mB41wRZMhKP+hc34YdW+KmtYYK9iqvHSwo2k9FEH3fhGXvOPV6yz2IcSrfRUDg==", "dev": true }, "debug": { @@ -8772,12 +8378,13 @@ } }, "enquirer": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz", - "integrity": "sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==", + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.4.1.tgz", + "integrity": "sha512-rRqJg/6gd538VHvR3PSrdRBb/1Vy2YfzHqzvbhGIQpDRKIa4FgV/54b5Q1xYSxOOwKvjXweS26E0Q+nAMwp2pQ==", "dev": true, "requires": { - "ansi-colors": "^4.1.1" + "ansi-colors": "^4.1.1", + "strip-ansi": "^6.0.1" } }, "entities": { @@ -9102,8 +8709,7 @@ "function-bind": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", - "peer": true + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" }, "get-caller-file": { "version": "2.0.5", @@ -9115,7 +8721,6 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.1.tgz", "integrity": "sha512-2DcsyfABl+gVHEfCOaTrWgyt+tb6MSEGmKq+kI5HwLbIYgjgmMcV8KQ41uaKz1xxUcn9tJtgFbQUEVcEbd0FYw==", - "peer": true, "requires": { "function-bind": "^1.1.1", "has": "^1.0.3", @@ -9187,9 +8792,9 @@ "peer": true }, "global-dirs": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/global-dirs/-/global-dirs-3.0.0.tgz", - "integrity": "sha512-v8ho2DS5RiCjftj1nD9NmnfaOzTdud7RRnVd9kFNOjqZbISlx5DQ+OrTkywgd0dIt7oFCvKetZSHoHcP3sDdiA==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/global-dirs/-/global-dirs-3.0.1.tgz", + "integrity": "sha512-NBcGGFbBA9s1VzD41QXDG+3++t9Mn5t1FpLdhESY6oKY4gYTFpX4wO3sqGUa0Srjtbfj3szX0RnemmrVRUdULA==", "dev": true, "requires": { "ini": "2.0.0" @@ -9238,7 +8843,6 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", - "peer": true, "requires": { "function-bind": "^1.1.1" } @@ -9252,14 +8856,12 @@ "has-proto": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz", - "integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==", - "peer": true + "integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==" }, "has-symbols": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", - "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", - "peer": true + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==" }, "he": { "version": "1.2.0", @@ -9592,6 +9194,12 @@ "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", "dev": true }, + "jsonc-parser": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.3.1.tgz", + "integrity": "sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ==", + "dev": true + }, "jsonfile": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", @@ -9833,6 +9441,12 @@ "yallist": "^4.0.0" } }, + "lunr": { + "version": "2.3.9", + "resolved": "https://registry.npmjs.org/lunr/-/lunr-2.3.9.tgz", + "integrity": "sha512-zTU3DaZaF3Rt9rhN3uBMGQD3dD2/vFQqnvZCDv4dl5iOzq2IZQqTxu90r4E5J+nP70J3ilqVCrbho2eWaeW8Ow==", + "dev": true + }, "make-error": { "version": "1.3.6", "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", @@ -9860,9 +9474,9 @@ "requires": {} }, "marked": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/marked/-/marked-4.1.0.tgz", - "integrity": "sha512-+Z6KDjSPa6/723PQYyc1axYZpYYpDnECDaU6hkaf5gqBieBkMKYReL5hteF2QizhlMbgbo8umXl/clZ67+GlsA==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/marked/-/marked-4.3.0.tgz", + "integrity": "sha512-PRsaiG84bK+AMvxziE/lCFss8juXjNaWzVbN5tXAm4XjeaS9NAHhop+PjQxz2A9h8Q4M/xGmzP8vqNwy6JeK0A==", "dev": true }, "mdast-util-from-markdown": { @@ -10181,9 +9795,9 @@ } }, "minimist": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", - "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==", + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", "dev": true }, "minipass": { @@ -10407,8 +10021,7 @@ "object-inspect": { "version": "1.12.3", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz", - "integrity": "sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==", - "peer": true + "integrity": "sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==" }, "object-to-spawn-args": { "version": "2.0.1", @@ -10534,6 +10147,12 @@ "integrity": "sha512-FFw039TmrBqFK8ma/7OL3sDz/VytdtJr044/QUJtH0wK9lb9jLq9tJyIxUwtQJHwar2BqtiA4iCWSwo9JLkzFg==", "dev": true }, + "process": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", + "dev": true + }, "proxy-from-env": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.0.0.tgz", @@ -10563,9 +10182,18 @@ "dev": true }, "qs": { - "version": "6.5.3", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.3.tgz", - "integrity": "sha512-qxXIEh4pCGfHICj1mAJQ2/2XVZkjCDTcEgfoSQxc/fYivUZxTkk7L3bDBJSoNrEzXI17oUO5Dp07ktqE5KzczA==", + "version": "6.10.4", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.10.4.tgz", + "integrity": "sha512-OQiU+C+Ds5qiH91qh/mg0w+8nwQuLjM4F4M/PbmhDOoYehPh+Fb0bDjtR1sOvy7YKxvj28Y/M0PhP5uVX0kB+g==", + "dev": true, + "requires": { + "side-channel": "^1.0.4" + } + }, + "querystringify": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", + "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==", "dev": true }, "queue-microtask": { @@ -10711,6 +10339,12 @@ "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", "dev": true }, + "requires-port": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", + "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", + "dev": true + }, "requizzle": { "version": "0.2.3", "resolved": "https://registry.npmjs.org/requizzle/-/requizzle-0.2.3.tgz", @@ -10737,20 +10371,11 @@ "dev": true }, "rfdc": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.3.0.tgz", - "integrity": "sha512-V2hovdzFbOi77/WajaSMXk2OLm+xNIeQdMMuB7icj7bk6zi2F8GGAxigcnDFpJHbNyNcgyJDiP+8nOrY5cZGrA==", + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz", + "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==", "dev": true }, - "rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "dev": true, - "requires": { - "glob": "^7.1.3" - } - }, "run-parallel": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", @@ -10854,17 +10479,22 @@ "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", "dev": true }, - "shell-quote": { - "version": "1.7.3", - "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.7.3.tgz", - "integrity": "sha512-Vpfqwm4EnqGdlsBFNmHhxhElJYrdfcxPThu+ryKS5J8L/fhAwLazFZtq+S+TWZ9ANj2piSQLGj6NQg+lKPmxrw==", - "dev": true + "shiki": { + "version": "0.14.7", + "resolved": "https://registry.npmjs.org/shiki/-/shiki-0.14.7.tgz", + "integrity": "sha512-dNPAPrxSc87ua2sKJ3H5dQ/6ZaY8RNnaAqK+t0eG7p0Soi2ydiqbGOTaZCqaYvA/uZYfS1LJnemt3Q+mSfcPCg==", + "dev": true, + "requires": { + "ansi-sequence-parser": "^1.1.0", + "jsonc-parser": "^3.2.0", + "vscode-oniguruma": "^1.7.0", + "vscode-textmate": "^8.0.0" + } }, "side-channel": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", - "peer": true, "requires": { "call-bind": "^1.0.0", "get-intrinsic": "^1.0.2", @@ -10934,16 +10564,10 @@ "source-map": "^0.6.0" } }, - "spawn-command": { - "version": "0.0.2-1", - "resolved": "https://registry.npmjs.org/spawn-command/-/spawn-command-0.0.2-1.tgz", - "integrity": "sha512-n98l9E2RMSJ9ON1AKisHzz7V42VDiBQGY6PB1BwRglz99wpVsSuGzQ+jOi6lFXBGVTCrRpltvjm+/XA+tpeJrg==", - "dev": true - }, "sshpk": { - "version": "1.17.0", - "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.17.0.tgz", - "integrity": "sha512-/9HIEs1ZXGhSPE8X6Ccm7Nam1z8KcoCqPdI7ecm1N33EzAetWahvQWVqLZtaZQ+IDKX4IyA2o0gBzqIMkAagHQ==", + "version": "1.18.0", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.18.0.tgz", + "integrity": "sha512-2p2KJZTSqQ/I3+HX42EpYOa2l3f8Erv8MWKsy2I9uf4wA7yFIkXRffYdsx86y6z4vHtV8u7g+pPlr8/4ouAxsQ==", "dev": true, "requires": { "asn1": "~0.2.3", @@ -11174,9 +10798,9 @@ "peer": true }, "throttleit": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/throttleit/-/throttleit-1.0.0.tgz", - "integrity": "sha512-rkTVqu6IjfQ/6+uNuuc3sZek4CEYxTJom3IktzgdSxcZqdARuebbA/f4QmAxMQIxqq9ZLEUkSYqvuk1I6VKq4g==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/throttleit/-/throttleit-1.0.1.tgz", + "integrity": "sha512-vDZpf9Chs9mAdfY046mcPt8fg5QSZr37hEH4TXYBnDF+izxgrbRGUAAaBvIk/fJm9aOFCGFd1EsNg5AZCbnQCQ==", "dev": true }, "through": { @@ -11186,13 +10810,10 @@ "dev": true }, "tmp": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.1.tgz", - "integrity": "sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ==", - "dev": true, - "requires": { - "rimraf": "^3.0.0" - } + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.3.tgz", + "integrity": "sha512-nZD7m9iCPC5g0pYmcaxogYKggSfLsdxl8of3Q/oIbqCqLLIO9IAF0GWjX1z9NZRHPiXv8Wex4yDCaZsgEw0Y8w==", + "dev": true }, "to-regex-range": { "version": "5.0.1", @@ -11204,13 +10825,23 @@ } }, "tough-cookie": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", - "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.4.tgz", + "integrity": "sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==", "dev": true, "requires": { - "psl": "^1.1.28", - "punycode": "^2.1.1" + "psl": "^1.1.33", + "punycode": "^2.1.1", + "universalify": "^0.2.0", + "url-parse": "^1.5.3" + }, + "dependencies": { + "universalify": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz", + "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==", + "dev": true + } } }, "tr46": { @@ -11218,12 +10849,6 @@ "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" }, - "tree-kill": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", - "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==", - "dev": true - }, "trough": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/trough/-/trough-2.1.0.tgz", @@ -11391,6 +11016,47 @@ "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", "dev": true }, + "typedoc": { + "version": "0.24.8", + "resolved": "https://registry.npmjs.org/typedoc/-/typedoc-0.24.8.tgz", + "integrity": "sha512-ahJ6Cpcvxwaxfu4KtjA8qZNqS43wYt6JL27wYiIgl1vd38WW/KWX11YuAeZhuz9v+ttrutSsgK+XO1CjL1kA3w==", + "dev": true, + "requires": { + "lunr": "^2.3.9", + "marked": "^4.3.0", + "minimatch": "^9.0.0", + "shiki": "^0.14.1" + }, + "dependencies": { + "brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0" + } + }, + "minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "requires": { + "brace-expansion": "^2.0.1" + } + } + } + }, + "typedoc-plugin-markdown": { + "version": "3.17.1", + "resolved": "https://registry.npmjs.org/typedoc-plugin-markdown/-/typedoc-plugin-markdown-3.17.1.tgz", + "integrity": "sha512-QzdU3fj0Kzw2XSdoL15ExLASt2WPqD7FbLeaqwT70+XjKyTshBnUlQA5nNREO1C2P8Uen0CDjsBLMsCQ+zd0lw==", + "dev": true, + "requires": { + "handlebars": "^4.7.7" + } + }, "typescript": { "version": "4.8.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.8.3.tgz", @@ -11478,6 +11144,16 @@ "punycode": "^2.1.0" } }, + "url-parse": { + "version": "1.5.10", + "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", + "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", + "dev": true, + "requires": { + "querystringify": "^2.1.1", + "requires-port": "^1.0.0" + } + }, "uuid": { "version": "8.3.2", "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", @@ -11535,6 +11211,18 @@ "unist-util-stringify-position": "^3.0.0" } }, + "vscode-oniguruma": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/vscode-oniguruma/-/vscode-oniguruma-1.7.0.tgz", + "integrity": "sha512-L9WMGRfrjOhgHSdOYgCt/yRMsXzLDJSL7BPrOZt73gU0iWO4mpqzqQzOz5srxqTvMBaR0XZTSrVWo4j55Rc6cA==", + "dev": true + }, + "vscode-textmate": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/vscode-textmate/-/vscode-textmate-8.0.0.tgz", + "integrity": "sha512-AFbieoL7a5LMqcnOF04ji+rpXadgOXnZsxQr//r83kLPr7biP7am3g9zbaZIaBGwBRWeSvoMD4mgPdX3e4NWBg==", + "dev": true + }, "walk-back": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/walk-back/-/walk-back-5.1.0.tgz", @@ -11626,53 +11314,11 @@ } }, "which-module": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", - "integrity": "sha512-B+enWhmw6cjfVC7kS8Pj9pCrKSc5txArRyaYGe088shv/FGWH+0Rjx/xPgtsWfsUtS27FkP697E4DDhgrgoc0Q==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.1.tgz", + "integrity": "sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==", "dev": true }, - "wide-align": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", - "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", - "dev": true, - "requires": { - "string-width": "^1.0.2 || 2" - }, - "dependencies": { - "ansi-regex": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.1.tgz", - "integrity": "sha512-+O9Jct8wf++lXxxFc4hc8LsjaSq0HFzzL7cVsw8pRDIPdjKD2mT4ytDZlLuSBZ4cLKZFXIrMGO7DbQCtMJJMKw==", - "dev": true - }, - "is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w==", - "dev": true - }, - "string-width": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", - "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", - "dev": true, - "requires": { - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^4.0.0" - } - }, - "strip-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", - "integrity": "sha512-4XaJ2zQdCzROZDivEVIDPkcQn8LMFSa8kj8Gxb/Lnwzv9A8VctNZ+lfivC/sV3ivW8ElJTERXZoPBRrZKkNKow==", - "dev": true, - "requires": { - "ansi-regex": "^3.0.0" - } - } - } - }, "wordwrap": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", @@ -11737,29 +11383,6 @@ "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" }, - "yargs": { - "version": "17.5.1", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.5.1.tgz", - "integrity": "sha512-t6YAJcxDkNX7NFYiVtKvWUz8l+PaKTLiL63mJYWR2GnHq2gjEWISzsLp9wg3aY36dY1j+gfIEL3pIF+XlJJfbA==", - "dev": true, - "requires": { - "cliui": "^7.0.2", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.3", - "y18n": "^5.0.5", - "yargs-parser": "^21.0.0" - }, - "dependencies": { - "yargs-parser": { - "version": "21.1.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", - "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", - "dev": true - } - } - }, "yargs-parser": { "version": "20.2.4", "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.4.tgz", diff --git a/bindings/wasm/package.json b/bindings/wasm/package.json index 8681e9407b..5701fa41dc 100644 --- a/bindings/wasm/package.json +++ b/bindings/wasm/package.json @@ -1,6 +1,6 @@ { "name": "@iota/identity-wasm", - "version": "1.2.0", + "version": "1.4.0", "description": "WASM bindings for IOTA Identity - A Self Sovereign Identity Framework implementing the DID and VC standards from W3C. To be used in Javascript/Typescript", "repository": { "type": "git", @@ -15,20 +15,24 @@ "bundle:web": "wasm-bindgen target/wasm32-unknown-unknown/release/identity_wasm.wasm --typescript --target web --out-dir web && node ./build/web && tsc --project ./lib/tsconfig.web.json && node ./build/replace_paths ./lib/tsconfig.web.json web", "build:nodejs": "npm run build:src && npm run bundle:nodejs && wasm-opt -O node/identity_wasm_bg.wasm -o node/identity_wasm_bg.wasm", "build:web": "npm run build:src && npm run bundle:web && wasm-opt -O web/identity_wasm_bg.wasm -o web/identity_wasm_bg.wasm", - "build:docs": "node ./build/docs", + "build:docs": "typedoc && npm run fix_docs", "build:examples:web": "tsc --project ./examples/tsconfig.web.json && node ./build/replace_paths ./examples/tsconfig.web.json ./examples/dist resolve", "build": "npm run build:web && npm run build:nodejs && npm run build:docs", "example:node": "ts-node --project tsconfig.node.json -r tsconfig-paths/register ./examples/src/main.ts", - "test": "npm run test:unit:node && npm run test:examples", - "test:examples": "npm run test:readme && concurrently -g --timings \"npm run test:node\" \"npm run test:browser:parallel\"", + "test": "npm run test:unit:node && npm run test:readme && npm run test:node && test:browser:parallel", "test:node": "ts-mocha -r tsconfig-paths/register -p tsconfig.node.json ./examples/src/tests/*.ts --parallel --jobs 4 --retries 3 --timeout 180000 --exit", - "test:browser:parallel": "npm run build:examples:web && cypress-parallel -s test:browser -t 4 -d cypress/e2e -a '\"--quiet\"'", + "test:browser:parallel": "cypress-parallel -s test:browser -t 4 -d cypress/e2e -a '\"--quiet\"'", + "test:browser:parallel:firefox": "cypress-parallel -s test:browser:firefox -t 4 -d cypress/e2e -a '\"--quiet\"'", + "test:browser:parallel:chrome": "cypress-parallel -s test:browser:chrome -t 4 -d cypress/e2e -a '\"--quiet\"'", "test:browser": "cypress run --headless", + "test:browser:firefox": "cypress run --headless --browser firefox", + "test:browser:chrome": "cypress run --headless --browser chrome", "test:readme": "mocha ./tests/txm_readme.js --retries 3 --timeout 180000 --exit", "test:readme:rust": "mocha ./tests/txm_readme_rust.js --retries 3 --timeout 360000 --exit", "test:unit:node": "ts-mocha -p tsconfig.node.json ./tests/*.ts --parallel --exit", "cypress": "cypress open", - "fmt": "dprint fmt" + "fmt": "dprint fmt", + "fix_docs": "sed -Ei 's/(\\.md?#([^#]*)?)#/\\1/' ./docs/wasm/**/*.md" }, "config": { "CYPRESS_VERIFY_TIMEOUT": 100000 @@ -56,10 +60,9 @@ "@transmute/did-key-ed25519": "0.3.0-unstable.9", "@types/mocha": "^9.1.0", "big-integer": "^1.6.51", - "concurrently": "^7.6.0", "copy-webpack-plugin": "^7.0.0", - "cypress": "^10.11.0", - "cypress-parallel": "^0.9.1", + "cypress": "^13.12.0", + "cypress-parallel": "^0.14.0", "dprint": "^0.33.0", "fs-extra": "^10.1.0", "jsdoc-to-markdown": "^7.1.1", @@ -68,12 +71,15 @@ "ts-node": "^10.9.1", "tsconfig-paths": "^4.1.0", "txm": "^8.1.0", + "typedoc": "^0.24.6", + "typedoc-plugin-markdown": "^3.14.0", "typescript": "^4.7.2", "wasm-opt": "^1.3.0" }, "dependencies": { "@noble/ed25519": "^1.7.3", "@types/node-fetch": "^2.6.2", + "base64-arraybuffer": "^1.0.2", "node-fetch": "^2.6.7" }, "peerDependencies": { diff --git a/bindings/wasm/src/common/types.rs b/bindings/wasm/src/common/types.rs index 295e0ea447..8264e923ce 100644 --- a/bindings/wasm/src/common/types.rs +++ b/bindings/wasm/src/common/types.rs @@ -75,3 +75,9 @@ impl TryFrom<&Object> for MapStringAny { Ok(map.unchecked_into::()) } } + +impl Default for MapStringAny { + fn default() -> Self { + js_sys::Map::new().unchecked_into() + } +} diff --git a/bindings/wasm/src/credential/domain_linkage_validator.rs b/bindings/wasm/src/credential/domain_linkage_validator.rs index a38639d853..37674e21b7 100644 --- a/bindings/wasm/src/credential/domain_linkage_validator.rs +++ b/bindings/wasm/src/credential/domain_linkage_validator.rs @@ -24,11 +24,11 @@ pub struct WasmJwtDomainLinkageValidator { #[wasm_bindgen(js_class = JwtDomainLinkageValidator)] impl WasmJwtDomainLinkageValidator { /// Creates a new {@link JwtDomainLinkageValidator}. If a `signatureVerifier` is provided it will be used when - /// verifying decoded JWS signatures, otherwise the default which is only capable of handling the `EdDSA` - /// algorithm will be used. + /// verifying decoded JWS signatures, otherwise a default verifier capable of handling the `EdDSA`, `ES256`, `ES256K` + /// algorithms will be used. #[wasm_bindgen(constructor)] #[allow(non_snake_case)] - pub fn new(signatureVerifier: IJwsVerifier) -> WasmJwtDomainLinkageValidator { + pub fn new(signatureVerifier: Option) -> WasmJwtDomainLinkageValidator { let signature_verifier = WasmJwsVerifier::new(signatureVerifier); WasmJwtDomainLinkageValidator { validator: JwtDomainLinkageValidator::with_signature_verifier(signature_verifier), diff --git a/bindings/wasm/src/credential/jpt.rs b/bindings/wasm/src/credential/jpt.rs new file mode 100644 index 0000000000..e3e3daab2b --- /dev/null +++ b/bindings/wasm/src/credential/jpt.rs @@ -0,0 +1,45 @@ +// Copyright 2020-2024 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +use identity_iota::credential::Jpt; +use wasm_bindgen::prelude::*; + +/// A JSON Proof Token (JPT). +#[wasm_bindgen(js_name = Jpt)] +pub struct WasmJpt(pub(crate) Jpt); + +#[wasm_bindgen(js_class = Jpt)] +impl WasmJpt { + /// Creates a new {@link Jpt}. + #[wasm_bindgen(constructor)] + pub fn new(jpt_string: String) -> Self { + WasmJpt(Jpt::new(jpt_string)) + } + + // Returns the string representation for this {@link Jpt}. + #[allow(clippy::inherent_to_string)] + #[wasm_bindgen(js_name = "toString")] + pub fn to_string(&self) -> String { + self.0.as_str().to_owned() + } +} + +impl_wasm_clone!(WasmJpt, Jpt); + +impl From for WasmJpt { + fn from(value: Jpt) -> Self { + WasmJpt(value) + } +} + +impl From for Jpt { + fn from(value: WasmJpt) -> Self { + value.0 + } +} + +#[wasm_bindgen] +extern "C" { + #[wasm_bindgen(typescript_type = "Promise")] + pub type PromiseJpt; +} diff --git a/bindings/wasm/src/credential/jpt_credential_validator/decoded_jpt_credential.rs b/bindings/wasm/src/credential/jpt_credential_validator/decoded_jpt_credential.rs new file mode 100644 index 0000000000..46c999a40f --- /dev/null +++ b/bindings/wasm/src/credential/jpt_credential_validator/decoded_jpt_credential.rs @@ -0,0 +1,52 @@ +// Copyright 2020-2024 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +use identity_iota::core::Object; +use identity_iota::credential::DecodedJptCredential; +use wasm_bindgen::prelude::*; + +use crate::common::MapStringAny; +use crate::credential::WasmCredential; +use crate::error::Result; +use crate::jpt::WasmJwpIssued; + +#[wasm_bindgen(js_name = DecodedJptCredential)] +pub struct WasmDecodedJptCredential(pub(crate) DecodedJptCredential); + +impl_wasm_clone!(WasmDecodedJptCredential, DecodedJptCredential); + +#[wasm_bindgen(js_class = DecodedJptCredential)] +impl WasmDecodedJptCredential { + /// Returns the {@link Credential} embedded into this JPT. + #[wasm_bindgen] + pub fn credential(&self) -> WasmCredential { + WasmCredential(self.0.credential.clone()) + } + + /// Returns the custom claims parsed from the JPT. + #[wasm_bindgen(js_name = "customClaims")] + pub fn custom_claims(&self) -> Result { + match self.0.custom_claims.clone() { + Some(obj) => MapStringAny::try_from(obj), + None => Ok(MapStringAny::default()), + } + } + + // The decoded and verified issued JWP, will be used to construct the presented JWP. + #[wasm_bindgen(js_name = decodedJwp)] + pub fn decoded_jwp(&self) -> WasmJwpIssued { + WasmJwpIssued(self.0.decoded_jwp.clone()) + } +} + +impl From for WasmDecodedJptCredential { + fn from(value: DecodedJptCredential) -> Self { + WasmDecodedJptCredential(value) + } +} + +impl From for DecodedJptCredential { + fn from(value: WasmDecodedJptCredential) -> Self { + value.0 + } +} diff --git a/bindings/wasm/src/credential/jpt_credential_validator/jpt_credential_validation_options.rs b/bindings/wasm/src/credential/jpt_credential_validator/jpt_credential_validation_options.rs new file mode 100644 index 0000000000..aefc6ec443 --- /dev/null +++ b/bindings/wasm/src/credential/jpt_credential_validator/jpt_credential_validation_options.rs @@ -0,0 +1,80 @@ +// Copyright 2020-2024 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +use identity_iota::credential::JptCredentialValidationOptions; +use wasm_bindgen::prelude::*; + +use crate::error::Result; +use crate::error::WasmResult; + +/// Options to declare validation criteria for {@link Jpt}. +#[derive(Debug, Default, Clone)] +#[wasm_bindgen(js_name = "JptCredentialValidationOptions", inspectable)] +pub struct WasmJptCredentialValidationOptions(pub(crate) JptCredentialValidationOptions); + +impl_wasm_clone!(WasmJptCredentialValidationOptions, JptCredentialValidationOptions); +impl_wasm_json!(WasmJptCredentialValidationOptions, JptCredentialValidationOptions); + +#[wasm_bindgen(js_class = JptCredentialValidationOptions)] +impl WasmJptCredentialValidationOptions { + /// Creates a new default istance. + #[wasm_bindgen(constructor)] + pub fn new(opts: Option) -> Result { + if let Some(opts) = opts { + opts.into_serde().wasm_result().map(WasmJptCredentialValidationOptions) + } else { + Ok(WasmJptCredentialValidationOptions::default()) + } + } +} + +impl From for WasmJptCredentialValidationOptions { + fn from(value: JptCredentialValidationOptions) -> Self { + WasmJptCredentialValidationOptions(value) + } +} + +impl From for JptCredentialValidationOptions { + fn from(value: WasmJptCredentialValidationOptions) -> Self { + value.0 + } +} + +#[wasm_bindgen] +extern "C" { + #[wasm_bindgen(typescript_type = "IJptCredentialValidationOptions")] + pub type IJptCredentialValidationOptions; +} + +#[wasm_bindgen(typescript_custom_section)] +const I_JPT_CREDENTIAL_VALIDATION_OPTIONS: &'static str = r#" +/** Holds options to create a new {@link JptCredentialValidationOptions}. */ +interface IJptCredentialValidationOptions { + /** + * Declare that the credential is **not** considered valid if it expires before this {@link Timestamp}. + * Uses the current datetime during validation if not set. + */ + readonly earliestExpiryDate?: Timestamp; + + /** + * Declare that the credential is **not** considered valid if it was issued later than this {@link Timestamp}. + * Uses the current datetime during validation if not set. + */ + readonly latestIssuanceDate?: Timestamp; + + /** + * Validation behaviour for [`credentialStatus`](https://www.w3.org/TR/vc-data-model/#status). + */ + readonly status?: StatusCheck; + + /** Declares how credential subjects must relate to the presentation holder during validation. + * + * + */ + readonly subjectHolderRelationship?: [string, SubjectHolderRelationship]; + + /** + * Options which affect the verification of the proof on the credential. + */ + readonly verificationOptions?: JwpVerificationOptions; +}"#; diff --git a/bindings/wasm/src/credential/jpt_credential_validator/jpt_credential_validator.rs b/bindings/wasm/src/credential/jpt_credential_validator/jpt_credential_validator.rs new file mode 100644 index 0000000000..10876fe96f --- /dev/null +++ b/bindings/wasm/src/credential/jpt_credential_validator/jpt_credential_validator.rs @@ -0,0 +1,33 @@ +// Copyright 2020-2024 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +use crate::common::ImportedDocumentLock; +use crate::credential::WasmDecodedJptCredential; +use crate::credential::WasmFailFast; +use crate::credential::WasmJpt; +use crate::credential::WasmJptCredentialValidationOptions; +use crate::did::IToCoreDocument; +use crate::error::Result; +use crate::error::WasmResult; +use identity_iota::credential::JptCredentialValidator; +use wasm_bindgen::prelude::*; + +#[wasm_bindgen(js_name = JptCredentialValidator)] +pub struct WasmJptCredentialValidator; + +#[wasm_bindgen(js_class = JptCredentialValidator)] +impl WasmJptCredentialValidator { + #[wasm_bindgen] + pub fn validate( + credential_jpt: &WasmJpt, + issuer: &IToCoreDocument, + options: &WasmJptCredentialValidationOptions, + fail_fast: WasmFailFast, + ) -> Result { + let issuer_lock = ImportedDocumentLock::from(issuer); + let issuer_guard = issuer_lock.try_read()?; + JptCredentialValidator::validate(&credential_jpt.0, &issuer_guard, &options.0, fail_fast.into()) + .wasm_result() + .map(WasmDecodedJptCredential) + } +} diff --git a/bindings/wasm/src/credential/jpt_credential_validator/jpt_credential_validator_utils.rs b/bindings/wasm/src/credential/jpt_credential_validator/jpt_credential_validator_utils.rs new file mode 100644 index 0000000000..c2def61059 --- /dev/null +++ b/bindings/wasm/src/credential/jpt_credential_validator/jpt_credential_validator_utils.rs @@ -0,0 +1,102 @@ +// Copyright 2020-2024 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +use crate::common::ImportedDocumentLock; +use crate::common::WasmTimestamp; +use crate::credential::options::WasmStatusCheck; +use crate::credential::WasmCredential; +use crate::credential::WasmJpt; +use crate::did::IToCoreDocument; +use crate::did::WasmCoreDID; +use crate::error::Result; +use crate::error::WasmResult; +use identity_iota::core::Object; +use identity_iota::credential::JptCredentialValidatorUtils; +use identity_iota::did::CoreDID; +use wasm_bindgen::prelude::*; + +/// Utility functions for validating JPT credentials. +#[wasm_bindgen(js_name = JptCredentialValidatorUtils)] +#[derive(Default)] +pub struct WasmJptCredentialValidatorUtils; + +#[wasm_bindgen(js_class = JptCredentialValidatorUtils)] +impl WasmJptCredentialValidatorUtils { + #[wasm_bindgen(constructor)] + pub fn new() -> WasmJptCredentialValidatorUtils { + WasmJptCredentialValidatorUtils + } + + /// Utility for extracting the issuer field of a {@link Credential} as a DID. + /// # Errors + /// Fails if the issuer field is not a valid DID. + #[wasm_bindgen(js_name = "extractIssuer")] + pub fn extract_issuer(credential: &WasmCredential) -> Result { + JptCredentialValidatorUtils::extract_issuer::(&credential.0) + .wasm_result() + .map(WasmCoreDID::from) + } + /// Utility for extracting the issuer field of a credential in JPT representation as DID. + /// # Errors + /// If the JPT decoding fails or the issuer field is not a valid DID. + #[wasm_bindgen(js_name = "extractIssuerFromIssuedJpt")] + pub fn extract_issuer_from_issued_jpt(credential: &WasmJpt) -> Result { + JptCredentialValidatorUtils::extract_issuer_from_issued_jpt::(&credential.0) + .wasm_result() + .map(WasmCoreDID::from) + } + + #[wasm_bindgen(js_name = "checkTimeframesWithValidityTimeframe2024")] + pub fn check_timeframes_with_validity_timeframe_2024( + credential: &WasmCredential, + validity_timeframe: Option, + status_check: WasmStatusCheck, + ) -> Result<()> { + JptCredentialValidatorUtils::check_timeframes_with_validity_timeframe_2024( + &credential.0, + validity_timeframe.map(|t| t.0), + status_check.into(), + ) + .wasm_result() + } + + /// Checks whether the credential status has been revoked. + /// + /// Only supports `RevocationTimeframe2024`. + #[wasm_bindgen(js_name = "checkRevocationWithValidityTimeframe2024")] + pub fn check_revocation_with_validity_timeframe_2024( + credential: &WasmCredential, + issuer: &IToCoreDocument, + status_check: WasmStatusCheck, + ) -> Result<()> { + let issuer_lock = ImportedDocumentLock::from(issuer); + let issuer_guard = issuer_lock.try_read()?; + JptCredentialValidatorUtils::check_revocation_with_validity_timeframe_2024( + &credential.0, + &issuer_guard, + status_check.into(), + ) + .wasm_result() + } + + /// Checks whether the credential status has been revoked or the timeframe interval is INVALID + /// + /// Only supports `RevocationTimeframe2024`. + #[wasm_bindgen(js_name = "checkTimeframesAndRevocationWithValidityTimeframe2024")] + pub fn check_timeframes_and_revocation_with_validity_timeframe_2024( + credential: &WasmCredential, + issuer: &IToCoreDocument, + validity_timeframe: Option, + status_check: WasmStatusCheck, + ) -> Result<()> { + let issuer_lock = ImportedDocumentLock::from(issuer); + let issuer_guard = issuer_lock.try_read()?; + JptCredentialValidatorUtils::check_timeframes_and_revocation_with_validity_timeframe_2024( + &credential.0, + &issuer_guard, + validity_timeframe.map(|t| t.0), + status_check.into(), + ) + .wasm_result() + } +} diff --git a/bindings/wasm/src/credential/jpt_credential_validator/jwp_credential_options.rs b/bindings/wasm/src/credential/jpt_credential_validator/jwp_credential_options.rs new file mode 100644 index 0000000000..907e793996 --- /dev/null +++ b/bindings/wasm/src/credential/jpt_credential_validator/jwp_credential_options.rs @@ -0,0 +1,49 @@ +// Copyright 2020-2024 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +use crate::error::Result; +use crate::error::WasmResult; +use identity_iota::credential::JwpCredentialOptions; +use serde::Deserialize; +use serde::Serialize; +use wasm_bindgen::prelude::*; + +#[wasm_bindgen(js_name = JwpCredentialOptions, getter_with_clone, inspectable)] +#[derive(Serialize, Deserialize, Default)] +pub struct WasmJwpCredentialOptions { + pub kid: Option, +} + +#[wasm_bindgen(js_class = JwpCredentialOptions)] +impl WasmJwpCredentialOptions { + #[wasm_bindgen(constructor)] + pub fn new() -> WasmJwpCredentialOptions { + WasmJwpCredentialOptions::default() + } + + #[wasm_bindgen(js_name = fromJSON)] + pub fn from_json(value: JsValue) -> Result { + value.into_serde().wasm_result() + } + + #[wasm_bindgen(js_name = toJSON)] + pub fn to_json(&self) -> Result { + JsValue::from_serde(self).wasm_result() + } +} + +impl From for JwpCredentialOptions { + fn from(value: WasmJwpCredentialOptions) -> Self { + let WasmJwpCredentialOptions { kid } = value; + let mut jwp_options = JwpCredentialOptions::default(); + jwp_options.kid = kid; + + jwp_options + } +} + +impl From for WasmJwpCredentialOptions { + fn from(value: JwpCredentialOptions) -> Self { + WasmJwpCredentialOptions { kid: value.kid } + } +} diff --git a/bindings/wasm/src/credential/jpt_credential_validator/jwp_verification_options.rs b/bindings/wasm/src/credential/jpt_credential_validator/jwp_verification_options.rs new file mode 100644 index 0000000000..d7ef8b5b89 --- /dev/null +++ b/bindings/wasm/src/credential/jpt_credential_validator/jwp_verification_options.rs @@ -0,0 +1,48 @@ +// Copyright 2020-2024 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +use crate::error::Result; +use crate::error::WasmResult; +use identity_iota::document::verifiable::JwpVerificationOptions; +use wasm_bindgen::prelude::*; + +#[wasm_bindgen(js_name = JwpVerificationOptions, inspectable)] +#[derive(Clone, Debug, Default)] +pub struct WasmJwpVerificationOptions(pub(crate) JwpVerificationOptions); + +impl_wasm_clone!(WasmJwpVerificationOptions, JwpVerificationOptions); +impl_wasm_json!(WasmJwpVerificationOptions, JwpVerificationOptions); + +#[wasm_bindgen(js_class = JwpVerificationOptions)] +impl WasmJwpVerificationOptions { + pub fn new(opts: Option) -> Result { + if let Some(opts) = opts { + opts.into_serde().wasm_result().map(WasmJwpVerificationOptions) + } else { + Ok(WasmJwpVerificationOptions::default()) + } + } +} + +// Interface to allow creating {@link JwpVerificationOptions} easily. +#[wasm_bindgen] +extern "C" { + #[wasm_bindgen(typescript_type = "IJwpVerificationOptions")] + pub type IJwpVerificationOptions; +} + +#[wasm_bindgen(typescript_custom_section)] +const I_JWP_VERIFICATION_OPTIONS: &'static str = r#" +/** Holds options to create a new {@link JwpVerificationOptions}. */ +interface IJwpVerificationOptions { + /** + * Verify the signing verification method relation matches this. + */ + readonly methodScope?: MethodScope; + + /** + * The DID URL of the method, whose JWK should be used to verify the JWP. + * If unset, the `kid` of the JWP is used as the DID URL. + */ + readonly methodId?: DIDUrl; +}"#; diff --git a/bindings/wasm/src/credential/jpt_credential_validator/mod.rs b/bindings/wasm/src/credential/jpt_credential_validator/mod.rs new file mode 100644 index 0000000000..7da2b15114 --- /dev/null +++ b/bindings/wasm/src/credential/jpt_credential_validator/mod.rs @@ -0,0 +1,16 @@ +// Copyright 2020-2024 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +mod decoded_jpt_credential; +mod jpt_credential_validation_options; +mod jpt_credential_validator; +mod jpt_credential_validator_utils; +mod jwp_credential_options; +mod jwp_verification_options; + +pub use decoded_jpt_credential::*; +pub use jpt_credential_validation_options::*; +pub use jpt_credential_validator::*; +pub use jpt_credential_validator_utils::*; +pub use jwp_credential_options::*; +pub use jwp_verification_options::*; diff --git a/bindings/wasm/src/credential/jpt_presentiation_validation/decoded_jpt_presentation.rs b/bindings/wasm/src/credential/jpt_presentiation_validation/decoded_jpt_presentation.rs new file mode 100644 index 0000000000..698b9e3410 --- /dev/null +++ b/bindings/wasm/src/credential/jpt_presentiation_validation/decoded_jpt_presentation.rs @@ -0,0 +1,51 @@ +// Copyright 2020-2024 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +use identity_iota::core::Object; +use identity_iota::credential::DecodedJptPresentation; +use wasm_bindgen::prelude::*; + +use crate::common::MapStringAny; +use crate::credential::WasmCredential; +use crate::error::Result; + +#[wasm_bindgen(js_name = DecodedJptPresentation)] +pub struct WasmDecodedJptPresentation(pub(crate) DecodedJptPresentation); + +impl_wasm_clone!(WasmDecodedJptPresentation, DecodedJptPresentation); + +#[wasm_bindgen(js_class = DecodedJptPresentation)] +impl WasmDecodedJptPresentation { + /// Returns the {@link Credential} embedded into this JPT. + #[wasm_bindgen] + pub fn credential(&self) -> WasmCredential { + WasmCredential(self.0.credential.clone()) + } + + /// Returns the custom claims parsed from the JPT. + #[wasm_bindgen(js_name = "customClaims")] + pub fn custom_claims(&self) -> Result { + match self.0.custom_claims.clone() { + Some(obj) => MapStringAny::try_from(obj), + None => Ok(MapStringAny::default()), + } + } + + /// Returns the `aud` property parsed from the JWT claims. + #[wasm_bindgen] + pub fn aud(&self) -> Option { + self.0.aud.as_ref().map(ToString::to_string) + } +} + +impl From for WasmDecodedJptPresentation { + fn from(value: DecodedJptPresentation) -> Self { + WasmDecodedJptPresentation(value) + } +} + +impl From for DecodedJptPresentation { + fn from(value: WasmDecodedJptPresentation) -> Self { + value.0 + } +} diff --git a/bindings/wasm/src/credential/jpt_presentiation_validation/jpt_presentation_validation_options.rs b/bindings/wasm/src/credential/jpt_presentiation_validation/jpt_presentation_validation_options.rs new file mode 100644 index 0000000000..2576437ed4 --- /dev/null +++ b/bindings/wasm/src/credential/jpt_presentiation_validation/jpt_presentation_validation_options.rs @@ -0,0 +1,64 @@ +// Copyright 2020-2024 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +use identity_iota::credential::JptPresentationValidationOptions; +use wasm_bindgen::prelude::*; + +use crate::error::Result; +use crate::error::WasmResult; + +/// Options to declare validation criteria for a {@link Jpt} presentation. +#[derive(Debug, Default, Clone)] +#[wasm_bindgen(js_name = "JptPresentationValidationOptions", inspectable)] +pub struct WasmJptPresentationValidationOptions(pub(crate) JptPresentationValidationOptions); + +impl_wasm_clone!(WasmJptPresentationValidationOptions, JptPresentationValidationOptions); +impl_wasm_json!(WasmJptPresentationValidationOptions, JptPresentationValidationOptions); + +#[wasm_bindgen(js_class = JptPresentationValidationOptions)] +impl WasmJptPresentationValidationOptions { + #[wasm_bindgen(constructor)] + pub fn new(opts: Option) -> Result { + if let Some(opts) = opts { + opts + .into_serde() + .wasm_result() + .map(WasmJptPresentationValidationOptions) + } else { + Ok(WasmJptPresentationValidationOptions::default()) + } + } +} + +impl From for WasmJptPresentationValidationOptions { + fn from(value: JptPresentationValidationOptions) -> Self { + WasmJptPresentationValidationOptions(value) + } +} + +impl From for JptPresentationValidationOptions { + fn from(value: WasmJptPresentationValidationOptions) -> Self { + value.0 + } +} + +#[wasm_bindgen] +extern "C" { + #[wasm_bindgen(typescript_type = "IJptPresentationValidationOptions")] + pub type IJptPresentationValidationOptions; +} + +#[wasm_bindgen(typescript_custom_section)] +const I_JPT_PRESENTATION_VALIDATION_OPTIONS: &'static str = r#" +/** Holds options to create a new {@link JptPresentationValidationOptions}. */ +interface IJptPresentationValidationOptions { + /** + * The nonce to be placed in the Presentation Protected Header. + */ + readonly nonce?: string; + + /** + * Options which affect the verification of the proof on the credential. + */ + readonly verificationOptions?: JwpVerificationOptions; +}"#; diff --git a/bindings/wasm/src/credential/jpt_presentiation_validation/jpt_presentation_validator.rs b/bindings/wasm/src/credential/jpt_presentiation_validation/jpt_presentation_validator.rs new file mode 100644 index 0000000000..3843b48b81 --- /dev/null +++ b/bindings/wasm/src/credential/jpt_presentiation_validation/jpt_presentation_validator.rs @@ -0,0 +1,41 @@ +// Copyright 2020-2024 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +use crate::common::ImportedDocumentLock; +use crate::credential::WasmDecodedJptPresentation; +use crate::credential::WasmFailFast; +use crate::credential::WasmJpt; +use crate::credential::WasmJptPresentationValidationOptions; +use crate::did::IToCoreDocument; +use crate::error::Result; +use crate::error::WasmResult; +use identity_iota::credential::JptPresentationValidator; +use wasm_bindgen::prelude::*; + +#[wasm_bindgen(js_name = JptPresentationValidator)] +pub struct WasmJptPresentationValidator; + +#[wasm_bindgen(js_class = JptPresentationValidator)] +impl WasmJptPresentationValidator { + /// Decodes and validates a Presented {@link Credential} issued as a JPT (JWP Presented Form). A + /// {@link DecodedJptPresentation} is returned upon success. + /// + /// The following properties are validated according to `options`: + /// - the holder's proof on the JWP, + /// - the expiration date, + /// - the issuance date, + /// - the semantic structure. + #[wasm_bindgen] + pub fn validate( + presentation_jpt: &WasmJpt, + issuer: &IToCoreDocument, + options: &WasmJptPresentationValidationOptions, + fail_fast: WasmFailFast, + ) -> Result { + let issuer_lock = ImportedDocumentLock::from(issuer); + let issuer_guard = issuer_lock.try_read()?; + JptPresentationValidator::validate(&presentation_jpt.0, &issuer_guard, &options.0, fail_fast.into()) + .wasm_result() + .map(WasmDecodedJptPresentation) + } +} diff --git a/bindings/wasm/src/credential/jpt_presentiation_validation/jpt_presentation_validator_utils.rs b/bindings/wasm/src/credential/jpt_presentiation_validation/jpt_presentation_validator_utils.rs new file mode 100644 index 0000000000..d3d927b82f --- /dev/null +++ b/bindings/wasm/src/credential/jpt_presentiation_validation/jpt_presentation_validator_utils.rs @@ -0,0 +1,44 @@ +// Copyright 2020-2024 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +use crate::common::WasmTimestamp; +use crate::credential::options::WasmStatusCheck; +use crate::credential::WasmCredential; +use crate::credential::WasmJpt; +use crate::did::WasmCoreDID; +use crate::error::Result; +use crate::error::WasmResult; +use identity_iota::credential::JptPresentationValidatorUtils; +use wasm_bindgen::prelude::*; + +/// Utility functions for verifying JPT presentations. +#[wasm_bindgen(js_name = JptPresentationValidatorUtils)] +pub struct WasmJptPresentationValidatorUtils; + +#[wasm_bindgen(js_class = JptPresentationValidatorUtils)] +impl WasmJptPresentationValidatorUtils { + /// Utility for extracting the issuer field of a credential in JPT representation as DID. + /// # Errors + /// If the JPT decoding fails or the issuer field is not a valid DID. + #[wasm_bindgen(js_name = "extractIssuerFromPresentedJpt")] + pub fn extract_issuer_from_presented_jpt(presentation: &WasmJpt) -> Result { + JptPresentationValidatorUtils::extract_issuer_from_presented_jpt(&presentation.0) + .wasm_result() + .map(WasmCoreDID) + } + + /// Check timeframe interval in credentialStatus with `RevocationTimeframeStatus`. + #[wasm_bindgen(js_name = "checkTimeframesWithValidityTimeframe2024")] + pub fn check_timeframes_with_validity_timeframe_2024( + credential: &WasmCredential, + validity_timeframe: Option, + status_check: WasmStatusCheck, + ) -> Result<()> { + JptPresentationValidatorUtils::check_timeframes_with_validity_timeframe_2024( + &credential.0, + validity_timeframe.map(|t| t.0), + status_check.into(), + ) + .wasm_result() + } +} diff --git a/bindings/wasm/src/credential/jpt_presentiation_validation/jwp_presentation_options.rs b/bindings/wasm/src/credential/jpt_presentiation_validation/jwp_presentation_options.rs new file mode 100644 index 0000000000..7bc30851a5 --- /dev/null +++ b/bindings/wasm/src/credential/jpt_presentiation_validation/jwp_presentation_options.rs @@ -0,0 +1,37 @@ +// Copyright 2020-2024 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +use identity_iota::core::Url; +use identity_iota::credential::JwpPresentationOptions; +use wasm_bindgen::prelude::*; + +/// Options to be set in the JWT claims of a verifiable presentation. +#[wasm_bindgen(js_name = JwpPresentationOptions, inspectable, getter_with_clone)] +#[derive(Default, Clone)] +pub struct WasmJwpPresentationOptions { + /// Sets the audience for presentation (`aud` property in JWP Presentation Header). + pub audience: Option, + /// The nonce to be placed in the Presentation Protected Header. + pub nonce: Option, +} + +#[wasm_bindgen(js_class = JwpPresentationOptions)] +impl WasmJwpPresentationOptions { + #[wasm_bindgen(constructor)] + pub fn new() -> WasmJwpPresentationOptions { + Self::default() + } +} + +impl TryFrom for JwpPresentationOptions { + type Error = JsError; + fn try_from(value: WasmJwpPresentationOptions) -> Result { + let WasmJwpPresentationOptions { audience, nonce } = value; + let audience = audience + .map(Url::parse) + .transpose() + .map_err(|e| JsError::new(&e.to_string()))?; + + Ok(JwpPresentationOptions { audience, nonce }) + } +} diff --git a/bindings/wasm/src/credential/jpt_presentiation_validation/mod.rs b/bindings/wasm/src/credential/jpt_presentiation_validation/mod.rs new file mode 100644 index 0000000000..8a2663c85f --- /dev/null +++ b/bindings/wasm/src/credential/jpt_presentiation_validation/mod.rs @@ -0,0 +1,14 @@ +// Copyright 2020-2024 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +mod decoded_jpt_presentation; +mod jpt_presentation_validation_options; +mod jpt_presentation_validator; +mod jpt_presentation_validator_utils; +mod jwp_presentation_options; + +pub use decoded_jpt_presentation::*; +pub use jpt_presentation_validation_options::*; +pub use jpt_presentation_validator::*; +pub use jpt_presentation_validator_utils::*; +pub use jwp_presentation_options::*; diff --git a/bindings/wasm/src/credential/jwt_credential_validation/jwt_credential_validator.rs b/bindings/wasm/src/credential/jwt_credential_validation/jwt_credential_validator.rs index 9434a6d521..b6a26c35d5 100644 --- a/bindings/wasm/src/credential/jwt_credential_validation/jwt_credential_validator.rs +++ b/bindings/wasm/src/credential/jwt_credential_validation/jwt_credential_validator.rs @@ -37,11 +37,11 @@ pub struct WasmJwtCredentialValidator(JwtCredentialValidator); #[wasm_bindgen(js_class = JwtCredentialValidator)] impl WasmJwtCredentialValidator { /// Creates a new {@link JwtCredentialValidator}. If a `signatureVerifier` is provided it will be used when - /// verifying decoded JWS signatures, otherwise the default which is only capable of handling the `EdDSA` - /// algorithm will be used. + /// verifying decoded JWS signatures, otherwise a default verifier capable of handling the `EdDSA`, `ES256`, `ES256K` + /// algorithms will be used. #[wasm_bindgen(constructor)] #[allow(non_snake_case)] - pub fn new(signatureVerifier: IJwsVerifier) -> WasmJwtCredentialValidator { + pub fn new(signatureVerifier: Option) -> WasmJwtCredentialValidator { let signature_verifier = WasmJwsVerifier::new(signatureVerifier); WasmJwtCredentialValidator(JwtCredentialValidator::with_signature_verifier(signature_verifier)) } diff --git a/bindings/wasm/src/credential/jwt_credential_validation/sd_jwt_validator.rs b/bindings/wasm/src/credential/jwt_credential_validation/sd_jwt_validator.rs index 812b25414b..a8342c9aec 100644 --- a/bindings/wasm/src/credential/jwt_credential_validation/sd_jwt_validator.rs +++ b/bindings/wasm/src/credential/jwt_credential_validation/sd_jwt_validator.rs @@ -29,11 +29,11 @@ pub struct WasmSdJwtCredentialValidator(SdJwtCredentialValidator WasmSdJwtCredentialValidator { + pub fn new(signatureVerifier: Option) -> WasmSdJwtCredentialValidator { let signature_verifier = WasmJwsVerifier::new(signatureVerifier); WasmSdJwtCredentialValidator(SdJwtCredentialValidator::with_signature_verifier( signature_verifier, diff --git a/bindings/wasm/src/credential/jwt_presentation_validation/jwt_presentation_validator.rs b/bindings/wasm/src/credential/jwt_presentation_validation/jwt_presentation_validator.rs index 40a44f916b..640f96c2ef 100644 --- a/bindings/wasm/src/credential/jwt_presentation_validation/jwt_presentation_validator.rs +++ b/bindings/wasm/src/credential/jwt_presentation_validation/jwt_presentation_validator.rs @@ -23,11 +23,11 @@ pub struct WasmJwtPresentationValidator(JwtPresentationValidator WasmJwtPresentationValidator { + pub fn new(signatureVerifier: Option) -> WasmJwtPresentationValidator { let signature_verifier = WasmJwsVerifier::new(signatureVerifier); WasmJwtPresentationValidator(JwtPresentationValidator::with_signature_verifier(signature_verifier)) } diff --git a/bindings/wasm/src/credential/linked_verifiable_presentation_service.rs b/bindings/wasm/src/credential/linked_verifiable_presentation_service.rs new file mode 100644 index 0000000000..1033316cc7 --- /dev/null +++ b/bindings/wasm/src/credential/linked_verifiable_presentation_service.rs @@ -0,0 +1,109 @@ +// Copyright 2020-2023 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +use crate::common::ArrayString; +use crate::did::WasmService; +use crate::error::Result; +use crate::error::WasmResult; +use identity_iota::core::Object; +use identity_iota::core::OneOrSet; +use identity_iota::core::Url; +use identity_iota::credential::LinkedVerifiablePresentationService; +use identity_iota::did::DIDUrl; +use identity_iota::document::Service; +use proc_typescript::typescript; +use wasm_bindgen::prelude::wasm_bindgen; +use wasm_bindgen::prelude::*; +use wasm_bindgen::JsCast; + +#[wasm_bindgen(js_name = LinkedVerifiablePresentationService, inspectable)] +pub struct WasmLinkedVerifiablePresentationService(LinkedVerifiablePresentationService); + +/// A service wrapper for a [Linked Verifiable Presentation Service Endpoint](https://identity.foundation/linked-vp/#linked-verifiable-presentation-service-endpoint). +#[wasm_bindgen(js_class = LinkedVerifiablePresentationService)] +impl WasmLinkedVerifiablePresentationService { + /// Constructs a new {@link LinkedVerifiablePresentationService} that wraps a spec compliant [Linked Verifiable Presentation Service Endpoint](https://identity.foundation/linked-vp/#linked-verifiable-presentation-service-endpoint). + #[wasm_bindgen(constructor)] + pub fn new(options: ILinkedVerifiablePresentationService) -> Result { + let ILinkedVerifiablePresentationServiceHelper { + id, + linked_vp, + properties, + } = options + .into_serde::() + .wasm_result()?; + Ok(Self( + LinkedVerifiablePresentationService::new(id, linked_vp, properties).wasm_result()?, + )) + } + + /// Returns the domains contained in the Linked Verifiable Presentation Service. + #[wasm_bindgen(js_name = verifiablePresentationUrls)] + pub fn vp_urls(&self) -> ArrayString { + self + .0 + .verifiable_presentation_urls() + .iter() + .map(|url| url.to_string()) + .map(JsValue::from) + .collect::() + .unchecked_into::() + } + + /// Returns the inner service which can be added to a DID Document. + #[wasm_bindgen(js_name = toService)] + pub fn to_service(&self) -> WasmService { + let service: Service = self.0.clone().into(); + WasmService(service) + } + + /// Creates a new {@link LinkedVerifiablePresentationService} from a {@link Service}. + /// + /// # Error + /// + /// Errors if `service` is not a valid Linked Verifiable Presentation Service. + #[wasm_bindgen(js_name = fromService)] + pub fn from_service(service: &WasmService) -> Result { + Ok(Self( + LinkedVerifiablePresentationService::try_from(service.0.clone()).wasm_result()?, + )) + } + + /// Returns `true` if a {@link Service} is a valid Linked Verifiable Presentation Service. + #[wasm_bindgen(js_name = isValid)] + pub fn is_valid(service: &WasmService) -> bool { + LinkedVerifiablePresentationService::check_structure(&service.0).is_ok() + } +} + +#[wasm_bindgen] +extern "C" { + #[wasm_bindgen(typescript_type = "ILinkedVerifiablePresentationService")] + pub type ILinkedVerifiablePresentationService; +} + +/// Fields for constructing a new {@link LinkedVerifiablePresentationService}. +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +#[typescript(name = "ILinkedVerifiablePresentationService", readonly, optional)] +struct ILinkedVerifiablePresentationServiceHelper { + /// Service id. + #[typescript(optional = false, type = "DIDUrl")] + id: DIDUrl, + /// A unique URI that may be used to identify the {@link Credential}. + #[typescript(optional = false, type = "string | string[]")] + linked_vp: OneOrSet, + /// Miscellaneous properties. + #[serde(flatten)] + #[typescript(optional = false, name = "[properties: string]", type = "unknown")] + properties: Object, +} + +impl_wasm_clone!( + WasmLinkedVerifiablePresentationService, + LinkedVerifiablePresentationService +); +impl_wasm_json!( + WasmLinkedVerifiablePresentationService, + LinkedVerifiablePresentationService +); diff --git a/bindings/wasm/src/credential/mod.rs b/bindings/wasm/src/credential/mod.rs index 832eac1cd4..408f302f11 100644 --- a/bindings/wasm/src/credential/mod.rs +++ b/bindings/wasm/src/credential/mod.rs @@ -6,10 +6,14 @@ pub use self::credential::WasmCredential; pub use self::credential_builder::*; pub use self::domain_linkage_configuration::WasmDomainLinkageConfiguration; +pub use self::jpt::*; +pub use self::jpt_credential_validator::*; +pub use self::jpt_presentiation_validation::*; pub use self::jws::WasmJws; pub use self::jwt::WasmJwt; pub use self::jwt_credential_validation::*; pub use self::jwt_presentation_validation::*; +pub use self::linked_verifiable_presentation_service::*; pub use self::options::WasmFailFast; pub use self::options::WasmSubjectHolderRelationship; pub use self::presentation::*; @@ -22,11 +26,15 @@ mod credential_builder; mod domain_linkage_configuration; mod domain_linkage_credential_builder; mod domain_linkage_validator; +mod jpt; +mod jpt_credential_validator; +mod jpt_presentiation_validation; mod jws; mod jwt; mod jwt_credential_validation; mod jwt_presentation_validation; mod linked_domain_service; +mod linked_verifiable_presentation_service; mod options; mod presentation; mod proof; diff --git a/bindings/wasm/src/credential/revocation/mod.rs b/bindings/wasm/src/credential/revocation/mod.rs index 7ad04980b4..c0f075df39 100644 --- a/bindings/wasm/src/credential/revocation/mod.rs +++ b/bindings/wasm/src/credential/revocation/mod.rs @@ -2,3 +2,4 @@ // SPDX-License-Identifier: Apache-2.0 pub mod status_list_2021; +pub mod validity_timeframe_2024; diff --git a/bindings/wasm/src/credential/revocation/validity_timeframe_2024/mod.rs b/bindings/wasm/src/credential/revocation/validity_timeframe_2024/mod.rs new file mode 100644 index 0000000000..36474c70bb --- /dev/null +++ b/bindings/wasm/src/credential/revocation/validity_timeframe_2024/mod.rs @@ -0,0 +1,6 @@ +// Copyright 2020-2024 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +mod status; + +pub use status::*; diff --git a/bindings/wasm/src/credential/revocation/validity_timeframe_2024/status.rs b/bindings/wasm/src/credential/revocation/validity_timeframe_2024/status.rs new file mode 100644 index 0000000000..fb85bbeee3 --- /dev/null +++ b/bindings/wasm/src/credential/revocation/validity_timeframe_2024/status.rs @@ -0,0 +1,75 @@ +// Copyright 2020-2024 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +use identity_iota::core::Url; +use identity_iota::credential::RevocationTimeframeStatus; +use wasm_bindgen::prelude::*; + +use crate::common::WasmDuration; +use crate::common::WasmTimestamp; +use crate::error::Result; +use crate::error::WasmResult; + +/// Information used to determine the current status of a {@link Credential}. +#[wasm_bindgen(js_name = RevocationTimeframeStatus, inspectable)] +pub struct WasmRevocationTimeframeStatus(pub(crate) RevocationTimeframeStatus); + +impl_wasm_clone!(WasmRevocationTimeframeStatus, RevocationTimeframeStatus); +impl_wasm_json!(WasmRevocationTimeframeStatus, RevocationTimeframeStatus); + +#[wasm_bindgen(js_class = RevocationTimeframeStatus)] +impl WasmRevocationTimeframeStatus { + /// Creates a new `RevocationTimeframeStatus`. + #[wasm_bindgen(constructor)] + pub fn new( + id: String, + index: u32, + duration: WasmDuration, + start_validity: Option, + ) -> Result { + RevocationTimeframeStatus::new( + start_validity.map(|t| t.0), + duration.0, + Url::parse(id).wasm_result()?, + index, + ) + .wasm_result() + .map(WasmRevocationTimeframeStatus) + } + + /// Get startValidityTimeframe value. + #[wasm_bindgen(js_name = "startValidityTimeframe")] + pub fn start_validity_timeframe(&self) -> WasmTimestamp { + self.0.start_validity_timeframe().into() + } + + /// Get endValidityTimeframe value. + #[wasm_bindgen(js_name = "endValidityTimeframe")] + pub fn end_validity_timeframe(&self) -> WasmTimestamp { + self.0.end_validity_timeframe().into() + } + + /// Return the URL fo the `RevocationBitmapStatus`. + #[wasm_bindgen] + pub fn id(&self) -> String { + self.0.id().to_string() + } + + /// Return the index of the credential in the issuer's revocation bitmap + #[wasm_bindgen] + pub fn index(&self) -> Option { + self.0.index() + } +} + +impl From for WasmRevocationTimeframeStatus { + fn from(value: RevocationTimeframeStatus) -> Self { + WasmRevocationTimeframeStatus(value) + } +} + +impl From for RevocationTimeframeStatus { + fn from(value: WasmRevocationTimeframeStatus) -> Self { + value.0 + } +} diff --git a/bindings/wasm/src/did/did_jwk.rs b/bindings/wasm/src/did/did_jwk.rs new file mode 100644 index 0000000000..15ce291eca --- /dev/null +++ b/bindings/wasm/src/did/did_jwk.rs @@ -0,0 +1,105 @@ +// Copyright 2020-2024 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +use identity_iota::did::DIDJwk; +use identity_iota::did::DID as _; +use wasm_bindgen::prelude::*; + +use super::wasm_core_did::get_core_did_clone; +use super::IToCoreDID; +use super::WasmCoreDID; +use crate::error::Result; +use crate::error::WasmResult; +use crate::jose::WasmJwk; + +/// `did:jwk` DID. +#[wasm_bindgen(js_name = DIDJwk)] +pub struct WasmDIDJwk(pub(crate) DIDJwk); + +#[wasm_bindgen(js_class = DIDJwk)] +impl WasmDIDJwk { + #[wasm_bindgen(constructor)] + /// Creates a new {@link DIDJwk} from a {@link CoreDID}. + /// + /// ### Errors + /// Throws an error if the given did is not a valid `did:jwk` DID. + pub fn new(did: IToCoreDID) -> Result { + let did = get_core_did_clone(&did).0; + DIDJwk::try_from(did).wasm_result().map(Self) + } + /// Parses a {@link DIDJwk} from the given `input`. + /// + /// ### Errors + /// + /// Throws an error if the input is not a valid {@link DIDJwk}. + #[wasm_bindgen] + pub fn parse(input: &str) -> Result { + DIDJwk::parse(input).wasm_result().map(Self) + } + + /// Returns the JSON WEB KEY (JWK) encoded inside this `did:jwk`. + #[wasm_bindgen] + pub fn jwk(&self) -> WasmJwk { + self.0.jwk().into() + } + + // =========================================================================== + // DID trait + // =========================================================================== + + /// Returns the {@link CoreDID} scheme. + /// + /// E.g. + /// - `"did:example:12345678" -> "did"` + /// - `"did:iota:smr:12345678" -> "did"` + #[wasm_bindgen] + pub fn scheme(&self) -> String { + self.0.scheme().to_owned() + } + + /// Returns the {@link CoreDID} authority: the method name and method-id. + /// + /// E.g. + /// - `"did:example:12345678" -> "example:12345678"` + /// - `"did:iota:smr:12345678" -> "iota:smr:12345678"` + #[wasm_bindgen] + pub fn authority(&self) -> String { + self.0.authority().to_owned() + } + + /// Returns the {@link CoreDID} method name. + /// + /// E.g. + /// - `"did:example:12345678" -> "example"` + /// - `"did:iota:smr:12345678" -> "iota"` + #[wasm_bindgen] + pub fn method(&self) -> String { + self.0.method().to_owned() + } + + /// Returns the {@link CoreDID} method-specific ID. + /// + /// E.g. + /// - `"did:example:12345678" -> "12345678"` + /// - `"did:iota:smr:12345678" -> "smr:12345678"` + #[wasm_bindgen(js_name = methodId)] + pub fn method_id(&self) -> String { + self.0.method_id().to_owned() + } + + /// Returns the {@link CoreDID} as a string. + #[allow(clippy::inherent_to_string)] + #[wasm_bindgen(js_name = toString)] + pub fn to_string(&self) -> String { + self.0.to_string() + } + + // Only intended to be called internally. + #[wasm_bindgen(js_name = toCoreDid, skip_typescript)] + pub fn to_core_did(&self) -> WasmCoreDID { + WasmCoreDID(self.0.clone().into()) + } +} + +impl_wasm_json!(WasmDIDJwk, DIDJwk); +impl_wasm_clone!(WasmDIDJwk, DIDJwk); diff --git a/bindings/wasm/src/did/mod.rs b/bindings/wasm/src/did/mod.rs index b89db3edbf..ae2e89bc0c 100644 --- a/bindings/wasm/src/did/mod.rs +++ b/bindings/wasm/src/did/mod.rs @@ -1,6 +1,7 @@ // Copyright 2020-2023 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 +mod did_jwk; mod jws_verification_options; mod service; mod wasm_core_did; @@ -19,5 +20,6 @@ pub use self::wasm_core_document::PromiseJws; pub use self::wasm_core_document::PromiseJwt; pub use self::wasm_core_document::WasmCoreDocument; pub use self::wasm_did_url::WasmDIDUrl; +pub use did_jwk::*; pub use self::jws_verification_options::*; diff --git a/bindings/wasm/src/did/wasm_core_document.rs b/bindings/wasm/src/did/wasm_core_document.rs index 0bae16c048..fd66c4e7ca 100644 --- a/bindings/wasm/src/did/wasm_core_document.rs +++ b/bindings/wasm/src/did/wasm_core_document.rs @@ -24,6 +24,7 @@ use crate::credential::WasmJwt; use crate::credential::WasmPresentation; use crate::did::service::WasmService; use crate::did::wasm_did_url::WasmDIDUrl; +use crate::did::WasmDIDJwk; use crate::error::Result; use crate::error::WasmResult; use crate::jose::WasmDecodedJws; @@ -494,8 +495,9 @@ impl WasmCoreDocument { // =========================================================================== /// Decodes and verifies the provided JWS according to the passed `options` and `signatureVerifier`. - /// If no `signatureVerifier` argument is provided a default verifier will be used that is (only) capable of - /// verifying EdDSA signatures. + /// If a `signatureVerifier` is provided it will be used when + /// verifying decoded JWS signatures, otherwise a default verifier capable of handling the `EdDSA`, `ES256`, `ES256K` + /// algorithms will be used. /// /// Regardless of which options are passed the following conditions must be met in order for a verification attempt to /// take place. @@ -508,7 +510,7 @@ impl WasmCoreDocument { &self, jws: &WasmJws, options: &WasmJwsVerificationOptions, - signatureVerifier: IJwsVerifier, + signatureVerifier: Option, detachedPayload: Option, ) -> Result { let jws_verifier = WasmJwsVerifier::new(signatureVerifier); @@ -765,6 +767,12 @@ impl WasmCoreDocument { }); Ok(promise.unchecked_into()) } + + /// Creates a {@link CoreDocument} from the given {@link DIDJwk}. + #[wasm_bindgen(js_name = expandDIDJwk)] + pub fn expand_did_jwk(did: WasmDIDJwk) -> Result { + CoreDocument::expand_did_jwk(did.0).wasm_result().map(Self::from) + } } #[wasm_bindgen] @@ -835,6 +843,9 @@ extern "C" { #[wasm_bindgen(typescript_type = "Promise")] pub type PromiseJwt; + + #[wasm_bindgen(typescript_type = "Promise")] + pub type PromiseJpt; } #[wasm_bindgen(typescript_custom_section)] diff --git a/bindings/wasm/src/error.rs b/bindings/wasm/src/error.rs index d7e8dfa3d8..035e7838bf 100644 --- a/bindings/wasm/src/error.rs +++ b/bindings/wasm/src/error.rs @@ -126,6 +126,8 @@ macro_rules! impl_wasm_error_from_with_struct_name { } } +impl_wasm_error_from_with_struct_name!(jsonprooftoken::errors::CustomError); + // identity_iota::iota now has some errors where the error message does not include the source error's error message. // This is in compliance with the Rust error handling project group's recommendation: // * An error type with a source error should either return that error via source or include that source's error message diff --git a/bindings/wasm/src/iota/iota_document.rs b/bindings/wasm/src/iota/iota_document.rs index 8d004422ad..1747f82e6e 100644 --- a/bindings/wasm/src/iota/iota_document.rs +++ b/bindings/wasm/src/iota/iota_document.rs @@ -44,13 +44,16 @@ use crate::common::RecordStringAny; use crate::common::UDIDUrlQuery; use crate::common::UOneOrManyNumber; use crate::common::WasmTimestamp; +use crate::credential::PromiseJpt; use crate::credential::UnknownCredential; use crate::credential::WasmCredential; +use crate::credential::WasmJpt; +use crate::credential::WasmJwpCredentialOptions; +use crate::credential::WasmJwpPresentationOptions; use crate::credential::WasmJws; use crate::credential::WasmJwt; use crate::credential::WasmPresentation; use crate::did::CoreDocumentLock; - use crate::did::PromiseJws; use crate::did::PromiseJwt; use crate::did::WasmCoreDocument; @@ -65,6 +68,9 @@ use crate::iota::WasmIotaDocumentMetadata; use crate::iota::WasmStateMetadataEncoding; use crate::jose::WasmDecodedJws; use crate::jose::WasmJwsAlgorithm; +use crate::jpt::WasmJptClaims; +use crate::jpt::WasmProofAlgorithm; +use crate::jpt::WasmSelectiveDisclosurePresentation; use crate::storage::WasmJwsSignatureOptions; use crate::storage::WasmJwtPresentationOptions; use crate::storage::WasmStorage; @@ -75,6 +81,7 @@ use crate::verification::WasmJwsVerifier; use crate::verification::WasmMethodRelationship; use crate::verification::WasmMethodScope; use crate::verification::WasmVerificationMethod; +use identity_iota::storage::JwpDocumentExt; pub(crate) struct IotaDocumentLock(tokio::sync::RwLock); @@ -377,8 +384,9 @@ impl WasmIotaDocument { // =========================================================================== /// Decodes and verifies the provided JWS according to the passed `options` and `signatureVerifier`. - /// If no `signatureVerifier` argument is provided a default verifier will be used that is (only) capable of - /// verifying EdDSA signatures. + /// If a `signatureVerifier` is provided it will be used when + /// verifying decoded JWS signatures, otherwise a default verifier capable of handling the `EdDSA`, `ES256`, `ES256K` + /// algorithms will be used. /// /// Regardless of which options are passed the following conditions must be met in order for a verification attempt to /// take place. @@ -390,7 +398,7 @@ impl WasmIotaDocument { &self, jws: &WasmJws, options: &WasmJwsVerificationOptions, - signatureVerifier: IJwsVerifier, + signatureVerifier: Option, detachedPayload: Option, ) -> Result { let jws_verifier = WasmJwsVerifier::new(signatureVerifier); @@ -852,6 +860,140 @@ impl WasmIotaDocument { }); Ok(promise.unchecked_into()) } + + #[wasm_bindgen(js_name = generateMethodJwp)] + pub fn generate_method_jwp( + &self, + storage: &WasmStorage, + alg: WasmProofAlgorithm, + fragment: Option, + scope: WasmMethodScope, + ) -> Result { + let document_lock_clone: Rc = self.0.clone(); + let storage_clone: Rc = storage.0.clone(); + let promise: Promise = future_to_promise(async move { + let method_fragment: String = document_lock_clone + .write() + .await + .generate_method_jwp( + &storage_clone, + KeyType::from_static_str("BLS12381"), + alg.into(), + fragment.as_deref(), + scope.0, + ) + .await + .wasm_result()?; + Ok(JsValue::from(method_fragment)) + }); + + Ok(promise.unchecked_into()) + } + + #[wasm_bindgen(js_name = createIssuedJwp)] + pub fn create_issued_jwp( + &self, + storage: &WasmStorage, + fragment: String, + jpt_claims: WasmJptClaims, + options: WasmJwpCredentialOptions, + ) -> Result { + let document_lock_clone: Rc = self.0.clone(); + let jpt_claims = jpt_claims.into_serde().wasm_result()?; + let storage_clone: Rc = storage.0.clone(); + let options = options.into(); + let promise: Promise = future_to_promise(async move { + let jwp: String = document_lock_clone + .write() + .await + .create_issued_jwp(&storage_clone, fragment.as_str(), &jpt_claims, &options) + .await + .wasm_result()?; + Ok(JsValue::from(jwp)) + }); + + Ok(promise.unchecked_into()) + } + + #[wasm_bindgen(js_name = createPresentedJwp)] + pub fn create_presented_jwp( + &self, + presentation: WasmSelectiveDisclosurePresentation, + method_id: String, + options: WasmJwpPresentationOptions, + ) -> Result { + let document_lock_clone: Rc = self.0.clone(); + let options = options.try_into()?; + let promise: Promise = future_to_promise(async move { + let mut presentation = presentation.0; + let jwp: String = document_lock_clone + .write() + .await + .create_presented_jwp(&mut presentation, method_id.as_str(), &options) + .await + .wasm_result()?; + Ok(JsValue::from(jwp)) + }); + + Ok(promise.unchecked_into()) + } + + #[wasm_bindgen(js_name = createCredentialJpt)] + pub fn create_credential_jpt( + &self, + credential: WasmCredential, + storage: &WasmStorage, + fragment: String, + options: WasmJwpCredentialOptions, + custom_claims: Option, + ) -> Result { + let document_lock_clone: Rc = self.0.clone(); + let storage_clone: Rc = storage.0.clone(); + let options = options.into(); + let custom_claims = custom_claims.and_then(|claims| claims.into_serde().ok()); + let promise: Promise = future_to_promise(async move { + let jpt = document_lock_clone + .write() + .await + .create_credential_jpt( + &credential.0, + &storage_clone, + fragment.as_str(), + &options, + custom_claims, + ) + .await + .map(WasmJpt) + .wasm_result()?; + Ok(JsValue::from(jpt)) + }); + + Ok(promise.unchecked_into()) + } + + #[wasm_bindgen(js_name = createPresentationJpt)] + pub fn create_presentation_jpt( + &self, + presentation: WasmSelectiveDisclosurePresentation, + method_id: String, + options: WasmJwpPresentationOptions, + ) -> Result { + let document_lock_clone: Rc = self.0.clone(); + let options = options.try_into()?; + let promise: Promise = future_to_promise(async move { + let mut presentation = presentation.0; + let jpt = document_lock_clone + .write() + .await + .create_presentation_jpt(&mut presentation, method_id.as_str(), &options) + .await + .map(WasmJpt) + .wasm_result()?; + Ok(JsValue::from(jpt)) + }); + + Ok(promise.unchecked_into()) + } } impl From for WasmIotaDocument { diff --git a/bindings/wasm/src/jpt/encoding.rs b/bindings/wasm/src/jpt/encoding.rs new file mode 100644 index 0000000000..e36a5307a5 --- /dev/null +++ b/bindings/wasm/src/jpt/encoding.rs @@ -0,0 +1,29 @@ +// Copyright 2020-2024 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +use jsonprooftoken::encoding::SerializationType; +use wasm_bindgen::prelude::*; + +#[wasm_bindgen(js_name = SerializationType)] +pub enum WasmSerializationType { + COMPACT = 0, + JSON = 1, +} + +impl From for SerializationType { + fn from(value: WasmSerializationType) -> Self { + match value { + WasmSerializationType::COMPACT => SerializationType::COMPACT, + WasmSerializationType::JSON => SerializationType::JSON, + } + } +} + +impl From for WasmSerializationType { + fn from(value: SerializationType) -> Self { + match value { + SerializationType::COMPACT => WasmSerializationType::COMPACT, + SerializationType::JSON => WasmSerializationType::JSON, + } + } +} diff --git a/bindings/wasm/src/jpt/issuer_protected_header.rs b/bindings/wasm/src/jpt/issuer_protected_header.rs new file mode 100644 index 0000000000..4499d42b69 --- /dev/null +++ b/bindings/wasm/src/jpt/issuer_protected_header.rs @@ -0,0 +1,52 @@ +// Copyright 2020-2024 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +use crate::jpt::WasmProofAlgorithm; +use jsonprooftoken::jwp::header::IssuerProtectedHeader; +use wasm_bindgen::prelude::*; + +#[wasm_bindgen(js_name = IssuerProtectedHeader, getter_with_clone, inspectable)] +pub struct WasmIssuerProtectedHeader { + /// JWP type (JPT). + pub typ: Option, + /// Algorithm used for the JWP. + pub alg: WasmProofAlgorithm, + /// ID for the key used for the JWP. + pub kid: Option, + /// Not handled for now. Will be used in the future to resolve external claims + pub cid: Option, + /// Claims. + claims: Vec, +} + +#[wasm_bindgen(js_class = IssuerProtectedHeader)] +impl WasmIssuerProtectedHeader { + #[wasm_bindgen] + pub fn claims(&self) -> Vec { + self.claims.clone() + } +} + +impl From for IssuerProtectedHeader { + fn from(value: WasmIssuerProtectedHeader) -> Self { + let WasmIssuerProtectedHeader { typ, alg, kid, cid, .. } = value; + let mut header = IssuerProtectedHeader::new(alg.into()); + header.set_typ(typ); + header.set_kid(kid); + header.set_cid(cid); + + header + } +} + +impl From for WasmIssuerProtectedHeader { + fn from(value: IssuerProtectedHeader) -> Self { + WasmIssuerProtectedHeader { + typ: value.typ().cloned(), + alg: value.alg().into(), + kid: value.kid().cloned(), + cid: value.cid().cloned(), + claims: value.claims().map(|claims| claims.clone().0).unwrap_or_default(), + } + } +} diff --git a/bindings/wasm/src/jpt/jpt_claims.rs b/bindings/wasm/src/jpt/jpt_claims.rs new file mode 100644 index 0000000000..ae9a6e0822 --- /dev/null +++ b/bindings/wasm/src/jpt/jpt_claims.rs @@ -0,0 +1,31 @@ +// Copyright 2020-2024 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +use wasm_bindgen::prelude::*; + +#[wasm_bindgen] +extern "C" { + #[wasm_bindgen(typescript_type = "JptClaims")] + pub type WasmJptClaims; +} + +#[wasm_bindgen(typescript_custom_section)] +const I_JPT_CLAIMS: &'static str = r#" +/** JPT claims */ + +interface JptClaims { + /** Who issued the JWP*/ + readonly iss?: string; + /** Subject of the JPT. */ + readonly sub?: string; + /** Expiration time. */ + readonly exp?: number; + /** Issuance date. */ + readonly iat?: number; + /** Time before which the JPT MUST NOT be accepted */ + readonly nbf?: number; + /** Unique ID for the JPT. */ + readonly jti?: string; + /** Custom claims. */ + readonly [properties: string]: any; +}"#; diff --git a/bindings/wasm/src/jpt/jwp_issued.rs b/bindings/wasm/src/jpt/jwp_issued.rs new file mode 100644 index 0000000000..e2c3826621 --- /dev/null +++ b/bindings/wasm/src/jpt/jwp_issued.rs @@ -0,0 +1,50 @@ +// Copyright 2020-2024 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +use jsonprooftoken::jwp::issued::JwpIssued; +use wasm_bindgen::prelude::*; + +use super::WasmPayloads; +use super::WasmSerializationType; +use crate::error::Result; +use crate::error::WasmResult; +use crate::jpt::WasmIssuerProtectedHeader; + +#[wasm_bindgen(js_name = JwpIssued)] +pub struct WasmJwpIssued(pub(crate) JwpIssued); + +impl_wasm_json!(WasmJwpIssued, JwpIssued); +impl_wasm_clone!(WasmJwpIssued, JwpIssued); + +#[wasm_bindgen(js_class = JwpIssued)] +impl WasmJwpIssued { + #[wasm_bindgen] + pub fn encode(&self, serialization: WasmSerializationType) -> Result { + self.0.encode(serialization.into()).wasm_result() + } + + #[wasm_bindgen(js_name = "setProof")] + pub fn set_proof(&mut self, proof: &[u8]) { + self.0.set_proof(proof) + } + + #[wasm_bindgen(js_name = "getProof")] + pub fn get_proof(&self) -> Vec { + self.0.get_proof().to_owned() + } + + #[wasm_bindgen(js_name = "getPayloads")] + pub fn get_payloads(&self) -> WasmPayloads { + self.0.get_payloads().clone().into() + } + + #[wasm_bindgen(js_name = "setPayloads")] + pub fn set_payloads(&mut self, payloads: WasmPayloads) { + self.0.set_payloads(payloads.into()) + } + + #[wasm_bindgen(js_name = getIssuerProtectedHeader)] + pub fn get_issuer_protected_header(&self) -> WasmIssuerProtectedHeader { + self.0.get_issuer_protected_header().clone().into() + } +} diff --git a/bindings/wasm/src/jpt/jwp_presentation_builder.rs b/bindings/wasm/src/jpt/jwp_presentation_builder.rs new file mode 100644 index 0000000000..64797ee4cf --- /dev/null +++ b/bindings/wasm/src/jpt/jwp_presentation_builder.rs @@ -0,0 +1,83 @@ +// Copyright 2020-2024 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +use super::WasmJwpIssued; +use super::WasmPresentationProtectedHeader; +use crate::error::Result; +use crate::error::WasmResult; +use identity_iota::credential::SelectiveDisclosurePresentation; +use wasm_bindgen::prelude::*; + +/// Used to construct a JwpPresentedBuilder and handle the selective disclosure of attributes +/// - @context MUST NOT be blinded +/// - id MUST be blinded +/// - type MUST NOT be blinded +/// - issuer MUST NOT be blinded +/// - issuanceDate MUST be blinded (if Timeframe Revocation mechanism is used) +/// - expirationDate MUST be blinded (if Timeframe Revocation mechanism is used) +/// - credentialSubject (User have to choose which attribute must be blinded) +/// - credentialSchema MUST NOT be blinded +/// - credentialStatus MUST NOT be blinded +/// - refreshService MUST NOT be blinded (probably will be used for Timeslot Revocation mechanism) +/// - termsOfUse NO reason to use it in ZK VC (will be in any case blinded) +/// - evidence (User have to choose which attribute must be blinded) +#[wasm_bindgen(js_name = SelectiveDisclosurePresentation)] +pub struct WasmSelectiveDisclosurePresentation(pub(crate) SelectiveDisclosurePresentation); + +impl From for SelectiveDisclosurePresentation { + fn from(value: WasmSelectiveDisclosurePresentation) -> Self { + value.0 + } +} + +impl From for WasmSelectiveDisclosurePresentation { + fn from(value: SelectiveDisclosurePresentation) -> Self { + WasmSelectiveDisclosurePresentation(value) + } +} + +#[wasm_bindgen(js_class = SelectiveDisclosurePresentation)] +impl WasmSelectiveDisclosurePresentation { + /// Initialize a presentation starting from an Issued JWP. + /// The properties `jti`, `nbf`, `issuanceDate`, `expirationDate` and `termsOfUse` are concealed by default. + #[wasm_bindgen(constructor)] + pub fn new(issued_jwp: WasmJwpIssued) -> WasmSelectiveDisclosurePresentation { + SelectiveDisclosurePresentation::new(&issued_jwp.0).into() + } + + /// Selectively disclose "credentialSubject" attributes. + /// # Example + /// ``` + /// { + /// "id": 1234, + /// "name": "Alice", + /// "mainCourses": ["Object-oriented Programming", "Mathematics"], + /// "degree": { + /// "type": "BachelorDegree", + /// "name": "Bachelor of Science and Arts", + /// }, + /// "GPA": "4.0", + /// } + /// ``` + /// If you want to undisclose for example the Mathematics course and the name of the degree: + /// ``` + /// undisclose_subject("mainCourses[1]"); + /// undisclose_subject("degree.name"); + /// ``` + #[wasm_bindgen(js_name = concealInSubject)] + pub fn conceal_in_subject(&mut self, path: String) -> Result<()> { + self.0.conceal_in_subject(&path).wasm_result() + } + + /// Undiscloses "evidence" attributes. + #[wasm_bindgen(js_name = concealInEvidence)] + pub fn conceal_in_evidence(&mut self, path: String) -> Result<()> { + self.0.conceal_in_evidence(&path).wasm_result() + } + + /// Sets presentation protected header. + #[wasm_bindgen(js_name = setPresentationHeader)] + pub fn set_presentation_header(&mut self, header: WasmPresentationProtectedHeader) { + self.0.set_presentation_header(header.into()) + } +} diff --git a/bindings/wasm/src/jpt/mod.rs b/bindings/wasm/src/jpt/mod.rs new file mode 100644 index 0000000000..631572003b --- /dev/null +++ b/bindings/wasm/src/jpt/mod.rs @@ -0,0 +1,20 @@ +// Copyright 2020-2024 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +mod encoding; +mod issuer_protected_header; +mod jpt_claims; +mod jwp_issued; +mod jwp_presentation_builder; +mod payload; +mod presentation_protected_header; +mod proof_algorithm; + +pub use encoding::*; +pub use issuer_protected_header::*; +pub use jpt_claims::*; +pub use jwp_issued::*; +pub use jwp_presentation_builder::*; +pub use payload::*; +pub use presentation_protected_header::*; +pub use proof_algorithm::*; diff --git a/bindings/wasm/src/jpt/payload.rs b/bindings/wasm/src/jpt/payload.rs new file mode 100644 index 0000000000..cdb06638b3 --- /dev/null +++ b/bindings/wasm/src/jpt/payload.rs @@ -0,0 +1,151 @@ +// Copyright 2020-2024 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +use crate::error::Result; +use crate::error::WasmError; +use crate::error::WasmResult; +use jsonprooftoken::jpt::payloads::PayloadType; +use jsonprooftoken::jpt::payloads::Payloads; +use serde_json::Value; +use std::borrow::Cow; +use wasm_bindgen::prelude::*; +use wasm_bindgen::JsValue; + +#[wasm_bindgen(js_name = PayloadType)] +#[derive(Clone, Copy, Debug)] +pub enum WasmPayloadType { + Disclosed = 0, + Undisclosed = 1, + ProofMethods = 2, +} + +impl From for PayloadType { + fn from(value: WasmPayloadType) -> PayloadType { + match value { + WasmPayloadType::Disclosed => PayloadType::Disclosed, + WasmPayloadType::ProofMethods => PayloadType::ProofMethods, + WasmPayloadType::Undisclosed => PayloadType::Undisclosed, + } + } +} + +impl From for WasmPayloadType { + fn from(value: PayloadType) -> WasmPayloadType { + match value { + PayloadType::Disclosed => WasmPayloadType::Disclosed, + PayloadType::ProofMethods => WasmPayloadType::ProofMethods, + PayloadType::Undisclosed => WasmPayloadType::Undisclosed, + } + } +} + +#[wasm_bindgen(js_name = PayloadEntry)] +pub struct WasmPayloadEntry(JsValue, pub WasmPayloadType); + +#[wasm_bindgen(js_class = PayloadEntry)] +impl WasmPayloadEntry { + #[wasm_bindgen(setter)] + pub fn set_value(&mut self, value: JsValue) { + self.0 = value; + } + #[wasm_bindgen(getter)] + pub fn value(&self) -> JsValue { + self.0.clone() + } +} + +#[wasm_bindgen(js_name = Payloads, inspectable)] +pub struct WasmPayloads(pub(crate) Payloads); + +impl_wasm_json!(WasmPayloads, Payloads); +impl_wasm_clone!(WasmPayloads, Payloads); + +#[wasm_bindgen(js_class = Payloads)] +impl WasmPayloads { + #[wasm_bindgen(constructor)] + pub fn new(entries: Vec) -> Result { + entries + .into_iter() + .map(|WasmPayloadEntry(value, type_)| value.into_serde().wasm_result().map(|value| (value, type_.into()))) + .collect::>>() + .map(Payloads) + .map(WasmPayloads) + } + + #[wasm_bindgen(js_name = newFromValues)] + pub fn new_from_values(values: Vec) -> Result { + let values = values + .into_iter() + .map(|v| v.into_serde().wasm_result()) + .collect::>>()?; + + Ok(Payloads::new_from_values(values).into()) + } + + #[wasm_bindgen(js_name = "getValues")] + pub fn get_values(&self) -> Result> { + self + .0 + .get_values() + .into_iter() + .map(|value| JsValue::from_serde(&value).wasm_result()) + .collect() + } + + #[wasm_bindgen(js_name = "getUndisclosedIndexes")] + pub fn get_undisclosed_indexes(&self) -> Vec { + self.0.get_undisclosed_indexes() + } + + #[wasm_bindgen(js_name = "getDisclosedIndexes")] + pub fn get_disclosed_indexes(&self) -> Vec { + self.0.get_disclosed_indexes() + } + + #[wasm_bindgen(js_name = "getUndisclosedPayloads")] + pub fn get_undisclosed_payloads(&self) -> Result> { + self + .0 + .get_undisclosed_payloads() + .into_iter() + .map(|value| JsValue::from_serde(&value).wasm_result()) + .collect() + } + + #[wasm_bindgen(js_name = "getDisclosedPayloads")] + pub fn get_disclosed_payloads(&self) -> WasmPayloads { + self.0.get_disclosed_payloads().into() + } + + #[wasm_bindgen(js_name = "setUndisclosed")] + pub fn set_undisclosed(&mut self, index: usize) { + self.0.set_undisclosed(index) + } + + #[wasm_bindgen(js_name = "replacePayloadAtIndex")] + pub fn replace_payload_at_index(&mut self, index: usize, value: JsValue) -> Result { + let value = value.into_serde().wasm_result()?; + self + .0 + .replace_payload_at_index(index, value) + .map_err(|_| { + JsValue::from(WasmError::new( + Cow::Borrowed("Index out of bounds"), + Cow::Borrowed("The provided index exceeds the array's bounds"), + )) + }) + .and_then(|v| JsValue::from_serde(&v).wasm_result()) + } +} + +impl From for WasmPayloads { + fn from(value: Payloads) -> Self { + WasmPayloads(value) + } +} + +impl From for Payloads { + fn from(value: WasmPayloads) -> Self { + value.0 + } +} diff --git a/bindings/wasm/src/jpt/presentation_protected_header.rs b/bindings/wasm/src/jpt/presentation_protected_header.rs new file mode 100644 index 0000000000..398870da4c --- /dev/null +++ b/bindings/wasm/src/jpt/presentation_protected_header.rs @@ -0,0 +1,86 @@ +// Copyright 2020-2024 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +use jsonprooftoken::jpa::algs::PresentationProofAlgorithm; +use jsonprooftoken::jwp::header::PresentationProtectedHeader; +use wasm_bindgen::prelude::*; + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +#[wasm_bindgen(js_name = PresentationProofAlgorithm)] +#[allow(non_camel_case_types)] +pub enum WasmPresentationProofAlgorithm { + BLS12381_SHA256_PROOF, + BLS12381_SHAKE256_PROOF, + SU_ES256, + MAC_H256, + MAC_H384, + MAC_H512, + MAC_K25519, + MAC_K448, + MAC_H256K, +} + +impl From for PresentationProofAlgorithm { + fn from(value: WasmPresentationProofAlgorithm) -> Self { + match value { + WasmPresentationProofAlgorithm::BLS12381_SHA256_PROOF => PresentationProofAlgorithm::BLS12381_SHA256_PROOF, + WasmPresentationProofAlgorithm::BLS12381_SHAKE256_PROOF => PresentationProofAlgorithm::BLS12381_SHAKE256_PROOF, + WasmPresentationProofAlgorithm::SU_ES256 => PresentationProofAlgorithm::SU_ES256, + WasmPresentationProofAlgorithm::MAC_H256 => PresentationProofAlgorithm::MAC_H256, + WasmPresentationProofAlgorithm::MAC_H384 => PresentationProofAlgorithm::MAC_H384, + WasmPresentationProofAlgorithm::MAC_H512 => PresentationProofAlgorithm::MAC_H512, + WasmPresentationProofAlgorithm::MAC_K25519 => PresentationProofAlgorithm::MAC_K25519, + WasmPresentationProofAlgorithm::MAC_K448 => PresentationProofAlgorithm::MAC_K448, + WasmPresentationProofAlgorithm::MAC_H256K => PresentationProofAlgorithm::MAC_H256K, + } + } +} + +impl From for WasmPresentationProofAlgorithm { + fn from(value: PresentationProofAlgorithm) -> Self { + match value { + PresentationProofAlgorithm::BLS12381_SHA256_PROOF => WasmPresentationProofAlgorithm::BLS12381_SHA256_PROOF, + PresentationProofAlgorithm::BLS12381_SHAKE256_PROOF => WasmPresentationProofAlgorithm::BLS12381_SHAKE256_PROOF, + PresentationProofAlgorithm::SU_ES256 => WasmPresentationProofAlgorithm::SU_ES256, + PresentationProofAlgorithm::MAC_H256 => WasmPresentationProofAlgorithm::MAC_H256, + PresentationProofAlgorithm::MAC_H384 => WasmPresentationProofAlgorithm::MAC_H384, + PresentationProofAlgorithm::MAC_H512 => WasmPresentationProofAlgorithm::MAC_H512, + PresentationProofAlgorithm::MAC_K25519 => WasmPresentationProofAlgorithm::MAC_K25519, + PresentationProofAlgorithm::MAC_K448 => WasmPresentationProofAlgorithm::MAC_K448, + PresentationProofAlgorithm::MAC_H256K => WasmPresentationProofAlgorithm::MAC_H256K, + } + } +} + +#[wasm_bindgen(js_name = PresentationProtectedHeader, inspectable, getter_with_clone)] +pub struct WasmPresentationProtectedHeader { + pub alg: WasmPresentationProofAlgorithm, + /// ID for the key used for the JWP. + pub kid: Option, + /// Who have received the JPT. + pub aud: Option, + /// For replay attacks. + pub nonce: Option, +} + +impl From for PresentationProtectedHeader { + fn from(value: WasmPresentationProtectedHeader) -> Self { + let WasmPresentationProtectedHeader { alg, kid, aud, nonce } = value; + let mut protected_header = PresentationProtectedHeader::new(alg.into()); + protected_header.set_kid(kid); + protected_header.set_aud(aud); + protected_header.set_nonce(nonce); + protected_header + } +} + +impl From for WasmPresentationProtectedHeader { + fn from(value: PresentationProtectedHeader) -> Self { + let alg = value.alg().into(); + let kid = value.kid().cloned(); + let aud = value.aud().cloned(); + let nonce = value.nonce().cloned(); + + WasmPresentationProtectedHeader { alg, kid, aud, nonce } + } +} diff --git a/bindings/wasm/src/jpt/proof_algorithm.rs b/bindings/wasm/src/jpt/proof_algorithm.rs new file mode 100644 index 0000000000..0f7f6986f1 --- /dev/null +++ b/bindings/wasm/src/jpt/proof_algorithm.rs @@ -0,0 +1,52 @@ +// Copyright 2020-2024 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +use jsonprooftoken::jpa::algs::ProofAlgorithm; +use wasm_bindgen::prelude::*; + +#[allow(non_camel_case_types)] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +#[wasm_bindgen(js_name = ProofAlgorithm)] +pub enum WasmProofAlgorithm { + BLS12381_SHA256, + BLS12381_SHAKE256, + SU_ES256, + MAC_H256, + MAC_H384, + MAC_H512, + MAC_K25519, + MAC_K448, + MAC_H256K, +} + +impl From for WasmProofAlgorithm { + fn from(value: ProofAlgorithm) -> Self { + match value { + ProofAlgorithm::BLS12381_SHA256 => WasmProofAlgorithm::BLS12381_SHA256, + ProofAlgorithm::BLS12381_SHAKE256 => WasmProofAlgorithm::BLS12381_SHAKE256, + ProofAlgorithm::SU_ES256 => WasmProofAlgorithm::SU_ES256, + ProofAlgorithm::MAC_H256 => WasmProofAlgorithm::MAC_H256, + ProofAlgorithm::MAC_H384 => WasmProofAlgorithm::MAC_H384, + ProofAlgorithm::MAC_H512 => WasmProofAlgorithm::MAC_H512, + ProofAlgorithm::MAC_K25519 => WasmProofAlgorithm::MAC_K25519, + ProofAlgorithm::MAC_K448 => WasmProofAlgorithm::MAC_K448, + ProofAlgorithm::MAC_H256K => WasmProofAlgorithm::MAC_H256K, + } + } +} + +impl From for ProofAlgorithm { + fn from(value: WasmProofAlgorithm) -> Self { + match value { + WasmProofAlgorithm::BLS12381_SHA256 => ProofAlgorithm::BLS12381_SHA256, + WasmProofAlgorithm::BLS12381_SHAKE256 => ProofAlgorithm::BLS12381_SHAKE256, + WasmProofAlgorithm::SU_ES256 => ProofAlgorithm::SU_ES256, + WasmProofAlgorithm::MAC_H256 => ProofAlgorithm::MAC_H256, + WasmProofAlgorithm::MAC_H384 => ProofAlgorithm::MAC_H384, + WasmProofAlgorithm::MAC_H512 => ProofAlgorithm::MAC_H512, + WasmProofAlgorithm::MAC_K25519 => ProofAlgorithm::MAC_K25519, + WasmProofAlgorithm::MAC_K448 => ProofAlgorithm::MAC_K448, + WasmProofAlgorithm::MAC_H256K => ProofAlgorithm::MAC_H256K, + } + } +} diff --git a/bindings/wasm/src/lib.rs b/bindings/wasm/src/lib.rs index 208edca0d0..cf8344925a 100644 --- a/bindings/wasm/src/lib.rs +++ b/bindings/wasm/src/lib.rs @@ -24,6 +24,7 @@ pub mod did; pub mod error; pub mod iota; pub mod jose; +pub mod jpt; pub mod resolver; pub mod revocation; pub mod sd_jwt; diff --git a/bindings/wasm/src/sd_jwt/encoder.rs b/bindings/wasm/src/sd_jwt/encoder.rs index 550742b42b..6e912e885e 100644 --- a/bindings/wasm/src/sd_jwt/encoder.rs +++ b/bindings/wasm/src/sd_jwt/encoder.rs @@ -47,7 +47,6 @@ impl WasmSdObjectEncoder { /// Path "/id" conceals `"id": "did:value"` /// Path "/claim1/abc" conceals `"abc": true` /// Path "/claim2/0" conceals `val_1` - /// ``` /// /// ## Errors /// * `InvalidPath` if pointer is invalid. diff --git a/bindings/wasm/src/storage/jpt_timeframe_revocation_ext.rs b/bindings/wasm/src/storage/jpt_timeframe_revocation_ext.rs new file mode 100644 index 0000000000..9529128e20 --- /dev/null +++ b/bindings/wasm/src/storage/jpt_timeframe_revocation_ext.rs @@ -0,0 +1,69 @@ +// Copyright 2020-2024 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +use identity_iota::storage::ProofUpdateCtx; +use wasm_bindgen::prelude::*; + +#[wasm_bindgen(js_name = ProofUpdateCtx, inspectable, getter_with_clone)] +pub struct WasmProofUpdateCtx { + /// Old `startValidityTimeframe` value + pub old_start_validity_timeframe: Vec, + /// New `startValidityTimeframe` value to be signed + pub new_start_validity_timeframe: Vec, + /// Old `endValidityTimeframe` value + pub old_end_validity_timeframe: Vec, + /// New `endValidityTimeframe` value to be signed + pub new_end_validity_timeframe: Vec, + /// Index of `startValidityTimeframe` claim inside the array of Claims + pub index_start_validity_timeframe: usize, + /// Index of `endValidityTimeframe` claim inside the array of Claims + pub index_end_validity_timeframe: usize, + /// Number of signed messages, number of payloads in a JWP + pub number_of_signed_messages: usize, +} + +impl From for WasmProofUpdateCtx { + fn from(value: ProofUpdateCtx) -> Self { + let ProofUpdateCtx { + old_start_validity_timeframe, + new_start_validity_timeframe, + old_end_validity_timeframe, + new_end_validity_timeframe, + index_start_validity_timeframe, + index_end_validity_timeframe, + number_of_signed_messages, + } = value; + Self { + old_start_validity_timeframe, + new_start_validity_timeframe, + old_end_validity_timeframe, + new_end_validity_timeframe, + index_start_validity_timeframe, + index_end_validity_timeframe, + number_of_signed_messages, + } + } +} + +impl From for ProofUpdateCtx { + fn from(value: WasmProofUpdateCtx) -> Self { + let WasmProofUpdateCtx { + old_start_validity_timeframe, + new_start_validity_timeframe, + old_end_validity_timeframe, + new_end_validity_timeframe, + index_start_validity_timeframe, + index_end_validity_timeframe, + number_of_signed_messages, + } = value; + Self { + old_start_validity_timeframe, + new_start_validity_timeframe, + old_end_validity_timeframe, + new_end_validity_timeframe, + index_start_validity_timeframe, + index_end_validity_timeframe, + number_of_signed_messages, + } + } +} diff --git a/bindings/wasm/src/storage/jwk_storage.rs b/bindings/wasm/src/storage/jwk_storage.rs index 4616def075..6adf78845b 100644 --- a/bindings/wasm/src/storage/jwk_storage.rs +++ b/bindings/wasm/src/storage/jwk_storage.rs @@ -50,6 +50,9 @@ extern "C" { #[wasm_bindgen(method)] pub fn exists(this: &WasmJwkStorage, key_id: String) -> PromiseBool; + + #[wasm_bindgen(method)] + pub(crate) fn _get_key(this: &WasmJwkStorage, key_id: &str) -> Option; } #[async_trait::async_trait(?Send)] diff --git a/bindings/wasm/src/storage/jwk_storage_bbs_plus_ext.rs b/bindings/wasm/src/storage/jwk_storage_bbs_plus_ext.rs new file mode 100644 index 0000000000..d92f12e607 --- /dev/null +++ b/bindings/wasm/src/storage/jwk_storage_bbs_plus_ext.rs @@ -0,0 +1,132 @@ +// Copyright 2020-2024 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +use std::str::FromStr; + +use crate::error::Result as WasmResult; +use crate::error::WasmResult as _; +use crate::jose::WasmJwk; +use crate::jpt::WasmProofAlgorithm; + +use super::WasmJwkGenOutput; +use super::WasmJwkStorage; +use super::WasmProofUpdateCtx; + +use identity_iota::storage::bls::encode_bls_jwk; +use identity_iota::storage::bls::expand_bls_jwk; +use identity_iota::storage::bls::generate_bbs_keypair; +use identity_iota::storage::bls::sign_bbs; +use identity_iota::storage::bls::update_bbs_signature; +use identity_iota::storage::JwkGenOutput; +use identity_iota::storage::JwkStorage; +use identity_iota::storage::JwkStorageBbsPlusExt; +use identity_iota::storage::KeyId; +use identity_iota::storage::KeyStorageError; +use identity_iota::storage::KeyStorageErrorKind; +use identity_iota::storage::KeyStorageResult; +use identity_iota::storage::KeyType; +use identity_iota::storage::ProofUpdateCtx; +use identity_iota::verification::jwk::Jwk; +use jsonprooftoken::jpa::algs::ProofAlgorithm; +use wasm_bindgen::prelude::*; + +#[wasm_bindgen(js_class = JwkStorage)] +impl WasmJwkStorage { + #[wasm_bindgen(js_name = generateBBS)] + /// Generates a new BBS+ keypair. + pub async fn _generate_bbs(&self, alg: WasmProofAlgorithm) -> WasmResult { + self + .generate_bbs(KeyType::from_static_str("BLS12381"), alg.into()) + .await + .map(WasmJwkGenOutput::from) + .wasm_result() + } + + #[wasm_bindgen(js_name = signBBS)] + pub async fn _sign_bbs( + &self, + key_id: String, + data: Vec, + public_key: WasmJwk, + header: Option>, + ) -> WasmResult { + let key_id = KeyId::new(key_id); + let data = data.into_iter().map(|arr| arr.to_vec()).collect::>(); + let header = header.unwrap_or_default(); + self + .sign_bbs(&key_id, &data, header.as_slice(), &public_key.into()) + .await + .map(|v| js_sys::Uint8Array::from(v.as_slice())) + .wasm_result() + } + + #[wasm_bindgen(js_name = updateBBSSignature)] + pub async fn _update_signature( + &self, + key_id: String, + public_key: &WasmJwk, + signature: Vec, + ctx: WasmProofUpdateCtx, + ) -> WasmResult { + let key_id = KeyId::new(key_id); + self + .update_signature(&key_id, &public_key.0, &signature, ctx.into()) + .await + .map(|sig| js_sys::Uint8Array::from(sig.as_slice())) + .wasm_result() + } +} + +#[async_trait::async_trait(?Send)] +impl JwkStorageBbsPlusExt for WasmJwkStorage { + async fn generate_bbs(&self, _key_type: KeyType, alg: ProofAlgorithm) -> KeyStorageResult { + let (sk, pk) = generate_bbs_keypair(alg)?; + + let (jwk, public_jwk) = encode_bls_jwk(&sk, &pk, alg); + let kid = ::insert(self, jwk).await?; + + Ok(JwkGenOutput::new(kid, public_jwk)) + } + async fn sign_bbs( + &self, + key_id: &KeyId, + data: &[Vec], + header: &[u8], + public_key: &Jwk, + ) -> KeyStorageResult> { + let Some(private_jwk) = WasmJwkStorage::_get_key(self, key_id.as_str()).map(Jwk::from) else { + return Err(KeyStorageError::new(KeyStorageErrorKind::KeyNotFound)); + }; + // Extract the required alg from the given public key + let alg = public_key + .alg() + .ok_or(KeyStorageErrorKind::UnsupportedProofAlgorithm) + .and_then(|alg_str| { + ProofAlgorithm::from_str(alg_str).map_err(|_| KeyStorageErrorKind::UnsupportedProofAlgorithm) + })?; + + let (sk, pk) = expand_bls_jwk(&private_jwk)?; + sign_bbs(alg, data, &sk.expect("jwk was private"), &pk, header) + } + async fn update_signature( + &self, + key_id: &KeyId, + public_key: &Jwk, + signature: &[u8], + ctx: ProofUpdateCtx, + ) -> KeyStorageResult> { + // Extract the required alg from the given public key + let alg = public_key + .alg() + .ok_or(KeyStorageErrorKind::UnsupportedProofAlgorithm) + .and_then(|alg_str| { + ProofAlgorithm::from_str(alg_str).map_err(|_| KeyStorageErrorKind::UnsupportedProofAlgorithm) + })?; + + let Some(private_jwk) = WasmJwkStorage::_get_key(self, key_id.as_str()).map(Jwk::from) else { + return Err(KeyStorageError::new(KeyStorageErrorKind::KeyNotFound)); + }; + let sk = expand_bls_jwk(&private_jwk)?.0.expect("jwk is private"); + update_bbs_signature(alg, signature, &sk, &ctx) + } +} diff --git a/bindings/wasm/src/storage/mod.rs b/bindings/wasm/src/storage/mod.rs index 8295d95e88..fe54110e9d 100644 --- a/bindings/wasm/src/storage/mod.rs +++ b/bindings/wasm/src/storage/mod.rs @@ -1,14 +1,17 @@ // Copyright 2020-2023 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 +mod jpt_timeframe_revocation_ext; mod jwk_gen_output; mod jwk_storage; +mod jwk_storage_bbs_plus_ext; mod jwt_presentation_options; mod key_id_storage; mod method_digest; mod signature_options; mod wasm_storage; +pub use jpt_timeframe_revocation_ext::*; pub use jwk_gen_output::*; pub use jwk_storage::*; pub use jwt_presentation_options::*; diff --git a/bindings/wasm/src/verification/custom_verification.rs b/bindings/wasm/src/verification/custom_verification.rs index 4c82d8dcfe..9fe2f9e8b7 100644 --- a/bindings/wasm/src/verification/custom_verification.rs +++ b/bindings/wasm/src/verification/custom_verification.rs @@ -1,6 +1,9 @@ // Copyright 2020-2023 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 +use identity_ecdsa_verifier::EcDSAJwsVerifier; +use identity_eddsa_verifier::EdDSAJwsVerifier; +use identity_iota::verification::jws::JwsAlgorithm; use identity_iota::verification::jws::JwsVerifier; use identity_iota::verification::jws::SignatureVerificationError; use identity_iota::verification::jws::SignatureVerificationErrorKind; @@ -10,12 +13,12 @@ use wasm_bindgen::prelude::*; use crate::jose::WasmJwk; /// Wrapper that enables custom TS JWS signature verification plugins to be used where the -/// JwsVerifier trait is required. Falls back to the default implementation if a custom -/// implementation was not passed. -pub(crate) struct WasmJwsVerifier(IJwsVerifier); +/// JwsVerifier trait is required. Falls back to the default implementation capable of handling +/// EdDSA (ED25519), ES256, ES256K if a custom implementation is not passed. +pub(crate) struct WasmJwsVerifier(Option); impl WasmJwsVerifier { - pub(crate) fn new(verifier: IJwsVerifier) -> Self { + pub(crate) fn new(verifier: Option) -> Self { Self(verifier) } } @@ -26,22 +29,30 @@ impl JwsVerifier for WasmJwsVerifier { input: identity_iota::verification::jws::VerificationInput, public_key: &identity_iota::verification::jwk::Jwk, ) -> Result<(), identity_iota::verification::jws::SignatureVerificationError> { - let VerificationInput { - alg, - signing_input, - decoded_signature, - } = input; - let verification_result = IJwsVerifier::verify( - &self.0, - alg.name().to_owned(), - signing_input.into(), - decoded_signature.into(), - WasmJwk(public_key.to_owned()), - ); - // Convert error - crate::error::stringify_js_error(verification_result).map_err(|error_string| { - SignatureVerificationError::new(SignatureVerificationErrorKind::Unspecified).with_custom_message(error_string) - }) + if let Some(verifier) = &self.0 { + let VerificationInput { + alg, + signing_input, + decoded_signature, + } = input; + let verification_result = IJwsVerifier::verify( + verifier, + alg.name().to_owned(), + signing_input.into(), + decoded_signature.into(), + WasmJwk(public_key.to_owned()), + ); + // Convert error + crate::error::stringify_js_error(verification_result).map_err(|error_string| { + SignatureVerificationError::new(SignatureVerificationErrorKind::Unspecified).with_custom_message(error_string) + }) + } else { + match input.alg { + JwsAlgorithm::EdDSA => EdDSAJwsVerifier::default().verify(input, public_key), + JwsAlgorithm::ES256 | JwsAlgorithm::ES256K => EcDSAJwsVerifier::default().verify(input, public_key), + _ => Err(identity_iota::verification::jws::SignatureVerificationErrorKind::UnsupportedAlg.into()), + } + } } } #[wasm_bindgen(typescript_custom_section)] diff --git a/bindings/wasm/src/verification/jws_verifier.rs b/bindings/wasm/src/verification/jws_verifier.rs index 6113674828..bd016910c8 100644 --- a/bindings/wasm/src/verification/jws_verifier.rs +++ b/bindings/wasm/src/verification/jws_verifier.rs @@ -1,6 +1,7 @@ // Copyright 2020-2023 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 +use identity_ecdsa_verifier::EcDSAJwsVerifier; use identity_eddsa_verifier::Ed25519Verifier; use identity_eddsa_verifier::EdDSAJwsVerifier; use identity_iota::verification::jws::JwsAlgorithm; @@ -80,3 +81,43 @@ impl WasmEdDSAJwsVerifier { EdDSAJwsVerifier::default().verify(input, &publicKey.0).wasm_result() } } + +/// An implementor of `IJwsVerifier` that can handle the +/// `EcDSA` algorithm. +#[wasm_bindgen(js_name = EcDSAJwsVerifier)] +pub struct WasmEcDSAJwsVerifier(); + +#[wasm_bindgen(js_class = EcDSAJwsVerifier)] +#[allow(clippy::new_without_default)] +impl WasmEcDSAJwsVerifier { + /// Constructs an EcDSAJwsVerifier. + #[wasm_bindgen(constructor)] + pub fn new() -> Self { + Self() + } + + /// Verify a JWS signature secured with the `EcDSA` algorithm. + /// Only the `ES256` and `ES256K` curves are supported for now. + /// + /// # Warning + /// + /// This function does not check the `alg` property in the protected header. Callers are expected to assert this + /// prior to calling the function. + #[wasm_bindgen] + #[allow(non_snake_case)] + pub fn verify( + &self, + alg: WasmJwsAlgorithm, + signingInput: &[u8], + decodedSignature: &[u8], + publicKey: &WasmJwk, + ) -> Result<(), JsValue> { + let alg: JwsAlgorithm = JwsAlgorithm::try_from(alg)?; + let input: VerificationInput = VerificationInput { + alg, + signing_input: signingInput.into(), + decoded_signature: decodedSignature.into(), + }; + EcDSAJwsVerifier::default().verify(input, &publicKey.0).wasm_result() + } +} diff --git a/bindings/wasm/src/verification/wasm_method_type.rs b/bindings/wasm/src/verification/wasm_method_type.rs index 4b7d297a62..c143b7e53f 100644 --- a/bindings/wasm/src/verification/wasm_method_type.rs +++ b/bindings/wasm/src/verification/wasm_method_type.rs @@ -20,13 +20,19 @@ impl WasmMethodType { WasmMethodType(MethodType::X25519_KEY_AGREEMENT_KEY_2019) } - /// A verification method for use with JWT verification as prescribed by the {@link Jwk} - /// in the `publicKeyJwk` entry. - #[wasm_bindgen(js_name = JsonWebKey)] + /// @deprecated Use {@link JsonWebKey2020} instead. + #[wasm_bindgen(js_name = JsonWebKey, skip_jsdoc)] pub fn json_web_key() -> WasmMethodType { WasmMethodType(MethodType::JSON_WEB_KEY) } + /// A verification method for use with JWT verification as prescribed by the {@link Jwk} + /// in the `publicKeyJwk` entry. + #[wasm_bindgen(js_name = JsonWebKey2020)] + pub fn json_web_key_2020() -> WasmMethodType { + WasmMethodType(MethodType::JSON_WEB_KEY_2020) + } + /// A custom method. pub fn custom(type_: String) -> WasmMethodType { WasmMethodType(MethodType::custom(type_)) diff --git a/bindings/wasm/tests/core.ts b/bindings/wasm/tests/core.ts index 396ef146d9..9bde0e335d 100644 --- a/bindings/wasm/tests/core.ts +++ b/bindings/wasm/tests/core.ts @@ -225,7 +225,7 @@ describe("CoreDocument", function() { // Resolve. const resolved = doc.resolveMethod(fragment, scope)!; assert.deepStrictEqual(resolved.id().fragment(), fragment); - assert.deepStrictEqual(resolved.type().toString(), MethodType.JsonWebKey().toString()); + assert.deepStrictEqual(resolved.type().toString(), MethodType.JsonWebKey2020().toString()); assert.deepStrictEqual(resolved.controller().toString(), doc.id().toString()); assert.deepStrictEqual(resolved.data().tryPublicKeyJwk().toJSON(), JWK.toJSON()); assert.deepStrictEqual(resolved.toJSON(), method.toJSON()); diff --git a/bindings/wasm/tests/iota.ts b/bindings/wasm/tests/iota.ts index b32279c3ae..9037dd5af4 100644 --- a/bindings/wasm/tests/iota.ts +++ b/bindings/wasm/tests/iota.ts @@ -100,7 +100,7 @@ describe("IotaDocument", function() { // Resolve. const resolved = doc.resolveMethod(fragment, scope)!; assert.deepStrictEqual(resolved.id().fragment(), fragment); - assert.deepStrictEqual(resolved.type().toString(), MethodType.JsonWebKey().toString()); + assert.deepStrictEqual(resolved.type().toString(), MethodType.JsonWebKey2020().toString()); assert.deepStrictEqual(resolved.controller().toString(), doc.id().toString()); assert.deepStrictEqual(resolved.data().tryPublicKeyJwk().toJSON(), JWK.toJSON()); assert.deepStrictEqual(resolved.toJSON(), method.toJSON()); diff --git a/bindings/wasm/tsconfig.typedoc.json b/bindings/wasm/tsconfig.typedoc.json new file mode 100644 index 0000000000..02841974ec --- /dev/null +++ b/bindings/wasm/tsconfig.typedoc.json @@ -0,0 +1,4 @@ +{ + "extends": "./tsconfig.node.json", + "include": ["node/**/*"] +} diff --git a/bindings/wasm/typedoc.json b/bindings/wasm/typedoc.json new file mode 100644 index 0000000000..c6874ebe33 --- /dev/null +++ b/bindings/wasm/typedoc.json @@ -0,0 +1,23 @@ +{ + "$schema": "https://typedoc.org/schema.json", + "disableSources": true, + "excludePrivate": true, + "excludeInternal": true, + "excludeNotDocumented": true, + "excludeExternals": true, + "entryPoints": ["./node/"], + "entryPointStrategy": "expand", + "tsconfig": "./tsconfig.typedoc.json", + "out": "./docs/wasm", + "plugin": ["typedoc-plugin-markdown"], + "readme": "none", + "githubPages": false, + "theme": "markdown", + "entryDocument": "api_ref.md", + "hideBreadcrumbs": true, + "hideGenerator": true, + "sort": ["source-order"], + "compilerOptions": { + "skipLibCheck": true, + } +} diff --git a/examples/0_basic/7_revoke_vc.rs b/examples/0_basic/7_revoke_vc.rs index c4d7225e3e..3f4c41840e 100644 --- a/examples/0_basic/7_revoke_vc.rs +++ b/examples/0_basic/7_revoke_vc.rs @@ -74,6 +74,8 @@ async fn main() -> anyhow::Result<()> { .publish_did_document_update(issuer_document.clone(), TEST_GAS_BUDGET) .await?; + println!("DID Document > {issuer_document:#}"); + // Create a credential subject indicating the degree earned by Alice. let subject: Subject = Subject::from_json_value(json!({ "id": holder_document.id().as_str(), diff --git a/examples/1_advanced/10_zkp_revocation.rs b/examples/1_advanced/10_zkp_revocation.rs new file mode 100644 index 0000000000..7787c147ff --- /dev/null +++ b/examples/1_advanced/10_zkp_revocation.rs @@ -0,0 +1,541 @@ +// Copyright 2020-2024 IOTA Stiftung, Fondazione Links +// SPDX-License-Identifier: Apache-2.0 + +use examples::get_client_and_create_account; +use examples::MemStorage; +use examples::TEST_GAS_BUDGET; + +use identity_eddsa_verifier::EdDSAJwsVerifier; +use identity_iota::core::json; +use identity_iota::core::Duration; +use identity_iota::core::FromJson; +use identity_iota::core::Object; +use identity_iota::core::Timestamp; +use identity_iota::core::Url; +use identity_iota::credential::Credential; +use identity_iota::credential::CredentialBuilder; +use identity_iota::credential::DecodedJwtPresentation; +use identity_iota::credential::FailFast; +use identity_iota::credential::Jpt; +use identity_iota::credential::JptCredentialValidationOptions; +use identity_iota::credential::JptCredentialValidator; +use identity_iota::credential::JptCredentialValidatorUtils; +use identity_iota::credential::JptPresentationValidationOptions; +use identity_iota::credential::JptPresentationValidator; +use identity_iota::credential::JptPresentationValidatorUtils; +use identity_iota::credential::JwpCredentialOptions; +use identity_iota::credential::JwpPresentationOptions; +use identity_iota::credential::Jwt; +use identity_iota::credential::JwtPresentationOptions; +use identity_iota::credential::JwtPresentationValidationOptions; +use identity_iota::credential::JwtPresentationValidator; +use identity_iota::credential::JwtPresentationValidatorUtils; +use identity_iota::credential::JwtValidationError; +use identity_iota::credential::Presentation; +use identity_iota::credential::PresentationBuilder; +use identity_iota::credential::RevocationBitmap; +use identity_iota::credential::RevocationTimeframeStatus; +use identity_iota::credential::SelectiveDisclosurePresentation; +use identity_iota::credential::Status; +use identity_iota::credential::StatusCheck; +use identity_iota::credential::Subject; +use identity_iota::credential::SubjectHolderRelationship; +use identity_iota::did::CoreDID; +use identity_iota::did::DIDUrl; +use identity_iota::did::DID; +use identity_iota::document::verifiable::JwsVerificationOptions; +use identity_iota::document::Service; +use identity_iota::iota::rebased::client::IdentityClient; +use identity_iota::iota::rebased::client::IotaKeySignature; +use identity_iota::iota::rebased::transaction::Transaction; +use identity_iota::iota::rebased::transaction::TransactionOutput; +use identity_iota::iota::IotaDocument; +use identity_iota::iota::NetworkName; +use identity_iota::resolver::Resolver; +use identity_iota::storage::JwkDocumentExt; +use identity_iota::storage::JwkMemStore; +use identity_iota::storage::JwpDocumentExt; +use identity_iota::storage::JwsSignatureOptions; +use identity_iota::storage::KeyIdMemstore; +use identity_iota::storage::KeyType; +use identity_iota::storage::TimeframeRevocationExtension; +use identity_iota::verification::jws::JwsAlgorithm; +use identity_iota::verification::MethodScope; +use identity_storage::Storage; +use jsonprooftoken::jpa::algs::ProofAlgorithm; +use secret_storage::Signer; +use std::thread; +use std::time::Duration as SleepDuration; + +// // Creates a DID with a JWP verification method. +// pub async fn create_did( +// identity_client: &IdentityClient, +// storage: &Storage, +// key_type: KeyType, +// alg: ProofAlgorithm, +// ) -> anyhow::Result<(IotaDocument, String)> +// where +// K: identity_storage::JwkStorage + identity_storage::JwkStorageBbsPlusExt, +// I: identity_storage::KeyIdStorage, +// S: Signer + Sync, +// { +// // Create a new DID document with a placeholder DID. +// // The DID will be derived from the Alias Id of the Alias Output after publishing. +// let mut unpublished: IotaDocument = IotaDocument::new(identity_client.network()); + +// let verification_method_fragment = unpublished +// .generate_method_jwp(storage, key_type, alg, None, MethodScope::VerificationMethod) +// .await?; + +// let document = identity_client +// .publish_did_document(unpublished) +// .execute_with_gas(TEST_GAS_BUDGET, identity_client) +// .await?; + +// Ok((document, verification_method_fragment)) +// } +async fn create_did( + identity_client: &IdentityClient, + storage: &Storage, + key_type: KeyType, + alg: Option, + proof_alg: Option, +) -> anyhow::Result<(IotaDocument, String)> +where + K: identity_storage::JwkStorage + identity_storage::JwkStorageBbsPlusExt, + I: identity_storage::KeyIdStorage, + S: Signer + Sync, +{ + // Get the network name. + let network_name: &NetworkName = identity_client.network(); + + // Create a new DID document with a placeholder DID. + // The DID will be derived from the Alias Id of the Alias Output after publishing. + let mut unpublished: IotaDocument = IotaDocument::new(network_name); + + // New Verification Method containing a BBS+ key + let fragment = if let Some(alg) = alg { + unpublished + .generate_method(storage, key_type, alg, None, MethodScope::VerificationMethod) + .await? + } else if let Some(proof_alg) = proof_alg { + let fragment = unpublished + .generate_method_jwp(storage, key_type, proof_alg, None, MethodScope::VerificationMethod) + .await?; + + // Create a new empty revocation bitmap. No credential is revoked yet. + let revocation_bitmap: RevocationBitmap = RevocationBitmap::new(); + + // Add the revocation bitmap to the DID document of the issuer as a service. + let service_id: DIDUrl = unpublished.id().to_url().join("#my-revocation-service")?; + let service: Service = revocation_bitmap.to_service(service_id)?; + + assert!(unpublished.insert_service(service).is_ok()); + + fragment + } else { + return Err(anyhow::Error::msg("You have to pass at least one algorithm")); + }; + + let TransactionOutput:: { output: document, .. } = identity_client + .publish_did_document(unpublished) + .execute_with_gas(TEST_GAS_BUDGET, identity_client) + .await?; + + println!("Published DID document: {document:#}"); + + Ok((document, fragment)) +} + +/// Demonstrates how to create an Anonymous Credential with BBS+. +#[tokio::main] +async fn main() -> anyhow::Result<()> { + // =========================================================================== + // Step 1: Create identities and for the issuer and the holder. + // =========================================================================== + let storage_issuer: MemStorage = MemStorage::new(JwkMemStore::new(), KeyIdMemstore::new()); + + let storage_holder: MemStorage = MemStorage::new(JwkMemStore::new(), KeyIdMemstore::new()); + + let issuer_identity_client = get_client_and_create_account(&storage_issuer).await?; + + let holder_identity_client = get_client_and_create_account(&storage_holder).await?; + + let (mut issuer_document, fragment_issuer): (IotaDocument, String) = create_did( + &issuer_identity_client, + &storage_issuer, + JwkMemStore::BLS12381G2_KEY_TYPE, + None, + Some(ProofAlgorithm::BLS12381_SHA256), + ) + .await?; + + let (holder_document, fragment_holder): (IotaDocument, String) = create_did( + &holder_identity_client, + &storage_holder, + JwkMemStore::ED25519_KEY_TYPE, + Some(JwsAlgorithm::EdDSA), + None, + ) + .await?; + + // Create a credential subject indicating the degree earned by Alice. + let subject: Subject = Subject::from_json_value(json!({ + "id": holder_document.id().as_str(), + "name": "Alice", + "mainCourses": ["Object-oriented Programming", "Mathematics"], + "degree": { + "type": "BachelorDegree", + "name": "Bachelor of Science and Arts", + }, + "GPA": "4.0", + }))?; + + // ========================================================================================= + // Step 1: Create a new RevocationTimeframeStatus containing the current validityTimeframe + // ======================================================================================= + let duration = Duration::minutes(1); + // The issuer also chooses a unique `RevocationBitmap` index to be able to revoke it later. + let service_url = issuer_document.id().to_url().join("#my-revocation-service")?; + let credential_index: u32 = 5; + + let start_validity_timeframe = Timestamp::now_utc(); + let status: Status = RevocationTimeframeStatus::new( + Some(start_validity_timeframe), + duration, + service_url.into(), + credential_index, + )? + .into(); + + // Build credential using subject above and issuer. + let credential: Credential = CredentialBuilder::default() + .id(Url::parse("https://example.edu/credentials/3732")?) + .issuer(Url::parse(issuer_document.id().as_str())?) + .type_("UniversityDegreeCredential") + .subject(subject) + .status(status) + .build()?; + + let credential_jpt: Jpt = issuer_document + .create_credential_jpt( + &credential, + &storage_issuer, + &fragment_issuer, + &JwpCredentialOptions::default(), + None, + ) + .await?; + + // Validate the credential's proof using the issuer's DID Document, the credential's semantic structure, + // that the issuance date is not in the future and that the expiration date is not in the past: + let decoded_jpt = JptCredentialValidator::validate::<_, Object>( + &credential_jpt, + &issuer_document, + &JptCredentialValidationOptions::default(), + FailFast::FirstError, + ) + .unwrap(); + + assert_eq!(credential, decoded_jpt.credential); + + // Issuer sends the Verifiable Credential to the holder. + println!( + "Sending credential (as JPT) to the holder: {}\n", + credential_jpt.as_str() + ); + + // Holder validate the credential and retrieve the JwpIssued, needed to construct the JwpPresented + + let validation_result = JptCredentialValidator::validate::<_, Object>( + &credential_jpt, + &issuer_document, + &JptCredentialValidationOptions::default(), + FailFast::FirstError, + ); + + let decoded_credential = validation_result.unwrap(); + + // =========================================================================== + // Credential's Status check + // =========================================================================== + + // Timeframe check + let timeframe_result = JptCredentialValidatorUtils::check_timeframes_with_validity_timeframe_2024( + &decoded_credential.credential, + None, + StatusCheck::Strict, + ); + + assert!(timeframe_result.is_ok()); + + let revocation_result = JptCredentialValidatorUtils::check_revocation_with_validity_timeframe_2024( + &decoded_credential.credential, + &issuer_document, + StatusCheck::Strict, + ); + + assert!(revocation_result.is_ok()); + + // Both checks + + let revocation_result = JptCredentialValidatorUtils::check_timeframes_and_revocation_with_validity_timeframe_2024( + &decoded_credential.credential, + &issuer_document, + None, + StatusCheck::Strict, + ); + + assert!(revocation_result.is_ok()); + + let challenge: &str = "475a7984-1bb5-4c4c-a56f-822bccd46440"; + + let method_id = decoded_credential + .decoded_jwp + .get_issuer_protected_header() + .kid() + .unwrap(); + + let mut selective_disclosure_presentation = SelectiveDisclosurePresentation::new(&decoded_credential.decoded_jwp); + selective_disclosure_presentation + .conceal_in_subject("mainCourses[1]") + .unwrap(); + selective_disclosure_presentation + .conceal_in_subject("degree.name") + .unwrap(); + + let presentation_jpt: Jpt = issuer_document + .create_presentation_jpt( + &mut selective_disclosure_presentation, + method_id, + &JwpPresentationOptions::default().nonce(challenge), + ) + .await?; + + // Holder sends a Presentation JPT to the Verifier. + println!( + "Sending presentation (as JPT) to the verifier: {}\n", + presentation_jpt.as_str() + ); + + // =========================================================================== + // Step 2a: Verifier receives the Presentation and verifies it. + // =========================================================================== + + let presentation_validation_options = JptPresentationValidationOptions::default().nonce(challenge); + + // Verifier validate the Presented Credential and retrieve the JwpPresented + let decoded_presented_credential = JptPresentationValidator::validate::<_, Object>( + &presentation_jpt, + &issuer_document, + &presentation_validation_options, + FailFast::FirstError, + ) + .unwrap(); + + // Check validityTimeframe + + let timeframe_result = JptPresentationValidatorUtils::check_timeframes_with_validity_timeframe_2024( + &decoded_presented_credential.credential, + None, + StatusCheck::Strict, + ); + + assert!(timeframe_result.is_ok()); + + // Since no errors were thrown by `verify_presentation` we know that the validation was successful. + println!( + "Presented Credential successfully validated: {:#}", + decoded_presented_credential.credential + ); + + // =========================================================================== + // Step 2b: Waiting for the next validityTimeframe, will result in the Credential timeframe interval NOT valid + // =========================================================================== + + thread::sleep(SleepDuration::from_secs(61)); + + let timeframe_result = JptPresentationValidatorUtils::check_timeframes_with_validity_timeframe_2024( + &decoded_presented_credential.credential, + None, + StatusCheck::Strict, + ); + + // We expect validation to no longer succeed because the credential was NOT updated. + if matches!(timeframe_result.unwrap_err(), JwtValidationError::OutsideTimeframe) { + println!("Validity Timeframe interval NOT valid\n"); + } + + // =========================================================================== + // 3: Update credential + // =========================================================================== + + // =========================================================================== + // 3.1: Issuer sends the holder a challenge and requests a signed Verifiable Presentation. + // =========================================================================== + + // A unique random challenge generated by the requester per presentation can mitigate replay attacks. + let challenge: &str = "475a7984-1bb5-4c4c-a56f-822bccd46440"; + + // The Holder and Issuer also agree that the signature should have an expiry date + // 10 minutes from now. + let expires: Timestamp = Timestamp::now_utc().checked_add(Duration::minutes(10)).unwrap(); + + // =========================================================================== + // 3.2: Holder creates and signs a verifiable presentation from the issued credential. + // =========================================================================== + + // Create an unsigned Presentation from the previously issued ZK Verifiable Credential. + let presentation: Presentation = + PresentationBuilder::new(holder_document.id().to_url().into(), Default::default()) + .credential(credential_jpt) + .build()?; + + // Create a JWT verifiable presentation using the holder's verification method + // and include the requested challenge and expiry timestamp. + let presentation_jwt: Jwt = holder_document + .create_presentation_jwt( + &presentation, + &storage_holder, + &fragment_holder, + &JwsSignatureOptions::default().nonce(challenge.to_owned()), + &JwtPresentationOptions::default().expiration_date(expires), + ) + .await?; + + // =========================================================================== + // 3.3: Holder sends a verifiable presentation to the verifier. + // =========================================================================== + println!( + "Sending presentation (as JWT) to the Issuer: {}\n", + presentation_jwt.as_str() + ); + + // =========================================================================== + // 3.4: Issuer validate Verifiable Presentation and ZK Verifiable Credential. + // =========================================================================== + + // ================================================ + // 3.4.1: Issuer validate Verifiable Presentation. + // ================================================ + + let presentation_verifier_options: JwsVerificationOptions = + JwsVerificationOptions::default().nonce(challenge.to_owned()); + + let mut resolver: Resolver = Resolver::new(); + resolver.attach_kinesis_iota_handler((*holder_identity_client).clone()); + + // Resolve the holder's document. + let holder_did: CoreDID = JwtPresentationValidatorUtils::extract_holder(&presentation_jwt)?; + let holder: IotaDocument = resolver.resolve(&holder_did).await?; + + // Validate presentation. Note that this doesn't validate the included credentials. + let presentation_validation_options = + JwtPresentationValidationOptions::default().presentation_verifier_options(presentation_verifier_options); + let presentation: DecodedJwtPresentation = JwtPresentationValidator::with_signature_verifier( + EdDSAJwsVerifier::default(), + ) + .validate(&presentation_jwt, &holder, &presentation_validation_options)?; + + // ======================================================================= + // 3.4.2: Issuer validate ZK Verifiable Credential inside the Presentation. + // ======================================================================== + + let validation_options: JptCredentialValidationOptions = JptCredentialValidationOptions::default() + .subject_holder_relationship(holder_did.to_url().into(), SubjectHolderRelationship::AlwaysSubject); + + let jpt_credentials: &Vec = &presentation.presentation.verifiable_credential; + + // Extract ZK Verifiable Credential in JPT format + let jpt_vc = jpt_credentials.first().unwrap(); + + // Issuer checks the Credential integrity. + let mut verified_credential_result = + JptCredentialValidator::validate::<_, Object>(jpt_vc, &issuer_document, &validation_options, FailFast::FirstError) + .unwrap(); + + // Issuer checks if the Credential has been revoked + let revocation_result = JptCredentialValidatorUtils::check_revocation_with_validity_timeframe_2024( + &verified_credential_result.credential, + &issuer_document, + StatusCheck::Strict, + ); + + assert!(!revocation_result.is_err_and(|e| matches!(e, JwtValidationError::Revoked))); + + // =========================================================================== + // 3.5: Issuer ready for Update. + // =========================================================================== + + // Since no errors were thrown during the Verifiable Presentation validation and the verification of inner Credentials + println!( + "Ready for Update - VP successfully validated: {:#?}", + presentation.presentation + ); + + // Issuer updates the credential + let new_credential_jpt = issuer_document + .update( + &storage_issuer, + &fragment_issuer, + None, + duration, + &mut verified_credential_result.decoded_jwp, + ) + .await?; + + // Issuer sends back the credential updated + + println!( + "Sending updated credential (as JPT) to the holder: {}\n", + new_credential_jpt.as_str() + ); + + // Holder check validity of the updated credential + + let validation_result = JptCredentialValidator::validate::<_, Object>( + &new_credential_jpt, + &issuer_document, + &JptCredentialValidationOptions::default(), + FailFast::FirstError, + ) + .unwrap(); + + let timeframe_result = JptCredentialValidatorUtils::check_timeframes_with_validity_timeframe_2024( + &validation_result.credential, + None, + StatusCheck::Strict, + ); + + assert!(!timeframe_result + .as_ref() + .is_err_and(|e| matches!(e, JwtValidationError::OutsideTimeframe))); + println!("Updated credential is VALID!"); + + // =========================================================================== + // Issuer decides to Revoke Holder's Credential + // =========================================================================== + + println!("Issuer decides to revoke the Credential"); + + // Update the RevocationBitmap service in the issuer's DID Document. + // This revokes the credential's unique index. + + issuer_document.revoke_credentials("my-revocation-service", &[credential_index])?; + + // Publish the changes. + issuer_identity_client + .publish_did_document_update(issuer_document.clone(), TEST_GAS_BUDGET) + .await?; + + // Holder checks if his credential has been revoked by the Issuer + let revocation_result = JptCredentialValidatorUtils::check_revocation_with_validity_timeframe_2024( + &decoded_credential.credential, + &issuer_document, + StatusCheck::Strict, + ); + assert!(revocation_result.is_err_and(|e| matches!(e, JwtValidationError::Revoked))); + + println!("Credential Revoked!"); + Ok(()) +} diff --git a/examples/1_advanced/11_linked_verifiable_presentation.rs b/examples/1_advanced/11_linked_verifiable_presentation.rs new file mode 100644 index 0000000000..75c22e2104 --- /dev/null +++ b/examples/1_advanced/11_linked_verifiable_presentation.rs @@ -0,0 +1,165 @@ +// Copyright 2020-2024 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +use anyhow::Context; + +use examples::create_kinesis_did_document; +use examples::get_client_and_create_account; +use examples::get_memstorage; +use examples::MemStorage; +use examples::TEST_GAS_BUDGET; + +use identity_eddsa_verifier::EdDSAJwsVerifier; +use identity_iota::core::FromJson; +use identity_iota::core::Object; +use identity_iota::core::OrderedSet; +use identity_iota::core::Url; +use identity_iota::credential::CompoundJwtPresentationValidationError; +use identity_iota::credential::CredentialBuilder; +use identity_iota::credential::DecodedJwtPresentation; +use identity_iota::credential::Jwt; +use identity_iota::credential::JwtPresentationOptions; +use identity_iota::credential::JwtPresentationValidationOptions; +use identity_iota::credential::JwtPresentationValidator; +use identity_iota::credential::JwtPresentationValidatorUtils; +use identity_iota::credential::LinkedVerifiablePresentationService; +use identity_iota::credential::PresentationBuilder; +use identity_iota::credential::Subject; +use identity_iota::did::CoreDID; +use identity_iota::did::DIDUrl; +use identity_iota::did::DID; +use identity_iota::document::verifiable::JwsVerificationOptions; +use identity_iota::iota::IotaDID; +use identity_iota::iota::IotaDocument; +use identity_iota::resolver::Resolver; +use identity_iota::storage::JwkDocumentExt; +use identity_iota::storage::JwsSignatureOptions; + +#[tokio::main] +async fn main() -> anyhow::Result<()> { + // =========================================================================== + // Step 1: Create identities and Client + // =========================================================================== + + let storage = get_memstorage()?; + + let identity_client = get_client_and_create_account(&storage).await?; + + // create new DID document and publish it + let (mut did_document, fragment) = create_kinesis_did_document(&identity_client, &storage).await?; + + println!("Published DID document: {did_document:#}"); + + let did: IotaDID = did_document.id().clone(); + + // ===================================================== + // Create Linked Verifiable Presentation service + // ===================================================== + + // The DID should link to the following VPs. + let verifiable_presentation_url_1: Url = Url::parse("https://foo.example.com/verifiable-presentation.jwt")?; + let verifiable_presentation_url_2: Url = Url::parse("https://bar.example.com/verifiable-presentation.jsonld")?; + + let mut verifiable_presentation_urls: OrderedSet = OrderedSet::new(); + verifiable_presentation_urls.append(verifiable_presentation_url_1.clone()); + verifiable_presentation_urls.append(verifiable_presentation_url_2.clone()); + + // Create a Linked Verifiable Presentation Service to enable the discovery of the linked VPs through the DID Document. + // This is optional since it is not a hard requirement by the specs. + let service_url: DIDUrl = did.clone().join("#linked-vp")?; + let linked_verifiable_presentation_service = + LinkedVerifiablePresentationService::new(service_url, verifiable_presentation_urls, Object::new())?; + did_document.insert_service(linked_verifiable_presentation_service.into())?; + + let updated_did_document: IotaDocument = identity_client + .publish_did_document_update(did_document, TEST_GAS_BUDGET) + .await?; + + println!("DID document with linked verifiable presentation service: {updated_did_document:#}"); + + // ===================================================== + // Verification + // ===================================================== + + // Init a resolver for resolving DID Documents. + let mut resolver: Resolver = Resolver::new(); + resolver.attach_kinesis_iota_handler((*identity_client).clone()); + + // Resolve the DID Document of the DID that issued the credential. + let did_document: IotaDocument = resolver.resolve(&did).await?; + + // Get the Linked Verifiable Presentation Services from the DID Document. + let linked_verifiable_presentation_services: Vec = did_document + .service() + .iter() + .cloned() + .filter_map(|service| LinkedVerifiablePresentationService::try_from(service).ok()) + .collect(); + + assert_eq!(linked_verifiable_presentation_services.len(), 1); + + // Get the VPs included in the service. + let _verifiable_presentation_urls: &[Url] = linked_verifiable_presentation_services + .first() + .ok_or_else(|| anyhow::anyhow!("expected verifiable presentation urls"))? + .verifiable_presentation_urls(); + + // Fetch the verifiable presentation from the URL (for example using `reqwest`). + // But since the URLs do not point to actual online resource, we will simply create an example JWT. + let presentation_jwt: Jwt = make_vp_jwt(&did_document, &storage, &fragment).await?; + + // Resolve the holder's document. + let holder_did: CoreDID = JwtPresentationValidatorUtils::extract_holder(&presentation_jwt)?; + let holder: IotaDocument = resolver.resolve(&holder_did).await?; + + // Validate linked presentation. Note that this doesn't validate the included credentials. + let presentation_verifier_options: JwsVerificationOptions = JwsVerificationOptions::default(); + let presentation_validation_options = + JwtPresentationValidationOptions::default().presentation_verifier_options(presentation_verifier_options); + let validation_result: Result, CompoundJwtPresentationValidationError> = + JwtPresentationValidator::with_signature_verifier(EdDSAJwsVerifier::default()).validate( + &presentation_jwt, + &holder, + &presentation_validation_options, + ); + + assert!(validation_result.is_ok()); + + Ok(()) +} + +async fn make_vp_jwt(did_doc: &IotaDocument, storage: &MemStorage, fragment: &str) -> anyhow::Result { + // first we create a credential encoding it as jwt + let credential = CredentialBuilder::new(Object::default()) + .id(Url::parse("https://example.edu/credentials/3732")?) + .issuer(Url::parse(did_doc.id().as_str())?) + .type_("UniversityDegreeCredential") + .subject(Subject::from_json_value(serde_json::json!({ + "id": did_doc.id().as_str(), + "name": "Alice", + "degree": { + "type": "BachelorDegree", + "name": "Bachelor of Science and Arts", + }, + "GPA": "4.0", + }))?) + .build()?; + let credential = did_doc + .create_credential_jwt(&credential, storage, fragment, &JwsSignatureOptions::default(), None) + .await?; + // then we create a presentation including the just created JWT encoded credential. + let presentation = PresentationBuilder::new(Url::parse(did_doc.id().as_str())?, Object::default()) + .credential(credential) + .build()?; + // we encode the presentation as JWT + did_doc + .create_presentation_jwt( + &presentation, + storage, + fragment, + &JwsSignatureOptions::default(), + &JwtPresentationOptions::default(), + ) + .await + .context("jwt presentation failed") +} diff --git a/examples/1_advanced/9_zkp.rs b/examples/1_advanced/9_zkp.rs new file mode 100644 index 0000000000..ddceb2e900 --- /dev/null +++ b/examples/1_advanced/9_zkp.rs @@ -0,0 +1,241 @@ +// Copyright 2020-2024 IOTA Stiftung, Fondazione Links +// SPDX-License-Identifier: Apache-2.0 + +use examples::get_client_and_create_account; + +use examples::get_memstorage; +use examples::TEST_GAS_BUDGET; +use identity_iota::core::json; +use identity_iota::core::FromJson; +use identity_iota::core::Object; +use identity_iota::core::Url; +use identity_iota::credential::Credential; +use identity_iota::credential::CredentialBuilder; +use identity_iota::credential::FailFast; +use identity_iota::credential::Jpt; +use identity_iota::credential::JptCredentialValidationOptions; +use identity_iota::credential::JptCredentialValidator; +use identity_iota::credential::JptCredentialValidatorUtils; +use identity_iota::credential::JptPresentationValidationOptions; +use identity_iota::credential::JptPresentationValidator; +use identity_iota::credential::JptPresentationValidatorUtils; +use identity_iota::credential::JwpCredentialOptions; +use identity_iota::credential::JwpPresentationOptions; +use identity_iota::credential::SelectiveDisclosurePresentation; +use identity_iota::credential::Subject; +use identity_iota::did::CoreDID; +use identity_iota::did::DID; + +use identity_iota::iota::rebased::transaction::TransactionOutput; +use identity_iota::iota::IotaDocument; +use identity_iota::resolver::Resolver; +use identity_iota::storage::JwkMemStore; +use identity_iota::storage::JwpDocumentExt; +use identity_iota::storage::KeyType; +use identity_iota::verification::MethodScope; + +use identity_iota::iota::rebased::client::IdentityClient; +use identity_iota::iota::rebased::client::IotaKeySignature; +use identity_iota::iota::rebased::transaction::Transaction; +use identity_storage::Storage; +use jsonprooftoken::jpa::algs::ProofAlgorithm; +use secret_storage::Signer; + +// Creates a DID with a JWP verification method. +pub async fn create_did( + identity_client: &IdentityClient, + storage: &Storage, + key_type: KeyType, + alg: ProofAlgorithm, +) -> anyhow::Result<(IotaDocument, String)> +where + K: identity_storage::JwkStorage + identity_storage::JwkStorageBbsPlusExt, + I: identity_storage::KeyIdStorage, + S: Signer + Sync, +{ + // Create a new DID document with a placeholder DID. + // The DID will be derived from the Alias Id of the Alias Output after publishing. + let mut unpublished: IotaDocument = IotaDocument::new(identity_client.network()); + + let verification_method_fragment = unpublished + .generate_method_jwp(storage, key_type, alg, None, MethodScope::VerificationMethod) + .await?; + + let TransactionOutput:: { output: document, .. } = identity_client + .publish_did_document(unpublished) + .execute_with_gas(TEST_GAS_BUDGET, identity_client) + .await?; + + Ok((document, verification_method_fragment)) +} + +/// Demonstrates how to create an Anonymous Credential with BBS+. +#[tokio::main] +async fn main() -> anyhow::Result<()> { + // =========================================================================== + // Step 1: Create identities and Client + // =========================================================================== + + let storage_issuer = get_memstorage()?; + + let identity_client = get_client_and_create_account(&storage_issuer).await?; + + let (issuer_document, fragment_issuer): (IotaDocument, String) = create_did( + &identity_client, + &storage_issuer, + JwkMemStore::BLS12381G2_KEY_TYPE, + ProofAlgorithm::BLS12381_SHA256, + ) + .await?; + + // =========================================================================== + // Step 2: Issuer creates and signs a Verifiable Credential with BBS algorithm. + // =========================================================================== + + // Create a credential subject indicating the degree earned by Alice. + let subject: Subject = Subject::from_json_value(json!({ + "name": "Alice", + "mainCourses": ["Object-oriented Programming", "Mathematics"], + "degree": { + "type": "BachelorDegree", + "name": "Bachelor of Science and Arts", + }, + "GPA": "4.0", + }))?; + + // Build credential using subject above and issuer. + let credential: Credential = CredentialBuilder::default() + .id(Url::parse("https://example.edu/credentials/3732")?) + .issuer(Url::parse(issuer_document.id().as_str())?) + .type_("UniversityDegreeCredential") + .subject(subject) + .build()?; + + let credential_jpt: Jpt = issuer_document + .create_credential_jpt( + &credential, + &storage_issuer, + &fragment_issuer, + &JwpCredentialOptions::default(), + None, + ) + .await?; + + // Validate the credential's proof using the issuer's DID Document, the credential's semantic structure, + // that the issuance date is not in the future and that the expiration date is not in the past: + let decoded_jpt = JptCredentialValidator::validate::<_, Object>( + &credential_jpt, + &issuer_document, + &JptCredentialValidationOptions::default(), + FailFast::FirstError, + ) + .unwrap(); + + assert_eq!(credential, decoded_jpt.credential); + + // =========================================================================== + // Step 3: Issuer sends the Verifiable Credential to the holder. + // =========================================================================== + println!( + "Sending credential (as JPT) to the holder: {}\n", + credential_jpt.as_str() + ); + + // ============================================================================================ + // Step 4: Holder resolves Issuer's DID, retrieve Issuer's document and validate the Credential + // ============================================================================================ + + let mut resolver: Resolver = Resolver::new(); + resolver.attach_kinesis_iota_handler((*identity_client).clone()); + + // Holder resolves issuer's DID + let issuer: CoreDID = JptCredentialValidatorUtils::extract_issuer_from_issued_jpt(&credential_jpt).unwrap(); + let issuer_document: IotaDocument = resolver.resolve(&issuer).await?; + + // Holder validates the credential and retrieve the JwpIssued, needed to construct the JwpPresented + let decoded_credential = JptCredentialValidator::validate::<_, Object>( + &credential_jpt, + &issuer_document, + &JptCredentialValidationOptions::default(), + FailFast::FirstError, + ) + .unwrap(); + + // =========================================================================== + // Step 5: Verifier sends the holder a challenge and requests a Presentation. + // + // Please be aware that when we mention "Presentation," we are not alluding to the Verifiable Presentation standard as defined by W3C (https://www.w3.org/TR/vc-data-model/#presentations). + // Instead, our reference is to a JWP Presentation (https://datatracker.ietf.org/doc/html/draft-ietf-jose-json-web-proof#name-presented-form), which differs from the W3C standard. + // =========================================================================== + + // A unique random challenge generated by the requester per presentation can mitigate replay attacks. + let challenge: &str = "475a7984-1bb5-4c4c-a56f-822bccd46440"; + + // ========================================================================================================= + // Step 6: Holder engages in the Selective Disclosure of credential's attributes. + // ========================================================================================================= + + let method_id = decoded_credential + .decoded_jwp + .get_issuer_protected_header() + .kid() + .unwrap(); + + let mut selective_disclosure_presentation = SelectiveDisclosurePresentation::new(&decoded_credential.decoded_jwp); + selective_disclosure_presentation + .conceal_in_subject("mainCourses[1]") + .unwrap(); + selective_disclosure_presentation + .conceal_in_subject("degree.name") + .unwrap(); + + // ======================================================================================================================================= + // Step 7: Holder needs Issuer's Public Key to compute the Signature Proof of Knowledge and construct the Presentation + // JPT. + // ======================================================================================================================================= + + // Construct a JPT(JWP in the Presentation form) representing the Selectively Disclosed Verifiable Credential + let presentation_jpt: Jpt = issuer_document + .create_presentation_jpt( + &mut selective_disclosure_presentation, + method_id, + &JwpPresentationOptions::default().nonce(challenge), + ) + .await?; + + // =========================================================================== + // Step 8: Holder sends a Presentation JPT to the Verifier. + // =========================================================================== + + println!( + "Sending presentation (as JPT) to the verifier: {}\n", + presentation_jpt.as_str() + ); + + // =========================================================================== + // Step 9: Verifier receives the Presentation and verifies it. + // =========================================================================== + + // Verifier resolve Issuer DID + let issuer: CoreDID = JptPresentationValidatorUtils::extract_issuer_from_presented_jpt(&presentation_jpt).unwrap(); + let issuer_document: IotaDocument = resolver.resolve(&issuer).await?; + + let presentation_validation_options = JptPresentationValidationOptions::default().nonce(challenge); + + // Verifier validate the Presented Credential and retrieve the JwpPresented + let decoded_presented_credential = JptPresentationValidator::validate::<_, Object>( + &presentation_jpt, + &issuer_document, + &presentation_validation_options, + FailFast::FirstError, + ) + .unwrap(); + + // Since no errors were thrown by `verify_presentation` we know that the validation was successful. + println!( + "Presented Credential successfully validated: {:#?}", + decoded_presented_credential.credential + ); + + Ok(()) +} diff --git a/examples/Cargo.toml b/examples/Cargo.toml index 954f0abc1a..6ad0c3a888 100644 --- a/examples/Cargo.toml +++ b/examples/Cargo.toml @@ -12,6 +12,7 @@ identity_storage = { path = "../identity_storage" } identity_stronghold = { path = "../identity_stronghold", default-features = false, features = ["send-sync-storage"] } iota-sdk = { git = "https://github.com/iotaledger/iota.git", package = "iota-sdk", tag = "v0.7.0-alpha" } iota-sdk-legacy = { package = "iota-sdk", version = "1.0", default-features = false, features = ["tls", "client", "stronghold"] } +json-proof-token.workspace = true rand = "0.8.5" sd-jwt-payload = { version = "0.2.1", default-features = false, features = ["sha"] } secret-storage = { git = "https://github.com/iotaledger/secret-storage.git", branch = "main" } @@ -25,6 +26,7 @@ features = [ "client", "domain-linkage", "iota-client", + "jpt-bbs-plus", "kinesis-client", "memstore", "resolver", @@ -87,3 +89,15 @@ name = "7_sd_jwt" [[example]] path = "1_advanced/8_status_list_2021.rs" name = "8_status_list_2021" + +[[example]] +path = "1_advanced/9_zkp.rs" +name = "9_zkp" + +[[example]] +path = "1_advanced/10_zkp_revocation.rs" +name = "10_zkp_revocation" + +[[example]] +path = "1_advanced/11_linked_verifiable_presentation.rs" +name = "11_linked_verifiable_presentation" diff --git a/examples/README.md b/examples/README.md index 2076f8b4b2..8ea9ab2145 100644 --- a/examples/README.md +++ b/examples/README.md @@ -18,13 +18,12 @@ cargo run --release --example 0_create_did ### Note: Running the examples with the release flag will be significantly faster due to stronghold performance issues in debug mode. - ## Basic Examples The following basic CRUD (Create, Read, Update, Delete) examples are available: | Name | Information | -|:--------------------------------------------------|:-------------------------------------------------------------------------------------| +| :------------------------------------------------ | :----------------------------------------------------------------------------------- | | [0_create_did](./0_basic/0_create_did.rs) | Demonstrates how to create a DID Document and publish it in a new Alias Output. | | [1_update_did](./0_basic/1_update_did.rs) | Demonstrates how to update a DID document in an existing Alias Output. | | [2_resolve_did](./0_basic/2_resolve_did.rs) | Demonstrates how to resolve an existing DID in an Alias Output. | @@ -38,14 +37,15 @@ The following basic CRUD (Create, Read, Update, Delete) examples are available: The following advanced examples are available: -| Name | Information | -|:-----------------------------------------------------------------|:---------------------------------------------------------------------------------------------------------| -| [0_did_controls_did](./1_advanced/0_did_controls_did.rs) | Demonstrates how an identity can control another identity. | -| [1_did_issues_nft](./1_advanced/1_did_issues_nft.rs) | Demonstrates how an identity can issue and own NFTs, and how observers can verify the issuer of the NFT. | -| [2_nft_owns_did](./1_advanced/2_nft_owns_did.rs) | Demonstrates how an identity can be owned by NFTs, and how observers can verify that relationship. | -| [3_did_issues_tokens](./1_advanced/3_did_issues_tokens.rs) | Demonstrates how an identity can issue and control a Token Foundry and its tokens. | -| [4_alias_output_history](./1_advanced/4_alias_output_history.rs) | Demonstrates fetching the history of an Alias Output. | -| [5_custom_resolution](./1_advanced/5_custom_resolution.rs) | Demonstrates how to set up a resolver using custom handlers. | -| [6_domain_linkage](./1_advanced/6_domain_linkage) | Demonstrates how to link a domain and a DID and verify the linkage. | -| [7_sd_jwt](./1_advanced/7_sd_jwt) | Demonstrates how to create and verify selective disclosure verifiable credentials. | -| [8_status_list_2021](./1_advanced/8_status_list_2021.rs) | Demonstrates how to revoke a credential using `StatusList2021`. | +| Name | Information | +| :------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------------- | +| [0_did_controls_did](./1_advanced/0_did_controls_did.rs) | Demonstrates how an identity can control another identity. | +| [1_did_issues_nft](./1_advanced/1_did_issues_nft.rs) | Demonstrates how an identity can issue and own NFTs, and how observers can verify the issuer of the NFT. | +| [2_nft_owns_did](./1_advanced/2_nft_owns_did.rs) | Demonstrates how an identity can be owned by NFTs, and how observers can verify that relationship. | +| [3_did_issues_tokens](./1_advanced/3_did_issues_tokens.rs) | Demonstrates how an identity can issue and control a Token Foundry and its tokens. | +| [4_alias_output_history](./1_advanced/4_alias_output_history.rs) | Demonstrates fetching the history of an Alias Output. | +| [5_custom_resolution](./1_advanced/5_custom_resolution.rs) | Demonstrates how to set up a resolver using custom handlers. | +| [6_domain_linkage](./1_advanced/6_domain_linkage) | Demonstrates how to link a domain and a DID and verify the linkage. | +| [7_sd_jwt](./1_advanced/7_sd_jwt) | Demonstrates how to create and verify selective disclosure verifiable credentials. | +| [8_status_list_2021](./1_advanced/8_status_list_2021.rs) | Demonstrates how to revoke a credential using `StatusList2021`. | +| [11_linked_verifiable_presentation](./1_advanced/11_linked_verifiable_presentation.rs) | Demonstrates how to link a public Verifiable Presentation to an identity and how it can be verified. | diff --git a/identity_core/Cargo.toml b/identity_core/Cargo.toml index 0d11810b3d..a286a9bb85 100644 --- a/identity_core/Cargo.toml +++ b/identity_core/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "identity_core" -version = "1.2.0" +version = "1.4.0" authors.workspace = true edition.workspace = true homepage.workspace = true @@ -12,7 +12,6 @@ rust-version.workspace = true description = "The core traits and types for the identity-rs library." [dependencies] -iota-crypto = { version = "0.23", default-features = false, features = ["ed25519", "random", "sha", "x25519", "std"] } multibase = { version = "0.9", default-features = false, features = ["std"] } serde = { workspace = true, features = ["std"] } serde_json = { workspace = true, features = ["std"] } @@ -22,7 +21,7 @@ time = { version = "0.3.23", default-features = false, features = ["std", "serde url = { version = "2.4", default-features = false, features = ["serde"] } [target.'cfg(all(target_arch = "wasm32", not(target_os = "wasi")))'.dependencies] -js-sys = { version = "0.3.55", default-features = false } +js-sys = { version = "0.3.55", default-features = false, optional = true } [dev-dependencies] proptest = { version = "1.0.0" } @@ -37,3 +36,12 @@ rustdoc-args = ["--cfg", "docsrs"] [lints] workspace = true + +[features] +default = ["dep:js-sys"] +# Enables a macro to provide a custom time (Timestamp::now_utc) implementation, see src/custom_time.rs +custom_time = [] + +[[test]] +name = "custom_time" +required-features = ["custom_time"] diff --git a/identity_core/src/common/ordered_set.rs b/identity_core/src/common/ordered_set.rs index b3650490ef..885342409b 100644 --- a/identity_core/src/common/ordered_set.rs +++ b/identity_core/src/common/ordered_set.rs @@ -488,7 +488,7 @@ mod tests { /// Produces a strategy for generating an ordered set together with two values according to the following algorithm: /// 1. Call `f` to get a pair of sets (x,y). /// 2. Toss a coin to decide whether to pick an element from x at random, or from y (if the chosen set is empty - /// Default is called). 3. Repeat step 2 and let the two outcomes be denoted a and b. + /// Default is called). 3. Repeat step 2 and let the two outcomes be denoted a and b. /// 4. Toss a coin to decide whether to swap the keys of a and b. /// 5. return (x,a,b) fn set_with_values(f: F) -> impl Strategy, T, T)> diff --git a/identity_core/src/common/timestamp.rs b/identity_core/src/common/timestamp.rs index 8de1832409..4f03db2cea 100644 --- a/identity_core/src/common/timestamp.rs +++ b/identity_core/src/common/timestamp.rs @@ -42,7 +42,10 @@ impl Timestamp { /// fractional seconds truncated. /// /// See the [`datetime` DID-core specification](https://www.w3.org/TR/did-core/#production). - #[cfg(not(all(target_arch = "wasm32", not(target_os = "wasi"))))] + #[cfg(all( + not(all(target_arch = "wasm32", not(target_os = "wasi"))), + not(feature = "custom_time") + ))] pub fn now_utc() -> Self { Self(truncate_fractional_seconds(OffsetDateTime::now_utc())) } @@ -51,7 +54,7 @@ impl Timestamp { /// fractional seconds truncated. /// /// See the [`datetime` DID-core specification](https://www.w3.org/TR/did-core/#production). - #[cfg(all(target_arch = "wasm32", not(target_os = "wasi")))] + #[cfg(all(target_arch = "wasm32", not(target_os = "wasi"), not(feature = "custom_time")))] pub fn now_utc() -> Self { let milliseconds_since_unix_epoch: i64 = js_sys::Date::now() as i64; let seconds: i64 = milliseconds_since_unix_epoch / 1000; @@ -59,6 +62,15 @@ impl Timestamp { Self::from_unix(seconds).expect("Timestamp failed to convert system datetime") } + /// Creates a new `Timestamp` with the current date and time, normalized to UTC+00:00 with + /// fractional seconds truncated. + /// + /// See the [`datetime` DID-core specification](https://www.w3.org/TR/did-core/#production). + #[cfg(feature = "custom_time")] + pub fn now_utc() -> Self { + crate::custom_time::now_utc_custom() + } + /// Returns the `Timestamp` as an [RFC 3339](https://tools.ietf.org/html/rfc3339) `String`. pub fn to_rfc3339(&self) -> String { // expect is okay, constructors ensure RFC 3339 compatible timestamps. diff --git a/identity_core/src/custom_time.rs b/identity_core/src/custom_time.rs new file mode 100644 index 0000000000..ef509a19de --- /dev/null +++ b/identity_core/src/custom_time.rs @@ -0,0 +1,88 @@ +// Copyright 2020-2024 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +//! An implementation of `now_utc` which calls out to an externally defined function. +use crate::common::Timestamp; + +/// Register a function to be invoked by `identity_core` in order to get a [Timestamp] representing +/// "now". +/// +/// ## Writing a custom `now_utc` implementation +/// +/// The function to register must have the same signature as +/// [`Timestamp::now_utc`](Timestamp::now_utc). The function can be defined +/// wherever you want, either in root crate or a dependent crate. +/// +/// For example, if we wanted a `static_now_utc` crate containing an +/// implementation that always returns the same timestamp, we would first depend on `identity_core` +/// (for the [`Timestamp`] type) in `static_now_utc/Cargo.toml`: +/// ```toml +/// [dependencies] +/// identity_core = "1" +/// ``` +/// Note that the crate containing this function does **not** need to enable the +/// `"custom_time"` Cargo feature. +/// +/// Next, in `static_now_utc/src/lib.rs`, we define our function: +/// ```rust +/// use identity_core::common::Timestamp; +/// +/// // Some fixed timestamp +/// const MY_FIXED_TIMESTAMP: i64 = 1724402964; +/// pub fn static_now_utc() -> Timestamp { +/// Timestamp::from_unix(MY_FIXED_TIMESTAMP).unwrap() +/// } +/// ``` +/// +/// ## Registering a custom `now_utc` implementation +/// +/// Functions can only be registered in the root binary crate. Attempting to +/// register a function in a non-root crate will result in a linker error. +/// This is similar to +/// [`#[panic_handler]`](https://doc.rust-lang.org/nomicon/panic-handler.html) or +/// [`#[global_allocator]`](https://doc.rust-lang.org/edition-guide/rust-2018/platform-and-target-support/global-allocators.html), +/// where helper crates define handlers/allocators but only the binary crate +/// actually _uses_ the functionality. +/// +/// To register the function, we first depend on `static_now_utc` _and_ +/// `identity_core` in `Cargo.toml`: +/// ```toml +/// [dependencies] +/// static_now_utc = "0.1" +/// identity_core = { version = "1", features = ["custom_time"] } +/// ``` +/// +/// Then, we register the function in `src/main.rs`: +/// ```rust +/// # mod static_now_utc { pub fn static_now_utc() -> Timestamp { unimplemented!() } } +/// +/// use identity_core::register_custom_now_utc; +/// use static_now_utc::static_now_utc; +/// +/// register_custom_now_utc!(static_now_utc); +/// ``` +/// +/// Now any user of `now_utc` (direct or indirect) on this target will use the +/// registered function. +#[macro_export] +macro_rules! register_custom_now_utc { + ($path:path) => { + const __GET_TIME_INTERNAL: () = { + // We use Rust ABI to be safe against potential panics in the passed function. + #[no_mangle] + unsafe fn __now_utc_custom() -> Timestamp { + // Make sure the passed function has the type of `now_utc_custom` + type F = fn() -> Timestamp; + let f: F = $path; + f() + } + }; + }; +} + +pub(crate) fn now_utc_custom() -> Timestamp { + extern "Rust" { + fn __now_utc_custom() -> Timestamp; + } + unsafe { __now_utc_custom() } +} diff --git a/identity_core/src/lib.rs b/identity_core/src/lib.rs index b915fcdeba..0e439441e4 100644 --- a/identity_core/src/lib.rs +++ b/identity_core/src/lib.rs @@ -1,7 +1,6 @@ // Copyright 2020-2021 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -#![forbid(unsafe_code)] #![doc = include_str!("./../README.md")] #![allow(clippy::upper_case_acronyms)] #![warn( @@ -19,9 +18,15 @@ #[doc(inline)] pub use serde_json::json; +#[forbid(unsafe_code)] pub mod common; +#[forbid(unsafe_code)] pub mod convert; +#[forbid(unsafe_code)] pub mod error; +#[cfg(feature = "custom_time")] +pub mod custom_time; + pub use self::error::Error; pub use self::error::Result; diff --git a/identity_core/tests/custom_time.rs b/identity_core/tests/custom_time.rs new file mode 100644 index 0000000000..9c700d523e --- /dev/null +++ b/identity_core/tests/custom_time.rs @@ -0,0 +1,18 @@ +// Copyright 2020-2024 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +use identity_core::common::Timestamp; +use identity_core::register_custom_now_utc; + +const STATIC_TIME: i64 = 1724402964; // 2024-08-23T11:33:30+00:00 +pub fn static_now_utc() -> Timestamp { + Timestamp::from_unix(STATIC_TIME).unwrap() +} + +register_custom_now_utc!(static_now_utc); + +#[test] +fn should_use_registered_static_time() { + let timestamp = Timestamp::now_utc(); + assert_eq!(timestamp.to_unix(), STATIC_TIME) +} diff --git a/identity_credential/Cargo.toml b/identity_credential/Cargo.toml index cbc0f074e2..7413a9dc4f 100644 --- a/identity_credential/Cargo.toml +++ b/identity_credential/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "identity_credential" -version = "1.2.0" +version = "1.4.0" authors = ["IOTA Stiftung"] edition = "2021" homepage.workspace = true @@ -12,30 +12,34 @@ rust-version.workspace = true description = "An implementation of the Verifiable Credentials standard." [dependencies] +async-trait = { version = "0.1.64", default-features = false } +bls12_381_plus = { workspace = true, optional = true } flate2 = { version = "1.0.28", default-features = false, features = ["rust_backend"], optional = true } futures = { version = "0.3", default-features = false, optional = true } -identity_core = { version = "=1.2.0", path = "../identity_core", default-features = false } -identity_did = { version = "=1.2.0", path = "../identity_did", default-features = false } -identity_document = { version = "=1.2.0", path = "../identity_document", default-features = false } -identity_verification = { version = "=1.2.0", path = "../identity_verification", default-features = false } +identity_core = { version = "=1.4.0", path = "../identity_core", default-features = false } +identity_did = { version = "=1.4.0", path = "../identity_did", default-features = false } +identity_document = { version = "=1.4.0", path = "../identity_document", default-features = false } +identity_verification = { version = "=1.4.0", path = "../identity_verification", default-features = false } indexmap = { version = "2.0", default-features = false, features = ["std", "serde"] } itertools = { version = "0.11", default-features = false, features = ["use_std"], optional = true } +json-proof-token = { workspace = true, optional = true } once_cell = { version = "1.18", default-features = false, features = ["std"] } reqwest = { version = "0.11", default-features = false, features = ["default-tls", "json", "stream"], optional = true } roaring = { version = "0.10.2", default-features = false, features = ["serde"], optional = true } sd-jwt-payload = { version = "0.2.1", default-features = false, features = ["sha"], optional = true } serde.workspace = true -serde-aux = { version = "4.3.1", default-features = false, optional = true } +serde-aux = { version = "4.3.1", default-features = false } serde_json.workspace = true serde_repr = { version = "0.1", default-features = false, optional = true } strum.workspace = true thiserror.workspace = true url = { version = "2.5", default-features = false } +zkryptium = { workspace = true, optional = true } [dev-dependencies] anyhow = "1.0.62" identity_eddsa_verifier = { path = "../identity_eddsa_verifier", default-features = false, features = ["ed25519"] } -iota-crypto = { version = "0.23", default-features = false, features = ["ed25519", "std", "random"] } +iota-crypto = { version = "0.23.2", default-features = false, features = ["ed25519", "std", "random"] } proptest = { version = "1.4.0", default-features = false, features = ["std"] } [package.metadata.docs.rs] @@ -49,11 +53,12 @@ default = ["revocation-bitmap", "validator", "credential", "presentation", "doma credential = [] presentation = ["credential"] revocation-bitmap = ["dep:flate2", "dep:roaring"] -status-list-2021 = ["revocation-bitmap", "dep:serde-aux"] +status-list-2021 = ["revocation-bitmap"] validator = ["dep:itertools", "dep:serde_repr", "credential", "presentation"] domain-linkage = ["validator"] domain-linkage-fetch = ["domain-linkage", "dep:reqwest", "dep:futures"] -sd-jwt = ["credential", "validator", "sd-jwt-payload"] +sd-jwt = ["credential", "validator", "dep:sd-jwt-payload"] +jpt-bbs-plus = ["credential", "validator", "dep:zkryptium", "dep:bls12_381_plus", "dep:json-proof-token"] [lints] workspace = true diff --git a/identity_credential/src/credential/credential.rs b/identity_credential/src/credential/credential.rs index decbb8b7c2..03c482c6f6 100644 --- a/identity_credential/src/credential/credential.rs +++ b/identity_credential/src/credential/credential.rs @@ -5,6 +5,8 @@ use core::fmt::Display; use core::fmt::Formatter; use identity_core::convert::ToJson; +#[cfg(feature = "jpt-bbs-plus")] +use jsonprooftoken::jpt::claims::JptClaims; use once_cell::sync::Lazy; use serde::Deserialize; use serde::Serialize; @@ -174,6 +176,16 @@ impl Credential { .to_json() .map_err(|err| Error::JwtClaimsSetSerializationError(err.into())) } + + ///Serializes the [`Credential`] as a JPT claims set + #[cfg(feature = "jpt-bbs-plus")] + pub fn serialize_jpt(&self, custom_claims: Option) -> Result + where + T: ToOwned + serde::Serialize + serde::de::DeserializeOwned, + { + let jwt_representation: CredentialJwtClaims<'_, T> = CredentialJwtClaims::new(self, custom_claims)?; + Ok(jwt_representation.into()) + } } impl Display for Credential diff --git a/identity_credential/src/credential/jpt.rs b/identity_credential/src/credential/jpt.rs new file mode 100644 index 0000000000..feab003949 --- /dev/null +++ b/identity_credential/src/credential/jpt.rs @@ -0,0 +1,33 @@ +// Copyright 2020-2024 IOTA Stiftung, Fondazione Links +// SPDX-License-Identifier: Apache-2.0 + +use serde::Deserialize; +use serde::Serialize; + +/// This JSON Proof Token could represent a JWP both in the Issued and Presented forms. +#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq)] +pub struct Jpt(String); + +impl Jpt { + /// Creates a new `Jwt` from the given string. + pub fn new(jpt_string: String) -> Self { + Self(jpt_string) + } + + /// Returns a reference of the JWT string. + pub fn as_str(&self) -> &str { + &self.0 + } +} + +impl From for Jpt { + fn from(jpt: String) -> Self { + Self::new(jpt) + } +} + +impl From for String { + fn from(jpt: Jpt) -> Self { + jpt.0 + } +} diff --git a/identity_credential/src/credential/jwp_credential_options.rs b/identity_credential/src/credential/jwp_credential_options.rs new file mode 100644 index 0000000000..f607c2f68e --- /dev/null +++ b/identity_credential/src/credential/jwp_credential_options.rs @@ -0,0 +1,28 @@ +// Copyright 2020-2024 IOTA Stiftung, Fondazione Links +// SPDX-License-Identifier: Apache-2.0 + +/// Options for creating a JSON Web Proof. +#[non_exhaustive] +#[derive(Debug, Default, serde::Serialize, serde::Deserialize, Eq, PartialEq, Clone)] +#[serde(rename_all = "camelCase")] +#[serde(default)] +pub struct JwpCredentialOptions { + /// The kid to set in the Issuer Protected Header. + /// + /// If unset, the kid of the JWK with which the JWP is produced is used. + #[serde(skip_serializing_if = "Option::is_none")] + pub kid: Option, +} + +impl JwpCredentialOptions { + /// Creates a new [`JwsSignatureOptions`]. + pub fn new() -> Self { + Self::default() + } + + /// Replace the value of the `kid` field. + pub fn kid(mut self, value: impl Into) -> Self { + self.kid = Some(value.into()); + self + } +} diff --git a/identity_credential/src/credential/jwt_serialization.rs b/identity_credential/src/credential/jwt_serialization.rs index 83c4278559..b1d1886183 100644 --- a/identity_credential/src/credential/jwt_serialization.rs +++ b/identity_credential/src/credential/jwt_serialization.rs @@ -3,6 +3,8 @@ use std::borrow::Cow; +#[cfg(feature = "jpt-bbs-plus")] +use jsonprooftoken::jpt::claims::JptClaims; use serde::Deserialize; use serde::Serialize; @@ -42,7 +44,7 @@ impl TryFrom for Credential { /// This type is opinionated in the following ways: /// 1. Serialization tries to duplicate as little as possible between the required registered claims and the `vc` entry. /// 2. Only allows serializing/deserializing claims "exp, iss, nbf &/or iat, jti, sub and vc". Other custom properties -/// must be set in the `vc` entry. +/// must be set in the `vc` entry. #[derive(Serialize, Deserialize)] pub(crate) struct CredentialJwtClaims<'credential, T = Object> where @@ -372,6 +374,57 @@ where proof: Option>, } +#[cfg(feature = "jpt-bbs-plus")] +impl<'credential, T> From> for JptClaims +where + T: ToOwned + Serialize, + ::Owned: DeserializeOwned, +{ + fn from(item: CredentialJwtClaims<'credential, T>) -> Self { + let CredentialJwtClaims { + exp, + iss, + issuance_date, + jti, + sub, + vc, + custom, + } = item; + + let mut claims = JptClaims::new(); + + if let Some(exp) = exp { + claims.set_exp(exp); + } + + claims.set_iss(iss.url().to_string()); + + if let Some(iat) = issuance_date.iat { + claims.set_iat(iat); + } + + if let Some(nbf) = issuance_date.nbf { + claims.set_nbf(nbf); + } + + if let Some(jti) = jti { + claims.set_jti(jti.to_string()); + } + + if let Some(sub) = sub { + claims.set_sub(sub.to_string()); + } + + claims.set_claim(Some("vc"), vc, true); + + if let Some(custom) = custom { + claims.set_claim(None, custom, true); + } + + claims + } +} + #[cfg(test)] mod tests { use identity_core::common::Object; diff --git a/identity_credential/src/credential/linked_verifiable_presentation_service.rs b/identity_credential/src/credential/linked_verifiable_presentation_service.rs new file mode 100644 index 0000000000..20e34e0a97 --- /dev/null +++ b/identity_credential/src/credential/linked_verifiable_presentation_service.rs @@ -0,0 +1,207 @@ +// Copyright 2020-2024 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +use identity_core::common::Object; +use identity_core::common::OrderedSet; +use identity_core::common::Url; +use identity_did::DIDUrl; +use identity_document::service::Service; +use identity_document::service::ServiceBuilder; +use identity_document::service::ServiceEndpoint; +use serde::Deserialize; +use serde::Serialize; + +use crate::error::Result; +use crate::Error; +use crate::Error::LinkedVerifiablePresentationError; + +/// A service wrapper for a [Linked Verifiable Presentation Service Endpoint](https://identity.foundation/linked-vp/#linked-verifiable-presentation-service-endpoint). +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(try_from = "Service", into = "Service")] +pub struct LinkedVerifiablePresentationService(Service); + +impl TryFrom for LinkedVerifiablePresentationService { + type Error = Error; + + fn try_from(service: Service) -> std::result::Result { + LinkedVerifiablePresentationService::check_structure(&service)?; + Ok(LinkedVerifiablePresentationService(service)) + } +} + +impl From for Service { + fn from(service: LinkedVerifiablePresentationService) -> Self { + service.0 + } +} + +impl LinkedVerifiablePresentationService { + pub(crate) fn linked_verifiable_presentation_service_type() -> &'static str { + "LinkedVerifiablePresentation" + } + + /// Constructs a new `LinkedVerifiablePresentationService` that wraps a spec compliant + /// [Linked Verifiable Presentation Service Endpoint](https://identity.foundation/linked-vp/#linked-verifiable-presentation-service-endpoint). + pub fn new( + did_url: DIDUrl, + verifiable_presentation_urls: impl Into>, + properties: Object, + ) -> Result { + let verifiable_presentation_urls: OrderedSet = verifiable_presentation_urls.into(); + let builder: ServiceBuilder = Service::builder(properties) + .id(did_url) + .type_(Self::linked_verifiable_presentation_service_type()); + if verifiable_presentation_urls.len() == 1 { + let vp_url = verifiable_presentation_urls + .into_iter() + .next() + .expect("element 0 exists"); + let service = builder + .service_endpoint(vp_url) + .build() + .map_err(|err| LinkedVerifiablePresentationError(Box::new(err)))?; + Ok(Self(service)) + } else { + let service = builder + .service_endpoint(ServiceEndpoint::Set(verifiable_presentation_urls)) + .build() + .map_err(|err| LinkedVerifiablePresentationError(Box::new(err)))?; + Ok(Self(service)) + } + } + + /// Checks the semantic structure of a Linked Verifiable Presentation Service. + /// + /// Note: `{"type": ["LinkedVerifiablePresentation"]}` might be serialized the same way as `{"type": + /// "LinkedVerifiablePresentation"}` which passes the semantic check. + pub fn check_structure(service: &Service) -> Result<()> { + if service.type_().len() != 1 { + return Err(LinkedVerifiablePresentationError("invalid service type".into())); + } + + let service_type = service + .type_() + .get(0) + .ok_or_else(|| LinkedVerifiablePresentationError("missing service type".into()))?; + + if service_type != Self::linked_verifiable_presentation_service_type() { + return Err(LinkedVerifiablePresentationError( + format!( + "expected `{}` service type", + Self::linked_verifiable_presentation_service_type() + ) + .into(), + )); + } + + match service.service_endpoint() { + ServiceEndpoint::One(_) => Ok(()), + ServiceEndpoint::Set(_) => Ok(()), + ServiceEndpoint::Map(_) => Err(LinkedVerifiablePresentationError( + "service endpoints must be either a string or a set".into(), + )), + } + } + + /// Returns the Verifiable Presentations contained in the Linked Verifiable Presentation Service. + pub fn verifiable_presentation_urls(&self) -> &[Url] { + match self.0.service_endpoint() { + ServiceEndpoint::One(endpoint) => std::slice::from_ref(endpoint), + ServiceEndpoint::Set(endpoints) => endpoints.as_slice(), + ServiceEndpoint::Map(_) => { + unreachable!("the service endpoint is never a map per the `LinkedVerifiablePresentationService` type invariant") + } + } + } + + /// Returns a reference to the `Service` id. + pub fn id(&self) -> &DIDUrl { + self.0.id() + } +} + +#[cfg(test)] +mod tests { + use crate::credential::linked_verifiable_presentation_service::LinkedVerifiablePresentationService; + use identity_core::common::Object; + use identity_core::common::OrderedSet; + use identity_core::common::Url; + use identity_core::convert::FromJson; + use identity_did::DIDUrl; + use identity_document::service::Service; + use serde_json::json; + + #[test] + fn test_create_service_single_vp() { + let mut linked_vps: OrderedSet = OrderedSet::new(); + linked_vps.append(Url::parse("https://foo.example-1.com").unwrap()); + + let service: LinkedVerifiablePresentationService = LinkedVerifiablePresentationService::new( + DIDUrl::parse("did:example:123#foo").unwrap(), + linked_vps, + Object::new(), + ) + .unwrap(); + + let service_from_json: Service = Service::from_json_value(json!({ + "id": "did:example:123#foo", + "type": "LinkedVerifiablePresentation", + "serviceEndpoint": "https://foo.example-1.com" + })) + .unwrap(); + assert_eq!(Service::from(service), service_from_json); + } + + #[test] + fn test_create_service_multiple_vps() { + let url_1 = "https://foo.example-1.com"; + let url_2 = "https://bar.example-2.com"; + let mut linked_vps = OrderedSet::new(); + linked_vps.append(Url::parse(url_1).unwrap()); + linked_vps.append(Url::parse(url_2).unwrap()); + + let service: LinkedVerifiablePresentationService = LinkedVerifiablePresentationService::new( + DIDUrl::parse("did:example:123#foo").unwrap(), + linked_vps, + Object::new(), + ) + .unwrap(); + + let service_from_json: Service = Service::from_json_value(json!({ + "id":"did:example:123#foo", + "type": "LinkedVerifiablePresentation", + "serviceEndpoint": [url_1, url_2] + })) + .unwrap(); + assert_eq!(Service::from(service), service_from_json); + } + + #[test] + fn test_valid_single_vp() { + let service: Service = Service::from_json_value(json!({ + "id": "did:example:123#foo", + "type": "LinkedVerifiablePresentation", + "serviceEndpoint": "https://foo.example-1.com" + })) + .unwrap(); + let service: LinkedVerifiablePresentationService = LinkedVerifiablePresentationService::try_from(service).unwrap(); + let linked_vps: Vec = vec![Url::parse("https://foo.example-1.com").unwrap()]; + assert_eq!(service.verifiable_presentation_urls(), linked_vps); + } + + #[test] + fn test_valid_multiple_vps() { + let service: Service = Service::from_json_value(json!({ + "id": "did:example:123#foo", + "type": "LinkedVerifiablePresentation", + "serviceEndpoint": ["https://foo.example-1.com", "https://foo.example-2.com"] + })) + .unwrap(); + let service: LinkedVerifiablePresentationService = LinkedVerifiablePresentationService::try_from(service).unwrap(); + let linked_vps: Vec = vec![ + Url::parse("https://foo.example-1.com").unwrap(), + Url::parse("https://foo.example-2.com").unwrap(), + ]; + assert_eq!(service.verifiable_presentation_urls(), linked_vps); + } +} diff --git a/identity_credential/src/credential/mod.rs b/identity_credential/src/credential/mod.rs index 8738484459..3d7422c83b 100644 --- a/identity_credential/src/credential/mod.rs +++ b/identity_credential/src/credential/mod.rs @@ -9,10 +9,15 @@ mod builder; mod credential; mod evidence; mod issuer; +#[cfg(feature = "jpt-bbs-plus")] +mod jpt; +#[cfg(feature = "jpt-bbs-plus")] +mod jwp_credential_options; mod jws; mod jwt; mod jwt_serialization; mod linked_domain_service; +mod linked_verifiable_presentation_service; mod policy; mod proof; mod refresh; @@ -26,14 +31,21 @@ pub use self::builder::CredentialBuilder; pub use self::credential::Credential; pub use self::evidence::Evidence; pub use self::issuer::Issuer; +#[cfg(feature = "jpt-bbs-plus")] +pub use self::jpt::Jpt; +#[cfg(feature = "jpt-bbs-plus")] +pub use self::jwp_credential_options::JwpCredentialOptions; pub use self::jws::Jws; pub use self::jwt::Jwt; pub use self::jwt_serialization::JwtCredential; pub use self::linked_domain_service::LinkedDomainService; +pub use self::linked_verifiable_presentation_service::LinkedVerifiablePresentationService; pub use self::policy::Policy; pub use self::proof::Proof; pub use self::refresh::RefreshService; #[cfg(feature = "revocation-bitmap")] +pub use self::revocation_bitmap_status::try_index_to_u32; +#[cfg(feature = "revocation-bitmap")] pub use self::revocation_bitmap_status::RevocationBitmapStatus; pub use self::schema::Schema; pub use self::status::Status; diff --git a/identity_credential/src/credential/proof.rs b/identity_credential/src/credential/proof.rs index 03e4bca663..ab779014a2 100644 --- a/identity_credential/src/credential/proof.rs +++ b/identity_credential/src/credential/proof.rs @@ -52,7 +52,7 @@ mod tests { assert_eq!(proof.type_, "test-proof"); let value = proof .properties - .get(&"signature".to_owned()) + .get("signature") .expect("property in proof doesn't exist"); assert_eq!(value, "abc123"); } @@ -88,7 +88,7 @@ mod tests { assert_eq!(proof.type_, "RsaSignature2018"); let value = proof .properties - .get(&"proofPurpose".to_owned()) + .get("proofPurpose") .expect("property in proof doesn't exist"); assert_eq!(value, "assertionMethod"); assert_eq!(proof.properties.len(), 4); diff --git a/identity_credential/src/credential/revocation_bitmap_status.rs b/identity_credential/src/credential/revocation_bitmap_status.rs index d4310d154a..b607e1758d 100644 --- a/identity_credential/src/credential/revocation_bitmap_status.rs +++ b/identity_credential/src/credential/revocation_bitmap_status.rs @@ -129,7 +129,7 @@ impl From for Status { } /// Attempts to convert the given index string to a u32. -fn try_index_to_u32(index: &str, name: &str) -> Result { +pub fn try_index_to_u32(index: &str, name: &str) -> Result { u32::from_str(index).map_err(|err| { Error::InvalidStatus(format!( "{name} cannot be converted to an unsigned, 32-bit integer: {err}", diff --git a/identity_credential/src/domain_linkage/domain_linkage_validator.rs b/identity_credential/src/domain_linkage/domain_linkage_validator.rs index 24969c1c65..be67c96832 100644 --- a/identity_credential/src/domain_linkage/domain_linkage_validator.rs +++ b/identity_credential/src/domain_linkage/domain_linkage_validator.rs @@ -38,15 +38,15 @@ impl JwtDomainLinkageValidator { /// Validates the linkage between a domain and a DID. /// [`DomainLinkageConfiguration`] is validated according to [DID Configuration Resource Verification](https://identity.foundation/.well-known/resources/did-configuration/#did-configuration-resource-verification). /// - /// * `issuer`: DID Document of the linked DID. Issuer of the Domain Linkage Credential included - /// in the Domain Linkage Configuration. + /// * `issuer`: DID Document of the linked DID. Issuer of the Domain Linkage Credential included in the Domain Linkage + /// Configuration. /// * `configuration`: Domain Linkage Configuration fetched from the domain at "/.well-known/did-configuration.json". /// * `domain`: domain from which the Domain Linkage Configuration has been fetched. /// * `validation_options`: Further validation options to be applied on the Domain Linkage Credential. /// /// # Note: /// - Only the [JSON Web Token Proof Format](https://identity.foundation/.well-known/resources/did-configuration/#json-web-token-proof-format) - /// is supported. + /// is supported. /// - Only the Credential issued by `issuer` is verified. /// /// # Errors diff --git a/identity_credential/src/error.rs b/identity_credential/src/error.rs index 62964415e4..1c814c3899 100644 --- a/identity_credential/src/error.rs +++ b/identity_credential/src/error.rs @@ -37,6 +37,9 @@ pub enum Error { /// Caused when constructing an invalid `LinkedDomainService` or `DomainLinkageConfiguration`. #[error("domain linkage error: {0}")] DomainLinkageError(#[source] Box), + /// Caused when constructing an invalid `LinkedVerifiablePresentationService`. + #[error("linked verifiable presentation error: {0}")] + LinkedVerifiablePresentationError(#[source] Box), /// Caused when attempting to encode a `Credential` containing multiple subjects as a JWT. #[error("could not create JWT claim set from verifiable credential: more than one subject")] MoreThanOneSubjectInJwt, @@ -68,4 +71,12 @@ pub enum Error { /// JSON. #[error("could not deserialize JWT claims set")] JwtClaimsSetDeserializationError(#[source] Box), + + /// Caused by a failure to deserialize the JPT claims set representation of a `Credential` JSON. + #[error("could not deserialize JWT claims set")] + JptClaimsSetDeserializationError(#[source] Box), + + /// Cause by an invalid attribute path + #[error("Attribute Not found")] + SelectiveDisclosureError, } diff --git a/identity_credential/src/presentation/jwp_presentation_builder.rs b/identity_credential/src/presentation/jwp_presentation_builder.rs new file mode 100644 index 0000000000..e6919058a2 --- /dev/null +++ b/identity_credential/src/presentation/jwp_presentation_builder.rs @@ -0,0 +1,124 @@ +// Copyright 2020-2024 IOTA Stiftung, Fondazione Links +// SPDX-License-Identifier: Apache-2.0 + +use crate::error::Error; +use crate::error::Result; +use jsonprooftoken::jwp::header::PresentationProtectedHeader; +use jsonprooftoken::jwp::issued::JwpIssued; +use jsonprooftoken::jwp::presented::JwpPresentedBuilder; + +/// Used to construct a JwpPresentedBuilder and handle the selective disclosure of attributes. +// - @context MUST NOT be blinded +// - id MUST be blinded +// - type MUST NOT be blinded +// - issuer MUST NOT be blinded +// - issuanceDate MUST be blinded (if Timeframe Revocation mechanism is used) +// - expirationDate MUST be blinded (if Timeframe Revocation mechanism is used) +// - credentialSubject (Users have to choose which attribute must be blinded) +// - credentialSchema MUST NOT be blinded +// - credentialStatus MUST NOT be blinded +// - refreshService MUST NOT be blinded (probably will be used for Timeslot Revocation mechanism) +// - termsOfUse NO reason to use it in ZK VC (will be in any case blinded) +// - evidence (Users have to choose which attribute must be blinded) +pub struct SelectiveDisclosurePresentation { + jwp_builder: JwpPresentedBuilder, +} + +impl SelectiveDisclosurePresentation { + /// Initialize a presentation starting from an Issued JWP. + /// The following properties are concealed by default: + /// + /// - `exp` + /// - `expirationDate` + /// - `issuanceDate` + /// - `jti` + /// - `nbf` + /// - `sub` + /// - `termsOfUse` + /// - `vc.credentialStatus.revocationBitmapIndex` + /// - `vc.credentialSubject.id` + pub fn new(issued_jwp: &JwpIssued) -> Self { + let mut jwp_builder = JwpPresentedBuilder::new(issued_jwp); + + jwp_builder.set_undisclosed("jti").ok(); // contains the credential's id, provides linkability + + jwp_builder.set_undisclosed("issuanceDate").ok(); // Depending on the revocation method used it will be necessary or not + jwp_builder.set_undisclosed("nbf").ok(); + + jwp_builder.set_undisclosed("expirationDate").ok(); // Depending on the revocation method used it will be necessary or not + jwp_builder.set_undisclosed("exp").ok(); + + jwp_builder.set_undisclosed("termsOfUse").ok(); // Provides linkability so, there is NO reason to use it in ZK VC + + jwp_builder + .set_undisclosed("vc.credentialStatus.revocationBitmapIndex") + .ok(); + + jwp_builder.set_undisclosed("vc.credentialSubject.id").ok(); + jwp_builder.set_undisclosed("sub").ok(); + + Self { jwp_builder } + } + + /// Selectively conceal "credentialSubject" attributes. + /// # Example + /// ```ignore + /// { + /// "id": 1234, + /// "name": "Alice", + /// "mainCourses": ["Object-oriented Programming", "Mathematics"], + /// "degree": { + /// "type": "BachelorDegree", + /// "name": "Bachelor of Science and Arts", + /// }, + /// "GPA": "4.0", + /// } + /// ``` + /// If you want to undisclose for example the Mathematics course and the name of the degree: + /// ```ignore + /// presentation_builder.conceal_in_subject("mainCourses[1]"); + /// presentation_builder.conceal_in_subject("degree.name"); + /// ``` + pub fn conceal_in_subject(&mut self, path: &str) -> Result<(), Error> { + let _ = self + .jwp_builder + .set_undisclosed(&("vc.credentialSubject.".to_owned() + path)) + .map_err(|_| Error::SelectiveDisclosureError); + Ok(()) + } + + /// Undisclose "evidence" attributes. + /// # Example + /// ```ignore + /// { + /// "id": "https://example.edu/evidence/f2aeec97-fc0d-42bf-8ca7-0548192d4231", + /// "type": ["DocumentVerification"], + /// "verifier": "https://example.edu/issuers/14", + /// "evidenceDocument": "DriversLicense", + /// "subjectPresence": "Physical", + /// "documentPresence": "Physical", + /// "licenseNumber": "123AB4567" + /// } + /// ``` + /// To conceal the `licenseNumber` field: + /// ```ignore + /// presentation_builder.conceal_in_evidence("licenseNumber"); + /// ``` + pub fn conceal_in_evidence(&mut self, path: &str) -> Result<(), Error> { + let _ = self + .jwp_builder + .set_undisclosed(&("vc.evidence.".to_owned() + path)) + .map_err(|_| Error::SelectiveDisclosureError); + Ok(()) + } + + /// Set Presentation Protected Header. + pub fn set_presentation_header(&mut self, ph: PresentationProtectedHeader) { + self.jwp_builder.set_presentation_protected_header(ph); + } + + /// Get the builder. + pub fn builder(&self) -> &JwpPresentedBuilder { + &self.jwp_builder + } +} diff --git a/identity_credential/src/presentation/jwp_presentation_options.rs b/identity_credential/src/presentation/jwp_presentation_options.rs new file mode 100644 index 0000000000..fba35a7f1f --- /dev/null +++ b/identity_credential/src/presentation/jwp_presentation_options.rs @@ -0,0 +1,33 @@ +// Copyright 2020-2024 IOTA Stiftung, Fondazione Links +// SPDX-License-Identifier: Apache-2.0 + +use identity_core::common::Url; +use serde::Deserialize; +use serde::Serialize; + +/// Options to be set in the JWT claims of a verifiable presentation. +#[derive(Clone, Debug, Serialize, Deserialize, Default)] +pub struct JwpPresentationOptions { + /// Sets the audience for presentation (`aud` property in JWP Presentation Header). + /// Default: `None`. + #[serde(skip_serializing_if = "Option::is_none")] + pub audience: Option, + + /// The nonce to be placed in the Presentation Protected Header. + #[serde(skip_serializing_if = "Option::is_none")] + pub nonce: Option, +} + +impl JwpPresentationOptions { + /// Sets the audience for presentation (`aud` property in JWT claims). + pub fn audience(mut self, audience: Url) -> Self { + self.audience = Some(audience); + self + } + + /// Replace the value of the `nonce` field. + pub fn nonce(mut self, value: impl Into) -> Self { + self.nonce = Some(value.into()); + self + } +} diff --git a/identity_credential/src/presentation/mod.rs b/identity_credential/src/presentation/mod.rs index 94f8768e02..76adc145c6 100644 --- a/identity_credential/src/presentation/mod.rs +++ b/identity_credential/src/presentation/mod.rs @@ -5,14 +5,22 @@ #![allow(clippy::module_inception)] +#[cfg(feature = "jpt-bbs-plus")] +mod jwp_presentation_builder; +#[cfg(feature = "jpt-bbs-plus")] +mod jwp_presentation_options; mod jwt_presentation_options; mod jwt_serialization; mod presentation; mod presentation_builder; +#[cfg(feature = "jpt-bbs-plus")] +pub use self::jwp_presentation_builder::SelectiveDisclosurePresentation; pub use self::jwt_presentation_options::JwtPresentationOptions; pub use self::presentation::Presentation; pub use self::presentation_builder::PresentationBuilder; +#[cfg(feature = "jpt-bbs-plus")] +pub use jwp_presentation_options::JwpPresentationOptions; #[cfg(feature = "validator")] pub(crate) use self::jwt_serialization::PresentationJwtClaims; diff --git a/identity_credential/src/revocation/mod.rs b/identity_credential/src/revocation/mod.rs index 6732ff4194..1553022c74 100644 --- a/identity_credential/src/revocation/mod.rs +++ b/identity_credential/src/revocation/mod.rs @@ -9,6 +9,11 @@ mod revocation_bitmap_2022; #[cfg(feature = "status-list-2021")] pub mod status_list_2021; +#[cfg(feature = "jpt-bbs-plus")] +pub mod validity_timeframe_2024; + pub use self::error::RevocationError; pub use self::error::RevocationResult; pub use revocation_bitmap_2022::*; +#[cfg(feature = "jpt-bbs-plus")] +pub use validity_timeframe_2024::*; diff --git a/identity_credential/src/revocation/status_list_2021/credential.rs b/identity_credential/src/revocation/status_list_2021/credential.rs index cc52916967..ed96643cb7 100644 --- a/identity_credential/src/revocation/status_list_2021/credential.rs +++ b/identity_credential/src/revocation/status_list_2021/credential.rs @@ -123,7 +123,7 @@ impl StatusList2021Credential { /// /// ## Note: /// - A revoked credential cannot ever be unrevoked and will lead to a - /// [`StatusList2021CredentialError::UnreversibleRevocation`]. + /// [`StatusList2021CredentialError::UnreversibleRevocation`]. /// - Trying to set `revoked_or_suspended` to `false` for an already valid credential will have no impact. pub fn set_credential_status( &mut self, @@ -279,7 +279,7 @@ impl StatusList2021CredentialSubject { return Err(StatusList2021CredentialError::MultipleCredentialSubject); }; if let Some(subject_type) = subject.properties.get("type") { - if !subject_type.as_str().is_some_and(|t| t == CREDENTIAL_SUBJECT_TYPE) { + if subject_type.as_str() != Some(CREDENTIAL_SUBJECT_TYPE) { return Err(StatusList2021CredentialError::InvalidProperty("credentialSubject.type")); } } else { diff --git a/identity_credential/src/revocation/status_list_2021/entry.rs b/identity_credential/src/revocation/status_list_2021/entry.rs index 7eecf2f28e..92415d06b7 100644 --- a/identity_credential/src/revocation/status_list_2021/entry.rs +++ b/identity_credential/src/revocation/status_list_2021/entry.rs @@ -37,6 +37,14 @@ where .map(ToOwned::to_owned) } +/// Serialize usize as string. +fn serialize_number_as_string(value: &usize, serializer: S) -> Result +where + S: serde::Serializer, +{ + serializer.serialize_str(&value.to_string()) +} + /// [StatusList2021Entry](https://www.w3.org/TR/2023/WD-vc-status-list-20230427/#statuslist2021entry) implementation. #[derive(Debug, Clone, Serialize, Deserialize, Hash, Eq, PartialEq)] #[serde(rename_all = "camelCase")] @@ -45,7 +53,10 @@ pub struct StatusList2021Entry { #[serde(rename = "type", deserialize_with = "deserialize_status_entry_type")] type_: String, status_purpose: StatusPurpose, - #[serde(deserialize_with = "serde_aux::prelude::deserialize_number_from_string")] + #[serde( + deserialize_with = "serde_aux::prelude::deserialize_number_from_string", + serialize_with = "serialize_number_as_string" + )] status_list_index: usize, status_list_credential: Url, } @@ -142,4 +153,13 @@ mod tests { }); serde_json::from_value::(status).expect("wrong type"); } + + #[test] + fn test_status_list_index_serialization() { + let base_url = Url::parse("https://example.com/credentials/status/3").unwrap(); + + let entry1 = StatusList2021Entry::new(base_url.clone(), StatusPurpose::Revocation, 94567, None); + let json1 = serde_json::to_value(&entry1).unwrap(); + assert_eq!(json1["statusListIndex"], "94567"); + } } diff --git a/identity_credential/src/revocation/validity_timeframe_2024/mod.rs b/identity_credential/src/revocation/validity_timeframe_2024/mod.rs new file mode 100644 index 0000000000..179d5696ec --- /dev/null +++ b/identity_credential/src/revocation/validity_timeframe_2024/mod.rs @@ -0,0 +1,8 @@ +// Copyright 2020-2024 IOTA Stiftung, Fondazione Links +// SPDX-License-Identifier: Apache-2.0 + +//! Implementation of a new Revocation mechanism for ZK Verifiable Credentials. + +mod revocation_timeframe_status; + +pub use revocation_timeframe_status::*; diff --git a/identity_credential/src/revocation/validity_timeframe_2024/revocation_timeframe_status.rs b/identity_credential/src/revocation/validity_timeframe_2024/revocation_timeframe_status.rs new file mode 100644 index 0000000000..0a70589112 --- /dev/null +++ b/identity_credential/src/revocation/validity_timeframe_2024/revocation_timeframe_status.rs @@ -0,0 +1,220 @@ +// Copyright 2020-2024 IOTA Stiftung, Fondazione Links +// SPDX-License-Identifier: Apache-2.0 + +use crate::credential::Status; +use crate::error::Error; +use crate::error::Result; +use identity_core::common::Duration; +use identity_core::common::Object; +use identity_core::common::Timestamp; +use identity_core::common::Url; +use identity_core::common::Value; +use serde::de::Visitor; +use serde::Deserialize; +use serde::Serialize; + +fn deserialize_status_entry_type<'de, D>(deserializer: D) -> Result +where + D: serde::Deserializer<'de>, +{ + struct ExactStrVisitor(&'static str); + impl<'a> Visitor<'a> for ExactStrVisitor { + type Value = &'static str; + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(formatter, "the exact string \"{}\"", self.0) + } + fn visit_str(self, str: &str) -> Result { + if str == self.0 { + Ok(self.0) + } else { + Err(E::custom(format!("not \"{}\"", self.0))) + } + } + } + + deserializer + .deserialize_str(ExactStrVisitor(RevocationTimeframeStatus::TYPE)) + .map(ToOwned::to_owned) +} + +/// Information used to determine the current status of a [`Credential`][crate::credential::Credential] +#[derive(Debug, Clone, Serialize, Deserialize, Hash, Eq, PartialEq)] +#[serde(rename_all = "camelCase")] +pub struct RevocationTimeframeStatus { + id: Url, + #[serde(rename = "type", deserialize_with = "deserialize_status_entry_type")] + type_: String, + start_validity_timeframe: Timestamp, + end_validity_timeframe: Timestamp, + #[serde( + deserialize_with = "serde_aux::prelude::deserialize_option_number_from_string", + skip_serializing_if = "Option::is_none" + )] + revocation_bitmap_index: Option, +} + +impl RevocationTimeframeStatus { + /// startValidityTimeframe property name. + pub const START_TIMEFRAME_PROPERTY: &'static str = "startValidityTimeframe"; + /// endValidityTimeframe property name. + pub const END_TIMEFRAME_PROPERTY: &'static str = "endValidityTimeframe"; + /// Type name of the revocation mechanism. + pub const TYPE: &'static str = "RevocationTimeframe2024"; + /// index property name for [`Status`] conversion + const INDEX_PROPERTY: &'static str = "revocationBitmapIndex"; + + /// Creates a new `RevocationTimeframeStatus`. + pub fn new(start_validity: Option, duration: Duration, id: Url, index: u32) -> Result { + let start_validity_timeframe = start_validity.unwrap_or(Timestamp::now_utc()); + let end_validity_timeframe = start_validity_timeframe + .checked_add(duration) + .ok_or(Error::InvalidStatus( + "With that granularity, endValidityTimeFrame will turn out not to be in the valid range for RFC 3339" + .to_owned(), + ))?; + + Ok(Self { + id, + type_: Self::TYPE.to_owned(), + start_validity_timeframe, + end_validity_timeframe, + revocation_bitmap_index: Some(index), + }) + } + + /// Get startValidityTimeframe value. + pub fn start_validity_timeframe(&self) -> Timestamp { + self.start_validity_timeframe + } + + /// Get endValidityTimeframe value. + pub fn end_validity_timeframe(&self) -> Timestamp { + self.end_validity_timeframe + } + + /// Returns the [`Url`] of the `RevocationBitmapStatus`, which should resolve + /// to a `RevocationBitmap2022` service in a DID Document. + pub fn id(&self) -> &Url { + &self.id + } + + /// Returns the index of the credential in the issuer's revocation bitmap if it can be decoded. + pub fn index(&self) -> Option { + self.revocation_bitmap_index + } +} + +impl TryFrom<&Status> for RevocationTimeframeStatus { + type Error = Error; + fn try_from(status: &Status) -> Result { + // serialize into String to ensure macros work properly + // see [issue](https://github.com/iddm/serde-aux/issues/34#issuecomment-1508207530) in `serde-aux` + let json_status: String = serde_json::to_string(&status) + .map_err(|err| Self::Error::InvalidStatus(format!("failed to read `Status`; {}", &err.to_string())))?; + serde_json::from_str(&json_status).map_err(|err| { + Self::Error::InvalidStatus(format!( + "failed to convert `Status` to `RevocationTimeframeStatus`; {}", + &err.to_string(), + )) + }) + } +} + +impl From for Status { + fn from(revocation_timeframe_status: RevocationTimeframeStatus) -> Self { + let mut properties = Object::new(); + properties.insert( + RevocationTimeframeStatus::START_TIMEFRAME_PROPERTY.to_owned(), + Value::String(revocation_timeframe_status.start_validity_timeframe().to_rfc3339()), + ); + properties.insert( + RevocationTimeframeStatus::END_TIMEFRAME_PROPERTY.to_owned(), + Value::String(revocation_timeframe_status.end_validity_timeframe().to_rfc3339()), + ); + if let Some(value) = revocation_timeframe_status.index() { + properties.insert( + RevocationTimeframeStatus::INDEX_PROPERTY.to_owned(), + Value::String(value.to_string()), + ); + } + + Status::new_with_properties( + revocation_timeframe_status.id, + RevocationTimeframeStatus::TYPE.to_owned(), + properties, + ) + } +} + +/// Verifier +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct VerifierRevocationTimeframeStatus(pub(crate) RevocationTimeframeStatus); + +impl TryFrom for VerifierRevocationTimeframeStatus { + type Error = Error; + + fn try_from(status: Status) -> Result { + Ok(Self((&status).try_into().map_err(|err: Error| { + Self::Error::InvalidStatus(format!( + "failed to convert `Status` to `VerifierRevocationTimeframeStatus`; {}", + &err.to_string() + )) + })?)) + } +} + +impl From for Status { + fn from(status: VerifierRevocationTimeframeStatus) -> Self { + status.0.into() + } +} + +#[cfg(test)] +mod tests { + use super::*; + + const EXAMPLE_SERIALIZED: &str = r#"{ + "id": "did:iota:snd:0xae6ccfdb155a69e0ef153fb5fcfd50c08a8fee36babe1f7d71dede8f4e202432#my-revocation-service", + "startValidityTimeframe": "2024-03-19T13:57:50Z", + "endValidityTimeframe": "2024-03-19T13:58:50Z", + "revocationBitmapIndex": "5", + "type": "RevocationTimeframe2024" + }"#; + + fn get_example_status() -> anyhow::Result { + let duration = Duration::minutes(1); + let service_url = Url::parse( + "did:iota:snd:0xae6ccfdb155a69e0ef153fb5fcfd50c08a8fee36babe1f7d71dede8f4e202432#my-revocation-service", + )?; + let credential_index: u32 = 5; + let start_validity_timeframe = Timestamp::parse("2024-03-19T13:57:50Z")?; + + Ok(RevocationTimeframeStatus::new( + Some(start_validity_timeframe), + duration, + service_url, + credential_index, + )?) + } + + #[test] + fn revocation_timeframe_status_serialization_works() -> anyhow::Result<()> { + let status = get_example_status()?; + + let serialized = serde_json::to_string(&status).expect("Failed to deserialize"); + dbg!(&serialized); + + Ok(()) + } + + #[test] + fn revocation_timeframe_status_deserialization_works() -> anyhow::Result<()> { + let status = get_example_status()?; + let deserialized = + serde_json::from_str::(EXAMPLE_SERIALIZED).expect("Failed to deserialize"); + + assert_eq!(status, deserialized); + + Ok(()) + } +} diff --git a/identity_credential/src/validator/jpt_credential_validation/decoded_jpt_credential.rs b/identity_credential/src/validator/jpt_credential_validation/decoded_jpt_credential.rs new file mode 100644 index 0000000000..b574abfa13 --- /dev/null +++ b/identity_credential/src/validator/jpt_credential_validation/decoded_jpt_credential.rs @@ -0,0 +1,19 @@ +// Copyright 2020-2024 IOTA Stiftung, Fondazione Links +// SPDX-License-Identifier: Apache-2.0 + +use identity_core::common::Object; +use jsonprooftoken::jwp::issued::JwpIssued; + +use crate::credential::Credential; + +/// Decoded [`Credential`] from a cryptographically verified JWP. +#[non_exhaustive] +#[derive(Debug, Clone)] +pub struct DecodedJptCredential { + /// The decoded credential parsed to the [Verifiable Credentials Data model](https://www.w3.org/TR/vc-data-model/). + pub credential: Credential, + /// The custom claims parsed from the JPT. + pub custom_claims: Option, + /// The decoded and verifier Issued JWP, will be used to construct the Presented JWP + pub decoded_jwp: JwpIssued, +} diff --git a/identity_credential/src/validator/jpt_credential_validation/jpt_credential_validation_options.rs b/identity_credential/src/validator/jpt_credential_validation/jpt_credential_validation_options.rs new file mode 100644 index 0000000000..2cbaafac28 --- /dev/null +++ b/identity_credential/src/validator/jpt_credential_validation/jpt_credential_validation_options.rs @@ -0,0 +1,87 @@ +// Copyright 2020-2024 IOTA Stiftung, Fondazione Links +// SPDX-License-Identifier: Apache-2.0 + +use crate::validator::SubjectHolderRelationship; +use identity_core::common::Timestamp; +use identity_core::common::Url; +use identity_document::verifiable::JwpVerificationOptions; +use serde::Deserialize; +use serde::Serialize; + +/// Options to declare validation criteria for [`Credential`](crate::credential::Credential)s. +#[non_exhaustive] +#[derive(Debug, Default, Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct JptCredentialValidationOptions { + /// Declares that the credential is **not** considered valid if it expires before this + /// [`Timestamp`]. + /// Uses the current datetime during validation if not set. + #[serde(default)] + pub earliest_expiry_date: Option, + + /// Declares that the credential is **not** considered valid if it was issued later than this + /// [`Timestamp`]. + /// Uses the current datetime during validation if not set. + #[serde(default)] + pub latest_issuance_date: Option, + + /// Validation behaviour for [`credentialStatus`](https://www.w3.org/TR/vc-data-model/#status). + /// + /// Default: [`StatusCheck::Strict`](crate::validator::StatusCheck::Strict). + #[serde(default)] + pub status: crate::validator::StatusCheck, + + /// Declares how credential subjects must relate to the presentation holder during validation. + /// + /// + pub subject_holder_relationship: Option<(Url, SubjectHolderRelationship)>, + + /// Options which affect the verification of the proof on the credential. + #[serde(default)] + pub verification_options: JwpVerificationOptions, +} + +impl JptCredentialValidationOptions { + /// Constructor that sets all options to their defaults. + pub fn new() -> Self { + Self::default() + } + + /// Declare that the credential is **not** considered valid if it expires before this [`Timestamp`]. + /// Uses the current datetime during validation if not set. + pub fn earliest_expiry_date(mut self, timestamp: Timestamp) -> Self { + self.earliest_expiry_date = Some(timestamp); + self + } + + /// Declare that the credential is **not** considered valid if it was issued later than this [`Timestamp`]. + /// Uses the current datetime during validation if not set. + pub fn latest_issuance_date(mut self, timestamp: Timestamp) -> Self { + self.latest_issuance_date = Some(timestamp); + self + } + + /// Sets the validation behaviour for [`credentialStatus`](https://www.w3.org/TR/vc-data-model/#status). + pub fn status_check(mut self, status_check: crate::validator::StatusCheck) -> Self { + self.status = status_check; + self + } + + /// Declares how credential subjects must relate to the presentation holder during validation. + /// + /// + pub fn subject_holder_relationship( + mut self, + holder: Url, + subject_holder_relationship: SubjectHolderRelationship, + ) -> Self { + self.subject_holder_relationship = Some((holder, subject_holder_relationship)); + self + } + + /// Set options which affect the verification of the JWP proof. + pub fn verification_options(mut self, options: JwpVerificationOptions) -> Self { + self.verification_options = options; + self + } +} diff --git a/identity_credential/src/validator/jpt_credential_validation/jpt_credential_validator.rs b/identity_credential/src/validator/jpt_credential_validation/jpt_credential_validator.rs new file mode 100644 index 0000000000..3639d1a229 --- /dev/null +++ b/identity_credential/src/validator/jpt_credential_validation/jpt_credential_validator.rs @@ -0,0 +1,225 @@ +// Copyright 2020-2024 IOTA Stiftung, Fondazione Links +// SPDX-License-Identifier: Apache-2.0 + +use identity_core::convert::FromJson; +use identity_core::convert::ToJson; +use identity_did::CoreDID; +use identity_did::DIDUrl; +use identity_document::document::CoreDocument; +use identity_document::verifiable::JwpVerificationOptions; +use jsonprooftoken::encoding::SerializationType; +use jsonprooftoken::jpt::claims::JptClaims; +use jsonprooftoken::jwk::key::Jwk as JwkExt; +use jsonprooftoken::jwp::issued::JwpIssuedDecoder; + +use super::DecodedJptCredential; +use crate::credential::Credential; +use crate::credential::CredentialJwtClaims; +use crate::credential::Jpt; +use crate::validator::jwt_credential_validation::SignerContext; +use crate::validator::CompoundCredentialValidationError; +use crate::validator::FailFast; +use crate::validator::JptCredentialValidationOptions; +use crate::validator::JwtCredentialValidatorUtils; +use crate::validator::JwtValidationError; + +/// A type for decoding and validating [`Credential`]s in JPT format. +#[non_exhaustive] +pub struct JptCredentialValidator; + +impl JptCredentialValidator { + /// Decodes and validates a [`Credential`] issued as a JPT (JWP Issued Form). A [`DecodedJptCredential`] is returned + /// upon success. + /// + /// The following properties are validated according to `options`: + /// - the issuer's proof on the JWP, + /// - the expiration date, + /// - the issuance date, + /// - the semantic structure. + pub fn validate( + credential_jpt: &Jpt, + issuer: &DOC, + options: &JptCredentialValidationOptions, + fail_fast: FailFast, + ) -> Result, CompoundCredentialValidationError> + where + T: ToOwned + serde::Serialize + serde::de::DeserializeOwned, + DOC: AsRef, + { + // First verify the JWP proof and decode the result into a credential token, then apply all other validations. + let credential_token = + Self::verify_proof(credential_jpt, issuer, &options.verification_options).map_err(|err| { + CompoundCredentialValidationError { + validation_errors: [err].into(), + } + })?; + + let credential: &Credential = &credential_token.credential; + + Self::validate_credential::(credential, options, fail_fast)?; + + Ok(credential_token) + } + + pub(crate) fn validate_credential( + credential: &Credential, + options: &JptCredentialValidationOptions, + fail_fast: FailFast, + ) -> Result<(), CompoundCredentialValidationError> + where + T: ToOwned + serde::Serialize + serde::de::DeserializeOwned, + { + // Run all single concern Credential validations in turn and fail immediately if `fail_fast` is true. + let expiry_date_validation = std::iter::once_with(|| { + JwtCredentialValidatorUtils::check_expires_on_or_after( + credential, + options.earliest_expiry_date.unwrap_or_default(), + ) + }); + + let issuance_date_validation = std::iter::once_with(|| { + JwtCredentialValidatorUtils::check_issued_on_or_before( + credential, + options.latest_issuance_date.unwrap_or_default(), + ) + }); + + let structure_validation = std::iter::once_with(|| JwtCredentialValidatorUtils::check_structure(credential)); + + let subject_holder_validation = std::iter::once_with(|| { + options + .subject_holder_relationship + .as_ref() + .map(|(holder, relationship)| { + JwtCredentialValidatorUtils::check_subject_holder_relationship(credential, holder, *relationship) + }) + .unwrap_or(Ok(())) + }); + + let validation_units_iter = issuance_date_validation + .chain(expiry_date_validation) + .chain(structure_validation) + .chain(subject_holder_validation); + + let validation_units_error_iter = validation_units_iter.filter_map(|result| result.err()); + let validation_errors: Vec = match fail_fast { + FailFast::FirstError => validation_units_error_iter.take(1).collect(), + FailFast::AllErrors => validation_units_error_iter.collect(), + }; + + if validation_errors.is_empty() { + Ok(()) + } else { + Err(CompoundCredentialValidationError { validation_errors }) + } + } + + /// Proof verification function + fn verify_proof( + credential: &Jpt, + issuer: &DOC, + options: &JwpVerificationOptions, + ) -> Result, JwtValidationError> + where + T: ToOwned + serde::Serialize + serde::de::DeserializeOwned, + DOC: AsRef, + { + let decoded = JwpIssuedDecoder::decode(credential.as_str(), SerializationType::COMPACT) + .map_err(JwtValidationError::JwpDecodingError)?; + + // If no method_url is set, parse the `kid` to a DID Url which should be the identifier + // of a verification method in a trusted issuer's DID document. + let method_id: DIDUrl = match &options.method_id { + Some(method_id) => method_id.clone(), + None => { + let kid: &str = decoded + .get_header() + .kid() + .ok_or(JwtValidationError::MethodDataLookupError { + source: None, + message: "could not extract kid from protected header", + signer_ctx: SignerContext::Issuer, + })?; + + // Convert kid to DIDUrl + DIDUrl::parse(kid).map_err(|err| JwtValidationError::MethodDataLookupError { + source: Some(err.into()), + message: "could not parse kid as a DID Url", + signer_ctx: SignerContext::Issuer, + })? + } + }; + + // check issuer + let issuer: &CoreDocument = issuer.as_ref(); + + if issuer.id() != method_id.did() { + return Err(JwtValidationError::DocumentMismatch(SignerContext::Issuer)); + } + + // Obtain the public key from the issuer's DID document + let public_key: JwkExt = issuer + .resolve_method(&method_id, options.method_scope) + .and_then(|method| method.data().public_key_jwk()) + .and_then(|k| k.try_into().ok()) //Conversio into jsonprooftoken::Jwk type + .ok_or_else(|| JwtValidationError::MethodDataLookupError { + source: None, + message: "could not extract JWK from a method identified by kid", + signer_ctx: SignerContext::Issuer, + })?; + + let credential_token = Self::verify_decoded_jwp(decoded, &public_key)?; + + // Check that the DID component of the parsed `kid` does indeed correspond to the issuer in the credential before + // returning. + let issuer_id: CoreDID = JwtCredentialValidatorUtils::extract_issuer(&credential_token.credential)?; + if &issuer_id != method_id.did() { + return Err(JwtValidationError::IdentifierMismatch { + signer_ctx: SignerContext::Issuer, + }); + }; + Ok(credential_token) + } + + /// Verify the decoded issued JWP proof using the given `public_key`. + fn verify_decoded_jwp( + decoded: JwpIssuedDecoder, + public_key: &JwkExt, + ) -> Result, JwtValidationError> + where + T: ToOwned + serde::Serialize + serde::de::DeserializeOwned, + { + // Verify Jwp proof + let decoded_jwp = decoded + .verify(public_key) + .map_err(JwtValidationError::JwpProofVerificationError)?; + + let claims = decoded_jwp.get_claims().ok_or("Claims not present").map_err(|err| { + JwtValidationError::CredentialStructure(crate::Error::JptClaimsSetDeserializationError(err.into())) + })?; + let payloads = decoded_jwp.get_payloads(); + let jpt_claims = JptClaims::from_claims_and_payloads(claims, payloads); + let jpt_claims_json = jpt_claims.to_json_vec().map_err(|err| { + JwtValidationError::CredentialStructure(crate::Error::JptClaimsSetDeserializationError(err.into())) + })?; + + // Deserialize the raw claims + let credential_claims: CredentialJwtClaims<'_, T> = CredentialJwtClaims::from_json_slice(&jpt_claims_json) + .map_err(|err| { + JwtValidationError::CredentialStructure(crate::Error::JwtClaimsSetDeserializationError(err.into())) + })?; + + let custom_claims = credential_claims.custom.clone(); + + // Construct the credential token containing the credential and the protected header. + let credential: Credential = credential_claims + .try_into_credential() + .map_err(JwtValidationError::CredentialStructure)?; + + Ok(DecodedJptCredential { + credential, + custom_claims, + decoded_jwp, + }) + } +} diff --git a/identity_credential/src/validator/jpt_credential_validation/jpt_credential_validator_utils.rs b/identity_credential/src/validator/jpt_credential_validation/jpt_credential_validator_utils.rs new file mode 100644 index 0000000000..258df619d4 --- /dev/null +++ b/identity_credential/src/validator/jpt_credential_validation/jpt_credential_validator_utils.rs @@ -0,0 +1,242 @@ +// Copyright 2020-2024 IOTA Stiftung, Fondazione Links +// SPDX-License-Identifier: Apache-2.0 + +use crate::credential::Credential; +use crate::revocation::RevocationDocumentExt; +use crate::revocation::RevocationTimeframeStatus; +use std::str::FromStr; + +use identity_core::common::Object; +use identity_core::common::Timestamp; +use identity_core::convert::FromJson; +use identity_core::convert::ToJson; +use identity_did::DID; +use jsonprooftoken::encoding::SerializationType; +use jsonprooftoken::jpt::claims::JptClaims; +use jsonprooftoken::jwp::issued::JwpIssuedDecoder; + +use crate::credential::CredentialJwtClaims; +use crate::credential::Jpt; +use crate::validator::JwtValidationError; +use crate::validator::SignerContext; + +/// Utility functions for verifying JPT credentials. +#[derive(Debug)] +#[non_exhaustive] +pub struct JptCredentialValidatorUtils; + +type ValidationUnitResult = std::result::Result; + +impl JptCredentialValidatorUtils { + /// Utility for extracting the issuer field of a [`Credential`] as a DID. + /// + /// # Errors + /// + /// Fails if the issuer field is not a valid DID. + pub fn extract_issuer(credential: &Credential) -> std::result::Result + where + D: DID, + ::Err: std::error::Error + Send + Sync + 'static, + { + D::from_str(credential.issuer.url().as_str()).map_err(|err| JwtValidationError::SignerUrl { + signer_ctx: SignerContext::Issuer, + source: err.into(), + }) + } + + /// Utility for extracting the issuer field of a credential in JPT representation as DID. + /// + /// # Errors + /// + /// If the JPT decoding fails or the issuer field is not a valid DID. + pub fn extract_issuer_from_issued_jpt(credential: &Jpt) -> std::result::Result + where + D: DID, + ::Err: std::error::Error + Send + Sync + 'static, + { + let decoded = JwpIssuedDecoder::decode(credential.as_str(), SerializationType::COMPACT) + .map_err(JwtValidationError::JwpDecodingError)?; + let claims = decoded + .get_header() + .claims() + .ok_or("Claims not present") + .map_err(|err| { + JwtValidationError::CredentialStructure(crate::Error::JptClaimsSetDeserializationError(err.into())) + })?; + let payloads = decoded.get_payloads(); + let jpt_claims = JptClaims::from_claims_and_payloads(claims, payloads); + let jpt_claims_json = jpt_claims.to_json_vec().map_err(|err| { + JwtValidationError::CredentialStructure(crate::Error::JptClaimsSetDeserializationError(err.into())) + })?; + + // Deserialize the raw claims + let credential_claims: CredentialJwtClaims<'_, Object> = CredentialJwtClaims::from_json_slice(&jpt_claims_json) + .map_err(|err| { + JwtValidationError::CredentialStructure(crate::Error::JwtClaimsSetDeserializationError(err.into())) + })?; + + D::from_str(credential_claims.iss.url().as_str()).map_err(|err| JwtValidationError::SignerUrl { + signer_ctx: SignerContext::Issuer, + source: err.into(), + }) + } + + /// Check timeframe interval in credentialStatus with `RevocationTimeframeStatus`. + pub fn check_timeframes_with_validity_timeframe_2024( + credential: &Credential, + validity_timeframe: Option, + status_check: crate::validator::StatusCheck, + ) -> ValidationUnitResult { + if status_check == crate::validator::StatusCheck::SkipAll { + return Ok(()); + } + + match &credential.credential_status { + None => Ok(()), + Some(status) => { + if status.type_ == RevocationTimeframeStatus::TYPE { + let status: RevocationTimeframeStatus = + RevocationTimeframeStatus::try_from(status).map_err(JwtValidationError::InvalidStatus)?; + + Self::check_validity_timeframe(status, validity_timeframe) + } else { + if status_check == crate::validator::StatusCheck::SkipUnsupported { + return Ok(()); + } + Err(JwtValidationError::InvalidStatus(crate::Error::InvalidStatus(format!( + "unsupported type '{}'", + status.type_ + )))) + } + } + } + } + + pub(crate) fn check_validity_timeframe( + status: RevocationTimeframeStatus, + validity_timeframe: Option, + ) -> ValidationUnitResult { + let timeframe = validity_timeframe.unwrap_or(Timestamp::now_utc()); + + let check = timeframe >= status.start_validity_timeframe() && timeframe <= status.end_validity_timeframe(); + + if !check { + Err(JwtValidationError::OutsideTimeframe) + } else { + Ok(()) + } + } + + /// Checks whether the credential status has been revoked. + /// + /// Only supports `RevocationTimeframe2024`. + pub fn check_revocation_with_validity_timeframe_2024< + DOC: AsRef + ?Sized, + T, + >( + credential: &Credential, + issuer: &DOC, + status_check: crate::validator::StatusCheck, + ) -> ValidationUnitResult { + if status_check == crate::validator::StatusCheck::SkipAll { + return Ok(()); + } + + match &credential.credential_status { + None => Ok(()), + Some(status) => { + if status.type_ == RevocationTimeframeStatus::TYPE { + let status: RevocationTimeframeStatus = + RevocationTimeframeStatus::try_from(status).map_err(JwtValidationError::InvalidStatus)?; + + Self::check_revocation_bitmap(issuer, status) + } else { + if status_check == crate::validator::StatusCheck::SkipUnsupported { + return Ok(()); + } + Err(JwtValidationError::InvalidStatus(crate::Error::InvalidStatus(format!( + "unsupported type '{}'", + status.type_ + )))) + } + } + } + } + + /// Check the given `status` against the matching [`RevocationBitmap`] service in the issuer's DID Document. + fn check_revocation_bitmap + ?Sized>( + issuer: &DOC, + status: RevocationTimeframeStatus, + ) -> ValidationUnitResult { + let issuer_service_url: identity_did::DIDUrl = + identity_did::DIDUrl::parse(status.id().to_string()).map_err(|err| { + JwtValidationError::InvalidStatus(crate::Error::InvalidStatus(format!( + "could not convert status id to DIDUrl; {}", + err, + ))) + })?; + + // Check whether index is revoked. + let revocation_bitmap: crate::revocation::RevocationBitmap = issuer + .as_ref() + .resolve_revocation_bitmap(issuer_service_url.into()) + .map_err(|_| JwtValidationError::ServiceLookupError)?; + + if let Some(index) = status.index() { + if revocation_bitmap.is_revoked(index) { + return Err(JwtValidationError::Revoked); + } + } + Ok(()) + } + + /// Checks whether the credential status has been revoked or the timeframe interval is INVALID + /// + /// Only supports `RevocationTimeframe2024`. + pub fn check_timeframes_and_revocation_with_validity_timeframe_2024< + DOC: AsRef + ?Sized, + T, + >( + credential: &Credential, + issuer: &DOC, + validity_timeframe: Option, + status_check: crate::validator::StatusCheck, + ) -> ValidationUnitResult { + if status_check == crate::validator::StatusCheck::SkipAll { + return Ok(()); + } + + match &credential.credential_status { + None => Ok(()), + Some(status) => { + if status.type_ == RevocationTimeframeStatus::TYPE { + let status: RevocationTimeframeStatus = + RevocationTimeframeStatus::try_from(status).map_err(JwtValidationError::InvalidStatus)?; + + let revocation = std::iter::once_with(|| Self::check_revocation_bitmap(issuer, status.clone())); + + let timeframes = std::iter::once_with(|| Self::check_validity_timeframe(status.clone(), validity_timeframe)); + + let checks_iter = revocation.chain(timeframes); + + let checks_error_iter = checks_iter.filter_map(|result| result.err()); + + let mut checks_errors: Vec = checks_error_iter.take(1).collect(); + + match checks_errors.pop() { + Some(err) => Err(err), + None => Ok(()), + } + } else { + if status_check == crate::validator::StatusCheck::SkipUnsupported { + return Ok(()); + } + Err(JwtValidationError::InvalidStatus(crate::Error::InvalidStatus(format!( + "unsupported type '{}'", + status.type_ + )))) + } + } + } + } +} diff --git a/identity_credential/src/validator/jpt_credential_validation/mod.rs b/identity_credential/src/validator/jpt_credential_validation/mod.rs new file mode 100644 index 0000000000..60455ba606 --- /dev/null +++ b/identity_credential/src/validator/jpt_credential_validation/mod.rs @@ -0,0 +1,12 @@ +// Copyright 2020-2024 IOTA Stiftung, Fondazione Links +// SPDX-License-Identifier: Apache-2.0 + +mod decoded_jpt_credential; +mod jpt_credential_validation_options; +mod jpt_credential_validator; +mod jpt_credential_validator_utils; + +pub use decoded_jpt_credential::*; +pub use jpt_credential_validation_options::*; +pub use jpt_credential_validator::*; +pub use jpt_credential_validator_utils::*; diff --git a/identity_credential/src/validator/jpt_presentation_validation/decoded_jpt_presentation.rs b/identity_credential/src/validator/jpt_presentation_validation/decoded_jpt_presentation.rs new file mode 100644 index 0000000000..fb62181057 --- /dev/null +++ b/identity_credential/src/validator/jpt_presentation_validation/decoded_jpt_presentation.rs @@ -0,0 +1,22 @@ +// Copyright 2020-2024 IOTA Stiftung, Fondazione Links +// SPDX-License-Identifier: Apache-2.0 + +use identity_core::common::Object; +use identity_core::common::Url; +use jsonprooftoken::jwp::presented::JwpPresented; + +use crate::credential::Credential; + +/// Decoded [`Credential`] from a cryptographically verified JWP. +#[non_exhaustive] +#[derive(Debug, Clone)] +pub struct DecodedJptPresentation { + /// The decoded credential parsed to the [Verifiable Credentials Data model](https://www.w3.org/TR/vc-data-model/). + pub credential: Credential, + /// The `aud` property parsed from the JWT claims. + pub aud: Option, + /// The custom claims parsed from the JPT. + pub custom_claims: Option, + /// The decoded and verifier Issued JWP, will be used to construct the Presented JWP + pub decoded_jwp: JwpPresented, +} diff --git a/identity_credential/src/validator/jpt_presentation_validation/jpt_presentation_validation_options.rs b/identity_credential/src/validator/jpt_presentation_validation/jpt_presentation_validation_options.rs new file mode 100644 index 0000000000..302b45f8c4 --- /dev/null +++ b/identity_credential/src/validator/jpt_presentation_validation/jpt_presentation_validation_options.rs @@ -0,0 +1,40 @@ +// Copyright 2020-2024 IOTA Stiftung, Fondazione Links +// SPDX-License-Identifier: Apache-2.0 + +use identity_document::verifiable::JwpVerificationOptions; +use serde::Deserialize; +use serde::Serialize; + +/// Criteria for validating a [`Presentation`](crate::presentation::Presentation). +#[derive(Debug, Default, Clone, Serialize, Deserialize)] +#[non_exhaustive] +#[serde(rename_all = "camelCase")] +pub struct JptPresentationValidationOptions { + /// The nonce to be placed in the Presentation Protected Header. + #[serde(default)] + pub nonce: Option, + + /// Options which affect the verification of the proof on the credential. + #[serde(default)] + pub verification_options: JwpVerificationOptions, +} + +impl JptPresentationValidationOptions { + /// Constructor that sets all options to their defaults. + pub fn new() -> Self { + Self::default() + } + + /// Declare that the presentation is **not** considered valid if it expires before this [`Timestamp`]. + /// Uses the current datetime during validation if not set. + pub fn nonce(mut self, nonce: impl Into) -> Self { + self.nonce = Some(nonce.into()); + self + } + + /// Set options which affect the verification of the JWP proof. + pub fn verification_options(mut self, options: JwpVerificationOptions) -> Self { + self.verification_options = options; + self + } +} diff --git a/identity_credential/src/validator/jpt_presentation_validation/jpt_presentation_validator.rs b/identity_credential/src/validator/jpt_presentation_validation/jpt_presentation_validator.rs new file mode 100644 index 0000000000..ac32e9878f --- /dev/null +++ b/identity_credential/src/validator/jpt_presentation_validation/jpt_presentation_validator.rs @@ -0,0 +1,226 @@ +// Copyright 2020-2024 IOTA Stiftung, Fondazione Links +// SPDX-License-Identifier: Apache-2.0 + +use std::str::FromStr; + +use identity_core::common::Url; +use identity_core::convert::FromJson; +use identity_core::convert::ToJson; +use identity_did::CoreDID; +use identity_did::DIDUrl; +use identity_document::document::CoreDocument; +use jsonprooftoken::encoding::SerializationType; +use jsonprooftoken::jpt::claims::JptClaims; +use jsonprooftoken::jwk::key::Jwk as JwkExt; +use jsonprooftoken::jwp::presented::JwpPresentedDecoder; + +use crate::credential::Credential; +use crate::credential::CredentialJwtClaims; +use crate::credential::Jpt; +use crate::validator::CompoundCredentialValidationError; +use crate::validator::FailFast; +use crate::validator::JwtCredentialValidatorUtils; +use crate::validator::JwtValidationError; +use crate::validator::SignerContext; + +use super::DecodedJptPresentation; +use super::JptPresentationValidationOptions; + +/// A type for decoding and validating Presented [`Credential`]s in JPT format. +#[non_exhaustive] +pub struct JptPresentationValidator; + +impl JptPresentationValidator { + /// Decodes and validates a Presented [`Credential`] issued as a JPT (JWP Presented Form). A + /// [`DecodedJptPresentation`] is returned upon success. + /// + /// The following properties are validated according to `options`: + /// - the holder's proof on the JWP, + /// - the expiration date, + /// - the issuance date, + /// - the semantic structure. + pub fn validate( + presentation_jpt: &Jpt, + issuer: &DOC, + options: &JptPresentationValidationOptions, + fail_fast: FailFast, + ) -> Result, CompoundCredentialValidationError> + where + T: ToOwned + serde::Serialize + serde::de::DeserializeOwned, + DOC: AsRef, + { + // First verify the JWP proof and decode the result into a presented credential token, then apply all other + // validations. + let presented_credential_token = + Self::verify_proof(presentation_jpt, issuer, options).map_err(|err| CompoundCredentialValidationError { + validation_errors: [err].into(), + })?; + + let credential: &Credential = &presented_credential_token.credential; + + Self::validate_presented_credential::(credential, fail_fast)?; + + Ok(presented_credential_token) + } + + pub(crate) fn validate_presented_credential( + credential: &Credential, + fail_fast: FailFast, + ) -> Result<(), CompoundCredentialValidationError> + where + T: ToOwned + serde::Serialize + serde::de::DeserializeOwned, + { + let structure_validation = std::iter::once_with(|| JwtCredentialValidatorUtils::check_structure(credential)); + + let validation_units_iter = structure_validation; + + let validation_units_error_iter = validation_units_iter.filter_map(|result| result.err()); + let validation_errors: Vec = match fail_fast { + FailFast::FirstError => validation_units_error_iter.take(1).collect(), + FailFast::AllErrors => validation_units_error_iter.collect(), + }; + + if validation_errors.is_empty() { + Ok(()) + } else { + Err(CompoundCredentialValidationError { validation_errors }) + } + } + + /// Proof verification function + fn verify_proof( + presentation_jpt: &Jpt, + issuer: &DOC, + options: &JptPresentationValidationOptions, + ) -> Result, JwtValidationError> + where + T: ToOwned + serde::Serialize + serde::de::DeserializeOwned, + DOC: AsRef, + { + let decoded: JwpPresentedDecoder = + JwpPresentedDecoder::decode(presentation_jpt.as_str(), SerializationType::COMPACT) + .map_err(JwtValidationError::JwpDecodingError)?; + + let nonce: Option<&String> = options.nonce.as_ref(); + // Validate the nonce + if decoded.get_presentation_header().nonce() != nonce { + return Err(JwtValidationError::JwsDecodingError( + identity_verification::jose::error::Error::InvalidParam("invalid nonce value"), + )); + } + + // If no method_url is set, parse the `kid` to a DID Url which should be the identifier + // of a verification method in a trusted issuer's DID document. + let method_id: DIDUrl = match &options.verification_options.method_id { + Some(method_id) => method_id.clone(), + None => { + let kid: &str = decoded + .get_issuer_header() + .kid() + .ok_or(JwtValidationError::MethodDataLookupError { + source: None, + message: "could not extract kid from protected header", + signer_ctx: SignerContext::Issuer, + })?; + + // Convert kid to DIDUrl + DIDUrl::parse(kid).map_err(|err| JwtValidationError::MethodDataLookupError { + source: Some(err.into()), + message: "could not parse kid as a DID Url", + signer_ctx: SignerContext::Issuer, + })? + } + }; + + // check issuer + let issuer: &CoreDocument = issuer.as_ref(); + + if issuer.id() != method_id.did() { + return Err(JwtValidationError::DocumentMismatch(SignerContext::Issuer)); + } + + // Obtain the public key from the issuer's DID document + let public_key: JwkExt = issuer + .resolve_method(&method_id, options.verification_options.method_scope) + .and_then(|method| method.data().public_key_jwk()) + .and_then(|k| k.try_into().ok()) //Conversio into jsonprooftoken::Jwk type + .ok_or_else(|| JwtValidationError::MethodDataLookupError { + source: None, + message: "could not extract JWK from a method identified by kid", + signer_ctx: SignerContext::Issuer, + })?; + + let credential_token = Self::verify_decoded_jwp(decoded, &public_key)?; + + // Check that the DID component of the parsed `kid` does indeed correspond to the issuer in the credential before + // returning. + let issuer_id: CoreDID = JwtCredentialValidatorUtils::extract_issuer(&credential_token.credential)?; + if &issuer_id != method_id.did() { + return Err(JwtValidationError::IdentifierMismatch { + signer_ctx: SignerContext::Issuer, + }); + }; + Ok(credential_token) + } + + /// Verify the decoded presented JWP proof using the given `public_key`. + fn verify_decoded_jwp( + decoded: JwpPresentedDecoder, + public_key: &JwkExt, + ) -> Result, JwtValidationError> + where + T: ToOwned + serde::Serialize + serde::de::DeserializeOwned, + { + // Verify Jwp proof + let decoded_jwp = decoded + .verify(public_key) + .map_err(JwtValidationError::JwpProofVerificationError)?; + + let claims = decoded_jwp.get_claims().ok_or("Claims not present").map_err(|err| { + JwtValidationError::CredentialStructure(crate::Error::JptClaimsSetDeserializationError(err.into())) + })?; + let payloads = decoded_jwp.get_payloads(); + let mut jpt_claims = JptClaims::from_claims_and_payloads(claims, payloads); + // if not set the deserializatioon will throw an error since even the iat is not set, so we set this to 0 + jpt_claims.nbf.map_or_else( + || { + jpt_claims.set_nbf(0); + }, + |_| (), + ); + + let jpt_claims_json = jpt_claims.to_json_vec().map_err(|err| { + JwtValidationError::CredentialStructure(crate::Error::JptClaimsSetDeserializationError(err.into())) + })?; + + // Deserialize the raw claims + let credential_claims: CredentialJwtClaims<'_, T> = CredentialJwtClaims::from_json_slice(&jpt_claims_json) + .map_err(|err| { + JwtValidationError::CredentialStructure(crate::Error::JwtClaimsSetDeserializationError(err.into())) + })?; + + let custom_claims = credential_claims.custom.clone(); + + // Construct the credential token containing the credential and the protected header. + let credential: Credential = credential_claims + .try_into_credential() + .map_err(JwtValidationError::CredentialStructure)?; + + let aud: Option = decoded_jwp.get_presentation_protected_header().aud().and_then(|aud| { + Url::from_str(aud) + .map_err(|_| { + JwtValidationError::JwsDecodingError(identity_verification::jose::error::Error::InvalidParam( + "invalid audience value", + )) + }) + .ok() + }); + + Ok(DecodedJptPresentation { + credential, + aud, + custom_claims, + decoded_jwp, + }) + } +} diff --git a/identity_credential/src/validator/jpt_presentation_validation/jpt_presentation_validator_utils.rs b/identity_credential/src/validator/jpt_presentation_validation/jpt_presentation_validator_utils.rs new file mode 100644 index 0000000000..3bdf17a00e --- /dev/null +++ b/identity_credential/src/validator/jpt_presentation_validation/jpt_presentation_validator_utils.rs @@ -0,0 +1,99 @@ +// Copyright 2020-2024 IOTA Stiftung, Fondazione Links +// SPDX-License-Identifier: Apache-2.0 + +use std::str::FromStr; + +use identity_core::common::Object; +use identity_core::common::Timestamp; +use identity_core::convert::FromJson; +use identity_core::convert::ToJson; +use identity_did::DID; +use jsonprooftoken::encoding::SerializationType; +use jsonprooftoken::jpt::claims::JptClaims; +use jsonprooftoken::jwp::presented::JwpPresentedDecoder; + +use crate::credential::Credential; +use crate::credential::CredentialJwtClaims; +use crate::credential::Jpt; +use crate::revocation::RevocationTimeframeStatus; +use crate::revocation::VerifierRevocationTimeframeStatus; +use crate::validator::JptCredentialValidatorUtils; +use crate::validator::JwtValidationError; +use crate::validator::SignerContext; + +/// Utility functions for verifying JPT credentials. +#[derive(Debug)] +#[non_exhaustive] +pub struct JptPresentationValidatorUtils; + +type ValidationUnitResult = std::result::Result; + +impl JptPresentationValidatorUtils { + /// Utility for extracting the issuer field of a credential in JPT representation as DID. + /// + /// # Errors + /// + /// If the JPT decoding fails or the issuer field is not a valid DID. + pub fn extract_issuer_from_presented_jpt(presentation: &Jpt) -> std::result::Result + where + D: DID, + ::Err: std::error::Error + Send + Sync + 'static, + { + let decoded = JwpPresentedDecoder::decode(presentation.as_str(), SerializationType::COMPACT) + .map_err(JwtValidationError::JwpDecodingError)?; + let claims = decoded + .get_issuer_header() + .claims() + .ok_or("Claims not present") + .map_err(|err| { + JwtValidationError::CredentialStructure(crate::Error::JptClaimsSetDeserializationError(err.into())) + })?; + let payloads = decoded.get_payloads(); + let jpt_claims = JptClaims::from_claims_and_payloads(claims, payloads); + let jpt_claims_json = jpt_claims.to_json_vec().map_err(|err| { + JwtValidationError::CredentialStructure(crate::Error::JptClaimsSetDeserializationError(err.into())) + })?; + + // Deserialize the raw claims + let credential_claims: CredentialJwtClaims<'_, Object> = CredentialJwtClaims::from_json_slice(&jpt_claims_json) + .map_err(|err| { + JwtValidationError::CredentialStructure(crate::Error::JwtClaimsSetDeserializationError(err.into())) + })?; + + D::from_str(credential_claims.iss.url().as_str()).map_err(|err| JwtValidationError::SignerUrl { + signer_ctx: SignerContext::Issuer, + source: err.into(), + }) + } + + /// Check timeframe interval in credentialStatus with `RevocationTimeframeStatus`. + pub fn check_timeframes_with_validity_timeframe_2024( + credential: &Credential, + validity_timeframe: Option, + status_check: crate::validator::StatusCheck, + ) -> ValidationUnitResult { + if status_check == crate::validator::StatusCheck::SkipAll { + return Ok(()); + } + + match &credential.credential_status { + None => Ok(()), + Some(status) => { + if status.type_ == RevocationTimeframeStatus::TYPE { + let status: VerifierRevocationTimeframeStatus = + VerifierRevocationTimeframeStatus::try_from(status.clone()).map_err(JwtValidationError::InvalidStatus)?; + + JptCredentialValidatorUtils::check_validity_timeframe(status.0, validity_timeframe) + } else { + if status_check == crate::validator::StatusCheck::SkipUnsupported { + return Ok(()); + } + Err(JwtValidationError::InvalidStatus(crate::Error::InvalidStatus(format!( + "unsupported type '{}'", + status.type_ + )))) + } + } + } + } +} diff --git a/identity_credential/src/validator/jpt_presentation_validation/mod.rs b/identity_credential/src/validator/jpt_presentation_validation/mod.rs new file mode 100644 index 0000000000..1cab953dc5 --- /dev/null +++ b/identity_credential/src/validator/jpt_presentation_validation/mod.rs @@ -0,0 +1,12 @@ +// Copyright 2020-2024 IOTA Stiftung, Fondazione Links +// SPDX-License-Identifier: Apache-2.0 + +mod decoded_jpt_presentation; +mod jpt_presentation_validation_options; +mod jpt_presentation_validator; +mod jpt_presentation_validator_utils; + +pub use decoded_jpt_presentation::*; +pub use jpt_presentation_validation_options::*; +pub use jpt_presentation_validator::*; +pub use jpt_presentation_validator_utils::*; diff --git a/identity_credential/src/validator/jwt_credential_validation/error.rs b/identity_credential/src/validator/jwt_credential_validation/error.rs index 57e7aab7df..3fb0211ee2 100644 --- a/identity_credential/src/validator/jwt_credential_validation/error.rs +++ b/identity_credential/src/validator/jwt_credential_validation/error.rs @@ -104,6 +104,18 @@ pub enum JwtValidationError { /// Indicates that the credential has been suspended. #[error("credential has been suspended")] Suspended, + /// Indicates that the credential's timeframe interval is not valid + #[cfg(feature = "jpt-bbs-plus")] + #[error("timeframe interval not valid")] + OutsideTimeframe, + /// Indicates that the JWP representation of an issued credential or presentation could not be decoded. + #[cfg(feature = "jpt-bbs-plus")] + #[error("could not decode jwp")] + JwpDecodingError(#[source] jsonprooftoken::errors::CustomError), + /// Indicates that the verification of the JWP has failed + #[cfg(feature = "jpt-bbs-plus")] + #[error("could not verify jwp")] + JwpProofVerificationError(#[source] jsonprooftoken::errors::CustomError), } /// Specifies whether an error is related to a credential issuer or the presentation holder. diff --git a/identity_credential/src/validator/jwt_presentation_validation/jwt_presentation_validator.rs b/identity_credential/src/validator/jwt_presentation_validation/jwt_presentation_validator.rs index a02b2cf56a..d6d97fcf6e 100644 --- a/identity_credential/src/validator/jwt_presentation_validation/jwt_presentation_validator.rs +++ b/identity_credential/src/validator/jwt_presentation_validation/jwt_presentation_validator.rs @@ -47,11 +47,11 @@ where /// # Warning /// /// * This method does NOT validate the constituent credentials and therefore also not the relationship between the - /// credentials' subjects and the presentation holder. This can be done with - /// [`JwtCredentialValidationOptions`](crate::validator::JwtCredentialValidationOptions). + /// credentials' subjects and the presentation holder. This can be done with + /// [`JwtCredentialValidationOptions`](crate::validator::JwtCredentialValidationOptions). /// * The lack of an error returned from this method is in of itself not enough to conclude that the presentation can - /// be trusted. This section contains more information on additional checks that should be carried out before and - /// after calling this method. + /// be trusted. This section contains more information on additional checks that should be carried out before and + /// after calling this method. /// /// ## The state of the supplied DID Documents. /// diff --git a/identity_credential/src/validator/mod.rs b/identity_credential/src/validator/mod.rs index 37611334c3..2266618ddd 100644 --- a/identity_credential/src/validator/mod.rs +++ b/identity_credential/src/validator/mod.rs @@ -3,6 +3,10 @@ //! Verifiable Credential and Presentation validators. +#[cfg(feature = "jpt-bbs-plus")] +pub use self::jpt_credential_validation::*; +#[cfg(feature = "jpt-bbs-plus")] +pub use self::jpt_presentation_validation::*; pub use self::jwt_credential_validation::*; pub use self::jwt_presentation_validation::*; pub use self::options::FailFast; @@ -11,6 +15,10 @@ pub use self::options::SubjectHolderRelationship; #[cfg(feature = "sd-jwt")] pub use self::sd_jwt::*; +#[cfg(feature = "jpt-bbs-plus")] +mod jpt_credential_validation; +#[cfg(feature = "jpt-bbs-plus")] +mod jpt_presentation_validation; mod jwt_credential_validation; mod jwt_presentation_validation; mod options; diff --git a/identity_credential/src/validator/sd_jwt/validator.rs b/identity_credential/src/validator/sd_jwt/validator.rs index 0eedf13bf5..e01985fa01 100644 --- a/identity_credential/src/validator/sd_jwt/validator.rs +++ b/identity_credential/src/validator/sd_jwt/validator.rs @@ -53,10 +53,10 @@ impl SdJwtCredentialValidator { /// /// # Warning /// * The key binding JWT is not validated. If needed, it must be validated separately using - /// `SdJwtValidator::validate_key_binding_jwt`. + /// `SdJwtValidator::validate_key_binding_jwt`. /// * The lack of an error returned from this method is in of itself not enough to conclude that the credential can be - /// trusted. This section contains more information on additional checks that should be carried out before and after - /// calling this method. + /// trusted. This section contains more information on additional checks that should be carried out before and after + /// calling this method. /// /// ## The state of the issuer's DID Document /// The caller must ensure that `issuer` represents an up-to-date DID Document. diff --git a/identity_credential/src/validator/test_utils.rs b/identity_credential/src/validator/test_utils.rs index 9febb41a1f..22a18a7605 100644 --- a/identity_credential/src/validator/test_utils.rs +++ b/identity_credential/src/validator/test_utils.rs @@ -19,7 +19,7 @@ pub(crate) fn encode_public_ed25519_jwk(public_key: &PublicKey) -> Jwk { let mut params = JwkParamsOkp::new(); params.x = x; params.d = None; - params.crv = EdCurve::Ed25519.name().to_owned(); + params.crv = EdCurve::Ed25519.name().to_string(); let mut jwk = Jwk::from_params(params); jwk.set_alg(JwsAlgorithm::EdDSA.name()); jwk diff --git a/identity_did/Cargo.toml b/identity_did/Cargo.toml index 92a5e7fa30..473ffc8860 100644 --- a/identity_did/Cargo.toml +++ b/identity_did/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "identity_did" -version = "1.2.0" +version = "1.4.0" authors.workspace = true edition = "2021" homepage.workspace = true @@ -13,7 +13,8 @@ description = "Agnostic implementation of the Decentralized Identifiers (DID) st [dependencies] did_url_parser = { version = "0.2.0", features = ["std", "serde"] } form_urlencoded = { version = "1.2.0", default-features = false, features = ["alloc"] } -identity_core = { version = "=1.2.0", path = "../identity_core" } +identity_core = { version = "=1.4.0", path = "../identity_core", default-features = false } +identity_jose = { version = "=1.4.0", path = "../identity_jose" } serde.workspace = true strum.workspace = true thiserror.workspace = true diff --git a/identity_did/src/did_jwk.rs b/identity_did/src/did_jwk.rs new file mode 100644 index 0000000000..5ebd61021c --- /dev/null +++ b/identity_did/src/did_jwk.rs @@ -0,0 +1,123 @@ +// Copyright 2020-2024 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +use std::fmt::Debug; +use std::fmt::Display; +use std::str::FromStr; + +use identity_jose::jwk::Jwk; +use identity_jose::jwu::decode_b64_json; + +use crate::CoreDID; +use crate::Error; +use crate::DID; + +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, serde::Deserialize, serde::Serialize)] +#[repr(transparent)] +#[serde(into = "CoreDID", try_from = "CoreDID")] +/// A type representing a `did:jwk` DID. +pub struct DIDJwk(CoreDID); + +impl DIDJwk { + /// [`DIDJwk`]'s method. + pub const METHOD: &'static str = "jwk"; + + /// Tries to parse a [`DIDJwk`] from a string. + pub fn parse(s: &str) -> Result { + s.parse() + } + + /// Returns the JWK encoded inside this did:jwk. + pub fn jwk(&self) -> Jwk { + decode_b64_json(self.method_id()).expect("did:jwk encodes a valid JWK") + } +} + +impl AsRef for DIDJwk { + fn as_ref(&self) -> &CoreDID { + &self.0 + } +} + +impl From for CoreDID { + fn from(value: DIDJwk) -> Self { + value.0 + } +} + +impl<'a> TryFrom<&'a str> for DIDJwk { + type Error = Error; + fn try_from(value: &'a str) -> Result { + value.parse() + } +} + +impl Display for DIDJwk { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.0) + } +} + +impl FromStr for DIDJwk { + type Err = Error; + fn from_str(s: &str) -> Result { + s.parse::().and_then(TryFrom::try_from) + } +} + +impl From for String { + fn from(value: DIDJwk) -> Self { + value.to_string() + } +} + +impl TryFrom for DIDJwk { + type Error = Error; + fn try_from(value: CoreDID) -> Result { + let Self::METHOD = value.method() else { + return Err(Error::InvalidMethodName); + }; + decode_b64_json::(value.method_id()) + .map(|_| Self(value)) + .map_err(|_| Error::InvalidMethodId) + } +} + +#[cfg(test)] +mod tests { + use identity_core::convert::FromJson; + + use super::*; + + #[test] + fn test_valid_deserialization() -> Result<(), Error> { + "did:jwk:eyJrdHkiOiJPS1AiLCJjcnYiOiJYMjU1MTkiLCJ1c2UiOiJlbmMiLCJ4IjoiM3A3YmZYdDl3YlRUVzJIQzdPUTFOei1EUThoYmVHZE5yZngtRkctSUswOCJ9".parse::()?; + "did:jwk:eyJjcnYiOiJQLTI1NiIsImt0eSI6IkVDIiwieCI6ImFjYklRaXVNczNpOF91c3pFakoydHBUdFJNNEVVM3l6OTFQSDZDZEgyVjAiLCJ5IjoiX0tjeUxqOXZXTXB0bm1LdG00NkdxRHo4d2Y3NEk1TEtncmwyR3pIM25TRSJ9".parse::()?; + + Ok(()) + } + + #[test] + fn test_jwk() { + let did = DIDJwk::parse("did:jwk:eyJrdHkiOiJPS1AiLCJjcnYiOiJYMjU1MTkiLCJ1c2UiOiJlbmMiLCJ4IjoiM3A3YmZYdDl3YlRUVzJIQzdPUTFOei1EUThoYmVHZE5yZngtRkctSUswOCJ9").unwrap(); + let target_jwk = Jwk::from_json_value(serde_json::json!({ + "kty":"OKP","crv":"X25519","use":"enc","x":"3p7bfXt9wbTTW2HC7OQ1Nz-DQ8hbeGdNrfx-FG-IK08" + })) + .unwrap(); + + assert_eq!(did.jwk(), target_jwk); + } + + #[test] + fn test_invalid_deserialization() { + assert!( + "did:iota:0xf4d6f08f5a1b80dd578da7dc1b49c886d580acd4cf7d48119dfeb82b538ad88a" + .parse::() + .is_err() + ); + assert!("did:jwk:".parse::().is_err()); + assert!("did:jwk:z6MkiTBz1ymuepAQ4HEHYSF1H8quG5GLVVQR3djdX3mDooWp" + .parse::() + .is_err()); + } +} diff --git a/identity_did/src/lib.rs b/identity_did/src/lib.rs index 9289419211..62c846847e 100644 --- a/identity_did/src/lib.rs +++ b/identity_did/src/lib.rs @@ -18,6 +18,7 @@ #[allow(clippy::module_inception)] mod did; +mod did_jwk; mod did_url; mod error; @@ -26,4 +27,5 @@ pub use crate::did_url::RelativeDIDUrl; pub use ::did_url_parser::DID as BaseDIDUrl; pub use did::CoreDID; pub use did::DID; +pub use did_jwk::*; pub use error::Error; diff --git a/identity_document/Cargo.toml b/identity_document/Cargo.toml index cd02c58728..a257ea4de8 100644 --- a/identity_document/Cargo.toml +++ b/identity_document/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "identity_document" -version = "1.2.0" +version = "1.4.0" authors.workspace = true edition.workspace = true homepage.workspace = true @@ -12,9 +12,9 @@ rust-version.workspace = true description = "Method-agnostic implementation of the Decentralized Identifiers (DID) standard." [dependencies] -identity_core = { version = "=1.2.0", path = "../identity_core" } -identity_did = { version = "=1.2.0", path = "../identity_did" } -identity_verification = { version = "=1.2.0", path = "../identity_verification", default-features = false } +identity_core = { version = "=1.4.0", path = "../identity_core", default-features = false } +identity_did = { version = "=1.4.0", path = "../identity_did" } +identity_verification = { version = "=1.4.0", path = "../identity_verification", default-features = false } indexmap = { version = "2.0", default-features = false, features = ["std", "serde"] } serde.workspace = true strum.workspace = true diff --git a/identity_document/src/document/core_document.rs b/identity_document/src/document/core_document.rs index 87fddd0fed..2747f7fae6 100644 --- a/identity_document/src/document/core_document.rs +++ b/identity_document/src/document/core_document.rs @@ -7,6 +7,7 @@ use core::fmt::Formatter; use std::collections::HashMap; use std::convert::Infallible; +use identity_did::DIDJwk; use identity_verification::jose::jwk::Jwk; use identity_verification::jose::jws::DecodedJws; use identity_verification::jose::jws::Decoder; @@ -770,7 +771,7 @@ impl CoreDocument { } /// Returns the first [`Service`] with an `id` property matching the provided `service_query`, if present. - // NOTE: This method demonstrates unexpected behaviour in the edge cases where the document contains + // NOTE: This method demonstrates unexpected behavior in the edge cases where the document contains // services whose ids are of the form #. pub fn resolve_service<'query, 'me, Q>(&'me self, service_query: Q) -> Option<&Service> where @@ -938,8 +939,8 @@ impl CoreDocument { /// Regardless of which options are passed the following conditions must be met in order for a verification attempt to /// take place. /// - The JWS must be encoded according to the JWS compact serialization. - /// - The `kid` value in the protected header must be an identifier of a verification method in this DID document, - /// or set explicitly in the `options`. + /// - The `kid` value in the protected header must be an identifier of a verification method in this DID document, or + /// set explicitly in the `options`. // // NOTE: This is tested in `identity_storage` and `identity_credential`. pub fn verify_jws<'jws, T: JwsVerifier>( @@ -984,6 +985,23 @@ impl CoreDocument { } } +impl CoreDocument { + /// Creates a [`CoreDocument`] from a did:jwk DID. + pub fn expand_did_jwk(did_jwk: DIDJwk) -> Result { + let verification_method = VerificationMethod::try_from(did_jwk.clone()).map_err(Error::InvalidKeyMaterial)?; + let verification_method_id = verification_method.id().clone(); + + DocumentBuilder::default() + .id(did_jwk.into()) + .verification_method(verification_method) + .assertion_method(verification_method_id.clone()) + .authentication(verification_method_id.clone()) + .capability_invocation(verification_method_id.clone()) + .capability_delegation(verification_method_id.clone()) + .build() + } +} + #[cfg(test)] mod tests { use identity_core::convert::FromJson; @@ -1682,4 +1700,33 @@ mod tests { verifier(json); } } + + #[test] + fn test_did_jwk_expansion() { + let did_jwk = "did:jwk:eyJrdHkiOiJPS1AiLCJjcnYiOiJYMjU1MTkiLCJ1c2UiOiJlbmMiLCJ4IjoiM3A3YmZYdDl3YlRUVzJIQzdPUTFOei1EUThoYmVHZE5yZngtRkctSUswOCJ9" + .parse::() + .unwrap(); + let target_doc = serde_json::from_value(serde_json::json!({ + "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" + } + } + ], + "assertionMethod": ["did:jwk:eyJrdHkiOiJPS1AiLCJjcnYiOiJYMjU1MTkiLCJ1c2UiOiJlbmMiLCJ4IjoiM3A3YmZYdDl3YlRUVzJIQzdPUTFOei1EUThoYmVHZE5yZngtRkctSUswOCJ9#0"], + "authentication": ["did:jwk:eyJrdHkiOiJPS1AiLCJjcnYiOiJYMjU1MTkiLCJ1c2UiOiJlbmMiLCJ4IjoiM3A3YmZYdDl3YlRUVzJIQzdPUTFOei1EUThoYmVHZE5yZngtRkctSUswOCJ9#0"], + "capabilityInvocation": ["did:jwk:eyJrdHkiOiJPS1AiLCJjcnYiOiJYMjU1MTkiLCJ1c2UiOiJlbmMiLCJ4IjoiM3A3YmZYdDl3YlRUVzJIQzdPUTFOei1EUThoYmVHZE5yZngtRkctSUswOCJ9#0"], + "capabilityDelegation": ["did:jwk:eyJrdHkiOiJPS1AiLCJjcnYiOiJYMjU1MTkiLCJ1c2UiOiJlbmMiLCJ4IjoiM3A3YmZYdDl3YlRUVzJIQzdPUTFOei1EUThoYmVHZE5yZngtRkctSUswOCJ9#0"] + })).unwrap(); + + assert_eq!(CoreDocument::expand_did_jwk(did_jwk).unwrap(), target_doc); + } } diff --git a/identity_document/src/verifiable/jwp_verification_options.rs b/identity_document/src/verifiable/jwp_verification_options.rs new file mode 100644 index 0000000000..65667968ea --- /dev/null +++ b/identity_document/src/verifiable/jwp_verification_options.rs @@ -0,0 +1,36 @@ +// Copyright 2020-2024 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +use identity_did::DIDUrl; +use identity_verification::MethodScope; + +/// Holds additional options for verifying a JWP +#[non_exhaustive] +#[derive(Default, Debug, serde::Serialize, serde::Deserialize, Clone)] +#[serde(rename_all = "camelCase")] +pub struct JwpVerificationOptions { + /// Verify the signing verification method relation matches this. + pub method_scope: Option, + /// The DID URl of the method, whose JWK should be used to verify the JWP. + /// If unset, the `kid` of the JWP is used as the DID Url. + pub method_id: Option, +} + +impl JwpVerificationOptions { + /// Creates a new [`JwpVerificationOptions`]. + pub fn new() -> Self { + Self::default() + } + + /// Set the scope of the verification methods that may be used to verify the given JWP. + pub fn method_scope(mut self, value: MethodScope) -> Self { + self.method_scope = Some(value); + self + } + + /// The DID URl of the method, whose JWK should be used to verify the JWP. + pub fn method_id(mut self, value: DIDUrl) -> Self { + self.method_id = Some(value); + self + } +} diff --git a/identity_document/src/verifiable/mod.rs b/identity_document/src/verifiable/mod.rs index da91055ca1..6f0386d3fb 100644 --- a/identity_document/src/verifiable/mod.rs +++ b/identity_document/src/verifiable/mod.rs @@ -3,6 +3,8 @@ //! Additional functionality for DID assisted digital signatures. +pub use self::jwp_verification_options::JwpVerificationOptions; pub use self::jws_verification_options::JwsVerificationOptions; +mod jwp_verification_options; mod jws_verification_options; diff --git a/identity_ecdsa_verifier/Cargo.toml b/identity_ecdsa_verifier/Cargo.toml new file mode 100644 index 0000000000..6829d41ae0 --- /dev/null +++ b/identity_ecdsa_verifier/Cargo.toml @@ -0,0 +1,32 @@ +[package] +name = "identity_ecdsa_verifier" +version = "1.4.0" +authors = ["IOTA Stiftung", "Filancore GmbH"] +edition.workspace = true +homepage.workspace = true +keywords = ["iota", "identity", "jose", "jwk", "jws"] +license.workspace = true +readme = "./README.md" +repository.workspace = true +rust-version.workspace = true +description = "JWS ECDSA signature verification for IOTA Identity" + +[lints] +workspace = true + +[dependencies] +identity_verification = { version = "=1.4.0", path = "../identity_verification", default-features = false } +k256 = { version = "0.13.3", default-features = false, features = ["std", "ecdsa", "ecdsa-core"], optional = true } +p256 = { version = "0.13.2", default-features = false, features = ["std", "ecdsa", "ecdsa-core"], optional = true } +signature = { version = "2", default-features = false } + +[dev-dependencies] +josekit = "0.8.6" +serde_json.workspace = true + +[features] +default = ["es256", "es256k"] +# Enables the EcDSAJwsVerifier to verify JWS with alg = ES256. +es256 = ["dep:p256"] +# Enables the EcDSAJwsVerifier to verify JWS with alg = ES256K. +es256k = ["dep:k256"] diff --git a/identity_ecdsa_verifier/README.md b/identity_ecdsa_verifier/README.md new file mode 100644 index 0000000000..4ccb0f36b9 --- /dev/null +++ b/identity_ecdsa_verifier/README.md @@ -0,0 +1,3 @@ +# ECDSA Verifier + +This crate implements a `JwsVerifier` capable of verifying EcDSA signatures with algorithms `ES256` and `ES256K`. diff --git a/identity_ecdsa_verifier/src/ecdsa_jws_verifier.rs b/identity_ecdsa_verifier/src/ecdsa_jws_verifier.rs new file mode 100644 index 0000000000..6371b40b78 --- /dev/null +++ b/identity_ecdsa_verifier/src/ecdsa_jws_verifier.rs @@ -0,0 +1,34 @@ +// Copyright 2020-2024 IOTA Stiftung, Filancore GmbH +// SPDX-License-Identifier: Apache-2.0 + +use identity_verification::jws::JwsAlgorithm; +use identity_verification::jws::JwsVerifier; +use identity_verification::jws::SignatureVerificationErrorKind; + +/// An implementor of [`JwsVerifier`](identity_verification::jws::JwsVerifier) +/// that can handle a selection of EcDSA algorithms. +/// +/// The following algorithms are supported, if the respective feature on the +/// crate is activated: +/// +/// - [`JwsAlgorithm::ES256`](identity_verification::jws::JwsAlgorithm::ES256). +/// - [`JwsAlgorithm::ES256K`](identity_verification::jws::JwsAlgorithm::ES256K). +#[derive(Debug, Default)] +#[non_exhaustive] +pub struct EcDSAJwsVerifier {} + +impl JwsVerifier for EcDSAJwsVerifier { + fn verify( + &self, + input: identity_verification::jws::VerificationInput, + public_key: &identity_verification::jwk::Jwk, + ) -> Result<(), identity_verification::jws::SignatureVerificationError> { + match input.alg { + #[cfg(feature = "es256")] + JwsAlgorithm::ES256 => crate::Secp256R1Verifier::verify(&input, public_key), + #[cfg(feature = "es256k")] + JwsAlgorithm::ES256K => crate::Secp256K1Verifier::verify(&input, public_key), + _ => Err(SignatureVerificationErrorKind::UnsupportedAlg.into()), + } + } +} diff --git a/identity_ecdsa_verifier/src/lib.rs b/identity_ecdsa_verifier/src/lib.rs new file mode 100644 index 0000000000..6136a3eae1 --- /dev/null +++ b/identity_ecdsa_verifier/src/lib.rs @@ -0,0 +1,29 @@ +// Copyright 2020-2024 IOTA Stiftung, Filancore GmbH +// SPDX-License-Identifier: Apache-2.0 + +#![doc = include_str!("./../README.md")] +#![warn( + rust_2018_idioms, + unreachable_pub, + missing_docs, + rustdoc::missing_crate_level_docs, + rustdoc::broken_intra_doc_links, + rustdoc::private_intra_doc_links, + rustdoc::private_doc_tests, + clippy::missing_safety_doc +)] + +mod ecdsa_jws_verifier; +#[cfg(feature = "es256k")] +mod secp256k1; +#[cfg(feature = "es256")] +mod secp256r1; + +pub use ecdsa_jws_verifier::*; +#[cfg(feature = "es256k")] +pub use secp256k1::*; +#[cfg(feature = "es256")] +pub use secp256r1::*; + +#[cfg(test)] +mod tests; diff --git a/identity_ecdsa_verifier/src/secp256k1.rs b/identity_ecdsa_verifier/src/secp256k1.rs new file mode 100644 index 0000000000..9c77412cc8 --- /dev/null +++ b/identity_ecdsa_verifier/src/secp256k1.rs @@ -0,0 +1,93 @@ +// Copyright 2020-2024 IOTA Stiftung, Filancore GmbH +// SPDX-License-Identifier: Apache-2.0 + +use std::ops::Deref; + +use identity_verification::jwk::JwkParamsEc; +use identity_verification::jws::SignatureVerificationError; +use identity_verification::jws::SignatureVerificationErrorKind; +use identity_verification::jwu::{self}; +use k256::ecdsa::Signature; +use k256::ecdsa::VerifyingKey; +use k256::elliptic_curve::sec1::FromEncodedPoint; +use k256::elliptic_curve::subtle::CtOption; +use k256::EncodedPoint; +use k256::PublicKey; + +/// A verifier that can handle the +/// [`JwsAlgorithm::ES256K`](identity_verification::jws::JwsAlgorithm::ES256K) +/// algorithm. +#[derive(Debug, Default)] +#[non_exhaustive] +pub struct Secp256K1Verifier {} + +impl Secp256K1Verifier { + /// Verify a JWS signature secured with the + /// [`JwsAlgorithm::ES256K`](identity_verification::jws::JwsAlgorithm::ES256K) + /// algorithm. + /// + /// This function is useful when one is building a + /// [`JwsVerifier`](identity_verification::jws::JwsVerifier) that + /// handles the + /// [`JwsAlgorithm::ES256K`](identity_verification::jws::JwsAlgorithm::ES256K) + /// in the same manner as the [`Secp256K1Verifier`] hence extending its + /// capabilities. + /// + /// # Warning + /// + /// This function does not check whether `alg = ES256K` in the protected + /// header. Callers are expected to assert this prior to calling the + /// function. + pub fn verify( + input: &identity_verification::jws::VerificationInput, + public_key: &identity_verification::jwk::Jwk, + ) -> Result<(), SignatureVerificationError> { + // Obtain a K256 public key. + let params: &JwkParamsEc = public_key + .try_ec_params() + .map_err(|_| SignatureVerificationErrorKind::UnsupportedKeyType)?; + + // Concatenate x and y coordinates as required by + // EncodedPoint::from_untagged_bytes. + let public_key_bytes = jwu::decode_b64(¶ms.x) + .map_err(|err| { + SignatureVerificationError::new(SignatureVerificationErrorKind::KeyDecodingFailure).with_source(err) + })? + .into_iter() + .chain(jwu::decode_b64(¶ms.y).map_err(|err| { + SignatureVerificationError::new(SignatureVerificationErrorKind::KeyDecodingFailure).with_source(err) + })?) + .collect(); + + // The JWK contains the uncompressed x and y coordinates, so we can create the + // encoded point directly without prefixing an SEC1 tag. + let encoded_point: EncodedPoint = EncodedPoint::from_untagged_bytes(&public_key_bytes); + let public_key: PublicKey = { + let opt_public_key: CtOption = PublicKey::from_encoded_point(&encoded_point); + if opt_public_key.is_none().into() { + return Err(SignatureVerificationError::new( + SignatureVerificationErrorKind::KeyDecodingFailure, + )); + } else { + opt_public_key.unwrap() + } + }; + + let verifying_key: VerifyingKey = VerifyingKey::from(public_key); + + let mut signature: Signature = Signature::try_from(input.decoded_signature.deref()).map_err(|err| { + SignatureVerificationError::new(SignatureVerificationErrorKind::InvalidSignature).with_source(err) + })?; + + if let Some(normalized) = signature.normalize_s() { + signature = normalized; + } + + match signature::Verifier::verify(&verifying_key, &input.signing_input, &signature) { + Ok(()) => Ok(()), + Err(err) => { + Err(SignatureVerificationError::new(SignatureVerificationErrorKind::InvalidSignature).with_source(err)) + } + } + } +} diff --git a/identity_ecdsa_verifier/src/secp256r1.rs b/identity_ecdsa_verifier/src/secp256r1.rs new file mode 100644 index 0000000000..09201570d0 --- /dev/null +++ b/identity_ecdsa_verifier/src/secp256r1.rs @@ -0,0 +1,89 @@ +// Copyright 2020-2024 IOTA Stiftung, Filancore GmbH +// SPDX-License-Identifier: Apache-2.0 + +use std::ops::Deref; + +use identity_verification::jwk::JwkParamsEc; +use identity_verification::jws::SignatureVerificationError; +use identity_verification::jws::SignatureVerificationErrorKind; +use identity_verification::jwu::{self}; +use p256::ecdsa::Signature; +use p256::ecdsa::VerifyingKey; +use p256::elliptic_curve::sec1::FromEncodedPoint; +use p256::elliptic_curve::subtle::CtOption; +use p256::EncodedPoint; +use p256::PublicKey; + +/// A verifier that can handle the +/// [`JwsAlgorithm::ES256`](identity_verification::jws::JwsAlgorithm::ES256) +/// algorithm. +#[derive(Debug, Default)] +#[non_exhaustive] +pub struct Secp256R1Verifier {} + +impl Secp256R1Verifier { + /// Verify a JWS signature secured with the + /// [`JwsAlgorithm::ES256`](identity_verification::jws::JwsAlgorithm::ES256) + /// algorithm. + /// + /// This function is useful when one is building a + /// [`JwsVerifier`](identity_verification::jws::JwsVerifier) that + /// handles the + /// [`JwsAlgorithm::ES256`](identity_verification::jws::JwsAlgorithm::ES256) + /// in the same manner as the [`Secp256R1Verifier`] hence extending its + /// capabilities. + /// + /// # Warning + /// + /// This function does not check whether `alg = ES256` in the protected + /// header. Callers are expected to assert this prior to calling the + /// function. + pub fn verify( + input: &identity_verification::jws::VerificationInput, + public_key: &identity_verification::jwk::Jwk, + ) -> Result<(), SignatureVerificationError> { + // Obtain a P256 public key. + let params: &JwkParamsEc = public_key + .try_ec_params() + .map_err(|_| SignatureVerificationErrorKind::UnsupportedKeyType)?; + + // Concatenate x and y coordinates as required by + // EncodedPoint::from_untagged_bytes. + let public_key_bytes = jwu::decode_b64(¶ms.x) + .map_err(|err| { + SignatureVerificationError::new(SignatureVerificationErrorKind::KeyDecodingFailure).with_source(err) + })? + .into_iter() + .chain(jwu::decode_b64(¶ms.y).map_err(|err| { + SignatureVerificationError::new(SignatureVerificationErrorKind::KeyDecodingFailure).with_source(err) + })?) + .collect(); + + // The JWK contains the uncompressed x and y coordinates, so we can create the + // encoded point directly without prefixing an SEC1 tag. + let encoded_point: EncodedPoint = EncodedPoint::from_untagged_bytes(&public_key_bytes); + let public_key: PublicKey = { + let opt_public_key: CtOption = PublicKey::from_encoded_point(&encoded_point); + if opt_public_key.is_none().into() { + return Err(SignatureVerificationError::new( + SignatureVerificationErrorKind::KeyDecodingFailure, + )); + } else { + opt_public_key.unwrap() + } + }; + + let verifying_key: VerifyingKey = VerifyingKey::from(public_key); + + let signature: Signature = Signature::try_from(input.decoded_signature.deref()).map_err(|err| { + SignatureVerificationError::new(SignatureVerificationErrorKind::InvalidSignature).with_source(err) + })?; + + match signature::Verifier::verify(&verifying_key, &input.signing_input, &signature) { + Ok(()) => Ok(()), + Err(err) => { + Err(SignatureVerificationError::new(SignatureVerificationErrorKind::InvalidSignature).with_source(err)) + } + } + } +} diff --git a/identity_ecdsa_verifier/src/tests/mod.rs b/identity_ecdsa_verifier/src/tests/mod.rs new file mode 100644 index 0000000000..63e508fa33 --- /dev/null +++ b/identity_ecdsa_verifier/src/tests/mod.rs @@ -0,0 +1,5 @@ +// Copyright 2020-2024 IOTA Stiftung, Filancore GmbH +// SPDX-License-Identifier: Apache-2.0 + +mod secp256; +mod secp256k; diff --git a/identity_ecdsa_verifier/src/tests/secp256.rs b/identity_ecdsa_verifier/src/tests/secp256.rs new file mode 100644 index 0000000000..c6700a85e4 --- /dev/null +++ b/identity_ecdsa_verifier/src/tests/secp256.rs @@ -0,0 +1,77 @@ +// Copyright 2020-2024 IOTA Stiftung, Filancore GmbH +// SPDX-License-Identifier: Apache-2.0 + +mod es256 { + use identity_verification::jwk::EcCurve; + use identity_verification::jwk::Jwk; + use identity_verification::jwk::JwkParamsEc; + use identity_verification::jwu; + use p256::ecdsa::Signature; + use p256::ecdsa::SigningKey; + use p256::SecretKey; + + pub(crate) fn expand_p256_jwk(jwk: &Jwk) -> SecretKey { + let params: &JwkParamsEc = jwk.try_ec_params().unwrap(); + + if params.try_ec_curve().unwrap() != EcCurve::P256 { + panic!("expected a P256 curve"); + } + + let sk_bytes = params.d.as_ref().map(jwu::decode_b64).unwrap().unwrap(); + SecretKey::from_slice(&sk_bytes).unwrap() + } + + pub(crate) fn sign(message: &[u8], private_key: &Jwk) -> impl AsRef<[u8]> { + let sk: SecretKey = expand_p256_jwk(private_key); + let signing_key: SigningKey = SigningKey::from(sk); + let signature: Signature = signature::Signer::sign(&signing_key, message); + signature.to_bytes() + } +} + +use identity_verification::jwk::Jwk; +use identity_verification::jws; +use identity_verification::jws::JwsHeader; + +use crate::EcDSAJwsVerifier; + +#[test] +fn test_es256_rfc7515() { + // Test Vector taken from https://datatracker.ietf.org/doc/html/rfc7515#appendix-A.3. + let tv_header: &str = r#"{"alg":"ES256"}"#; + let tv_claims: &[u8] = &[ + 123, 34, 105, 115, 115, 34, 58, 34, 106, 111, 101, 34, 44, 13, 10, 32, 34, 101, 120, 112, 34, 58, 49, 51, 48, 48, + 56, 49, 57, 51, 56, 48, 44, 13, 10, 32, 34, 104, 116, 116, 112, 58, 47, 47, 101, 120, 97, 109, 112, 108, 101, 46, + 99, 111, 109, 47, 105, 115, 95, 114, 111, 111, 116, 34, 58, 116, 114, 117, 101, 125, + ]; + let tv_encoded: &[u8] = b"eyJhbGciOiJFUzI1NiJ9.eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ.e4ZrhZdbFQ7630Tq51E6RQiJaae9bFNGJszIhtusEwzvO21rzH76Wer6yRn2Zb34VjIm3cVRl0iQctbf4uBY3w"; + let tv_private_key: &str = r#" + { + "kty": "EC", + "crv": "P-256", + "x": "f83OJ3D2xF1Bg8vub9tLe1gHMzV76e8Tus9uPHvRVEU", + "y": "x_FEzRu9m36HLN_tue659LNpXW6pCyStikYjKIWI5a0", + "d": "jpsQnnGQmL-YBIffH1136cspYG6-0iY7X1fCE9-E9LI" + } + "#; + + let header: JwsHeader = serde_json::from_str(tv_header).unwrap(); + let jwk: Jwk = serde_json::from_str(tv_private_key).unwrap(); + let encoder: jws::CompactJwsEncoder<'_> = jws::CompactJwsEncoder::new(tv_claims, &header).unwrap(); + let signing_input: &[u8] = encoder.signing_input(); + let encoded: String = { + let signature = es256::sign(signing_input, &jwk); + encoder.into_jws(signature.as_ref()) + }; + assert_eq!(encoded.as_bytes(), tv_encoded); + + let jws_verifier = EcDSAJwsVerifier::default(); + let decoder = jws::Decoder::new(); + let token = decoder + .decode_compact_serialization(tv_encoded, None) + .and_then(|decoded| decoded.verify(&jws_verifier, &jwk)) + .unwrap(); + + assert_eq!(token.protected, header); + assert_eq!(token.claims, tv_claims); +} diff --git a/identity_ecdsa_verifier/src/tests/secp256k.rs b/identity_ecdsa_verifier/src/tests/secp256k.rs new file mode 100644 index 0000000000..49f234a3c7 --- /dev/null +++ b/identity_ecdsa_verifier/src/tests/secp256k.rs @@ -0,0 +1,112 @@ +// Copyright 2020-2024 IOTA Stiftung, Filancore GmbH +// SPDX-License-Identifier: Apache-2.0 + +mod es256k1 { + use identity_verification::jwk::EcCurve; + use identity_verification::jwk::Jwk; + use identity_verification::jwk::JwkParamsEc; + use identity_verification::jwu; + use k256::ecdsa::Signature; + use k256::ecdsa::SigningKey; + use k256::SecretKey; + + pub(crate) fn expand_k256_jwk(jwk: &Jwk) -> SecretKey { + let params: &JwkParamsEc = jwk.try_ec_params().unwrap(); + + if params.try_ec_curve().unwrap() != EcCurve::Secp256K1 { + panic!("expected a Secp256K1 curve"); + } + + let sk_bytes = params.d.as_ref().map(jwu::decode_b64).unwrap().unwrap(); + SecretKey::from_slice(&sk_bytes).unwrap() + } + + pub(crate) fn sign(message: &[u8], private_key: &Jwk) -> impl AsRef<[u8]> { + let sk: SecretKey = expand_k256_jwk(private_key); + let signing_key: SigningKey = SigningKey::from(sk); + let signature: Signature = signature::Signer::sign(&signing_key, message); + signature.to_bytes() + } +} + +use identity_verification::jwk::Jwk; +use identity_verification::jws; +use identity_verification::jws::JwsHeader; + +use crate::EcDSAJwsVerifier; + +#[test] +fn test_es256k_verifier() { + let tv_header: &str = r#"{ + "typ": "JWT", + "alg":"ES256K" + }"#; + let tv_private_key: &str = r#" + { + "kty":"EC", + "crv":"secp256k1", + "d":"y0zUV7bLeUG_kDOvACFHnSmtH7j8MSJek25R2wJbWWg", + "x":"BBobbZkiC8E4C4EYekPNJkcXFCsMNHhh0AV2USy_xSs", + "y":"VQcPHjIQClX0b5TLluFl6jpIf9U-norWC0oEvIQRNyU" + }"#; + let tv_claims: &[u8] = br#"{"key":"value"}"#; + + let header: JwsHeader = serde_json::from_str(tv_header).unwrap(); + let jwk: Jwk = serde_json::from_str(tv_private_key).unwrap(); + let encoder: jws::CompactJwsEncoder<'_> = jws::CompactJwsEncoder::new(tv_claims, &header).unwrap(); + let signing_input: &[u8] = encoder.signing_input(); + let encoded: String = { + let signature = es256k1::sign(signing_input, &jwk); + encoder.into_jws(signature.as_ref()) + }; + + let jws_verifier = EcDSAJwsVerifier::default(); + let jwk: Jwk = serde_json::from_str(tv_private_key).unwrap(); + let decoder = jws::Decoder::new(); + assert!(decoder + .decode_compact_serialization(encoded.as_bytes(), None) + .and_then(|decoded| decoded.verify(&jws_verifier, &jwk)) + .is_ok()); +} + +/// In the absence of official test vectors for secp256k1, +/// this ensures we can verify JWTs created by other libraries. +mod test_es256k_josekit { + use identity_verification::jws; + use josekit::jwk::alg::ec::EcKeyPair; + use josekit::jwk::Jwk; + use josekit::jws::JwsHeader; + use josekit::jwt::JwtPayload; + + use crate::EcDSAJwsVerifier; + + #[test] + fn test_es256k_josekit() { + let alg = josekit::jws::ES256K; + + let private_key: &str = r#" + { + "kty":"EC", + "crv":"secp256k1", + "d":"y0zUV7bLeUG_kDOvACFHnSmtH7j8MSJek25R2wJbWWg", + "x":"BBobbZkiC8E4C4EYekPNJkcXFCsMNHhh0AV2USy_xSs", + "y":"VQcPHjIQClX0b5TLluFl6jpIf9U-norWC0oEvIQRNyU" + }"#; + let josekit_jwk: Jwk = serde_json::from_str(private_key).unwrap(); + let mut src_header = JwsHeader::new(); + src_header.set_token_type("JWT"); + let mut src_payload = JwtPayload::new(); + src_payload.set_claim("key", Some("value".into())).unwrap(); + let eckp = EcKeyPair::from_jwk(&josekit_jwk).unwrap(); + let signer = alg.signer_from_jwk(&eckp.to_jwk_key_pair()).unwrap(); + let jwt_string = josekit::jwt::encode_with_signer(&src_payload, &src_header, &signer).unwrap(); + + let jws_verifier = EcDSAJwsVerifier::default(); + let decoder = jws::Decoder::new(); + let jwk: identity_verification::jwk::Jwk = serde_json::from_str(private_key).unwrap(); + assert!(decoder + .decode_compact_serialization(jwt_string.as_bytes(), None) + .and_then(|decoded| decoded.verify(&jws_verifier, &jwk)) + .is_ok()); + } +} diff --git a/identity_eddsa_verifier/Cargo.toml b/identity_eddsa_verifier/Cargo.toml index b8a3cff943..b7da49295a 100644 --- a/identity_eddsa_verifier/Cargo.toml +++ b/identity_eddsa_verifier/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "identity_eddsa_verifier" -version = "1.2.0" +version = "1.4.0" authors.workspace = true edition.workspace = true homepage.workspace = true @@ -12,8 +12,8 @@ rust-version.workspace = true description = "JWS EdDSA signature verification for IOTA Identity" [dependencies] -identity_jose = { version = "=1.2.0", path = "../identity_jose", default-features = false } -iota-crypto = { version = "0.23", default-features = false, features = ["std"] } +identity_jose = { version = "=1.4.0", path = "../identity_jose", default-features = false } +iota-crypto = { version = "0.23.2", default-features = false, features = ["std"] } [features] ed25519 = ["iota-crypto/ed25519"] diff --git a/identity_iota/Cargo.toml b/identity_iota/Cargo.toml index 6dc782d3ae..f6db2209d2 100644 --- a/identity_iota/Cargo.toml +++ b/identity_iota/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "identity_iota" -version = "1.2.0" +version = "1.4.0" authors.workspace = true edition.workspace = true homepage.workspace = true @@ -12,14 +12,14 @@ rust-version.workspace = true description = "Framework for Self-Sovereign Identity with IOTA DID." [dependencies] -identity_core = { version = "=1.2.0", path = "../identity_core", default-features = false } -identity_credential = { version = "=1.2.0", path = "../identity_credential", features = ["validator"], default-features = false } -identity_did = { version = "=1.2.0", path = "../identity_did", default-features = false } -identity_document = { version = "=1.2.0", path = "../identity_document", default-features = false } -identity_iota_core = { version = "=1.2.0", path = "../identity_iota_core", default-features = false } -identity_resolver = { version = "=1.2.0", path = "../identity_resolver", default-features = false, optional = true } -identity_storage = { version = "=1.2.0", path = "../identity_storage", default-features = false, features = ["iota-document"] } -identity_verification = { version = "=1.2.0", path = "../identity_verification", default-features = false } +identity_core = { version = "=1.4.0", path = "../identity_core", default-features = false } +identity_credential = { version = "=1.4.0", path = "../identity_credential", features = ["validator"], default-features = false } +identity_did = { version = "=1.4.0", path = "../identity_did", default-features = false } +identity_document = { version = "=1.4.0", path = "../identity_document", default-features = false } +identity_iota_core = { version = "=1.4.0", path = "../identity_iota_core", default-features = false } +identity_resolver = { version = "=1.4.0", path = "../identity_resolver", default-features = false, optional = true } +identity_storage = { version = "=1.4.0", path = "../identity_storage", default-features = false, features = ["iota-document"] } +identity_verification = { version = "=1.4.0", path = "../identity_verification", default-features = false } [features] default = ["revocation-bitmap", "client", "iota-client", "kinesis-client", "resolver"] @@ -66,6 +66,9 @@ memstore = ["identity_storage/memstore"] # Enables selective disclosure features. sd-jwt = ["identity_credential/sd-jwt"] +# Enables zero knowledge selective disclosurable VCs +jpt-bbs-plus = ["identity_storage/jpt-bbs-plus", "identity_credential/jpt-bbs-plus"] + [package.metadata.docs.rs] # To build locally: # RUSTDOCFLAGS="--cfg docsrs" cargo +nightly doc --all-features --no-deps --workspace --open diff --git a/identity_iota/README.md b/identity_iota/README.md index ea2c38b2ec..ef2d8cb15e 100644 --- a/identity_iota/README.md +++ b/identity_iota/README.md @@ -32,6 +32,9 @@ IOTA Identity is a [Rust](https://www.rust-lang.org/) implementation of decentra - [Web Assembly](https://github.com/iotaledger/identity.rs/blob/HEAD/bindings/wasm/) (JavaScript/TypeScript) +## gRPC + +We provide a collection of experimental [gRPC services](https://github.com/iotaledger/identity.rs/blob/HEAD/bindings/grpc/) ## Documentation and Resources - API References: @@ -51,7 +54,7 @@ If you want to include IOTA Identity in your project, simply add it as a depende ```toml [dependencies] -identity_iota = { version = "1.2.0" } +identity_iota = { version = "1.4.0" } ``` To try out the [examples](https://github.com/iotaledger/identity.rs/blob/HEAD/examples), you can also do this: @@ -67,6 +70,17 @@ See the [instructions](https://github.com/iotaledger/iota-sandbox) on running yo _Cargo.toml_ + + + ```toml [package] name = "iota_identity_example" @@ -74,7 +88,7 @@ version = "1.0.0" edition = "2021" [dependencies] -identity_iota = { version = "1.2.0", features = ["memstore"] } +identity_iota = { version = "1.4.0", features = ["memstore"] } iota-sdk = { version = "1.0.2", default-features = true, features = ["tls", "client", "stronghold"] } tokio = { version = "1", features = ["full"] } anyhow = "1.0.62" @@ -83,6 +97,19 @@ rand = "0.8.5" _main.__rs_ + + + + ```rust,no_run use identity_iota::core::ToJson; use identity_iota::iota::IotaClientExt; diff --git a/identity_iota/src/lib.rs b/identity_iota/src/lib.rs index 24a20359eb..9ab2e53805 100644 --- a/identity_iota/src/lib.rs +++ b/identity_iota/src/lib.rs @@ -105,7 +105,22 @@ pub mod verification { pub mod storage { //! Storage traits. - pub use identity_storage::*; + /// KeyIdStorage types and functionalities. + pub mod key_id_storage { + pub use identity_storage::key_id_storage::*; + } + /// KeyStorage types and functionalities. + pub mod key_storage { + pub use identity_storage::key_storage::public_modules::*; + } + /// Storage types and functionalities. + #[allow(clippy::module_inception)] + pub mod storage { + pub use identity_storage::storage::*; + } + pub use identity_storage::key_id_storage::*; + pub use identity_storage::key_storage::*; + pub use identity_storage::storage::*; } #[cfg(feature = "sd-jwt")] diff --git a/identity_iota_core/Cargo.toml b/identity_iota_core/Cargo.toml index fcd092dc8d..381d959ba6 100644 --- a/identity_iota_core/Cargo.toml +++ b/identity_iota_core/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "identity_iota_core" -version = "1.2.0" +version = "1.4.0" authors.workspace = true edition.workspace = true homepage.workspace = true @@ -15,11 +15,11 @@ description = "An IOTA Ledger integration for the IOTA DID Method." anyhow = "1.0.75" async-trait = { version = "0.1.81", default-features = false, optional = true } futures = { version = "0.3", default-features = false } -identity_core = { version = "=1.2.0", path = "../identity_core", default-features = false } -identity_credential = { version = "=1.2.0", path = "../identity_credential", default-features = false, features = ["validator"] } -identity_did = { version = "=1.2.0", path = "../identity_did", default-features = false } -identity_document = { version = "=1.2.0", path = "../identity_document", default-features = false } -identity_verification = { version = "=1.2.0", path = "../identity_verification", default-features = false } +identity_core = { version = "=1.4.0", path = "../identity_core", default-features = false } +identity_credential = { version = "=1.4.0", path = "../identity_credential", default-features = false, features = ["validator"] } +identity_did = { version = "=1.4.0", path = "../identity_did", default-features = false } +identity_document = { version = "=1.4.0", path = "../identity_document", default-features = false } +identity_verification = { version = "=1.4.0", path = "../identity_verification", default-features = false } iota_sdk_legacy = { version = "1.1.5", default-features = false, features = ["serde", "std"], optional = true, package = "iota-sdk" } num-derive = { version = "0.4", default-features = false } num-traits = { version = "0.2", default-features = false, features = ["std"] } @@ -34,8 +34,8 @@ thiserror.workspace = true # for feature `kinesis-client` bcs = { version = "0.1.4", optional = true } fastcrypto = { git = "https://github.com/MystenLabs/fastcrypto", rev = "5f2c63266a065996d53f98156f0412782b468597", package = "fastcrypto", optional = true } -identity_eddsa_verifier = { version = "=1.2.0", path = "../identity_eddsa_verifier", optional = true } -identity_jose = { version = "=1.2.0", path = "../identity_jose", optional = true } +identity_eddsa_verifier = { version = "=1.4.0", path = "../identity_eddsa_verifier", optional = true } +identity_jose = { version = "=1.4.0", path = "../identity_jose", optional = true } iota-config = { git = "https://github.com/iotaledger/iota.git", package = "iota-config", tag = "v0.7.0-alpha", optional = true } iota-crypto = { version = "0.23", optional = true } iota-sdk = { git = "https://github.com/iotaledger/iota.git", package = "iota-sdk", tag = "v0.7.0-alpha", optional = true } diff --git a/identity_iota_core/src/document/iota_document.rs b/identity_iota_core/src/document/iota_document.rs index e779077336..5ee3cfe778 100644 --- a/identity_iota_core/src/document/iota_document.rs +++ b/identity_iota_core/src/document/iota_document.rs @@ -555,6 +555,15 @@ impl From for CoreDocument { } } +impl From for IotaDocument { + fn from(value: CoreDocument) -> Self { + IotaDocument { + document: value, + metadata: IotaDocumentMetadata::default(), + } + } +} + impl TryFrom<(CoreDocument, IotaDocumentMetadata)> for IotaDocument { type Error = Error; /// Converts the tuple into an [`IotaDocument`] if the given [`CoreDocument`] has an identifier satisfying the diff --git a/identity_iota_core/src/document/test_utils.rs b/identity_iota_core/src/document/test_utils.rs index b8c48cadf4..b45d418751 100644 --- a/identity_iota_core/src/document/test_utils.rs +++ b/identity_iota_core/src/document/test_utils.rs @@ -24,7 +24,7 @@ fn encode_public_ed25519_jwk(public_key: &[u8]) -> Jwk { let mut params = JwkParamsOkp::new(); params.x = x; params.d = None; - params.crv = EdCurve::Ed25519.name().to_owned(); + params.crv = EdCurve::Ed25519.name().to_string(); let mut jwk = Jwk::from_params(params); jwk.set_alg(JwsAlgorithm::EdDSA.name()); jwk diff --git a/identity_iota_core/src/rebased/assets/asset.rs b/identity_iota_core/src/rebased/assets/asset.rs index a696710616..525cefb5f6 100644 --- a/identity_iota_core/src/rebased/assets/asset.rs +++ b/identity_iota_core/src/rebased/assets/asset.rs @@ -33,7 +33,7 @@ use serde::Deserialize; use serde::Deserializer; use serde::Serialize; -// An on-chain asset that carries information about its owned and its creator. +/// An on-chain asset that carries information about its owned and its creator. #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct AuthenticatedAsset { id: UID, @@ -229,12 +229,12 @@ impl AuthenticatedAssetBuilder { /// A [`TransferProposal`] is a **shared** _Move_ object that represents a request to transfer ownership /// of an [`AuthenticatedAsset`] to a new owner. /// -/// When a [`TransferProposal`] is created, it will seize the asset and send a `SenderCap` token to the current asset's owner -/// and a `RecipientCap` to the specified `recipient` address. -/// `recipient` can accept the transfer by presenting its `RecipientCap` (this prevents other users from claiming the asset -/// for themselves). -/// The current owner can cancel the proposal at any time - given the transfer hasn't been conclued yet - by presenting its -/// `SenderCap`. +/// When a [`TransferProposal`] is created, it will seize the asset and send a `SenderCap` token to the current asset's +/// owner and a `RecipientCap` to the specified `recipient` address. +/// `recipient` can accept the transfer by presenting its `RecipientCap` (this prevents other users from claiming the +/// asset for themselves). +/// The current owner can cancel the proposal at any time - given the transfer hasn't been conclued yet - by presenting +/// its `SenderCap`. #[derive(Debug, Clone, Serialize, Deserialize)] pub struct TransferProposal { id: UID, @@ -341,8 +341,8 @@ impl TransferProposal { /// Concludes or cancels this [`TransferProposal`]. /// # Warning /// * This operation only has an effects when it's invoked by this [`TransferProposal`]'s `sender`. - /// * Accepting a [`TransferProposal`] **doesn't** consume it from the ledger. This function must be used - /// to correctly consume both [`TransferProposal`] and `SenderCap`. + /// * Accepting a [`TransferProposal`] **doesn't** consume it from the ledger. This function must be used to correctly + /// consume both [`TransferProposal`] and `SenderCap`. pub fn conclude_or_cancel(self) -> ConcludeTransferTx { ConcludeTransferTx(self) } diff --git a/identity_iota_core/src/rebased/error.rs b/identity_iota_core/src/rebased/error.rs index fe01f090ea..e537e22a97 100644 --- a/identity_iota_core/src/rebased/error.rs +++ b/identity_iota_core/src/rebased/error.rs @@ -57,6 +57,7 @@ pub enum Error { #[error("identity error; {0}")] Identity(String), #[error("unexpected state when looking up identity history; {0}")] + /// Unexpected state when looking up identity history. InvalidIdentityHistory(String), /// An operation cannot be carried on for a lack of permissions - e.g. missing capability. #[error("the requested operation cannot be performed for a lack of permissions; {0}")] diff --git a/identity_iota_core/src/rebased/proposals/borrow.rs b/identity_iota_core/src/rebased/proposals/borrow.rs index 524339b26f..60d309e7b0 100644 --- a/identity_iota_core/src/rebased/proposals/borrow.rs +++ b/identity_iota_core/src/rebased/proposals/borrow.rs @@ -4,14 +4,15 @@ use std::collections::HashMap; use std::marker::PhantomData; -use crate::rebased::{ - client::{IdentityClient, IotaKeySignature}, - migration::Proposal, - sui::move_calls, - transaction::{ProtoTransaction, Transaction, TransactionOutput}, - utils::MoveType, - Error, -}; +use crate::rebased::client::IdentityClient; +use crate::rebased::client::IotaKeySignature; +use crate::rebased::migration::Proposal; +use crate::rebased::sui::move_calls; +use crate::rebased::transaction::ProtoTransaction; +use crate::rebased::transaction::Transaction; +use crate::rebased::transaction::TransactionOutput; +use crate::rebased::utils::MoveType; +use crate::rebased::Error; use async_trait::async_trait; use iota_sdk::rpc_types::IotaObjectData; use iota_sdk::rpc_types::IotaTransactionBlockResponse; diff --git a/identity_iota_core/src/rebased/sui/move_calls/identity/borrow_asset.rs b/identity_iota_core/src/rebased/sui/move_calls/identity/borrow_asset.rs index 9e3378ffe6..2e5268e5ea 100644 --- a/identity_iota_core/src/rebased/sui/move_calls/identity/borrow_asset.rs +++ b/identity_iota_core/src/rebased/sui/move_calls/identity/borrow_asset.rs @@ -3,17 +3,20 @@ use std::collections::HashMap; -use iota_sdk::{ - rpc_types::{IotaObjectData, OwnedObjectRef}, - types::{ - base_types::{ObjectID, ObjectRef, ObjectType}, - programmable_transaction_builder::ProgrammableTransactionBuilder, - transaction::{Argument, ObjectArg, ProgrammableTransaction}, - }, -}; +use iota_sdk::rpc_types::IotaObjectData; +use iota_sdk::rpc_types::OwnedObjectRef; +use iota_sdk::types::base_types::ObjectID; +use iota_sdk::types::base_types::ObjectRef; +use iota_sdk::types::base_types::ObjectType; +use iota_sdk::types::programmable_transaction_builder::ProgrammableTransactionBuilder; +use iota_sdk::types::transaction::Argument; +use iota_sdk::types::transaction::ObjectArg; +use iota_sdk::types::transaction::ProgrammableTransaction; use move_core_types::ident_str; -use crate::rebased::{proposals::BorrowAction, sui::move_calls::utils, utils::MoveType}; +use crate::rebased::proposals::BorrowAction; +use crate::rebased::sui::move_calls::utils; +use crate::rebased::utils::MoveType; pub(crate) fn propose_borrow( identity: OwnedObjectRef, diff --git a/identity_iota_core/src/rebased/sui/move_calls/migration.rs b/identity_iota_core/src/rebased/sui/move_calls/migration.rs index f6afe64a4a..f56f291ba3 100644 --- a/identity_iota_core/src/rebased/sui/move_calls/migration.rs +++ b/identity_iota_core/src/rebased/sui/move_calls/migration.rs @@ -2,15 +2,13 @@ // SPDX-License-Identifier: Apache-2.0 use super::utils; -use iota_sdk::{ - rpc_types::OwnedObjectRef, - types::{ - base_types::{ObjectID, ObjectRef}, - programmable_transaction_builder::ProgrammableTransactionBuilder as Ptb, - transaction::{ObjectArg, ProgrammableTransaction}, - IOTA_FRAMEWORK_PACKAGE_ID, - }, -}; +use iota_sdk::rpc_types::OwnedObjectRef; +use iota_sdk::types::base_types::ObjectID; +use iota_sdk::types::base_types::ObjectRef; +use iota_sdk::types::programmable_transaction_builder::ProgrammableTransactionBuilder as Ptb; +use iota_sdk::types::transaction::ObjectArg; +use iota_sdk::types::transaction::ProgrammableTransaction; +use iota_sdk::types::IOTA_FRAMEWORK_PACKAGE_ID; use move_core_types::ident_str; pub(crate) fn migrate_did_output( diff --git a/identity_jose/Cargo.toml b/identity_jose/Cargo.toml index 00c7826304..8da1e2ec5a 100644 --- a/identity_jose/Cargo.toml +++ b/identity_jose/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "identity_jose" -version = "1.2.0" +version = "1.4.0" authors.workspace = true edition.workspace = true homepage.workspace = true @@ -12,8 +12,10 @@ rust-version.workspace = true description = "A library for JOSE (JSON Object Signing and Encryption)" [dependencies] -identity_core = { version = "=1.2.0", path = "../identity_core", default-features = false } -iota-crypto = { version = "0.23", default-features = false, features = ["std", "sha"] } +bls12_381_plus.workspace = true +identity_core = { version = "=1.4.0", path = "../identity_core" } +iota-crypto = { version = "0.23.2", default-features = false, features = ["std", "sha"] } +json-proof-token.workspace = true serde.workspace = true serde_json = { version = "1.0", default-features = false, features = ["std"] } thiserror.workspace = true @@ -30,3 +32,10 @@ test = true [lints] workspace = true + +[features] +custom_alg = [] + +[[test]] +name = "custom_alg" +required-features = ["custom_alg"] diff --git a/identity_jose/src/jwk/curve/bls.rs b/identity_jose/src/jwk/curve/bls.rs new file mode 100644 index 0000000000..97b68bf678 --- /dev/null +++ b/identity_jose/src/jwk/curve/bls.rs @@ -0,0 +1,43 @@ +// Copyright 2020-2024 IOTA Stiftung, Fondazione Links +// SPDX-License-Identifier: Apache-2.0 + +use core::fmt::Display; +use core::fmt::Formatter; +use core::fmt::Result; + +/// Supported BLS Curves. +/// +/// [More Info](https://datatracker.ietf.org/doc/html/draft-ietf-cose-bls-key-representations-05#name-curve-parameter-registratio) +#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)] +pub enum BlsCurve { + /// A cryptographic key on the Barreto-Lynn-Scott (BLS) curve featuring an embedding degree 12 with 381-bit p in the + /// subgroup of G1. + BLS12381G1, + /// A cryptographic key on the Barreto-Lynn-Scott (BLS) curve featuring an embedding degree 12 with 381-bit p in the + /// subgroup of G2. + BLS12381G2, + /// A cryptographic key on the Barreto-Lynn-Scott (BLS) curve featuring an embedding degree 48 with 581-bit p in the + /// subgroup of G1. + BLS48581G1, + /// A cryptographic key on the Barreto-Lynn-Scott (BLS) curve featuring an embedding degree 48 with 581-bit p in the + /// subgroup of G2. + BLS48581G2, +} + +impl BlsCurve { + /// Returns the name of the curve as a string slice. + pub const fn name(self) -> &'static str { + match self { + Self::BLS12381G1 => "BLS12381G1", + Self::BLS12381G2 => "BLS12381G2", + Self::BLS48581G1 => "BLS48581G1", + Self::BLS48581G2 => "BLS48581G2", + } + } +} + +impl Display for BlsCurve { + fn fmt(&self, f: &mut Formatter<'_>) -> Result { + f.write_str(self.name()) + } +} diff --git a/identity_jose/src/jwk/curve/mod.rs b/identity_jose/src/jwk/curve/mod.rs index 38a1e3bba7..8e1627219f 100644 --- a/identity_jose/src/jwk/curve/mod.rs +++ b/identity_jose/src/jwk/curve/mod.rs @@ -1,10 +1,12 @@ // Copyright 2020-2023 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 +mod bls; mod ec; mod ecx; mod ed; +pub use self::bls::*; pub use self::ec::*; pub use self::ecx::*; pub use self::ed::*; diff --git a/identity_jose/src/jwk/jwk_ext.rs b/identity_jose/src/jwk/jwk_ext.rs new file mode 100644 index 0000000000..39fc02fa93 --- /dev/null +++ b/identity_jose/src/jwk/jwk_ext.rs @@ -0,0 +1,162 @@ +// Copyright 2020-2024 IOTA Stiftung, Fondazione Links +// SPDX-License-Identifier: Apache-2.0 + +use super::Jwk; +use super::JwkOperation; +use super::JwkParams; +use super::JwkParamsEc; +use super::JwkType; +use super::JwkUse; +use identity_core::common::Url; +use jsonprooftoken::jpa::algs::ProofAlgorithm; +use jsonprooftoken::jwk::alg_parameters::Algorithm; +use jsonprooftoken::jwk::alg_parameters::JwkAlgorithmParameters; +use jsonprooftoken::jwk::alg_parameters::JwkEllipticCurveKeyParameters; +use jsonprooftoken::jwk::curves::EllipticCurveTypes; +use jsonprooftoken::jwk::key::Jwk as JwkExt; +use jsonprooftoken::jwk::key::KeyOps; +use jsonprooftoken::jwk::key::PKUse; +use jsonprooftoken::jwk::types::KeyType; +use std::str::FromStr; + +impl From for JwkOperation { + fn from(value: KeyOps) -> Self { + match value { + KeyOps::Sign => Self::Sign, + KeyOps::Verify => Self::Verify, + KeyOps::Encrypt => Self::Encrypt, + KeyOps::Decrypt => Self::Decrypt, + KeyOps::WrapKey => Self::WrapKey, + KeyOps::UnwrapKey => Self::UnwrapKey, + KeyOps::DeriveKey => Self::DeriveKey, + KeyOps::DeriveBits => Self::DeriveBits, + KeyOps::ProofGeneration => Self::ProofGeneration, + KeyOps::ProofVerification => Self::ProofVerification, + } + } +} + +impl From for KeyOps { + fn from(value: JwkOperation) -> Self { + match value { + JwkOperation::Sign => Self::Sign, + JwkOperation::Verify => Self::Verify, + JwkOperation::Encrypt => Self::Encrypt, + JwkOperation::Decrypt => Self::Decrypt, + JwkOperation::WrapKey => Self::WrapKey, + JwkOperation::UnwrapKey => Self::UnwrapKey, + JwkOperation::DeriveKey => Self::DeriveKey, + JwkOperation::DeriveBits => Self::DeriveBits, + JwkOperation::ProofGeneration => Self::ProofGeneration, + JwkOperation::ProofVerification => Self::ProofVerification, + } + } +} + +impl From for JwkUse { + fn from(value: PKUse) -> Self { + match value { + PKUse::Signature => Self::Signature, + PKUse::Encryption => Self::Encryption, + PKUse::Proof => Self::Proof, + } + } +} + +impl From for PKUse { + fn from(value: JwkUse) -> Self { + match value { + JwkUse::Signature => Self::Signature, + JwkUse::Encryption => Self::Encryption, + JwkUse::Proof => Self::Proof, + } + } +} + +impl From for JwkParamsEc { + fn from(value: JwkEllipticCurveKeyParameters) -> Self { + Self { + crv: value.crv.to_string(), + x: value.x, + y: value.y, + d: value.d, + } + } +} + +impl TryInto for &JwkParamsEc { + type Error = crate::error::Error; + + fn try_into(self) -> Result { + Ok(JwkEllipticCurveKeyParameters { + kty: KeyType::EllipticCurve, + crv: EllipticCurveTypes::from_str(&self.crv).map_err(|_| Self::Error::KeyError("crv not supported!"))?, + x: self.x.clone(), + y: self.y.clone(), + d: self.d.clone(), + }) + } +} + +impl TryFrom for Jwk { + type Error = crate::error::Error; + + fn try_from(value: JwkExt) -> Result { + let x5u = match value.x5u { + Some(v) => Some(Url::from_str(&v).map_err(|_| Self::Error::InvalidClaim("x5u"))?), + None => None, + }; + + let (kty, params) = match value.key_params { + JwkAlgorithmParameters::EllipticCurve(p) => (JwkType::Ec, JwkParams::Ec(JwkParamsEc::from(p))), + _ => unreachable!(), + }; + + Ok(Self { + kty, + use_: value.pk_use.map(JwkUse::from), + key_ops: value + .key_ops + .map(|vec_key_ops| vec_key_ops.into_iter().map(JwkOperation::from).collect()), + alg: value.alg.map(|a| a.to_string()), + kid: value.kid, + x5u, + x5c: value.x5c, + x5t: value.x5t, + x5t_s256: None, + params, + }) + } +} + +impl TryInto for &Jwk { + type Error = crate::error::Error; + + fn try_into(self) -> Result { + let params = match &self.params { + JwkParams::Ec(p) => JwkAlgorithmParameters::EllipticCurve(p.try_into()?), + _ => return Err(Self::Error::InvalidParam("Parameters not supported!")), + }; + + let alg = match &self.alg { + Some(a) => Some(Algorithm::Proof( + ProofAlgorithm::from_str(a).map_err(|_| Self::Error::KeyError("Invalid alg"))?, + )), + None => None, + }; + + Ok(JwkExt { + kid: self.kid.clone(), + pk_use: self.use_.map(|u| u.into()), + key_ops: self + .key_ops + .as_deref() + .and_then(|vec_key_ops| vec_key_ops.iter().map(|o| Some((*o).into())).collect()), + alg, + x5u: self.x5u.as_ref().map(|v| v.as_str().to_string()), + x5c: self.x5c.clone(), + x5t: self.x5t.clone(), + key_params: params, + }) + } +} diff --git a/identity_jose/src/jwk/key.rs b/identity_jose/src/jwk/key.rs index be1db84c35..e2cb05d62d 100644 --- a/identity_jose/src/jwk/key.rs +++ b/identity_jose/src/jwk/key.rs @@ -395,9 +395,9 @@ impl Jwk { // =========================================================================== /// Checks if the `alg` claim of the JWK is equal to `expected`. - pub fn check_alg(&self, expected: &str) -> Result<()> { + pub fn check_alg(&self, expected: impl AsRef) -> Result<()> { match self.alg() { - Some(value) if value == expected => Ok(()), + Some(value) if value == expected.as_ref() => Ok(()), Some(_) => Err(Error::InvalidClaim("alg")), None => Ok(()), } diff --git a/identity_jose/src/jwk/key_operation.rs b/identity_jose/src/jwk/key_operation.rs index 8fda0b6a23..ac6b7b0ce8 100644 --- a/identity_jose/src/jwk/key_operation.rs +++ b/identity_jose/src/jwk/key_operation.rs @@ -27,6 +27,10 @@ pub enum JwkOperation { DeriveKey, /// Derive bits not to be used as a key. DeriveBits, + /// Compute proof + ProofGeneration, + /// Verify proof + ProofVerification, } impl JwkOperation { @@ -41,6 +45,8 @@ impl JwkOperation { Self::UnwrapKey => "unwrapKey", Self::DeriveKey => "deriveKey", Self::DeriveBits => "deriveBits", + Self::ProofGeneration => "proofGeneration", + Self::ProofVerification => "proofVerification", } } @@ -55,6 +61,8 @@ impl JwkOperation { Self::UnwrapKey => Self::WrapKey, Self::DeriveKey => Self::DeriveKey, Self::DeriveBits => Self::DeriveBits, + Self::ProofGeneration => Self::ProofVerification, + Self::ProofVerification => Self::ProofGeneration, } } } diff --git a/identity_jose/src/jwk/key_params.rs b/identity_jose/src/jwk/key_params.rs index 8fa5a36aa1..9d1437637a 100644 --- a/identity_jose/src/jwk/key_params.rs +++ b/identity_jose/src/jwk/key_params.rs @@ -10,6 +10,8 @@ use crate::jwk::EcxCurve; use crate::jwk::EdCurve; use crate::jwk::JwkType; +use super::BlsCurve; + /// Algorithm-specific parameters for JSON Web Keys. /// /// [More Info](https://tools.ietf.org/html/rfc7518#section-6) @@ -104,9 +106,9 @@ pub struct JwkParamsEc { } impl Default for JwkParamsEc { - fn default() -> Self { - Self::new() - } + fn default() -> Self { + Self::new() + } } impl JwkParamsEc { @@ -155,6 +157,17 @@ impl JwkParamsEc { _ => Err(Error::KeyError("Ec Curve")), } } + + /// Returns the [`BlsCurve`] if it is of a supported type. + pub fn try_bls_curve(&self) -> Result { + match &*self.crv { + "BLS12381G1" => Ok(BlsCurve::BLS12381G1), + "BLS12381G2" => Ok(BlsCurve::BLS12381G2), + "BLS48581G1" => Ok(BlsCurve::BLS48581G1), + "BLS48581G2" => Ok(BlsCurve::BLS48581G2), + _ => Err(Error::KeyError("BLS Curve")), + } + } } impl From for JwkParams { @@ -245,9 +258,9 @@ pub struct JwkParamsRsaPrime { } impl Default for JwkParamsRsa { - fn default() -> Self { - Self::new() - } + fn default() -> Self { + Self::new() + } } impl JwkParamsRsa { @@ -333,9 +346,9 @@ pub struct JwkParamsOct { } impl Default for JwkParamsOct { - fn default() -> Self { - Self::new() - } + fn default() -> Self { + Self::new() + } } impl JwkParamsOct { @@ -388,9 +401,9 @@ pub struct JwkParamsOkp { } impl Default for JwkParamsOkp { - fn default() -> Self { - Self::new() - } + fn default() -> Self { + Self::new() + } } impl JwkParamsOkp { diff --git a/identity_jose/src/jwk/key_use.rs b/identity_jose/src/jwk/key_use.rs index a686ba79cc..edd427c578 100644 --- a/identity_jose/src/jwk/key_use.rs +++ b/identity_jose/src/jwk/key_use.rs @@ -16,6 +16,9 @@ pub enum JwkUse { /// Encryption. #[serde(rename = "enc")] Encryption, + /// Proof + #[serde(rename = "proof")] + Proof, } impl JwkUse { @@ -24,6 +27,7 @@ impl JwkUse { match self { Self::Signature => "sig", Self::Encryption => "enc", + Self::Proof => "proof", } } } diff --git a/identity_jose/src/jwk/mod.rs b/identity_jose/src/jwk/mod.rs index a714cbf5ac..780c7f9861 100644 --- a/identity_jose/src/jwk/mod.rs +++ b/identity_jose/src/jwk/mod.rs @@ -4,6 +4,7 @@ //! JSON Web Keys ([JWK](https://tools.ietf.org/html/rfc7517)) mod curve; +mod jwk_ext; mod key; mod key_operation; mod key_params; diff --git a/identity_jose/src/jws/algorithm.rs b/identity_jose/src/jws/algorithm.rs index a60dc84050..1d6b1c319c 100644 --- a/identity_jose/src/jws/algorithm.rs +++ b/identity_jose/src/jws/algorithm.rs @@ -6,12 +6,11 @@ use core::fmt::Formatter; use core::fmt::Result; use std::str::FromStr; -use crate::error::Error; - /// Supported algorithms for the JSON Web Signatures `alg` claim. /// /// [More Info](https://www.iana.org/assignments/jose/jose.xhtml#web-signature-encryption-algorithms) -#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, PartialOrd, Ord, serde::Deserialize, serde::Serialize)] +#[derive(Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord, serde::Deserialize, serde::Serialize)] +#[cfg_attr(not(feature = "custom_alg"), derive(Copy))] #[allow(non_camel_case_types)] pub enum JwsAlgorithm { /// HMAC using SHA-256 @@ -45,10 +44,19 @@ pub enum JwsAlgorithm { NONE, /// EdDSA signature algorithms EdDSA, + /// Custom algorithm + #[cfg(feature = "custom_alg")] + #[serde(untagged)] + Custom(String), } impl JwsAlgorithm { /// A slice of all supported [`JwsAlgorithm`]s. + /// + /// Not available when feature `custom_alg` is enabled + /// as it is not possible to enumerate all variants when + /// supporting arbitrary `alg` values. + #[cfg(not(feature = "custom_alg"))] pub const ALL: &'static [Self] = &[ Self::HS256, Self::HS384, @@ -68,6 +76,7 @@ impl JwsAlgorithm { ]; /// Returns the JWS algorithm as a `str` slice. + #[cfg(not(feature = "custom_alg"))] pub const fn name(self) -> &'static str { match self { Self::HS256 => "HS256", @@ -87,6 +96,29 @@ impl JwsAlgorithm { Self::EdDSA => "EdDSA", } } + + /// Returns the JWS algorithm as a `str` slice. + #[cfg(feature = "custom_alg")] + pub fn name(&self) -> String { + match self { + Self::HS256 => "HS256".to_string(), + Self::HS384 => "HS384".to_string(), + Self::HS512 => "HS512".to_string(), + Self::RS256 => "RS256".to_string(), + Self::RS384 => "RS384".to_string(), + Self::RS512 => "RS512".to_string(), + Self::PS256 => "PS256".to_string(), + Self::PS384 => "PS384".to_string(), + Self::PS512 => "PS512".to_string(), + Self::ES256 => "ES256".to_string(), + Self::ES384 => "ES384".to_string(), + Self::ES512 => "ES512".to_string(), + Self::ES256K => "ES256K".to_string(), + Self::NONE => "none".to_string(), + Self::EdDSA => "EdDSA".to_string(), + Self::Custom(name) => name.clone(), + } + } } impl FromStr for JwsAlgorithm { @@ -109,13 +141,24 @@ impl FromStr for JwsAlgorithm { "ES256K" => Ok(Self::ES256K), "none" => Ok(Self::NONE), "EdDSA" => Ok(Self::EdDSA), - _ => Err(Error::JwsAlgorithmParsingError), + #[cfg(feature = "custom_alg")] + value => Ok(Self::Custom(value.to_string())), + #[cfg(not(feature = "custom_alg"))] + _ => Err(crate::error::Error::JwsAlgorithmParsingError), } } } +#[cfg(not(feature = "custom_alg"))] impl Display for JwsAlgorithm { fn fmt(&self, f: &mut Formatter<'_>) -> Result { f.write_str(self.name()) } } + +#[cfg(feature = "custom_alg")] +impl Display for JwsAlgorithm { + fn fmt(&self, f: &mut Formatter<'_>) -> Result { + f.write_str(&(*self).name()) + } +} diff --git a/identity_jose/src/jws/header.rs b/identity_jose/src/jws/header.rs index b5749608a1..30b4f5fe82 100644 --- a/identity_jose/src/jws/header.rs +++ b/identity_jose/src/jws/header.rs @@ -67,7 +67,7 @@ impl JwsHeader { /// Returns the value for the algorithm claim (alg). pub fn alg(&self) -> Option { - self.alg.as_ref().copied() + self.alg.as_ref().cloned() } /// Sets a value for the algorithm claim (alg). diff --git a/identity_jose/src/jwt/header.rs b/identity_jose/src/jwt/header.rs index 09059e7268..ca87211c84 100644 --- a/identity_jose/src/jwt/header.rs +++ b/identity_jose/src/jwt/header.rs @@ -106,9 +106,9 @@ pub struct JwtHeader { } impl Default for JwtHeader { - fn default() -> Self { - Self::new() - } + fn default() -> Self { + Self::new() + } } impl JwtHeader { diff --git a/identity_jose/src/jwu/serde.rs b/identity_jose/src/jwu/serde.rs index a5e6c1f84d..e875da0e10 100644 --- a/identity_jose/src/jwu/serde.rs +++ b/identity_jose/src/jwu/serde.rs @@ -24,10 +24,10 @@ pub(crate) fn parse_utf8(slice: &(impl AsRef<[u8]> + ?Sized)) -> Result<&str> { str::from_utf8(slice.as_ref()).map_err(Error::InvalidUtf8) } -pub(crate) fn filter_non_empty_bytes<'a, T, U: 'a>(value: T) -> Option<&'a [u8]> +pub(crate) fn filter_non_empty_bytes<'a, T, U>(value: T) -> Option<&'a [u8]> where T: Into>, - U: AsRef<[u8]> + ?Sized, + U: AsRef<[u8]> + ?Sized + 'a, { value.into().map(AsRef::as_ref).filter(|value| !value.is_empty()) } @@ -57,8 +57,7 @@ pub(crate) fn validate_jws_headers(protected: Option<&JwsHeader>, unprotected: O /// Validates that the "crit" parameter satisfies the following requirements: /// 1. It is integrity protected. /// 2. It is not encoded as an empty list. -/// 3. It does not contain any header parameters defined by the -/// JOSE JWS/JWA specifications. +/// 3. It does not contain any header parameters defined by the JOSE JWS/JWA specifications. /// 4. It's values are contained in the given `permitted` array. /// 5. All values in "crit" are present in at least one of the `protected` or `unprotected` headers. /// diff --git a/identity_jose/src/tests/rfc8037.rs b/identity_jose/src/tests/rfc8037.rs index aada7a7369..27bb755979 100644 --- a/identity_jose/src/tests/rfc8037.rs +++ b/identity_jose/src/tests/rfc8037.rs @@ -50,21 +50,20 @@ fn test_rfc8037_ed25519() { .and_then(|decoded| decoded.verify(&jws_verifier, &public)) .unwrap(); - #[cfg(feature = "eddsa")] - { - let jws_signature_verifier = JwsVerifierFn::from(|input: VerificationInput, key: &Jwk| match input.alg { - JwsAlgorithm::EdDSA => ed25519::verify(input, key), - other => unimplemented!("{other}"), - }); - - let decoder = Decoder::new(); - let token_with_default = decoder - .decode_compact_serialization(jws.as_bytes(), None) - .and_then(|decoded| decoded.verify(&jws_signature_verifier, &public)) - .unwrap(); - assert_eq!(token, token_with_default); - } assert_eq!(token.protected, header); assert_eq!(token.claims, tv.payload.as_bytes()); + + let jws_signature_verifier = JwsVerifierFn::from(|input: VerificationInput, key: &Jwk| match input.alg { + JwsAlgorithm::EdDSA => ed25519::verify(input, key), + other => unimplemented!("{other}"), + }); + + let decoder = Decoder::new(); + let token_with_default = decoder + .decode_compact_serialization(jws.as_bytes(), None) + .and_then(|decoded| decoded.verify(&jws_signature_verifier, &public)) + .unwrap(); + + assert_eq!(token, token_with_default); } } diff --git a/identity_jose/tests/custom_alg.rs b/identity_jose/tests/custom_alg.rs new file mode 100644 index 0000000000..3297d6258b --- /dev/null +++ b/identity_jose/tests/custom_alg.rs @@ -0,0 +1,110 @@ +// Copyright 2020-2024 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +use std::ops::Deref; +use std::time::SystemTime; + +use crypto::signatures::ed25519::PublicKey; +use crypto::signatures::ed25519::SecretKey; +use crypto::signatures::ed25519::Signature; +use identity_jose::jwk::EdCurve; +use identity_jose::jwk::Jwk; +use identity_jose::jwk::JwkParamsOkp; +use identity_jose::jwk::JwkType; +use identity_jose::jws::CompactJwsEncoder; +use identity_jose::jws::Decoder; +use identity_jose::jws::JwsAlgorithm; +use identity_jose::jws::JwsHeader; +use identity_jose::jws::JwsVerifierFn; +use identity_jose::jws::SignatureVerificationError; +use identity_jose::jws::SignatureVerificationErrorKind; +use identity_jose::jws::VerificationInput; +use identity_jose::jwt::JwtClaims; +use identity_jose::jwu; +use jsonprooftoken::encoding::base64url_decode; + +#[test] +fn custom_alg_roundtrip() { + let secret_key = SecretKey::generate().unwrap(); + let public_key = secret_key.public_key(); + + let mut header: JwsHeader = JwsHeader::new(); + header.set_alg(JwsAlgorithm::Custom("test".to_string())); + let kid = "did:iota:0x123#signing-key"; + header.set_kid(kid); + + let mut claims: JwtClaims = JwtClaims::new(); + claims.set_iss("issuer"); + claims.set_iat( + SystemTime::now() + .duration_since(SystemTime::UNIX_EPOCH) + .unwrap() + .as_secs() as i64, + ); + claims.set_custom(serde_json::json!({"num": 42u64})); + + let claims_bytes: Vec = serde_json::to_vec(&claims).unwrap(); + + let encoder: CompactJwsEncoder<'_> = CompactJwsEncoder::new(&claims_bytes, &header).unwrap(); + let signing_input: &[u8] = encoder.signing_input(); + let signature = secret_key.sign(signing_input).to_bytes(); + let jws = encoder.into_jws(&signature); + + let header = jws.split(".").next().unwrap(); + let header_json = String::from_utf8(base64url_decode(header.as_bytes())).expect("failed to decode header"); + assert_eq!(header_json, r#"{"kid":"did:iota:0x123#signing-key","alg":"test"}"#); + + let verifier = JwsVerifierFn::from(|input: VerificationInput, key: &Jwk| { + if input.alg != JwsAlgorithm::Custom("test".to_string()) { + panic!("invalid algorithm"); + } + verify(input, key) + }); + let decoder = Decoder::new(); + let mut public_key_jwk = Jwk::new(JwkType::Okp); + public_key_jwk.set_kid(kid); + public_key_jwk + .set_params(JwkParamsOkp { + crv: "Ed25519".into(), + x: jwu::encode_b64(public_key.as_slice()), + d: None, + }) + .unwrap(); + + let token = decoder + .decode_compact_serialization(jws.as_bytes(), None) + .and_then(|decoded| decoded.verify(&verifier, &public_key_jwk)) + .unwrap(); + + let recovered_claims: JwtClaims = serde_json::from_slice(&token.claims).unwrap(); + + assert_eq!(token.protected.alg(), Some(JwsAlgorithm::Custom("test".to_string()))); + assert_eq!(claims, recovered_claims); +} + +fn verify(verification_input: VerificationInput, jwk: &Jwk) -> Result<(), SignatureVerificationError> { + let public_key = expand_public_jwk(jwk); + + let signature_arr = <[u8; Signature::LENGTH]>::try_from(verification_input.decoded_signature.deref()) + .map_err(|err| err.to_string()) + .unwrap(); + + let signature = Signature::from_bytes(signature_arr); + if public_key.verify(&signature, &verification_input.signing_input) { + Ok(()) + } else { + Err(SignatureVerificationErrorKind::InvalidSignature.into()) + } +} + +fn expand_public_jwk(jwk: &Jwk) -> PublicKey { + let params: &JwkParamsOkp = jwk.try_okp_params().unwrap(); + + if params.try_ed_curve().unwrap() != EdCurve::Ed25519 { + panic!("expected an ed25519 jwk"); + } + + let pk: [u8; PublicKey::LENGTH] = jwu::decode_b64(params.x.as_str()).unwrap().try_into().unwrap(); + + PublicKey::try_from(pk).unwrap() +} diff --git a/identity_resolver/Cargo.toml b/identity_resolver/Cargo.toml index a652d46b26..8346aede14 100644 --- a/identity_resolver/Cargo.toml +++ b/identity_resolver/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "identity_resolver" -version = "1.2.0" +version = "1.4.0" authors.workspace = true edition.workspace = true homepage.workspace = true @@ -15,16 +15,16 @@ description = "DID Resolution utilities for the identity.rs library." # This is currently necessary for the ResolutionHandler trait. This can be made an optional dependency if alternative ways of attaching handlers are introduced. async-trait = { version = "0.1", default-features = false } futures = { version = "0.3" } -identity_core = { version = "=1.2.0", path = "../identity_core", default-features = false } -identity_credential = { version = "=1.2.0", path = "../identity_credential", default-features = false, features = ["validator"] } -identity_did = { version = "=1.2.0", path = "../identity_did", default-features = false } -identity_document = { version = "=1.2.0", path = "../identity_document", default-features = false } +identity_core = { version = "=1.4.0", path = "../identity_core", default-features = false } +identity_credential = { version = "=1.4.0", path = "../identity_credential", default-features = false, features = ["validator"] } +identity_did = { version = "=1.4.0", path = "../identity_did", default-features = false } +identity_document = { version = "=1.4.0", path = "../identity_document", default-features = false } serde = { version = "1.0", default-features = false, features = ["std", "derive"] } strum.workspace = true thiserror = { version = "1.0", default-features = false } [dependencies.identity_iota_core] -version = "=1.2.0" +version = "=1.4.0" path = "../identity_iota_core" default-features = false features = ["send-sync-client-ext", "iota-client"] @@ -32,7 +32,7 @@ optional = true [dev-dependencies] identity_iota_core = { path = "../identity_iota_core", features = ["test"] } -iota-sdk = { version = "1.0.2" } +iota-sdk = { version = "1.1.5" } tokio = { version = "1.29.0", default-features = false, features = ["rt-multi-thread", "macros"] } [features] diff --git a/identity_resolver/src/resolution/resolver.rs b/identity_resolver/src/resolution/resolver.rs index 04abb9e5bc..caf216b387 100644 --- a/identity_resolver/src/resolution/resolver.rs +++ b/identity_resolver/src/resolution/resolver.rs @@ -4,6 +4,7 @@ use core::future::Future; use futures::stream::FuturesUnordered; use futures::TryStreamExt; +use identity_did::DIDJwk; use identity_did::DID; use std::collections::HashSet; @@ -247,6 +248,22 @@ impl Resolver> { } } +impl + 'static> Resolver> { + /// Attaches a handler capable of resolving `did:jwk` DIDs. + pub fn attach_did_jwk_handler(&mut self) { + let handler = |did_jwk: DIDJwk| async move { CoreDocument::expand_did_jwk(did_jwk) }; + self.attach_handler(DIDJwk::METHOD.to_string(), handler) + } +} + +impl + 'static> Resolver> { + /// Attaches a handler capable of resolving `did:jwk` DIDs. + pub fn attach_did_jwk_handler(&mut self) { + let handler = |did_jwk: DIDJwk| async move { CoreDocument::expand_did_jwk(did_jwk) }; + self.attach_handler(DIDJwk::METHOD.to_string(), handler) + } +} + #[cfg(any(feature = "iota", feature = "kinesis"))] mod iota_handler { use crate::ErrorCause; @@ -306,10 +323,10 @@ mod iota_handler { /// /// # Note /// - /// - Using `attach_iota_handler` or `attach_handler` for the IOTA method would override all - /// previously added clients. - /// - This function does not validate the provided configuration. Ensure that the provided - /// network name corresponds with the client, possibly by using `client.network_name()`. + /// - Using `attach_iota_handler` or `attach_handler` for the IOTA method would override all previously added + /// clients. + /// - This function does not validate the provided configuration. Ensure that the provided network name + /// corresponds with the client, possibly by using `client.network_name()`. pub fn attach_multiple_iota_handlers(&mut self, clients: I) where CLI: IotaIdentityClientExt + Send + Sync + 'static, @@ -386,10 +403,10 @@ mod iota_handler { /// /// # Note /// - /// - Using `attach_iota_handler` or `attach_handler` for the IOTA method would override all - /// previously added clients. - /// - This function does not validate the provided configuration. Ensure that the provided - /// network name corresponds with the client, possibly by using `client.network_name()`. + /// - Using `attach_iota_handler` or `attach_handler` for the IOTA method would override all previously added + /// clients. + /// - This function does not validate the provided configuration. Ensure that the provided network name + /// corresponds with the client, possibly by using `client.network_name()`. pub fn attach_multiple_kinesis_iota_handlers(&mut self, clients: I) where I: IntoIterator, @@ -503,4 +520,15 @@ mod tests { let doc = resolver.resolve(&did2).await.unwrap(); assert_eq!(doc.id(), &did2); } + + #[tokio::test] + async fn test_did_jwk_resolution() { + let mut resolver = Resolver::::new(); + resolver.attach_did_jwk_handler(); + + let did_jwk = "did:jwk:eyJrdHkiOiJPS1AiLCJjcnYiOiJYMjU1MTkiLCJ1c2UiOiJlbmMiLCJ4IjoiM3A3YmZYdDl3YlRUVzJIQzdPUTFOei1EUThoYmVHZE5yZngtRkctSUswOCJ9".parse::().unwrap(); + + let doc = resolver.resolve(&did_jwk).await.unwrap(); + assert_eq!(doc.id(), did_jwk.as_ref()); + } } diff --git a/identity_storage/Cargo.toml b/identity_storage/Cargo.toml index d2825dcdac..fed168beab 100644 --- a/identity_storage/Cargo.toml +++ b/identity_storage/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "identity_storage" -version = "1.2.0" +version = "1.4.0" authors.workspace = true edition.workspace = true homepage.workspace = true @@ -12,16 +12,18 @@ rust-version.workspace = true description = "Abstractions over storage for cryptographic keys used in DID Documents" [dependencies] -anyhow = { version = "1.0.57" } +anyhow = "1.0.82" async-trait = { version = "0.1.64", default-features = false } +bls12_381_plus = { workspace = true, optional = true } futures = { version = "0.3.27", default-features = false, features = ["async-await"] } -identity_core = { version = "=1.2.0", path = "../identity_core", default-features = false } -identity_credential = { version = "=1.2.0", path = "../identity_credential", default-features = false, features = ["credential", "presentation"] } -identity_did = { version = "=1.2.0", path = "../identity_did", default-features = false } -identity_document = { version = "=1.2.0", path = "../identity_document", default-features = false } -identity_iota_core = { version = "=1.2.0", path = "../identity_iota_core", default-features = false, optional = true } -identity_verification = { version = "=1.2.0", path = "../identity_verification", default-features = false } -iota-crypto = { version = "0.23", default-features = false, features = ["ed25519"], optional = true } +identity_core = { version = "=1.4.0", path = "../identity_core", default-features = false } +identity_credential = { version = "=1.4.0", path = "../identity_credential", default-features = false, features = ["credential", "presentation"] } +identity_did = { version = "=1.4.0", path = "../identity_did", default-features = false } +identity_document = { version = "=1.4.0", path = "../identity_document", default-features = false } +identity_iota_core = { version = "=1.4.0", path = "../identity_iota_core", default-features = false, optional = true } +identity_verification = { version = "=1.4.0", path = "../identity_verification", default-features = false } +iota-crypto = { version = "0.23.2", default-features = false, features = ["ed25519"], optional = true } +json-proof-token = { workspace = true, optional = true } rand = { version = "0.8.5", default-features = false, features = ["std", "std_rng"], optional = true } seahash = { version = "4.1.0", default-features = false } secret-storage = { git = "https://github.com/iotaledger/secret-storage.git", branch = "main", optional = true } @@ -29,10 +31,11 @@ serde.workspace = true serde_json.workspace = true thiserror.workspace = true tokio = { version = "1.29.0", default-features = false, features = ["macros", "sync"], optional = true } +zkryptium = { workspace = true, optional = true } [dev-dependencies] -identity_credential = { version = "=1.2.0", path = "../identity_credential", features = ["revocation-bitmap"] } -identity_eddsa_verifier = { version = "=1.2.0", path = "../identity_eddsa_verifier", default-features = false, features = ["ed25519"] } +identity_credential = { version = "=1.4.0", path = "../identity_credential", features = ["revocation-bitmap"] } +identity_eddsa_verifier = { version = "=1.4.0", path = "../identity_eddsa_verifier", default-features = false, features = ["ed25519"] } once_cell = { version = "1.18", default-features = false } tokio = { version = "1.29.0", default-features = false, features = ["macros", "sync", "rt"] } @@ -46,6 +49,13 @@ send-sync-storage = ["identity_iota_core?/send-sync-client-ext"] iota-document = ["dep:identity_iota_core"] # enables support to sign via storage storage-signer = ["identity_iota_core?/kinesis-client", "dep:secret-storage"] +# Enables JSON Proof Token & BBS+ related features +jpt-bbs-plus = [ + "identity_credential/jpt-bbs-plus", + "dep:zkryptium", + "dep:bls12_381_plus", + "dep:json-proof-token", +] [lints] workspace = true diff --git a/identity_storage/src/key_id_storage/key_id_storage_error.rs b/identity_storage/src/key_id_storage/key_id_storage_error.rs index 046b6cc5f8..d628585692 100644 --- a/identity_storage/src/key_id_storage/key_id_storage_error.rs +++ b/identity_storage/src/key_id_storage/key_id_storage_error.rs @@ -52,7 +52,7 @@ impl KeyIdStorageErrorKind { Self::KeyIdNotFound => "key id not found in storage", Self::Unavailable => "key id storage unavailable", Self::Unauthenticated => "authentication with the key id storage failed", - Self::Unspecified => "key storage operation failed", + Self::Unspecified => "key id storage operation failed", Self::RetryableIOFailure => "key id storage was unsuccessful because of an I/O failure", Self::SerializationError => "(de)serialization error", } diff --git a/identity_storage/src/key_storage/bls.rs b/identity_storage/src/key_storage/bls.rs new file mode 100644 index 0000000000..2a3b38a0a7 --- /dev/null +++ b/identity_storage/src/key_storage/bls.rs @@ -0,0 +1,203 @@ +// Copyright 2020-2024 IOTA Stiftung, Fondazione Links +// SPDX-License-Identifier: Apache-2.0 + +use anyhow::Context; +use identity_verification::jose::jwk::Jwk; +use identity_verification::jose::jwu; +use identity_verification::jwk::BlsCurve; +use identity_verification::jwk::JwkParamsEc; +use jsonprooftoken::jpa::algs::ProofAlgorithm; +use zkryptium::bbsplus::ciphersuites::BbsCiphersuite; +use zkryptium::bbsplus::ciphersuites::Bls12381Sha256; +use zkryptium::bbsplus::ciphersuites::Bls12381Shake256; +use zkryptium::bbsplus::keys::BBSplusPublicKey; +use zkryptium::bbsplus::keys::BBSplusSecretKey; +use zkryptium::keys::pair::KeyPair; +use zkryptium::schemes::algorithms::BBSplus; +use zkryptium::schemes::generics::Signature; + +use crate::key_storage::KeyStorageError; +use crate::key_storage::KeyStorageErrorKind; +use crate::key_storage::KeyStorageResult; +use crate::ProofUpdateCtx; + +fn random_bbs_keypair() -> Result<(BBSplusSecretKey, BBSplusPublicKey), zkryptium::errors::Error> +where + S: BbsCiphersuite, +{ + KeyPair::>::random().map(KeyPair::into_parts) +} + +/// Generates a new BBS+ keypair using either `BLS12381-SHA256` or `BLS12381-SHAKE256`. +pub fn generate_bbs_keypair(alg: ProofAlgorithm) -> KeyStorageResult<(BBSplusSecretKey, BBSplusPublicKey)> { + match alg { + ProofAlgorithm::BLS12381_SHA256 => random_bbs_keypair::(), + ProofAlgorithm::BLS12381_SHAKE256 => random_bbs_keypair::(), + _ => return Err(KeyStorageErrorKind::UnsupportedProofAlgorithm.into()), + } + .map_err(|err| KeyStorageError::new(KeyStorageErrorKind::Unspecified).with_source(err)) +} + +/// Encodes a private BBS+ key into JWK. +pub fn encode_bls_jwk( + private_key: &BBSplusSecretKey, + public_key: &BBSplusPublicKey, + alg: ProofAlgorithm, +) -> (Jwk, Jwk) { + let (x, y) = public_key.to_coordinates(); + let x = jwu::encode_b64(x); + let y = jwu::encode_b64(y); + + let d = jwu::encode_b64(private_key.to_bytes()); + let params = JwkParamsEc { + x, + y, + d: Some(d), + crv: BlsCurve::BLS12381G2.name().to_owned(), + }; + + let mut jwk = Jwk::from_params(params); + + jwk.set_alg(alg.to_string()); + jwk.set_kid(jwk.thumbprint_sha256_b64()); + let public_jwk = jwk.to_public().expect("kty != oct"); + + (jwk, public_jwk) +} + +/// Attempts to decode JWK into a BBS+ keypair. +pub fn expand_bls_jwk(jwk: &Jwk) -> KeyStorageResult<(Option, BBSplusPublicKey)> { + // Check the provided JWK represents a BLS12381G2 key. + let params = jwk + .try_ec_params() + .ok() + .filter(|params| { + params + .try_bls_curve() + .map(|curve| curve == BlsCurve::BLS12381G2) + .unwrap_or(false) + }) + .context(format!("not a {} curve key", BlsCurve::BLS12381G2)) + .map_err(|e| KeyStorageError::new(KeyStorageErrorKind::UnsupportedKeyType).with_source(e))?; + + let sk = params + .d + .as_deref() + .map(|d| { + jwu::decode_b64(d) + .context("`d` parameter is not base64 encoded") + .and_then(|bytes| BBSplusSecretKey::from_bytes(&bytes).context("invalid key size")) + }) + .transpose() + .map_err(|e| KeyStorageError::new(KeyStorageErrorKind::Unspecified).with_source(e))?; + + let x = jwu::decode_b64(¶ms.x) + .context("`x` parameter is not base64 encoded") + .and_then(|bytes| bytes.try_into().ok().context("invalid coordinate size")) + .map_err(|e| KeyStorageError::new(KeyStorageErrorKind::UnsupportedKeyType).with_source(e))?; + let y = jwu::decode_b64(¶ms.y) + .context("`y` parameter is not base64 encoded") + .and_then(|bytes| bytes.try_into().ok().context("invalid coordinate size")) + .map_err(|e| KeyStorageError::new(KeyStorageErrorKind::UnsupportedKeyType).with_source(e))?; + + let pk = BBSplusPublicKey::from_coordinates(&x, &y).map_err(|e| { + KeyStorageError::new(KeyStorageErrorKind::Unspecified) + .with_source(e) + .with_custom_message("invalid BBS+ public key".to_owned()) + })?; + + Ok((sk, pk)) +} + +fn _sign_bbs( + data: &[Vec], + sk: &BBSplusSecretKey, + pk: &BBSplusPublicKey, + header: &[u8], +) -> Result, zkryptium::errors::Error> +where + S: BbsCiphersuite, +{ + Signature::>::sign(Some(data), sk, pk, Some(header)).map(|s| s.to_bytes().to_vec()) +} + +/// Signs data and header using the given keys. +pub fn sign_bbs( + alg: ProofAlgorithm, + data: &[Vec], + sk: &BBSplusSecretKey, + pk: &BBSplusPublicKey, + header: &[u8], +) -> KeyStorageResult> { + match alg { + ProofAlgorithm::BLS12381_SHA256 => _sign_bbs::(data, sk, pk, header), + ProofAlgorithm::BLS12381_SHAKE256 => _sign_bbs::(data, sk, pk, header), + _ => return Err(KeyStorageErrorKind::UnsupportedProofAlgorithm.into()), + } + .map_err(|e| { + KeyStorageError::new(KeyStorageErrorKind::Unspecified) + .with_source(e) + .with_custom_message("signature failed".to_owned()) + }) +} + +fn _update_bbs_signature( + sig: &[u8; 80], + sk: &BBSplusSecretKey, + update_ctx: &ProofUpdateCtx, +) -> Result<[u8; 80], zkryptium::errors::Error> +where + S: BbsCiphersuite, +{ + let sig = Signature::>::from_bytes(sig)?; + let ProofUpdateCtx { + old_start_validity_timeframe, + new_start_validity_timeframe, + old_end_validity_timeframe, + new_end_validity_timeframe, + index_start_validity_timeframe, + index_end_validity_timeframe, + number_of_signed_messages, + } = update_ctx; + let half_updated = sig.update_signature( + sk, + old_start_validity_timeframe, + new_start_validity_timeframe, + *index_start_validity_timeframe, + *number_of_signed_messages, + )?; + half_updated + .update_signature( + sk, + old_end_validity_timeframe, + new_end_validity_timeframe, + *index_end_validity_timeframe, + *number_of_signed_messages, + ) + .map(|sig| sig.to_bytes()) +} + +/// Updates BBS+ signature's timeframe data. +pub fn update_bbs_signature( + alg: ProofAlgorithm, + sig: &[u8], + sk: &BBSplusSecretKey, + update_ctx: &ProofUpdateCtx, +) -> KeyStorageResult> { + let exact_size_signature = sig.try_into().map_err(|_| { + KeyStorageError::new(KeyStorageErrorKind::Unspecified).with_custom_message("invalid signature size".to_owned()) + })?; + match alg { + ProofAlgorithm::BLS12381_SHA256 => _update_bbs_signature::(exact_size_signature, sk, update_ctx), + ProofAlgorithm::BLS12381_SHAKE256 => { + _update_bbs_signature::(exact_size_signature, sk, update_ctx) + } + _ => return Err(KeyStorageErrorKind::UnsupportedProofAlgorithm.into()), + } + .map(Vec::from) + .map_err(|e| { + KeyStorageError::new(KeyStorageErrorKind::Unspecified) + .with_custom_message("signature failed") + .with_source(e) + }) +} diff --git a/identity_storage/src/key_storage/ed25519.rs b/identity_storage/src/key_storage/ed25519.rs index c8750e1f39..619493c35d 100644 --- a/identity_storage/src/key_storage/ed25519.rs +++ b/identity_storage/src/key_storage/ed25519.rs @@ -53,6 +53,6 @@ pub(crate) fn encode_jwk(private_key: &SecretKey, public_key: &crypto::signature let mut params = JwkParamsOkp::new(); params.x = x; params.d = Some(d); - params.crv = EdCurve::Ed25519.name().to_owned(); + params.crv = EdCurve::Ed25519.name().to_string(); Jwk::from_params(params) } diff --git a/identity_storage/src/key_storage/jwk_storage_bbs_plus_ext.rs b/identity_storage/src/key_storage/jwk_storage_bbs_plus_ext.rs new file mode 100644 index 0000000000..276c39d4cb --- /dev/null +++ b/identity_storage/src/key_storage/jwk_storage_bbs_plus_ext.rs @@ -0,0 +1,40 @@ +// Copyright 2020-2024 IOTA Stiftung, Fondazione Links +// SPDX-License-Identifier: Apache-2.0 + +use async_trait::async_trait; +use identity_verification::jwk::Jwk; +use jsonprooftoken::jpa::algs::ProofAlgorithm; + +use crate::JwkGenOutput; +use crate::JwkStorage; +use crate::KeyId; +use crate::KeyStorageResult; +use crate::KeyType; +use crate::ProofUpdateCtx; + +/// Extension to the JwkStorage to handle BBS+ keys +#[cfg_attr(not(feature = "send-sync-storage"), async_trait(?Send))] +#[cfg_attr(feature = "send-sync-storage", async_trait)] +pub trait JwkStorageBbsPlusExt: JwkStorage { + /// Generates a JWK representing a BBS+ signature + async fn generate_bbs(&self, key_type: KeyType, alg: ProofAlgorithm) -> KeyStorageResult; + + /// Sign the provided `data` and `header` using the private key identified by `key_id` according to the requirements + /// of the corresponding `public_key` (see [`Jwk::alg`](Jwk::alg()) etc.). + async fn sign_bbs( + &self, + key_id: &KeyId, + data: &[Vec], + header: &[u8], + public_key: &Jwk, + ) -> KeyStorageResult>; + + /// Update proof functionality for timeframe revocation mechanism + async fn update_signature( + &self, + key_id: &KeyId, + public_key: &Jwk, + signature: &[u8], + ctx: ProofUpdateCtx, + ) -> KeyStorageResult>; +} diff --git a/identity_storage/src/key_storage/key_storage_error.rs b/identity_storage/src/key_storage/key_storage_error.rs index 59ff10968e..060d0794a6 100644 --- a/identity_storage/src/key_storage/key_storage_error.rs +++ b/identity_storage/src/key_storage/key_storage_error.rs @@ -23,6 +23,9 @@ pub enum KeyStorageErrorKind { /// Indicates an attempt to parse a signature algorithm that is not recognized by the key storage implementation. UnsupportedSignatureAlgorithm, + /// Indicates an attempt to parse a proof algorithm that is not recognized by the key storage implementation. + UnsupportedProofAlgorithm, + /// Indicates that the key storage implementation is not able to find the requested key. KeyNotFound, @@ -59,6 +62,7 @@ impl KeyStorageErrorKind { Self::UnsupportedKeyType => "key generation failed: the provided multikey schema is not supported", Self::KeyAlgorithmMismatch => "the key type cannot be used with the algorithm", Self::UnsupportedSignatureAlgorithm => "signing algorithm parsing failed", + Self::UnsupportedProofAlgorithm => "proof algorithm parsing failed", Self::KeyNotFound => "key not found in storage", Self::Unavailable => "key storage unavailable", Self::Unauthenticated => "authentication with the key storage failed", diff --git a/identity_storage/src/key_storage/memstore.rs b/identity_storage/src/key_storage/memstore.rs index f101af4759..2f51be97ce 100644 --- a/identity_storage/src/key_storage/memstore.rs +++ b/identity_storage/src/key_storage/memstore.rs @@ -1,4 +1,4 @@ -// Copyright 2020-2023 IOTA Stiftung +// Copyright 2020-2023 IOTA Stiftung, Fondazione Links // SPDX-License-Identifier: Apache-2.0 use core::fmt::Debug; @@ -12,6 +12,7 @@ use identity_verification::jose::jwk::EdCurve; use identity_verification::jose::jwk::Jwk; use identity_verification::jose::jwk::JwkType; use identity_verification::jose::jws::JwsAlgorithm; +use identity_verification::jwk::BlsCurve; use rand::distributions::DistString; use shared::Shared; use tokio::sync::RwLockReadGuard; @@ -57,7 +58,7 @@ impl JwkStorage for JwkMemStore { async fn generate(&self, key_type: KeyType, alg: JwsAlgorithm) -> KeyStorageResult { let key_type: MemStoreKeyType = MemStoreKeyType::try_from(&key_type)?; - check_key_alg_compatibility(key_type, alg)?; + check_key_alg_compatibility(key_type, &alg)?; let (private_key, public_key) = match key_type { MemStoreKeyType::Ed25519 => { @@ -66,6 +67,12 @@ impl JwkStorage for JwkMemStore { let public_key = private_key.public_key(); (private_key, public_key) } + other => { + return Err( + KeyStorageError::new(KeyStorageErrorKind::UnsupportedKeyType) + .with_custom_message(format!("{other} is not supported")), + ); + } }; let kid: KeyId = random_key_id(); @@ -95,7 +102,7 @@ impl JwkStorage for JwkMemStore { Some(alg) => { let alg: JwsAlgorithm = JwsAlgorithm::from_str(alg) .map_err(|err| KeyStorageError::new(KeyStorageErrorKind::UnsupportedSignatureAlgorithm).with_source(err))?; - check_key_alg_compatibility(key_type, alg)?; + check_key_alg_compatibility(key_type, &alg)?; } None => { return Err( @@ -183,18 +190,24 @@ impl JwkStorage for JwkMemStore { #[derive(Debug, Copy, Clone)] enum MemStoreKeyType { Ed25519, + BLS12381G2, } impl JwkMemStore { const ED25519_KEY_TYPE_STR: &'static str = "Ed25519"; /// The Ed25519 key type. pub const ED25519_KEY_TYPE: KeyType = KeyType::from_static_str(Self::ED25519_KEY_TYPE_STR); + + const BLS12381G2_KEY_TYPE_STR: &'static str = "BLS12381G2"; + /// The BLS12381G2 key type + pub const BLS12381G2_KEY_TYPE: KeyType = KeyType::from_static_str(Self::BLS12381G2_KEY_TYPE_STR); } impl MemStoreKeyType { const fn name(&self) -> &'static str { match self { - MemStoreKeyType::Ed25519 => "Ed25519", + MemStoreKeyType::Ed25519 => JwkMemStore::ED25519_KEY_TYPE_STR, + MemStoreKeyType::BLS12381G2 => JwkMemStore::BLS12381G2_KEY_TYPE_STR, } } } @@ -211,6 +224,7 @@ impl TryFrom<&KeyType> for MemStoreKeyType { fn try_from(value: &KeyType) -> Result { match value.as_str() { JwkMemStore::ED25519_KEY_TYPE_STR => Ok(MemStoreKeyType::Ed25519), + JwkMemStore::BLS12381G2_KEY_TYPE_STR => Ok(MemStoreKeyType::BLS12381G2), _ => Err(KeyStorageError::new(KeyStorageErrorKind::UnsupportedKeyType)), } } @@ -239,6 +253,24 @@ impl TryFrom<&Jwk> for MemStoreKeyType { ), } } + JwkType::Ec => { + let ec_params = jwk.try_ec_params().map_err(|err| { + KeyStorageError::new(KeyStorageErrorKind::UnsupportedKeyType) + .with_custom_message("expected EC parameters for a JWK with `kty` Ec") + .with_source(err) + })?; + match ec_params.try_bls_curve().map_err(|err| { + KeyStorageError::new(KeyStorageErrorKind::UnsupportedKeyType) + .with_custom_message("only Ed curves are supported for signing") + .with_source(err) + })? { + BlsCurve::BLS12381G2 => Ok(MemStoreKeyType::BLS12381G2), + curve => Err( + KeyStorageError::new(KeyStorageErrorKind::UnsupportedKeyType) + .with_custom_message(format!("{curve} not supported")), + ), + } + } other => Err( KeyStorageError::new(KeyStorageErrorKind::UnsupportedKeyType) .with_custom_message(format!("Jwk `kty` {other} not supported")), @@ -259,7 +291,7 @@ fn random_key_id() -> KeyId { } /// Check that the key type can be used with the algorithm. -fn check_key_alg_compatibility(key_type: MemStoreKeyType, alg: JwsAlgorithm) -> KeyStorageResult<()> { +fn check_key_alg_compatibility(key_type: MemStoreKeyType, alg: &JwsAlgorithm) -> KeyStorageResult<()> { match (key_type, alg) { (MemStoreKeyType::Ed25519, JwsAlgorithm::EdDSA) => Ok(()), (key_type, alg) => Err( @@ -269,6 +301,125 @@ fn check_key_alg_compatibility(key_type: MemStoreKeyType, alg: JwsAlgorithm) -> } } +#[cfg(feature = "jpt-bbs-plus")] +mod bbs_plus_impl { + use std::str::FromStr as _; + + use crate::key_storage::bls::encode_bls_jwk; + use crate::key_storage::bls::expand_bls_jwk; + use crate::key_storage::bls::generate_bbs_keypair; + use crate::key_storage::bls::sign_bbs; + use crate::key_storage::bls::update_bbs_signature; + use crate::JwkGenOutput; + use crate::JwkMemStore; + use crate::JwkStorageBbsPlusExt; + use crate::KeyId; + use crate::KeyStorageError; + use crate::KeyStorageErrorKind; + use crate::KeyStorageResult; + use crate::KeyType; + use crate::ProofUpdateCtx; + use async_trait::async_trait; + use identity_verification::jwk::BlsCurve; + use identity_verification::jwk::Jwk; + use jsonprooftoken::jpa::algs::ProofAlgorithm; + + use super::random_key_id; + + /// JwkStorageBbsPlusExt implementation for JwkMemStore + #[cfg_attr(not(feature = "send-sync-storage"), async_trait(?Send))] + #[cfg_attr(feature = "send-sync-storage", async_trait)] + impl JwkStorageBbsPlusExt for JwkMemStore { + async fn generate_bbs(&self, key_type: KeyType, alg: ProofAlgorithm) -> KeyStorageResult { + if key_type != JwkMemStore::BLS12381G2_KEY_TYPE { + return Err( + KeyStorageError::new(KeyStorageErrorKind::UnsupportedKeyType) + .with_custom_message(format!("unsupported key type {key_type}")), + ); + } + + let (private_key, public_key) = generate_bbs_keypair(alg)?; + let (jwk, public_jwk) = encode_bls_jwk(&private_key, &public_key, alg); + + let kid: KeyId = random_key_id(); + let mut jwk_store = self.jwk_store.write().await; + jwk_store.insert(kid.clone(), jwk); + + Ok(JwkGenOutput::new(kid, public_jwk)) + } + + async fn sign_bbs( + &self, + key_id: &KeyId, + data: &[Vec], + header: &[u8], + public_key: &Jwk, + ) -> KeyStorageResult> { + let jwk_store = self.jwk_store.read().await; + + // Extract the required alg from the given public key + let alg = public_key + .alg() + .and_then(|alg_str| ProofAlgorithm::from_str(alg_str).ok()) + .ok_or(KeyStorageErrorKind::UnsupportedProofAlgorithm)?; + + // Check the provided JWK represents a BLS12381G2 key. + if !public_key + .try_ec_params() + .map(|ec| ec.crv == BlsCurve::BLS12381G2.to_string()) + .unwrap_or(false) + { + return Err( + KeyStorageError::new(KeyStorageErrorKind::UnsupportedKeyType) + .with_custom_message(format!("expected a key from the {} curve", BlsCurve::BLS12381G2)), + ); + } + + // Obtain the corresponding private key. + let jwk: &Jwk = jwk_store.get(key_id).ok_or(KeyStorageErrorKind::KeyNotFound)?; + let (sk, pk) = expand_bls_jwk(jwk)?; + + sign_bbs(alg, data, &sk.expect("jwk is private"), &pk, header) + } + + async fn update_signature( + &self, + key_id: &KeyId, + public_key: &Jwk, + signature: &[u8], + ctx: ProofUpdateCtx, + ) -> KeyStorageResult> { + let jwk_store = self.jwk_store.read().await; + + // Extract the required alg from the given public key + let alg = public_key + .alg() + .ok_or(KeyStorageErrorKind::UnsupportedProofAlgorithm) + .and_then(|alg_str| { + ProofAlgorithm::from_str(alg_str).map_err(|_| KeyStorageErrorKind::UnsupportedProofAlgorithm) + })?; + + // Check the provided JWK represents a BLS12381G2 key. + if !public_key + .try_ec_params() + .map(|ec| ec.crv == BlsCurve::BLS12381G2.to_string()) + .unwrap_or(false) + { + return Err( + KeyStorageError::new(KeyStorageErrorKind::UnsupportedKeyType) + .with_custom_message(format!("expected a key from the {} curve", BlsCurve::BLS12381G2)), + ); + } + + // Obtain the corresponding private key. + let jwk = jwk_store.get(key_id).ok_or(KeyStorageErrorKind::KeyNotFound)?; + let sk = expand_bls_jwk(jwk)?.0.expect("jwk is private"); + + // Update the signature. + update_bbs_signature(alg, signature, &sk, &ctx) + } + } +} pub(crate) mod shared { use core::fmt::Debug; use core::fmt::Formatter; @@ -362,10 +513,10 @@ mod tests { let store: JwkMemStore = JwkMemStore::new(); let mut ec_params = JwkParamsEc::new(); - ec_params.crv = EcCurve::P256.name().to_owned(); - ec_params.x = "".to_owned(); - ec_params.y = "".to_owned(); - ec_params.d = Some("".to_owned()); + ec_params.crv = EcCurve::P256.name().to_string(); + ec_params.x = String::new(); + ec_params.y = String::new(); + ec_params.d = Some(String::new()); let jwk_ec = Jwk::from_params(ec_params); let err = store.insert(jwk_ec).await.unwrap_err(); diff --git a/identity_storage/src/key_storage/mod.rs b/identity_storage/src/key_storage/mod.rs index 70006b0537..f54f9d5233 100644 --- a/identity_storage/src/key_storage/mod.rs +++ b/identity_storage/src/key_storage/mod.rs @@ -6,10 +6,15 @@ //! This module provides the [`JwkStorage`] trait that //! abstracts over storages that store JSON Web Keys. +#[cfg(feature = "jpt-bbs-plus")] +/// BLS12381 utils. +pub mod bls; #[cfg(feature = "memstore")] mod ed25519; mod jwk_gen_output; mod jwk_storage; +#[cfg(feature = "jpt-bbs-plus")] +mod jwk_storage_bbs_plus_ext; mod key_id; mod key_storage_error; mod key_type; @@ -19,10 +24,17 @@ mod memstore; #[cfg(test)] pub(crate) mod tests; -pub use jwk_gen_output::*; -pub use jwk_storage::*; -pub use key_id::*; -pub use key_storage_error::*; -pub use key_type::*; -#[cfg(feature = "memstore")] -pub use memstore::*; +/// All modules that should be made available to end-users. +pub mod public_modules { + pub use super::jwk_gen_output::*; + pub use super::jwk_storage::*; + #[cfg(feature = "jpt-bbs-plus")] + pub use super::jwk_storage_bbs_plus_ext::*; + pub use super::key_id::*; + pub use super::key_storage_error::*; + pub use super::key_type::*; + #[cfg(feature = "memstore")] + pub use super::memstore::*; +} + +pub use public_modules::*; diff --git a/identity_storage/src/key_storage/tests/utils.rs b/identity_storage/src/key_storage/tests/utils.rs index 379df562b4..b5ca210301 100644 --- a/identity_storage/src/key_storage/tests/utils.rs +++ b/identity_storage/src/key_storage/tests/utils.rs @@ -45,10 +45,10 @@ pub(crate) async fn test_incompatible_key_alg(store: impl JwkStorage) { pub(crate) async fn test_incompatible_key_type(store: impl JwkStorage) { let mut ec_params = JwkParamsEc::new(); - ec_params.crv = EcCurve::P256.name().to_owned(); - ec_params.x = "".to_owned(); - ec_params.y = "".to_owned(); - ec_params.d = Some("".to_owned()); + ec_params.crv = EcCurve::P256.name().to_string(); + ec_params.x = String::new(); + ec_params.y = String::new(); + ec_params.d = Some(String::new()); let jwk_ec = Jwk::from_params(ec_params); let err = store.insert(jwk_ec).await.unwrap_err(); diff --git a/identity_storage/src/lib.rs b/identity_storage/src/lib.rs index da1b0b66f4..643e1e7444 100644 --- a/identity_storage/src/lib.rs +++ b/identity_storage/src/lib.rs @@ -19,5 +19,5 @@ pub mod key_storage; pub mod storage; pub use key_id_storage::*; -pub use key_storage::*; +pub use key_storage::public_modules::*; pub use storage::*; diff --git a/identity_storage/src/storage/error.rs b/identity_storage/src/storage/error.rs index 05df9bc1ad..51af20ead0 100644 --- a/identity_storage/src/storage/error.rs +++ b/identity_storage/src/storage/error.rs @@ -27,6 +27,16 @@ pub enum JwkStorageDocumentError { /// Caused by an invalid JWS algorithm. #[error("invalid JWS algorithm")] InvalidJwsAlgorithm, + /// Caused by an invalid JWP algorithm. + #[error("invalid JWP algorithm")] + InvalidJwpAlgorithm, + /// Cannot cunstruct a valid Jwp (issued or presented form) + #[error("Not able to construct a valid Jwp")] + JwpBuildingError, + /// Credential's proof update internal error + #[error("Credential's proof internal error")] + ProofUpdateError(String), + /// Caused by a failure to construct a verification method. #[error("method generation failed: unable to create a valid verification method")] VerificationMethodConstructionError(#[source] identity_verification::Error), diff --git a/identity_storage/src/storage/jwk_document_ext.rs b/identity_storage/src/storage/jwk_document_ext.rs index 8b412a285a..f9ee100986 100644 --- a/identity_storage/src/storage/jwk_document_ext.rs +++ b/identity_storage/src/storage/jwk_document_ext.rs @@ -153,20 +153,20 @@ mod private { // copious amounts of repetition. // NOTE: If such use of macros becomes very common it is probably better to use the duplicate crate: https://docs.rs/duplicate/latest/duplicate/ macro_rules! generate_method_for_document_type { - ($t:ty, $name:ident) => { + ($t:ty, $a:ty, $k:path, $f:path, $name:ident) => { async fn $name( document: &mut $t, storage: &Storage, key_type: KeyType, - alg: JwsAlgorithm, + alg: $a, fragment: Option<&str>, scope: MethodScope, ) -> StorageResult where - K: JwkStorage, + K: $k, I: KeyIdStorage, { - let JwkGenOutput { key_id, jwk } = ::generate(&storage.key_storage(), key_type, alg) + let JwkGenOutput { key_id, jwk } = $f(storage.key_storage(), key_type, alg) .await .map_err(Error::KeyStorageError)?; @@ -304,7 +304,13 @@ macro_rules! purge_method_for_document_type { // CoreDocument // ==================================================================================================================== -generate_method_for_document_type!(CoreDocument, generate_method_core_document); +generate_method_for_document_type!( + CoreDocument, + JwsAlgorithm, + JwkStorage, + JwkStorage::generate, + generate_method_core_document +); purge_method_for_document_type!(CoreDocument, purge_method_core_document); #[cfg_attr(not(feature = "send-sync-storage"), async_trait(?Send))] @@ -505,7 +511,7 @@ impl JwkDocumentExt for CoreDocument { /// Attempt to revert key generation. If this succeeds the original `source_error` is returned, /// otherwise [`JwkStorageDocumentError::UndoOperationFailed`] is returned with the `source_error` attached as /// `source`. -async fn try_undo_key_generation(storage: &Storage, key_id: &KeyId, source_error: Error) -> Error +pub(crate) async fn try_undo_key_generation(storage: &Storage, key_id: &KeyId, source_error: Error) -> Error where K: JwkStorage, I: KeyIdStorage, @@ -531,7 +537,13 @@ mod iota_document { use identity_credential::credential::Jwt; use identity_iota_core::IotaDocument; - generate_method_for_document_type!(IotaDocument, generate_method_iota_document); + generate_method_for_document_type!( + IotaDocument, + JwsAlgorithm, + JwkStorage, + JwkStorage::generate, + generate_method_iota_document + ); purge_method_for_document_type!(IotaDocument, purge_method_iota_document); #[cfg_attr(not(feature = "send-sync-storage"), async_trait(?Send))] diff --git a/identity_storage/src/storage/jwp_document_ext.rs b/identity_storage/src/storage/jwp_document_ext.rs new file mode 100644 index 0000000000..21ef7fafaa --- /dev/null +++ b/identity_storage/src/storage/jwp_document_ext.rs @@ -0,0 +1,362 @@ +// Copyright 2020-2024 IOTA Stiftung, Fondazione Links +// SPDX-License-Identifier: Apache-2.0 + +use super::JwkStorageDocumentError as Error; +use crate::key_id_storage::MethodDigest; +use crate::try_undo_key_generation; +use crate::JwkGenOutput; +use crate::JwkStorageBbsPlusExt; +use crate::KeyIdStorage; +use crate::KeyType; +use crate::Storage; +use crate::StorageResult; +use async_trait::async_trait; +use identity_core::common::Object; +use identity_core::convert::ToJson; +use identity_credential::credential::Credential; +use identity_credential::credential::Jpt; +use identity_credential::credential::JwpCredentialOptions; +use identity_credential::presentation::JwpPresentationOptions; +use identity_credential::presentation::SelectiveDisclosurePresentation; +use identity_did::DIDUrl; +use identity_document::document::CoreDocument; +use identity_verification::MethodData; +use identity_verification::MethodScope; +use identity_verification::VerificationMethod; +use jsonprooftoken::encoding::SerializationType; +use jsonprooftoken::jpa::algs::ProofAlgorithm; +use jsonprooftoken::jpt::claims::JptClaims; +use jsonprooftoken::jwk::key::Jwk; +use jsonprooftoken::jwp::header::IssuerProtectedHeader; +use jsonprooftoken::jwp::header::PresentationProtectedHeader; +use jsonprooftoken::jwp::issued::JwpIssuedBuilder; +use serde::de::DeserializeOwned; +use serde::Serialize; + +/// Handle JWP-based operations on DID Documents. +#[cfg_attr(not(feature = "send-sync-storage"), async_trait(?Send))] +#[cfg_attr(feature = "send-sync-storage", async_trait)] +pub trait JwpDocumentExt { + /// Generate new key material in the given `storage` and insert a new verification method with the corresponding + /// public key material into the DID document. This supports BBS+ keys. + async fn generate_method_jwp( + &mut self, + storage: &Storage, + key_type: KeyType, + alg: ProofAlgorithm, + fragment: Option<&str>, + scope: MethodScope, + ) -> StorageResult + where + K: JwkStorageBbsPlusExt, + I: KeyIdStorage; + + /// Compute a JWP in the Issued form representing the Verifiable Credential + /// See [JSON Web Proof draft](https://datatracker.ietf.org/doc/html/draft-ietf-jose-json-web-proof#name-issued-form) + async fn create_issued_jwp( + &self, + storage: &Storage, + fragment: &str, + jpt_claims: &JptClaims, + options: &JwpCredentialOptions, + ) -> StorageResult + where + K: JwkStorageBbsPlusExt, + I: KeyIdStorage; + + /// Compute a JWP in the Presented form representing the presented Verifiable Credential after the Selective + /// Disclosure of attributes See [JSON Web Proof draft](https://datatracker.ietf.org/doc/html/draft-ietf-jose-json-web-proof#name-presented-form) + async fn create_presented_jwp( + &self, + presentation: &mut SelectiveDisclosurePresentation, + method_id: &str, + options: &JwpPresentationOptions, + ) -> StorageResult; + + /// Produces a JPT where the payload is produced from the given `credential`. + async fn create_credential_jpt( + &self, + credential: &Credential, + storage: &Storage, + fragment: &str, + options: &JwpCredentialOptions, + custom_claims: Option, + ) -> StorageResult + where + K: JwkStorageBbsPlusExt, + I: KeyIdStorage, + T: ToOwned + Serialize + DeserializeOwned + Sync; + + /// Produces a JPT where the payload contains the Selective Disclosed attributes of a `credential`. + async fn create_presentation_jpt( + &self, + presentation: &mut SelectiveDisclosurePresentation, + method_id: &str, + options: &JwpPresentationOptions, + ) -> StorageResult; +} + +// ==================================================================================================================== +// CoreDocument +// ==================================================================================================================== + +generate_method_for_document_type!( + CoreDocument, + ProofAlgorithm, + JwkStorageBbsPlusExt, + JwkStorageBbsPlusExt::generate_bbs, + generate_method_core_document +); + +#[cfg_attr(not(feature = "send-sync-storage"), async_trait(?Send))] +#[cfg_attr(feature = "send-sync-storage", async_trait)] +impl JwpDocumentExt for CoreDocument { + async fn generate_method_jwp( + &mut self, + storage: &Storage, + key_type: KeyType, + alg: ProofAlgorithm, + fragment: Option<&str>, + scope: MethodScope, + ) -> StorageResult + where + K: JwkStorageBbsPlusExt, + I: KeyIdStorage, + { + generate_method_core_document(self, storage, key_type, alg, fragment, scope).await + } + + async fn create_issued_jwp( + &self, + storage: &Storage, + fragment: &str, + jpt_claims: &JptClaims, + options: &JwpCredentialOptions, + ) -> StorageResult + where + K: JwkStorageBbsPlusExt, + I: KeyIdStorage, + { + // Obtain the method corresponding to the given fragment. + let method: &VerificationMethod = self.resolve_method(fragment, None).ok_or(Error::MethodNotFound)?; + let MethodData::PublicKeyJwk(ref jwk) = method.data() else { + return Err(Error::NotPublicKeyJwk); + }; + + // Extract JwsAlgorithm. + let alg: ProofAlgorithm = jwk + .alg() + .unwrap_or("") + .parse() + .map_err(|_| Error::InvalidJwpAlgorithm)?; + + let typ = "JPT".to_string(); + + let kid = if let Some(ref kid) = options.kid { + kid.clone() + } else { + method.id().to_string() + }; + + let mut issuer_header = IssuerProtectedHeader::new(alg); + issuer_header.set_typ(Some(typ)); + issuer_header.set_kid(Some(kid)); + + // Get the key identifier corresponding to the given method from the KeyId storage. + let method_digest: MethodDigest = MethodDigest::new(method).map_err(Error::MethodDigestConstructionError)?; + let key_id = ::get_key_id(storage.key_id_storage(), &method_digest) + .await + .map_err(Error::KeyIdStorageError)?; + + let jwp_builder = JwpIssuedBuilder::new(issuer_header, jpt_claims.clone()); + + let header = jwp_builder.get_issuer_protected_header().map_or_else( + || Err(Error::JwpBuildingError), + |h| h.to_json_vec().map_err(|_| Error::JwpBuildingError), + )?; + + let data = jwp_builder.get_payloads().map_or_else( + || Err(Error::JwpBuildingError), + |p| p.to_bytes().map_err(|_| Error::JwpBuildingError), + )?; + + let signature = ::sign_bbs(storage.key_storage(), &key_id, &data, &header, jwk) + .await + .map_err(Error::KeyStorageError)?; + + jwp_builder + .build_with_proof(signature) + .map_err(|_| Error::JwpBuildingError)? + .encode(SerializationType::COMPACT) + .map_err(|err| Error::EncodingError(Box::new(err))) + } + + async fn create_presented_jwp( + &self, + presentation: &mut SelectiveDisclosurePresentation, + method_id: &str, + options: &JwpPresentationOptions, + ) -> StorageResult { + // Obtain the method corresponding to the given fragment. + let method: &VerificationMethod = self.resolve_method(method_id, None).ok_or(Error::MethodNotFound)?; + let MethodData::PublicKeyJwk(ref jwk) = method.data() else { + return Err(Error::NotPublicKeyJwk); + }; + + // Extract JwsAlgorithm. + let alg: ProofAlgorithm = jwk + .alg() + .unwrap_or("") + .parse() + .map_err(|_| Error::InvalidJwpAlgorithm)?; + + let public_key: Jwk = jwk.try_into().map_err(|_| Error::NotPublicKeyJwk)?; + + let mut presentation_header = PresentationProtectedHeader::new(alg.into()); + presentation_header.set_nonce(options.nonce.clone()); + presentation_header.set_aud(options.audience.as_ref().map(|u| u.to_string())); + + presentation.set_presentation_header(presentation_header); + + let jwp_builder = presentation.builder(); + + let presented_jwp = jwp_builder.build(&public_key).map_err(|_| Error::JwpBuildingError)?; + + Ok( + presented_jwp + .encode(SerializationType::COMPACT) + .map_err(|e| Error::EncodingError(Box::new(e)))?, + ) + } + + async fn create_credential_jpt( + &self, + credential: &Credential, + storage: &Storage, + fragment: &str, + options: &JwpCredentialOptions, + custom_claims: Option, + ) -> StorageResult + where + K: JwkStorageBbsPlusExt, + I: KeyIdStorage, + T: ToOwned + Serialize + DeserializeOwned + Sync, + { + let jpt_claims = credential + .serialize_jpt(custom_claims) + .map_err(Error::ClaimsSerializationError)?; + + self + .create_issued_jwp(storage, fragment, &jpt_claims, options) + .await + .map(Jpt::new) + } + + async fn create_presentation_jpt( + &self, + presentation: &mut SelectiveDisclosurePresentation, + method_id: &str, + options: &JwpPresentationOptions, + ) -> StorageResult { + self + .create_presented_jwp(presentation, method_id, options) + .await + .map(Jpt::new) + } +} + +// ==================================================================================================================== +// IotaDocument +// ==================================================================================================================== +#[cfg(feature = "iota-document")] +mod iota_document { + use super::*; + use identity_iota_core::IotaDocument; + + generate_method_for_document_type!( + IotaDocument, + ProofAlgorithm, + JwkStorageBbsPlusExt, + JwkStorageBbsPlusExt::generate_bbs, + generate_method_iota_document + ); + + #[cfg_attr(not(feature = "send-sync-storage"), async_trait(?Send))] + #[cfg_attr(feature = "send-sync-storage", async_trait)] + impl JwpDocumentExt for IotaDocument { + async fn generate_method_jwp( + &mut self, + storage: &Storage, + key_type: KeyType, + alg: ProofAlgorithm, + fragment: Option<&str>, + scope: MethodScope, + ) -> StorageResult + where + K: JwkStorageBbsPlusExt, + I: KeyIdStorage, + { + generate_method_iota_document(self, storage, key_type, alg, fragment, scope).await + } + + async fn create_issued_jwp( + &self, + storage: &Storage, + fragment: &str, + jpt_claims: &JptClaims, + options: &JwpCredentialOptions, + ) -> StorageResult + where + K: JwkStorageBbsPlusExt, + I: KeyIdStorage, + { + self + .core_document() + .create_issued_jwp(storage, fragment, jpt_claims, options) + .await + } + + async fn create_presented_jwp( + &self, + presentation: &mut SelectiveDisclosurePresentation, + method_id: &str, + options: &JwpPresentationOptions, + ) -> StorageResult { + self + .core_document() + .create_presented_jwp(presentation, method_id, options) + .await + } + + async fn create_credential_jpt( + &self, + credential: &Credential, + storage: &Storage, + fragment: &str, + options: &JwpCredentialOptions, + custom_claims: Option, + ) -> StorageResult + where + K: JwkStorageBbsPlusExt, + I: KeyIdStorage, + T: ToOwned + Serialize + DeserializeOwned + Sync, + { + self + .core_document() + .create_credential_jpt(credential, storage, fragment, options, custom_claims) + .await + } + + async fn create_presentation_jpt( + &self, + presentation: &mut SelectiveDisclosurePresentation, + method_id: &str, + options: &JwpPresentationOptions, + ) -> StorageResult { + self + .core_document() + .create_presentation_jpt(presentation, method_id, options) + .await + } + } +} diff --git a/identity_storage/src/storage/mod.rs b/identity_storage/src/storage/mod.rs index c36cea1cee..a1fff2c089 100644 --- a/identity_storage/src/storage/mod.rs +++ b/identity_storage/src/storage/mod.rs @@ -4,18 +4,29 @@ //! This module provides a type wrapping a key and key id storage. mod error; +#[macro_use] mod jwk_document_ext; +#[cfg(feature = "jpt-bbs-plus")] +mod jwp_document_ext; mod signature_options; +#[cfg(feature = "jpt-bbs-plus")] +mod timeframe_revocation_ext; + #[cfg(feature = "storage-signer")] mod storage_signer; #[cfg(all(test, feature = "memstore"))] pub(crate) mod tests; pub use error::*; + pub use jwk_document_ext::*; +#[cfg(feature = "jpt-bbs-plus")] +pub use jwp_document_ext::*; pub use signature_options::*; #[cfg(feature = "storage-signer")] pub use storage_signer::*; +#[cfg(feature = "jpt-bbs-plus")] +pub use timeframe_revocation_ext::*; /// A type wrapping a key and key id storage, typically used with [`JwkStorage`](crate::key_storage::JwkStorage) and /// [`KeyIdStorage`](crate::key_id_storage::KeyIdStorage) that should always be used together when calling methods from diff --git a/identity_storage/src/storage/tests/test_utils.rs b/identity_storage/src/storage/tests/test_utils.rs index ebc0660147..77b1a92072 100644 --- a/identity_storage/src/storage/tests/test_utils.rs +++ b/identity_storage/src/storage/tests/test_utils.rs @@ -192,7 +192,7 @@ pub(crate) fn encode_public_ed25519_jwk(public_key: &PublicKey) -> Jwk { let mut params = JwkParamsOkp::new(); params.x = x; params.d = None; - params.crv = EdCurve::Ed25519.name().to_owned(); + params.crv = EdCurve::Ed25519.name().to_string(); let mut jwk = Jwk::from_params(params); jwk.set_alg(JwsAlgorithm::EdDSA.name()); jwk diff --git a/identity_storage/src/storage/timeframe_revocation_ext.rs b/identity_storage/src/storage/timeframe_revocation_ext.rs new file mode 100644 index 0000000000..f53f2a9639 --- /dev/null +++ b/identity_storage/src/storage/timeframe_revocation_ext.rs @@ -0,0 +1,198 @@ +// Copyright 2020-2024 IOTA Stiftung, Fondazione Links +// SPDX-License-Identifier: Apache-2.0 + +use super::JwkStorageDocumentError as Error; +use crate::JwkStorageBbsPlusExt; +use crate::KeyIdStorage; +use crate::MethodDigest; +use crate::Storage; +use crate::StorageResult; +use async_trait::async_trait; +use identity_core::common::Duration; +use identity_core::common::Timestamp; +use identity_credential::credential::Jpt; +use identity_credential::revocation::RevocationTimeframeStatus; +use identity_document::document::CoreDocument; +use identity_verification::MethodData; +use identity_verification::VerificationMethod; +use jsonprooftoken::encoding::SerializationType; +use jsonprooftoken::jpt::payloads::Payloads; +use jsonprooftoken::jwp::issued::JwpIssued; +use serde_json::Value; +use zkryptium::bbsplus::signature::BBSplusSignature; + +/// Contains information needed to update the signature in the RevocationTimeframe2024 revocation mechanism. +pub struct ProofUpdateCtx { + /// Old `startValidityTimeframe` value + pub old_start_validity_timeframe: Vec, + /// New `startValidityTimeframe` value to be signed + pub new_start_validity_timeframe: Vec, + /// Old `endValidityTimeframe` value + pub old_end_validity_timeframe: Vec, + /// New `endValidityTimeframe` value to be signed + pub new_end_validity_timeframe: Vec, + /// Index of `startValidityTimeframe` claim inside the array of Claims + pub index_start_validity_timeframe: usize, + /// Index of `endValidityTimeframe` claim inside the array of Claims + pub index_end_validity_timeframe: usize, + /// Number of signed messages, number of payloads in a JWP + pub number_of_signed_messages: usize, +} + +/// CoreDocument and IotaDocument extension to handle Credential' signature update for RevocationTimeframe2024 +#[cfg_attr(not(feature = "send-sync-storage"), async_trait(?Send))] +#[cfg_attr(feature = "send-sync-storage", async_trait)] +pub trait TimeframeRevocationExtension { + /// Update Credential' signature considering the Timeframe interval + async fn update( + &self, + storage: &Storage, + fragment: &str, + start_validity: Option, + duration: Duration, + credential_jwp: &mut JwpIssued, + ) -> StorageResult + where + K: JwkStorageBbsPlusExt, + I: KeyIdStorage; +} + +// ==================================================================================================================== +// CoreDocument +// ==================================================================================================================== + +#[cfg_attr(not(feature = "send-sync-storage"), async_trait(?Send))] +#[cfg_attr(feature = "send-sync-storage", async_trait)] +impl TimeframeRevocationExtension for CoreDocument { + async fn update( + &self, + storage: &Storage, + fragment: &str, + start_validity: Option, + duration: Duration, + credential_jwp: &mut JwpIssued, + ) -> StorageResult + where + K: JwkStorageBbsPlusExt, + I: KeyIdStorage, + { + // Obtain the method corresponding to the given fragment. + let method: &VerificationMethod = self.resolve_method(fragment, None).ok_or(Error::MethodNotFound)?; + let MethodData::PublicKeyJwk(ref jwk) = method.data() else { + return Err(Error::NotPublicKeyJwk); + }; + + // Get the key identifier corresponding to the given method from the KeyId storage. + let method_digest: MethodDigest = MethodDigest::new(method).map_err(Error::MethodDigestConstructionError)?; + let key_id = ::get_key_id(storage.key_id_storage(), &method_digest) + .await + .map_err(Error::KeyIdStorageError)?; + + let new_start_validity_timeframe = start_validity.unwrap_or(Timestamp::now_utc()); + let new_end_validity_timeframe = new_start_validity_timeframe + .checked_add(duration) + .ok_or(Error::ProofUpdateError("Invalid granularity".to_owned()))?; + let new_start_validity_timeframe = new_start_validity_timeframe.to_rfc3339(); + let new_end_validity_timeframe = new_end_validity_timeframe.to_rfc3339(); + + let proof = credential_jwp.get_proof(); + let claims = credential_jwp + .get_claims() + .ok_or(Error::ProofUpdateError("Should not happen".to_owned()))?; + let mut payloads: Payloads = credential_jwp.get_payloads().clone(); + + let index_start_validity_timeframe = claims + .get_claim_index(format!( + "vc.credentialStatus.{}", + RevocationTimeframeStatus::START_TIMEFRAME_PROPERTY + )) + .ok_or(Error::ProofUpdateError( + "'startValidityTimeframe' property NOT found".to_owned(), + ))?; + let index_end_validity_timeframe = claims + .get_claim_index(format!( + "vc.credentialStatus.{}", + RevocationTimeframeStatus::END_TIMEFRAME_PROPERTY + )) + .ok_or(Error::ProofUpdateError( + "'endValidityTimeframe' property NOT found".to_owned(), + ))?; + + let old_start_validity_timeframe = payloads + .replace_payload_at_index( + index_start_validity_timeframe, + Value::String(new_start_validity_timeframe.clone()), + ) + .map(serde_json::from_value::) + .map_err(|_| Error::ProofUpdateError("'startValidityTimeframe' value NOT found".to_owned()))? + .map_err(|_| Error::ProofUpdateError("'startValidityTimeframe' value NOT a JSON String".to_owned()))?; + + let old_end_validity_timeframe = payloads + .replace_payload_at_index( + index_end_validity_timeframe, + Value::String(new_end_validity_timeframe.clone()), + ) + .map(serde_json::from_value::) + .map_err(|_| Error::ProofUpdateError("'endValidityTimeframe' value NOT found".to_owned()))? + .map_err(|_| Error::ProofUpdateError("'endValidityTimeframe' value NOT a JSON String".to_owned()))?; + + let proof: [u8; BBSplusSignature::BYTES] = proof + .try_into() + .map_err(|_| Error::ProofUpdateError("Invalid bytes length of JWP proof".to_owned()))?; + + let proof_update_ctx = ProofUpdateCtx { + old_start_validity_timeframe: serde_json::to_vec(&old_start_validity_timeframe).unwrap(), + new_start_validity_timeframe: serde_json::to_vec(&new_start_validity_timeframe).unwrap(), + old_end_validity_timeframe: serde_json::to_vec(&old_end_validity_timeframe).unwrap(), + new_end_validity_timeframe: serde_json::to_vec(&new_end_validity_timeframe).unwrap(), + index_start_validity_timeframe, + index_end_validity_timeframe, + number_of_signed_messages: payloads.0.len(), + }; + + let new_proof = + ::update_signature(storage.key_storage(), &key_id, jwk, &proof, proof_update_ctx) + .await + .map_err(Error::KeyStorageError)?; + + credential_jwp.set_proof(&new_proof); + credential_jwp.set_payloads(payloads); + + let jpt = credential_jwp + .encode(SerializationType::COMPACT) + .map_err(|e| Error::EncodingError(Box::new(e)))?; + + Ok(Jpt::new(jpt)) + } +} + +// ==================================================================================================================== +// IotaDocument +// ==================================================================================================================== +#[cfg(feature = "iota-document")] +mod iota_document { + use super::*; + use identity_iota_core::IotaDocument; + + #[cfg_attr(not(feature = "send-sync-storage"), async_trait(?Send))] + #[cfg_attr(feature = "send-sync-storage", async_trait)] + impl TimeframeRevocationExtension for IotaDocument { + async fn update( + &self, + storage: &Storage, + fragment: &str, + start_validity: Option, + duration: Duration, + credential_jwp: &mut JwpIssued, + ) -> StorageResult + where + K: JwkStorageBbsPlusExt, + I: KeyIdStorage, + { + self + .core_document() + .update(storage, fragment, start_validity, duration, credential_jwp) + .await + } + } +} diff --git a/identity_stronghold/Cargo.toml b/identity_stronghold/Cargo.toml index 346950e8f4..b7c61a998f 100644 --- a/identity_stronghold/Cargo.toml +++ b/identity_stronghold/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "identity_stronghold" -version = "1.2.0" +version = "1.4.0" authors.workspace = true edition.workspace = true homepage.workspace = true @@ -13,23 +13,37 @@ description = "Secure JWK storage with Stronghold for IOTA Identity" [dependencies] async-trait = { version = "0.1.64", default-features = false } -identity_storage = { version = "=1.2.0", path = "../identity_storage", default-features = false } -identity_verification = { version = "=1.2.0", path = "../identity_verification", default-features = false } -iota-crypto = { version = "0.23", default-features = false, features = ["ed25519"] } -iota-sdk = { version = "1.0.2", default-features = false, features = ["client", "stronghold"] } -iota_stronghold = { version = "2.0", default-features = false } +bls12_381_plus = { workspace = true, optional = true } +identity_storage = { version = "=1.4.0", path = "../identity_storage", default-features = false } +identity_verification = { version = "=1.4.0", path = "../identity_verification", default-features = false } +iota-crypto = { version = "0.23.2", default-features = false, features = ["ed25519"] } +iota-sdk = { version = "1.1.5", default-features = false, features = ["client", "stronghold"] } +iota_stronghold = { version = "2.1.0", default-features = false } +json-proof-token = { workspace = true, optional = true } rand = { version = "0.8.5", default-features = false, features = ["std", "std_rng"] } tokio = { version = "1.29.0", default-features = false, features = ["macros", "sync"] } zeroize = { version = "1.6.0", default-features = false } +zkryptium = { workspace = true, optional = true } [dev-dependencies] -identity_did = { version = "=1.2.0", path = "../identity_did", default-features = false } +anyhow = "1.0.82" +bls12_381_plus = { workspace = true } +identity_did = { version = "=1.4.0", path = "../identity_did", default-features = false } +identity_storage = { version = "=1.4.0", path = "../identity_storage", default-features = false, features = ["jpt-bbs-plus"] } +json-proof-token = { workspace = true } tokio = { version = "1.29.0", default-features = false, features = ["macros", "sync", "rt"] } +zkryptium = { workspace = true } [features] default = [] # Enables `Send` + `Sync` bounds for the trait implementations on `StrongholdStorage`. send-sync-storage = ["identity_storage/send-sync-storage"] +bbs-plus = [ + "identity_storage/jpt-bbs-plus", + "dep:zkryptium", + "dep:bls12_381_plus", + "dep:json-proof-token", +] [lints] workspace = true diff --git a/identity_stronghold/src/ed25519.rs b/identity_stronghold/src/ed25519.rs index 13c3135bb0..933983cdfc 100644 --- a/identity_stronghold/src/ed25519.rs +++ b/identity_stronghold/src/ed25519.rs @@ -53,6 +53,6 @@ pub(crate) fn encode_jwk(private_key: &SecretKey, public_key: &crypto::signature let mut params = JwkParamsOkp::new(); params.x = x; params.d = Some(d); - params.crv = EdCurve::Ed25519.name().to_owned(); + params.crv = EdCurve::Ed25519.name().to_string(); Jwk::from_params(params) } diff --git a/identity_stronghold/src/lib.rs b/identity_stronghold/src/lib.rs index decb2c4c00..ae8f8aef5b 100644 --- a/identity_stronghold/src/lib.rs +++ b/identity_stronghold/src/lib.rs @@ -2,9 +2,11 @@ // SPDX-License-Identifier: Apache-2.0 pub(crate) mod ed25519; -mod stronghold_jwk_storage; -mod stronghold_key_id; +mod storage; +pub(crate) mod stronghold_key_type; #[cfg(test)] mod tests; +pub(crate) mod utils; -pub use stronghold_jwk_storage::*; +pub use storage::*; +pub use stronghold_key_type::*; diff --git a/identity_stronghold/src/storage/mod.rs b/identity_stronghold/src/storage/mod.rs new file mode 100644 index 0000000000..cb02b9274b --- /dev/null +++ b/identity_stronghold/src/storage/mod.rs @@ -0,0 +1,163 @@ +// Copyright 2020-2024 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +mod stronghold_jwk_storage; +#[cfg(any(feature = "bbs-plus", test))] +mod stronghold_jwk_storage_bbs_plus_ext; +mod stronghold_key_id; + +use std::sync::Arc; + +#[cfg(feature = "bbs-plus")] +use identity_storage::key_storage::bls::encode_bls_jwk; +use identity_storage::KeyId; +use identity_storage::KeyStorageError; +use identity_storage::KeyStorageErrorKind; +use identity_storage::KeyStorageResult; +use identity_verification::jwk::EdCurve; +use identity_verification::jwk::Jwk; +use identity_verification::jwk::JwkParamsOkp; +use identity_verification::jws::JwsAlgorithm; +use identity_verification::jwu; +use iota_sdk::client::secret::stronghold::StrongholdSecretManager; +use iota_sdk::client::secret::SecretManager; +#[cfg(feature = "bbs-plus")] +use iota_stronghold::procedures::FatalProcedureError; +use iota_stronghold::procedures::KeyType as ProceduresKeyType; +#[cfg(feature = "bbs-plus")] +use iota_stronghold::procedures::Runner as _; +use iota_stronghold::procedures::StrongholdProcedure; +use iota_stronghold::Location; +use iota_stronghold::Stronghold; +#[cfg(feature = "bbs-plus")] +use jsonprooftoken::jpa::algs::ProofAlgorithm; +use tokio::sync::MutexGuard; +#[cfg(feature = "bbs-plus")] +use zeroize::Zeroizing; +#[cfg(feature = "bbs-plus")] +use zkryptium::bbsplus::keys::BBSplusSecretKey; + +use crate::stronghold_key_type::StrongholdKeyType; +use crate::utils::get_client; +use crate::utils::IDENTITY_VAULT_PATH; + +/// Wrapper around a [`StrongholdSecretManager`] that implements the [`KeyIdStorage`](crate::KeyIdStorage) +/// and [`JwkStorage`](crate::JwkStorage) interfaces. +#[derive(Clone, Debug)] +pub struct StrongholdStorage(Arc); + +impl StrongholdStorage { + /// Creates a new [`StrongholdStorage`]. + pub fn new(stronghold_secret_manager: StrongholdSecretManager) -> Self { + Self(Arc::new(SecretManager::Stronghold(stronghold_secret_manager))) + } + + /// Shared reference to the inner [`SecretManager`]. + pub fn as_secret_manager(&self) -> &SecretManager { + self.0.as_ref() + } + + /// Acquire lock of the inner [`Stronghold`]. + pub(crate) async fn get_stronghold(&self) -> MutexGuard<'_, Stronghold> { + match *self.0 { + SecretManager::Stronghold(ref stronghold) => stronghold.inner().await, + _ => unreachable!("secret manager can be only constructed from stronghold"), + } + } + + async fn get_ed25519_public_key(&self, key_id: &KeyId) -> KeyStorageResult { + let stronghold = self.get_stronghold().await; + let client = get_client(&stronghold)?; + + let location = Location::generic( + IDENTITY_VAULT_PATH.as_bytes().to_vec(), + key_id.to_string().as_bytes().to_vec(), + ); + + let public_key_procedure = iota_stronghold::procedures::PublicKey { + ty: ProceduresKeyType::Ed25519, + private_key: location, + }; + + let procedure_result = client + .execute_procedure(StrongholdProcedure::PublicKey(public_key_procedure)) + .map_err(|err| KeyStorageError::new(KeyStorageErrorKind::KeyNotFound).with_source(err))?; + + let public_key: Vec = procedure_result.into(); + + let mut params = JwkParamsOkp::new(); + params.x = jwu::encode_b64(public_key); + EdCurve::Ed25519.name().clone_into(&mut params.crv); + let mut jwk: Jwk = Jwk::from_params(params); + jwk.set_alg(JwsAlgorithm::EdDSA.name()); + jwk.set_kid(jwk.thumbprint_sha256_b64()); + + Ok(jwk) + } + + #[cfg(feature = "bbs-plus")] + async fn get_bls12381g2_public_key(&self, key_id: &KeyId) -> KeyStorageResult { + let stronghold = self.get_stronghold().await; + let client = get_client(&stronghold)?; + + let location = Location::generic( + IDENTITY_VAULT_PATH.as_bytes().to_vec(), + key_id.to_string().as_bytes().to_vec(), + ); + + client + .get_guards([location], |[sk]| { + let sk = BBSplusSecretKey::from_bytes(&sk.borrow()).map_err(|e| FatalProcedureError::from(e.to_string()))?; + let pk = sk.public_key(); + let public_jwk = encode_bls_jwk(&sk, &pk, ProofAlgorithm::BLS12381_SHA256).1; + + drop(Zeroizing::new(sk.to_bytes())); + Ok(public_jwk) + }) + .map_err(|e| KeyStorageError::new(KeyStorageErrorKind::KeyNotFound).with_source(e)) + } + + /// Attepts to retrieve the public key corresponding to the key of id `key_id`, + /// returning it as a `key_type` encoded public JWK. + pub async fn get_public_key_with_type(&self, key_id: &KeyId, key_type: StrongholdKeyType) -> KeyStorageResult { + match key_type { + StrongholdKeyType::Ed25519 => self.get_ed25519_public_key(key_id).await, + #[cfg(feature = "bbs-plus")] + StrongholdKeyType::Bls12381G2 => self.get_bls12381g2_public_key(key_id).await, + #[allow(unreachable_patterns)] + _ => Err(KeyStorageErrorKind::UnsupportedKeyType.into()), + } + } + + /// Retrieve the public key corresponding to `key_id`. + #[deprecated(since = "1.3.0", note = "use `get_public_key_with_type` instead")] + pub async fn get_public_key(&self, key_id: &KeyId) -> KeyStorageResult { + let stronghold = self.get_stronghold().await; + let client = get_client(&stronghold)?; + + let location = Location::generic( + IDENTITY_VAULT_PATH.as_bytes().to_vec(), + key_id.to_string().as_bytes().to_vec(), + ); + + let public_key_procedure = iota_stronghold::procedures::PublicKey { + ty: ProceduresKeyType::Ed25519, + private_key: location, + }; + + let procedure_result = client + .execute_procedure(StrongholdProcedure::PublicKey(public_key_procedure)) + .map_err(|err| KeyStorageError::new(KeyStorageErrorKind::KeyNotFound).with_source(err))?; + + let public_key: Vec = procedure_result.into(); + + let mut params = JwkParamsOkp::new(); + params.x = jwu::encode_b64(public_key); + EdCurve::Ed25519.name().clone_into(&mut params.crv); + let mut jwk: Jwk = Jwk::from_params(params); + jwk.set_alg(JwsAlgorithm::EdDSA.name()); + jwk.set_kid(jwk.thumbprint_sha256_b64()); + + Ok(jwk) + } +} diff --git a/identity_stronghold/src/storage/stronghold_jwk_storage.rs b/identity_stronghold/src/storage/stronghold_jwk_storage.rs new file mode 100644 index 0000000000..efe0f6531b --- /dev/null +++ b/identity_stronghold/src/storage/stronghold_jwk_storage.rs @@ -0,0 +1,231 @@ +// Copyright 2020-2023 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +//! Wrapper around [`StrongholdSecretManager`](StrongholdSecretManager). + +use async_trait::async_trait; +use identity_storage::key_storage::JwkStorage; +use identity_storage::JwkGenOutput; +use identity_storage::KeyId; +use identity_storage::KeyStorageError; +use identity_storage::KeyStorageErrorKind; +use identity_storage::KeyStorageResult; +use identity_storage::KeyType; +use identity_verification::jwk::EdCurve; +use identity_verification::jwk::Jwk; +use identity_verification::jwk::JwkParamsOkp; +use identity_verification::jws::JwsAlgorithm; +use identity_verification::jwu; +use iota_stronghold::procedures::Ed25519Sign; +use iota_stronghold::procedures::GenerateKey; +use iota_stronghold::procedures::KeyType as ProceduresKeyType; +use iota_stronghold::procedures::StrongholdProcedure; +use iota_stronghold::Location; +use std::str::FromStr; + +use crate::ed25519; +use crate::stronghold_key_type::StrongholdKeyType; +use crate::utils::*; +use crate::StrongholdStorage; + +#[cfg_attr(not(feature = "send-sync-storage"), async_trait(?Send))] +#[cfg_attr(feature = "send-sync-storage", async_trait)] +impl JwkStorage for StrongholdStorage { + async fn generate(&self, key_type: KeyType, alg: JwsAlgorithm) -> KeyStorageResult { + let stronghold = self.get_stronghold().await; + + let client = get_client(&stronghold)?; + let key_type = StrongholdKeyType::try_from(&key_type)?; + check_key_alg_compatibility(key_type, &alg)?; + + let keytype: ProceduresKeyType = match key_type { + StrongholdKeyType::Ed25519 => ProceduresKeyType::Ed25519, + StrongholdKeyType::Bls12381G2 => { + return Err( + KeyStorageError::new(KeyStorageErrorKind::Unspecified).with_custom_message(format!( + "`{key_type}` is supported but `JwkStorageBbsPlusExt::generate_bbs` should be called instead." + )), + ) + } + }; + + let key_id: KeyId = random_key_id(); + let location = Location::generic( + IDENTITY_VAULT_PATH.as_bytes().to_vec(), + key_id.to_string().as_bytes().to_vec(), + ); + + let generate_key_procedure = GenerateKey { + ty: keytype.clone(), + output: location.clone(), + }; + + client + .execute_procedure(StrongholdProcedure::GenerateKey(generate_key_procedure)) + .map_err(|err| { + KeyStorageError::new(KeyStorageErrorKind::Unspecified) + .with_custom_message("stronghold generate key procedure failed") + .with_source(err) + })?; + + let public_key_procedure = iota_stronghold::procedures::PublicKey { + ty: keytype, + private_key: location, + }; + + let procedure_result = client + .execute_procedure(StrongholdProcedure::PublicKey(public_key_procedure)) + .map_err(|err| { + KeyStorageError::new(KeyStorageErrorKind::Unspecified) + .with_custom_message("stronghold public key procedure failed") + .with_source(err) + })?; + let public_key: Vec = procedure_result.into(); + persist_changes(self.as_secret_manager(), stronghold).await?; + + let mut params = JwkParamsOkp::new(); + params.x = jwu::encode_b64(public_key); + params.crv = EdCurve::Ed25519.name().to_string(); + let mut jwk: Jwk = Jwk::from_params(params); + jwk.set_alg(alg.name()); + jwk.set_kid(jwk.thumbprint_sha256_b64()); + + Ok(JwkGenOutput::new(key_id, jwk)) + } + + async fn insert(&self, jwk: Jwk) -> KeyStorageResult { + let key_type = StrongholdKeyType::try_from(&jwk)?; + if !jwk.is_private() { + return Err( + KeyStorageError::new(KeyStorageErrorKind::Unspecified) + .with_custom_message("expected a Jwk with all private key components set"), + ); + } + + match jwk.alg() { + Some(alg) => { + let alg: JwsAlgorithm = JwsAlgorithm::from_str(alg) + .map_err(|err| KeyStorageError::new(KeyStorageErrorKind::UnsupportedSignatureAlgorithm).with_source(err))?; + check_key_alg_compatibility(key_type, &alg)?; + } + None => { + return Err( + KeyStorageError::new(KeyStorageErrorKind::UnsupportedSignatureAlgorithm) + .with_custom_message("expected a Jwk with an `alg` parameter"), + ); + } + } + let secret_key = ed25519::expand_secret_jwk(&jwk)?; + let key_id: KeyId = random_key_id(); + + let location = Location::generic( + IDENTITY_VAULT_PATH.as_bytes().to_vec(), + key_id.to_string().as_bytes().to_vec(), + ); + let stronghold = self.get_stronghold().await; + let client = get_client(&stronghold)?; + client + .vault(IDENTITY_VAULT_PATH.as_bytes()) + .write_secret(location, zeroize::Zeroizing::from(secret_key.to_bytes().to_vec())) + .map_err(|err| { + KeyStorageError::new(KeyStorageErrorKind::Unspecified) + .with_custom_message("stronghold write secret failed") + .with_source(err) + })?; + + persist_changes(self.as_secret_manager(), stronghold).await?; + + Ok(key_id) + } + + async fn sign(&self, key_id: &KeyId, data: &[u8], public_key: &Jwk) -> KeyStorageResult> { + // Extract the required alg from the given public key + let alg = public_key + .alg() + .ok_or(KeyStorageErrorKind::UnsupportedSignatureAlgorithm) + .and_then(|alg_str| { + JwsAlgorithm::from_str(alg_str).map_err(|_| KeyStorageErrorKind::UnsupportedSignatureAlgorithm) + })?; + + // Check that `kty` is `Okp` and `crv = Ed25519`. + match alg { + JwsAlgorithm::EdDSA => { + let okp_params = public_key.try_okp_params().map_err(|err| { + KeyStorageError::new(KeyStorageErrorKind::Unspecified) + .with_custom_message(format!("expected a Jwk with Okp params in order to sign with {alg}")) + .with_source(err) + })?; + if okp_params.crv != EdCurve::Ed25519.name() { + return Err( + KeyStorageError::new(KeyStorageErrorKind::Unspecified).with_custom_message(format!( + "expected Jwk with Okp {} crv in order to sign with {alg}", + EdCurve::Ed25519 + )), + ); + } + } + other => { + return Err( + KeyStorageError::new(KeyStorageErrorKind::UnsupportedSignatureAlgorithm) + .with_custom_message(format!("{other} is not supported")), + ); + } + }; + + let location = Location::generic( + IDENTITY_VAULT_PATH.as_bytes().to_vec(), + key_id.to_string().as_bytes().to_vec(), + ); + let procedure: Ed25519Sign = Ed25519Sign { + private_key: location, + msg: data.to_vec(), + }; + + let stronghold = self.get_stronghold().await; + let client = get_client(&stronghold)?; + + let signature: [u8; 64] = client.execute_procedure(procedure).map_err(|err| { + KeyStorageError::new(KeyStorageErrorKind::Unspecified) + .with_custom_message("stronghold Ed25519Sign procedure failed") + .with_source(err) + })?; + + Ok(signature.to_vec()) + } + + async fn delete(&self, key_id: &KeyId) -> KeyStorageResult<()> { + let stronghold = self.get_stronghold().await; + let client = get_client(&stronghold)?; + let deleted = client + .vault(IDENTITY_VAULT_PATH.as_bytes()) + .delete_secret(key_id.to_string().as_bytes()) + .map_err(|err| { + KeyStorageError::new(KeyStorageErrorKind::Unspecified) + .with_custom_message("stronghold client error") + .with_source(err) + })?; + + if !deleted { + return Err(KeyStorageError::new(KeyStorageErrorKind::KeyNotFound)); + } + + persist_changes(self.as_secret_manager(), stronghold).await?; + + Ok(()) + } + + async fn exists(&self, key_id: &KeyId) -> KeyStorageResult { + let stronghold = self.get_stronghold().await; + let client = get_client(&stronghold)?; + let location = Location::generic( + IDENTITY_VAULT_PATH.as_bytes().to_vec(), + key_id.to_string().as_bytes().to_vec(), + ); + let exists = client.record_exists(&location).map_err(|err| { + KeyStorageError::new(KeyStorageErrorKind::Unspecified) + .with_custom_message("stronghold client error") + .with_source(err) + })?; + Ok(exists) + } +} diff --git a/identity_stronghold/src/storage/stronghold_jwk_storage_bbs_plus_ext.rs b/identity_stronghold/src/storage/stronghold_jwk_storage_bbs_plus_ext.rs new file mode 100644 index 0000000000..10fbe7faa0 --- /dev/null +++ b/identity_stronghold/src/storage/stronghold_jwk_storage_bbs_plus_ext.rs @@ -0,0 +1,174 @@ +// Copyright 2020-2024 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +use async_trait::async_trait; +use identity_storage::key_storage::bls::*; +use identity_storage::key_storage::JwkStorage; +use identity_storage::JwkGenOutput; +use identity_storage::JwkStorageBbsPlusExt; +use identity_storage::KeyId; +use identity_storage::KeyStorageError; +use identity_storage::KeyStorageErrorKind; +use identity_storage::KeyStorageResult; +use identity_storage::KeyType; +use identity_storage::ProofUpdateCtx; +use identity_verification::jwk::Jwk; +use iota_stronghold::procedures::FatalProcedureError; +use iota_stronghold::procedures::Products; +use iota_stronghold::procedures::Runner as _; +use iota_stronghold::Location; +use jsonprooftoken::jpa::algs::ProofAlgorithm; +use std::str::FromStr; +use zeroize::Zeroizing; +use zkryptium::bbsplus::keys::BBSplusSecretKey; + +use crate::stronghold_key_type::*; +use crate::utils::*; +use crate::StrongholdStorage; + +#[cfg_attr(not(feature = "send-sync-storage"), async_trait(?Send))] +#[cfg_attr(feature = "send-sync-storage", async_trait)] +impl JwkStorageBbsPlusExt for StrongholdStorage { + async fn generate_bbs(&self, key_type: KeyType, alg: ProofAlgorithm) -> KeyStorageResult { + let key_type = StrongholdKeyType::try_from(&key_type)?; + + if !matches!(key_type, StrongholdKeyType::Bls12381G2) { + return Err( + KeyStorageError::new(KeyStorageErrorKind::UnsupportedKeyType) + .with_custom_message(format!("{key_type} is not supported")), + ); + } + + if !matches!(alg, ProofAlgorithm::BLS12381_SHA256 | ProofAlgorithm::BLS12381_SHAKE256) { + return Err(KeyStorageErrorKind::UnsupportedProofAlgorithm.into()); + } + + // Get a key id that's not already used. + let mut kid = random_key_id(); + while self.exists(&kid).await? { + kid = random_key_id(); + } + + let stronghold = self.get_stronghold().await; + let client = get_client(&stronghold)?; + let target_key_location = Location::generic( + IDENTITY_VAULT_PATH.as_bytes().to_vec(), + kid.to_string().as_bytes().to_vec(), + ); + let jwk = client + .exec_proc([], &target_key_location, |_| { + let (sk, pk) = generate_bbs_keypair(alg).map_err(|e| FatalProcedureError::from(e.to_string()))?; + let public_jwk = encode_bls_jwk(&sk, &pk, alg).1; + + Ok(Products { + output: public_jwk, + secret: Zeroizing::new(sk.to_bytes().to_vec()), + }) + }) + .map_err(|e| { + KeyStorageError::new(KeyStorageErrorKind::Unspecified) + .with_custom_message("Failed to execute stronghold procedure") + .with_source(e) + })?; + + persist_changes(self.as_secret_manager(), stronghold).await?; + + Ok(JwkGenOutput::new(kid, jwk)) + } + + async fn sign_bbs( + &self, + key_id: &KeyId, + data: &[Vec], + header: &[u8], + public_key: &Jwk, + ) -> KeyStorageResult> { + // Extract the required alg from the given public key + let alg = public_key + .alg() + .ok_or(KeyStorageErrorKind::UnsupportedProofAlgorithm) + .and_then(|alg_str| { + ProofAlgorithm::from_str(alg_str).map_err(|_| KeyStorageErrorKind::UnsupportedProofAlgorithm) + })?; + + // Check `key_id` exists in store. + if !self.exists(key_id).await? { + return Err(KeyStorageError::new(KeyStorageErrorKind::KeyNotFound)); + } + + let pk = expand_bls_jwk(public_key) + .map_err(|e| KeyStorageError::new(KeyStorageErrorKind::Unspecified).with_source(e))? + .1; + + let sk_location = Location::Generic { + vault_path: IDENTITY_VAULT_PATH.as_bytes().to_vec(), + record_path: key_id.to_string().as_bytes().to_vec(), + }; + + let stronghold = self.get_stronghold().await; + let client = get_client(&stronghold)?; + client + .get_guards([sk_location], |[sk]| { + let sk = BBSplusSecretKey::from_bytes(&sk.borrow()).map_err(|e| FatalProcedureError::from(e.to_string()))?; + // Ensure `sk` and `pk` matches. + if sk.public_key() != pk { + return Err(FatalProcedureError::from( + "`public_key` is not the public key of key with id `key_id`".to_owned(), + )); + } + let signature_result = + sign_bbs(alg, data, &sk, &pk, header).map_err(|e| FatalProcedureError::from(e.to_string())); + // clean up `sk` to avoid leaking. + drop(Zeroizing::new(sk.to_bytes())); + signature_result + }) + .map(|sig| sig.to_vec()) + .map_err(|e| { + KeyStorageError::new(KeyStorageErrorKind::Unspecified) + .with_custom_message("Signature failed") + .with_source(e) + }) + } + + async fn update_signature( + &self, + key_id: &KeyId, + public_key: &Jwk, + signature: &[u8], + ctx: ProofUpdateCtx, + ) -> KeyStorageResult> { + // Extract the required alg from the given public key + let alg = public_key + .alg() + .ok_or(KeyStorageErrorKind::UnsupportedProofAlgorithm) + .and_then(|alg_str| { + ProofAlgorithm::from_str(alg_str).map_err(|_| KeyStorageErrorKind::UnsupportedProofAlgorithm) + })?; + + // Check `key_id` exists in store. + if !self.exists(key_id).await? { + return Err(KeyStorageError::new(KeyStorageErrorKind::KeyNotFound)); + } + + let sk_location = Location::Generic { + vault_path: IDENTITY_VAULT_PATH.as_bytes().to_vec(), + record_path: key_id.to_string().as_bytes().to_vec(), + }; + let stronghold = self.get_stronghold().await; + let client = get_client(&stronghold)?; + + client + .get_guards([sk_location], |[sk]| { + let sk = BBSplusSecretKey::from_bytes(&sk.borrow()).map_err(|e| FatalProcedureError::from(e.to_string()))?; + let signature_update_result = + update_bbs_signature(alg, signature, &sk, &ctx).map_err(|e| FatalProcedureError::from(e.to_string())); + drop(Zeroizing::new(sk.to_bytes())); + signature_update_result + }) + .map_err(|e| { + KeyStorageError::new(KeyStorageErrorKind::Unspecified) + .with_custom_message("Signature update failed") + .with_source(e) + }) + } +} diff --git a/identity_stronghold/src/stronghold_key_id.rs b/identity_stronghold/src/storage/stronghold_key_id.rs similarity index 98% rename from identity_stronghold/src/stronghold_key_id.rs rename to identity_stronghold/src/storage/stronghold_key_id.rs index dcd3755cab..f7b7aa6436 100644 --- a/identity_stronghold/src/stronghold_key_id.rs +++ b/identity_stronghold/src/storage/stronghold_key_id.rs @@ -1,7 +1,7 @@ // Copyright 2020-2023 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -use crate::stronghold_jwk_storage::IDENTITY_CLIENT_PATH; +use crate::utils::IDENTITY_CLIENT_PATH; use crate::StrongholdStorage; use async_trait::async_trait; use identity_storage::key_id_storage::KeyIdStorage; diff --git a/identity_stronghold/src/stronghold_jwk_storage.rs b/identity_stronghold/src/stronghold_jwk_storage.rs deleted file mode 100644 index 16f25584b6..0000000000 --- a/identity_stronghold/src/stronghold_jwk_storage.rs +++ /dev/null @@ -1,423 +0,0 @@ -// Copyright 2020-2023 IOTA Stiftung -// SPDX-License-Identifier: Apache-2.0 - -//! Wrapper around [`StrongholdSecretManager`](StrongholdSecretManager). - -use async_trait::async_trait; -use identity_storage::key_storage::JwkStorage; -use identity_storage::JwkGenOutput; -use identity_storage::KeyId; -use identity_storage::KeyStorageError; -use identity_storage::KeyStorageErrorKind; -use identity_storage::KeyStorageResult; -use identity_storage::KeyType; -use identity_verification::jwk::EdCurve; -use identity_verification::jwk::Jwk; -use identity_verification::jwk::JwkParamsOkp; -use identity_verification::jwk::JwkType; -use identity_verification::jws::JwsAlgorithm; -use identity_verification::jwu; -use iota_sdk::client::secret::stronghold::StrongholdSecretManager; -use iota_sdk::client::secret::SecretManager; -use iota_stronghold::procedures::Ed25519Sign; -use iota_stronghold::procedures::GenerateKey; -use iota_stronghold::procedures::KeyType as ProceduresKeyType; -use iota_stronghold::procedures::StrongholdProcedure; -use iota_stronghold::Client; -use iota_stronghold::ClientError; -use iota_stronghold::Location; -use iota_stronghold::Stronghold; -use rand::distributions::DistString; -use std::fmt::Display; -use std::str::FromStr; -use std::sync::Arc; -use tokio::sync::MutexGuard; - -use crate::ed25519; - -const ED25519_KEY_TYPE_STR: &str = "Ed25519"; -static IDENTITY_VAULT_PATH: &str = "iota_identity_vault"; -pub(crate) static IDENTITY_CLIENT_PATH: &[u8] = b"iota_identity_client"; - -/// The Ed25519 key type. -pub const ED25519_KEY_TYPE: &KeyType = &KeyType::from_static_str(ED25519_KEY_TYPE_STR); - -/// Wrapper around a [`StrongholdSecretManager`] that implements the [`KeyIdStorage`](crate::KeyIdStorage) -/// and [`JwkStorage`](crate::JwkStorage) interfaces. -#[derive(Clone, Debug)] -pub struct StrongholdStorage(Arc); - -impl StrongholdStorage { - /// Creates a new [`StrongholdStorage`]. - pub fn new(stronghold_secret_manager: StrongholdSecretManager) -> Self { - Self(Arc::new(SecretManager::Stronghold(stronghold_secret_manager))) - } - - /// Shared reference to the inner [`SecretManager`]. - pub fn as_secret_manager(&self) -> &SecretManager { - self.0.as_ref() - } - - /// Acquire lock of the inner [`Stronghold`]. - pub(crate) async fn get_stronghold(&self) -> MutexGuard<'_, Stronghold> { - match *self.0 { - SecretManager::Stronghold(ref stronghold) => stronghold.inner().await, - _ => unreachable!("secret manager can be only constructed from stronghold"), - } - } - - /// Retrieve the public key corresponding to `key_id`. - pub async fn get_public_key(&self, key_id: &KeyId) -> KeyStorageResult { - let stronghold = self.get_stronghold().await; - let client = get_client(&stronghold)?; - - let location = Location::generic( - IDENTITY_VAULT_PATH.as_bytes().to_vec(), - key_id.to_string().as_bytes().to_vec(), - ); - - let public_key_procedure = iota_stronghold::procedures::PublicKey { - ty: ProceduresKeyType::Ed25519, - private_key: location, - }; - - let procedure_result = client - .execute_procedure(StrongholdProcedure::PublicKey(public_key_procedure)) - .map_err(|err| KeyStorageError::new(KeyStorageErrorKind::KeyNotFound).with_source(err))?; - - let public_key: Vec = procedure_result.into(); - - let mut params = JwkParamsOkp::new(); - params.x = jwu::encode_b64(public_key); - params.crv = EdCurve::Ed25519.name().to_owned(); - let mut jwk: Jwk = Jwk::from_params(params); - jwk.set_alg(JwsAlgorithm::EdDSA.name()); - jwk.set_kid(jwk.thumbprint_sha256_b64()); - - Ok(jwk) - } -} - -#[cfg_attr(not(feature = "send-sync-storage"), async_trait(?Send))] -#[cfg_attr(feature = "send-sync-storage", async_trait)] -impl JwkStorage for StrongholdStorage { - async fn generate(&self, key_type: KeyType, alg: JwsAlgorithm) -> KeyStorageResult { - let stronghold = self.get_stronghold().await; - - let client = get_client(&stronghold)?; - let key_type = StrongholdKeyType::try_from(&key_type)?; - check_key_alg_compatibility(key_type, alg)?; - - let keytype: ProceduresKeyType = match key_type { - StrongholdKeyType::Ed25519 => ProceduresKeyType::Ed25519, - }; - - let key_id: KeyId = random_key_id(); - let location = Location::generic( - IDENTITY_VAULT_PATH.as_bytes().to_vec(), - key_id.to_string().as_bytes().to_vec(), - ); - - let generate_key_procedure = GenerateKey { - ty: keytype.clone(), - output: location.clone(), - }; - - client - .execute_procedure(StrongholdProcedure::GenerateKey(generate_key_procedure)) - .map_err(|err| { - KeyStorageError::new(KeyStorageErrorKind::Unspecified) - .with_custom_message("stronghold generate key procedure failed") - .with_source(err) - })?; - - let public_key_procedure = iota_stronghold::procedures::PublicKey { - ty: keytype, - private_key: location, - }; - - let procedure_result = client - .execute_procedure(StrongholdProcedure::PublicKey(public_key_procedure)) - .map_err(|err| { - KeyStorageError::new(KeyStorageErrorKind::Unspecified) - .with_custom_message("stronghold public key procedure failed") - .with_source(err) - })?; - persist_changes(self, stronghold).await?; - let public_key: Vec = procedure_result.into(); - - let mut params = JwkParamsOkp::new(); - params.x = jwu::encode_b64(public_key); - params.crv = EdCurve::Ed25519.name().to_owned(); - let mut jwk: Jwk = Jwk::from_params(params); - jwk.set_alg(alg.name()); - jwk.set_kid(jwk.thumbprint_sha256_b64()); - - Ok(JwkGenOutput::new(key_id, jwk)) - } - - async fn insert(&self, jwk: Jwk) -> KeyStorageResult { - let key_type: StrongholdKeyType = StrongholdKeyType::try_from(&jwk)?; - if !jwk.is_private() { - return Err( - KeyStorageError::new(KeyStorageErrorKind::Unspecified) - .with_custom_message("expected a Jwk with all private key components set"), - ); - } - - match jwk.alg() { - Some(alg) => { - let alg: JwsAlgorithm = JwsAlgorithm::from_str(alg) - .map_err(|err| KeyStorageError::new(KeyStorageErrorKind::UnsupportedSignatureAlgorithm).with_source(err))?; - check_key_alg_compatibility(key_type, alg)?; - } - None => { - return Err( - KeyStorageError::new(KeyStorageErrorKind::UnsupportedSignatureAlgorithm) - .with_custom_message("expected a Jwk with an `alg` parameter"), - ); - } - } - let secret_key = ed25519::expand_secret_jwk(&jwk)?; - let key_id: KeyId = random_key_id(); - - let location = Location::generic( - IDENTITY_VAULT_PATH.as_bytes().to_vec(), - key_id.to_string().as_bytes().to_vec(), - ); - let stronghold = self.get_stronghold().await; - let client = get_client(&stronghold)?; - client - .vault(IDENTITY_VAULT_PATH.as_bytes()) - .write_secret(location, zeroize::Zeroizing::from(secret_key.to_bytes().to_vec())) - .map_err(|err| { - KeyStorageError::new(KeyStorageErrorKind::Unspecified) - .with_custom_message("stronghold write secret failed") - .with_source(err) - })?; - persist_changes(self, stronghold).await?; - - Ok(key_id) - } - - async fn sign(&self, key_id: &KeyId, data: &[u8], public_key: &Jwk) -> KeyStorageResult> { - // Extract the required alg from the given public key - let alg = public_key - .alg() - .ok_or(KeyStorageErrorKind::UnsupportedSignatureAlgorithm) - .and_then(|alg_str| { - JwsAlgorithm::from_str(alg_str).map_err(|_| KeyStorageErrorKind::UnsupportedSignatureAlgorithm) - })?; - - // Check that `kty` is `Okp` and `crv = Ed25519`. - match alg { - JwsAlgorithm::EdDSA => { - let okp_params = public_key.try_okp_params().map_err(|err| { - KeyStorageError::new(KeyStorageErrorKind::Unspecified) - .with_custom_message(format!("expected a Jwk with Okp params in order to sign with {alg}")) - .with_source(err) - })?; - if okp_params.crv != EdCurve::Ed25519.name() { - return Err( - KeyStorageError::new(KeyStorageErrorKind::Unspecified).with_custom_message(format!( - "expected Jwk with Okp {} crv in order to sign with {alg}", - EdCurve::Ed25519 - )), - ); - } - } - other => { - return Err( - KeyStorageError::new(KeyStorageErrorKind::UnsupportedSignatureAlgorithm) - .with_custom_message(format!("{other} is not supported")), - ); - } - }; - - let location = Location::generic( - IDENTITY_VAULT_PATH.as_bytes().to_vec(), - key_id.to_string().as_bytes().to_vec(), - ); - let procedure: Ed25519Sign = Ed25519Sign { - private_key: location, - msg: data.to_vec(), - }; - - let stronghold = self.get_stronghold().await; - let client = get_client(&stronghold)?; - - let signature: [u8; 64] = client.execute_procedure(procedure).map_err(|err| { - KeyStorageError::new(KeyStorageErrorKind::Unspecified) - .with_custom_message("stronghold Ed25519Sign procedure failed") - .with_source(err) - })?; - - Ok(signature.to_vec()) - } - - async fn delete(&self, key_id: &KeyId) -> KeyStorageResult<()> { - let stronghold = self.get_stronghold().await; - let client = get_client(&stronghold)?; - let deleted = client - .vault(IDENTITY_VAULT_PATH.as_bytes()) - .delete_secret(key_id.to_string().as_bytes()) - .map_err(|err| { - KeyStorageError::new(KeyStorageErrorKind::Unspecified) - .with_custom_message("stronghold client error") - .with_source(err) - })?; - - if !deleted { - return Err(KeyStorageError::new(KeyStorageErrorKind::KeyNotFound)); - } - persist_changes(self, stronghold).await?; - - Ok(()) - } - - async fn exists(&self, key_id: &KeyId) -> KeyStorageResult { - let stronghold = self.get_stronghold().await; - let client = get_client(&stronghold)?; - let location = Location::generic( - IDENTITY_VAULT_PATH.as_bytes().to_vec(), - key_id.to_string().as_bytes().to_vec(), - ); - let exists = client.record_exists(&location).map_err(|err| { - KeyStorageError::new(KeyStorageErrorKind::Unspecified) - .with_custom_message("stronghold client error") - .with_source(err) - })?; - Ok(exists) - } -} - -/// Generate a random alphanumeric string of len 32. -fn random_key_id() -> KeyId { - KeyId::new(rand::distributions::Alphanumeric.sample_string(&mut rand::thread_rng(), 32)) -} - -/// Check that the key type can be used with the algorithm. -fn check_key_alg_compatibility(key_type: StrongholdKeyType, alg: JwsAlgorithm) -> KeyStorageResult<()> { - match (key_type, alg) { - (StrongholdKeyType::Ed25519, JwsAlgorithm::EdDSA) => Ok(()), - (key_type, alg) => Err( - KeyStorageError::new(identity_storage::KeyStorageErrorKind::KeyAlgorithmMismatch) - .with_custom_message(format!("cannot use key type `{key_type}` with algorithm `{alg}`")), - ), - } -} - -fn get_client(stronghold: &Stronghold) -> KeyStorageResult { - let client = stronghold.get_client(IDENTITY_CLIENT_PATH); - match client { - Ok(client) => Ok(client), - Err(ClientError::ClientDataNotPresent) => load_or_create_client(stronghold), - Err(err) => Err(KeyStorageError::new(KeyStorageErrorKind::Unspecified).with_source(err)), - } -} - -fn load_or_create_client(stronghold: &Stronghold) -> KeyStorageResult { - match stronghold.load_client(IDENTITY_CLIENT_PATH) { - Ok(client) => Ok(client), - Err(ClientError::ClientDataNotPresent) => stronghold - .create_client(IDENTITY_CLIENT_PATH) - .map_err(|err| KeyStorageError::new(KeyStorageErrorKind::Unspecified).with_source(err)), - Err(err) => Err(KeyStorageError::new(KeyStorageErrorKind::Unspecified).with_source(err)), - } -} - -async fn persist_changes( - secret_manager: &StrongholdStorage, - stronghold: MutexGuard<'_, Stronghold>, -) -> KeyStorageResult<()> { - stronghold.write_client(IDENTITY_CLIENT_PATH).map_err(|err| { - KeyStorageError::new(KeyStorageErrorKind::Unspecified) - .with_custom_message("stronghold write client error") - .with_source(err) - })?; - // Must be dropped since `write_stronghold_snapshot` needs to acquire the stronghold lock. - drop(stronghold); - - match secret_manager.as_secret_manager() { - iota_sdk::client::secret::SecretManager::Stronghold(stronghold_manager) => { - stronghold_manager - .write_stronghold_snapshot(None) - .await - .map_err(|err| { - KeyStorageError::new(KeyStorageErrorKind::Unspecified) - .with_custom_message("writing to stronghold snapshot failed") - .with_source(err) - })?; - } - _ => { - return Err( - KeyStorageError::new(KeyStorageErrorKind::Unspecified) - .with_custom_message("secret manager is not of type stronghold"), - ) - } - }; - Ok(()) -} - -/// Key Types supported by the stronghold storage implementation. -#[derive(Debug, Copy, Clone)] -enum StrongholdKeyType { - Ed25519, -} - -impl StrongholdKeyType { - /// String representation of the key type. - const fn name(&self) -> &'static str { - match self { - StrongholdKeyType::Ed25519 => "Ed25519", - } - } -} - -impl Display for StrongholdKeyType { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.write_str(self.name()) - } -} - -impl TryFrom<&KeyType> for StrongholdKeyType { - type Error = KeyStorageError; - - fn try_from(value: &KeyType) -> Result { - match value.as_str() { - ED25519_KEY_TYPE_STR => Ok(StrongholdKeyType::Ed25519), - _ => Err(KeyStorageError::new(KeyStorageErrorKind::UnsupportedKeyType)), - } - } -} - -impl TryFrom<&Jwk> for StrongholdKeyType { - type Error = KeyStorageError; - - fn try_from(jwk: &Jwk) -> Result { - match jwk.kty() { - JwkType::Okp => { - let okp_params = jwk.try_okp_params().map_err(|err| { - KeyStorageError::new(KeyStorageErrorKind::UnsupportedKeyType) - .with_custom_message("expected Okp parameters for a JWK with `kty` Okp") - .with_source(err) - })?; - match okp_params.try_ed_curve().map_err(|err| { - KeyStorageError::new(KeyStorageErrorKind::UnsupportedKeyType) - .with_custom_message("only Ed curves are supported for signing") - .with_source(err) - })? { - EdCurve::Ed25519 => Ok(StrongholdKeyType::Ed25519), - curve => Err( - KeyStorageError::new(KeyStorageErrorKind::UnsupportedKeyType) - .with_custom_message(format!("{curve} not supported")), - ), - } - } - other => Err( - KeyStorageError::new(KeyStorageErrorKind::UnsupportedKeyType) - .with_custom_message(format!("Jwk `kty` {other} not supported")), - ), - } - } -} diff --git a/identity_stronghold/src/stronghold_key_type.rs b/identity_stronghold/src/stronghold_key_type.rs new file mode 100644 index 0000000000..c78deb4d3a --- /dev/null +++ b/identity_stronghold/src/stronghold_key_type.rs @@ -0,0 +1,109 @@ +// Copyright 2020-2024 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +use std::fmt::Display; + +use identity_storage::KeyStorageError; +use identity_storage::KeyStorageErrorKind; +use identity_storage::KeyType; +use identity_verification::jwk::BlsCurve; +use identity_verification::jwk::EdCurve; +use identity_verification::jwk::Jwk; +use identity_verification::jwk::JwkType; + +pub const ED25519_KEY_TYPE_STR: &str = "Ed25519"; +/// The Ed25519 key type. +pub const ED25519_KEY_TYPE: KeyType = KeyType::from_static_str(ED25519_KEY_TYPE_STR); +pub const BLS12381G2_KEY_TYPE_STR: &str = "BLS12381G2"; +/// The BLS12381G2 key type +pub const BLS12381G2_KEY_TYPE: KeyType = KeyType::from_static_str(BLS12381G2_KEY_TYPE_STR); + +/// Key Types supported by the stronghold storage implementation. +#[derive(Debug, Copy, Clone)] +pub enum StrongholdKeyType { + Ed25519, + Bls12381G2, +} + +impl StrongholdKeyType { + /// String representation of the key type. + const fn name(&self) -> &'static str { + match self { + StrongholdKeyType::Ed25519 => ED25519_KEY_TYPE_STR, + StrongholdKeyType::Bls12381G2 => BLS12381G2_KEY_TYPE_STR, + } + } +} + +impl Display for StrongholdKeyType { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str(self.name()) + } +} + +impl TryFrom<&KeyType> for StrongholdKeyType { + type Error = KeyStorageError; + + fn try_from(value: &KeyType) -> Result { + match value.as_str() { + ED25519_KEY_TYPE_STR => Ok(StrongholdKeyType::Ed25519), + BLS12381G2_KEY_TYPE_STR => Ok(StrongholdKeyType::Bls12381G2), + _ => Err(KeyStorageError::new(KeyStorageErrorKind::UnsupportedKeyType)), + } + } +} + +impl From for KeyType { + fn from(key_type: StrongholdKeyType) -> KeyType { + KeyType::from_static_str(key_type.name()) + } +} + +impl TryFrom<&Jwk> for StrongholdKeyType { + type Error = KeyStorageError; + + fn try_from(jwk: &Jwk) -> Result { + match jwk.kty() { + JwkType::Okp => { + let okp_params = jwk.try_okp_params().map_err(|err| { + KeyStorageError::new(KeyStorageErrorKind::UnsupportedKeyType) + .with_custom_message("expected Okp parameters for a JWK with `kty` Okp") + .with_source(err) + })?; + match okp_params.try_ed_curve().map_err(|err| { + KeyStorageError::new(KeyStorageErrorKind::UnsupportedKeyType) + .with_custom_message("only Ed curves are supported for signing") + .with_source(err) + })? { + EdCurve::Ed25519 => Ok(StrongholdKeyType::Ed25519), + curve => Err( + KeyStorageError::new(KeyStorageErrorKind::UnsupportedKeyType) + .with_custom_message(format!("{curve} not supported")), + ), + } + } + JwkType::Ec => { + let ec_params = jwk.try_ec_params().map_err(|err| { + KeyStorageError::new(KeyStorageErrorKind::UnsupportedKeyType) + .with_custom_message("expected EC parameters for a JWK with `kty` Ec") + .with_source(err) + })?; + match ec_params.try_bls_curve().map_err(|err| { + KeyStorageError::new(KeyStorageErrorKind::UnsupportedKeyType) + .with_custom_message("only Ed curves are supported for signing") + .with_source(err) + })? { + BlsCurve::BLS12381G2 => Ok(StrongholdKeyType::Bls12381G2), + curve => Err( + KeyStorageError::new(KeyStorageErrorKind::UnsupportedKeyType) + .with_custom_message(format!("{curve} not supported")), + ), + } + } + other => Err( + KeyStorageError::new(KeyStorageErrorKind::UnsupportedKeyType) + .with_custom_message(format!("Jwk `kty` {other} not supported")), + ), + } + } +} diff --git a/identity_stronghold/src/tests/mod.rs b/identity_stronghold/src/tests/mod.rs index 96db03f0aa..54c5488c05 100644 --- a/identity_stronghold/src/tests/mod.rs +++ b/identity_stronghold/src/tests/mod.rs @@ -1,6 +1,7 @@ // Copyright 2020-2023 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 +mod test_bbs_ext; mod test_jwk_storage; mod test_key_id_storage; pub(crate) mod utils; diff --git a/identity_stronghold/src/tests/test_bbs_ext.rs b/identity_stronghold/src/tests/test_bbs_ext.rs new file mode 100644 index 0000000000..efa71f3cc2 --- /dev/null +++ b/identity_stronghold/src/tests/test_bbs_ext.rs @@ -0,0 +1,93 @@ +// Copyright 2020-2024 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +use identity_storage::key_storage::bls::expand_bls_jwk; +use identity_storage::key_storage::bls::sign_bbs; +use identity_storage::JwkGenOutput; +use identity_storage::JwkStorage; +use identity_storage::JwkStorageBbsPlusExt; +use identity_storage::KeyStorageErrorKind; +use iota_stronghold::procedures::Runner; +use iota_stronghold::Location; +use jsonprooftoken::jpa::algs::ProofAlgorithm; +use rand::RngCore; +use zkryptium::bbsplus::keys::BBSplusSecretKey; + +use crate::stronghold_key_type::StrongholdKeyType; +use crate::tests::utils::create_stronghold_secret_manager; +use crate::utils::get_client; +use crate::utils::IDENTITY_VAULT_PATH; +use crate::StrongholdStorage; + +#[tokio::test] +async fn stronghold_bbs_keypair_gen_works() -> anyhow::Result<()> { + let stronghold_storage = StrongholdStorage::new(create_stronghold_secret_manager()); + let JwkGenOutput { key_id, jwk, .. } = stronghold_storage + .generate_bbs(StrongholdKeyType::Bls12381G2.into(), ProofAlgorithm::BLS12381_SHA256) + .await?; + + assert!(jwk.is_public()); + assert!(stronghold_storage.exists(&key_id).await?); + + Ok(()) +} + +#[tokio::test] +async fn stronghold_bbs_keypair_gen_fails_with_wrong_key_type() -> anyhow::Result<()> { + let stronghold_storage = StrongholdStorage::new(create_stronghold_secret_manager()); + let error = stronghold_storage + .generate_bbs(StrongholdKeyType::Ed25519.into(), ProofAlgorithm::BLS12381_SHA256) + .await + .unwrap_err(); + assert!(matches!(error.kind(), KeyStorageErrorKind::UnsupportedKeyType)); + + Ok(()) +} + +#[tokio::test] +async fn stronghold_bbs_keypair_gen_fails_with_wrong_alg() -> anyhow::Result<()> { + let stronghold_storage = StrongholdStorage::new(create_stronghold_secret_manager()); + let error = stronghold_storage + .generate_bbs(StrongholdKeyType::Bls12381G2.into(), ProofAlgorithm::MAC_H256) + .await + .unwrap_err(); + + assert!(matches!(error.kind(), KeyStorageErrorKind::UnsupportedProofAlgorithm)); + + Ok(()) +} + +#[tokio::test] +async fn stronghold_sign_bbs_works() -> anyhow::Result<()> { + let stronghold_storage = StrongholdStorage::new(create_stronghold_secret_manager()); + let JwkGenOutput { key_id, jwk, .. } = stronghold_storage + .generate_bbs(StrongholdKeyType::Bls12381G2.into(), ProofAlgorithm::BLS12381_SHA256) + .await?; + let pk = expand_bls_jwk(&jwk)?.1; + let sk = { + let stronghold = stronghold_storage.get_stronghold().await; + let client = get_client(&stronghold)?; + let sk_location = Location::Generic { + vault_path: IDENTITY_VAULT_PATH.as_bytes().to_owned(), + record_path: key_id.as_str().as_bytes().to_owned(), + }; + client + .get_guards([sk_location], |[sk]| Ok(BBSplusSecretKey::from_bytes(&sk.borrow()))) + .unwrap() + }?; + + let mut data = vec![0; 1024]; + rand::thread_rng().fill_bytes(&mut data); + let expected_signature = sign_bbs( + ProofAlgorithm::BLS12381_SHA256, + std::slice::from_ref(&data), + &sk, + &pk, + &[], + )?; + + let signature = stronghold_storage.sign_bbs(&key_id, &[data], &[], &jwk).await?; + assert_eq!(signature, expected_signature); + + Ok(()) +} diff --git a/identity_stronghold/src/tests/test_jwk_storage.rs b/identity_stronghold/src/tests/test_jwk_storage.rs index 61e25af808..e7ccbb2a05 100644 --- a/identity_stronghold/src/tests/test_jwk_storage.rs +++ b/identity_stronghold/src/tests/test_jwk_storage.rs @@ -33,7 +33,10 @@ async fn retrieve() { .unwrap(); let key_id = &generate.key_id; - let pub_key: Jwk = stronghold_storage.get_public_key(key_id).await.unwrap(); + let pub_key: Jwk = stronghold_storage + .get_public_key_with_type(key_id, crate::stronghold_key_type::StrongholdKeyType::Ed25519) + .await + .unwrap(); assert_eq!(generate.jwk, pub_key); } @@ -168,10 +171,10 @@ mod jwk_storage_tests { pub(crate) async fn test_incompatible_key_type(store: impl JwkStorage) { let mut ec_params = JwkParamsEc::new(); - ec_params.crv = EcCurve::P256.name().to_owned(); - ec_params.x = "".to_owned(); - ec_params.y = "".to_owned(); - ec_params.d = Some("".to_owned()); + ec_params.crv = EcCurve::P256.name().to_string(); + ec_params.x = String::new(); + ec_params.y = String::new(); + ec_params.d = Some(String::new()); let jwk_ec = Jwk::from_params(ec_params); let err = store.insert(jwk_ec).await.unwrap_err(); diff --git a/identity_stronghold/src/tests/utils.rs b/identity_stronghold/src/tests/utils.rs index 9fec954f0f..5113c95f28 100644 --- a/identity_stronghold/src/tests/utils.rs +++ b/identity_stronghold/src/tests/utils.rs @@ -28,7 +28,7 @@ pub(crate) fn encode_public_ed25519_jwk(public_key: &PublicKey) -> Jwk { let mut params = JwkParamsOkp::new(); params.x = x; params.d = None; - params.crv = EdCurve::Ed25519.name().to_owned(); + params.crv = EdCurve::Ed25519.name().to_string(); let mut jwk = Jwk::from_params(params); jwk.set_alg(JwsAlgorithm::EdDSA.name()); jwk diff --git a/identity_stronghold/src/utils.rs b/identity_stronghold/src/utils.rs new file mode 100644 index 0000000000..0bf83e1f18 --- /dev/null +++ b/identity_stronghold/src/utils.rs @@ -0,0 +1,87 @@ +// Copyright 2020-2024 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +use identity_storage::KeyId; +use identity_storage::KeyStorageError; +use identity_storage::KeyStorageErrorKind; +use identity_storage::KeyStorageResult; +use identity_verification::jws::JwsAlgorithm; +use iota_sdk::client::secret::SecretManager; +use iota_stronghold::Client; +use iota_stronghold::ClientError; +use iota_stronghold::Stronghold; +use rand::distributions::DistString as _; +use tokio::sync::MutexGuard; + +use crate::stronghold_key_type::StrongholdKeyType; + +pub static IDENTITY_VAULT_PATH: &str = "iota_identity_vault"; +pub static IDENTITY_CLIENT_PATH: &[u8] = b"iota_identity_client"; + +/// Generate a random alphanumeric string of len 32. +pub fn random_key_id() -> KeyId { + KeyId::new(rand::distributions::Alphanumeric.sample_string(&mut rand::thread_rng(), 32)) +} + +/// Check that the key type can be used with the algorithm. +pub fn check_key_alg_compatibility(key_type: StrongholdKeyType, alg: &JwsAlgorithm) -> KeyStorageResult<()> { + match (key_type, alg) { + (StrongholdKeyType::Ed25519, JwsAlgorithm::EdDSA) => Ok(()), + (key_type, alg) => Err( + KeyStorageError::new(identity_storage::KeyStorageErrorKind::KeyAlgorithmMismatch) + .with_custom_message(format!("cannot use key type `{key_type}` with algorithm `{alg}`")), + ), + } +} + +pub fn get_client(stronghold: &Stronghold) -> KeyStorageResult { + let client = stronghold.get_client(IDENTITY_CLIENT_PATH); + match client { + Ok(client) => Ok(client), + Err(ClientError::ClientDataNotPresent) => load_or_create_client(stronghold), + Err(err) => Err(KeyStorageError::new(KeyStorageErrorKind::Unspecified).with_source(err)), + } +} + +fn load_or_create_client(stronghold: &Stronghold) -> KeyStorageResult { + match stronghold.load_client(IDENTITY_CLIENT_PATH) { + Ok(client) => Ok(client), + Err(ClientError::ClientDataNotPresent) => stronghold + .create_client(IDENTITY_CLIENT_PATH) + .map_err(|err| KeyStorageError::new(KeyStorageErrorKind::Unspecified).with_source(err)), + Err(err) => Err(KeyStorageError::new(KeyStorageErrorKind::Unspecified).with_source(err)), + } +} + +pub async fn persist_changes( + secret_manager: &SecretManager, + stronghold: MutexGuard<'_, Stronghold>, +) -> KeyStorageResult<()> { + stronghold.write_client(IDENTITY_CLIENT_PATH).map_err(|err| { + KeyStorageError::new(KeyStorageErrorKind::Unspecified) + .with_custom_message("stronghold write client error") + .with_source(err) + })?; + // Must be dropped since `write_stronghold_snapshot` needs to acquire the stronghold lock. + drop(stronghold); + + match secret_manager { + iota_sdk::client::secret::SecretManager::Stronghold(stronghold_manager) => { + stronghold_manager + .write_stronghold_snapshot(None) + .await + .map_err(|err| { + KeyStorageError::new(KeyStorageErrorKind::Unspecified) + .with_custom_message("writing to stronghold snapshot failed") + .with_source(err) + })?; + } + _ => { + return Err( + KeyStorageError::new(KeyStorageErrorKind::Unspecified) + .with_custom_message("secret manager is not of type stronghold"), + ) + } + }; + Ok(()) +} diff --git a/identity_verification/Cargo.toml b/identity_verification/Cargo.toml index 812cfe43ec..46fcc5ac24 100644 --- a/identity_verification/Cargo.toml +++ b/identity_verification/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "identity_verification" -version = "1.2.0" +version = "1.4.0" authors.workspace = true edition.workspace = true homepage.workspace = true @@ -10,9 +10,9 @@ rust-version.workspace = true description = "Verification data types and functionality for identity.rs" [dependencies] -identity_core = { version = "=1.2.0", path = "./../identity_core", default-features = false } -identity_did = { version = "=1.2.0", path = "./../identity_did", default-features = false } -identity_jose = { version = "=1.2.0", path = "./../identity_jose", default-features = false } +identity_core = { version = "=1.4.0", path = "./../identity_core" } +identity_did = { version = "=1.4.0", path = "./../identity_did", default-features = false } +identity_jose = { version = "=1.4.0", path = "./../identity_jose", default-features = false } serde.workspace = true serde_json.workspace = true strum.workspace = true diff --git a/identity_verification/src/verification_method/method.rs b/identity_verification/src/verification_method/method.rs index 8c48e06893..084956c3a9 100644 --- a/identity_verification/src/verification_method/method.rs +++ b/identity_verification/src/verification_method/method.rs @@ -5,6 +5,7 @@ use core::fmt::Display; use core::fmt::Formatter; use std::borrow::Cow; +use identity_did::DIDJwk; use identity_jose::jwk::Jwk; use serde::de; use serde::Deserialize; @@ -220,7 +221,7 @@ impl VerificationMethod { MethodBuilder::default() .id(id) .controller(did.into()) - .type_(MethodType::JSON_WEB_KEY) + .type_(MethodType::JSON_WEB_KEY_2020) .data(MethodData::PublicKeyJwk(key)) .build() } @@ -247,6 +248,14 @@ impl KeyComparable for VerificationMethod { } } +impl TryFrom for VerificationMethod { + type Error = Error; + fn try_from(did: DIDJwk) -> Result { + let jwk = did.jwk(); + Self::new_from_jwk(did, jwk, Some("0")) + } +} + // Horrible workaround for a tracked serde issue https://github.com/serde-rs/serde/issues/2200. Serde doesn't "consume" // the input when deserializing flattened enums (MethodData in this case) causing duplication of data (in this case // it ends up in the properties object). This workaround simply removes the duplication. diff --git a/identity_verification/src/verification_method/method_type.rs b/identity_verification/src/verification_method/method_type.rs index ae3877948d..5a3eadd4f1 100644 --- a/identity_verification/src/verification_method/method_type.rs +++ b/identity_verification/src/verification_method/method_type.rs @@ -12,6 +12,7 @@ use crate::error::Result; const ED25519_VERIFICATION_KEY_2018_STR: &str = "Ed25519VerificationKey2018"; const X25519_KEY_AGREEMENT_KEY_2019_STR: &str = "X25519KeyAgreementKey2019"; const JSON_WEB_KEY_METHOD_TYPE: &str = "JsonWebKey"; +const JSON_WEB_KEY_2020_STR: &str = "JsonWebKey2020"; /// verification method types. #[derive(Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord, Deserialize, Serialize)] @@ -24,7 +25,11 @@ impl MethodType { pub const X25519_KEY_AGREEMENT_KEY_2019: Self = Self(Cow::Borrowed(X25519_KEY_AGREEMENT_KEY_2019_STR)); /// A verification method for use with JWT verification as prescribed by the [`Jwk`](::identity_jose::jwk::Jwk) /// in the [`publicKeyJwk`](crate::MethodData::PublicKeyJwk) entry. + #[deprecated(since = "1.3.0", note = "use JSON_WEB_KEY_2020 instead")] pub const JSON_WEB_KEY: Self = Self(Cow::Borrowed(JSON_WEB_KEY_METHOD_TYPE)); + /// A verification method for use with JWT verification as prescribed by the [`Jwk`](::identity_jose::jwk::Jwk) + /// in the [`publicKeyJwk`](crate::MethodData::PublicKeyJwk) entry. + pub const JSON_WEB_KEY_2020: Self = Self(Cow::Borrowed(JSON_WEB_KEY_2020_STR)); /// Construct a custom method type. pub fn custom(type_: impl AsRef) -> Self { Self(Cow::Owned(type_.as_ref().to_owned())) @@ -57,7 +62,11 @@ impl FromStr for MethodType { match string { ED25519_VERIFICATION_KEY_2018_STR => Ok(Self::ED25519_VERIFICATION_KEY_2018), X25519_KEY_AGREEMENT_KEY_2019_STR => Ok(Self::X25519_KEY_AGREEMENT_KEY_2019), - JSON_WEB_KEY_METHOD_TYPE => Ok(Self::JSON_WEB_KEY), + JSON_WEB_KEY_METHOD_TYPE => Ok( + #[allow(deprecated)] + Self::JSON_WEB_KEY, + ), + JSON_WEB_KEY_2020_STR => Ok(Self::JSON_WEB_KEY_2020), _ => Ok(Self(Cow::Owned(string.to_owned()))), } } @@ -74,6 +83,7 @@ mod tests { for method_type in [ MethodType::ED25519_VERIFICATION_KEY_2018, MethodType::X25519_KEY_AGREEMENT_KEY_2019, + MethodType::JSON_WEB_KEY_2020, ] { let ser: Value = serde_json::to_value(method_type.clone()).unwrap(); assert_eq!(ser.as_str().unwrap(), method_type.as_str());