From 6fa9da9c5491ac50b54bc2f79ded6a40bd88ec34 Mon Sep 17 00:00:00 2001 From: Frank Hinek Date: Wed, 16 Aug 2023 13:38:07 -0400 Subject: [PATCH] Merge agent refactor into main branch Signed-off-by: Frank Hinek --- .github/workflows/alpha-npm.yml | 12 +- .github/workflows/release-npm.yml | 18 +- CONTRIBUTING.md | 6 +- README.md | 12 +- codecov.yml | 30 +- package-lock.json | 15814 ++++++++-------- package.json | 43 +- packages/{web5-agent => agent}/.c8rc.json | 6 +- packages/{web5-agent => agent}/.mocharc.json | 0 .../{web5-agent => agent}/.vscode/launch.json | 0 .../{web5-agent => agent}/.vscode/tasks.json | 2 +- .../build/bundles.js | 2 +- .../build/esbuild-browser-config.cjs | 4 +- packages/agent/karma.conf.cjs | 86 + .../{web5-user-agent => agent}/package.json | 51 +- packages/agent/src/app-data-store.ts | 374 + packages/agent/src/did-manager.ts | 371 + packages/agent/src/dwn-manager.ts | 519 + packages/agent/src/identity-manager.ts | 165 + packages/agent/src/index.ts | 17 + .../{web5-agent => agent}/src/json-rpc.ts | 0 packages/agent/src/key-manager.ts | 302 + .../src/kms-local => agent/src}/kms-local.ts | 236 +- packages/agent/src/rpc-client.ts | 110 + packages/agent/src/store-managed-did.ts | 299 + packages/agent/src/store-managed-identity.ts | 242 + packages/agent/src/store-managed-key.ts | 739 + packages/agent/src/test-managed-agent.ts | 243 + packages/agent/src/types/agent.ts | 147 + packages/agent/src/types/managed-key.ts | 442 + packages/agent/src/utils.ts | 190 + packages/agent/tests/app-data-vault.spec.ts | 139 + packages/agent/tests/did-manager.spec.ts | 815 + packages/agent/tests/dwn-manager.spec.ts | 431 + packages/agent/tests/identity-manager.spec.ts | 772 + .../tests/key-manager.spec.ts | 567 +- .../{crypto => agent}/tests/kms-local.spec.ts | 643 +- .../agent/tests/store-managed-key.spec.ts | 1396 ++ .../{web5-agent => agent}/tests/tsconfig.json | 0 packages/agent/tests/utils/test-agent.ts | 191 + packages/{web5 => agent}/tsconfig.cjs.json | 3 +- packages/{web5-agent => agent}/tsconfig.json | 3 +- packages/{web5-proxy-agent => api}/.c8rc.json | 6 +- .../{web5-proxy-agent => api}/.mocharc.json | 0 packages/{web5 => api}/.vscode/launch.json | 0 packages/{web5 => api}/.vscode/tasks.json | 2 +- packages/{web5-agent => api}/LICENSE | 0 packages/api/README.md | 440 + packages/{web5 => api}/build/bundles.js | 0 .../build/esbuild-browser-config.cjs | 4 +- packages/{web5 => api}/karma.conf.cjs | 0 packages/{web5 => api}/package.json | 36 +- packages/api/src/did-api.ts | 101 + .../{web5 => api}/src/did-resolution-cache.ts | 2 +- packages/api/src/dwn-api.ts | 370 + packages/api/src/index.ts | 1 + packages/api/src/protocol.ts | 40 + packages/{web5 => api}/src/record.ts | 67 +- packages/api/src/tech-preview.ts | 55 + packages/api/src/utils.ts | 27 + packages/api/src/vc-api.ts | 16 + packages/api/src/web5.ts | 161 + .../tests-old}/did-resolution-cache.spec.ts | 0 .../{web5 => api}/tests/chai-plugins.d.ts | 0 .../fixtures/protocol-definitions/email.json | 48 + packages/api/tests/record.spec.ts | 1015 + packages/api/tests/tech-preview.spec.ts | 162 + .../tests/tsconfig.json | 0 packages/api/tests/utils.spec.ts | 58 + .../tests/utils}/chai-plugins.ts | 0 .../api/tests/utils/test-data-generator.ts | 71 + packages/api/tests/utils/test-user-agent.ts | 196 + packages/api/tests/web5-did.spec.ts | 38 + packages/api/tests/web5-dwn.spec.ts | 418 + packages/api/tests/web5-vc.spec.ts | 50 + packages/api/tests/web5.spec.ts | 42 + packages/api/tsconfig.cjs.json | 19 + packages/{web5 => api}/tsconfig.json | 8 +- packages/common/.c8rc.json | 6 +- packages/common/build/cjs-bundle.js | 4 +- .../common/build/esbuild-browser-config.cjs | 4 +- packages/common/package.json | 22 +- packages/common/src/convert.ts | 18 +- packages/common/src/{main.ts => index.ts} | 1 + packages/common/src/multicodec.ts | 60 +- packages/common/src/object.ts | 43 + packages/common/src/stores.ts | 37 +- packages/common/tests/convert.spec.ts | 45 + packages/common/tests/multicodec.spec.ts | 39 +- packages/common/tests/object.spec.ts | 88 + packages/common/tests/stores.spec.ts | 4 +- packages/common/tests/type-utils.spec.ts | 4 + packages/common/tsconfig.json | 3 + packages/credentials/.c8rc.json | 6 +- packages/credentials/.vscode/launch.json | 4 +- packages/credentials/build/bundles.js | 2 +- .../build/esbuild-browser-config.cjs | 4 +- packages/credentials/package.json | 19 +- .../credentials/src/{main.ts => index.ts} | 0 packages/credentials/tests/example.spec.ts | 2 +- packages/credentials/tsconfig.json | 3 + packages/crypto/.c8rc.json | 9 +- packages/crypto/.mocharc.json | 8 +- packages/crypto/.vscode/launch.json | 4 +- .../crypto/build/esbuild-browser-config.cjs | 4 +- packages/crypto/package.json | 24 +- .../crypto/src/algorithms-api/aes/base.ts | 10 +- packages/crypto/src/algorithms-api/aes/ctr.ts | 12 +- .../src/algorithms-api/crypto-algorithm.ts | 28 +- .../crypto/src/algorithms-api/crypto-key.ts | 12 +- packages/crypto/src/algorithms-api/ec/base.ts | 8 +- packages/crypto/src/algorithms-api/ec/ecdh.ts | 6 +- .../crypto/src/algorithms-api/ec/ecdsa.ts | 10 +- .../crypto/src/algorithms-api/ec/eddsa.ts | 8 +- .../crypto/src/crypto-algorithms/aes-ctr.ts | 18 +- packages/crypto/src/crypto-algorithms/ecdh.ts | 21 +- .../crypto/src/crypto-algorithms/ecdsa.ts | 23 +- .../crypto/src/crypto-algorithms/eddsa.ts | 23 +- .../crypto/src/crypto-primitives/aes-ctr.ts | 46 +- .../crypto/src/crypto-primitives/aes-gcm.ts | 52 +- .../src/crypto-primitives/concat-kdf.ts | 23 +- .../crypto/src/crypto-primitives/ed25519.ts | 150 +- .../crypto/src/crypto-primitives/index.ts | 3 +- .../crypto/src/crypto-primitives/secp256k1.ts | 220 +- .../crypto/src/crypto-primitives/x25519.ts | 58 +- .../crypto-primitives/xchacha20-poly1305.ts | 46 + .../crypto/src/crypto-primitives/xchacha20.ts | 39 +- packages/crypto/src/ed25519.ts | 87 - packages/crypto/src/index.ts | 8 + packages/crypto/src/jose.ts | 938 + packages/crypto/src/key-manager/index.ts | 2 - .../crypto/src/key-manager/key-manager.ts | 219 - packages/crypto/src/key-manager/key-store.ts | 52 - packages/crypto/src/kms-local/index.ts | 2 - packages/crypto/src/kms-local/key-store.ts | 109 - .../src/kms-local/supported-algorithms.ts | 22 - packages/crypto/src/main.ts | 11 - packages/crypto/src/types.ts | 47 - packages/crypto/src/types/crypto-key.ts | 184 +- packages/crypto/src/types/crypto-manager.ts | 233 - packages/crypto/src/types/index.ts | 3 - packages/crypto/src/types/web5-crypto.ts | 22 +- .../crypto/src/{utils-new.ts => utils.ts} | 76 +- packages/crypto/tests/algorithms-api.spec.ts | 128 +- .../crypto/tests/crypto-algorithms.spec.ts | 102 +- .../crypto/tests/crypto-primitives.spec.ts | 930 +- .../tests/fixtures/test-vectors/ed25519.ts | 20 + .../tests/fixtures/test-vectors/jose.ts | 428 + .../tests/fixtures/test-vectors/secp256k1.ts | 46 + packages/crypto/tests/jose.spec.ts | 236 + packages/crypto/tests/utils-new.spec.ts | 60 - packages/crypto/tests/utils.spec.ts | 187 + packages/crypto/tsconfig.cjs.json | 3 +- packages/crypto/tsconfig.json | 3 + packages/dids/.c8rc.json | 6 +- .../dids/build/esbuild-browser-config.cjs | 21 +- packages/dids/package.json | 33 +- packages/dids/src/did-ion.old | 187 + packages/dids/src/did-ion.ts | 641 +- packages/dids/src/did-key.ts | 800 +- packages/dids/src/did-resolver.ts | 96 +- packages/dids/src/{main.ts => index.ts} | 1 - .../dids/src/{nop-cache.ts => noop-cache.ts} | 12 +- packages/dids/src/types.ts | 300 +- packages/dids/src/utils.ts | 152 +- packages/dids/tests/did-ion.spec.ts | 676 +- packages/dids/tests/did-key.spec.ts | 115 +- packages/dids/tests/did-resolver.spec.ts | 23 + .../tests/fixtures/test-vectors/did-ion.ts | 448 + .../tests/fixtures/test-vectors/did-key.ts | 318 + .../fixtures/test-vectors/did-resolver.ts | 38 + .../tests/fixtures/test-vectors/did-utils.ts | 253 + ...{tech-preview.spec.ts => tech-preview.old} | 12 +- packages/dids/tests/tsconfig.json | 1 + packages/dids/tests/utils.spec.ts | 88 +- packages/dids/tsconfig.cjs.json | 6 +- packages/dids/tsconfig.json | 3 +- .../decentralized-identity__ion-pow-sdk.d.ts | 7 + .../decentralized-identity__ion-tools.d.ts | 1 - packages/{web5 => identity-agent}/.c8rc.json | 6 +- .../{web5 => identity-agent}/.mocharc.json | 0 .../.vscode/launch.json | 0 packages/identity-agent/.vscode/tasks.json | 37 + packages/identity-agent/build/bundles.js | 16 + .../build/esbuild-browser-config.cjs | 30 + packages/identity-agent/karma.conf.cjs | 86 + .../package.json | 38 +- packages/identity-agent/src/identity-agent.ts | 238 + packages/identity-agent/src/index.ts | 1 + .../tests/identity-agent.spec.ts | 177 + .../tests/managing-identities.spec.ts | 222 + .../tests/tsconfig.json | 0 .../tsconfig.cjs.json | 0 .../tsconfig.json | 7 +- .../old/{ => pre-polyrepo}/build/bundles.cjs | 0 .../build/esbuild-browser-config.cjs | 0 .../build/publish-unstable.sh | 0 .../old/{ => pre-polyrepo}/examples/README.md | 0 .../examples/simple-agent/.gitignore | 0 .../examples/simple-agent/README.md | 0 .../examples/simple-agent/etc/did.json | 0 .../examples/simple-agent/package-lock.json | 0 .../examples/simple-agent/package.json | 0 .../simple-agent/resources/test-protocol.json | 0 .../examples/simple-agent/src/index.js | 0 .../examples/simple-agent/src/utils.js | 0 .../desktop-agent-original.html | 0 .../test-dashboard/desktop-agent.html | 0 .../examples/test-dashboard/index.js | 0 .../examples/test-dashboard/package-lock.json | 0 .../examples/test-dashboard/package.json | 0 .../examples/test-dashboard/simple-agent.html | 0 packages/old/{ => pre-polyrepo}/jsdoc.json | 0 .../old/{ => pre-polyrepo}/karma.conf.cjs | 0 .../old/{ => pre-polyrepo}/package-lock.json | 0 packages/old/{ => pre-polyrepo}/package.json | 0 .../src/did/connect/connect.js | 0 .../src/did/connect/utils.js | 0 .../src/did/connect/ws-client.js | 0 .../src/did/crypto/ciphers.js | 0 .../did/crypto/x25519-xsalsa20-poly1305.js | 0 .../old/{ => pre-polyrepo}/src/did/manager.js | 0 .../{ => pre-polyrepo}/src/did/methods/ion.js | 0 .../{ => pre-polyrepo}/src/did/methods/key.js | 0 .../src/did/methods/methods.js | 0 .../old/{ => pre-polyrepo}/src/did/utils.js | 0 .../{ => pre-polyrepo}/src/did/web5-did.js | 0 .../{ => pre-polyrepo}/src/dwn/dwn-utils.js | 0 .../src/dwn/interfaces/interface.js | 0 .../src/dwn/interfaces/permissions.js | 0 .../src/dwn/interfaces/protocols.js | 0 .../src/dwn/interfaces/records.js | 0 .../src/dwn/models/record.js | 0 .../{ => pre-polyrepo}/src/dwn/web5-dwn.js | 0 packages/old/{ => pre-polyrepo}/src/main.js | 0 .../src/storage/local-storage.js | 0 .../src/storage/memory-storage.js | 0 .../{ => pre-polyrepo}/src/storage/storage.js | 0 .../src/transport/app-transport.js | 0 .../src/transport/http-transport.js | 0 .../src/transport/transport.js | 0 packages/old/{ => pre-polyrepo}/src/types.js | 0 packages/old/{ => pre-polyrepo}/src/utils.js | 0 packages/old/{ => pre-polyrepo}/src/web5.js | 0 .../tests/did/manager.spec.js | 0 .../tests/did/methods/ion.spec.js | 0 .../tests/did/methods/key.spec.js | 0 .../tests/did/utils.spec.js | 0 .../tests/did/web5-did.spec.js | 0 .../tests/dwn/interfaces/records.spec.js | 0 .../tests/dwn/models/record.spec.js | 0 .../tests/fixtures/did-documents.js | 0 .../tests/storage/memory-storage.spec.js | 0 .../tests/test-utils/promises.js | 0 .../tests/test-utils/test-data-generator.js | 0 .../tests/test-utils/test-dwn.js | 0 .../{ => pre-polyrepo}/tests/utils.spec.js | 0 .../web5-agent}/.c8rc.json | 8 +- packages/old/web5-agent/.mocharc.json | 5 + packages/old/web5-agent/.vscode/launch.json | 22 + packages/old/web5-agent/.vscode/tasks.json | 45 + .../web5-agent}/LICENSE | 0 packages/{ => old}/web5-agent/README.md | 0 packages/old/web5-agent/build/bundles.js | 55 + .../build/esbuild-browser-config.cjs | 19 + packages/{ => old}/web5-agent/karma.conf.cjs | 0 packages/old/web5-agent/package.json | 117 + packages/old/web5-agent/src/json-rpc.ts | 107 + packages/{ => old}/web5-agent/src/main.ts | 0 .../{ => old}/web5-agent/src/query-store.ts | 0 .../{ => old}/web5-agent/src/web5-agent.ts | 2 +- .../{ => old}/web5-agent/tests/needed.spec.ts | 0 packages/old/web5-agent/tsconfig.json | 28 + packages/old/web5-agent/tsconfig.test.json | 29 + packages/old/web5-proxy-agent/.c8rc.json | 18 + packages/old/web5-proxy-agent/.mocharc.json | 5 + .../old/web5-proxy-agent/.vscode/launch.json | 22 + .../old/web5-proxy-agent/.vscode/tasks.json | 45 + .../web5-proxy-agent}/LICENSE | 0 packages/{ => old}/web5-proxy-agent/README.md | 0 .../old/web5-proxy-agent/build/bundles.js | 55 + .../build/esbuild-browser-config.cjs | 5 + .../{ => old}/web5-proxy-agent/karma.conf.cjs | 0 packages/old/web5-proxy-agent/package.json | 116 + .../{ => old}/web5-proxy-agent/src/main.ts | 0 .../web5-proxy-agent/src/web5-proxy-agent.ts | 0 .../web5-proxy-agent/tests/needed.spec.ts | 0 packages/old/web5-proxy-agent/tsconfig.json | 28 + .../old/web5-proxy-agent/tsconfig.test.json | 29 + packages/old/web5-user-agent/.c8rc.json | 18 + packages/{ => old}/web5-user-agent/.gitignore | 0 packages/old/web5-user-agent/.mocharc.json | 5 + .../old/web5-user-agent/.vscode/launch.json | 22 + .../old/web5-user-agent/.vscode/tasks.json | 45 + .../{web5 => old/web5-user-agent}/LICENSE | 0 packages/{ => old}/web5-user-agent/README.md | 0 packages/old/web5-user-agent/build/bundles.js | 62 + .../build/esbuild-browser-config.cjs | 19 + .../{ => old}/web5-user-agent/karma.conf.cjs | 0 packages/old/web5-user-agent/package.json | 126 + .../old/web5-user-agent/playwright.config.ts | 77 + .../web5-user-agent/src/dwn-rpc-client.ts | 10 +- .../{ => old}/web5-user-agent/src/main.ts | 3 +- .../web5-user-agent/src/profile-api.ts | 0 .../web5-user-agent/src/profile-index.ts | 0 .../web5-user-agent/src/profile-manager.ts | 0 .../web5-user-agent/src/profile-store.ts | 0 .../{ => old}/web5-user-agent/src/sync-api.ts | 128 +- .../web5-user-agent/src/sync-manager.ts | 0 .../{ => old}/web5-user-agent/src/utils.ts | 0 .../web5-user-agent/src/web5-user-agent.ts | 38 +- .../web5-user-agent/tests/browser/index.html | 55 + .../web5-user-agent/tests/browser/test.jpeg | Bin 0 -> 234053 bytes .../tests/browser/web5-user-agent.spec.ts | 14 + .../tests/common/utils/test-user-agent.ts | 5 +- .../tests/common/web5-user-agent.spec.ts | 0 .../fixtures/protocol-definitions/email.json | 0 .../protocol-definitions/message.json | 0 .../tests/fixtures/test-profiles.ts | 0 .../tests/node/web5-user-agent.spec.ts | 0 packages/old/web5-user-agent/tsconfig.json | 27 + .../old/web5-user-agent/tsconfig.test.json | 32 + packages/old/web5/.c8rc.json | 18 + packages/old/web5/.mocharc.json | 5 + packages/old/web5/.vscode/launch.json | 29 + packages/old/web5/.vscode/tasks.json | 45 + packages/old/web5/LICENSE | 201 + packages/{ => old}/web5/README.md | 0 packages/old/web5/build/bundles.js | 55 + .../old/web5/build/esbuild-browser-config.cjs | 19 + packages/{ => old}/web5/examples/in-page.html | 0 packages/old/web5/karma.conf.cjs | 87 + packages/old/web5/package.json | 130 + packages/{ => old}/web5/src/app-storage.ts | 0 packages/{ => old}/web5/src/did-api.ts | 0 packages/old/web5/src/did-resolution-cache.ts | 77 + packages/{ => old}/web5/src/dwn-api.ts | 0 packages/old/web5/src/main.ts | 1 + packages/{ => old}/web5/src/protocol.ts | 22 +- packages/old/web5/src/record.ts | 350 + packages/{ => old}/web5/src/utils.ts | 0 packages/{ => old}/web5/src/vc-api.ts | 8 +- packages/{ => old}/web5/src/web5.ts | 23 +- packages/old/web5/tests/chai-plugins.d.ts | 14 + .../web5/tests/did-resolution-cache.spec.ts | 99 + .../web5/tests/fixtures/did-documents.js | 0 .../fixtures/protocol-definitions/email.json | 1 - .../protocol-definitions/message.json | 0 .../web5/tests/fixtures/test-profiles.ts | 0 packages/{ => old}/web5/tests/record.spec.ts | 12 +- .../{ => old}/web5/tests/tech-preview.spec.ts | 3 + .../old/web5/tests/test-utils/chai-plugins.ts | 59 + .../web5/tests/test-utils/promises.ts | 0 .../tests/test-utils/test-data-generator.ts | 0 .../web5/tests/test-utils/test-user-agent.ts | 3 +- .../{ => old}/web5/tests/web5-did.spec.ts | 0 .../{ => old}/web5/tests/web5-dwn.spec.ts | 1 - packages/{ => old}/web5/tests/web5-vc.spec.ts | 0 packages/old/web5/tsconfig.json | 28 + packages/old/web5/tsconfig.test.json | 29 + packages/proxy-agent/.c8rc.json | 19 + .../.mocharc.json | 2 +- .../.vscode/launch.json | 4 +- .../.vscode/tasks.json | 2 +- .../build/bundles.js | 2 +- .../build/esbuild-browser-config.cjs | 30 + packages/proxy-agent/karma.conf.cjs | 86 + .../package.json | 36 +- packages/proxy-agent/src/index.ts | 1 + packages/proxy-agent/src/proxy-agent.ts | 240 + .../proxy-agent/tests/proxy-agent.spec.ts | 87 + .../{web5 => proxy-agent}/tests/tsconfig.json | 0 .../tsconfig.cjs.json | 0 .../tsconfig.json | 6 +- packages/user-agent/.c8rc.json | 19 + packages/user-agent/.mocharc.json | 5 + packages/user-agent/.vscode/launch.json | 19 + .../.vscode/tasks.json | 2 +- .../build/bundles.js | 2 +- .../build/esbuild-browser-config.cjs | 30 + packages/user-agent/karma.conf.cjs | 86 + packages/user-agent/package.json | 103 + packages/user-agent/src/index.ts | 1 + packages/user-agent/src/user-agent.ts | 240 + packages/user-agent/tests/tsconfig.json | 15 + packages/user-agent/tests/user-agent.spec.ts | 87 + .../tsconfig.cjs.json | 0 packages/user-agent/tsconfig.json | 27 + .../build/esbuild-browser-config.cjs | 13 - packages/web5/src/main.ts | 4 - web5-js.code-workspace | 210 +- 391 files changed, 31994 insertions(+), 11581 deletions(-) rename packages/{web5-agent => agent}/.c8rc.json (66%) rename packages/{web5-agent => agent}/.mocharc.json (100%) rename packages/{web5-agent => agent}/.vscode/launch.json (100%) rename packages/{web5-agent => agent}/.vscode/tasks.json (93%) rename packages/{web5-user-agent => agent}/build/bundles.js (92%) rename packages/{web5-user-agent => agent}/build/esbuild-browser-config.cjs (96%) create mode 100644 packages/agent/karma.conf.cjs rename packages/{web5-user-agent => agent}/package.json (72%) create mode 100644 packages/agent/src/app-data-store.ts create mode 100644 packages/agent/src/did-manager.ts create mode 100644 packages/agent/src/dwn-manager.ts create mode 100644 packages/agent/src/identity-manager.ts create mode 100644 packages/agent/src/index.ts rename packages/{web5-agent => agent}/src/json-rpc.ts (100%) create mode 100644 packages/agent/src/key-manager.ts rename packages/{crypto/src/kms-local => agent/src}/kms-local.ts (54%) create mode 100644 packages/agent/src/rpc-client.ts create mode 100644 packages/agent/src/store-managed-did.ts create mode 100644 packages/agent/src/store-managed-identity.ts create mode 100644 packages/agent/src/store-managed-key.ts create mode 100644 packages/agent/src/test-managed-agent.ts create mode 100644 packages/agent/src/types/agent.ts create mode 100644 packages/agent/src/types/managed-key.ts create mode 100644 packages/agent/src/utils.ts create mode 100644 packages/agent/tests/app-data-vault.spec.ts create mode 100644 packages/agent/tests/did-manager.spec.ts create mode 100644 packages/agent/tests/dwn-manager.spec.ts create mode 100644 packages/agent/tests/identity-manager.spec.ts rename packages/{crypto => agent}/tests/key-manager.spec.ts (68%) rename packages/{crypto => agent}/tests/kms-local.spec.ts (63%) create mode 100644 packages/agent/tests/store-managed-key.spec.ts rename packages/{web5-agent => agent}/tests/tsconfig.json (100%) create mode 100644 packages/agent/tests/utils/test-agent.ts rename packages/{web5 => agent}/tsconfig.cjs.json (81%) rename packages/{web5-agent => agent}/tsconfig.json (95%) rename packages/{web5-proxy-agent => api}/.c8rc.json (66%) rename packages/{web5-proxy-agent => api}/.mocharc.json (100%) rename packages/{web5 => api}/.vscode/launch.json (100%) rename packages/{web5 => api}/.vscode/tasks.json (94%) rename packages/{web5-agent => api}/LICENSE (100%) create mode 100644 packages/api/README.md rename packages/{web5 => api}/build/bundles.js (100%) rename packages/{web5 => api}/build/esbuild-browser-config.cjs (96%) rename packages/{web5 => api}/karma.conf.cjs (100%) rename packages/{web5 => api}/package.json (81%) create mode 100644 packages/api/src/did-api.ts rename packages/{web5 => api}/src/did-resolution-cache.ts (96%) create mode 100644 packages/api/src/dwn-api.ts create mode 100644 packages/api/src/index.ts create mode 100644 packages/api/src/protocol.ts rename packages/{web5 => api}/src/record.ts (83%) create mode 100644 packages/api/src/tech-preview.ts create mode 100644 packages/api/src/utils.ts create mode 100644 packages/api/src/vc-api.ts create mode 100644 packages/api/src/web5.ts rename packages/{web5/tests => api/tests-old}/did-resolution-cache.spec.ts (100%) rename packages/{web5 => api}/tests/chai-plugins.d.ts (100%) create mode 100644 packages/api/tests/fixtures/protocol-definitions/email.json create mode 100644 packages/api/tests/record.spec.ts create mode 100644 packages/api/tests/tech-preview.spec.ts rename packages/{web5-proxy-agent => api}/tests/tsconfig.json (100%) create mode 100644 packages/api/tests/utils.spec.ts rename packages/{web5/tests/test-utils => api/tests/utils}/chai-plugins.ts (100%) create mode 100644 packages/api/tests/utils/test-data-generator.ts create mode 100644 packages/api/tests/utils/test-user-agent.ts create mode 100644 packages/api/tests/web5-did.spec.ts create mode 100644 packages/api/tests/web5-dwn.spec.ts create mode 100644 packages/api/tests/web5-vc.spec.ts create mode 100644 packages/api/tests/web5.spec.ts create mode 100644 packages/api/tsconfig.cjs.json rename packages/{web5 => api}/tsconfig.json (55%) rename packages/common/src/{main.ts => index.ts} (84%) create mode 100644 packages/common/src/object.ts create mode 100644 packages/common/tests/object.spec.ts rename packages/credentials/src/{main.ts => index.ts} (100%) create mode 100644 packages/crypto/src/crypto-primitives/xchacha20-poly1305.ts delete mode 100644 packages/crypto/src/ed25519.ts create mode 100644 packages/crypto/src/index.ts create mode 100644 packages/crypto/src/jose.ts delete mode 100644 packages/crypto/src/key-manager/index.ts delete mode 100644 packages/crypto/src/key-manager/key-manager.ts delete mode 100644 packages/crypto/src/key-manager/key-store.ts delete mode 100644 packages/crypto/src/kms-local/index.ts delete mode 100644 packages/crypto/src/kms-local/key-store.ts delete mode 100644 packages/crypto/src/kms-local/supported-algorithms.ts delete mode 100644 packages/crypto/src/main.ts delete mode 100644 packages/crypto/src/types.ts delete mode 100644 packages/crypto/src/types/crypto-manager.ts delete mode 100644 packages/crypto/src/types/index.ts rename packages/crypto/src/{utils-new.ts => utils.ts} (58%) create mode 100644 packages/crypto/tests/fixtures/test-vectors/ed25519.ts create mode 100644 packages/crypto/tests/fixtures/test-vectors/jose.ts create mode 100644 packages/crypto/tests/fixtures/test-vectors/secp256k1.ts create mode 100644 packages/crypto/tests/jose.spec.ts delete mode 100644 packages/crypto/tests/utils-new.spec.ts create mode 100644 packages/crypto/tests/utils.spec.ts create mode 100644 packages/dids/src/did-ion.old rename packages/dids/src/{main.ts => index.ts} (82%) rename packages/dids/src/{nop-cache.ts => noop-cache.ts} (78%) create mode 100644 packages/dids/tests/did-resolver.spec.ts create mode 100644 packages/dids/tests/fixtures/test-vectors/did-ion.ts create mode 100644 packages/dids/tests/fixtures/test-vectors/did-key.ts create mode 100644 packages/dids/tests/fixtures/test-vectors/did-resolver.ts create mode 100644 packages/dids/tests/fixtures/test-vectors/did-utils.ts rename packages/dids/tests/{tech-preview.spec.ts => tech-preview.old} (90%) create mode 100644 packages/dids/typings/decentralized-identity__ion-pow-sdk.d.ts delete mode 100644 packages/dids/typings/decentralized-identity__ion-tools.d.ts rename packages/{web5 => identity-agent}/.c8rc.json (66%) rename packages/{web5 => identity-agent}/.mocharc.json (100%) rename packages/{web5-user-agent => identity-agent}/.vscode/launch.json (100%) create mode 100644 packages/identity-agent/.vscode/tasks.json create mode 100644 packages/identity-agent/build/bundles.js create mode 100644 packages/identity-agent/build/esbuild-browser-config.cjs create mode 100644 packages/identity-agent/karma.conf.cjs rename packages/{web5-agent => identity-agent}/package.json (77%) create mode 100644 packages/identity-agent/src/identity-agent.ts create mode 100644 packages/identity-agent/src/index.ts create mode 100644 packages/identity-agent/tests/identity-agent.spec.ts create mode 100644 packages/identity-agent/tests/managing-identities.spec.ts rename packages/{web5-user-agent => identity-agent}/tests/tsconfig.json (100%) rename packages/{web5-agent => identity-agent}/tsconfig.cjs.json (100%) rename packages/{web5-user-agent => identity-agent}/tsconfig.json (87%) rename packages/old/{ => pre-polyrepo}/build/bundles.cjs (100%) rename packages/old/{ => pre-polyrepo}/build/esbuild-browser-config.cjs (100%) rename packages/old/{ => pre-polyrepo}/build/publish-unstable.sh (100%) rename packages/old/{ => pre-polyrepo}/examples/README.md (100%) rename packages/old/{ => pre-polyrepo}/examples/simple-agent/.gitignore (100%) rename packages/old/{ => pre-polyrepo}/examples/simple-agent/README.md (100%) rename packages/old/{ => pre-polyrepo}/examples/simple-agent/etc/did.json (100%) rename packages/old/{ => pre-polyrepo}/examples/simple-agent/package-lock.json (100%) rename packages/old/{ => pre-polyrepo}/examples/simple-agent/package.json (100%) rename packages/old/{ => pre-polyrepo}/examples/simple-agent/resources/test-protocol.json (100%) rename packages/old/{ => pre-polyrepo}/examples/simple-agent/src/index.js (100%) rename packages/old/{ => pre-polyrepo}/examples/simple-agent/src/utils.js (100%) rename packages/old/{ => pre-polyrepo}/examples/test-dashboard/desktop-agent-original.html (100%) rename packages/old/{ => pre-polyrepo}/examples/test-dashboard/desktop-agent.html (100%) rename packages/old/{ => pre-polyrepo}/examples/test-dashboard/index.js (100%) rename packages/old/{ => pre-polyrepo}/examples/test-dashboard/package-lock.json (100%) rename packages/old/{ => pre-polyrepo}/examples/test-dashboard/package.json (100%) rename packages/old/{ => pre-polyrepo}/examples/test-dashboard/simple-agent.html (100%) rename packages/old/{ => pre-polyrepo}/jsdoc.json (100%) rename packages/old/{ => pre-polyrepo}/karma.conf.cjs (100%) rename packages/old/{ => pre-polyrepo}/package-lock.json (100%) rename packages/old/{ => pre-polyrepo}/package.json (100%) rename packages/old/{ => pre-polyrepo}/src/did/connect/connect.js (100%) rename packages/old/{ => pre-polyrepo}/src/did/connect/utils.js (100%) rename packages/old/{ => pre-polyrepo}/src/did/connect/ws-client.js (100%) rename packages/old/{ => pre-polyrepo}/src/did/crypto/ciphers.js (100%) rename packages/old/{ => pre-polyrepo}/src/did/crypto/x25519-xsalsa20-poly1305.js (100%) rename packages/old/{ => pre-polyrepo}/src/did/manager.js (100%) rename packages/old/{ => pre-polyrepo}/src/did/methods/ion.js (100%) rename packages/old/{ => pre-polyrepo}/src/did/methods/key.js (100%) rename packages/old/{ => pre-polyrepo}/src/did/methods/methods.js (100%) rename packages/old/{ => pre-polyrepo}/src/did/utils.js (100%) rename packages/old/{ => pre-polyrepo}/src/did/web5-did.js (100%) rename packages/old/{ => pre-polyrepo}/src/dwn/dwn-utils.js (100%) rename packages/old/{ => pre-polyrepo}/src/dwn/interfaces/interface.js (100%) rename packages/old/{ => pre-polyrepo}/src/dwn/interfaces/permissions.js (100%) rename packages/old/{ => pre-polyrepo}/src/dwn/interfaces/protocols.js (100%) rename packages/old/{ => pre-polyrepo}/src/dwn/interfaces/records.js (100%) rename packages/old/{ => pre-polyrepo}/src/dwn/models/record.js (100%) rename packages/old/{ => pre-polyrepo}/src/dwn/web5-dwn.js (100%) rename packages/old/{ => pre-polyrepo}/src/main.js (100%) rename packages/old/{ => pre-polyrepo}/src/storage/local-storage.js (100%) rename packages/old/{ => pre-polyrepo}/src/storage/memory-storage.js (100%) rename packages/old/{ => pre-polyrepo}/src/storage/storage.js (100%) rename packages/old/{ => pre-polyrepo}/src/transport/app-transport.js (100%) rename packages/old/{ => pre-polyrepo}/src/transport/http-transport.js (100%) rename packages/old/{ => pre-polyrepo}/src/transport/transport.js (100%) rename packages/old/{ => pre-polyrepo}/src/types.js (100%) rename packages/old/{ => pre-polyrepo}/src/utils.js (100%) rename packages/old/{ => pre-polyrepo}/src/web5.js (100%) rename packages/old/{ => pre-polyrepo}/tests/did/manager.spec.js (100%) rename packages/old/{ => pre-polyrepo}/tests/did/methods/ion.spec.js (100%) rename packages/old/{ => pre-polyrepo}/tests/did/methods/key.spec.js (100%) rename packages/old/{ => pre-polyrepo}/tests/did/utils.spec.js (100%) rename packages/old/{ => pre-polyrepo}/tests/did/web5-did.spec.js (100%) rename packages/old/{ => pre-polyrepo}/tests/dwn/interfaces/records.spec.js (100%) rename packages/old/{ => pre-polyrepo}/tests/dwn/models/record.spec.js (100%) rename packages/old/{ => pre-polyrepo}/tests/fixtures/did-documents.js (100%) rename packages/old/{ => pre-polyrepo}/tests/storage/memory-storage.spec.js (100%) rename packages/old/{ => pre-polyrepo}/tests/test-utils/promises.js (100%) rename packages/old/{ => pre-polyrepo}/tests/test-utils/test-data-generator.js (100%) rename packages/old/{ => pre-polyrepo}/tests/test-utils/test-dwn.js (100%) rename packages/old/{ => pre-polyrepo}/tests/utils.spec.js (100%) rename packages/{web5-user-agent => old/web5-agent}/.c8rc.json (50%) create mode 100644 packages/old/web5-agent/.mocharc.json create mode 100644 packages/old/web5-agent/.vscode/launch.json create mode 100644 packages/old/web5-agent/.vscode/tasks.json rename packages/{web5-proxy-agent => old/web5-agent}/LICENSE (100%) rename packages/{ => old}/web5-agent/README.md (100%) create mode 100644 packages/old/web5-agent/build/bundles.js create mode 100644 packages/old/web5-agent/build/esbuild-browser-config.cjs rename packages/{ => old}/web5-agent/karma.conf.cjs (100%) create mode 100644 packages/old/web5-agent/package.json create mode 100644 packages/old/web5-agent/src/json-rpc.ts rename packages/{ => old}/web5-agent/src/main.ts (100%) rename packages/{ => old}/web5-agent/src/query-store.ts (100%) rename packages/{ => old}/web5-agent/src/web5-agent.ts (99%) rename packages/{ => old}/web5-agent/tests/needed.spec.ts (100%) create mode 100644 packages/old/web5-agent/tsconfig.json create mode 100644 packages/old/web5-agent/tsconfig.test.json create mode 100644 packages/old/web5-proxy-agent/.c8rc.json create mode 100644 packages/old/web5-proxy-agent/.mocharc.json create mode 100644 packages/old/web5-proxy-agent/.vscode/launch.json create mode 100644 packages/old/web5-proxy-agent/.vscode/tasks.json rename packages/{web5-user-agent => old/web5-proxy-agent}/LICENSE (100%) rename packages/{ => old}/web5-proxy-agent/README.md (100%) create mode 100644 packages/old/web5-proxy-agent/build/bundles.js rename packages/{ => old}/web5-proxy-agent/build/esbuild-browser-config.cjs (54%) rename packages/{ => old}/web5-proxy-agent/karma.conf.cjs (100%) create mode 100644 packages/old/web5-proxy-agent/package.json rename packages/{ => old}/web5-proxy-agent/src/main.ts (100%) rename packages/{ => old}/web5-proxy-agent/src/web5-proxy-agent.ts (100%) rename packages/{ => old}/web5-proxy-agent/tests/needed.spec.ts (100%) create mode 100644 packages/old/web5-proxy-agent/tsconfig.json create mode 100644 packages/old/web5-proxy-agent/tsconfig.test.json create mode 100644 packages/old/web5-user-agent/.c8rc.json rename packages/{ => old}/web5-user-agent/.gitignore (100%) create mode 100644 packages/old/web5-user-agent/.mocharc.json create mode 100644 packages/old/web5-user-agent/.vscode/launch.json create mode 100644 packages/old/web5-user-agent/.vscode/tasks.json rename packages/{web5 => old/web5-user-agent}/LICENSE (100%) rename packages/{ => old}/web5-user-agent/README.md (100%) create mode 100644 packages/old/web5-user-agent/build/bundles.js create mode 100644 packages/old/web5-user-agent/build/esbuild-browser-config.cjs rename packages/{ => old}/web5-user-agent/karma.conf.cjs (100%) create mode 100644 packages/old/web5-user-agent/package.json create mode 100644 packages/old/web5-user-agent/playwright.config.ts rename packages/{ => old}/web5-user-agent/src/dwn-rpc-client.ts (91%) rename packages/{ => old}/web5-user-agent/src/main.ts (75%) rename packages/{ => old}/web5-user-agent/src/profile-api.ts (100%) rename packages/{ => old}/web5-user-agent/src/profile-index.ts (100%) rename packages/{ => old}/web5-user-agent/src/profile-manager.ts (100%) rename packages/{ => old}/web5-user-agent/src/profile-store.ts (100%) rename packages/{ => old}/web5-user-agent/src/sync-api.ts (76%) rename packages/{ => old}/web5-user-agent/src/sync-manager.ts (100%) rename packages/{ => old}/web5-user-agent/src/utils.ts (100%) rename packages/{ => old}/web5-user-agent/src/web5-user-agent.ts (90%) create mode 100644 packages/old/web5-user-agent/tests/browser/index.html create mode 100644 packages/old/web5-user-agent/tests/browser/test.jpeg create mode 100644 packages/old/web5-user-agent/tests/browser/web5-user-agent.spec.ts rename packages/{ => old}/web5-user-agent/tests/common/utils/test-user-agent.ts (96%) rename packages/{ => old}/web5-user-agent/tests/common/web5-user-agent.spec.ts (100%) rename packages/{ => old}/web5-user-agent/tests/fixtures/protocol-definitions/email.json (100%) rename packages/{ => old}/web5-user-agent/tests/fixtures/protocol-definitions/message.json (100%) rename packages/{ => old}/web5-user-agent/tests/fixtures/test-profiles.ts (100%) rename packages/{ => old}/web5-user-agent/tests/node/web5-user-agent.spec.ts (100%) create mode 100644 packages/old/web5-user-agent/tsconfig.json create mode 100644 packages/old/web5-user-agent/tsconfig.test.json create mode 100644 packages/old/web5/.c8rc.json create mode 100644 packages/old/web5/.mocharc.json create mode 100644 packages/old/web5/.vscode/launch.json create mode 100644 packages/old/web5/.vscode/tasks.json create mode 100644 packages/old/web5/LICENSE rename packages/{ => old}/web5/README.md (100%) create mode 100644 packages/old/web5/build/bundles.js create mode 100644 packages/old/web5/build/esbuild-browser-config.cjs rename packages/{ => old}/web5/examples/in-page.html (100%) create mode 100644 packages/old/web5/karma.conf.cjs create mode 100644 packages/old/web5/package.json rename packages/{ => old}/web5/src/app-storage.ts (100%) rename packages/{ => old}/web5/src/did-api.ts (100%) create mode 100644 packages/old/web5/src/did-resolution-cache.ts rename packages/{ => old}/web5/src/dwn-api.ts (100%) create mode 100644 packages/old/web5/src/main.ts rename packages/{ => old}/web5/src/protocol.ts (56%) create mode 100644 packages/old/web5/src/record.ts rename packages/{ => old}/web5/src/utils.ts (100%) rename packages/{ => old}/web5/src/vc-api.ts (65%) rename packages/{ => old}/web5/src/web5.ts (88%) create mode 100644 packages/old/web5/tests/chai-plugins.d.ts create mode 100644 packages/old/web5/tests/did-resolution-cache.spec.ts rename packages/{ => old}/web5/tests/fixtures/did-documents.js (100%) rename packages/{ => old}/web5/tests/fixtures/protocol-definitions/email.json (97%) rename packages/{ => old}/web5/tests/fixtures/protocol-definitions/message.json (100%) rename packages/{ => old}/web5/tests/fixtures/test-profiles.ts (100%) rename packages/{ => old}/web5/tests/record.spec.ts (99%) rename packages/{ => old}/web5/tests/tech-preview.spec.ts (99%) create mode 100644 packages/old/web5/tests/test-utils/chai-plugins.ts rename packages/{ => old}/web5/tests/test-utils/promises.ts (100%) rename packages/{ => old}/web5/tests/test-utils/test-data-generator.ts (100%) rename packages/{ => old}/web5/tests/test-utils/test-user-agent.ts (96%) rename packages/{ => old}/web5/tests/web5-did.spec.ts (100%) rename packages/{ => old}/web5/tests/web5-dwn.spec.ts (99%) rename packages/{ => old}/web5/tests/web5-vc.spec.ts (100%) create mode 100644 packages/old/web5/tsconfig.json create mode 100644 packages/old/web5/tsconfig.test.json create mode 100644 packages/proxy-agent/.c8rc.json rename packages/{web5-user-agent => proxy-agent}/.mocharc.json (50%) rename packages/{web5-proxy-agent => proxy-agent}/.vscode/launch.json (89%) rename packages/{web5-user-agent => proxy-agent}/.vscode/tasks.json (92%) rename packages/{web5-agent => proxy-agent}/build/bundles.js (90%) create mode 100644 packages/proxy-agent/build/esbuild-browser-config.cjs create mode 100644 packages/proxy-agent/karma.conf.cjs rename packages/{web5-proxy-agent => proxy-agent}/package.json (79%) create mode 100644 packages/proxy-agent/src/index.ts create mode 100644 packages/proxy-agent/src/proxy-agent.ts create mode 100644 packages/proxy-agent/tests/proxy-agent.spec.ts rename packages/{web5 => proxy-agent}/tests/tsconfig.json (100%) rename packages/{web5-proxy-agent => proxy-agent}/tsconfig.cjs.json (100%) rename packages/{web5-proxy-agent => proxy-agent}/tsconfig.json (91%) create mode 100644 packages/user-agent/.c8rc.json create mode 100644 packages/user-agent/.mocharc.json create mode 100644 packages/user-agent/.vscode/launch.json rename packages/{web5-proxy-agent => user-agent}/.vscode/tasks.json (92%) rename packages/{web5-proxy-agent => user-agent}/build/bundles.js (91%) create mode 100644 packages/user-agent/build/esbuild-browser-config.cjs create mode 100644 packages/user-agent/karma.conf.cjs create mode 100644 packages/user-agent/package.json create mode 100644 packages/user-agent/src/index.ts create mode 100644 packages/user-agent/src/user-agent.ts create mode 100644 packages/user-agent/tests/tsconfig.json create mode 100644 packages/user-agent/tests/user-agent.spec.ts rename packages/{web5-user-agent => user-agent}/tsconfig.cjs.json (100%) create mode 100644 packages/user-agent/tsconfig.json delete mode 100644 packages/web5-agent/build/esbuild-browser-config.cjs delete mode 100644 packages/web5/src/main.ts diff --git a/.github/workflows/alpha-npm.yml b/.github/workflows/alpha-npm.yml index a9ce82365..189bc5ff1 100644 --- a/.github/workflows/alpha-npm.yml +++ b/.github/workflows/alpha-npm.yml @@ -23,13 +23,15 @@ jobs: matrix: package: [ + "agent", + "api", "common", + "credentials", "crypto", "dids", - "web5", - "web5-agent", - "web5-proxy-agent", - "web5-user-agent", + "identity-agent", + "proxy-agent", + "user-agent", ] steps: @@ -79,7 +81,7 @@ jobs: - name: Build all workspace packages run: npm run build - - name: Publish @tbd54566975/${{ matrix.package }}@${{ env.ALPHA_VERSION }} + - name: Publish @web5/${{ matrix.package }}@${{ env.ALPHA_VERSION }} env: NODE_AUTH_TOKEN: ${{secrets.npm_token}} run: | diff --git a/.github/workflows/release-npm.yml b/.github/workflows/release-npm.yml index 92c825a28..2fd864d67 100644 --- a/.github/workflows/release-npm.yml +++ b/.github/workflows/release-npm.yml @@ -24,13 +24,15 @@ jobs: matrix: package: [ + "agent", + "api", "common", + "credentials", "crypto", "dids", - "web5", - "web5-agent", - "web5-proxy-agent", - "web5-user-agent", + "identity-agent", + "proxy-agent", + "user-agent", ] steps: @@ -62,7 +64,7 @@ jobs: cd packages/${{ matrix.package }} # Fetch the published version on NPMjs.com. - PUBLISHED_VERSION=$(npm view @tbd54566975/${{ matrix.package }} version 2>/dev/null || echo "0.0.0") + PUBLISHED_VERSION=$(npm view @web5/${{ matrix.package }} version 2>/dev/null || echo "0.0.0") echo "Published Version: $PUBLISHED_VERSION" # Fetch the version in the GitHub repo's package.json file. @@ -73,10 +75,10 @@ jobs: # Compare the repo and NPMjs.com package versions. IS_GREATER=$(semver --range ">$PUBLISHED_VERSION" $REPO_VERSION || true) if [ -n "$IS_GREATER" ] ; then - echo "@tbd54566975/${{ matrix.package }}@$REPO_VERSION is latest" + echo "@web5/${{ matrix.package }}@$REPO_VERSION is latest" echo "IS_LATEST=true" >> $GITHUB_ENV else - echo "@tbd54566975/${{ matrix.package }}@$REPO_VERSION is already published or repo version is lower" + echo "@web5/${{ matrix.package }}@$REPO_VERSION is already published or repo version is lower" echo "IS_LATEST=false" >> $GITHUB_ENV fi shell: bash @@ -89,7 +91,7 @@ jobs: if: env.IS_LATEST == 'true' run: npm run build - - name: Publish @tbd54566975/${{ matrix.package }}@${{ env.REPO_VERSION }} + - name: Publish @web5/${{ matrix.package }}@${{ env.REPO_VERSION }} if: env.IS_LATEST == 'true' env: NODE_AUTH_TOKEN: ${{secrets.npm_token}} diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 92d30f9a3..6454c9c23 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -172,7 +172,7 @@ After one or more PRs have been approved and merged by project maintainers, a Gi version tag. The act of creating the GitHub release triggers automated publication of the package to the [NPM Registry](https://npmjs.com) which will be tagged as _latest_. -The next time someone runs `npm install @tbd54566975/` the newly published release will be installed. +The next time someone runs `npm install @web5/` the newly published release will be installed. #### Alpha Releases @@ -198,5 +198,5 @@ the [NPM Registry](https://npmjs.com) within a few minutes. > **Note** > Alpha version will never be tagged as _latest_. -To install an `alpha` tagged release use either the `npm install @tbd54566975/@alpha` or -`npm install @tbd5456975/@x.y.z-alpha-YYYYMMDD-commithash` syntax. +To install an `alpha` tagged release use either the `npm install @web5/@alpha` or +`npm install @web5/@x.y.z-alpha-YYYYMMDD-commithash` syntax. diff --git a/README.md b/README.md index bc8ac7471..7736a488d 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,9 @@ # Web5 JS SDK -[![NPM](https://img.shields.io/npm/v/@tbd54566975/web5.svg?style=flat-square&logo=npm&logoColor=FFFFFF&color=FFEC19&santize=true)](https://www.npmjs.com/package/@tbd54566975/web5) +[![NPM](https://img.shields.io/npm/v/@web5/web5.svg?style=flat-square&logo=npm&logoColor=FFFFFF&color=FFEC19&santize=true)](https://www.npmjs.com/package/@web5/web5) [![Build Status](https://img.shields.io/github/actions/workflow/status/TBD54566975/web5-js/tests-ci.yml?branch=main&logo=github&label=ci&logoColor=FFFFFF&style=flat-square)](https://github.com/TBD54566975/web5-js/actions/workflows/tests-ci.yml) [![Coverage](https://img.shields.io/codecov/c/gh/frankhinek/test-web5-js/main?logo=codecov&logoColor=FFFFFF&style=flat-square&token=YI87CKF1LI)](https://codecov.io/github/TBD54566975/web5-js) -[![License](https://img.shields.io/npm/l/@tbd54566975/web5.svg?style=flat-square&color=24f2ff&logo=apache&logoColor=FFFFFF&santize=true)](https://github.com/TBD54566975/web5-js/blob/main/LICENSE) +[![License](https://img.shields.io/npm/l/@web5/web5.svg?style=flat-square&color=24f2ff&logo=apache&logoColor=FFFFFF&santize=true)](https://github.com/TBD54566975/web5-js/blob/main/LICENSE) [![Chat](https://img.shields.io/badge/chat-on%20discord-7289da.svg?style=flat-square&color=9a1aff&logo=discord&logoColor=FFFFFF&sanitize=true)](https://discord.com/channels/937858703112155166/969272658501976117) Making developing with Web5 components at least 5 times easier to work with. @@ -46,17 +46,17 @@ possible. _NPM_ ```yaml -npm install @tbd54566975/web5 +npm install @web5/api ``` _CDNs_ ```yaml -https://unpkg.com/@tbd54566975/web5@0.7.11/dist/browser.js +https://unpkg.com/@web5/api@0.7.11/dist/browser.js ``` ```yaml -https://cdn.jsdelivr.net/npm/@tbd54566975/web5@0.7.11/dist/browser.mjs +https://cdn.jsdelivr.net/npm/@web5/api@0.7.11/dist/browser.mjs ``` ## Usage @@ -64,7 +64,7 @@ https://cdn.jsdelivr.net/npm/@tbd54566975/web5@0.7.11/dist/browser.mjs ### Importing the SDK ```javascript -import { Web5 } from "@tbd54566975/web5"; +import { Web5 } from "@web5/api"; ``` or diff --git a/codecov.yml b/codecov.yml index b8264a083..4eeee39d5 100644 --- a/codecov.yml +++ b/codecov.yml @@ -8,9 +8,13 @@ component_management: target: auto # auto compares coverage to the previous base commit threshold: 5% # allows a 5% drop from the previous base commit coverage - type: patch - target: 100 # every PR opened should strive for full test coverage + target: 90 # every PR opened should strive for at least 90% coverage individual_components: + - component_id: package-api + name: api + paths: ["packages/api/**"] + - component_id: package_common name: common paths: ["packages/common/**"] @@ -27,21 +31,21 @@ component_management: name: dids paths: ["packages/dids/**"] - - component_id: package-web5 - name: web5 - paths: ["packages/web5/**"] + - component_id: package-agent + name: agent + paths: ["packages/agent/**"] - - component_id: package-web5-agent - name: web5-agent - paths: ["packages/web5-agent/**"] + - component_id: package-identity-agent + name: identity-agent + paths: ["packages/identity-agent/**"] - - component_id: package-web5-proxy-agent - name: web5-proxy-agent - paths: ["packages/web5-proxy-agent/**"] + - component_id: package-proxy-agent + name: proxy-agent + paths: ["packages/proxy-agent/**"] - - component_id: package-web5-user-agent - name: web5-user-agent - paths: ["packages/web5-user-agent/**"] + - component_id: package-user-agent + name: user-agent + paths: ["packages/user-agent/**"] coverage: status: diff --git a/package-lock.json b/package-lock.json index 3260e6aa5..882c42973 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,7912 +1,7910 @@ { - "name": "web5-js", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "workspaces": [ - "packages/common", - "packages/crypto", - "packages/web5-agent", - "packages/dids", - "packages/credentials", - "packages/web5-user-agent", - "packages/web5-proxy-agent", - "packages/web5" - ] - }, - "node_modules/@aashutoshrathi/word-wrap": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz", - "integrity": "sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/@assemblyscript/loader": { - "version": "0.9.4", - "resolved": "https://registry.npmjs.org/@assemblyscript/loader/-/loader-0.9.4.tgz", - "integrity": "sha512-HazVq9zwTVwGmqdwYzu7WyQ6FQVZ7SwET0KKQuKm55jD0IfUpZgN0OPIiZG3zV1iSrVYcN0bdwLRXI/VNCYsUA==" - }, - "node_modules/@bcoe/v8-coverage": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", - "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", - "dev": true - }, - "node_modules/@colors/colors": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", - "integrity": "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==", - "dev": true, - "engines": { - "node": ">=0.1.90" - } - }, - "node_modules/@decentralized-identity/ion-pow-sdk": { - "version": "1.0.17", - "resolved": "https://registry.npmjs.org/@decentralized-identity/ion-pow-sdk/-/ion-pow-sdk-1.0.17.tgz", - "integrity": "sha512-vk7DTDM8aKDbFyu1ad/qkoRrGL4q+KvNeL/FNZXhkWPaDhVExBN/qGEoRLf1YSfFe+myto3+4RYTPut+riiqnw==", - "dependencies": { - "buffer": "6.0.3", - "cross-fetch": "3.1.5", - "hash-wasm": "4.9.0" - } - }, - "node_modules/@decentralized-identity/ion-pow-sdk/node_modules/cross-fetch": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.5.tgz", - "integrity": "sha512-lvb1SBsI0Z7GDwmuid+mU3kWVBwTVUbe7S0H52yaaAdQOXq2YktTCZdlAcNKFzE6QtRz0snpw9bNiPeOIkkQvw==", - "dependencies": { - "node-fetch": "2.6.7" - } - }, - "node_modules/@decentralized-identity/ion-pow-sdk/node_modules/node-fetch": { - "version": "2.6.7", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", - "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", - "dependencies": { - "whatwg-url": "^5.0.0" - }, - "engines": { - "node": "4.x || >=6.0.0" - }, - "peerDependencies": { - "encoding": "^0.1.0" - }, - "peerDependenciesMeta": { - "encoding": { - "optional": true - } - } - }, - "node_modules/@decentralized-identity/ion-sdk": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@decentralized-identity/ion-sdk/-/ion-sdk-1.0.1.tgz", - "integrity": "sha512-+P+DXcRSFjsEsI5KIqUmVjpzgUT28B2lWpTO+IxiBcfibAN/1Sg20NebGTO/+serz2CnSZf95N2a1OZ6eXypGQ==", - "dependencies": { - "@noble/ed25519": "^2.0.0", - "@noble/secp256k1": "^2.0.0", - "canonicalize": "^2.0.0", - "multiformats": "^12.0.1", - "multihashes": "^4.0.3", - "uri-js": "^4.4.1" - } - }, - "node_modules/@decentralized-identity/ion-sdk/node_modules/multiformats": { - "version": "12.0.1", - "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-12.0.1.tgz", - "integrity": "sha512-s01wijBJoDUqESWSzePY0lvTw7J3PVO9x2Cc6ASI5AMZM2Gnhh7BC17+nlFhHKU7dDzaCaRfb+NiqNzOsgPUoQ==", - "engines": { - "node": ">=16.0.0", - "npm": ">=7.0.0" - } - }, - "node_modules/@decentralized-identity/ion-tools": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/@decentralized-identity/ion-tools/-/ion-tools-1.1.4.tgz", - "integrity": "sha512-/ouKiDXM8nRVsXJ6CbN0sY+SUj6PGv6LRAQitxEOj1NlKEDkMD+ZqkAeM+Ar/IHEW9PdsE2Wc+WQAsesQesbwg==", - "dependencies": { - "@decentralized-identity/ion-pow-sdk": "^1.0.17", - "@decentralized-identity/ion-sdk": "^1.0.1", - "@noble/ed25519": "^2.0.0", - "@noble/secp256k1": "^2.0.0", - "chai": "^4.3.7", - "cross-fetch": "4.0.0", - "multiformats": "^12.0.1" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/@decentralized-identity/ion-tools/node_modules/multiformats": { - "version": "12.0.1", - "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-12.0.1.tgz", - "integrity": "sha512-s01wijBJoDUqESWSzePY0lvTw7J3PVO9x2Cc6ASI5AMZM2Gnhh7BC17+nlFhHKU7dDzaCaRfb+NiqNzOsgPUoQ==", - "engines": { - "node": ">=16.0.0", - "npm": ">=7.0.0" - } - }, - "node_modules/@esbuild/android-arm": { - "version": "0.16.17", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.16.17.tgz", - "integrity": "sha512-N9x1CMXVhtWEAMS7pNNONyA14f71VPQN9Cnavj1XQh6T7bskqiLLrSca4O0Vr8Wdcga943eThxnVp3JLnBMYtw==", - "cpu": [ - "arm" - ], - "dev": true, - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/android-arm64": { - "version": "0.16.17", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.16.17.tgz", - "integrity": "sha512-MIGl6p5sc3RDTLLkYL1MyL8BMRN4tLMRCn+yRJJmEDvYZ2M7tmAf80hx1kbNEUX2KJ50RRtxZ4JHLvCfuB6kBg==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/android-x64": { - "version": "0.16.17", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.16.17.tgz", - "integrity": "sha512-a3kTv3m0Ghh4z1DaFEuEDfz3OLONKuFvI4Xqczqx4BqLyuFaFkuaG4j2MtA6fuWEFeC5x9IvqnX7drmRq/fyAQ==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/darwin-arm64": { - "version": "0.16.17", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.16.17.tgz", - "integrity": "sha512-/2agbUEfmxWHi9ARTX6OQ/KgXnOWfsNlTeLcoV7HSuSTv63E4DqtAc+2XqGw1KHxKMHGZgbVCZge7HXWX9Vn+w==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/darwin-x64": { - "version": "0.16.17", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.16.17.tgz", - "integrity": "sha512-2By45OBHulkd9Svy5IOCZt376Aa2oOkiE9QWUK9fe6Tb+WDr8hXL3dpqi+DeLiMed8tVXspzsTAvd0jUl96wmg==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/freebsd-arm64": { - "version": "0.16.17", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.16.17.tgz", - "integrity": "sha512-mt+cxZe1tVx489VTb4mBAOo2aKSnJ33L9fr25JXpqQqzbUIw/yzIzi+NHwAXK2qYV1lEFp4OoVeThGjUbmWmdw==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/freebsd-x64": { - "version": "0.16.17", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.16.17.tgz", - "integrity": "sha512-8ScTdNJl5idAKjH8zGAsN7RuWcyHG3BAvMNpKOBaqqR7EbUhhVHOqXRdL7oZvz8WNHL2pr5+eIT5c65kA6NHug==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-arm": { - "version": "0.16.17", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.16.17.tgz", - "integrity": "sha512-iihzrWbD4gIT7j3caMzKb/RsFFHCwqqbrbH9SqUSRrdXkXaygSZCZg1FybsZz57Ju7N/SHEgPyaR0LZ8Zbe9gQ==", - "cpu": [ - "arm" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-arm64": { - "version": "0.16.17", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.16.17.tgz", - "integrity": "sha512-7S8gJnSlqKGVJunnMCrXHU9Q8Q/tQIxk/xL8BqAP64wchPCTzuM6W3Ra8cIa1HIflAvDnNOt2jaL17vaW+1V0g==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-ia32": { - "version": "0.16.17", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.16.17.tgz", - "integrity": "sha512-kiX69+wcPAdgl3Lonh1VI7MBr16nktEvOfViszBSxygRQqSpzv7BffMKRPMFwzeJGPxcio0pdD3kYQGpqQ2SSg==", - "cpu": [ - "ia32" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-loong64": { - "version": "0.16.17", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.16.17.tgz", - "integrity": "sha512-dTzNnQwembNDhd654cA4QhbS9uDdXC3TKqMJjgOWsC0yNCbpzfWoXdZvp0mY7HU6nzk5E0zpRGGx3qoQg8T2DQ==", - "cpu": [ - "loong64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-mips64el": { - "version": "0.16.17", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.16.17.tgz", - "integrity": "sha512-ezbDkp2nDl0PfIUn0CsQ30kxfcLTlcx4Foz2kYv8qdC6ia2oX5Q3E/8m6lq84Dj/6b0FrkgD582fJMIfHhJfSw==", - "cpu": [ - "mips64el" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-ppc64": { - "version": "0.16.17", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.16.17.tgz", - "integrity": "sha512-dzS678gYD1lJsW73zrFhDApLVdM3cUF2MvAa1D8K8KtcSKdLBPP4zZSLy6LFZ0jYqQdQ29bjAHJDgz0rVbLB3g==", - "cpu": [ - "ppc64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-riscv64": { - "version": "0.16.17", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.16.17.tgz", - "integrity": "sha512-ylNlVsxuFjZK8DQtNUwiMskh6nT0vI7kYl/4fZgV1llP5d6+HIeL/vmmm3jpuoo8+NuXjQVZxmKuhDApK0/cKw==", - "cpu": [ - "riscv64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-s390x": { - "version": "0.16.17", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.16.17.tgz", - "integrity": "sha512-gzy7nUTO4UA4oZ2wAMXPNBGTzZFP7mss3aKR2hH+/4UUkCOyqmjXiKpzGrY2TlEUhbbejzXVKKGazYcQTZWA/w==", - "cpu": [ - "s390x" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-x64": { - "version": "0.16.17", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.16.17.tgz", - "integrity": "sha512-mdPjPxfnmoqhgpiEArqi4egmBAMYvaObgn4poorpUaqmvzzbvqbowRllQ+ZgzGVMGKaPkqUmPDOOFQRUFDmeUw==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/netbsd-x64": { - "version": "0.16.17", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.16.17.tgz", - "integrity": "sha512-/PzmzD/zyAeTUsduZa32bn0ORug+Jd1EGGAUJvqfeixoEISYpGnAezN6lnJoskauoai0Jrs+XSyvDhppCPoKOA==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/openbsd-x64": { - "version": "0.16.17", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.16.17.tgz", - "integrity": "sha512-2yaWJhvxGEz2RiftSk0UObqJa/b+rIAjnODJgv2GbGGpRwAfpgzyrg1WLK8rqA24mfZa9GvpjLcBBg8JHkoodg==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/sunos-x64": { - "version": "0.16.17", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.16.17.tgz", - "integrity": "sha512-xtVUiev38tN0R3g8VhRfN7Zl42YCJvyBhRKw1RJjwE1d2emWTVToPLNEQj/5Qxc6lVFATDiy6LjVHYhIPrLxzw==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "sunos" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/win32-arm64": { - "version": "0.16.17", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.16.17.tgz", - "integrity": "sha512-ga8+JqBDHY4b6fQAmOgtJJue36scANy4l/rL97W+0wYmijhxKetzZdKOJI7olaBaMhWt8Pac2McJdZLxXWUEQw==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/win32-ia32": { - "version": "0.16.17", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.16.17.tgz", - "integrity": "sha512-WnsKaf46uSSF/sZhwnqE4L/F89AYNMiD4YtEcYekBt9Q7nj0DiId2XH2Ng2PHM54qi5oPrQ8luuzGszqi/veig==", - "cpu": [ - "ia32" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/win32-x64": { - "version": "0.16.17", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.16.17.tgz", - "integrity": "sha512-y+EHuSchhL7FjHgvQL/0fnnFmO4T1bhvWANX6gcnqTjtnKWbTvUMCpGnv2+t+31d7RzyEAYAd4u2fnIhHL6N/Q==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@eslint-community/eslint-utils": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", - "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", - "dev": true, - "dependencies": { - "eslint-visitor-keys": "^3.3.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "peerDependencies": { - "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" - } - }, - "node_modules/@eslint-community/regexpp": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.6.2.tgz", - "integrity": "sha512-pPTNuaAG3QMH+buKyBIGJs3g/S5y0caxw0ygM3YyE6yJFySwiGGSzA+mM3KJ8QQvzeLh3blwgSonkFjgQdxzMw==", - "dev": true, - "engines": { - "node": "^12.0.0 || ^14.0.0 || >=16.0.0" - } - }, - "node_modules/@eslint/eslintrc": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.1.tgz", - "integrity": "sha512-9t7ZA7NGGK8ckelF0PQCfcxIUzs1Md5rrO6U/c+FIQNanea5UZC0wqKXH4vHBccmu4ZJgZ2idtPeW7+Q2npOEA==", - "dev": true, - "dependencies": { - "ajv": "^6.12.4", - "debug": "^4.3.2", - "espree": "^9.6.0", - "globals": "^13.19.0", - "ignore": "^5.2.0", - "import-fresh": "^3.2.1", - "js-yaml": "^4.1.0", - "minimatch": "^3.1.2", - "strip-json-comments": "^3.1.1" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/@eslint/eslintrc/node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, - "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/@eslint/eslintrc/node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true - }, - "node_modules/@eslint/js": { - "version": "8.39.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.39.0.tgz", - "integrity": "sha512-kf9RB0Fg7NZfap83B3QOqOGg9QmD9yBudqQXzzOtn3i4y7ZUXe5ONeW34Gwi+TxhH4mvj72R1Zc300KUMa9Bng==", - "dev": true, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - } - }, - "node_modules/@faker-js/faker": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/@faker-js/faker/-/faker-8.0.1.tgz", - "integrity": "sha512-kbh5MenpTN9U0B4QcOI1NoTPlZHniSYQ3BHbhAnPjJGAmmFqxoxTE4sGdpy7ZOO9038DPGCuhXyMkjOr05uVwA==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/fakerjs" - } - ], - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0", - "npm": ">=6.14.13" - } - }, - "node_modules/@humanwhocodes/config-array": { - "version": "0.11.10", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.10.tgz", - "integrity": "sha512-KVVjQmNUepDVGXNuoRRdmmEjruj0KfiGSbS8LVc12LMsWDQzRXJ0qdhN8L8uUigKpfEHRhlaQFY0ib1tnUbNeQ==", - "dev": true, - "dependencies": { - "@humanwhocodes/object-schema": "^1.2.1", - "debug": "^4.1.1", - "minimatch": "^3.0.5" - }, - "engines": { - "node": ">=10.10.0" - } - }, - "node_modules/@humanwhocodes/module-importer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", - "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", - "dev": true, - "engines": { - "node": ">=12.22" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" - } - }, - "node_modules/@humanwhocodes/object-schema": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", - "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", - "dev": true - }, - "node_modules/@ipld/dag-cbor": { - "version": "9.0.3", - "resolved": "https://registry.npmjs.org/@ipld/dag-cbor/-/dag-cbor-9.0.3.tgz", - "integrity": "sha512-A2UFccS0+sARK9xwXiVZIaWbLbPxLGP3UZOjBeOMWfDY04SXi8h1+t4rHBzOlKYF/yWNm3RbFLyclWO7hZcy4g==", - "dependencies": { - "cborg": "^2.0.1", - "multiformats": "^12.0.1" - }, - "engines": { - "node": ">=16.0.0", - "npm": ">=7.0.0" - } - }, - "node_modules/@ipld/dag-cbor/node_modules/multiformats": { - "version": "12.0.1", - "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-12.0.1.tgz", - "integrity": "sha512-s01wijBJoDUqESWSzePY0lvTw7J3PVO9x2Cc6ASI5AMZM2Gnhh7BC17+nlFhHKU7dDzaCaRfb+NiqNzOsgPUoQ==", - "engines": { - "node": ">=16.0.0", - "npm": ">=7.0.0" - } - }, - "node_modules/@ipld/dag-pb": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/@ipld/dag-pb/-/dag-pb-4.0.4.tgz", - "integrity": "sha512-lX0c6ZAwD8ZKtjbawxotP8XNyR6z7/NIk7wXuhDlFT4MrNo/AOefZEUWjAw8CGz3EG3mau4P66VpsZwToVLHDg==", - "dependencies": { - "multiformats": "^12.0.1" - }, - "engines": { - "node": ">=16.0.0", - "npm": ">=7.0.0" - } - }, - "node_modules/@ipld/dag-pb/node_modules/multiformats": { - "version": "12.0.1", - "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-12.0.1.tgz", - "integrity": "sha512-s01wijBJoDUqESWSzePY0lvTw7J3PVO9x2Cc6ASI5AMZM2Gnhh7BC17+nlFhHKU7dDzaCaRfb+NiqNzOsgPUoQ==", - "engines": { - "node": ">=16.0.0", - "npm": ">=7.0.0" - } - }, - "node_modules/@istanbuljs/schema": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", - "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz", - "integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==", - "dev": true, - "peer": true, - "dependencies": { - "@jridgewell/set-array": "^1.0.1", - "@jridgewell/sourcemap-codec": "^1.4.10", - "@jridgewell/trace-mapping": "^0.3.9" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", - "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==", - "dev": true, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/set-array": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", - "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", - "dev": true, - "peer": true, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/source-map": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.5.tgz", - "integrity": "sha512-UTYAUj/wviwdsMfzoSJspJxbkH5o1snzwX0//0ENX1u/55kkZZkcTZP6u9bwKGkv+dkk9at4m1Cpt0uY80kcpQ==", - "dev": true, - "peer": true, - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.0", - "@jridgewell/trace-mapping": "^0.3.9" - } - }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.4.14", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", - "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==", - "dev": true - }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.18", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.18.tgz", - "integrity": "sha512-w+niJYzMHdd7USdiH2U6869nqhD2nbfZXND5Yp93qIbEmnDNk7PD48o+YchRVpzMU7M6jVCbenTR7PA1FLQ9pA==", - "dev": true, - "dependencies": { - "@jridgewell/resolve-uri": "3.1.0", - "@jridgewell/sourcemap-codec": "1.4.14" - } - }, - "node_modules/@js-temporal/polyfill": { - "version": "0.4.4", - "resolved": "https://registry.npmjs.org/@js-temporal/polyfill/-/polyfill-0.4.4.tgz", - "integrity": "sha512-2X6bvghJ/JAoZO52lbgyAPFj8uCflhTo2g7nkFzEQdXd/D8rEeD4HtmTEpmtGCva260fcd66YNXBOYdnmHqSOg==", - "dependencies": { - "jsbi": "^4.3.0", - "tslib": "^2.4.1" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/@multiformats/base-x": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@multiformats/base-x/-/base-x-4.0.1.tgz", - "integrity": "sha512-eMk0b9ReBbV23xXU693TAIrLyeO5iTgBZGSJfpqriG8UkYvr/hC9u9pyMlAakDNHWmbhMZCDs6KQO0jzKD8OTw==" - }, - "node_modules/@multiformats/murmur3": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@multiformats/murmur3/-/murmur3-2.1.5.tgz", - "integrity": "sha512-etjrdN/gJ1PhIg3vv+4QypYgXsqBQCfTFEMzSclz3t1YwLSnd9i8R1nL50CIznUraVlsKzbcH/xCB9dC0XbFow==", - "dependencies": { - "multiformats": "^12.0.1", - "murmurhash3js-revisited": "^3.0.0" - }, - "engines": { - "node": ">=16.0.0", - "npm": ">=7.0.0" - } - }, - "node_modules/@multiformats/murmur3/node_modules/multiformats": { - "version": "12.0.1", - "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-12.0.1.tgz", - "integrity": "sha512-s01wijBJoDUqESWSzePY0lvTw7J3PVO9x2Cc6ASI5AMZM2Gnhh7BC17+nlFhHKU7dDzaCaRfb+NiqNzOsgPUoQ==", - "engines": { - "node": ">=16.0.0", - "npm": ">=7.0.0" - } - }, - "node_modules/@noble/ciphers": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/@noble/ciphers/-/ciphers-0.1.3.tgz", - "integrity": "sha512-L75KBG/jf6YNxSvV0w0tsC8OIOsLh9sk6mqLUNZ+3/UhDBrAHQAvTTspwMoBlB84ymupIhq+E9QOhr6H1g/xog==", - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/@noble/curves": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.1.0.tgz", - "integrity": "sha512-091oBExgENk/kGj3AZmtBDMpxQPDtxQABR2B9lb1JbVTs6ytdzZNwvhxQ4MWasRNEzlbEH8jCWFCwhF/Obj5AA==", - "dependencies": { - "@noble/hashes": "1.3.1" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/@noble/ed25519": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@noble/ed25519/-/ed25519-2.0.0.tgz", - "integrity": "sha512-/extjhkwFupyopDrt80OMWKdLgP429qLZj+z6sYJz90rF2Iz0gjZh2ArMKPImUl13Kx+0EXI2hN9T/KJV0/Zng==", - "funding": [ - { - "type": "individual", - "url": "https://paulmillr.com/funding/" - } - ] - }, - "node_modules/@noble/hashes": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.1.tgz", - "integrity": "sha512-EbqwksQwz9xDRGfDST86whPBgM65E0OH/pCgqW0GBVzO22bNE+NuIbeTb714+IfSjU3aRk47EUvXIb5bTsenKA==", - "engines": { - "node": ">= 16" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/@noble/secp256k1": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@noble/secp256k1/-/secp256k1-2.0.0.tgz", - "integrity": "sha512-rUGBd95e2a45rlmFTqQJYEFA4/gdIARFfuTuTqLglz0PZ6AKyzyXsEZZq7UZn8hZsvaBgpCzKKBJizT2cJERXw==", - "funding": [ - { - "type": "individual", - "url": "https://paulmillr.com/funding/" - } - ] - }, - "node_modules/@nodelib/fs.scandir": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", - "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", - "dev": true, - "dependencies": { - "@nodelib/fs.stat": "2.0.5", - "run-parallel": "^1.1.9" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.stat": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", - "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", - "dev": true, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.walk": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", - "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", - "dev": true, - "dependencies": { - "@nodelib/fs.scandir": "2.1.5", - "fastq": "^1.6.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@protobufjs/aspromise": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", - "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==" - }, - "node_modules/@protobufjs/base64": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", - "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==" - }, - "node_modules/@protobufjs/codegen": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz", - "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==" - }, - "node_modules/@protobufjs/eventemitter": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz", - "integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==" - }, - "node_modules/@protobufjs/fetch": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz", - "integrity": "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==", - "dependencies": { - "@protobufjs/aspromise": "^1.1.1", - "@protobufjs/inquire": "^1.1.0" - } - }, - "node_modules/@protobufjs/float": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", - "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==" - }, - "node_modules/@protobufjs/inquire": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz", - "integrity": "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==" - }, - "node_modules/@protobufjs/path": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", - "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==" - }, - "node_modules/@protobufjs/pool": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", - "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==" - }, - "node_modules/@protobufjs/utf8": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", - "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==" - }, - "node_modules/@sinonjs/commons": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.0.tgz", - "integrity": "sha512-jXBtWAF4vmdNmZgD5FoKsVLv3rPgDnLgPbU84LIJ3otV44vJlDRokVng5v8NFJdCf/da9legHcKaRuZs4L7faA==", - "dev": true, - "dependencies": { - "type-detect": "4.0.8" - } - }, - "node_modules/@sinonjs/fake-timers": { - "version": "10.3.0", - "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz", - "integrity": "sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==", - "dev": true, - "dependencies": { - "@sinonjs/commons": "^3.0.0" - } - }, - "node_modules/@sinonjs/samsam": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-7.0.1.tgz", - "integrity": "sha512-zsAk2Jkiq89mhZovB2LLOdTCxJF4hqqTToGP0ASWlhp4I1hqOjcfmZGafXntCN7MDC6yySH0mFHrYtHceOeLmw==", - "dev": true, - "dependencies": { - "@sinonjs/commons": "^2.0.0", - "lodash.get": "^4.4.2", - "type-detect": "^4.0.8" - } - }, - "node_modules/@sinonjs/samsam/node_modules/@sinonjs/commons": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-2.0.0.tgz", - "integrity": "sha512-uLa0j859mMrg2slwQYdO/AkrOfmH+X6LTVmNTS9CqexuE2IvVORIkSpJLqePAbEnKJ77aMmCwr1NUZ57120Xcg==", - "dev": true, - "dependencies": { - "type-detect": "4.0.8" - } - }, - "node_modules/@sinonjs/text-encoding": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/@sinonjs/text-encoding/-/text-encoding-0.7.2.tgz", - "integrity": "sha512-sXXKG+uL9IrKqViTtao2Ws6dy0znu9sOaP1di/jKGW1M6VssO8vlpXCQcpZ+jisQ1tTFAC5Jo/EOzFbggBagFQ==", - "dev": true - }, - "node_modules/@socket.io/component-emitter": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.0.tgz", - "integrity": "sha512-+9jVqKhRSpsc591z5vX+X5Yyw+he/HCB4iQ/RYxw35CEPaY1gnsNE43nf9n9AaYjAQrTiI/mOwKUKdUs9vf7Xg==", - "dev": true - }, - "node_modules/@tbd54566975/common": { - "resolved": "packages/common", - "link": true - }, - "node_modules/@tbd54566975/credentials": { - "resolved": "packages/credentials", - "link": true - }, - "node_modules/@tbd54566975/crypto": { - "resolved": "packages/crypto", - "link": true - }, - "node_modules/@tbd54566975/dids": { - "resolved": "packages/dids", - "link": true - }, - "node_modules/@tbd54566975/dwn-sdk-js": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/@tbd54566975/dwn-sdk-js/-/dwn-sdk-js-0.2.1.tgz", - "integrity": "sha512-7rFi0zvpt0F/6E2Pow5+iCnf35YGIUneI9U4J43A8NfP0Gh5S2eJkApvhrPOyt50Xq2MerFR9F5E3BE2E0jJRQ==", - "dependencies": { - "@ipld/dag-cbor": "9.0.3", - "@js-temporal/polyfill": "0.4.4", - "@noble/ed25519": "2.0.0", - "@noble/secp256k1": "2.0.0", - "abstract-level": "1.0.3", - "ajv": "8.12.0", - "blockstore-core": "4.2.0", - "cross-fetch": "4.0.0", - "eciesjs": "0.4.0", - "flat": "5.0.2", - "interface-blockstore": "5.2.3", - "interface-store": "5.1.2", - "ipfs-unixfs-exporter": "13.1.5", - "ipfs-unixfs-importer": "15.1.5", - "level": "8.0.0", - "lodash": "4.17.21", - "lru-cache": "9.1.2", - "ms": "2.1.3", - "multiformats": "11.0.2", - "randombytes": "2.1.0", - "readable-stream": "4.4.0", - "ulid": "2.3.0", - "uuid": "8.3.2", - "varint": "6.0.0" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/@tbd54566975/dwn-sdk-js/node_modules/uuid": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", - "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", - "bin": { - "uuid": "dist/bin/uuid" - } - }, - "node_modules/@tbd54566975/web5": { - "resolved": "packages/web5", - "link": true - }, - "node_modules/@tbd54566975/web5-agent": { - "resolved": "packages/web5-agent", - "link": true - }, - "node_modules/@tbd54566975/web5-proxy-agent": { - "resolved": "packages/web5-proxy-agent", - "link": true - }, - "node_modules/@tbd54566975/web5-user-agent": { - "resolved": "packages/web5-user-agent", - "link": true - }, - "node_modules/@types/chai": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.3.0.tgz", - "integrity": "sha512-/ceqdqeRraGolFTcfoXNiqjyQhZzbINDngeoAq9GoHa8PPK1yNzTaxWjA6BFWp5Ua9JpXEMSS4s5i9tS0hOJtw==", - "dev": true - }, - "node_modules/@types/chai-as-promised": { - "version": "7.1.5", - "resolved": "https://registry.npmjs.org/@types/chai-as-promised/-/chai-as-promised-7.1.5.tgz", - "integrity": "sha512-jStwss93SITGBwt/niYrkf2C+/1KTeZCZl1LaeezTlqppAKeoQC7jxyqYuP72sxBGKCIbw7oHgbYssIRzT5FCQ==", - "dev": true, - "dependencies": { - "@types/chai": "*" - } - }, - "node_modules/@types/cookie": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.4.1.tgz", - "integrity": "sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q==", - "dev": true - }, - "node_modules/@types/cors": { - "version": "2.8.13", - "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.13.tgz", - "integrity": "sha512-RG8AStHlUiV5ysZQKq97copd2UmVYw3/pRMLefISZ3S1hK104Cwm7iLQ3fTKx+lsUH2CE8FlLaYeEA2LSeqYUA==", - "dev": true, - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/ed2curve": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/@types/ed2curve/-/ed2curve-0.2.2.tgz", - "integrity": "sha512-G1sTX5xo91ydevQPINbL2nfgVAj/s1ZiqZxC8OCWduwu+edoNGUm5JXtTkg9F3LsBZbRI46/0HES4CPUE2wc9g==", - "dev": true, - "dependencies": { - "tweetnacl": "^1.0.0" - } - }, - "node_modules/@types/eslint": { - "version": "8.37.0", - "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.37.0.tgz", - "integrity": "sha512-Piet7dG2JBuDIfohBngQ3rCt7MgO9xCO4xIMKxBThCq5PNRB91IjlJ10eJVwfoNtvTErmxLzwBZ7rHZtbOMmFQ==", - "dev": true, - "dependencies": { - "@types/estree": "*", - "@types/json-schema": "*" - } - }, - "node_modules/@types/eslint-scope": { - "version": "3.7.4", - "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.4.tgz", - "integrity": "sha512-9K4zoImiZc3HlIp6AVUDE4CWYx22a+lhSZMYNpbjW04+YF0KWj4pJXnEMjdnFTiQibFFmElcsasJXDbdI/EPhA==", - "dev": true, - "peer": true, - "dependencies": { - "@types/eslint": "*", - "@types/estree": "*" - } - }, - "node_modules/@types/estree": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.1.tgz", - "integrity": "sha512-LG4opVs2ANWZ1TJoKc937iMmNstM/d0ae1vNbnBvBhqCSezgVUOzcLCqbI5elV8Vy6WKwKjaqR+zO9VKirBBCA==", - "dev": true - }, - "node_modules/@types/flat": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/@types/flat/-/flat-5.0.2.tgz", - "integrity": "sha512-3zsplnP2djeps5P9OyarTxwRpMLoe5Ash8aL9iprw0JxB+FAHjY+ifn4yZUuW4/9hqtnmor6uvjSRzJhiVbrEQ==", - "dev": true - }, - "node_modules/@types/istanbul-lib-coverage": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz", - "integrity": "sha512-z/QT1XN4K4KYuslS23k62yDIDLwLFkzxOuMplDtObz0+y7VqJCaO2o+SPwHCvLFZh7xazvvoor2tA/hPz9ee7g==", - "dev": true - }, - "node_modules/@types/json-schema": { - "version": "7.0.12", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.12.tgz", - "integrity": "sha512-Hr5Jfhc9eYOQNPYO5WLDq/n4jqijdHNlDXjuAQkkt+mWdQR+XJToOHrsD4cPaMXpn6KO7y2+wM8AZEs8VpBLVA==", - "dev": true - }, - "node_modules/@types/mocha": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-10.0.1.tgz", - "integrity": "sha512-/fvYntiO1GeICvqbQ3doGDIP97vWmvFt83GKguJ6prmQM2iXZfFcq6YE8KteFyRtX2/h5Hf91BYvPodJKFYv5Q==", - "dev": true - }, - "node_modules/@types/ms": { - "version": "0.7.31", - "resolved": "https://registry.npmjs.org/@types/ms/-/ms-0.7.31.tgz", - "integrity": "sha512-iiUgKzV9AuaEkZqkOLDIvlQiL6ltuZd9tGcW3gwpnX8JbuiuhFlEGmmFXEXkN50Cvq7Os88IY2v0dkDqXYWVgA==", - "dev": true - }, - "node_modules/@types/node": { - "version": "20.4.7", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.4.7.tgz", - "integrity": "sha512-bUBrPjEry2QUTsnuEjzjbS7voGWCc30W0qzgMf90GPeDGFRakvrz47ju+oqDAKCXLUCe39u57/ORMl/O/04/9g==" - }, - "node_modules/@types/readable-stream": { - "version": "2.3.15", - "resolved": "https://registry.npmjs.org/@types/readable-stream/-/readable-stream-2.3.15.tgz", - "integrity": "sha512-oM5JSKQCcICF1wvGgmecmHldZ48OZamtMxcGGVICOJA8o8cahXC1zEVAif8iwoc5j8etxFaRFnf095+CDsuoFQ==", - "dev": true, - "dependencies": { - "@types/node": "*", - "safe-buffer": "~5.1.1" - } - }, - "node_modules/@types/semver": { - "version": "7.5.0", - "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.0.tgz", - "integrity": "sha512-G8hZ6XJiHnuhQKR7ZmysCeJWE08o8T0AXtk5darsCaTVsYZhhgUrq53jizaR2FvsoeCwJhlmwTjkXBY5Pn/ZHw==", - "dev": true - }, - "node_modules/@types/sinon": { - "version": "10.0.15", - "resolved": "https://registry.npmjs.org/@types/sinon/-/sinon-10.0.15.tgz", - "integrity": "sha512-3lrFNQG0Kr2LDzvjyjB6AMJk4ge+8iYhQfdnSwIwlG88FUOV43kPcQqDZkDa/h3WSZy6i8Fr0BSjfQtB1B3xuQ==", - "dev": true, - "dependencies": { - "@types/sinonjs__fake-timers": "*" - } - }, - "node_modules/@types/sinonjs__fake-timers": { - "version": "8.1.2", - "resolved": "https://registry.npmjs.org/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-8.1.2.tgz", - "integrity": "sha512-9GcLXF0/v3t80caGs5p2rRfkB+a8VBGLJZVih6CNFkx8IZ994wiKKLSRs9nuFwk1HevWs/1mnUmkApGrSGsShA==", - "dev": true - }, - "node_modules/@types/uuid": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-9.0.1.tgz", - "integrity": "sha512-rFT3ak0/2trgvp4yYZo5iKFEPsET7vKydKF+VRCxlQ9bpheehyAJH89dAkaLEq/j/RZXJIqcgsmPJKUP1Z28HA==", - "dev": true - }, - "node_modules/@typescript-eslint/eslint-plugin": { - "version": "5.59.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.59.0.tgz", - "integrity": "sha512-p0QgrEyrxAWBecR56gyn3wkG15TJdI//eetInP3zYRewDh0XS+DhB3VUAd3QqvziFsfaQIoIuZMxZRB7vXYaYw==", - "dev": true, - "dependencies": { - "@eslint-community/regexpp": "^4.4.0", - "@typescript-eslint/scope-manager": "5.59.0", - "@typescript-eslint/type-utils": "5.59.0", - "@typescript-eslint/utils": "5.59.0", - "debug": "^4.3.4", - "grapheme-splitter": "^1.0.4", - "ignore": "^5.2.0", - "natural-compare-lite": "^1.4.0", - "semver": "^7.3.7", - "tsutils": "^3.21.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "@typescript-eslint/parser": "^5.0.0", - "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@typescript-eslint/parser": { - "version": "5.59.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.59.0.tgz", - "integrity": "sha512-qK9TZ70eJtjojSUMrrEwA9ZDQ4N0e/AuoOIgXuNBorXYcBDk397D2r5MIe1B3cok/oCtdNC5j+lUUpVB+Dpb+w==", - "dev": true, - "dependencies": { - "@typescript-eslint/scope-manager": "5.59.0", - "@typescript-eslint/types": "5.59.0", - "@typescript-eslint/typescript-estree": "5.59.0", - "debug": "^4.3.4" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@typescript-eslint/scope-manager": { - "version": "5.59.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.59.0.tgz", - "integrity": "sha512-tsoldKaMh7izN6BvkK6zRMINj4Z2d6gGhO2UsI8zGZY3XhLq1DndP3Ycjhi1JwdwPRwtLMW4EFPgpuKhbCGOvQ==", - "dev": true, - "dependencies": { - "@typescript-eslint/types": "5.59.0", - "@typescript-eslint/visitor-keys": "5.59.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/type-utils": { - "version": "5.59.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.59.0.tgz", - "integrity": "sha512-d/B6VSWnZwu70kcKQSCqjcXpVH+7ABKH8P1KNn4K7j5PXXuycZTPXF44Nui0TEm6rbWGi8kc78xRgOC4n7xFgA==", - "dev": true, - "dependencies": { - "@typescript-eslint/typescript-estree": "5.59.0", - "@typescript-eslint/utils": "5.59.0", - "debug": "^4.3.4", - "tsutils": "^3.21.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "*" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@typescript-eslint/types": { - "version": "5.59.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.59.0.tgz", - "integrity": "sha512-yR2h1NotF23xFFYKHZs17QJnB51J/s+ud4PYU4MqdZbzeNxpgUr05+dNeCN/bb6raslHvGdd6BFCkVhpPk/ZeA==", - "dev": true, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/typescript-estree": { - "version": "5.59.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.59.0.tgz", - "integrity": "sha512-sUNnktjmI8DyGzPdZ8dRwW741zopGxltGs/SAPgGL/AAgDpiLsCFLcMNSpbfXfmnNeHmK9h3wGmCkGRGAoUZAg==", - "dev": true, - "dependencies": { - "@typescript-eslint/types": "5.59.0", - "@typescript-eslint/visitor-keys": "5.59.0", - "debug": "^4.3.4", - "globby": "^11.1.0", - "is-glob": "^4.0.3", - "semver": "^7.3.7", - "tsutils": "^3.21.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@typescript-eslint/utils": { - "version": "5.59.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.59.0.tgz", - "integrity": "sha512-GGLFd+86drlHSvPgN/el6dRQNYYGOvRSDVydsUaQluwIW3HvbXuxyuD5JETvBt/9qGYe+lOrDk6gRrWOHb/FvA==", - "dev": true, - "dependencies": { - "@eslint-community/eslint-utils": "^4.2.0", - "@types/json-schema": "^7.0.9", - "@types/semver": "^7.3.12", - "@typescript-eslint/scope-manager": "5.59.0", - "@typescript-eslint/types": "5.59.0", - "@typescript-eslint/typescript-estree": "5.59.0", - "eslint-scope": "^5.1.1", - "semver": "^7.3.7" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" - } - }, - "node_modules/@typescript-eslint/visitor-keys": { - "version": "5.59.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.59.0.tgz", - "integrity": "sha512-qZ3iXxQhanchCeaExlKPV3gDQFxMUmU35xfd5eCXB6+kUw1TUAbIy2n7QIrwz9s98DQLzNWyHp61fY0da4ZcbA==", - "dev": true, - "dependencies": { - "@typescript-eslint/types": "5.59.0", - "eslint-visitor-keys": "^3.3.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@webassemblyjs/ast": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.6.tgz", - "integrity": "sha512-IN1xI7PwOvLPgjcf180gC1bqn3q/QaOCwYUahIOhbYUu8KA/3tw2RT/T0Gidi1l7Hhj5D/INhJxiICObqpMu4Q==", - "dev": true, - "peer": true, - "dependencies": { - "@webassemblyjs/helper-numbers": "1.11.6", - "@webassemblyjs/helper-wasm-bytecode": "1.11.6" - } - }, - "node_modules/@webassemblyjs/floating-point-hex-parser": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.6.tgz", - "integrity": "sha512-ejAj9hfRJ2XMsNHk/v6Fu2dGS+i4UaXBXGemOfQ/JfQ6mdQg/WXtwleQRLLS4OvfDhv8rYnVwH27YJLMyYsxhw==", - "dev": true, - "peer": true - }, - "node_modules/@webassemblyjs/helper-api-error": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.6.tgz", - "integrity": "sha512-o0YkoP4pVu4rN8aTJgAyj9hC2Sv5UlkzCHhxqWj8butaLvnpdc2jOwh4ewE6CX0txSfLn/UYaV/pheS2Txg//Q==", - "dev": true, - "peer": true - }, - "node_modules/@webassemblyjs/helper-buffer": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.6.tgz", - "integrity": "sha512-z3nFzdcp1mb8nEOFFk8DrYLpHvhKC3grJD2ardfKOzmbmJvEf/tPIqCY+sNcwZIY8ZD7IkB2l7/pqhUhqm7hLA==", - "dev": true, - "peer": true - }, - "node_modules/@webassemblyjs/helper-numbers": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.6.tgz", - "integrity": "sha512-vUIhZ8LZoIWHBohiEObxVm6hwP034jwmc9kuq5GdHZH0wiLVLIPcMCdpJzG4C11cHoQ25TFIQj9kaVADVX7N3g==", - "dev": true, - "peer": true, - "dependencies": { - "@webassemblyjs/floating-point-hex-parser": "1.11.6", - "@webassemblyjs/helper-api-error": "1.11.6", - "@xtuc/long": "4.2.2" - } - }, - "node_modules/@webassemblyjs/helper-wasm-bytecode": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.6.tgz", - "integrity": "sha512-sFFHKwcmBprO9e7Icf0+gddyWYDViL8bpPjJJl0WHxCdETktXdmtWLGVzoHbqUcY4Be1LkNfwTmXOJUFZYSJdA==", - "dev": true, - "peer": true - }, - "node_modules/@webassemblyjs/helper-wasm-section": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.11.6.tgz", - "integrity": "sha512-LPpZbSOwTpEC2cgn4hTydySy1Ke+XEu+ETXuoyvuyezHO3Kjdu90KK95Sh9xTbmjrCsUwvWwCOQQNta37VrS9g==", - "dev": true, - "peer": true, - "dependencies": { - "@webassemblyjs/ast": "1.11.6", - "@webassemblyjs/helper-buffer": "1.11.6", - "@webassemblyjs/helper-wasm-bytecode": "1.11.6", - "@webassemblyjs/wasm-gen": "1.11.6" - } - }, - "node_modules/@webassemblyjs/ieee754": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.11.6.tgz", - "integrity": "sha512-LM4p2csPNvbij6U1f19v6WR56QZ8JcHg3QIJTlSwzFcmx6WSORicYj6I63f9yU1kEUtrpG+kjkiIAkevHpDXrg==", - "dev": true, - "peer": true, - "dependencies": { - "@xtuc/ieee754": "^1.2.0" - } - }, - "node_modules/@webassemblyjs/leb128": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.11.6.tgz", - "integrity": "sha512-m7a0FhE67DQXgouf1tbN5XQcdWoNgaAuoULHIfGFIEVKA6tu/edls6XnIlkmS6FrXAquJRPni3ZZKjw6FSPjPQ==", - "dev": true, - "peer": true, - "dependencies": { - "@xtuc/long": "4.2.2" - } - }, - "node_modules/@webassemblyjs/utf8": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.11.6.tgz", - "integrity": "sha512-vtXf2wTQ3+up9Zsg8sa2yWiQpzSsMyXj0qViVP6xKGCUT8p8YJ6HqI7l5eCnWx1T/FYdsv07HQs2wTFbbof/RA==", - "dev": true, - "peer": true - }, - "node_modules/@webassemblyjs/wasm-edit": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.6.tgz", - "integrity": "sha512-Ybn2I6fnfIGuCR+Faaz7YcvtBKxvoLV3Lebn1tM4o/IAJzmi9AWYIPWpyBfU8cC+JxAO57bk4+zdsTjJR+VTOw==", - "dev": true, - "peer": true, - "dependencies": { - "@webassemblyjs/ast": "1.11.6", - "@webassemblyjs/helper-buffer": "1.11.6", - "@webassemblyjs/helper-wasm-bytecode": "1.11.6", - "@webassemblyjs/helper-wasm-section": "1.11.6", - "@webassemblyjs/wasm-gen": "1.11.6", - "@webassemblyjs/wasm-opt": "1.11.6", - "@webassemblyjs/wasm-parser": "1.11.6", - "@webassemblyjs/wast-printer": "1.11.6" - } - }, - "node_modules/@webassemblyjs/wasm-gen": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.6.tgz", - "integrity": "sha512-3XOqkZP/y6B4F0PBAXvI1/bky7GryoogUtfwExeP/v7Nzwo1QLcq5oQmpKlftZLbT+ERUOAZVQjuNVak6UXjPA==", - "dev": true, - "peer": true, - "dependencies": { - "@webassemblyjs/ast": "1.11.6", - "@webassemblyjs/helper-wasm-bytecode": "1.11.6", - "@webassemblyjs/ieee754": "1.11.6", - "@webassemblyjs/leb128": "1.11.6", - "@webassemblyjs/utf8": "1.11.6" - } - }, - "node_modules/@webassemblyjs/wasm-opt": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.6.tgz", - "integrity": "sha512-cOrKuLRE7PCe6AsOVl7WasYf3wbSo4CeOk6PkrjS7g57MFfVUF9u6ysQBBODX0LdgSvQqRiGz3CXvIDKcPNy4g==", - "dev": true, - "peer": true, - "dependencies": { - "@webassemblyjs/ast": "1.11.6", - "@webassemblyjs/helper-buffer": "1.11.6", - "@webassemblyjs/wasm-gen": "1.11.6", - "@webassemblyjs/wasm-parser": "1.11.6" - } - }, - "node_modules/@webassemblyjs/wasm-parser": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.6.tgz", - "integrity": "sha512-6ZwPeGzMJM3Dqp3hCsLgESxBGtT/OeCvCZ4TA1JUPYgmhAx38tTPR9JaKy0S5H3evQpO/h2uWs2j6Yc/fjkpTQ==", - "dev": true, - "peer": true, - "dependencies": { - "@webassemblyjs/ast": "1.11.6", - "@webassemblyjs/helper-api-error": "1.11.6", - "@webassemblyjs/helper-wasm-bytecode": "1.11.6", - "@webassemblyjs/ieee754": "1.11.6", - "@webassemblyjs/leb128": "1.11.6", - "@webassemblyjs/utf8": "1.11.6" - } - }, - "node_modules/@webassemblyjs/wast-printer": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.11.6.tgz", - "integrity": "sha512-JM7AhRcE+yW2GWYaKeHL5vt4xqee5N2WcezptmgyhNS+ScggqcT1OtXykhAb13Sn5Yas0j2uv9tHgrjwvzAP4A==", - "dev": true, - "peer": true, - "dependencies": { - "@webassemblyjs/ast": "1.11.6", - "@xtuc/long": "4.2.2" - } - }, - "node_modules/@xtuc/ieee754": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", - "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", - "dev": true, - "peer": true - }, - "node_modules/@xtuc/long": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", - "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", - "dev": true, - "peer": true - }, - "node_modules/abab": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.6.tgz", - "integrity": "sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA==", - "dev": true - }, - "node_modules/abort-controller": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", - "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", - "dependencies": { - "event-target-shim": "^5.0.0" - }, - "engines": { - "node": ">=6.5" - } - }, - "node_modules/abstract-level": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/abstract-level/-/abstract-level-1.0.3.tgz", - "integrity": "sha512-t6jv+xHy+VYwc4xqZMn2Pa9DjcdzvzZmQGRjTFc8spIbRGHgBrEKbPq+rYXc7CCo0lxgYvSgKVg9qZAhpVQSjA==", - "dependencies": { - "buffer": "^6.0.3", - "catering": "^2.1.0", - "is-buffer": "^2.0.5", - "level-supports": "^4.0.0", - "level-transcoder": "^1.0.1", - "module-error": "^1.0.1", - "queue-microtask": "^1.2.3" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/accepts": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", - "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", - "dev": true, - "dependencies": { - "mime-types": "~2.1.34", - "negotiator": "0.6.3" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/acorn": { - "version": "8.10.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.10.0.tgz", - "integrity": "sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw==", - "dev": true, - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/acorn-import-assertions": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.9.0.tgz", - "integrity": "sha512-cmMwop9x+8KFhxvKrKfPYmN6/pKTYYHBqLa0DfvVZcKMJWNyWLnaqND7dx/qn66R7ewM1UX5XMaDVP5wlVTaVA==", - "dev": true, - "peer": true, - "peerDependencies": { - "acorn": "^8" - } - }, - "node_modules/acorn-jsx": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", - "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", - "dev": true, - "peerDependencies": { - "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" - } - }, - "node_modules/ajv": { - "version": "8.12.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", - "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", - "dependencies": { - "fast-deep-equal": "^3.1.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/ansi-colors": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", - "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/anymatch": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", - "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", - "dev": true, - "dependencies": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true - }, - "node_modules/array-union": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", - "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/asn1.js": { - "version": "5.4.1", - "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-5.4.1.tgz", - "integrity": "sha512-+I//4cYPccV8LdmBLiX8CYvf9Sp3vQsrqu2QNXRcrbiWvcx/UdlFiqUJJzxRQxgsZmvhXhn4cSKeSmoFjVdupA==", - "dev": true, - "dependencies": { - "bn.js": "^4.0.0", - "inherits": "^2.0.1", - "minimalistic-assert": "^1.0.0", - "safer-buffer": "^2.1.0" - } - }, - "node_modules/asn1.js/node_modules/bn.js": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", - "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", - "dev": true - }, - "node_modules/assert": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/assert/-/assert-2.0.0.tgz", - "integrity": "sha512-se5Cd+js9dXJnu6Ag2JFc00t+HmHOen+8Q+L7O9zI0PqQXr20uk2J0XQqMxZEeo5U50o8Nvmmx7dZrl+Ufr35A==", - "dev": true, - "dependencies": { - "es6-object-assign": "^1.1.0", - "is-nan": "^1.2.1", - "object-is": "^1.0.1", - "util": "^0.12.0" - } - }, - "node_modules/assertion-error": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", - "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", - "engines": { - "node": "*" - } - }, - "node_modules/available-typed-arrays": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz", - "integrity": "sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==", - "dev": true, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true - }, - "node_modules/base64-js": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", - "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/base64id": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz", - "integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==", - "dev": true, - "engines": { - "node": "^4.5.0 || >= 5.9" - } - }, - "node_modules/binary-extensions": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", - "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/bl": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/bl/-/bl-5.1.0.tgz", - "integrity": "sha512-tv1ZJHLfTDnXE6tMHv73YgSJaWR2AFuPwMntBe7XL/GBFHnT0CLnsHMogfk5+GzCDC5ZWarSCYaIGATZt9dNsQ==", - "dependencies": { - "buffer": "^6.0.3", - "inherits": "^2.0.4", - "readable-stream": "^3.4.0" - } - }, - "node_modules/bl/node_modules/readable-stream": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", - "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/blockstore-core": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/blockstore-core/-/blockstore-core-4.2.0.tgz", - "integrity": "sha512-F8BCobc75D+9/+hUD+5cixbU6zmZA+lBgNiuBkNlJqRgmAaBBvLOQF6Ad9Jei0Nvmy2a1jaF4CiN76W1apIghA==", - "dependencies": { - "err-code": "^3.0.1", - "interface-blockstore": "^5.0.0", - "interface-store": "^5.0.0", - "multiformats": "^11.0.2" - }, - "engines": { - "node": ">=16.0.0", - "npm": ">=7.0.0" - } - }, - "node_modules/bn.js": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.1.tgz", - "integrity": "sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ==", - "dev": true - }, - "node_modules/body-parser": { - "version": "1.20.2", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz", - "integrity": "sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==", - "dev": true, - "dependencies": { - "bytes": "3.1.2", - "content-type": "~1.0.5", - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "on-finished": "2.4.1", - "qs": "6.11.0", - "raw-body": "2.5.2", - "type-is": "~1.6.18", - "unpipe": "1.0.0" - }, - "engines": { - "node": ">= 0.8", - "npm": "1.2.8000 || >= 1.4.16" - } - }, - "node_modules/body-parser/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/body-parser/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "dev": true - }, - "node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "dev": true, - "dependencies": { - "fill-range": "^7.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/brorand": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz", - "integrity": "sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w==", - "dev": true - }, - "node_modules/browser-level": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/browser-level/-/browser-level-1.0.1.tgz", - "integrity": "sha512-XECYKJ+Dbzw0lbydyQuJzwNXtOpbMSq737qxJN11sIRTErOMShvDpbzTlgju7orJKvx4epULolZAuJGLzCmWRQ==", - "dependencies": { - "abstract-level": "^1.0.2", - "catering": "^2.1.1", - "module-error": "^1.0.2", - "run-parallel-limit": "^1.1.0" - } - }, - "node_modules/browser-resolve": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/browser-resolve/-/browser-resolve-2.0.0.tgz", - "integrity": "sha512-7sWsQlYL2rGLy2IWm8WL8DCTJvYLc/qlOnsakDac87SOoCd16WLsaAMdCiAqsTNHIe+SXfaqyxyo6THoWqs8WQ==", - "dev": true, - "dependencies": { - "resolve": "^1.17.0" - } - }, - "node_modules/browser-stdout": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", - "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", - "dev": true - }, - "node_modules/browserify-aes": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz", - "integrity": "sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==", - "dev": true, - "dependencies": { - "buffer-xor": "^1.0.3", - "cipher-base": "^1.0.0", - "create-hash": "^1.1.0", - "evp_bytestokey": "^1.0.3", - "inherits": "^2.0.1", - "safe-buffer": "^5.0.1" - } - }, - "node_modules/browserify-cipher": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/browserify-cipher/-/browserify-cipher-1.0.1.tgz", - "integrity": "sha512-sPhkz0ARKbf4rRQt2hTpAHqn47X3llLkUGn+xEJzLjwY8LRs2p0v7ljvI5EyoRO/mexrNunNECisZs+gw2zz1w==", - "dev": true, - "dependencies": { - "browserify-aes": "^1.0.4", - "browserify-des": "^1.0.0", - "evp_bytestokey": "^1.0.0" - } - }, - "node_modules/browserify-des": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/browserify-des/-/browserify-des-1.0.2.tgz", - "integrity": "sha512-BioO1xf3hFwz4kc6iBhI3ieDFompMhrMlnDFC4/0/vd5MokpuAc3R+LYbwTA9A5Yc9pq9UYPqffKpW2ObuwX5A==", - "dev": true, - "dependencies": { - "cipher-base": "^1.0.1", - "des.js": "^1.0.0", - "inherits": "^2.0.1", - "safe-buffer": "^5.1.2" - } - }, - "node_modules/browserify-rsa": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.1.0.tgz", - "integrity": "sha512-AdEER0Hkspgno2aR97SAf6vi0y0k8NuOpGnVH3O99rcA5Q6sh8QxcngtHuJ6uXwnfAXNM4Gn1Gb7/MV1+Ymbog==", - "dev": true, - "dependencies": { - "bn.js": "^5.0.0", - "randombytes": "^2.0.1" - } - }, - "node_modules/browserify-sign": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/browserify-sign/-/browserify-sign-4.2.1.tgz", - "integrity": "sha512-/vrA5fguVAKKAVTNJjgSm1tRQDHUU6DbwO9IROu/0WAzC8PKhucDSh18J0RMvVeHAn5puMd+QHC2erPRNf8lmg==", - "dev": true, - "dependencies": { - "bn.js": "^5.1.1", - "browserify-rsa": "^4.0.1", - "create-hash": "^1.2.0", - "create-hmac": "^1.1.7", - "elliptic": "^6.5.3", - "inherits": "^2.0.4", - "parse-asn1": "^5.1.5", - "readable-stream": "^3.6.0", - "safe-buffer": "^5.2.0" - } - }, - "node_modules/browserify-sign/node_modules/readable-stream": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", - "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", - "dev": true, - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/browserify-sign/node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/browserify-zlib": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/browserify-zlib/-/browserify-zlib-0.2.0.tgz", - "integrity": "sha512-Z942RysHXmJrhqk88FmKBVq/v5tqmSkDz7p54G/MGyjMnCFFnC79XWNbg+Vta8W6Wb2qtSZTSxIGkJrRpCFEiA==", - "dev": true, - "dependencies": { - "pako": "~1.0.5" - } - }, - "node_modules/browserslist": { - "version": "4.21.10", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.10.tgz", - "integrity": "sha512-bipEBdZfVH5/pwrvqc+Ub0kUPVfGUhlKxbvfD+z1BDnPEO/X98ruXGA1WP5ASpAFKan7Qr6j736IacbZQuAlKQ==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "peer": true, - "dependencies": { - "caniuse-lite": "^1.0.30001517", - "electron-to-chromium": "^1.4.477", - "node-releases": "^2.0.13", - "update-browserslist-db": "^1.0.11" - }, - "bin": { - "browserslist": "cli.js" - }, - "engines": { - "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" - } - }, - "node_modules/buffer": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", - "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "dependencies": { - "base64-js": "^1.3.1", - "ieee754": "^1.2.1" - } - }, - "node_modules/buffer-from": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", - "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", - "dev": true, - "peer": true - }, - "node_modules/buffer-xor": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/buffer-xor/-/buffer-xor-1.0.3.tgz", - "integrity": "sha512-571s0T7nZWK6vB67HI5dyUF7wXiNcfaPPPTl6zYCNApANjIvYJTg7hlud/+cJpdAhS7dVzqMLmfhfHR3rAcOjQ==", - "dev": true - }, - "node_modules/builtin-status-codes": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz", - "integrity": "sha512-HpGFw18DgFWlncDfjTa2rcQ4W88O1mC8e8yZ2AvQY5KDaktSTwo+KRf6nHK6FRI5FyRyb/5T6+TSxfP7QyGsmQ==", - "dev": true - }, - "node_modules/bytes": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", - "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", - "dev": true, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/c8": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/c8/-/c8-8.0.0.tgz", - "integrity": "sha512-XHA5vSfCLglAc0Xt8eLBZMv19lgiBSjnb1FLAQgnwkuhJYEonpilhEB4Ea3jPAbm0FhD6VVJrc0z73jPe7JyGQ==", - "dev": true, - "dependencies": { - "@bcoe/v8-coverage": "^0.2.3", - "@istanbuljs/schema": "^0.1.3", - "find-up": "^5.0.0", - "foreground-child": "^2.0.0", - "istanbul-lib-coverage": "^3.2.0", - "istanbul-lib-report": "^3.0.0", - "istanbul-reports": "^3.1.4", - "rimraf": "^3.0.2", - "test-exclude": "^6.0.0", - "v8-to-istanbul": "^9.0.0", - "yargs": "^16.2.0", - "yargs-parser": "^20.2.9" - }, - "bin": { - "c8": "bin/c8.js" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/c8/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/call-bind": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", - "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", - "dev": true, - "dependencies": { - "function-bind": "^1.1.1", - "get-intrinsic": "^1.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/camelcase": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", - "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/caniuse-lite": { - "version": "1.0.30001519", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001519.tgz", - "integrity": "sha512-0QHgqR+Jv4bxHMp8kZ1Kn8CH55OikjKJ6JmKkZYP1F3D7w+lnFXF70nG5eNfsZS89jadi5Ywy5UCSKLAglIRkg==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/caniuse-lite" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "peer": true - }, - "node_modules/canonicalize": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/canonicalize/-/canonicalize-2.0.0.tgz", - "integrity": "sha512-ulDEYPv7asdKvqahuAY35c1selLdzDwHqugK92hfkzvlDCwXRRelDkR+Er33md/PtnpqHemgkuDPanZ4fiYZ8w==" - }, - "node_modules/catering": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/catering/-/catering-2.1.1.tgz", - "integrity": "sha512-K7Qy8O9p76sL3/3m7/zLKbRkyOlSZAgzEaLhyj2mXS8PsCud2Eo4hAb8aLtZqHh0QGqLcb9dlJSu6lHRVENm1w==", - "engines": { - "node": ">=6" - } - }, - "node_modules/cborg": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/cborg/-/cborg-2.0.3.tgz", - "integrity": "sha512-f1IbyqgRLQK4ruNM+V3WikfYfXQg/f/zC1oneOw1P7F/Dn2OJX6MaXIdei3JMpz361IjY7OENBKcE53nkJFVCQ==", - "bin": { - "cborg": "cli.js" - } - }, - "node_modules/chai": { - "version": "4.3.7", - "resolved": "https://registry.npmjs.org/chai/-/chai-4.3.7.tgz", - "integrity": "sha512-HLnAzZ2iupm25PlN0xFreAlBA5zaBSv3og0DdeGA4Ar6h6rJ3A0rolRUKJhSF2V10GZKDgWF/VmAEsNWjCRB+A==", - "dependencies": { - "assertion-error": "^1.1.0", - "check-error": "^1.0.2", - "deep-eql": "^4.1.2", - "get-func-name": "^2.0.0", - "loupe": "^2.3.1", - "pathval": "^1.1.1", - "type-detect": "^4.0.5" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/chai-as-promised": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/chai-as-promised/-/chai-as-promised-7.1.1.tgz", - "integrity": "sha512-azL6xMoi+uxu6z4rhWQ1jbdUhOMhis2PvscD/xjLqNMkv3BPPp2JyyuTHOrf9BOosGpNQ11v6BKv/g57RXbiaA==", - "dev": true, - "dependencies": { - "check-error": "^1.0.2" - }, - "peerDependencies": { - "chai": ">= 2.1.2 < 5" - } - }, - "node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/check-error": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz", - "integrity": "sha512-BrgHpW9NURQgzoNyjfq0Wu6VFO6D7IZEmJNdtgNqpzGG8RuNFHt2jQxWlAs4HMe119chBnv+34syEZtc6IhLtA==", - "engines": { - "node": "*" - } - }, - "node_modules/chokidar": { - "version": "3.5.3", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", - "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://paulmillr.com/funding/" - } - ], - "dependencies": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" - }, - "engines": { - "node": ">= 8.10.0" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" - } - }, - "node_modules/chokidar/node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/chrome-trace-event": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz", - "integrity": "sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg==", - "dev": true, - "peer": true, - "engines": { - "node": ">=6.0" - } - }, - "node_modules/ci-info": { - "version": "3.8.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.8.0.tgz", - "integrity": "sha512-eXTggHWSooYhq49F2opQhuHWgzucfF2YgODK4e1566GQs5BIfP30B0oenwBJHfWxAs2fyPB1s7Mg949zLf61Yw==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/sibiraj-s" - } - ], - "engines": { - "node": ">=8" - } - }, - "node_modules/cipher-base": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.4.tgz", - "integrity": "sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q==", - "dev": true, - "dependencies": { - "inherits": "^2.0.1", - "safe-buffer": "^5.0.1" - } - }, - "node_modules/classic-level": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/classic-level/-/classic-level-1.3.0.tgz", - "integrity": "sha512-iwFAJQYtqRTRM0F6L8h4JCt00ZSGdOyqh7yVrhhjrOpFhmBjNlRUey64MCiyo6UmQHMJ+No3c81nujPv+n9yrg==", - "hasInstallScript": true, - "dependencies": { - "abstract-level": "^1.0.2", - "catering": "^2.1.0", - "module-error": "^1.0.1", - "napi-macros": "^2.2.2", - "node-gyp-build": "^4.3.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/cliui": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", - "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", - "dev": true, - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^7.0.0" - } - }, - "node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/commander": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", - "dev": true, - "peer": true - }, - "node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true - }, - "node_modules/connect": { - "version": "3.7.0", - "resolved": "https://registry.npmjs.org/connect/-/connect-3.7.0.tgz", - "integrity": "sha512-ZqRXc+tZukToSNmh5C2iWMSoV3X1YUcPbqEM4DkEG5tNQXrQUZCNVGGv3IuicnkMtPfGf3Xtp8WCXs295iQ1pQ==", - "dev": true, - "dependencies": { - "debug": "2.6.9", - "finalhandler": "1.1.2", - "parseurl": "~1.3.3", - "utils-merge": "1.0.1" - }, - "engines": { - "node": ">= 0.10.0" - } - }, - "node_modules/connect/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/connect/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "dev": true - }, - "node_modules/console-browserify": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/console-browserify/-/console-browserify-1.2.0.tgz", - "integrity": "sha512-ZMkYO/LkF17QvCPqM0gxw8yUzigAOZOSWSHg91FH6orS7vcEj5dVZTidN2fQ14yBSdg97RqhSNwLUXInd52OTA==", - "dev": true - }, - "node_modules/constants-browserify": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/constants-browserify/-/constants-browserify-1.0.0.tgz", - "integrity": "sha512-xFxOwqIzR/e1k1gLiWEophSCMqXcwVHIH7akf7b/vxcUeGunlj3hvZaaqxwHsTgn+IndtkQJgSztIDWeumWJDQ==", - "dev": true - }, - "node_modules/content-type": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", - "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", - "dev": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/convert-source-map": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", - "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", - "dev": true - }, - "node_modules/cookie": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz", - "integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==", - "dev": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/cors": { - "version": "2.8.5", - "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", - "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", - "dev": true, - "dependencies": { - "object-assign": "^4", - "vary": "^1" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/create-ecdh": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/create-ecdh/-/create-ecdh-4.0.4.tgz", - "integrity": "sha512-mf+TCx8wWc9VpuxfP2ht0iSISLZnt0JgWlrOKZiNqyUZWnjIaCIVNQArMHnCZKfEYRg6IM7A+NeJoN8gf/Ws0A==", - "dev": true, - "dependencies": { - "bn.js": "^4.1.0", - "elliptic": "^6.5.3" - } - }, - "node_modules/create-ecdh/node_modules/bn.js": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", - "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", - "dev": true - }, - "node_modules/create-hash": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz", - "integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==", - "dev": true, - "dependencies": { - "cipher-base": "^1.0.1", - "inherits": "^2.0.1", - "md5.js": "^1.3.4", - "ripemd160": "^2.0.1", - "sha.js": "^2.4.0" - } - }, - "node_modules/create-hmac": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz", - "integrity": "sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==", - "dev": true, - "dependencies": { - "cipher-base": "^1.0.3", - "create-hash": "^1.1.0", - "inherits": "^2.0.1", - "ripemd160": "^2.0.0", - "safe-buffer": "^5.0.1", - "sha.js": "^2.4.8" - } - }, - "node_modules/create-require": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", - "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", - "dev": true - }, - "node_modules/cross-fetch": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-4.0.0.tgz", - "integrity": "sha512-e4a5N8lVvuLgAWgnCrLr2PP0YyDOTHa9H/Rj54dirp61qXnNq46m82bRhNqIA5VccJtWBvPTFRV3TtvHUKPB1g==", - "dependencies": { - "node-fetch": "^2.6.12" - } - }, - "node_modules/cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", - "dev": true, - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/crypto-browserify": { - "version": "3.12.0", - "resolved": "https://registry.npmjs.org/crypto-browserify/-/crypto-browserify-3.12.0.tgz", - "integrity": "sha512-fz4spIh+znjO2VjL+IdhEpRJ3YN6sMzITSBijk6FK2UvTqruSQW+/cCZTSNsMiZNvUeq0CqurF+dAbyiGOY6Wg==", - "dev": true, - "dependencies": { - "browserify-cipher": "^1.0.0", - "browserify-sign": "^4.0.0", - "create-ecdh": "^4.0.0", - "create-hash": "^1.1.0", - "create-hmac": "^1.1.0", - "diffie-hellman": "^5.0.0", - "inherits": "^2.0.1", - "pbkdf2": "^3.0.3", - "public-encrypt": "^4.0.0", - "randombytes": "^2.0.0", - "randomfill": "^1.0.3" - }, - "engines": { - "node": "*" - } - }, - "node_modules/custom-event": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/custom-event/-/custom-event-1.0.1.tgz", - "integrity": "sha512-GAj5FOq0Hd+RsCGVJxZuKaIDXDf3h6GQoNEjFgbLLI/trgtavwUbSnZ5pVfg27DVCaWjIohryS0JFwIJyT2cMg==", - "dev": true - }, - "node_modules/date-format": { - "version": "4.0.14", - "resolved": "https://registry.npmjs.org/date-format/-/date-format-4.0.14.tgz", - "integrity": "sha512-39BOQLs9ZjKh0/patS9nrT8wc3ioX3/eA/zgbKNopnF2wCqJEoxywwwElATYvRsXdnOxA/OQeQoFZ3rFjVajhg==", - "dev": true, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/debug/node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" - }, - "node_modules/decamelize": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz", - "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/deep-eql": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-4.1.3.tgz", - "integrity": "sha512-WaEtAOpRA1MQ0eohqZjpGD8zdI0Ovsm8mmFhaDN8dvDZzyoUMcYDnf5Y6iu7HTXxf8JDS23qWa4a+hKCDyOPzw==", - "dependencies": { - "type-detect": "^4.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/deep-is": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", - "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", - "dev": true - }, - "node_modules/define-properties": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.0.tgz", - "integrity": "sha512-xvqAVKGfT1+UAvPwKTVw/njhdQ8ZhXK4lI0bCIuCMrp2up9nPnaDftrLtmpTazqd1o+UY4zgzU+avtMbDP+ldA==", - "dev": true, - "dependencies": { - "has-property-descriptors": "^1.0.0", - "object-keys": "^1.1.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/depd": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", - "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", - "dev": true, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/des.js": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/des.js/-/des.js-1.1.0.tgz", - "integrity": "sha512-r17GxjhUCjSRy8aiJpr8/UadFIzMzJGexI3Nmz4ADi9LYSFx4gTBp80+NaX/YsXWWLhpZ7v/v/ubEc/bCNfKwg==", - "dev": true, - "dependencies": { - "inherits": "^2.0.1", - "minimalistic-assert": "^1.0.0" - } - }, - "node_modules/destroy": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", - "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", - "dev": true, - "engines": { - "node": ">= 0.8", - "npm": "1.2.8000 || >= 1.4.16" - } - }, - "node_modules/di": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/di/-/di-0.0.1.tgz", - "integrity": "sha512-uJaamHkagcZtHPqCIHZxnFrXlunQXgBOsZSUOWwFw31QJCAbyTBoHMW75YOTur5ZNx8pIeAKgf6GWIgaqqiLhA==", - "dev": true - }, - "node_modules/diff": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz", - "integrity": "sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==", - "dev": true, - "engines": { - "node": ">=0.3.1" - } - }, - "node_modules/diffie-hellman": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz", - "integrity": "sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg==", - "dev": true, - "dependencies": { - "bn.js": "^4.1.0", - "miller-rabin": "^4.0.0", - "randombytes": "^2.0.0" - } - }, - "node_modules/diffie-hellman/node_modules/bn.js": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", - "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", - "dev": true - }, - "node_modules/dir-glob": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", - "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", - "dev": true, - "dependencies": { - "path-type": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/doctrine": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", - "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", - "dev": true, - "dependencies": { - "esutils": "^2.0.2" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/dom-serialize": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/dom-serialize/-/dom-serialize-2.2.1.tgz", - "integrity": "sha512-Yra4DbvoW7/Z6LBN560ZwXMjoNOSAN2wRsKFGc4iBeso+mpIA6qj1vfdf9HpMaKAqG6wXTy+1SYEzmNpKXOSsQ==", - "dev": true, - "dependencies": { - "custom-event": "~1.0.0", - "ent": "~2.2.0", - "extend": "^3.0.0", - "void-elements": "^2.0.0" - } - }, - "node_modules/domain-browser": { - "version": "4.22.0", - "resolved": "https://registry.npmjs.org/domain-browser/-/domain-browser-4.22.0.tgz", - "integrity": "sha512-IGBwjF7tNk3cwypFNH/7bfzBcgSCbaMOD3GsaY1AU/JRrnHnYgEM0+9kQt52iZxjNsjBtJYtao146V+f8jFZNw==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://bevry.me/fund" - } - }, - "node_modules/eciesjs": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/eciesjs/-/eciesjs-0.4.0.tgz", - "integrity": "sha512-z4dEeaH16xxYVgtxJ8YVwpifH4Keg4gyp5F451mnDNwbAN3MgL5jcoEQGpqJrapv/zW8KwDnXG21Dw5B0hqvmw==", - "dependencies": { - "@noble/curves": "^1.1.0" - } - }, - "node_modules/ed2curve": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/ed2curve/-/ed2curve-0.3.0.tgz", - "integrity": "sha512-8w2fmmq3hv9rCrcI7g9hms2pMunQr1JINfcjwR9tAyZqhtyaMN991lF/ZfHfr5tzZQ8c7y7aBgZbjfbd0fjFwQ==", - "dependencies": { - "tweetnacl": "1.x.x" - } - }, - "node_modules/ee-first": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", - "dev": true - }, - "node_modules/electron-to-chromium": { - "version": "1.4.485", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.485.tgz", - "integrity": "sha512-1ndQ5IBNEnFirPwvyud69GHL+31FkE09gH/CJ6m3KCbkx3i0EVOrjwz4UNxRmN9H8OVHbC6vMRZGN1yCvjSs9w==", - "dev": true, - "peer": true - }, - "node_modules/elliptic": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.4.tgz", - "integrity": "sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ==", - "dev": true, - "dependencies": { - "bn.js": "^4.11.9", - "brorand": "^1.1.0", - "hash.js": "^1.0.0", - "hmac-drbg": "^1.0.1", - "inherits": "^2.0.4", - "minimalistic-assert": "^1.0.1", - "minimalistic-crypto-utils": "^1.0.1" - } - }, - "node_modules/elliptic/node_modules/bn.js": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", - "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", - "dev": true - }, - "node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true - }, - "node_modules/encodeurl": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", - "dev": true, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/engine.io": { - "version": "6.5.2", - "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.5.2.tgz", - "integrity": "sha512-IXsMcGpw/xRfjra46sVZVHiSWo/nJ/3g1337q9KNXtS6YRzbW5yIzTCb9DjhrBe7r3GZQR0I4+nq+4ODk5g/cA==", - "dev": true, - "dependencies": { - "@types/cookie": "^0.4.1", - "@types/cors": "^2.8.12", - "@types/node": ">=10.0.0", - "accepts": "~1.3.4", - "base64id": "2.0.0", - "cookie": "~0.4.1", - "cors": "~2.8.5", - "debug": "~4.3.1", - "engine.io-parser": "~5.2.1", - "ws": "~8.11.0" - }, - "engines": { - "node": ">=10.2.0" - } - }, - "node_modules/engine.io-parser": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.1.tgz", - "integrity": "sha512-9JktcM3u18nU9N2Lz3bWeBgxVgOKpw7yhRaoxQA3FUDZzzw+9WlA6p4G4u0RixNkg14fH7EfEc/RhpurtiROTQ==", - "dev": true, - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/enhanced-resolve": { - "version": "5.15.0", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.15.0.tgz", - "integrity": "sha512-LXYT42KJ7lpIKECr2mAXIaMldcNCh/7E0KBKOu4KSfkHmP+mZmSs+8V5gBAqisWBy0OO4W5Oyys0GO1Y8KtdKg==", - "dev": true, - "peer": true, - "dependencies": { - "graceful-fs": "^4.2.4", - "tapable": "^2.2.0" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/ent": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/ent/-/ent-2.2.0.tgz", - "integrity": "sha512-GHrMyVZQWvTIdDtpiEXdHZnFQKzeO09apj8Cbl4pKWy4i0Oprcq17usfDt5aO63swf0JOeMWjWQE/LzgSRuWpA==", - "dev": true - }, - "node_modules/err-code": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/err-code/-/err-code-3.0.1.tgz", - "integrity": "sha512-GiaH0KJUewYok+eeY05IIgjtAe4Yltygk9Wqp1V5yVWLdhf0hYZchRjNIT9bb0mSwRcIusT3cx7PJUf3zEIfUA==" - }, - "node_modules/es-module-lexer": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.3.0.tgz", - "integrity": "sha512-vZK7T0N2CBmBOixhmjdqx2gWVbFZ4DXZ/NyRMZVlJXPa7CyFS+/a4QQsDGDQy9ZfEzxFuNEsMLeQJnKP2p5/JA==", - "dev": true, - "peer": true - }, - "node_modules/es6-object-assign": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/es6-object-assign/-/es6-object-assign-1.1.0.tgz", - "integrity": "sha512-MEl9uirslVwqQU369iHNWZXsI8yaZYGg/D65aOgZkeyFJwHYSxilf7rQzXKI7DdDuBPrBXbfk3sl9hJhmd5AUw==", - "dev": true - }, - "node_modules/esbuild": { - "version": "0.16.17", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.16.17.tgz", - "integrity": "sha512-G8LEkV0XzDMNwXKgM0Jwu3nY3lSTwSGY6XbxM9cr9+s0T/qSV1q1JVPBGzm3dcjhCic9+emZDmMffkwgPeOeLg==", - "dev": true, - "hasInstallScript": true, - "bin": { - "esbuild": "bin/esbuild" - }, - "engines": { - "node": ">=12" - }, - "optionalDependencies": { - "@esbuild/android-arm": "0.16.17", - "@esbuild/android-arm64": "0.16.17", - "@esbuild/android-x64": "0.16.17", - "@esbuild/darwin-arm64": "0.16.17", - "@esbuild/darwin-x64": "0.16.17", - "@esbuild/freebsd-arm64": "0.16.17", - "@esbuild/freebsd-x64": "0.16.17", - "@esbuild/linux-arm": "0.16.17", - "@esbuild/linux-arm64": "0.16.17", - "@esbuild/linux-ia32": "0.16.17", - "@esbuild/linux-loong64": "0.16.17", - "@esbuild/linux-mips64el": "0.16.17", - "@esbuild/linux-ppc64": "0.16.17", - "@esbuild/linux-riscv64": "0.16.17", - "@esbuild/linux-s390x": "0.16.17", - "@esbuild/linux-x64": "0.16.17", - "@esbuild/netbsd-x64": "0.16.17", - "@esbuild/openbsd-x64": "0.16.17", - "@esbuild/sunos-x64": "0.16.17", - "@esbuild/win32-arm64": "0.16.17", - "@esbuild/win32-ia32": "0.16.17", - "@esbuild/win32-x64": "0.16.17" - } - }, - "node_modules/escalade": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", - "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/escape-html": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", - "dev": true - }, - "node_modules/escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/eslint": { - "version": "8.39.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.39.0.tgz", - "integrity": "sha512-mwiok6cy7KTW7rBpo05k6+p4YVZByLNjAZ/ACB9DRCu4YDRwjXI01tWHp6KAUWelsBetTxKK/2sHB0vdS8Z2Og==", - "dev": true, - "dependencies": { - "@eslint-community/eslint-utils": "^4.2.0", - "@eslint-community/regexpp": "^4.4.0", - "@eslint/eslintrc": "^2.0.2", - "@eslint/js": "8.39.0", - "@humanwhocodes/config-array": "^0.11.8", - "@humanwhocodes/module-importer": "^1.0.1", - "@nodelib/fs.walk": "^1.2.8", - "ajv": "^6.10.0", - "chalk": "^4.0.0", - "cross-spawn": "^7.0.2", - "debug": "^4.3.2", - "doctrine": "^3.0.0", - "escape-string-regexp": "^4.0.0", - "eslint-scope": "^7.2.0", - "eslint-visitor-keys": "^3.4.0", - "espree": "^9.5.1", - "esquery": "^1.4.2", - "esutils": "^2.0.2", - "fast-deep-equal": "^3.1.3", - "file-entry-cache": "^6.0.1", - "find-up": "^5.0.0", - "glob-parent": "^6.0.2", - "globals": "^13.19.0", - "grapheme-splitter": "^1.0.4", - "ignore": "^5.2.0", - "import-fresh": "^3.0.0", - "imurmurhash": "^0.1.4", - "is-glob": "^4.0.0", - "is-path-inside": "^3.0.3", - "js-sdsl": "^4.1.4", - "js-yaml": "^4.1.0", - "json-stable-stringify-without-jsonify": "^1.0.1", - "levn": "^0.4.1", - "lodash.merge": "^4.6.2", - "minimatch": "^3.1.2", - "natural-compare": "^1.4.0", - "optionator": "^0.9.1", - "strip-ansi": "^6.0.1", - "strip-json-comments": "^3.1.0", - "text-table": "^0.2.0" - }, - "bin": { - "eslint": "bin/eslint.js" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/eslint-plugin-mocha": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-mocha/-/eslint-plugin-mocha-10.1.0.tgz", - "integrity": "sha512-xLqqWUF17llsogVOC+8C6/jvQ+4IoOREbN7ZCHuOHuD6cT5cDD4h7f2LgsZuzMAiwswWE21tO7ExaknHVDrSkw==", - "dev": true, - "dependencies": { - "eslint-utils": "^3.0.0", - "rambda": "^7.1.0" - }, - "engines": { - "node": ">=14.0.0" - }, - "peerDependencies": { - "eslint": ">=7.0.0" - } - }, - "node_modules/eslint-scope": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", - "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", - "dev": true, - "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^4.1.1" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/eslint-utils": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz", - "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==", - "dev": true, - "dependencies": { - "eslint-visitor-keys": "^2.0.0" - }, - "engines": { - "node": "^10.0.0 || ^12.0.0 || >= 14.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/mysticatea" - }, - "peerDependencies": { - "eslint": ">=5" - } - }, - "node_modules/eslint-utils/node_modules/eslint-visitor-keys": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", - "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", - "dev": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/eslint-visitor-keys": { - "version": "3.4.2", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.2.tgz", - "integrity": "sha512-8drBzUEyZ2llkpCA67iYrgEssKDUu68V8ChqqOfFupIaG/LCVPUT+CoGJpT77zJprs4T/W7p07LP7zAIMuweVw==", - "dev": true, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/eslint/node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, - "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/eslint/node_modules/eslint-scope": { - "version": "7.2.2", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", - "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", - "dev": true, - "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^5.2.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/eslint/node_modules/estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/eslint/node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true - }, - "node_modules/espree": { - "version": "9.6.1", - "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", - "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", - "dev": true, - "dependencies": { - "acorn": "^8.9.0", - "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^3.4.1" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/esquery": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz", - "integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==", - "dev": true, - "dependencies": { - "estraverse": "^5.1.0" - }, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/esquery/node_modules/estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/esrecurse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", - "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", - "dev": true, - "dependencies": { - "estraverse": "^5.2.0" - }, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/esrecurse/node_modules/estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/estraverse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", - "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", - "dev": true, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/esutils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/event-target-shim": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", - "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", - "engines": { - "node": ">=6" - } - }, - "node_modules/eventemitter3": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", - "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==" - }, - "node_modules/events": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", - "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", - "engines": { - "node": ">=0.8.x" - } - }, - "node_modules/evp_bytestokey": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz", - "integrity": "sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA==", - "dev": true, - "dependencies": { - "md5.js": "^1.3.4", - "safe-buffer": "^5.1.1" - } - }, - "node_modules/extend": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", - "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", - "dev": true - }, - "node_modules/fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" - }, - "node_modules/fast-glob": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.1.tgz", - "integrity": "sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg==", - "dev": true, - "dependencies": { - "@nodelib/fs.stat": "^2.0.2", - "@nodelib/fs.walk": "^1.2.3", - "glob-parent": "^5.1.2", - "merge2": "^1.3.0", - "micromatch": "^4.0.4" - }, - "engines": { - "node": ">=8.6.0" - } - }, - "node_modules/fast-glob/node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/fast-json-stable-stringify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true - }, - "node_modules/fast-levenshtein": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", - "dev": true - }, - "node_modules/fastq": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz", - "integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==", - "dev": true, - "dependencies": { - "reusify": "^1.0.4" - } - }, - "node_modules/file-entry-cache": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", - "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", - "dev": true, - "dependencies": { - "flat-cache": "^3.0.4" - }, - "engines": { - "node": "^10.12.0 || >=12.0.0" - } - }, - "node_modules/fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "dev": true, - "dependencies": { - "to-regex-range": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/finalhandler": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", - "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", - "dev": true, - "dependencies": { - "debug": "2.6.9", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "on-finished": "~2.3.0", - "parseurl": "~1.3.3", - "statuses": "~1.5.0", - "unpipe": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/finalhandler/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/finalhandler/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "dev": true - }, - "node_modules/finalhandler/node_modules/on-finished": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", - "integrity": "sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==", - "dev": true, - "dependencies": { - "ee-first": "1.1.1" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/find-up": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", - "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", - "dev": true, - "dependencies": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/flat": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", - "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", - "bin": { - "flat": "cli.js" - } - }, - "node_modules/flat-cache": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", - "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", - "dev": true, - "dependencies": { - "flatted": "^3.1.0", - "rimraf": "^3.0.2" - }, - "engines": { - "node": "^10.12.0 || >=12.0.0" - } - }, - "node_modules/flat-cache/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/flatted": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.7.tgz", - "integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==", - "dev": true - }, - "node_modules/follow-redirects": { - "version": "1.15.2", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz", - "integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://github.com/sponsors/RubenVerborgh" - } - ], - "engines": { - "node": ">=4.0" - }, - "peerDependenciesMeta": { - "debug": { - "optional": true - } - } - }, - "node_modules/for-each": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", - "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", - "dev": true, - "dependencies": { - "is-callable": "^1.1.3" - } - }, - "node_modules/foreground-child": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-2.0.0.tgz", - "integrity": "sha512-dCIq9FpEcyQyXKCkyzmlPTFNgrCzPudOe+mhvJU5zAtlBnGVy2yKxtfsxK2tQBThwq225jcvBjpw1Gr40uzZCA==", - "dev": true, - "dependencies": { - "cross-spawn": "^7.0.0", - "signal-exit": "^3.0.2" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/fs-extra": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", - "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", - "dev": true, - "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^4.0.0", - "universalify": "^0.1.0" - }, - "engines": { - "node": ">=6 <7 || >=8" - } - }, - "node_modules/fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "dev": true - }, - "node_modules/fsevents": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", - "dev": true, - "hasInstallScript": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", - "dev": true - }, - "node_modules/get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "dev": true, - "engines": { - "node": "6.* || 8.* || >= 10.*" - } - }, - "node_modules/get-func-name": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz", - "integrity": "sha512-Hm0ixYtaSZ/V7C8FJrtZIuBBI+iSgL+1Aq82zSu8VQNB4S3Gk8e7Qs3VwBDJAhmRZcFqkl3tQu36g/Foh5I5ig==", - "engines": { - "node": "*" - } - }, - "node_modules/get-intrinsic": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.1.tgz", - "integrity": "sha512-2DcsyfABl+gVHEfCOaTrWgyt+tb6MSEGmKq+kI5HwLbIYgjgmMcV8KQ41uaKz1xxUcn9tJtgFbQUEVcEbd0FYw==", - "dev": true, - "dependencies": { - "function-bind": "^1.1.1", - "has": "^1.0.3", - "has-proto": "^1.0.1", - "has-symbols": "^1.0.3" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "dev": true, - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/glob-parent": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", - "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", - "dev": true, - "dependencies": { - "is-glob": "^4.0.3" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/glob-to-regexp": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", - "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", - "dev": true, - "peer": true - }, - "node_modules/globals": { - "version": "13.20.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.20.0.tgz", - "integrity": "sha512-Qg5QtVkCy/kv3FUSlu4ukeZDVf9ee0iXLAUYX13gbR17bnejFTzr4iS9bY7kwCf1NztRNm1t91fjOiyx4CSwPQ==", - "dev": true, - "dependencies": { - "type-fest": "^0.20.2" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/globby": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", - "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", - "dev": true, - "dependencies": { - "array-union": "^2.1.0", - "dir-glob": "^3.0.1", - "fast-glob": "^3.2.9", - "ignore": "^5.2.0", - "merge2": "^1.4.1", - "slash": "^3.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/gopd": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", - "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", - "dev": true, - "dependencies": { - "get-intrinsic": "^1.1.3" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/graceful-fs": { - "version": "4.2.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", - "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", - "dev": true - }, - "node_modules/grapheme-splitter": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz", - "integrity": "sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==", - "dev": true - }, - "node_modules/hamt-sharding": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/hamt-sharding/-/hamt-sharding-3.0.2.tgz", - "integrity": "sha512-f0DzBD2tSmLFdFsLAvOflIBqFPjerbA7BfmwO8mVho/5hXwgyyYhv+ijIzidQf/DpDX3bRjAQvhGoBFj+DBvPw==", - "dependencies": { - "sparse-array": "^1.3.1", - "uint8arrays": "^4.0.2" - }, - "engines": { - "node": ">=16.0.0", - "npm": ">=7.0.0" - } - }, - "node_modules/has": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", - "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", - "dev": true, - "dependencies": { - "function-bind": "^1.1.1" - }, - "engines": { - "node": ">= 0.4.0" - } - }, - "node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/has-property-descriptors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz", - "integrity": "sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==", - "dev": true, - "dependencies": { - "get-intrinsic": "^1.1.1" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/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==", - "dev": true, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-symbols": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", - "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", - "dev": true, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-tostringtag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz", - "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==", - "dev": true, - "dependencies": { - "has-symbols": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/hash-base": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.1.0.tgz", - "integrity": "sha512-1nmYp/rhMDiE7AYkDw+lLwlAzz0AntGIe51F3RfFfEqyQ3feY2eI/NcwC6umIQVOASPMsWJLJScWKSSvzL9IVA==", - "dev": true, - "dependencies": { - "inherits": "^2.0.4", - "readable-stream": "^3.6.0", - "safe-buffer": "^5.2.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/hash-base/node_modules/readable-stream": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", - "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", - "dev": true, - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/hash-base/node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/hash-wasm": { - "version": "4.9.0", - "resolved": "https://registry.npmjs.org/hash-wasm/-/hash-wasm-4.9.0.tgz", - "integrity": "sha512-7SW7ejyfnRxuOc7ptQHSf4LDoZaWOivfzqw+5rpcQku0nHfmicPKE51ra9BiRLAmT8+gGLestr1XroUkqdjL6w==" - }, - "node_modules/hash.js": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz", - "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==", - "dev": true, - "dependencies": { - "inherits": "^2.0.3", - "minimalistic-assert": "^1.0.1" - } - }, - "node_modules/he": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", - "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", - "dev": true, - "bin": { - "he": "bin/he" - } - }, - "node_modules/hmac-drbg": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", - "integrity": "sha512-Tti3gMqLdZfhOQY1Mzf/AanLiqh1WTiJgEj26ZuYQ9fbkLomzGchCws4FyrSd4VkpBfiNhaE1On+lOz894jvXg==", - "dev": true, - "dependencies": { - "hash.js": "^1.0.3", - "minimalistic-assert": "^1.0.0", - "minimalistic-crypto-utils": "^1.0.1" - } - }, - "node_modules/html-escaper": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", - "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", - "dev": true - }, - "node_modules/http-errors": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", - "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", - "dev": true, - "dependencies": { - "depd": "2.0.0", - "inherits": "2.0.4", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "toidentifier": "1.0.1" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/http-errors/node_modules/statuses": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", - "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", - "dev": true, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/http-proxy": { - "version": "1.18.1", - "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz", - "integrity": "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==", - "dev": true, - "dependencies": { - "eventemitter3": "^4.0.0", - "follow-redirects": "^1.0.0", - "requires-port": "^1.0.0" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/https-browserify": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/https-browserify/-/https-browserify-1.0.0.tgz", - "integrity": "sha512-J+FkSdyD+0mA0N+81tMotaRMfSL9SGi+xpD3T6YApKsc3bGSXJlfXri3VyFOeYkfLRQisDk1W+jIFFKBeUBbBg==", - "dev": true - }, - "node_modules/iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "dev": true, - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/ieee754": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", - "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/ignore": { - "version": "5.2.4", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", - "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==", - "dev": true, - "engines": { - "node": ">= 4" - } - }, - "node_modules/import-fresh": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", - "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", - "dev": true, - "dependencies": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", - "dev": true, - "engines": { - "node": ">=0.8.19" - } - }, - "node_modules/inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "dev": true, - "dependencies": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" - }, - "node_modules/interface-blockstore": { - "version": "5.2.3", - "resolved": "https://registry.npmjs.org/interface-blockstore/-/interface-blockstore-5.2.3.tgz", - "integrity": "sha512-15cN+ZFdcVXdXo6I/SrSzFDsuJyDTyEI52XuvXQlR/G5fe3cK8p0tvVjfu5diRQH1XqNgmJEdMPixyt0xgjtvQ==", - "dependencies": { - "interface-store": "^5.0.0", - "multiformats": "^11.0.2" - }, - "engines": { - "node": ">=16.0.0", - "npm": ">=7.0.0" - } - }, - "node_modules/interface-store": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/interface-store/-/interface-store-5.1.2.tgz", - "integrity": "sha512-q2sLoqC+UdaWnjwGyghsH0jwqqVk226lsG207e3QwPB8sAZYmYIWUnJwJH3JjFNNRV9e6CUTmm+gDO0Xg4KRiw==", - "engines": { - "node": ">=16.0.0", - "npm": ">=7.0.0" - } - }, - "node_modules/ipfs-unixfs": { - "version": "11.0.1", - "resolved": "https://registry.npmjs.org/ipfs-unixfs/-/ipfs-unixfs-11.0.1.tgz", - "integrity": "sha512-SD9dqn14bfgMfkPstsR/2Av3zCzYMj2ntQJab4HZucgX4nNV6K7guZh4Hf3kiL8ONff1Ogft1ekFU083DIKEdQ==", - "dependencies": { - "err-code": "^3.0.1", - "protons-runtime": "^5.0.0", - "uint8arraylist": "^2.4.3" - }, - "engines": { - "node": ">=16.0.0", - "npm": ">=7.0.0" - } - }, - "node_modules/ipfs-unixfs-exporter": { - "version": "13.1.5", - "resolved": "https://registry.npmjs.org/ipfs-unixfs-exporter/-/ipfs-unixfs-exporter-13.1.5.tgz", - "integrity": "sha512-O5aMawsHoe4DaYk5FFil2EPrNOaU3pkHC6qUR5JMnW7es93W3b/RjJoO7AyDL1rpb+M3K0oRu86Yc5wLNQQ8jg==", - "dependencies": { - "@ipld/dag-cbor": "^9.0.0", - "@ipld/dag-pb": "^4.0.0", - "@multiformats/murmur3": "^2.0.0", - "err-code": "^3.0.1", - "hamt-sharding": "^3.0.0", - "interface-blockstore": "^5.0.0", - "ipfs-unixfs": "^11.0.0", - "it-filter": "^3.0.2", - "it-last": "^3.0.2", - "it-map": "^3.0.3", - "it-parallel": "^3.0.0", - "it-pipe": "^3.0.1", - "it-pushable": "^3.1.0", - "multiformats": "^11.0.0", - "p-queue": "^7.3.0", - "progress-events": "^1.0.0", - "uint8arrays": "^4.0.2" - }, - "engines": { - "node": ">=16.0.0", - "npm": ">=7.0.0" - } - }, - "node_modules/ipfs-unixfs-importer": { - "version": "15.1.5", - "resolved": "https://registry.npmjs.org/ipfs-unixfs-importer/-/ipfs-unixfs-importer-15.1.5.tgz", - "integrity": "sha512-TXaOI0M5KNpq2+qLw8AIYd0Lnc0gWTKCBqUd9eErBUwaP3Fna4qauF+JX9Rj2UrwaOvG/1xbF8Vm+92eOcKWMA==", - "dependencies": { - "@ipld/dag-pb": "^4.0.0", - "@multiformats/murmur3": "^2.0.0", - "err-code": "^3.0.1", - "hamt-sharding": "^3.0.0", - "interface-blockstore": "^5.0.0", - "interface-store": "^5.0.1", - "ipfs-unixfs": "^11.0.0", - "it-all": "^3.0.2", - "it-batch": "^3.0.2", - "it-first": "^3.0.2", - "it-parallel-batch": "^3.0.1", - "multiformats": "^11.0.0", - "progress-events": "^1.0.0", - "rabin-wasm": "^0.1.4", - "uint8arraylist": "^2.4.3", - "uint8arrays": "^4.0.2" - }, - "engines": { - "node": ">=16.0.0", - "npm": ">=7.0.0" - } - }, - "node_modules/is-arguments": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz", - "integrity": "sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "has-tostringtag": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-binary-path": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", - "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", - "dev": true, - "dependencies": { - "binary-extensions": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-buffer": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.5.tgz", - "integrity": "sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "engines": { - "node": ">=4" - } - }, - "node_modules/is-callable": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", - "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", - "dev": true, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-ci": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-3.0.1.tgz", - "integrity": "sha512-ZYvCgrefwqoQ6yTyYUbQu64HsITZ3NfKX1lzaEYdkTDcfKzzCI/wthRRYKkdjHKFVgNiXKAKm65Zo1pk2as/QQ==", - "dev": true, - "dependencies": { - "ci-info": "^3.2.0" - }, - "bin": { - "is-ci": "bin.js" - } - }, - "node_modules/is-core-module": { - "version": "2.12.1", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.12.1.tgz", - "integrity": "sha512-Q4ZuBAe2FUsKtyQJoQHlvP8OvBERxO3jEmy1I7hcRXcJBGGHFh/aJBswbXuS9sgrDH2QUO8ilkwNPHvHMd8clg==", - "dev": true, - "dependencies": { - "has": "^1.0.3" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-docker": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", - "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", - "dev": true, - "bin": { - "is-docker": "cli.js" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-generator-function": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.0.10.tgz", - "integrity": "sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A==", - "dev": true, - "dependencies": { - "has-tostringtag": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-glob": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, - "dependencies": { - "is-extglob": "^2.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-nan": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/is-nan/-/is-nan-1.3.2.tgz", - "integrity": "sha512-E+zBKpQ2t6MEo1VsonYmluk9NxGrbzpeeLC2xIViuO2EjU2xsXsBPwTr3Ykv9l08UYEVEdWeRZNouaZqF6RN0w==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.0", - "define-properties": "^1.1.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true, - "engines": { - "node": ">=0.12.0" - } - }, - "node_modules/is-path-inside": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", - "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-plain-obj": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", - "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-typed-array": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.12.tgz", - "integrity": "sha512-Z14TF2JNG8Lss5/HMqt0//T9JeHXttXy5pH/DBU4vi98ozO2btxzq9MwYDZYnKwU8nRsz/+GVFVRDq3DkVuSPg==", - "dev": true, - "dependencies": { - "which-typed-array": "^1.1.11" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-unicode-supported": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", - "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-wsl": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", - "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", - "dev": true, - "dependencies": { - "is-docker": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/isarray": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", - "integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==", - "dev": true - }, - "node_modules/isbinaryfile": { - "version": "4.0.10", - "resolved": "https://registry.npmjs.org/isbinaryfile/-/isbinaryfile-4.0.10.tgz", - "integrity": "sha512-iHrqe5shvBUcFbmZq9zOQHBoeOhZJu6RQGrDpBgenUm/Am+F3JM2MgQj+rK3Z601fzrL5gLZWtAPH2OBaSVcyw==", - "dev": true, - "engines": { - "node": ">= 8.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/gjtorikian/" - } - }, - "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true - }, - "node_modules/isomorphic-timers-promises": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/isomorphic-timers-promises/-/isomorphic-timers-promises-1.0.1.tgz", - "integrity": "sha512-u4sej9B1LPSxTGKB/HiuzvEQnXH0ECYkSVQU39koSwmFAxhlEAFl9RdTvLv4TOTQUgBS5O3O5fwUxk6byBZ+IQ==", - "dev": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/istanbul-lib-coverage": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz", - "integrity": "sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/istanbul-lib-report": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", - "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", - "dev": true, - "dependencies": { - "istanbul-lib-coverage": "^3.0.0", - "make-dir": "^4.0.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/istanbul-reports": { - "version": "3.1.6", - "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.6.tgz", - "integrity": "sha512-TLgnMkKg3iTDsQ9PbPTdpfAK2DzjF9mqUG7RMgcQl8oFjad8ob4laGxv5XV5U9MAfx8D6tSJiUyuAwzLicaxlg==", - "dev": true, - "dependencies": { - "html-escaper": "^2.0.0", - "istanbul-lib-report": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/it-all": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/it-all/-/it-all-3.0.2.tgz", - "integrity": "sha512-ujqWETXhsDbF6C+6X6fvRw5ohlowRoy/o/h9BC8D+R3JQ13oLQ153w9gSWkWupOY7omZFQbJiAL1aJo5Gwe2yw==", - "engines": { - "node": ">=16.0.0", - "npm": ">=7.0.0" - } - }, - "node_modules/it-batch": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/it-batch/-/it-batch-3.0.2.tgz", - "integrity": "sha512-Ypepz/vCxNFOvFkUPFvoxGb8WzqainzhflRaJahp1MBo3Y42ICdrgR3xIwOFE6WAgO+UWUM0zeMlbUStsW9AIQ==", - "engines": { - "node": ">=16.0.0", - "npm": ">=7.0.0" - } - }, - "node_modules/it-filter": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/it-filter/-/it-filter-3.0.2.tgz", - "integrity": "sha512-Hhzp5anX7tmKOBqTPasBYTPlq7l4Xk4lMBfLB5GfKZnL9WCc6pr8M9Waud4nHh3s9neb4xwDWk7KQsEapgWyJw==", - "dependencies": { - "it-peekable": "^3.0.0" - }, - "engines": { - "node": ">=16.0.0", - "npm": ">=7.0.0" - } - }, - "node_modules/it-first": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/it-first/-/it-first-3.0.2.tgz", - "integrity": "sha512-QPLAM2BOkait/o6W25HvP0XTEv+Os3Ce4wET//ADNaPv+WYAHWfQwJuMu5FB8X066hA1F7LEMnULvTpE7/4yQw==", - "engines": { - "node": ">=16.0.0", - "npm": ">=7.0.0" - } - }, - "node_modules/it-last": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/it-last/-/it-last-3.0.2.tgz", - "integrity": "sha512-aWoA5moJ7XSKe7+YuutBKhySroDDWkfjpo+UknekPh1M5YYdK4YNSPDarR+7o/NqRwzazwgzCi2UZzU0oqsprQ==", - "engines": { - "node": ">=16.0.0", - "npm": ">=7.0.0" - } - }, - "node_modules/it-map": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/it-map/-/it-map-3.0.3.tgz", - "integrity": "sha512-Yf89GJYeYUZb2NZzWkvFHm3IBXlxro74i2vGRmpf8BYau3BhlaS37ieDenJEdYzkTGJhL/EbM1jPPw/KGVVVIw==", - "dependencies": { - "it-peekable": "^3.0.0" - }, - "engines": { - "node": ">=16.0.0", - "npm": ">=7.0.0" - } - }, - "node_modules/it-merge": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/it-merge/-/it-merge-3.0.1.tgz", - "integrity": "sha512-I6hjU1ABO+k3xY1H6JtCSDXvUME88pxIXSgKeT4WI5rPYbQzpr98ldacVuG95WbjaJxKl6Qot6lUdxduLBQPHA==", - "dependencies": { - "it-pushable": "^3.1.0" - }, - "engines": { - "node": ">=16.0.0", - "npm": ">=7.0.0" - } - }, - "node_modules/it-parallel": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/it-parallel/-/it-parallel-3.0.3.tgz", - "integrity": "sha512-Q5KmdvERHCOLDcgKqrzQ+yiMCdG6H9h7ZL3Zjx/Tx9xhZy8txSKoy+EiCgWZFs0rfYvxJhk6UkOpKLzJ1zM9ZA==", - "dependencies": { - "p-defer": "^4.0.0" - }, - "engines": { - "node": ">=16.0.0", - "npm": ">=7.0.0" - } - }, - "node_modules/it-parallel-batch": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/it-parallel-batch/-/it-parallel-batch-3.0.1.tgz", - "integrity": "sha512-4KTvYVYpCdrYUrAHSeH6o5hnHuDVHWzB8TztV/hdckUZzZIjbax4kVblmnzoYREX8Huj5+50irBu7b+c8jyKQg==", - "dependencies": { - "it-batch": "^3.0.0" - }, - "engines": { - "node": ">=16.0.0", - "npm": ">=7.0.0" - } - }, - "node_modules/it-peekable": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/it-peekable/-/it-peekable-3.0.1.tgz", - "integrity": "sha512-5zBfkf6e+YoxxWV0YDXMwdQKnc7eeTX6xo3WYPm/8dIoctIiDnddInRWOW+83W/8/76sbnpWqqsO4gSyXandeQ==", - "engines": { - "node": ">=16.0.0", - "npm": ">=7.0.0" - } - }, - "node_modules/it-pipe": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/it-pipe/-/it-pipe-3.0.1.tgz", - "integrity": "sha512-sIoNrQl1qSRg2seYSBH/3QxWhJFn9PKYvOf/bHdtCBF0bnghey44VyASsWzn5dAx0DCDDABq1hZIuzKmtBZmKA==", - "dependencies": { - "it-merge": "^3.0.0", - "it-pushable": "^3.1.2", - "it-stream-types": "^2.0.1" - }, - "engines": { - "node": ">=16.0.0", - "npm": ">=7.0.0" - } - }, - "node_modules/it-pushable": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/it-pushable/-/it-pushable-3.2.1.tgz", - "integrity": "sha512-sLFz2Q0oyDCJpTciZog7ipP4vSftfPy3e6JnH6YyztRa1XqkpGQaafK3Jw/JlfEBtCXfnX9uVfcpu3xpSAqCVQ==", - "dependencies": { - "p-defer": "^4.0.0" - }, - "engines": { - "node": ">=16.0.0", - "npm": ">=7.0.0" - } - }, - "node_modules/it-stream-types": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/it-stream-types/-/it-stream-types-2.0.1.tgz", - "integrity": "sha512-6DmOs5r7ERDbvS4q8yLKENcj6Yecr7QQTqWApbZdfAUTEC947d+PEha7PCqhm//9oxaLYL7TWRekwhoXl2s6fg==", - "engines": { - "node": ">=16.0.0", - "npm": ">=7.0.0" - } - }, - "node_modules/jest-worker": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", - "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", - "dev": true, - "peer": true, - "dependencies": { - "@types/node": "*", - "merge-stream": "^2.0.0", - "supports-color": "^8.0.0" - }, - "engines": { - "node": ">= 10.13.0" - } - }, - "node_modules/jest-worker/node_modules/supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "dev": true, - "peer": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" - } - }, - "node_modules/js-sdsl": { - "version": "4.4.2", - "resolved": "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.4.2.tgz", - "integrity": "sha512-dwXFwByc/ajSV6m5bcKAPwe4yDDF6D614pxmIi5odytzxRlwqF6nwoiCek80Ixc7Cvma5awClxrzFtxCQvcM8w==", - "dev": true, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/js-sdsl" - } - }, - "node_modules/js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "dev": true, - "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/jsbi": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/jsbi/-/jsbi-4.3.0.tgz", - "integrity": "sha512-SnZNcinB4RIcnEyZqFPdGPVgrg2AcnykiBy0sHVJQKHYeaLUvi3Exj+iaPpLnFVkDPZIV4U0yvgC9/R4uEAZ9g==" - }, - "node_modules/json-parse-even-better-errors": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", - "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", - "dev": true, - "peer": true - }, - "node_modules/json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" - }, - "node_modules/json-stable-stringify-without-jsonify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", - "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", - "dev": true - }, - "node_modules/jsonfile": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", - "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", - "dev": true, - "optionalDependencies": { - "graceful-fs": "^4.1.6" - } - }, - "node_modules/just-extend": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-4.2.1.tgz", - "integrity": "sha512-g3UB796vUFIY90VIv/WX3L2c8CS2MdWUww3CNrYmqza1Fg0DURc2K/O4YrnklBdQarSJ/y8JnJYDGc+1iumQjg==", - "dev": true - }, - "node_modules/karma": { - "version": "6.4.1", - "resolved": "https://registry.npmjs.org/karma/-/karma-6.4.1.tgz", - "integrity": "sha512-Cj57NKOskK7wtFWSlMvZf459iX+kpYIPXmkNUzP2WAFcA7nhr/ALn5R7sw3w+1udFDcpMx/tuB8d5amgm3ijaA==", - "dev": true, - "dependencies": { - "@colors/colors": "1.5.0", - "body-parser": "^1.19.0", - "braces": "^3.0.2", - "chokidar": "^3.5.1", - "connect": "^3.7.0", - "di": "^0.0.1", - "dom-serialize": "^2.2.1", - "glob": "^7.1.7", - "graceful-fs": "^4.2.6", - "http-proxy": "^1.18.1", - "isbinaryfile": "^4.0.8", - "lodash": "^4.17.21", - "log4js": "^6.4.1", - "mime": "^2.5.2", - "minimatch": "^3.0.4", - "mkdirp": "^0.5.5", - "qjobs": "^1.2.0", - "range-parser": "^1.2.1", - "rimraf": "^3.0.2", - "socket.io": "^4.4.1", - "source-map": "^0.6.1", - "tmp": "^0.2.1", - "ua-parser-js": "^0.7.30", - "yargs": "^16.1.1" - }, - "bin": { - "karma": "bin/karma" - }, - "engines": { - "node": ">= 10" - } - }, - "node_modules/karma-chai": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/karma-chai/-/karma-chai-0.1.0.tgz", - "integrity": "sha512-mqKCkHwzPMhgTYca10S90aCEX9+HjVjjrBFAsw36Zj7BlQNbokXXCAe6Ji04VUMsxcY5RLP7YphpfO06XOubdg==", - "dev": true, - "peerDependencies": { - "chai": "*", - "karma": ">=0.10.9" - } - }, - "node_modules/karma-chrome-launcher": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/karma-chrome-launcher/-/karma-chrome-launcher-3.1.1.tgz", - "integrity": "sha512-hsIglcq1vtboGPAN+DGCISCFOxW+ZVnIqhDQcCMqqCp+4dmJ0Qpq5QAjkbA0X2L9Mi6OBkHi2Srrbmm7pUKkzQ==", - "dev": true, - "dependencies": { - "which": "^1.2.1" - } - }, - "node_modules/karma-chrome-launcher/node_modules/which": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", - "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", - "dev": true, - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "which": "bin/which" - } - }, - "node_modules/karma-esbuild": { - "version": "2.2.5", - "resolved": "https://registry.npmjs.org/karma-esbuild/-/karma-esbuild-2.2.5.tgz", - "integrity": "sha512-+NiRmZhUm/MqOsL1cAu8+RmiOMvIxWDaeYDLBB5upxHF9Hh3Og8YH43EAmDan40pxt2FKDcOjupgqIe4Tx2szQ==", - "dev": true, - "dependencies": { - "chokidar": "^3.5.1", - "source-map": "0.6.1" - }, - "peerDependencies": { - "esbuild": ">=0.8.45" - } - }, - "node_modules/karma-firefox-launcher": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/karma-firefox-launcher/-/karma-firefox-launcher-2.1.2.tgz", - "integrity": "sha512-VV9xDQU1QIboTrjtGVD4NCfzIH7n01ZXqy/qpBhnOeGVOkG5JYPEm8kuSd7psHE6WouZaQ9Ool92g8LFweSNMA==", - "dev": true, - "dependencies": { - "is-wsl": "^2.2.0", - "which": "^2.0.1" - } - }, - "node_modules/karma-mocha": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/karma-mocha/-/karma-mocha-2.0.1.tgz", - "integrity": "sha512-Tzd5HBjm8his2OA4bouAsATYEpZrp9vC7z5E5j4C5Of5Rrs1jY67RAwXNcVmd/Bnk1wgvQRou0zGVLey44G4tQ==", - "dev": true, - "dependencies": { - "minimist": "^1.2.3" - } - }, - "node_modules/karma-mocha-reporter": { - "version": "2.2.5", - "resolved": "https://registry.npmjs.org/karma-mocha-reporter/-/karma-mocha-reporter-2.2.5.tgz", - "integrity": "sha512-Hr6nhkIp0GIJJrvzY8JFeHpQZNseuIakGac4bpw8K1+5F0tLb6l7uvXRa8mt2Z+NVwYgCct4QAfp2R2QP6o00w==", - "dev": true, - "dependencies": { - "chalk": "^2.1.0", - "log-symbols": "^2.1.0", - "strip-ansi": "^4.0.0" - }, - "peerDependencies": { - "karma": ">=0.13" - } - }, - "node_modules/karma-mocha-reporter/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/karma-mocha-reporter/node_modules/ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "dependencies": { - "color-convert": "^1.9.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/karma-mocha-reporter/node_modules/chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/karma-mocha-reporter/node_modules/color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, - "dependencies": { - "color-name": "1.1.3" - } - }, - "node_modules/karma-mocha-reporter/node_modules/color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", - "dev": true - }, - "node_modules/karma-mocha-reporter/node_modules/escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", - "dev": true, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/karma-mocha-reporter/node_modules/has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/karma-mocha-reporter/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/karma-mocha-reporter/node_modules/supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/karma-webkit-launcher": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/karma-webkit-launcher/-/karma-webkit-launcher-2.1.0.tgz", - "integrity": "sha512-S5eqhH0DIcuJFi27nC6eBxZ3MTrPnYybPthDU2Q8dfG0yFrXx8FqNDKSbRZsFFvAKJ55QVtYH1bbArd3ddI5Sg==", - "dev": true, - "dependencies": { - "is-ci": "^3.0.1", - "uuid": "^9.0.0" - }, - "peerDependenciesMeta": { - "playwright": { - "optional": true - } - } - }, - "node_modules/karma/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/level": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/level/-/level-8.0.0.tgz", - "integrity": "sha512-ypf0jjAk2BWI33yzEaaotpq7fkOPALKAgDBxggO6Q9HGX2MRXn0wbP1Jn/tJv1gtL867+YOjOB49WaUF3UoJNQ==", - "dependencies": { - "browser-level": "^1.0.1", - "classic-level": "^1.2.0" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/level" - } - }, - "node_modules/level-supports": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/level-supports/-/level-supports-4.0.1.tgz", - "integrity": "sha512-PbXpve8rKeNcZ9C1mUicC9auIYFyGpkV9/i6g76tLgANwWhtG2v7I4xNBUlkn3lE2/dZF3Pi0ygYGtLc4RXXdA==", - "engines": { - "node": ">=12" - } - }, - "node_modules/level-transcoder": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/level-transcoder/-/level-transcoder-1.0.1.tgz", - "integrity": "sha512-t7bFwFtsQeD8cl8NIoQ2iwxA0CL/9IFw7/9gAjOonH0PWTTiRfY7Hq+Ejbsxh86tXobDQ6IOiddjNYIfOBs06w==", - "dependencies": { - "buffer": "^6.0.3", - "module-error": "^1.0.1" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/levn": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", - "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", - "dev": true, - "dependencies": { - "prelude-ls": "^1.2.1", - "type-check": "~0.4.0" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/loader-runner": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.0.tgz", - "integrity": "sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==", - "dev": true, - "peer": true, - "engines": { - "node": ">=6.11.5" - } - }, - "node_modules/locate-path": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", - "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", - "dev": true, - "dependencies": { - "p-locate": "^5.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" - }, - "node_modules/lodash.get": { - "version": "4.4.2", - "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", - "integrity": "sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==", - "dev": true - }, - "node_modules/lodash.merge": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", - "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", - "dev": true - }, - "node_modules/log-symbols": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-2.2.0.tgz", - "integrity": "sha512-VeIAFslyIerEJLXHziedo2basKbMKtTw3vfn5IzG0XTjhAVEJyNHnL2p7vc+wBDSdQuUpNw3M2u6xb9QsAY5Eg==", - "dev": true, - "dependencies": { - "chalk": "^2.0.1" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/log-symbols/node_modules/ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "dependencies": { - "color-convert": "^1.9.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/log-symbols/node_modules/chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/log-symbols/node_modules/color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, - "dependencies": { - "color-name": "1.1.3" - } - }, - "node_modules/log-symbols/node_modules/color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", - "dev": true - }, - "node_modules/log-symbols/node_modules/escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", - "dev": true, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/log-symbols/node_modules/has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/log-symbols/node_modules/supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/log4js": { - "version": "6.9.1", - "resolved": "https://registry.npmjs.org/log4js/-/log4js-6.9.1.tgz", - "integrity": "sha512-1somDdy9sChrr9/f4UlzhdaGfDR2c/SaD2a4T7qEkG4jTS57/B3qmnjLYePwQ8cqWnUHZI0iAKxMBpCZICiZ2g==", - "dev": true, - "dependencies": { - "date-format": "^4.0.14", - "debug": "^4.3.4", - "flatted": "^3.2.7", - "rfdc": "^1.3.0", - "streamroller": "^3.1.5" - }, - "engines": { - "node": ">=8.0" - } - }, - "node_modules/long": { - "version": "5.2.3", - "resolved": "https://registry.npmjs.org/long/-/long-5.2.3.tgz", - "integrity": "sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q==" - }, - "node_modules/loupe": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.6.tgz", - "integrity": "sha512-RaPMZKiMy8/JruncMU5Bt6na1eftNoo++R4Y+N2FrxkDVTrGvcyzFTsaGif4QTeKESheMGegbhw6iUAq+5A8zA==", - "dependencies": { - "get-func-name": "^2.0.0" - } - }, - "node_modules/lru-cache": { - "version": "9.1.2", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-9.1.2.tgz", - "integrity": "sha512-ERJq3FOzJTxBbFjZ7iDs+NiK4VI9Wz+RdrrAB8dio1oV+YvdPzUEE4QNiT2VD51DkIbCYRUUzCRkssXCHqSnKQ==", - "engines": { - "node": "14 || >=16.14" - } - }, - "node_modules/make-dir": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", - "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", - "dev": true, - "dependencies": { - "semver": "^7.5.3" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/md5.js": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz", - "integrity": "sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==", - "dev": true, - "dependencies": { - "hash-base": "^3.0.0", - "inherits": "^2.0.1", - "safe-buffer": "^5.1.2" - } - }, - "node_modules/media-typer": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", - "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", - "dev": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/merge-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", - "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", - "dev": true, - "peer": true - }, - "node_modules/merge2": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", - "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", - "dev": true, - "engines": { - "node": ">= 8" - } - }, - "node_modules/micromatch": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", - "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", - "dev": true, - "dependencies": { - "braces": "^3.0.2", - "picomatch": "^2.3.1" - }, - "engines": { - "node": ">=8.6" - } - }, - "node_modules/miller-rabin": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/miller-rabin/-/miller-rabin-4.0.1.tgz", - "integrity": "sha512-115fLhvZVqWwHPbClyntxEVfVDfl9DLLTuJvq3g2O/Oxi8AiNouAHvDSzHS0viUJc+V5vm3eq91Xwqn9dp4jRA==", - "dev": true, - "dependencies": { - "bn.js": "^4.0.0", - "brorand": "^1.0.1" - }, - "bin": { - "miller-rabin": "bin/miller-rabin" - } - }, - "node_modules/miller-rabin/node_modules/bn.js": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", - "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", - "dev": true - }, - "node_modules/mime": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz", - "integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==", - "dev": true, - "bin": { - "mime": "cli.js" - }, - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "dev": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "dev": true, - "dependencies": { - "mime-db": "1.52.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/minimalistic-assert": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", - "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==", - "dev": true - }, - "node_modules/minimalistic-crypto-utils": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz", - "integrity": "sha512-JIYlbt6g8i5jKfJ3xz7rF0LXmv2TkDxBLUkiBeZ7bAx4GnnNMr8xFpGnOxn6GhTEHx3SjRrZEoU+j04prX1ktg==", - "dev": true - }, - "node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/minimist": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", - "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/minipass": { - "version": "4.2.8", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-4.2.8.tgz", - "integrity": "sha512-fNzuVyifolSLFL4NzpF+wEF4qrgqaaKX0haXPQEdQ7NKAN+WecoKMHV09YcuL/DHxrUsYQOK3MiuDf7Ip2OXfQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/mkdirp": { - "version": "0.5.6", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", - "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", - "dev": true, - "dependencies": { - "minimist": "^1.2.6" - }, - "bin": { - "mkdirp": "bin/cmd.js" - } - }, - "node_modules/mocha": { - "version": "10.2.0", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.2.0.tgz", - "integrity": "sha512-IDY7fl/BecMwFHzoqF2sg/SHHANeBoMMXFlS9r0OXKDssYE1M5O43wUY/9BVPeIvfH2zmEbBfseqN9gBQZzXkg==", - "dev": true, - "dependencies": { - "ansi-colors": "4.1.1", - "browser-stdout": "1.3.1", - "chokidar": "3.5.3", - "debug": "4.3.4", - "diff": "5.0.0", - "escape-string-regexp": "4.0.0", - "find-up": "5.0.0", - "glob": "7.2.0", - "he": "1.2.0", - "js-yaml": "4.1.0", - "log-symbols": "4.1.0", - "minimatch": "5.0.1", - "ms": "2.1.3", - "nanoid": "3.3.3", - "serialize-javascript": "6.0.0", - "strip-json-comments": "3.1.1", - "supports-color": "8.1.1", - "workerpool": "6.2.1", - "yargs": "16.2.0", - "yargs-parser": "20.2.4", - "yargs-unparser": "2.0.0" - }, - "bin": { - "_mocha": "bin/_mocha", - "mocha": "bin/mocha.js" - }, - "engines": { - "node": ">= 14.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/mochajs" - } - }, - "node_modules/mocha/node_modules/glob": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", - "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", - "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" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/mocha/node_modules/glob/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/mocha/node_modules/log-symbols": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", - "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", - "dev": true, - "dependencies": { - "chalk": "^4.1.0", - "is-unicode-supported": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/mocha/node_modules/minimatch": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.0.1.tgz", - "integrity": "sha512-nLDxIFRyhDblz3qMuq+SoRZED4+miJ/G+tdDrjkkkRnjAsBexeGpgjLEQ0blJy7rHhR2b93rhQY4SvyWu9v03g==", - "dev": true, - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/mocha/node_modules/minimatch/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dev": true, - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/mocha/node_modules/supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" - } - }, - "node_modules/mocha/node_modules/yargs-parser": { - "version": "20.2.4", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.4.tgz", - "integrity": "sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==", - "dev": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/module-error": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/module-error/-/module-error-1.0.2.tgz", - "integrity": "sha512-0yuvsqSCv8LbaOKhnsQ/T5JhyFlCYLPXK3U2sgV10zoKQwzs/MyfuQUOZQ1V/6OCOJsK/TRgNVrPuPDqtdMFtA==", - "engines": { - "node": ">=10" - } - }, - "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==" - }, - "node_modules/multibase": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/multibase/-/multibase-4.0.6.tgz", - "integrity": "sha512-x23pDe5+svdLz/k5JPGCVdfn7Q5mZVMBETiC+ORfO+sor9Sgs0smJzAjfTbM5tckeCqnaUuMYoz+k3RXMmJClQ==", - "deprecated": "This module has been superseded by the multiformats module", - "dependencies": { - "@multiformats/base-x": "^4.0.1" - }, - "engines": { - "node": ">=12.0.0", - "npm": ">=6.0.0" - } - }, - "node_modules/multiformats": { - "version": "11.0.2", - "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-11.0.2.tgz", - "integrity": "sha512-b5mYMkOkARIuVZCpvijFj9a6m5wMVLC7cf/jIPd5D/ARDOfLC5+IFkbgDXQgcU2goIsTD/O9NY4DI/Mt4OGvlg==", - "engines": { - "node": ">=16.0.0", - "npm": ">=7.0.0" - } - }, - "node_modules/multihashes": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/multihashes/-/multihashes-4.0.3.tgz", - "integrity": "sha512-0AhMH7Iu95XjDLxIeuCOOE4t9+vQZsACyKZ9Fxw2pcsRmlX4iCn1mby0hS0bb+nQOVpdQYWPpnyusw4da5RPhA==", - "dependencies": { - "multibase": "^4.0.1", - "uint8arrays": "^3.0.0", - "varint": "^5.0.2" - }, - "engines": { - "node": ">=12.0.0", - "npm": ">=6.0.0" - } - }, - "node_modules/multihashes/node_modules/multiformats": { - "version": "9.9.0", - "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-9.9.0.tgz", - "integrity": "sha512-HoMUjhH9T8DDBNT+6xzkrd9ga/XiBI4xLr58LJACwK6G3HTOPeMz4nB4KJs33L2BelrIJa7P0VuNaVF3hMYfjg==" - }, - "node_modules/multihashes/node_modules/uint8arrays": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-3.1.1.tgz", - "integrity": "sha512-+QJa8QRnbdXVpHYjLoTpJIdCTiw9Ir62nocClWuXIq2JIh4Uta0cQsTSpFL678p2CN8B+XSApwcU+pQEqVpKWg==", - "dependencies": { - "multiformats": "^9.4.2" - } - }, - "node_modules/multihashes/node_modules/varint": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/varint/-/varint-5.0.2.tgz", - "integrity": "sha512-lKxKYG6H03yCZUpAGOPOsMcGxd1RHCu1iKvEHYDPmTyq2HueGhD73ssNBqqQWfvYs04G9iUFRvmAVLW20Jw6ow==" - }, - "node_modules/murmurhash3js-revisited": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/murmurhash3js-revisited/-/murmurhash3js-revisited-3.0.0.tgz", - "integrity": "sha512-/sF3ee6zvScXMb1XFJ8gDsSnY+X8PbOyjIuBhtgis10W2Jx4ZjIhikUCIF9c4gpJxVnQIsPAFrSwTCuAjicP6g==", - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/nanoid": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.3.tgz", - "integrity": "sha512-p1sjXuopFs0xg+fPASzQ28agW1oHD7xDsd9Xkf3T15H3c/cifrFHVwrh74PdoklAPi+i7MdRsE47vm2r6JoB+w==", - "dev": true, - "bin": { - "nanoid": "bin/nanoid.cjs" - }, - "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" - } - }, - "node_modules/napi-macros": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/napi-macros/-/napi-macros-2.2.2.tgz", - "integrity": "sha512-hmEVtAGYzVQpCKdbQea4skABsdXW4RUh5t5mJ2zzqowJS2OyXZTU1KhDVFhx+NlWZ4ap9mqR9TcDO3LTTttd+g==" - }, - "node_modules/natural-compare": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", - "dev": true - }, - "node_modules/natural-compare-lite": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare-lite/-/natural-compare-lite-1.4.0.tgz", - "integrity": "sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==", - "dev": true - }, - "node_modules/negotiator": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", - "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", - "dev": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/neo-async": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", - "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", - "dev": true, - "peer": true - }, - "node_modules/nise": { - "version": "5.1.4", - "resolved": "https://registry.npmjs.org/nise/-/nise-5.1.4.tgz", - "integrity": "sha512-8+Ib8rRJ4L0o3kfmyVCL7gzrohyDe0cMFTBa2d364yIrEGMEoetznKJx899YxjybU6bL9SQkYPSBBs1gyYs8Xg==", - "dev": true, - "dependencies": { - "@sinonjs/commons": "^2.0.0", - "@sinonjs/fake-timers": "^10.0.2", - "@sinonjs/text-encoding": "^0.7.1", - "just-extend": "^4.0.2", - "path-to-regexp": "^1.7.0" - } - }, - "node_modules/nise/node_modules/@sinonjs/commons": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-2.0.0.tgz", - "integrity": "sha512-uLa0j859mMrg2slwQYdO/AkrOfmH+X6LTVmNTS9CqexuE2IvVORIkSpJLqePAbEnKJ77aMmCwr1NUZ57120Xcg==", - "dev": true, - "dependencies": { - "type-detect": "4.0.8" - } - }, - "node_modules/node-fetch": { - "version": "2.6.12", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.12.tgz", - "integrity": "sha512-C/fGU2E8ToujUivIO0H+tpQ6HWo4eEmchoPIoXtxCrVghxdKq+QOHqEZW7tuP3KlV3bC8FRMO5nMCC7Zm1VP6g==", - "dependencies": { - "whatwg-url": "^5.0.0" - }, - "engines": { - "node": "4.x || >=6.0.0" - }, - "peerDependencies": { - "encoding": "^0.1.0" - }, - "peerDependenciesMeta": { - "encoding": { - "optional": true - } - } - }, - "node_modules/node-gyp-build": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.6.0.tgz", - "integrity": "sha512-NTZVKn9IylLwUzaKjkas1e4u2DLNcV4rdYagA4PWdPwW87Bi7z+BznyKSRwS/761tV/lzCGXplWsiaMjLqP2zQ==", - "bin": { - "node-gyp-build": "bin.js", - "node-gyp-build-optional": "optional.js", - "node-gyp-build-test": "build-test.js" - } - }, - "node_modules/node-releases": { - "version": "2.0.13", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.13.tgz", - "integrity": "sha512-uYr7J37ae/ORWdZeQ1xxMJe3NtdmqMC/JZK+geofDrkLUApKRHPd18/TxtBOJ4A0/+uUIliorNrfYV6s1b02eQ==", - "dev": true, - "peer": true - }, - "node_modules/node-stdlib-browser": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/node-stdlib-browser/-/node-stdlib-browser-1.2.0.tgz", - "integrity": "sha512-VSjFxUhRhkyed8AtLwSCkMrJRfQ3e2lGtG3sP6FEgaLKBBbxM/dLfjRe1+iLhjvyLFW3tBQ8+c0pcOtXGbAZJg==", - "dev": true, - "dependencies": { - "assert": "^2.0.0", - "browser-resolve": "^2.0.0", - "browserify-zlib": "^0.2.0", - "buffer": "^5.7.1", - "console-browserify": "^1.1.0", - "constants-browserify": "^1.0.0", - "create-require": "^1.1.1", - "crypto-browserify": "^3.11.0", - "domain-browser": "^4.22.0", - "events": "^3.0.0", - "https-browserify": "^1.0.0", - "isomorphic-timers-promises": "^1.0.1", - "os-browserify": "^0.3.0", - "path-browserify": "^1.0.1", - "pkg-dir": "^5.0.0", - "process": "^0.11.10", - "punycode": "^1.4.1", - "querystring-es3": "^0.2.1", - "readable-stream": "^3.6.0", - "stream-browserify": "^3.0.0", - "stream-http": "^3.2.0", - "string_decoder": "^1.0.0", - "timers-browserify": "^2.0.4", - "tty-browserify": "0.0.1", - "url": "^0.11.0", - "util": "^0.12.4", - "vm-browserify": "^1.0.1" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/node-stdlib-browser/node_modules/buffer": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", - "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "dependencies": { - "base64-js": "^1.3.1", - "ieee754": "^1.1.13" - } - }, - "node_modules/node-stdlib-browser/node_modules/readable-stream": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", - "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", - "dev": true, - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/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==", - "dev": true, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/object-is": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.5.tgz", - "integrity": "sha512-3cyDsyHgtmi7I7DfSSI2LDp6SK2lwvtbg0p0R1e0RvTqF5ceGx+K2dfSjm1bKDMVCFEDAQvy+o8c6a7VujOddw==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/object-keys": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", - "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", - "dev": true, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/on-finished": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", - "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", - "dev": true, - "dependencies": { - "ee-first": "1.1.1" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "dev": true, - "dependencies": { - "wrappy": "1" - } - }, - "node_modules/optionator": { - "version": "0.9.3", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz", - "integrity": "sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==", - "dev": true, - "dependencies": { - "@aashutoshrathi/word-wrap": "^1.2.3", - "deep-is": "^0.1.3", - "fast-levenshtein": "^2.0.6", - "levn": "^0.4.1", - "prelude-ls": "^1.2.1", - "type-check": "^0.4.0" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/os-browserify": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/os-browserify/-/os-browserify-0.3.0.tgz", - "integrity": "sha512-gjcpUc3clBf9+210TRaDWbf+rZZZEshZ+DlXMRCeAjp0xhTrnQsKHypIy1J3d5hKdUzj69t708EHtU8P6bUn0A==", - "dev": true - }, - "node_modules/p-defer": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/p-defer/-/p-defer-4.0.0.tgz", - "integrity": "sha512-Vb3QRvQ0Y5XnF40ZUWW7JfLogicVh/EnA5gBIvKDJoYpeI82+1E3AlB9yOcKFS0AhHrWVnAQO39fbR0G99IVEQ==", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "dev": true, - "dependencies": { - "yocto-queue": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-locate": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", - "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", - "dev": true, - "dependencies": { - "p-limit": "^3.0.2" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-queue": { - "version": "7.3.4", - "resolved": "https://registry.npmjs.org/p-queue/-/p-queue-7.3.4.tgz", - "integrity": "sha512-esox8CWt0j9EZECFvkFl2WNPat8LN4t7WWeXq73D9ha0V96qPRufApZi4ZhPwXAln1uVVal429HVVKPa2X0yQg==", - "dependencies": { - "eventemitter3": "^4.0.7", - "p-timeout": "^5.0.2" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-timeout": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-5.1.0.tgz", - "integrity": "sha512-auFDyzzzGZZZdHz3BtET9VEz0SE/uMEAx7uWfGPucfzEwwe/xH0iVeZibQmANYE/hp9T2+UUZT5m+BKyrDp3Ew==", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/pako": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", - "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", - "dev": true - }, - "node_modules/parent-module": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", - "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", - "dev": true, - "dependencies": { - "callsites": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/parse-asn1": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.6.tgz", - "integrity": "sha512-RnZRo1EPU6JBnra2vGHj0yhp6ebyjBZpmUCLHWiFhxlzvBCCpAuZ7elsBp1PVAbQN0/04VD/19rfzlBSwLstMw==", - "dev": true, - "dependencies": { - "asn1.js": "^5.2.0", - "browserify-aes": "^1.0.0", - "evp_bytestokey": "^1.0.0", - "pbkdf2": "^3.0.3", - "safe-buffer": "^5.1.1" - } - }, - "node_modules/parseurl": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", - "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", - "dev": true, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/path-browserify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-1.0.1.tgz", - "integrity": "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==", - "dev": true - }, - "node_modules/path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/path-parse": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", - "dev": true - }, - "node_modules/path-scurry": { - "version": "1.10.1", - "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.10.1.tgz", - "integrity": "sha512-MkhCqzzBEpPvxxQ71Md0b1Kk51W01lrYvlMzSUaIzNsODdd7mqhiimSZlr+VegAz5Z6Vzt9Xg2ttE//XBhH3EQ==", - "dev": true, - "dependencies": { - "lru-cache": "^9.1.1 || ^10.0.0", - "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/path-scurry/node_modules/minipass": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.0.2.tgz", - "integrity": "sha512-eL79dXrE1q9dBbDCLg7xfn/vl7MS4F1gvJAgjJrQli/jbQWdUttuVawphqpffoIYfRdq78LHx6GP4bU/EQ2ATA==", - "dev": true, - "engines": { - "node": ">=16 || 14 >=14.17" - } - }, - "node_modules/path-to-regexp": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.8.0.tgz", - "integrity": "sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA==", - "dev": true, - "dependencies": { - "isarray": "0.0.1" - } - }, - "node_modules/path-type": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", - "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/pathval": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz", - "integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==", - "engines": { - "node": "*" - } - }, - "node_modules/pbkdf2": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.1.2.tgz", - "integrity": "sha512-iuh7L6jA7JEGu2WxDwtQP1ddOpaJNC4KlDEFfdQajSGgGPNi4OyDc2R7QnbY2bR9QjBVGwgvTdNJZoE7RaxUMA==", - "dev": true, - "dependencies": { - "create-hash": "^1.1.2", - "create-hmac": "^1.1.4", - "ripemd160": "^2.0.1", - "safe-buffer": "^5.0.1", - "sha.js": "^2.4.8" - }, - "engines": { - "node": ">=0.12" - } - }, - "node_modules/picocolors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", - "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", - "dev": true, - "peer": true - }, - "node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true, - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/pkg-dir": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-5.0.0.tgz", - "integrity": "sha512-NPE8TDbzl/3YQYY7CSS228s3g2ollTFnc+Qi3tqmqJp9Vg2ovUpixcJEo2HJScN2Ez+kEaal6y70c0ehqJBJeA==", - "dev": true, - "dependencies": { - "find-up": "^5.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/playwright": { - "version": "1.36.2", - "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.36.2.tgz", - "integrity": "sha512-4Fmlq3KWsl85Bl4InJw1NC21aeQV0iSZuFvTDcy1F8zVmXmgQRe89GxF8zMSRt/KIS+2tUolak7EXVl9aC+JdA==", - "dev": true, - "hasInstallScript": true, - "dependencies": { - "playwright-core": "1.36.2" - }, - "bin": { - "playwright": "cli.js" - }, - "engines": { - "node": ">=16" - } - }, - "node_modules/playwright-core": { - "version": "1.36.2", - "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.36.2.tgz", - "integrity": "sha512-sQYZt31dwkqxOrP7xy2ggDfEzUxM1lodjhsQ3NMMv5uGTRDsLxU0e4xf4wwMkF2gplIxf17QMBCodSFgm6bFVQ==", - "dev": true, - "bin": { - "playwright-core": "cli.js" - }, - "engines": { - "node": ">=16" - } - }, - "node_modules/prelude-ls": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", - "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", - "dev": true, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/process": { - "version": "0.11.10", - "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", - "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", - "engines": { - "node": ">= 0.6.0" - } - }, - "node_modules/progress-events": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/progress-events/-/progress-events-1.0.0.tgz", - "integrity": "sha512-zIB6QDrSbPfRg+33FZalluFIowkbV5Xh1xSuetjG+rlC5he6u2dc6VQJ0TbMdlN3R1RHdpOqxEFMKTnQ+itUwA==", - "engines": { - "node": ">=16.0.0", - "npm": ">=7.0.0" - } - }, - "node_modules/protobufjs": { - "version": "7.2.4", - "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.2.4.tgz", - "integrity": "sha512-AT+RJgD2sH8phPmCf7OUZR8xGdcJRga4+1cOaXJ64hvcSkVhNcRHOwIxUatPH15+nj59WAGTDv3LSGZPEQbJaQ==", - "hasInstallScript": true, - "dependencies": { - "@protobufjs/aspromise": "^1.1.2", - "@protobufjs/base64": "^1.1.2", - "@protobufjs/codegen": "^2.0.4", - "@protobufjs/eventemitter": "^1.1.0", - "@protobufjs/fetch": "^1.1.0", - "@protobufjs/float": "^1.0.2", - "@protobufjs/inquire": "^1.1.0", - "@protobufjs/path": "^1.1.2", - "@protobufjs/pool": "^1.1.0", - "@protobufjs/utf8": "^1.1.0", - "@types/node": ">=13.7.0", - "long": "^5.0.0" - }, - "engines": { - "node": ">=12.0.0" - } - }, - "node_modules/protons-runtime": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/protons-runtime/-/protons-runtime-5.0.1.tgz", - "integrity": "sha512-AwyAA3pQ4Ka4tEBMdIjLi/cRdpb322f7sgv3NruVq9yguLggzwu5eeLe1HuRPFYlI4UsVN/QK/AQXjLPVLCzTA==", - "dependencies": { - "protobufjs": "^7.0.0", - "uint8arraylist": "^2.4.3" - }, - "peerDependencies": { - "uint8arraylist": "^2.3.2" - } - }, - "node_modules/public-encrypt": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/public-encrypt/-/public-encrypt-4.0.3.tgz", - "integrity": "sha512-zVpa8oKZSz5bTMTFClc1fQOnyyEzpl5ozpi1B5YcvBrdohMjH2rfsBtyXcuNuwjsDIXmBYlF2N5FlJYhR29t8Q==", - "dev": true, - "dependencies": { - "bn.js": "^4.1.0", - "browserify-rsa": "^4.0.0", - "create-hash": "^1.1.0", - "parse-asn1": "^5.0.0", - "randombytes": "^2.0.1", - "safe-buffer": "^5.1.2" - } - }, - "node_modules/public-encrypt/node_modules/bn.js": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", - "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", - "dev": true - }, - "node_modules/punycode": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", - "integrity": "sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ==", - "dev": true - }, - "node_modules/qjobs": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/qjobs/-/qjobs-1.2.0.tgz", - "integrity": "sha512-8YOJEHtxpySA3fFDyCRxA+UUV+fA+rTWnuWvylOK/NCjhY+b4ocCtmu8TtsWb+mYeU+GCHf/S66KZF/AsteKHg==", - "dev": true, - "engines": { - "node": ">=0.9" - } - }, - "node_modules/qs": { - "version": "6.11.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", - "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", - "dev": true, - "dependencies": { - "side-channel": "^1.0.4" - }, - "engines": { - "node": ">=0.6" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/querystring-es3": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/querystring-es3/-/querystring-es3-0.2.1.tgz", - "integrity": "sha512-773xhDQnZBMFobEiztv8LIl70ch5MSF/jUQVlhwFyBILqq96anmoctVIYz+ZRp0qbCKATTn6ev02M3r7Ga5vqA==", - "dev": true, - "engines": { - "node": ">=0.4.x" - } - }, - "node_modules/queue-microtask": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", - "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/rabin-wasm": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/rabin-wasm/-/rabin-wasm-0.1.5.tgz", - "integrity": "sha512-uWgQTo7pim1Rnj5TuWcCewRDTf0PEFTSlaUjWP4eY9EbLV9em08v89oCz/WO+wRxpYuO36XEHp4wgYQnAgOHzA==", - "dependencies": { - "@assemblyscript/loader": "^0.9.4", - "bl": "^5.0.0", - "debug": "^4.3.1", - "minimist": "^1.2.5", - "node-fetch": "^2.6.1", - "readable-stream": "^3.6.0" - }, - "bin": { - "rabin-wasm": "cli/bin.js" - } - }, - "node_modules/rabin-wasm/node_modules/readable-stream": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", - "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/rambda": { - "version": "7.5.0", - "resolved": "https://registry.npmjs.org/rambda/-/rambda-7.5.0.tgz", - "integrity": "sha512-y/M9weqWAH4iopRd7EHDEQQvpFPHj1AA3oHozE9tfITHUtTR7Z9PSlIRRG2l1GuW7sefC1cXFfIcF+cgnShdBA==", - "dev": true - }, - "node_modules/randombytes": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", - "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", - "dependencies": { - "safe-buffer": "^5.1.0" - } - }, - "node_modules/randomfill": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/randomfill/-/randomfill-1.0.4.tgz", - "integrity": "sha512-87lcbR8+MhcWcUiQ+9e+Rwx8MyR2P7qnt15ynUlbm3TU/fjbgz4GsvfSUDTemtCCtVCqb4ZcEFlyPNTh9bBTLw==", - "dev": true, - "dependencies": { - "randombytes": "^2.0.5", - "safe-buffer": "^5.1.0" - } - }, - "node_modules/range-parser": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", - "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", - "dev": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/raw-body": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", - "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", - "dev": true, - "dependencies": { - "bytes": "3.1.2", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "unpipe": "1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/readable-stream": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.4.0.tgz", - "integrity": "sha512-kDMOq0qLtxV9f/SQv522h8cxZBqNZXuXNyjyezmfAAuribMyVXziljpQ/uQhfE1XLg2/TLTW2DsnoE4VAi/krg==", - "dependencies": { - "abort-controller": "^3.0.0", - "buffer": "^6.0.3", - "events": "^3.3.0", - "process": "^0.11.10" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - } - }, - "node_modules/readable-web-to-node-stream": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/readable-web-to-node-stream/-/readable-web-to-node-stream-3.0.2.tgz", - "integrity": "sha512-ePeK6cc1EcKLEhJFt/AebMCLL+GgSKhuygrZ/GLaKZYEecIgIECf4UaUuaByiGtzckwR4ain9VzUh95T1exYGw==", - "dependencies": { - "readable-stream": "^3.6.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/Borewit" - } - }, - "node_modules/readable-web-to-node-stream/node_modules/readable-stream": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", - "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/readdirp": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", - "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", - "dev": true, - "dependencies": { - "picomatch": "^2.2.1" - }, - "engines": { - "node": ">=8.10.0" - } - }, - "node_modules/require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/require-from-string": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", - "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", - "engines": { - "node": ">=0.10.0" - } - }, - "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/resolve": { - "version": "1.22.2", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.2.tgz", - "integrity": "sha512-Sb+mjNHOULsBv818T40qSPeRiuWLyaGMa5ewydRLFimneixmVy2zdivRl+AF6jaYPC8ERxGDmFSiqui6SfPd+g==", - "dev": true, - "dependencies": { - "is-core-module": "^2.11.0", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" - }, - "bin": { - "resolve": "bin/resolve" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/resolve-from": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/reusify": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", - "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", - "dev": true, - "engines": { - "iojs": ">=1.0.0", - "node": ">=0.10.0" - } - }, - "node_modules/rfdc": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.3.0.tgz", - "integrity": "sha512-V2hovdzFbOi77/WajaSMXk2OLm+xNIeQdMMuB7icj7bk6zi2F8GGAxigcnDFpJHbNyNcgyJDiP+8nOrY5cZGrA==", - "dev": true - }, - "node_modules/rimraf": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-4.4.0.tgz", - "integrity": "sha512-X36S+qpCUR0HjXlkDe4NAOhS//aHH0Z+h8Ckf2auGJk3PTnx5rLmrHkwNdbVQuCSUhOyFrlRvFEllZOYE+yZGQ==", - "dev": true, - "dependencies": { - "glob": "^9.2.0" - }, - "bin": { - "rimraf": "dist/cjs/src/bin.js" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/rimraf/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dev": true, - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/rimraf/node_modules/glob": { - "version": "9.3.5", - "resolved": "https://registry.npmjs.org/glob/-/glob-9.3.5.tgz", - "integrity": "sha512-e1LleDykUz2Iu+MTYdkSsuWX8lvAjAcs0Xef0lNIu0S2wOAzuTxCJtcd9S3cijlwYF18EsU3rzb8jPVobxDh9Q==", - "dev": true, - "dependencies": { - "fs.realpath": "^1.0.0", - "minimatch": "^8.0.2", - "minipass": "^4.2.4", - "path-scurry": "^1.6.1" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/rimraf/node_modules/minimatch": { - "version": "8.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-8.0.4.tgz", - "integrity": "sha512-W0Wvr9HyFXZRGIDgCicunpQ299OKXs9RgZfaukz4qAW/pJhcpUfupc9c+OObPOFueNy8VSrZgEmDtk6Kh4WzDA==", - "dev": true, - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/ripemd160": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.2.tgz", - "integrity": "sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA==", - "dev": true, - "dependencies": { - "hash-base": "^3.0.0", - "inherits": "^2.0.1" - } - }, - "node_modules/run-parallel": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", - "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "dependencies": { - "queue-microtask": "^1.2.2" - } - }, - "node_modules/run-parallel-limit": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/run-parallel-limit/-/run-parallel-limit-1.1.0.tgz", - "integrity": "sha512-jJA7irRNM91jaKc3Hcl1npHsFLOXOoTkPCUL1JEa1R82O2miplXXRaGdjW/KM/98YQWDhJLiSs793CnXfblJUw==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "dependencies": { - "queue-microtask": "^1.2.2" - } - }, - "node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" - }, - "node_modules/safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "dev": true - }, - "node_modules/schema-utils": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", - "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", - "dev": true, - "peer": true, - "dependencies": { - "@types/json-schema": "^7.0.8", - "ajv": "^6.12.5", - "ajv-keywords": "^3.5.2" - }, - "engines": { - "node": ">= 10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - } - }, - "node_modules/schema-utils/node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, - "peer": true, - "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/schema-utils/node_modules/ajv-keywords": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", - "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", - "dev": true, - "peer": true, - "peerDependencies": { - "ajv": "^6.9.1" - } - }, - "node_modules/schema-utils/node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true, - "peer": true - }, - "node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", - "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/semver/node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/serialize-javascript": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz", - "integrity": "sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==", - "dev": true, - "dependencies": { - "randombytes": "^2.1.0" - } - }, - "node_modules/setimmediate": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", - "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==", - "dev": true - }, - "node_modules/setprototypeof": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", - "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", - "dev": true - }, - "node_modules/sha.js": { - "version": "2.4.11", - "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", - "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", - "dev": true, - "dependencies": { - "inherits": "^2.0.1", - "safe-buffer": "^5.0.1" - }, - "bin": { - "sha.js": "bin.js" - } - }, - "node_modules/shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "dependencies": { - "shebang-regex": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/side-channel": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", - "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.0", - "get-intrinsic": "^1.0.2", - "object-inspect": "^1.9.0" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/signal-exit": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", - "dev": true - }, - "node_modules/sinon": { - "version": "15.0.2", - "resolved": "https://registry.npmjs.org/sinon/-/sinon-15.0.2.tgz", - "integrity": "sha512-PCVP63XZkg0/LOqQH5rEU4LILuvTFMb5tNxTHfs6VUMNnZz2XrnGSTZbAGITjzwQWbl/Bl/8hi4G3zZWjyBwHg==", - "dev": true, - "dependencies": { - "@sinonjs/commons": "^3.0.0", - "@sinonjs/fake-timers": "^10.0.2", - "@sinonjs/samsam": "^7.0.1", - "diff": "^5.1.0", - "nise": "^5.1.4", - "supports-color": "^7.2.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/sinon" - } - }, - "node_modules/sinon/node_modules/diff": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-5.1.0.tgz", - "integrity": "sha512-D+mk+qE8VC/PAUrlAU34N+VfXev0ghe5ywmpqrawphmVZc1bEfn56uo9qpyGp1p4xpzOHkSW4ztBd6L7Xx4ACw==", - "dev": true, - "engines": { - "node": ">=0.3.1" - } - }, - "node_modules/slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/socket.io": { - "version": "4.7.2", - "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.7.2.tgz", - "integrity": "sha512-bvKVS29/I5fl2FGLNHuXlQaUH/BlzX1IN6S+NKLNZpBsPZIDH+90eQmCs2Railn4YUiww4SzUedJ6+uzwFnKLw==", - "dev": true, - "dependencies": { - "accepts": "~1.3.4", - "base64id": "~2.0.0", - "cors": "~2.8.5", - "debug": "~4.3.2", - "engine.io": "~6.5.2", - "socket.io-adapter": "~2.5.2", - "socket.io-parser": "~4.2.4" - }, - "engines": { - "node": ">=10.2.0" - } - }, - "node_modules/socket.io-adapter": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.5.2.tgz", - "integrity": "sha512-87C3LO/NOMc+eMcpcxUBebGjkpMDkNBS9tf7KJqcDsmL936EChtVva71Dw2q4tQcuVC+hAUy4an2NO/sYXmwRA==", - "dev": true, - "dependencies": { - "ws": "~8.11.0" - } - }, - "node_modules/socket.io-parser": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.4.tgz", - "integrity": "sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==", - "dev": true, - "dependencies": { - "@socket.io/component-emitter": "~3.1.0", - "debug": "~4.3.1" - }, - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/source-map-js": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", - "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/source-map-loader": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/source-map-loader/-/source-map-loader-4.0.1.tgz", - "integrity": "sha512-oqXpzDIByKONVY8g1NUPOTQhe0UTU5bWUl32GSkqK2LjJj0HmwTMVKxcUip0RgAYhY1mqgOxjbQM48a0mmeNfA==", - "dev": true, - "dependencies": { - "abab": "^2.0.6", - "iconv-lite": "^0.6.3", - "source-map-js": "^1.0.2" - }, - "engines": { - "node": ">= 14.15.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "webpack": "^5.72.1" - } - }, - "node_modules/source-map-loader/node_modules/iconv-lite": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", - "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", - "dev": true, - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/source-map-support": { - "version": "0.5.21", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", - "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", - "dev": true, - "peer": true, - "dependencies": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } - }, - "node_modules/sparse-array": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/sparse-array/-/sparse-array-1.3.2.tgz", - "integrity": "sha512-ZT711fePGn3+kQyLuv1fpd3rNSkNF8vd5Kv2D+qnOANeyKs3fx6bUMGWRPvgTTcYV64QMqZKZwcuaQSP3AZ0tg==" - }, - "node_modules/statuses": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", - "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==", - "dev": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/stream-browserify": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-3.0.0.tgz", - "integrity": "sha512-H73RAHsVBapbim0tU2JwwOiXUj+fikfiaoYAKHF3VJfA0pe2BCzkhAHBlLG6REzE+2WNZcxOXjK7lkso+9euLA==", - "dev": true, - "dependencies": { - "inherits": "~2.0.4", - "readable-stream": "^3.5.0" - } - }, - "node_modules/stream-browserify/node_modules/readable-stream": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", - "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", - "dev": true, - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/stream-http": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/stream-http/-/stream-http-3.2.0.tgz", - "integrity": "sha512-Oq1bLqisTyK3TSCXpPbT4sdeYNdmyZJv1LxpEm2vu1ZhK89kSE5YXwZc3cWk0MagGaKriBh9mCFbVGtO+vY29A==", - "dev": true, - "dependencies": { - "builtin-status-codes": "^3.0.0", - "inherits": "^2.0.4", - "readable-stream": "^3.6.0", - "xtend": "^4.0.2" - } - }, - "node_modules/stream-http/node_modules/readable-stream": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", - "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", - "dev": true, - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/streamroller": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/streamroller/-/streamroller-3.1.5.tgz", - "integrity": "sha512-KFxaM7XT+irxvdqSP1LGLgNWbYN7ay5owZ3r/8t77p+EtSUAfUgtl7be3xtqtOmGUl9K9YPO2ca8133RlTjvKw==", - "dev": true, - "dependencies": { - "date-format": "^4.0.14", - "debug": "^4.3.4", - "fs-extra": "^8.1.0" - }, - "engines": { - "node": ">=8.0" - } - }, - "node_modules/string_decoder": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", - "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", - "dependencies": { - "safe-buffer": "~5.2.0" - } - }, - "node_modules/string_decoder/node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-json-comments": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "dev": true, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/supports-preserve-symlinks-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", - "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", - "dev": true, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/tapable": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", - "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==", - "dev": true, - "peer": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/terser": { - "version": "5.19.2", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.19.2.tgz", - "integrity": "sha512-qC5+dmecKJA4cpYxRa5aVkKehYsQKc+AHeKl0Oe62aYjBL8ZA33tTljktDHJSaxxMnbI5ZYw+o/S2DxxLu8OfA==", - "dev": true, - "peer": true, - "dependencies": { - "@jridgewell/source-map": "^0.3.3", - "acorn": "^8.8.2", - "commander": "^2.20.0", - "source-map-support": "~0.5.20" - }, - "bin": { - "terser": "bin/terser" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/terser-webpack-plugin": { - "version": "5.3.9", - "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.9.tgz", - "integrity": "sha512-ZuXsqE07EcggTWQjXUj+Aot/OMcD0bMKGgF63f7UxYcu5/AJF53aIpK1YoP5xR9l6s/Hy2b+t1AM0bLNPRuhwA==", - "dev": true, - "peer": true, - "dependencies": { - "@jridgewell/trace-mapping": "^0.3.17", - "jest-worker": "^27.4.5", - "schema-utils": "^3.1.1", - "serialize-javascript": "^6.0.1", - "terser": "^5.16.8" - }, - "engines": { - "node": ">= 10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "webpack": "^5.1.0" - }, - "peerDependenciesMeta": { - "@swc/core": { - "optional": true - }, - "esbuild": { - "optional": true - }, - "uglify-js": { - "optional": true - } - } - }, - "node_modules/terser-webpack-plugin/node_modules/serialize-javascript": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.1.tgz", - "integrity": "sha512-owoXEFjWRllis8/M1Q+Cw5k8ZH40e3zhp/ovX+Xr/vi1qj6QesbyXXViFbpNvWvPNAD62SutwEXavefrLJWj7w==", - "dev": true, - "peer": true, - "dependencies": { - "randombytes": "^2.1.0" - } - }, - "node_modules/test-exclude": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", - "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", - "dev": true, - "dependencies": { - "@istanbuljs/schema": "^0.1.2", - "glob": "^7.1.4", - "minimatch": "^3.0.4" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/text-table": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", - "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", - "dev": true - }, - "node_modules/timers-browserify": { - "version": "2.0.12", - "resolved": "https://registry.npmjs.org/timers-browserify/-/timers-browserify-2.0.12.tgz", - "integrity": "sha512-9phl76Cqm6FhSX9Xe1ZUAMLtm1BLkKj2Qd5ApyWkXzsMRaA7dgr81kf4wJmQf/hAvg8EEyJxDo3du/0KlhPiKQ==", - "dev": true, - "dependencies": { - "setimmediate": "^1.0.4" - }, - "engines": { - "node": ">=0.6.0" - } - }, - "node_modules/tmp": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.1.tgz", - "integrity": "sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ==", - "dev": true, - "dependencies": { - "rimraf": "^3.0.0" - }, - "engines": { - "node": ">=8.17.0" - } - }, - "node_modules/tmp/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/to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "dependencies": { - "is-number": "^7.0.0" - }, - "engines": { - "node": ">=8.0" - } - }, - "node_modules/toidentifier": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", - "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", - "dev": true, - "engines": { - "node": ">=0.6" - } - }, - "node_modules/tr46": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" - }, - "node_modules/tslib": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.1.tgz", - "integrity": "sha512-t0hLfiEKfMUoqhG+U1oid7Pva4bbDPHYfJNiB7BiIjRkj1pyC++4N3huJfqY6aRH6VTB0rvtzQwjM4K6qpfOig==" - }, - "node_modules/tsutils": { - "version": "3.21.0", - "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", - "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", - "dev": true, - "dependencies": { - "tslib": "^1.8.1" - }, - "engines": { - "node": ">= 6" - }, - "peerDependencies": { - "typescript": ">=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta" - } - }, - "node_modules/tsutils/node_modules/tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", - "dev": true - }, - "node_modules/tty-browserify": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/tty-browserify/-/tty-browserify-0.0.1.tgz", - "integrity": "sha512-C3TaO7K81YvjCgQH9Q1S3R3P3BtN3RIM8n+OvX4il1K1zgE8ZhI0op7kClgkxtutIE8hQrcrHBXvIheqKUUCxw==", - "dev": true - }, - "node_modules/tweetnacl": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-1.0.3.tgz", - "integrity": "sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw==" - }, - "node_modules/type-check": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", - "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", - "dev": true, - "dependencies": { - "prelude-ls": "^1.2.1" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/type-detect": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", - "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", - "engines": { - "node": ">=4" - } - }, - "node_modules/type-fest": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/type-is": { - "version": "1.6.18", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", - "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", - "dev": true, - "dependencies": { - "media-typer": "0.3.0", - "mime-types": "~2.1.24" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/typescript": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.1.6.tgz", - "integrity": "sha512-zaWCozRZ6DLEWAWFrVDz1H6FVXzUSfTy5FUMWsQlU8Ym5JP9eO4xkTIROFCQvhQf61z6O/G6ugw3SgAnvvm+HA==", - "dev": true, - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=14.17" - } - }, - "node_modules/ua-parser-js": { - "version": "0.7.35", - "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.35.tgz", - "integrity": "sha512-veRf7dawaj9xaWEu9HoTVn5Pggtc/qj+kqTOFvNiN1l0YdxwC1kvel57UCjThjGa3BHBihE8/UJAHI+uQHmd/g==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/ua-parser-js" - }, - { - "type": "paypal", - "url": "https://paypal.me/faisalman" - } - ], - "engines": { - "node": "*" - } - }, - "node_modules/uint8arraylist": { - "version": "2.4.3", - "resolved": "https://registry.npmjs.org/uint8arraylist/-/uint8arraylist-2.4.3.tgz", - "integrity": "sha512-oEVZr4/GrH87K0kjNce6z8pSCzLEPqHNLNR5sj8cJOySrTP8Vb/pMIbZKLJGhQKxm1TiZ31atNrpn820Pyqpow==", - "dependencies": { - "uint8arrays": "^4.0.2" - }, - "engines": { - "node": ">=16.0.0", - "npm": ">=7.0.0" - } - }, - "node_modules/uint8arrays": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-4.0.6.tgz", - "integrity": "sha512-4ZesjQhqOU2Ip6GPReIwN60wRxIupavL8T0Iy36BBHr2qyMrNxsPJvr7vpS4eFt8F8kSguWUPad6ZM9izs/vyw==", - "dependencies": { - "multiformats": "^12.0.1" - } - }, - "node_modules/uint8arrays/node_modules/multiformats": { - "version": "12.0.1", - "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-12.0.1.tgz", - "integrity": "sha512-s01wijBJoDUqESWSzePY0lvTw7J3PVO9x2Cc6ASI5AMZM2Gnhh7BC17+nlFhHKU7dDzaCaRfb+NiqNzOsgPUoQ==", - "engines": { - "node": ">=16.0.0", - "npm": ">=7.0.0" - } - }, - "node_modules/ulid": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/ulid/-/ulid-2.3.0.tgz", - "integrity": "sha512-keqHubrlpvT6G2wH0OEfSW4mquYRcbe/J8NMmveoQOjUqmo+hXtO+ORCpWhdbZ7k72UtY61BL7haGxW6enBnjw==", - "bin": { - "ulid": "bin/cli.js" - } - }, - "node_modules/universalify": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", - "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", - "dev": true, - "engines": { - "node": ">= 4.0.0" - } - }, - "node_modules/unpipe": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", - "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", - "dev": true, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/update-browserslist-db": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.11.tgz", - "integrity": "sha512-dCwEFf0/oT85M1fHBg4F0jtLwJrutGoHSQXCh7u4o2t1drG+c0a9Flnqww6XUKSfQMPpJBRjU8d4RXB09qtvaA==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "peer": true, - "dependencies": { - "escalade": "^3.1.1", - "picocolors": "^1.0.0" - }, - "bin": { - "update-browserslist-db": "cli.js" - }, - "peerDependencies": { - "browserslist": ">= 4.21.0" - } - }, - "node_modules/uri-js": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", - "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "dependencies": { - "punycode": "^2.1.0" - } - }, - "node_modules/uri-js/node_modules/punycode": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz", - "integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==", - "engines": { - "node": ">=6" - } - }, - "node_modules/url": { - "version": "0.11.1", - "resolved": "https://registry.npmjs.org/url/-/url-0.11.1.tgz", - "integrity": "sha512-rWS3H04/+mzzJkv0eZ7vEDGiQbgquI1fGfOad6zKvgYQi1SzMmhl7c/DdRGxhaWrVH6z0qWITo8rpnxK/RfEhA==", - "dev": true, - "dependencies": { - "punycode": "^1.4.1", - "qs": "^6.11.0" - } - }, - "node_modules/util": { - "version": "0.12.5", - "resolved": "https://registry.npmjs.org/util/-/util-0.12.5.tgz", - "integrity": "sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA==", - "dev": true, - "dependencies": { - "inherits": "^2.0.3", - "is-arguments": "^1.0.4", - "is-generator-function": "^1.0.7", - "is-typed-array": "^1.1.3", - "which-typed-array": "^1.1.2" - } - }, - "node_modules/util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" - }, - "node_modules/utils-merge": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", - "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", - "dev": true, - "engines": { - "node": ">= 0.4.0" - } - }, - "node_modules/uuid": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.0.tgz", - "integrity": "sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==", - "bin": { - "uuid": "dist/bin/uuid" - } - }, - "node_modules/v8-to-istanbul": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.1.0.tgz", - "integrity": "sha512-6z3GW9x8G1gd+JIIgQQQxXuiJtCXeAjp6RaPEPLv62mH3iPHPxV6W3robxtCzNErRo6ZwTmzWhsbNvjyEBKzKA==", - "dev": true, - "dependencies": { - "@jridgewell/trace-mapping": "^0.3.12", - "@types/istanbul-lib-coverage": "^2.0.1", - "convert-source-map": "^1.6.0" - }, - "engines": { - "node": ">=10.12.0" - } - }, - "node_modules/varint": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/varint/-/varint-6.0.0.tgz", - "integrity": "sha512-cXEIW6cfr15lFv563k4GuVuW/fiwjknytD37jIOLSdSWuOI6WnO/oKwmP2FQTU2l01LP8/M5TSAJpzUaGe3uWg==" - }, - "node_modules/vary": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", - "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", - "dev": true, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/vm-browserify": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/vm-browserify/-/vm-browserify-1.1.2.tgz", - "integrity": "sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ==", - "dev": true - }, - "node_modules/void-elements": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/void-elements/-/void-elements-2.0.1.tgz", - "integrity": "sha512-qZKX4RnBzH2ugr8Lxa7x+0V6XD9Sb/ouARtiasEQCHB1EVU4NXtmHsDDrx1dO4ne5fc3J6EW05BP1Dl0z0iung==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/watchpack": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz", - "integrity": "sha512-Lcvm7MGST/4fup+ifyKi2hjyIAwcdI4HRgtvTpIUxBRhB+RFtUh8XtDOxUfctVCnhVi+QQj49i91OyvzkJl6cg==", - "dev": true, - "peer": true, - "dependencies": { - "glob-to-regexp": "^0.4.1", - "graceful-fs": "^4.1.2" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/webidl-conversions": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", - "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" - }, - "node_modules/webpack": { - "version": "5.88.2", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.88.2.tgz", - "integrity": "sha512-JmcgNZ1iKj+aiR0OvTYtWQqJwq37Pf683dY9bVORwVbUrDhLhdn/PlO2sHsFHPkj7sHNQF3JwaAkp49V+Sq1tQ==", - "dev": true, - "peer": true, - "dependencies": { - "@types/eslint-scope": "^3.7.3", - "@types/estree": "^1.0.0", - "@webassemblyjs/ast": "^1.11.5", - "@webassemblyjs/wasm-edit": "^1.11.5", - "@webassemblyjs/wasm-parser": "^1.11.5", - "acorn": "^8.7.1", - "acorn-import-assertions": "^1.9.0", - "browserslist": "^4.14.5", - "chrome-trace-event": "^1.0.2", - "enhanced-resolve": "^5.15.0", - "es-module-lexer": "^1.2.1", - "eslint-scope": "5.1.1", - "events": "^3.2.0", - "glob-to-regexp": "^0.4.1", - "graceful-fs": "^4.2.9", - "json-parse-even-better-errors": "^2.3.1", - "loader-runner": "^4.2.0", - "mime-types": "^2.1.27", - "neo-async": "^2.6.2", - "schema-utils": "^3.2.0", - "tapable": "^2.1.1", - "terser-webpack-plugin": "^5.3.7", - "watchpack": "^2.4.0", - "webpack-sources": "^3.2.3" - }, - "bin": { - "webpack": "bin/webpack.js" - }, - "engines": { - "node": ">=10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependenciesMeta": { - "webpack-cli": { - "optional": true - } - } - }, - "node_modules/webpack-sources": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.3.tgz", - "integrity": "sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==", - "dev": true, - "peer": true, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/whatwg-url": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", - "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", - "dependencies": { - "tr46": "~0.0.3", - "webidl-conversions": "^3.0.0" - } - }, - "node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/which-typed-array": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.11.tgz", - "integrity": "sha512-qe9UWWpkeG5yzZ0tNYxDmd7vo58HDBc39mZ0xWWpolAGADdFOzkfamWLDxkOWcvHQKVmdTyQdLD4NOfjLWTKew==", - "dev": true, - "dependencies": { - "available-typed-arrays": "^1.0.5", - "call-bind": "^1.0.2", - "for-each": "^0.3.3", - "gopd": "^1.0.1", - "has-tostringtag": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/workerpool": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.2.1.tgz", - "integrity": "sha512-ILEIE97kDZvF9Wb9f6h5aXK4swSlKGUcOEGiIYb2OOu/IrDU9iwj0fD//SsA6E5ibwJxpEvhullJY4Sl4GcpAw==", - "dev": true - }, - "node_modules/wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "dev": true - }, - "node_modules/ws": { - "version": "8.11.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.11.0.tgz", - "integrity": "sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg==", - "dev": true, - "engines": { - "node": ">=10.0.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": "^5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - }, - "node_modules/xtend": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", - "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", - "dev": true, - "engines": { - "node": ">=0.4" - } - }, - "node_modules/y18n": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "dev": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - }, - "node_modules/yargs": { - "version": "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/yargs-parser": { - "version": "20.2.9", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", - "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", - "dev": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/yargs-unparser": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz", - "integrity": "sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==", - "dev": true, - "dependencies": { - "camelcase": "^6.0.0", - "decamelize": "^4.0.0", - "flat": "^5.0.2", - "is-plain-obj": "^2.1.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/yocto-queue": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", - "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "packages/common": { - "name": "@tbd54566975/common", - "version": "0.8.0", - "license": "Apache-2.0", - "dependencies": { - "multiformats": "11.0.2" - }, - "devDependencies": { - "@types/chai": "4.3.0", - "@types/eslint": "8.37.0", - "@types/mocha": "10.0.1", - "@typescript-eslint/eslint-plugin": "5.59.0", - "@typescript-eslint/parser": "5.59.0", - "c8": "8.0.0", - "chai": "4.3.7", - "esbuild": "0.16.17", - "eslint": "8.39.0", - "eslint-plugin-mocha": "10.1.0", - "karma": "6.4.1", - "karma-chai": "0.1.0", - "karma-chrome-launcher": "3.1.1", - "karma-esbuild": "2.2.5", - "karma-firefox-launcher": "2.1.2", - "karma-mocha": "2.0.1", - "karma-mocha-reporter": "2.2.5", - "karma-webkit-launcher": "2.1.0", - "mocha": "10.2.0", - "playwright": "1.36.2", - "rimraf": "4.4.0", - "typescript": "5.1.6" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "packages/credentials": { - "name": "@tbd54566975/credentials", - "version": "0.8.0", - "license": "Apache-2.0", - "devDependencies": { - "@types/chai": "4.3.0", - "@types/eslint": "8.37.0", - "@types/mocha": "10.0.1", - "@typescript-eslint/eslint-plugin": "5.59.0", - "@typescript-eslint/parser": "5.59.0", - "c8": "8.0.0", - "chai": "4.3.7", - "esbuild": "0.16.17", - "eslint": "8.39.0", - "eslint-plugin-mocha": "10.1.0", - "karma": "6.4.1", - "karma-chai": "0.1.0", - "karma-chrome-launcher": "3.1.1", - "karma-esbuild": "2.2.5", - "karma-firefox-launcher": "2.1.2", - "karma-mocha": "2.0.1", - "karma-mocha-reporter": "2.2.5", - "karma-webkit-launcher": "2.1.0", - "mocha": "10.2.0", - "playwright": "1.36.2", - "rimraf": "4.4.0", - "typescript": "5.1.6" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "packages/crypto": { - "name": "@tbd54566975/crypto", - "version": "0.8.0", - "license": "Apache-2.0", - "dependencies": { - "@noble/ciphers": "0.1.3", - "@noble/curves": "1.1.0", - "@noble/hashes": "1.3.1", - "@tbd54566975/common": "0.8.0", - "ed2curve": "0.3.0" - }, - "devDependencies": { - "@types/chai": "4.3.0", - "@types/chai-as-promised": "7.1.5", - "@types/ed2curve": "0.2.2", - "@types/eslint": "8.37.0", - "@types/mocha": "10.0.1", - "@types/sinon": "10.0.15", - "@types/uuid": "9.0.1", - "@typescript-eslint/eslint-plugin": "5.59.0", - "@typescript-eslint/parser": "5.59.0", - "c8": "8.0.0", - "chai": "4.3.7", - "chai-as-promised": "7.1.1", - "esbuild": "0.16.17", - "eslint": "8.39.0", - "eslint-plugin-mocha": "10.1.0", - "karma": "6.4.1", - "karma-chai": "0.1.0", - "karma-chrome-launcher": "3.1.1", - "karma-esbuild": "2.2.5", - "karma-firefox-launcher": "2.1.2", - "karma-mocha": "2.0.1", - "karma-mocha-reporter": "2.2.5", - "karma-webkit-launcher": "2.1.0", - "mocha": "10.2.0", - "playwright": "1.36.2", - "rimraf": "4.4.0", - "sinon": "15.0.2", - "source-map-loader": "4.0.1", - "typescript": "5.1.6" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "packages/dids": { - "name": "@tbd54566975/dids", - "version": "0.8.0", - "license": "Apache-2.0", - "dependencies": { - "@decentralized-identity/ion-tools": "1.1.4", - "@tbd54566975/common": "0.8.0", - "@tbd54566975/crypto": "0.8.0", - "@tbd54566975/dwn-sdk-js": "0.2.1", - "cross-fetch": "4.0.0" - }, - "devDependencies": { - "@types/chai": "4.3.0", - "@types/chai-as-promised": "7.1.5", - "@types/eslint": "8.37.0", - "@types/mocha": "10.0.1", - "@types/sinon": "10.0.15", - "@typescript-eslint/eslint-plugin": "5.59.0", - "@typescript-eslint/parser": "5.59.0", - "c8": "8.0.0", - "chai": "4.3.7", - "chai-as-promised": "7.1.1", - "esbuild": "0.16.17", - "eslint": "8.39.0", - "eslint-plugin-mocha": "10.1.0", - "karma": "6.4.1", - "karma-chai": "0.1.0", - "karma-chrome-launcher": "3.1.1", - "karma-esbuild": "2.2.5", - "karma-firefox-launcher": "2.1.2", - "karma-mocha": "2.0.1", - "karma-mocha-reporter": "2.2.5", - "karma-webkit-launcher": "2.1.0", - "mocha": "10.2.0", - "node-stdlib-browser": "1.2.0", - "playwright": "1.36.2", - "rimraf": "4.4.0", - "sinon": "15.0.2", - "source-map-loader": "4.0.1", - "typescript": "5.1.6" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "packages/web5": { - "name": "@tbd54566975/web5", - "version": "0.8.0", - "license": "Apache-2.0", - "dependencies": { - "@decentralized-identity/ion-tools": "1.1.4", - "@tbd54566975/crypto": "0.8.0", - "@tbd54566975/dids": "0.8.0", - "@tbd54566975/dwn-sdk-js": "0.2.1", - "@tbd54566975/web5-agent": "0.8.0", - "@tbd54566975/web5-proxy-agent": "0.8.0", - "@tbd54566975/web5-user-agent": "0.8.0", - "level": "8.0.0", - "ms": "2.1.3", - "readable-web-to-node-stream": "3.0.2" - }, - "devDependencies": { - "@types/chai": "4.3.0", - "@types/chai-as-promised": "7.1.5", - "@types/eslint": "8.37.0", - "@types/mocha": "10.0.1", - "@types/ms": "0.7.31", - "@types/readable-stream": "2.3.15", - "@types/sinon": "10.0.15", - "@typescript-eslint/eslint-plugin": "5.59.0", - "@typescript-eslint/parser": "5.59.0", - "c8": "8.0.0", - "chai": "4.3.7", - "chai-as-promised": "7.1.1", - "esbuild": "0.16.17", - "eslint": "8.39.0", - "eslint-plugin-mocha": "10.1.0", - "karma": "6.4.1", - "karma-chai": "0.1.0", - "karma-chrome-launcher": "3.1.1", - "karma-esbuild": "2.2.5", - "karma-firefox-launcher": "2.1.2", - "karma-mocha": "2.0.1", - "karma-mocha-reporter": "2.2.5", - "karma-webkit-launcher": "2.1.0", - "mocha": "10.2.0", - "node-stdlib-browser": "1.2.0", - "playwright": "1.36.2", - "rimraf": "4.4.0", - "sinon": "15.0.2", - "source-map-loader": "4.0.1", - "typescript": "5.1.6" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "packages/web5-agent": { - "name": "@tbd54566975/web5-agent", - "version": "0.8.0", - "license": "Apache-2.0", - "dependencies": { - "@tbd54566975/dwn-sdk-js": "0.2.1", - "readable-stream": "4.4.0" - }, - "devDependencies": { - "@types/chai": "4.3.0", - "@types/chai-as-promised": "7.1.5", - "@types/eslint": "8.37.0", - "@types/mocha": "10.0.1", - "@types/readable-stream": "2.3.15", - "@types/sinon": "10.0.15", - "@typescript-eslint/eslint-plugin": "5.59.0", - "@typescript-eslint/parser": "5.59.0", - "c8": "8.0.0", - "chai": "4.3.7", - "chai-as-promised": "7.1.1", - "esbuild": "0.16.17", - "eslint": "8.39.0", - "eslint-plugin-mocha": "10.1.0", - "karma": "6.4.1", - "karma-chai": "0.1.0", - "karma-chrome-launcher": "3.1.1", - "karma-esbuild": "2.2.5", - "karma-firefox-launcher": "2.1.2", - "karma-mocha": "2.0.1", - "karma-mocha-reporter": "2.2.5", - "karma-webkit-launcher": "2.1.0", - "mocha": "10.2.0", - "playwright": "1.36.2", - "rimraf": "4.4.0", - "sinon": "15.0.2", - "source-map-loader": "4.0.1", - "typescript": "5.1.6" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "packages/web5-proxy-agent": { - "name": "@tbd54566975/web5-proxy-agent", - "version": "0.8.0", - "license": "Apache-2.0", - "dependencies": { - "@tbd54566975/web5-agent": "0.8.0" - }, - "devDependencies": { - "@types/chai": "4.3.0", - "@types/chai-as-promised": "7.1.5", - "@types/ed2curve": "0.2.2", - "@types/eslint": "8.37.0", - "@types/mocha": "10.0.1", - "@types/sinon": "10.0.15", - "@typescript-eslint/eslint-plugin": "5.59.0", - "@typescript-eslint/parser": "5.59.0", - "c8": "8.0.0", - "chai": "4.3.7", - "chai-as-promised": "7.1.1", - "esbuild": "0.16.17", - "eslint": "8.39.0", - "eslint-plugin-mocha": "10.1.0", - "karma": "6.4.1", - "karma-chai": "0.1.0", - "karma-chrome-launcher": "3.1.1", - "karma-esbuild": "2.2.5", - "karma-firefox-launcher": "2.1.2", - "karma-mocha": "2.0.1", - "karma-mocha-reporter": "2.2.5", - "karma-webkit-launcher": "2.1.0", - "mocha": "10.2.0", - "playwright": "1.36.2", - "rimraf": "4.4.0", - "sinon": "15.0.2", - "source-map-loader": "4.0.1", - "typescript": "5.1.6" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "packages/web5-user-agent": { - "name": "@tbd54566975/web5-user-agent", - "version": "0.8.0", - "license": "Apache-2.0", - "dependencies": { - "@decentralized-identity/ion-tools": "1.1.4", - "@tbd54566975/dids": "0.8.0", - "@tbd54566975/dwn-sdk-js": "0.2.1", - "@tbd54566975/web5-agent": "0.8.0", - "abstract-level": "1.0.3", - "cross-fetch": "4.0.0", - "flat": "5.0.2", - "level": "8.0.0", - "readable-web-to-node-stream": "3.0.2", - "uuid": "9.0.0" - }, - "devDependencies": { - "@faker-js/faker": "8.0.1", - "@types/chai": "4.3.0", - "@types/chai-as-promised": "7.1.5", - "@types/flat": "5.0.2", - "@types/mocha": "10.0.1", - "@types/readable-stream": "2.3.15", - "@types/sinon": "10.0.15", - "@typescript-eslint/eslint-plugin": "5.59.0", - "@typescript-eslint/parser": "5.59.0", - "c8": "8.0.0", - "chai": "4.3.7", - "chai-as-promised": "7.1.1", - "esbuild": "0.16.17", - "eslint": "8.39.0", - "eslint-plugin-mocha": "10.1.0", - "karma": "6.4.1", - "karma-chai": "0.1.0", - "karma-chrome-launcher": "3.1.1", - "karma-esbuild": "2.2.5", - "karma-firefox-launcher": "2.1.2", - "karma-mocha": "2.0.1", - "karma-mocha-reporter": "2.2.5", - "karma-webkit-launcher": "2.1.0", - "mocha": "10.2.0", - "node-stdlib-browser": "1.2.0", - "playwright": "1.36.2", - "rimraf": "4.4.0", - "sinon": "15.0.2", - "source-map-loader": "4.0.1", - "typescript": "5.1.6" - }, - "engines": { - "node": ">=18.0.0" - } + "name": "web5-js", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "workspaces": [ + "packages/common", + "packages/crypto", + "packages/dids", + "packages/credentials", + "packages/agent", + "packages/user-agent", + "packages/proxy-agent", + "packages/api", + "packages/identity-agent" + ] + }, + "node_modules/@aashutoshrathi/word-wrap": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz", + "integrity": "sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@assemblyscript/loader": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/@assemblyscript/loader/-/loader-0.9.4.tgz", + "integrity": "sha512-HazVq9zwTVwGmqdwYzu7WyQ6FQVZ7SwET0KKQuKm55jD0IfUpZgN0OPIiZG3zV1iSrVYcN0bdwLRXI/VNCYsUA==" + }, + "node_modules/@bcoe/v8-coverage": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", + "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", + "dev": true + }, + "node_modules/@colors/colors": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", + "integrity": "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==", + "dev": true, + "engines": { + "node": ">=0.1.90" + } + }, + "node_modules/@decentralized-identity/ion-pow-sdk": { + "version": "1.0.17", + "resolved": "https://registry.npmjs.org/@decentralized-identity/ion-pow-sdk/-/ion-pow-sdk-1.0.17.tgz", + "integrity": "sha512-vk7DTDM8aKDbFyu1ad/qkoRrGL4q+KvNeL/FNZXhkWPaDhVExBN/qGEoRLf1YSfFe+myto3+4RYTPut+riiqnw==", + "dependencies": { + "buffer": "6.0.3", + "cross-fetch": "3.1.5", + "hash-wasm": "4.9.0" + } + }, + "node_modules/@decentralized-identity/ion-sdk": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@decentralized-identity/ion-sdk/-/ion-sdk-1.0.1.tgz", + "integrity": "sha512-+P+DXcRSFjsEsI5KIqUmVjpzgUT28B2lWpTO+IxiBcfibAN/1Sg20NebGTO/+serz2CnSZf95N2a1OZ6eXypGQ==", + "dependencies": { + "@noble/ed25519": "^2.0.0", + "@noble/secp256k1": "^2.0.0", + "canonicalize": "^2.0.0", + "multiformats": "^12.0.1", + "multihashes": "^4.0.3", + "uri-js": "^4.4.1" + } + }, + "node_modules/@decentralized-identity/ion-sdk/node_modules/multiformats": { + "version": "12.0.1", + "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-12.0.1.tgz", + "integrity": "sha512-s01wijBJoDUqESWSzePY0lvTw7J3PVO9x2Cc6ASI5AMZM2Gnhh7BC17+nlFhHKU7dDzaCaRfb+NiqNzOsgPUoQ==", + "engines": { + "node": ">=16.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.16.17", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.16.17.tgz", + "integrity": "sha512-N9x1CMXVhtWEAMS7pNNONyA14f71VPQN9Cnavj1XQh6T7bskqiLLrSca4O0Vr8Wdcga943eThxnVp3JLnBMYtw==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.16.17", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.16.17.tgz", + "integrity": "sha512-MIGl6p5sc3RDTLLkYL1MyL8BMRN4tLMRCn+yRJJmEDvYZ2M7tmAf80hx1kbNEUX2KJ50RRtxZ4JHLvCfuB6kBg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.16.17", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.16.17.tgz", + "integrity": "sha512-a3kTv3m0Ghh4z1DaFEuEDfz3OLONKuFvI4Xqczqx4BqLyuFaFkuaG4j2MtA6fuWEFeC5x9IvqnX7drmRq/fyAQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.16.17", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.16.17.tgz", + "integrity": "sha512-/2agbUEfmxWHi9ARTX6OQ/KgXnOWfsNlTeLcoV7HSuSTv63E4DqtAc+2XqGw1KHxKMHGZgbVCZge7HXWX9Vn+w==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.16.17", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.16.17.tgz", + "integrity": "sha512-2By45OBHulkd9Svy5IOCZt376Aa2oOkiE9QWUK9fe6Tb+WDr8hXL3dpqi+DeLiMed8tVXspzsTAvd0jUl96wmg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.16.17", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.16.17.tgz", + "integrity": "sha512-mt+cxZe1tVx489VTb4mBAOo2aKSnJ33L9fr25JXpqQqzbUIw/yzIzi+NHwAXK2qYV1lEFp4OoVeThGjUbmWmdw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.16.17", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.16.17.tgz", + "integrity": "sha512-8ScTdNJl5idAKjH8zGAsN7RuWcyHG3BAvMNpKOBaqqR7EbUhhVHOqXRdL7oZvz8WNHL2pr5+eIT5c65kA6NHug==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.16.17", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.16.17.tgz", + "integrity": "sha512-iihzrWbD4gIT7j3caMzKb/RsFFHCwqqbrbH9SqUSRrdXkXaygSZCZg1FybsZz57Ju7N/SHEgPyaR0LZ8Zbe9gQ==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.16.17", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.16.17.tgz", + "integrity": "sha512-7S8gJnSlqKGVJunnMCrXHU9Q8Q/tQIxk/xL8BqAP64wchPCTzuM6W3Ra8cIa1HIflAvDnNOt2jaL17vaW+1V0g==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.16.17", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.16.17.tgz", + "integrity": "sha512-kiX69+wcPAdgl3Lonh1VI7MBr16nktEvOfViszBSxygRQqSpzv7BffMKRPMFwzeJGPxcio0pdD3kYQGpqQ2SSg==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.16.17", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.16.17.tgz", + "integrity": "sha512-dTzNnQwembNDhd654cA4QhbS9uDdXC3TKqMJjgOWsC0yNCbpzfWoXdZvp0mY7HU6nzk5E0zpRGGx3qoQg8T2DQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.16.17", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.16.17.tgz", + "integrity": "sha512-ezbDkp2nDl0PfIUn0CsQ30kxfcLTlcx4Foz2kYv8qdC6ia2oX5Q3E/8m6lq84Dj/6b0FrkgD582fJMIfHhJfSw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.16.17", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.16.17.tgz", + "integrity": "sha512-dzS678gYD1lJsW73zrFhDApLVdM3cUF2MvAa1D8K8KtcSKdLBPP4zZSLy6LFZ0jYqQdQ29bjAHJDgz0rVbLB3g==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.16.17", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.16.17.tgz", + "integrity": "sha512-ylNlVsxuFjZK8DQtNUwiMskh6nT0vI7kYl/4fZgV1llP5d6+HIeL/vmmm3jpuoo8+NuXjQVZxmKuhDApK0/cKw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.16.17", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.16.17.tgz", + "integrity": "sha512-gzy7nUTO4UA4oZ2wAMXPNBGTzZFP7mss3aKR2hH+/4UUkCOyqmjXiKpzGrY2TlEUhbbejzXVKKGazYcQTZWA/w==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.16.17", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.16.17.tgz", + "integrity": "sha512-mdPjPxfnmoqhgpiEArqi4egmBAMYvaObgn4poorpUaqmvzzbvqbowRllQ+ZgzGVMGKaPkqUmPDOOFQRUFDmeUw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.16.17", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.16.17.tgz", + "integrity": "sha512-/PzmzD/zyAeTUsduZa32bn0ORug+Jd1EGGAUJvqfeixoEISYpGnAezN6lnJoskauoai0Jrs+XSyvDhppCPoKOA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.16.17", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.16.17.tgz", + "integrity": "sha512-2yaWJhvxGEz2RiftSk0UObqJa/b+rIAjnODJgv2GbGGpRwAfpgzyrg1WLK8rqA24mfZa9GvpjLcBBg8JHkoodg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.16.17", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.16.17.tgz", + "integrity": "sha512-xtVUiev38tN0R3g8VhRfN7Zl42YCJvyBhRKw1RJjwE1d2emWTVToPLNEQj/5Qxc6lVFATDiy6LjVHYhIPrLxzw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.16.17", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.16.17.tgz", + "integrity": "sha512-ga8+JqBDHY4b6fQAmOgtJJue36scANy4l/rL97W+0wYmijhxKetzZdKOJI7olaBaMhWt8Pac2McJdZLxXWUEQw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.16.17", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.16.17.tgz", + "integrity": "sha512-WnsKaf46uSSF/sZhwnqE4L/F89AYNMiD4YtEcYekBt9Q7nj0DiId2XH2Ng2PHM54qi5oPrQ8luuzGszqi/veig==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.16.17", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.16.17.tgz", + "integrity": "sha512-y+EHuSchhL7FjHgvQL/0fnnFmO4T1bhvWANX6gcnqTjtnKWbTvUMCpGnv2+t+31d7RzyEAYAd4u2fnIhHL6N/Q==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", + "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", + "dev": true, + "dependencies": { + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.6.2.tgz", + "integrity": "sha512-pPTNuaAG3QMH+buKyBIGJs3g/S5y0caxw0ygM3YyE6yJFySwiGGSzA+mM3KJ8QQvzeLh3blwgSonkFjgQdxzMw==", + "dev": true, + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.2.tgz", + "integrity": "sha512-+wvgpDsrB1YqAMdEUCcnTlpfVBH7Vqn6A/NT3D8WVXFIaKMlErPIZT3oCIAVCOtarRpMtelZLqJeU3t7WY6X6g==", + "dev": true, + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^9.6.0", + "globals": "^13.19.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/@eslint/eslintrc/node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "node_modules/@eslint/js": { + "version": "8.39.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.39.0.tgz", + "integrity": "sha512-kf9RB0Fg7NZfap83B3QOqOGg9QmD9yBudqQXzzOtn3i4y7ZUXe5ONeW34Gwi+TxhH4mvj72R1Zc300KUMa9Bng==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/@humanwhocodes/config-array": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.10.tgz", + "integrity": "sha512-KVVjQmNUepDVGXNuoRRdmmEjruj0KfiGSbS8LVc12LMsWDQzRXJ0qdhN8L8uUigKpfEHRhlaQFY0ib1tnUbNeQ==", + "dev": true, + "dependencies": { + "@humanwhocodes/object-schema": "^1.2.1", + "debug": "^4.1.1", + "minimatch": "^3.0.5" + }, + "engines": { + "node": ">=10.10.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/object-schema": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", + "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", + "dev": true + }, + "node_modules/@ipld/dag-cbor": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/@ipld/dag-cbor/-/dag-cbor-9.0.3.tgz", + "integrity": "sha512-A2UFccS0+sARK9xwXiVZIaWbLbPxLGP3UZOjBeOMWfDY04SXi8h1+t4rHBzOlKYF/yWNm3RbFLyclWO7hZcy4g==", + "dependencies": { + "cborg": "^2.0.1", + "multiformats": "^12.0.1" + }, + "engines": { + "node": ">=16.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/@ipld/dag-cbor/node_modules/multiformats": { + "version": "12.0.1", + "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-12.0.1.tgz", + "integrity": "sha512-s01wijBJoDUqESWSzePY0lvTw7J3PVO9x2Cc6ASI5AMZM2Gnhh7BC17+nlFhHKU7dDzaCaRfb+NiqNzOsgPUoQ==", + "engines": { + "node": ">=16.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/@ipld/dag-pb": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/@ipld/dag-pb/-/dag-pb-4.0.5.tgz", + "integrity": "sha512-El2Jhmv6bWuakhvnw1dl6xOhqLeVhlY8DIAJ06NtZRAoDcOzeGzvOtPzMCszVgCT0EQz+LOctyfgQ5Oszba19A==", + "dependencies": { + "multiformats": "^12.0.1" + }, + "engines": { + "node": ">=16.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/@ipld/dag-pb/node_modules/multiformats": { + "version": "12.0.1", + "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-12.0.1.tgz", + "integrity": "sha512-s01wijBJoDUqESWSzePY0lvTw7J3PVO9x2Cc6ASI5AMZM2Gnhh7BC17+nlFhHKU7dDzaCaRfb+NiqNzOsgPUoQ==", + "engines": { + "node": ">=16.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/@istanbuljs/schema": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", + "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz", + "integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==", + "dev": true, + "peer": true, + "dependencies": { + "@jridgewell/set-array": "^1.0.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.9" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz", + "integrity": "sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/set-array": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", + "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", + "dev": true, + "peer": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/source-map": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.5.tgz", + "integrity": "sha512-UTYAUj/wviwdsMfzoSJspJxbkH5o1snzwX0//0ENX1u/55kkZZkcTZP6u9bwKGkv+dkk9at4m1Cpt0uY80kcpQ==", + "dev": true, + "peer": true, + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.0", + "@jridgewell/trace-mapping": "^0.3.9" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.4.15", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", + "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", + "dev": true + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.19", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.19.tgz", + "integrity": "sha512-kf37QtfW+Hwx/buWGMPcR60iF9ziHa6r/CZJIHbmcm4+0qrXiVdxegAH0F6yddEVQ7zdkjcGCgCzUu+BcbhQxw==", + "dev": true, + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@js-temporal/polyfill": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/@js-temporal/polyfill/-/polyfill-0.4.4.tgz", + "integrity": "sha512-2X6bvghJ/JAoZO52lbgyAPFj8uCflhTo2g7nkFzEQdXd/D8rEeD4HtmTEpmtGCva260fcd66YNXBOYdnmHqSOg==", + "dependencies": { + "jsbi": "^4.3.0", + "tslib": "^2.4.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@multiformats/base-x": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@multiformats/base-x/-/base-x-4.0.1.tgz", + "integrity": "sha512-eMk0b9ReBbV23xXU693TAIrLyeO5iTgBZGSJfpqriG8UkYvr/hC9u9pyMlAakDNHWmbhMZCDs6KQO0jzKD8OTw==" + }, + "node_modules/@multiformats/murmur3": { + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/@multiformats/murmur3/-/murmur3-2.1.6.tgz", + "integrity": "sha512-kpJDN+o8B0gJaaqbdV/spIVPj35hqew4rEw8VzPmcITsLpHSgP8pJDeaVaGGVeX/UM8n4IGctLCxw7PBfVks+A==", + "dependencies": { + "multiformats": "^12.0.1", + "murmurhash3js-revisited": "^3.0.0" + }, + "engines": { + "node": ">=16.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/@multiformats/murmur3/node_modules/multiformats": { + "version": "12.0.1", + "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-12.0.1.tgz", + "integrity": "sha512-s01wijBJoDUqESWSzePY0lvTw7J3PVO9x2Cc6ASI5AMZM2Gnhh7BC17+nlFhHKU7dDzaCaRfb+NiqNzOsgPUoQ==", + "engines": { + "node": ">=16.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/@noble/ciphers": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/@noble/ciphers/-/ciphers-0.1.4.tgz", + "integrity": "sha512-d3ZR8vGSpy3v/nllS+bD/OMN5UZqusWiQqkyj7AwzTnhXFH72pF5oB4Ach6DQ50g5kXxC28LdaYBEpsyv9KOUQ==", + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@noble/curves": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.1.0.tgz", + "integrity": "sha512-091oBExgENk/kGj3AZmtBDMpxQPDtxQABR2B9lb1JbVTs6ytdzZNwvhxQ4MWasRNEzlbEH8jCWFCwhF/Obj5AA==", + "dependencies": { + "@noble/hashes": "1.3.1" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@noble/ed25519": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@noble/ed25519/-/ed25519-2.0.0.tgz", + "integrity": "sha512-/extjhkwFupyopDrt80OMWKdLgP429qLZj+z6sYJz90rF2Iz0gjZh2ArMKPImUl13Kx+0EXI2hN9T/KJV0/Zng==", + "funding": [ + { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + ] + }, + "node_modules/@noble/hashes": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.1.tgz", + "integrity": "sha512-EbqwksQwz9xDRGfDST86whPBgM65E0OH/pCgqW0GBVzO22bNE+NuIbeTb714+IfSjU3aRk47EUvXIb5bTsenKA==", + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@noble/secp256k1": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@noble/secp256k1/-/secp256k1-2.0.0.tgz", + "integrity": "sha512-rUGBd95e2a45rlmFTqQJYEFA4/gdIARFfuTuTqLglz0PZ6AKyzyXsEZZq7UZn8hZsvaBgpCzKKBJizT2cJERXw==", + "funding": [ + { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + ] + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@playwright/test": { + "version": "1.36.2", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.36.2.tgz", + "integrity": "sha512-2rVZeyPRjxfPH6J0oGJqE8YxiM1IBRyM8hyrXYK7eSiAqmbNhxwcLa7dZ7fy9Kj26V7FYia5fh9XJRq4Dqme+g==", + "dev": true, + "dependencies": { + "@types/node": "*", + "playwright-core": "1.36.2" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=16" + }, + "optionalDependencies": { + "fsevents": "2.3.2" + } + }, + "node_modules/@protobufjs/aspromise": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", + "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==" + }, + "node_modules/@protobufjs/base64": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", + "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==" + }, + "node_modules/@protobufjs/codegen": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz", + "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==" + }, + "node_modules/@protobufjs/eventemitter": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz", + "integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==" + }, + "node_modules/@protobufjs/fetch": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz", + "integrity": "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==", + "dependencies": { + "@protobufjs/aspromise": "^1.1.1", + "@protobufjs/inquire": "^1.1.0" + } + }, + "node_modules/@protobufjs/float": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", + "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==" + }, + "node_modules/@protobufjs/inquire": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz", + "integrity": "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==" + }, + "node_modules/@protobufjs/path": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", + "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==" + }, + "node_modules/@protobufjs/pool": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", + "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==" + }, + "node_modules/@protobufjs/utf8": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", + "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==" + }, + "node_modules/@sinonjs/commons": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.0.tgz", + "integrity": "sha512-jXBtWAF4vmdNmZgD5FoKsVLv3rPgDnLgPbU84LIJ3otV44vJlDRokVng5v8NFJdCf/da9legHcKaRuZs4L7faA==", + "dev": true, + "dependencies": { + "type-detect": "4.0.8" + } + }, + "node_modules/@sinonjs/fake-timers": { + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz", + "integrity": "sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==", + "dev": true, + "dependencies": { + "@sinonjs/commons": "^3.0.0" + } + }, + "node_modules/@sinonjs/samsam": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-7.0.1.tgz", + "integrity": "sha512-zsAk2Jkiq89mhZovB2LLOdTCxJF4hqqTToGP0ASWlhp4I1hqOjcfmZGafXntCN7MDC6yySH0mFHrYtHceOeLmw==", + "dev": true, + "dependencies": { + "@sinonjs/commons": "^2.0.0", + "lodash.get": "^4.4.2", + "type-detect": "^4.0.8" + } + }, + "node_modules/@sinonjs/samsam/node_modules/@sinonjs/commons": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-2.0.0.tgz", + "integrity": "sha512-uLa0j859mMrg2slwQYdO/AkrOfmH+X6LTVmNTS9CqexuE2IvVORIkSpJLqePAbEnKJ77aMmCwr1NUZ57120Xcg==", + "dev": true, + "dependencies": { + "type-detect": "4.0.8" + } + }, + "node_modules/@sinonjs/text-encoding": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/@sinonjs/text-encoding/-/text-encoding-0.7.2.tgz", + "integrity": "sha512-sXXKG+uL9IrKqViTtao2Ws6dy0znu9sOaP1di/jKGW1M6VssO8vlpXCQcpZ+jisQ1tTFAC5Jo/EOzFbggBagFQ==", + "dev": true + }, + "node_modules/@socket.io/component-emitter": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.0.tgz", + "integrity": "sha512-+9jVqKhRSpsc591z5vX+X5Yyw+he/HCB4iQ/RYxw35CEPaY1gnsNE43nf9n9AaYjAQrTiI/mOwKUKdUs9vf7Xg==", + "dev": true + }, + "node_modules/@tbd54566975/dwn-sdk-js": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/@tbd54566975/dwn-sdk-js/-/dwn-sdk-js-0.2.1.tgz", + "integrity": "sha512-7rFi0zvpt0F/6E2Pow5+iCnf35YGIUneI9U4J43A8NfP0Gh5S2eJkApvhrPOyt50Xq2MerFR9F5E3BE2E0jJRQ==", + "dependencies": { + "@ipld/dag-cbor": "9.0.3", + "@js-temporal/polyfill": "0.4.4", + "@noble/ed25519": "2.0.0", + "@noble/secp256k1": "2.0.0", + "abstract-level": "1.0.3", + "ajv": "8.12.0", + "blockstore-core": "4.2.0", + "cross-fetch": "4.0.0", + "eciesjs": "0.4.0", + "flat": "5.0.2", + "interface-blockstore": "5.2.3", + "interface-store": "5.1.2", + "ipfs-unixfs-exporter": "13.1.5", + "ipfs-unixfs-importer": "15.1.5", + "level": "8.0.0", + "lodash": "4.17.21", + "lru-cache": "9.1.2", + "ms": "2.1.3", + "multiformats": "11.0.2", + "randombytes": "2.1.0", + "readable-stream": "4.4.0", + "ulid": "2.3.0", + "uuid": "8.3.2", + "varint": "6.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@tbd54566975/dwn-sdk-js/node_modules/cross-fetch": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-4.0.0.tgz", + "integrity": "sha512-e4a5N8lVvuLgAWgnCrLr2PP0YyDOTHa9H/Rj54dirp61qXnNq46m82bRhNqIA5VccJtWBvPTFRV3TtvHUKPB1g==", + "dependencies": { + "node-fetch": "^2.6.12" + } + }, + "node_modules/@tbd54566975/dwn-sdk-js/node_modules/node-fetch": { + "version": "2.6.12", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.12.tgz", + "integrity": "sha512-C/fGU2E8ToujUivIO0H+tpQ6HWo4eEmchoPIoXtxCrVghxdKq+QOHqEZW7tuP3KlV3bC8FRMO5nMCC7Zm1VP6g==", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/@tbd54566975/dwn-sdk-js/node_modules/readable-stream": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.4.0.tgz", + "integrity": "sha512-kDMOq0qLtxV9f/SQv522h8cxZBqNZXuXNyjyezmfAAuribMyVXziljpQ/uQhfE1XLg2/TLTW2DsnoE4VAi/krg==", + "dependencies": { + "abort-controller": "^3.0.0", + "buffer": "^6.0.3", + "events": "^3.3.0", + "process": "^0.11.10" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/@types/chai": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.3.0.tgz", + "integrity": "sha512-/ceqdqeRraGolFTcfoXNiqjyQhZzbINDngeoAq9GoHa8PPK1yNzTaxWjA6BFWp5Ua9JpXEMSS4s5i9tS0hOJtw==", + "dev": true + }, + "node_modules/@types/chai-as-promised": { + "version": "7.1.5", + "resolved": "https://registry.npmjs.org/@types/chai-as-promised/-/chai-as-promised-7.1.5.tgz", + "integrity": "sha512-jStwss93SITGBwt/niYrkf2C+/1KTeZCZl1LaeezTlqppAKeoQC7jxyqYuP72sxBGKCIbw7oHgbYssIRzT5FCQ==", + "dev": true, + "dependencies": { + "@types/chai": "*" + } + }, + "node_modules/@types/cookie": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.4.1.tgz", + "integrity": "sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q==", + "dev": true + }, + "node_modules/@types/cors": { + "version": "2.8.13", + "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.13.tgz", + "integrity": "sha512-RG8AStHlUiV5ysZQKq97copd2UmVYw3/pRMLefISZ3S1hK104Cwm7iLQ3fTKx+lsUH2CE8FlLaYeEA2LSeqYUA==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/ed2curve": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/@types/ed2curve/-/ed2curve-0.2.2.tgz", + "integrity": "sha512-G1sTX5xo91ydevQPINbL2nfgVAj/s1ZiqZxC8OCWduwu+edoNGUm5JXtTkg9F3LsBZbRI46/0HES4CPUE2wc9g==", + "dev": true, + "dependencies": { + "tweetnacl": "^1.0.0" + } + }, + "node_modules/@types/eslint": { + "version": "8.37.0", + "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.37.0.tgz", + "integrity": "sha512-Piet7dG2JBuDIfohBngQ3rCt7MgO9xCO4xIMKxBThCq5PNRB91IjlJ10eJVwfoNtvTErmxLzwBZ7rHZtbOMmFQ==", + "dev": true, + "dependencies": { + "@types/estree": "*", + "@types/json-schema": "*" + } + }, + "node_modules/@types/eslint-scope": { + "version": "3.7.4", + "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.4.tgz", + "integrity": "sha512-9K4zoImiZc3HlIp6AVUDE4CWYx22a+lhSZMYNpbjW04+YF0KWj4pJXnEMjdnFTiQibFFmElcsasJXDbdI/EPhA==", + "dev": true, + "peer": true, + "dependencies": { + "@types/eslint": "*", + "@types/estree": "*" + } + }, + "node_modules/@types/estree": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.1.tgz", + "integrity": "sha512-LG4opVs2ANWZ1TJoKc937iMmNstM/d0ae1vNbnBvBhqCSezgVUOzcLCqbI5elV8Vy6WKwKjaqR+zO9VKirBBCA==", + "dev": true + }, + "node_modules/@types/istanbul-lib-coverage": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz", + "integrity": "sha512-z/QT1XN4K4KYuslS23k62yDIDLwLFkzxOuMplDtObz0+y7VqJCaO2o+SPwHCvLFZh7xazvvoor2tA/hPz9ee7g==", + "dev": true + }, + "node_modules/@types/json-schema": { + "version": "7.0.12", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.12.tgz", + "integrity": "sha512-Hr5Jfhc9eYOQNPYO5WLDq/n4jqijdHNlDXjuAQkkt+mWdQR+XJToOHrsD4cPaMXpn6KO7y2+wM8AZEs8VpBLVA==", + "dev": true + }, + "node_modules/@types/mocha": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-10.0.1.tgz", + "integrity": "sha512-/fvYntiO1GeICvqbQ3doGDIP97vWmvFt83GKguJ6prmQM2iXZfFcq6YE8KteFyRtX2/h5Hf91BYvPodJKFYv5Q==", + "dev": true + }, + "node_modules/@types/ms": { + "version": "0.7.31", + "resolved": "https://registry.npmjs.org/@types/ms/-/ms-0.7.31.tgz", + "integrity": "sha512-iiUgKzV9AuaEkZqkOLDIvlQiL6ltuZd9tGcW3gwpnX8JbuiuhFlEGmmFXEXkN50Cvq7Os88IY2v0dkDqXYWVgA==", + "dev": true + }, + "node_modules/@types/node": { + "version": "20.5.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.5.0.tgz", + "integrity": "sha512-Mgq7eCtoTjT89FqNoTzzXg2XvCi5VMhRV6+I2aYanc6kQCBImeNaAYRs/DyoVqk1YEUJK5gN9VO7HRIdz4Wo3Q==" + }, + "node_modules/@types/readable-stream": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@types/readable-stream/-/readable-stream-4.0.0.tgz", + "integrity": "sha512-s6YqDV111kwuFsT9SwFC+FmZ5n1SEp4H9DXGg6Zqag0lPGeEvBGP9UaLJYpX4cxY7fAFnx2avy1QVvft0LLb7g==", + "dev": true, + "dependencies": { + "@types/node": "*", + "safe-buffer": "~5.1.1" + } + }, + "node_modules/@types/semver": { + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.0.tgz", + "integrity": "sha512-G8hZ6XJiHnuhQKR7ZmysCeJWE08o8T0AXtk5darsCaTVsYZhhgUrq53jizaR2FvsoeCwJhlmwTjkXBY5Pn/ZHw==", + "dev": true + }, + "node_modules/@types/sinon": { + "version": "10.0.15", + "resolved": "https://registry.npmjs.org/@types/sinon/-/sinon-10.0.15.tgz", + "integrity": "sha512-3lrFNQG0Kr2LDzvjyjB6AMJk4ge+8iYhQfdnSwIwlG88FUOV43kPcQqDZkDa/h3WSZy6i8Fr0BSjfQtB1B3xuQ==", + "dev": true, + "dependencies": { + "@types/sinonjs__fake-timers": "*" + } + }, + "node_modules/@types/sinonjs__fake-timers": { + "version": "8.1.2", + "resolved": "https://registry.npmjs.org/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-8.1.2.tgz", + "integrity": "sha512-9GcLXF0/v3t80caGs5p2rRfkB+a8VBGLJZVih6CNFkx8IZ994wiKKLSRs9nuFwk1HevWs/1mnUmkApGrSGsShA==", + "dev": true + }, + "node_modules/@types/uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-rFT3ak0/2trgvp4yYZo5iKFEPsET7vKydKF+VRCxlQ9bpheehyAJH89dAkaLEq/j/RZXJIqcgsmPJKUP1Z28HA==", + "dev": true + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "5.59.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.59.0.tgz", + "integrity": "sha512-p0QgrEyrxAWBecR56gyn3wkG15TJdI//eetInP3zYRewDh0XS+DhB3VUAd3QqvziFsfaQIoIuZMxZRB7vXYaYw==", + "dev": true, + "dependencies": { + "@eslint-community/regexpp": "^4.4.0", + "@typescript-eslint/scope-manager": "5.59.0", + "@typescript-eslint/type-utils": "5.59.0", + "@typescript-eslint/utils": "5.59.0", + "debug": "^4.3.4", + "grapheme-splitter": "^1.0.4", + "ignore": "^5.2.0", + "natural-compare-lite": "^1.4.0", + "semver": "^7.3.7", + "tsutils": "^3.21.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^5.0.0", + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "5.59.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.59.0.tgz", + "integrity": "sha512-qK9TZ70eJtjojSUMrrEwA9ZDQ4N0e/AuoOIgXuNBorXYcBDk397D2r5MIe1B3cok/oCtdNC5j+lUUpVB+Dpb+w==", + "dev": true, + "dependencies": { + "@typescript-eslint/scope-manager": "5.59.0", + "@typescript-eslint/types": "5.59.0", + "@typescript-eslint/typescript-estree": "5.59.0", + "debug": "^4.3.4" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "5.59.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.59.0.tgz", + "integrity": "sha512-tsoldKaMh7izN6BvkK6zRMINj4Z2d6gGhO2UsI8zGZY3XhLq1DndP3Ycjhi1JwdwPRwtLMW4EFPgpuKhbCGOvQ==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "5.59.0", + "@typescript-eslint/visitor-keys": "5.59.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "5.59.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.59.0.tgz", + "integrity": "sha512-d/B6VSWnZwu70kcKQSCqjcXpVH+7ABKH8P1KNn4K7j5PXXuycZTPXF44Nui0TEm6rbWGi8kc78xRgOC4n7xFgA==", + "dev": true, + "dependencies": { + "@typescript-eslint/typescript-estree": "5.59.0", + "@typescript-eslint/utils": "5.59.0", + "debug": "^4.3.4", + "tsutils": "^3.21.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/types": { + "version": "5.59.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.59.0.tgz", + "integrity": "sha512-yR2h1NotF23xFFYKHZs17QJnB51J/s+ud4PYU4MqdZbzeNxpgUr05+dNeCN/bb6raslHvGdd6BFCkVhpPk/ZeA==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "5.59.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.59.0.tgz", + "integrity": "sha512-sUNnktjmI8DyGzPdZ8dRwW741zopGxltGs/SAPgGL/AAgDpiLsCFLcMNSpbfXfmnNeHmK9h3wGmCkGRGAoUZAg==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "5.59.0", + "@typescript-eslint/visitor-keys": "5.59.0", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "semver": "^7.3.7", + "tsutils": "^3.21.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "5.59.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.59.0.tgz", + "integrity": "sha512-GGLFd+86drlHSvPgN/el6dRQNYYGOvRSDVydsUaQluwIW3HvbXuxyuD5JETvBt/9qGYe+lOrDk6gRrWOHb/FvA==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@types/json-schema": "^7.0.9", + "@types/semver": "^7.3.12", + "@typescript-eslint/scope-manager": "5.59.0", + "@typescript-eslint/types": "5.59.0", + "@typescript-eslint/typescript-estree": "5.59.0", + "eslint-scope": "^5.1.1", + "semver": "^7.3.7" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "5.59.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.59.0.tgz", + "integrity": "sha512-qZ3iXxQhanchCeaExlKPV3gDQFxMUmU35xfd5eCXB6+kUw1TUAbIy2n7QIrwz9s98DQLzNWyHp61fY0da4ZcbA==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "5.59.0", + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@web5/agent": { + "resolved": "packages/agent", + "link": true + }, + "node_modules/@web5/api": { + "resolved": "packages/api", + "link": true + }, + "node_modules/@web5/common": { + "resolved": "packages/common", + "link": true + }, + "node_modules/@web5/credentials": { + "resolved": "packages/credentials", + "link": true + }, + "node_modules/@web5/crypto": { + "resolved": "packages/crypto", + "link": true + }, + "node_modules/@web5/dids": { + "resolved": "packages/dids", + "link": true + }, + "node_modules/@web5/identity-agent": { + "resolved": "packages/identity-agent", + "link": true + }, + "node_modules/@web5/proxy-agent": { + "resolved": "packages/proxy-agent", + "link": true + }, + "node_modules/@web5/user-agent": { + "resolved": "packages/user-agent", + "link": true + }, + "node_modules/@webassemblyjs/ast": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.6.tgz", + "integrity": "sha512-IN1xI7PwOvLPgjcf180gC1bqn3q/QaOCwYUahIOhbYUu8KA/3tw2RT/T0Gidi1l7Hhj5D/INhJxiICObqpMu4Q==", + "dev": true, + "peer": true, + "dependencies": { + "@webassemblyjs/helper-numbers": "1.11.6", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6" + } + }, + "node_modules/@webassemblyjs/floating-point-hex-parser": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.6.tgz", + "integrity": "sha512-ejAj9hfRJ2XMsNHk/v6Fu2dGS+i4UaXBXGemOfQ/JfQ6mdQg/WXtwleQRLLS4OvfDhv8rYnVwH27YJLMyYsxhw==", + "dev": true, + "peer": true + }, + "node_modules/@webassemblyjs/helper-api-error": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.6.tgz", + "integrity": "sha512-o0YkoP4pVu4rN8aTJgAyj9hC2Sv5UlkzCHhxqWj8butaLvnpdc2jOwh4ewE6CX0txSfLn/UYaV/pheS2Txg//Q==", + "dev": true, + "peer": true + }, + "node_modules/@webassemblyjs/helper-buffer": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.6.tgz", + "integrity": "sha512-z3nFzdcp1mb8nEOFFk8DrYLpHvhKC3grJD2ardfKOzmbmJvEf/tPIqCY+sNcwZIY8ZD7IkB2l7/pqhUhqm7hLA==", + "dev": true, + "peer": true + }, + "node_modules/@webassemblyjs/helper-numbers": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.6.tgz", + "integrity": "sha512-vUIhZ8LZoIWHBohiEObxVm6hwP034jwmc9kuq5GdHZH0wiLVLIPcMCdpJzG4C11cHoQ25TFIQj9kaVADVX7N3g==", + "dev": true, + "peer": true, + "dependencies": { + "@webassemblyjs/floating-point-hex-parser": "1.11.6", + "@webassemblyjs/helper-api-error": "1.11.6", + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@webassemblyjs/helper-wasm-bytecode": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.6.tgz", + "integrity": "sha512-sFFHKwcmBprO9e7Icf0+gddyWYDViL8bpPjJJl0WHxCdETktXdmtWLGVzoHbqUcY4Be1LkNfwTmXOJUFZYSJdA==", + "dev": true, + "peer": true + }, + "node_modules/@webassemblyjs/helper-wasm-section": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.11.6.tgz", + "integrity": "sha512-LPpZbSOwTpEC2cgn4hTydySy1Ke+XEu+ETXuoyvuyezHO3Kjdu90KK95Sh9xTbmjrCsUwvWwCOQQNta37VrS9g==", + "dev": true, + "peer": true, + "dependencies": { + "@webassemblyjs/ast": "1.11.6", + "@webassemblyjs/helper-buffer": "1.11.6", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6", + "@webassemblyjs/wasm-gen": "1.11.6" + } + }, + "node_modules/@webassemblyjs/ieee754": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.11.6.tgz", + "integrity": "sha512-LM4p2csPNvbij6U1f19v6WR56QZ8JcHg3QIJTlSwzFcmx6WSORicYj6I63f9yU1kEUtrpG+kjkiIAkevHpDXrg==", + "dev": true, + "peer": true, + "dependencies": { + "@xtuc/ieee754": "^1.2.0" + } + }, + "node_modules/@webassemblyjs/leb128": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.11.6.tgz", + "integrity": "sha512-m7a0FhE67DQXgouf1tbN5XQcdWoNgaAuoULHIfGFIEVKA6tu/edls6XnIlkmS6FrXAquJRPni3ZZKjw6FSPjPQ==", + "dev": true, + "peer": true, + "dependencies": { + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@webassemblyjs/utf8": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.11.6.tgz", + "integrity": "sha512-vtXf2wTQ3+up9Zsg8sa2yWiQpzSsMyXj0qViVP6xKGCUT8p8YJ6HqI7l5eCnWx1T/FYdsv07HQs2wTFbbof/RA==", + "dev": true, + "peer": true + }, + "node_modules/@webassemblyjs/wasm-edit": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.6.tgz", + "integrity": "sha512-Ybn2I6fnfIGuCR+Faaz7YcvtBKxvoLV3Lebn1tM4o/IAJzmi9AWYIPWpyBfU8cC+JxAO57bk4+zdsTjJR+VTOw==", + "dev": true, + "peer": true, + "dependencies": { + "@webassemblyjs/ast": "1.11.6", + "@webassemblyjs/helper-buffer": "1.11.6", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6", + "@webassemblyjs/helper-wasm-section": "1.11.6", + "@webassemblyjs/wasm-gen": "1.11.6", + "@webassemblyjs/wasm-opt": "1.11.6", + "@webassemblyjs/wasm-parser": "1.11.6", + "@webassemblyjs/wast-printer": "1.11.6" + } + }, + "node_modules/@webassemblyjs/wasm-gen": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.6.tgz", + "integrity": "sha512-3XOqkZP/y6B4F0PBAXvI1/bky7GryoogUtfwExeP/v7Nzwo1QLcq5oQmpKlftZLbT+ERUOAZVQjuNVak6UXjPA==", + "dev": true, + "peer": true, + "dependencies": { + "@webassemblyjs/ast": "1.11.6", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6", + "@webassemblyjs/ieee754": "1.11.6", + "@webassemblyjs/leb128": "1.11.6", + "@webassemblyjs/utf8": "1.11.6" + } + }, + "node_modules/@webassemblyjs/wasm-opt": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.6.tgz", + "integrity": "sha512-cOrKuLRE7PCe6AsOVl7WasYf3wbSo4CeOk6PkrjS7g57MFfVUF9u6ysQBBODX0LdgSvQqRiGz3CXvIDKcPNy4g==", + "dev": true, + "peer": true, + "dependencies": { + "@webassemblyjs/ast": "1.11.6", + "@webassemblyjs/helper-buffer": "1.11.6", + "@webassemblyjs/wasm-gen": "1.11.6", + "@webassemblyjs/wasm-parser": "1.11.6" + } + }, + "node_modules/@webassemblyjs/wasm-parser": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.6.tgz", + "integrity": "sha512-6ZwPeGzMJM3Dqp3hCsLgESxBGtT/OeCvCZ4TA1JUPYgmhAx38tTPR9JaKy0S5H3evQpO/h2uWs2j6Yc/fjkpTQ==", + "dev": true, + "peer": true, + "dependencies": { + "@webassemblyjs/ast": "1.11.6", + "@webassemblyjs/helper-api-error": "1.11.6", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6", + "@webassemblyjs/ieee754": "1.11.6", + "@webassemblyjs/leb128": "1.11.6", + "@webassemblyjs/utf8": "1.11.6" + } + }, + "node_modules/@webassemblyjs/wast-printer": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.11.6.tgz", + "integrity": "sha512-JM7AhRcE+yW2GWYaKeHL5vt4xqee5N2WcezptmgyhNS+ScggqcT1OtXykhAb13Sn5Yas0j2uv9tHgrjwvzAP4A==", + "dev": true, + "peer": true, + "dependencies": { + "@webassemblyjs/ast": "1.11.6", + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@xtuc/ieee754": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", + "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", + "dev": true, + "peer": true + }, + "node_modules/@xtuc/long": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", + "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", + "dev": true, + "peer": true + }, + "node_modules/abab": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.6.tgz", + "integrity": "sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA==", + "dev": true + }, + "node_modules/abort-controller": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", + "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", + "dependencies": { + "event-target-shim": "^5.0.0" + }, + "engines": { + "node": ">=6.5" + } + }, + "node_modules/abstract-level": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/abstract-level/-/abstract-level-1.0.3.tgz", + "integrity": "sha512-t6jv+xHy+VYwc4xqZMn2Pa9DjcdzvzZmQGRjTFc8spIbRGHgBrEKbPq+rYXc7CCo0lxgYvSgKVg9qZAhpVQSjA==", + "dependencies": { + "buffer": "^6.0.3", + "catering": "^2.1.0", + "is-buffer": "^2.0.5", + "level-supports": "^4.0.0", + "level-transcoder": "^1.0.1", + "module-error": "^1.0.1", + "queue-microtask": "^1.2.3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "dev": true, + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/acorn": { + "version": "8.10.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.10.0.tgz", + "integrity": "sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-import-assertions": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.9.0.tgz", + "integrity": "sha512-cmMwop9x+8KFhxvKrKfPYmN6/pKTYYHBqLa0DfvVZcKMJWNyWLnaqND7dx/qn66R7ewM1UX5XMaDVP5wlVTaVA==", + "dev": true, + "peer": true, + "peerDependencies": { + "acorn": "^8" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/ajv": { + "version": "8.12.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", + "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-colors": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", + "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "node_modules/array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/asn1.js": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-5.4.1.tgz", + "integrity": "sha512-+I//4cYPccV8LdmBLiX8CYvf9Sp3vQsrqu2QNXRcrbiWvcx/UdlFiqUJJzxRQxgsZmvhXhn4cSKeSmoFjVdupA==", + "dev": true, + "dependencies": { + "bn.js": "^4.0.0", + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0", + "safer-buffer": "^2.1.0" + } + }, + "node_modules/asn1.js/node_modules/bn.js": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", + "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", + "dev": true + }, + "node_modules/assert": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/assert/-/assert-2.0.0.tgz", + "integrity": "sha512-se5Cd+js9dXJnu6Ag2JFc00t+HmHOen+8Q+L7O9zI0PqQXr20uk2J0XQqMxZEeo5U50o8Nvmmx7dZrl+Ufr35A==", + "dev": true, + "dependencies": { + "es6-object-assign": "^1.1.0", + "is-nan": "^1.2.1", + "object-is": "^1.0.1", + "util": "^0.12.0" + } + }, + "node_modules/assertion-error": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", + "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/available-typed-arrays": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz", + "integrity": "sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/base64id": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz", + "integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==", + "dev": true, + "engines": { + "node": "^4.5.0 || >= 5.9" + } + }, + "node_modules/binary-extensions": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", + "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/bl": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-5.1.0.tgz", + "integrity": "sha512-tv1ZJHLfTDnXE6tMHv73YgSJaWR2AFuPwMntBe7XL/GBFHnT0CLnsHMogfk5+GzCDC5ZWarSCYaIGATZt9dNsQ==", + "dependencies": { + "buffer": "^6.0.3", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "node_modules/bl/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/blockstore-core": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/blockstore-core/-/blockstore-core-4.2.0.tgz", + "integrity": "sha512-F8BCobc75D+9/+hUD+5cixbU6zmZA+lBgNiuBkNlJqRgmAaBBvLOQF6Ad9Jei0Nvmy2a1jaF4CiN76W1apIghA==", + "dependencies": { + "err-code": "^3.0.1", + "interface-blockstore": "^5.0.0", + "interface-store": "^5.0.0", + "multiformats": "^11.0.2" + }, + "engines": { + "node": ">=16.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/bn.js": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.1.tgz", + "integrity": "sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ==", + "dev": true + }, + "node_modules/body-parser": { + "version": "1.20.2", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz", + "integrity": "sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==", + "dev": true, + "dependencies": { + "bytes": "3.1.2", + "content-type": "~1.0.5", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "on-finished": "2.4.1", + "qs": "6.11.0", + "raw-body": "2.5.2", + "type-is": "~1.6.18", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/body-parser/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/body-parser/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "dependencies": { + "fill-range": "^7.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/brorand": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz", + "integrity": "sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w==", + "dev": true + }, + "node_modules/browser-level": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/browser-level/-/browser-level-1.0.1.tgz", + "integrity": "sha512-XECYKJ+Dbzw0lbydyQuJzwNXtOpbMSq737qxJN11sIRTErOMShvDpbzTlgju7orJKvx4epULolZAuJGLzCmWRQ==", + "dependencies": { + "abstract-level": "^1.0.2", + "catering": "^2.1.1", + "module-error": "^1.0.2", + "run-parallel-limit": "^1.1.0" + } + }, + "node_modules/browser-resolve": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/browser-resolve/-/browser-resolve-2.0.0.tgz", + "integrity": "sha512-7sWsQlYL2rGLy2IWm8WL8DCTJvYLc/qlOnsakDac87SOoCd16WLsaAMdCiAqsTNHIe+SXfaqyxyo6THoWqs8WQ==", + "dev": true, + "dependencies": { + "resolve": "^1.17.0" + } + }, + "node_modules/browser-stdout": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", + "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", + "dev": true + }, + "node_modules/browserify-aes": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz", + "integrity": "sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==", + "dev": true, + "dependencies": { + "buffer-xor": "^1.0.3", + "cipher-base": "^1.0.0", + "create-hash": "^1.1.0", + "evp_bytestokey": "^1.0.3", + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/browserify-cipher": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/browserify-cipher/-/browserify-cipher-1.0.1.tgz", + "integrity": "sha512-sPhkz0ARKbf4rRQt2hTpAHqn47X3llLkUGn+xEJzLjwY8LRs2p0v7ljvI5EyoRO/mexrNunNECisZs+gw2zz1w==", + "dev": true, + "dependencies": { + "browserify-aes": "^1.0.4", + "browserify-des": "^1.0.0", + "evp_bytestokey": "^1.0.0" + } + }, + "node_modules/browserify-des": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/browserify-des/-/browserify-des-1.0.2.tgz", + "integrity": "sha512-BioO1xf3hFwz4kc6iBhI3ieDFompMhrMlnDFC4/0/vd5MokpuAc3R+LYbwTA9A5Yc9pq9UYPqffKpW2ObuwX5A==", + "dev": true, + "dependencies": { + "cipher-base": "^1.0.1", + "des.js": "^1.0.0", + "inherits": "^2.0.1", + "safe-buffer": "^5.1.2" + } + }, + "node_modules/browserify-rsa": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.1.0.tgz", + "integrity": "sha512-AdEER0Hkspgno2aR97SAf6vi0y0k8NuOpGnVH3O99rcA5Q6sh8QxcngtHuJ6uXwnfAXNM4Gn1Gb7/MV1+Ymbog==", + "dev": true, + "dependencies": { + "bn.js": "^5.0.0", + "randombytes": "^2.0.1" + } + }, + "node_modules/browserify-sign": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/browserify-sign/-/browserify-sign-4.2.1.tgz", + "integrity": "sha512-/vrA5fguVAKKAVTNJjgSm1tRQDHUU6DbwO9IROu/0WAzC8PKhucDSh18J0RMvVeHAn5puMd+QHC2erPRNf8lmg==", + "dev": true, + "dependencies": { + "bn.js": "^5.1.1", + "browserify-rsa": "^4.0.1", + "create-hash": "^1.2.0", + "create-hmac": "^1.1.7", + "elliptic": "^6.5.3", + "inherits": "^2.0.4", + "parse-asn1": "^5.1.5", + "readable-stream": "^3.6.0", + "safe-buffer": "^5.2.0" + } + }, + "node_modules/browserify-sign/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dev": true, + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/browserify-sign/node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/browserify-zlib": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/browserify-zlib/-/browserify-zlib-0.2.0.tgz", + "integrity": "sha512-Z942RysHXmJrhqk88FmKBVq/v5tqmSkDz7p54G/MGyjMnCFFnC79XWNbg+Vta8W6Wb2qtSZTSxIGkJrRpCFEiA==", + "dev": true, + "dependencies": { + "pako": "~1.0.5" + } + }, + "node_modules/browserslist": { + "version": "4.21.10", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.10.tgz", + "integrity": "sha512-bipEBdZfVH5/pwrvqc+Ub0kUPVfGUhlKxbvfD+z1BDnPEO/X98ruXGA1WP5ASpAFKan7Qr6j736IacbZQuAlKQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "peer": true, + "dependencies": { + "caniuse-lite": "^1.0.30001517", + "electron-to-chromium": "^1.4.477", + "node-releases": "^2.0.13", + "update-browserslist-db": "^1.0.11" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true, + "peer": true + }, + "node_modules/buffer-xor": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/buffer-xor/-/buffer-xor-1.0.3.tgz", + "integrity": "sha512-571s0T7nZWK6vB67HI5dyUF7wXiNcfaPPPTl6zYCNApANjIvYJTg7hlud/+cJpdAhS7dVzqMLmfhfHR3rAcOjQ==", + "dev": true + }, + "node_modules/builtin-status-codes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz", + "integrity": "sha512-HpGFw18DgFWlncDfjTa2rcQ4W88O1mC8e8yZ2AvQY5KDaktSTwo+KRf6nHK6FRI5FyRyb/5T6+TSxfP7QyGsmQ==", + "dev": true + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/c8": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/c8/-/c8-8.0.0.tgz", + "integrity": "sha512-XHA5vSfCLglAc0Xt8eLBZMv19lgiBSjnb1FLAQgnwkuhJYEonpilhEB4Ea3jPAbm0FhD6VVJrc0z73jPe7JyGQ==", + "dev": true, + "dependencies": { + "@bcoe/v8-coverage": "^0.2.3", + "@istanbuljs/schema": "^0.1.3", + "find-up": "^5.0.0", + "foreground-child": "^2.0.0", + "istanbul-lib-coverage": "^3.2.0", + "istanbul-lib-report": "^3.0.0", + "istanbul-reports": "^3.1.4", + "rimraf": "^3.0.2", + "test-exclude": "^6.0.0", + "v8-to-istanbul": "^9.0.0", + "yargs": "^16.2.0", + "yargs-parser": "^20.2.9" + }, + "bin": { + "c8": "bin/c8.js" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/c8/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/call-bind": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", + "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.1", + "get-intrinsic": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001521", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001521.tgz", + "integrity": "sha512-fnx1grfpEOvDGH+V17eccmNjucGUnCbP6KL+l5KqBIerp26WK/+RQ7CIDE37KGJjaPyqWXXlFUyKiWmvdNNKmQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "peer": true + }, + "node_modules/canonicalize": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/canonicalize/-/canonicalize-2.0.0.tgz", + "integrity": "sha512-ulDEYPv7asdKvqahuAY35c1selLdzDwHqugK92hfkzvlDCwXRRelDkR+Er33md/PtnpqHemgkuDPanZ4fiYZ8w==" + }, + "node_modules/catering": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/catering/-/catering-2.1.1.tgz", + "integrity": "sha512-K7Qy8O9p76sL3/3m7/zLKbRkyOlSZAgzEaLhyj2mXS8PsCud2Eo4hAb8aLtZqHh0QGqLcb9dlJSu6lHRVENm1w==", + "engines": { + "node": ">=6" + } + }, + "node_modules/cborg": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/cborg/-/cborg-2.0.4.tgz", + "integrity": "sha512-QradkXyNBLIyg1XNxcoXqUG4stcOhfuR1uexq+qNzL+EvFV5TXvN+c+LCh5XXxTv2fjBIkNB+3I6IO17EnKuKg==", + "bin": { + "cborg": "cli.js" + } + }, + "node_modules/chai": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/chai/-/chai-4.3.7.tgz", + "integrity": "sha512-HLnAzZ2iupm25PlN0xFreAlBA5zaBSv3og0DdeGA4Ar6h6rJ3A0rolRUKJhSF2V10GZKDgWF/VmAEsNWjCRB+A==", + "dev": true, + "dependencies": { + "assertion-error": "^1.1.0", + "check-error": "^1.0.2", + "deep-eql": "^4.1.2", + "get-func-name": "^2.0.0", + "loupe": "^2.3.1", + "pathval": "^1.1.1", + "type-detect": "^4.0.5" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/chai-as-promised": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/chai-as-promised/-/chai-as-promised-7.1.1.tgz", + "integrity": "sha512-azL6xMoi+uxu6z4rhWQ1jbdUhOMhis2PvscD/xjLqNMkv3BPPp2JyyuTHOrf9BOosGpNQ11v6BKv/g57RXbiaA==", + "dev": true, + "dependencies": { + "check-error": "^1.0.2" + }, + "peerDependencies": { + "chai": ">= 2.1.2 < 5" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/check-error": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz", + "integrity": "sha512-BrgHpW9NURQgzoNyjfq0Wu6VFO6D7IZEmJNdtgNqpzGG8RuNFHt2jQxWlAs4HMe119chBnv+34syEZtc6IhLtA==", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/chokidar": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", + "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + ], + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/chokidar/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/chrome-trace-event": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz", + "integrity": "sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg==", + "dev": true, + "peer": true, + "engines": { + "node": ">=6.0" + } + }, + "node_modules/ci-info": { + "version": "3.8.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.8.0.tgz", + "integrity": "sha512-eXTggHWSooYhq49F2opQhuHWgzucfF2YgODK4e1566GQs5BIfP30B0oenwBJHfWxAs2fyPB1s7Mg949zLf61Yw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "engines": { + "node": ">=8" + } + }, + "node_modules/cipher-base": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.4.tgz", + "integrity": "sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q==", + "dev": true, + "dependencies": { + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/classic-level": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/classic-level/-/classic-level-1.3.0.tgz", + "integrity": "sha512-iwFAJQYtqRTRM0F6L8h4JCt00ZSGdOyqh7yVrhhjrOpFhmBjNlRUey64MCiyo6UmQHMJ+No3c81nujPv+n9yrg==", + "hasInstallScript": true, + "dependencies": { + "abstract-level": "^1.0.2", + "catering": "^2.1.0", + "module-error": "^1.0.1", + "napi-macros": "^2.2.2", + "node-gyp-build": "^4.3.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "dev": true, + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true, + "peer": true + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true + }, + "node_modules/connect": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/connect/-/connect-3.7.0.tgz", + "integrity": "sha512-ZqRXc+tZukToSNmh5C2iWMSoV3X1YUcPbqEM4DkEG5tNQXrQUZCNVGGv3IuicnkMtPfGf3Xtp8WCXs295iQ1pQ==", + "dev": true, + "dependencies": { + "debug": "2.6.9", + "finalhandler": "1.1.2", + "parseurl": "~1.3.3", + "utils-merge": "1.0.1" + }, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/connect/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/connect/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + }, + "node_modules/console-browserify": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/console-browserify/-/console-browserify-1.2.0.tgz", + "integrity": "sha512-ZMkYO/LkF17QvCPqM0gxw8yUzigAOZOSWSHg91FH6orS7vcEj5dVZTidN2fQ14yBSdg97RqhSNwLUXInd52OTA==", + "dev": true + }, + "node_modules/constants-browserify": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/constants-browserify/-/constants-browserify-1.0.0.tgz", + "integrity": "sha512-xFxOwqIzR/e1k1gLiWEophSCMqXcwVHIH7akf7b/vxcUeGunlj3hvZaaqxwHsTgn+IndtkQJgSztIDWeumWJDQ==", + "dev": true + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/convert-source-map": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", + "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", + "dev": true + }, + "node_modules/cookie": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz", + "integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cors": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", + "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "dev": true, + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/create-ecdh": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/create-ecdh/-/create-ecdh-4.0.4.tgz", + "integrity": "sha512-mf+TCx8wWc9VpuxfP2ht0iSISLZnt0JgWlrOKZiNqyUZWnjIaCIVNQArMHnCZKfEYRg6IM7A+NeJoN8gf/Ws0A==", + "dev": true, + "dependencies": { + "bn.js": "^4.1.0", + "elliptic": "^6.5.3" + } + }, + "node_modules/create-ecdh/node_modules/bn.js": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", + "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", + "dev": true + }, + "node_modules/create-hash": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz", + "integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==", + "dev": true, + "dependencies": { + "cipher-base": "^1.0.1", + "inherits": "^2.0.1", + "md5.js": "^1.3.4", + "ripemd160": "^2.0.1", + "sha.js": "^2.4.0" + } + }, + "node_modules/create-hmac": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz", + "integrity": "sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==", + "dev": true, + "dependencies": { + "cipher-base": "^1.0.3", + "create-hash": "^1.1.0", + "inherits": "^2.0.1", + "ripemd160": "^2.0.0", + "safe-buffer": "^5.0.1", + "sha.js": "^2.4.8" + } + }, + "node_modules/create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", + "dev": true + }, + "node_modules/cross-fetch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.5.tgz", + "integrity": "sha512-lvb1SBsI0Z7GDwmuid+mU3kWVBwTVUbe7S0H52yaaAdQOXq2YktTCZdlAcNKFzE6QtRz0snpw9bNiPeOIkkQvw==", + "dependencies": { + "node-fetch": "2.6.7" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/crypto-browserify": { + "version": "3.12.0", + "resolved": "https://registry.npmjs.org/crypto-browserify/-/crypto-browserify-3.12.0.tgz", + "integrity": "sha512-fz4spIh+znjO2VjL+IdhEpRJ3YN6sMzITSBijk6FK2UvTqruSQW+/cCZTSNsMiZNvUeq0CqurF+dAbyiGOY6Wg==", + "dev": true, + "dependencies": { + "browserify-cipher": "^1.0.0", + "browserify-sign": "^4.0.0", + "create-ecdh": "^4.0.0", + "create-hash": "^1.1.0", + "create-hmac": "^1.1.0", + "diffie-hellman": "^5.0.0", + "inherits": "^2.0.1", + "pbkdf2": "^3.0.3", + "public-encrypt": "^4.0.0", + "randombytes": "^2.0.0", + "randomfill": "^1.0.3" + }, + "engines": { + "node": "*" + } + }, + "node_modules/custom-event": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/custom-event/-/custom-event-1.0.1.tgz", + "integrity": "sha512-GAj5FOq0Hd+RsCGVJxZuKaIDXDf3h6GQoNEjFgbLLI/trgtavwUbSnZ5pVfg27DVCaWjIohryS0JFwIJyT2cMg==", + "dev": true + }, + "node_modules/date-format": { + "version": "4.0.14", + "resolved": "https://registry.npmjs.org/date-format/-/date-format-4.0.14.tgz", + "integrity": "sha512-39BOQLs9ZjKh0/patS9nrT8wc3ioX3/eA/zgbKNopnF2wCqJEoxywwwElATYvRsXdnOxA/OQeQoFZ3rFjVajhg==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/debug/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "node_modules/decamelize": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz", + "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/deep-eql": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-4.1.3.tgz", + "integrity": "sha512-WaEtAOpRA1MQ0eohqZjpGD8zdI0Ovsm8mmFhaDN8dvDZzyoUMcYDnf5Y6iu7HTXxf8JDS23qWa4a+hKCDyOPzw==", + "dev": true, + "dependencies": { + "type-detect": "^4.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true + }, + "node_modules/define-properties": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.0.tgz", + "integrity": "sha512-xvqAVKGfT1+UAvPwKTVw/njhdQ8ZhXK4lI0bCIuCMrp2up9nPnaDftrLtmpTazqd1o+UY4zgzU+avtMbDP+ldA==", + "dev": true, + "dependencies": { + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/des.js": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/des.js/-/des.js-1.1.0.tgz", + "integrity": "sha512-r17GxjhUCjSRy8aiJpr8/UadFIzMzJGexI3Nmz4ADi9LYSFx4gTBp80+NaX/YsXWWLhpZ7v/v/ubEc/bCNfKwg==", + "dev": true, + "dependencies": { + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0" + } + }, + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "dev": true, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/di": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/di/-/di-0.0.1.tgz", + "integrity": "sha512-uJaamHkagcZtHPqCIHZxnFrXlunQXgBOsZSUOWwFw31QJCAbyTBoHMW75YOTur5ZNx8pIeAKgf6GWIgaqqiLhA==", + "dev": true + }, + "node_modules/did-resolver": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/did-resolver/-/did-resolver-4.1.0.tgz", + "integrity": "sha512-S6fWHvCXkZg2IhS4RcVHxwuyVejPR7c+a4Go0xbQ9ps5kILa8viiYQgrM4gfTyeTjJ0ekgJH9gk/BawTpmkbZA==" + }, + "node_modules/diff": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz", + "integrity": "sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==", + "dev": true, + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/diffie-hellman": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz", + "integrity": "sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg==", + "dev": true, + "dependencies": { + "bn.js": "^4.1.0", + "miller-rabin": "^4.0.0", + "randombytes": "^2.0.0" + } + }, + "node_modules/diffie-hellman/node_modules/bn.js": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", + "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", + "dev": true + }, + "node_modules/dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, + "dependencies": { + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/dom-serialize": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/dom-serialize/-/dom-serialize-2.2.1.tgz", + "integrity": "sha512-Yra4DbvoW7/Z6LBN560ZwXMjoNOSAN2wRsKFGc4iBeso+mpIA6qj1vfdf9HpMaKAqG6wXTy+1SYEzmNpKXOSsQ==", + "dev": true, + "dependencies": { + "custom-event": "~1.0.0", + "ent": "~2.2.0", + "extend": "^3.0.0", + "void-elements": "^2.0.0" + } + }, + "node_modules/domain-browser": { + "version": "4.22.0", + "resolved": "https://registry.npmjs.org/domain-browser/-/domain-browser-4.22.0.tgz", + "integrity": "sha512-IGBwjF7tNk3cwypFNH/7bfzBcgSCbaMOD3GsaY1AU/JRrnHnYgEM0+9kQt52iZxjNsjBtJYtao146V+f8jFZNw==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://bevry.me/fund" + } + }, + "node_modules/eciesjs": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/eciesjs/-/eciesjs-0.4.0.tgz", + "integrity": "sha512-z4dEeaH16xxYVgtxJ8YVwpifH4Keg4gyp5F451mnDNwbAN3MgL5jcoEQGpqJrapv/zW8KwDnXG21Dw5B0hqvmw==", + "dependencies": { + "@noble/curves": "^1.1.0" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "dev": true + }, + "node_modules/electron-to-chromium": { + "version": "1.4.492", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.492.tgz", + "integrity": "sha512-36K9b/6skMVwAIEsC7GiQ8I8N3soCALVSHqWHzNDtGemAcI9Xu8hP02cywWM0A794rTHm0b0zHPeLJHtgFVamQ==", + "dev": true, + "peer": true + }, + "node_modules/elliptic": { + "version": "6.5.4", + "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.4.tgz", + "integrity": "sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ==", + "dev": true, + "dependencies": { + "bn.js": "^4.11.9", + "brorand": "^1.1.0", + "hash.js": "^1.0.0", + "hmac-drbg": "^1.0.1", + "inherits": "^2.0.4", + "minimalistic-assert": "^1.0.1", + "minimalistic-crypto-utils": "^1.0.1" + } + }, + "node_modules/elliptic/node_modules/bn.js": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", + "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", + "dev": true + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/engine.io": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.5.2.tgz", + "integrity": "sha512-IXsMcGpw/xRfjra46sVZVHiSWo/nJ/3g1337q9KNXtS6YRzbW5yIzTCb9DjhrBe7r3GZQR0I4+nq+4ODk5g/cA==", + "dev": true, + "dependencies": { + "@types/cookie": "^0.4.1", + "@types/cors": "^2.8.12", + "@types/node": ">=10.0.0", + "accepts": "~1.3.4", + "base64id": "2.0.0", + "cookie": "~0.4.1", + "cors": "~2.8.5", + "debug": "~4.3.1", + "engine.io-parser": "~5.2.1", + "ws": "~8.11.0" + }, + "engines": { + "node": ">=10.2.0" + } + }, + "node_modules/engine.io-parser": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.1.tgz", + "integrity": "sha512-9JktcM3u18nU9N2Lz3bWeBgxVgOKpw7yhRaoxQA3FUDZzzw+9WlA6p4G4u0RixNkg14fH7EfEc/RhpurtiROTQ==", + "dev": true, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/enhanced-resolve": { + "version": "5.15.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.15.0.tgz", + "integrity": "sha512-LXYT42KJ7lpIKECr2mAXIaMldcNCh/7E0KBKOu4KSfkHmP+mZmSs+8V5gBAqisWBy0OO4W5Oyys0GO1Y8KtdKg==", + "dev": true, + "peer": true, + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.2.0" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/ent": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/ent/-/ent-2.2.0.tgz", + "integrity": "sha512-GHrMyVZQWvTIdDtpiEXdHZnFQKzeO09apj8Cbl4pKWy4i0Oprcq17usfDt5aO63swf0JOeMWjWQE/LzgSRuWpA==", + "dev": true + }, + "node_modules/err-code": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/err-code/-/err-code-3.0.1.tgz", + "integrity": "sha512-GiaH0KJUewYok+eeY05IIgjtAe4Yltygk9Wqp1V5yVWLdhf0hYZchRjNIT9bb0mSwRcIusT3cx7PJUf3zEIfUA==" + }, + "node_modules/es-module-lexer": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.3.0.tgz", + "integrity": "sha512-vZK7T0N2CBmBOixhmjdqx2gWVbFZ4DXZ/NyRMZVlJXPa7CyFS+/a4QQsDGDQy9ZfEzxFuNEsMLeQJnKP2p5/JA==", + "dev": true, + "peer": true + }, + "node_modules/es6-object-assign": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/es6-object-assign/-/es6-object-assign-1.1.0.tgz", + "integrity": "sha512-MEl9uirslVwqQU369iHNWZXsI8yaZYGg/D65aOgZkeyFJwHYSxilf7rQzXKI7DdDuBPrBXbfk3sl9hJhmd5AUw==", + "dev": true + }, + "node_modules/esbuild": { + "version": "0.16.17", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.16.17.tgz", + "integrity": "sha512-G8LEkV0XzDMNwXKgM0Jwu3nY3lSTwSGY6XbxM9cr9+s0T/qSV1q1JVPBGzm3dcjhCic9+emZDmMffkwgPeOeLg==", + "dev": true, + "hasInstallScript": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/android-arm": "0.16.17", + "@esbuild/android-arm64": "0.16.17", + "@esbuild/android-x64": "0.16.17", + "@esbuild/darwin-arm64": "0.16.17", + "@esbuild/darwin-x64": "0.16.17", + "@esbuild/freebsd-arm64": "0.16.17", + "@esbuild/freebsd-x64": "0.16.17", + "@esbuild/linux-arm": "0.16.17", + "@esbuild/linux-arm64": "0.16.17", + "@esbuild/linux-ia32": "0.16.17", + "@esbuild/linux-loong64": "0.16.17", + "@esbuild/linux-mips64el": "0.16.17", + "@esbuild/linux-ppc64": "0.16.17", + "@esbuild/linux-riscv64": "0.16.17", + "@esbuild/linux-s390x": "0.16.17", + "@esbuild/linux-x64": "0.16.17", + "@esbuild/netbsd-x64": "0.16.17", + "@esbuild/openbsd-x64": "0.16.17", + "@esbuild/sunos-x64": "0.16.17", + "@esbuild/win32-arm64": "0.16.17", + "@esbuild/win32-ia32": "0.16.17", + "@esbuild/win32-x64": "0.16.17" + } + }, + "node_modules/escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "dev": true + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "8.39.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.39.0.tgz", + "integrity": "sha512-mwiok6cy7KTW7rBpo05k6+p4YVZByLNjAZ/ACB9DRCu4YDRwjXI01tWHp6KAUWelsBetTxKK/2sHB0vdS8Z2Og==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.4.0", + "@eslint/eslintrc": "^2.0.2", + "@eslint/js": "8.39.0", + "@humanwhocodes/config-array": "^0.11.8", + "@humanwhocodes/module-importer": "^1.0.1", + "@nodelib/fs.walk": "^1.2.8", + "ajv": "^6.10.0", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.3.2", + "doctrine": "^3.0.0", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^7.2.0", + "eslint-visitor-keys": "^3.4.0", + "espree": "^9.5.1", + "esquery": "^1.4.2", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "globals": "^13.19.0", + "grapheme-splitter": "^1.0.4", + "ignore": "^5.2.0", + "import-fresh": "^3.0.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "is-path-inside": "^3.0.3", + "js-sdsl": "^4.1.4", + "js-yaml": "^4.1.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.1", + "strip-ansi": "^6.0.1", + "strip-json-comments": "^3.1.0", + "text-table": "^0.2.0" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-plugin-mocha": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-mocha/-/eslint-plugin-mocha-10.1.0.tgz", + "integrity": "sha512-xLqqWUF17llsogVOC+8C6/jvQ+4IoOREbN7ZCHuOHuD6cT5cDD4h7f2LgsZuzMAiwswWE21tO7ExaknHVDrSkw==", + "dev": true, + "dependencies": { + "eslint-utils": "^3.0.0", + "rambda": "^7.1.0" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "eslint": ">=7.0.0" + } + }, + "node_modules/eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/eslint-utils": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz", + "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==", + "dev": true, + "dependencies": { + "eslint-visitor-keys": "^2.0.0" + }, + "engines": { + "node": "^10.0.0 || ^12.0.0 || >= 14.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + }, + "peerDependencies": { + "eslint": ">=5" + } + }, + "node_modules/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", + "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/eslint/node_modules/eslint-scope": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", + "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", + "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/eslint/node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "node_modules/espree": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", + "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", + "dev": true, + "dependencies": { + "acorn": "^8.9.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.4.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz", + "integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==", + "dev": true, + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esquery/node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esrecurse/node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/event-target-shim": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", + "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", + "engines": { + "node": ">=6" + } + }, + "node_modules/eventemitter3": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", + "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==" + }, + "node_modules/events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "engines": { + "node": ">=0.8.x" + } + }, + "node_modules/evp_bytestokey": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz", + "integrity": "sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA==", + "dev": true, + "dependencies": { + "md5.js": "^1.3.4", + "safe-buffer": "^5.1.1" + } + }, + "node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "dev": true + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" + }, + "node_modules/fast-glob": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.1.tgz", + "integrity": "sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true + }, + "node_modules/fastq": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz", + "integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==", + "dev": true, + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "dev": true, + "dependencies": { + "flat-cache": "^3.0.4" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/finalhandler": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", + "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", + "dev": true, + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "~2.3.0", + "parseurl": "~1.3.3", + "statuses": "~1.5.0", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/finalhandler/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/finalhandler/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + }, + "node_modules/finalhandler/node_modules/on-finished": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==", + "dev": true, + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", + "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", + "bin": { + "flat": "cli.js" + } + }, + "node_modules/flat-cache": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", + "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", + "dev": true, + "dependencies": { + "flatted": "^3.1.0", + "rimraf": "^3.0.2" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/flat-cache/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/flatted": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.7.tgz", + "integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==", + "dev": true + }, + "node_modules/follow-redirects": { + "version": "1.15.2", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz", + "integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/for-each": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", + "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", + "dev": true, + "dependencies": { + "is-callable": "^1.1.3" + } + }, + "node_modules/foreground-child": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-2.0.0.tgz", + "integrity": "sha512-dCIq9FpEcyQyXKCkyzmlPTFNgrCzPudOe+mhvJU5zAtlBnGVy2yKxtfsxK2tQBThwq225jcvBjpw1Gr40uzZCA==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.0", + "signal-exit": "^3.0.2" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/fs-extra": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", + "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + }, + "engines": { + "node": ">=6 <7 || >=8" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true + }, + "node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "dev": true + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-func-name": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz", + "integrity": "sha512-Hm0ixYtaSZ/V7C8FJrtZIuBBI+iSgL+1Aq82zSu8VQNB4S3Gk8e7Qs3VwBDJAhmRZcFqkl3tQu36g/Foh5I5ig==", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/get-intrinsic": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.1.tgz", + "integrity": "sha512-2DcsyfABl+gVHEfCOaTrWgyt+tb6MSEGmKq+kI5HwLbIYgjgmMcV8KQ41uaKz1xxUcn9tJtgFbQUEVcEbd0FYw==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/glob-to-regexp": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", + "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", + "dev": true, + "peer": true + }, + "node_modules/globals": { + "version": "13.21.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.21.0.tgz", + "integrity": "sha512-ybyme3s4yy/t/3s35bewwXKOf7cvzfreG2lH0lZl0JB7I4GxRP2ghxOK/Nb9EkRXdbBXZLfq/p/0W2JUONB/Gg==", + "dev": true, + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "dev": true, + "dependencies": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/gopd": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", + "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.1.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true + }, + "node_modules/grapheme-splitter": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz", + "integrity": "sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==", + "dev": true + }, + "node_modules/hamt-sharding": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/hamt-sharding/-/hamt-sharding-3.0.2.tgz", + "integrity": "sha512-f0DzBD2tSmLFdFsLAvOflIBqFPjerbA7BfmwO8mVho/5hXwgyyYhv+ijIzidQf/DpDX3bRjAQvhGoBFj+DBvPw==", + "dependencies": { + "sparse-array": "^1.3.1", + "uint8arrays": "^4.0.2" + }, + "engines": { + "node": ">=16.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.1" + }, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz", + "integrity": "sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.1.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/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==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz", + "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==", + "dev": true, + "dependencies": { + "has-symbols": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hash-base": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.1.0.tgz", + "integrity": "sha512-1nmYp/rhMDiE7AYkDw+lLwlAzz0AntGIe51F3RfFfEqyQ3feY2eI/NcwC6umIQVOASPMsWJLJScWKSSvzL9IVA==", + "dev": true, + "dependencies": { + "inherits": "^2.0.4", + "readable-stream": "^3.6.0", + "safe-buffer": "^5.2.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/hash-base/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dev": true, + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/hash-base/node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/hash-wasm": { + "version": "4.9.0", + "resolved": "https://registry.npmjs.org/hash-wasm/-/hash-wasm-4.9.0.tgz", + "integrity": "sha512-7SW7ejyfnRxuOc7ptQHSf4LDoZaWOivfzqw+5rpcQku0nHfmicPKE51ra9BiRLAmT8+gGLestr1XroUkqdjL6w==" + }, + "node_modules/hash.js": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz", + "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==", + "dev": true, + "dependencies": { + "inherits": "^2.0.3", + "minimalistic-assert": "^1.0.1" + } + }, + "node_modules/he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "dev": true, + "bin": { + "he": "bin/he" + } + }, + "node_modules/hmac-drbg": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", + "integrity": "sha512-Tti3gMqLdZfhOQY1Mzf/AanLiqh1WTiJgEj26ZuYQ9fbkLomzGchCws4FyrSd4VkpBfiNhaE1On+lOz894jvXg==", + "dev": true, + "dependencies": { + "hash.js": "^1.0.3", + "minimalistic-assert": "^1.0.0", + "minimalistic-crypto-utils": "^1.0.1" + } + }, + "node_modules/html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true + }, + "node_modules/http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "dev": true, + "dependencies": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/http-errors/node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/http-proxy": { + "version": "1.18.1", + "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz", + "integrity": "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==", + "dev": true, + "dependencies": { + "eventemitter3": "^4.0.0", + "follow-redirects": "^1.0.0", + "requires-port": "^1.0.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/https-browserify": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/https-browserify/-/https-browserify-1.0.0.tgz", + "integrity": "sha512-J+FkSdyD+0mA0N+81tMotaRMfSL9SGi+xpD3T6YApKsc3bGSXJlfXri3VyFOeYkfLRQisDk1W+jIFFKBeUBbBg==", + "dev": true + }, + "node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dev": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/ignore": { + "version": "5.2.4", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", + "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "dev": true, + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "dev": true, + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "node_modules/interface-blockstore": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/interface-blockstore/-/interface-blockstore-5.2.3.tgz", + "integrity": "sha512-15cN+ZFdcVXdXo6I/SrSzFDsuJyDTyEI52XuvXQlR/G5fe3cK8p0tvVjfu5diRQH1XqNgmJEdMPixyt0xgjtvQ==", + "dependencies": { + "interface-store": "^5.0.0", + "multiformats": "^11.0.2" + }, + "engines": { + "node": ">=16.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/interface-store": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/interface-store/-/interface-store-5.1.2.tgz", + "integrity": "sha512-q2sLoqC+UdaWnjwGyghsH0jwqqVk226lsG207e3QwPB8sAZYmYIWUnJwJH3JjFNNRV9e6CUTmm+gDO0Xg4KRiw==", + "engines": { + "node": ">=16.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/ipfs-unixfs": { + "version": "11.0.1", + "resolved": "https://registry.npmjs.org/ipfs-unixfs/-/ipfs-unixfs-11.0.1.tgz", + "integrity": "sha512-SD9dqn14bfgMfkPstsR/2Av3zCzYMj2ntQJab4HZucgX4nNV6K7guZh4Hf3kiL8ONff1Ogft1ekFU083DIKEdQ==", + "dependencies": { + "err-code": "^3.0.1", + "protons-runtime": "^5.0.0", + "uint8arraylist": "^2.4.3" + }, + "engines": { + "node": ">=16.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/ipfs-unixfs-exporter": { + "version": "13.1.5", + "resolved": "https://registry.npmjs.org/ipfs-unixfs-exporter/-/ipfs-unixfs-exporter-13.1.5.tgz", + "integrity": "sha512-O5aMawsHoe4DaYk5FFil2EPrNOaU3pkHC6qUR5JMnW7es93W3b/RjJoO7AyDL1rpb+M3K0oRu86Yc5wLNQQ8jg==", + "dependencies": { + "@ipld/dag-cbor": "^9.0.0", + "@ipld/dag-pb": "^4.0.0", + "@multiformats/murmur3": "^2.0.0", + "err-code": "^3.0.1", + "hamt-sharding": "^3.0.0", + "interface-blockstore": "^5.0.0", + "ipfs-unixfs": "^11.0.0", + "it-filter": "^3.0.2", + "it-last": "^3.0.2", + "it-map": "^3.0.3", + "it-parallel": "^3.0.0", + "it-pipe": "^3.0.1", + "it-pushable": "^3.1.0", + "multiformats": "^11.0.0", + "p-queue": "^7.3.0", + "progress-events": "^1.0.0", + "uint8arrays": "^4.0.2" + }, + "engines": { + "node": ">=16.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/ipfs-unixfs-importer": { + "version": "15.1.5", + "resolved": "https://registry.npmjs.org/ipfs-unixfs-importer/-/ipfs-unixfs-importer-15.1.5.tgz", + "integrity": "sha512-TXaOI0M5KNpq2+qLw8AIYd0Lnc0gWTKCBqUd9eErBUwaP3Fna4qauF+JX9Rj2UrwaOvG/1xbF8Vm+92eOcKWMA==", + "dependencies": { + "@ipld/dag-pb": "^4.0.0", + "@multiformats/murmur3": "^2.0.0", + "err-code": "^3.0.1", + "hamt-sharding": "^3.0.0", + "interface-blockstore": "^5.0.0", + "interface-store": "^5.0.1", + "ipfs-unixfs": "^11.0.0", + "it-all": "^3.0.2", + "it-batch": "^3.0.2", + "it-first": "^3.0.2", + "it-parallel-batch": "^3.0.1", + "multiformats": "^11.0.0", + "progress-events": "^1.0.0", + "rabin-wasm": "^0.1.4", + "uint8arraylist": "^2.4.3", + "uint8arrays": "^4.0.2" + }, + "engines": { + "node": ">=16.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/is-arguments": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz", + "integrity": "sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-buffer": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.5.tgz", + "integrity": "sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "engines": { + "node": ">=4" + } + }, + "node_modules/is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-ci": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-3.0.1.tgz", + "integrity": "sha512-ZYvCgrefwqoQ6yTyYUbQu64HsITZ3NfKX1lzaEYdkTDcfKzzCI/wthRRYKkdjHKFVgNiXKAKm65Zo1pk2as/QQ==", + "dev": true, + "dependencies": { + "ci-info": "^3.2.0" + }, + "bin": { + "is-ci": "bin.js" + } + }, + "node_modules/is-core-module": { + "version": "2.13.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.0.tgz", + "integrity": "sha512-Z7dk6Qo8pOCp3l4tsX2C5ZVas4V+UxwQodwZhLopL91TX8UyyHEXafPcyoeeWuLrwzHcr3igO78wNLwHJHsMCQ==", + "dev": true, + "dependencies": { + "has": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-docker": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", + "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", + "dev": true, + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-generator-function": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.0.10.tgz", + "integrity": "sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A==", + "dev": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-nan": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/is-nan/-/is-nan-1.3.2.tgz", + "integrity": "sha512-E+zBKpQ2t6MEo1VsonYmluk9NxGrbzpeeLC2xIViuO2EjU2xsXsBPwTr3Ykv9l08UYEVEdWeRZNouaZqF6RN0w==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.0", + "define-properties": "^1.1.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-plain-obj": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", + "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-typed-array": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.12.tgz", + "integrity": "sha512-Z14TF2JNG8Lss5/HMqt0//T9JeHXttXy5pH/DBU4vi98ozO2btxzq9MwYDZYnKwU8nRsz/+GVFVRDq3DkVuSPg==", + "dev": true, + "dependencies": { + "which-typed-array": "^1.1.11" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-unicode-supported": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", + "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-wsl": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", + "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", + "dev": true, + "dependencies": { + "is-docker": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==", + "dev": true + }, + "node_modules/isbinaryfile": { + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/isbinaryfile/-/isbinaryfile-4.0.10.tgz", + "integrity": "sha512-iHrqe5shvBUcFbmZq9zOQHBoeOhZJu6RQGrDpBgenUm/Am+F3JM2MgQj+rK3Z601fzrL5gLZWtAPH2OBaSVcyw==", + "dev": true, + "engines": { + "node": ">= 8.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/gjtorikian/" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true + }, + "node_modules/isomorphic-timers-promises": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/isomorphic-timers-promises/-/isomorphic-timers-promises-1.0.1.tgz", + "integrity": "sha512-u4sej9B1LPSxTGKB/HiuzvEQnXH0ECYkSVQU39koSwmFAxhlEAFl9RdTvLv4TOTQUgBS5O3O5fwUxk6byBZ+IQ==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-coverage": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz", + "integrity": "sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-report": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", + "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", + "dev": true, + "dependencies": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^4.0.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-reports": { + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.6.tgz", + "integrity": "sha512-TLgnMkKg3iTDsQ9PbPTdpfAK2DzjF9mqUG7RMgcQl8oFjad8ob4laGxv5XV5U9MAfx8D6tSJiUyuAwzLicaxlg==", + "dev": true, + "dependencies": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/it-all": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/it-all/-/it-all-3.0.3.tgz", + "integrity": "sha512-LwEVD1d0b1O5mDwumnZk+80jSBn5sXDxQ41xiD6j6l2lRiWH6lBLdxXx1C6mlKrXQwRHzUQagOZUmqttDUwb0A==" + }, + "node_modules/it-batch": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/it-batch/-/it-batch-3.0.3.tgz", + "integrity": "sha512-KdKVGOZgYhxiHTMphzPKaiNL99yyUgeDoqRmSedbKJr05nP5RxtiIPWX+i1dLCADRjExbGtKfFsrogNgH0h9SA==" + }, + "node_modules/it-filter": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/it-filter/-/it-filter-3.0.3.tgz", + "integrity": "sha512-2zXUrJuuV6QHM21ahc8NqVUUxkLMVDWXBoUBcj9GCQLQez2OXmddTHN0r0F5B+TkNTpeL618yIgXi1HNPJOxow==", + "dependencies": { + "it-peekable": "^3.0.0" + } + }, + "node_modules/it-first": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/it-first/-/it-first-3.0.3.tgz", + "integrity": "sha512-RC8tplctsDpoBUljwsp1viiyaR5fPvMe+FgbbcU0sFjKkJa7iwbB4CCPhHtVYSdjsrREfr0QEotfQrBoGyt7Dw==" + }, + "node_modules/it-last": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/it-last/-/it-last-3.0.3.tgz", + "integrity": "sha512-veF0zWTMEJ466Otxz7jPbExiJGfJYTOiHYGfg8iGwKPIV558zIGZQJU1WNyjHfugFzAyfuFlIZKsXCCtCl8LgQ==" + }, + "node_modules/it-map": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/it-map/-/it-map-3.0.4.tgz", + "integrity": "sha512-h5zCxovJQ+mzJT75xP4GkJuFrJQ5l7IIdhZ6AOWaE02g5F7T1k1j4CB/uKSRR05LLLOi1LqG+7CrH9bi8GIXYA==", + "dependencies": { + "it-peekable": "^3.0.0" + } + }, + "node_modules/it-merge": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/it-merge/-/it-merge-3.0.2.tgz", + "integrity": "sha512-bMk2km8lTz+Rwv30hzDUdGIcqQkOemFJqmGT2wqQZ6/zHKCsYqdRunPrteCqHLV/nIVhUK8nZZkDA2eJ4MJZiA==", + "dependencies": { + "it-pushable": "^3.2.0" + } + }, + "node_modules/it-parallel": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/it-parallel/-/it-parallel-3.0.4.tgz", + "integrity": "sha512-fuA+SysGxbZc+Yl7EUvzQqZ8bNYQghZ0Mq9zA+fxMQ5eQYzatNg6oJk1y1PvPvNqLgKJMzEInpRO6PbLC3hGAg==", + "dependencies": { + "p-defer": "^4.0.0" + } + }, + "node_modules/it-parallel-batch": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/it-parallel-batch/-/it-parallel-batch-3.0.2.tgz", + "integrity": "sha512-8ci62F5gn5aS8/wEC0kvRKWCmenHlIi+R8waEF/SP1ImQhhkve9l4/DXEmoL7UeI5FqJktzkMzZwqDng0ZppKw==", + "dependencies": { + "it-batch": "^3.0.0" + } + }, + "node_modules/it-peekable": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/it-peekable/-/it-peekable-3.0.2.tgz", + "integrity": "sha512-nWwUdhNQ1CfAuoJmsaUotNMYUrfNIlY9gBA1jwWfWSu1I0mLY2brwreKHGOUptXLJUiG5pR04He0xYZMWBRiGA==" + }, + "node_modules/it-pipe": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/it-pipe/-/it-pipe-3.0.1.tgz", + "integrity": "sha512-sIoNrQl1qSRg2seYSBH/3QxWhJFn9PKYvOf/bHdtCBF0bnghey44VyASsWzn5dAx0DCDDABq1hZIuzKmtBZmKA==", + "dependencies": { + "it-merge": "^3.0.0", + "it-pushable": "^3.1.2", + "it-stream-types": "^2.0.1" + }, + "engines": { + "node": ">=16.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/it-pushable": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/it-pushable/-/it-pushable-3.2.1.tgz", + "integrity": "sha512-sLFz2Q0oyDCJpTciZog7ipP4vSftfPy3e6JnH6YyztRa1XqkpGQaafK3Jw/JlfEBtCXfnX9uVfcpu3xpSAqCVQ==", + "dependencies": { + "p-defer": "^4.0.0" + }, + "engines": { + "node": ">=16.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/it-stream-types": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/it-stream-types/-/it-stream-types-2.0.1.tgz", + "integrity": "sha512-6DmOs5r7ERDbvS4q8yLKENcj6Yecr7QQTqWApbZdfAUTEC947d+PEha7PCqhm//9oxaLYL7TWRekwhoXl2s6fg==", + "engines": { + "node": ">=16.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/jest-worker": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", + "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", + "dev": true, + "peer": true, + "dependencies": { + "@types/node": "*", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/jest-worker/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "peer": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/js-sdsl": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.4.2.tgz", + "integrity": "sha512-dwXFwByc/ajSV6m5bcKAPwe4yDDF6D614pxmIi5odytzxRlwqF6nwoiCek80Ixc7Cvma5awClxrzFtxCQvcM8w==", + "dev": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/js-sdsl" + } + }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsbi": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/jsbi/-/jsbi-4.3.0.tgz", + "integrity": "sha512-SnZNcinB4RIcnEyZqFPdGPVgrg2AcnykiBy0sHVJQKHYeaLUvi3Exj+iaPpLnFVkDPZIV4U0yvgC9/R4uEAZ9g==" + }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true, + "peer": true + }, + "node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true + }, + "node_modules/jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", + "dev": true, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/just-extend": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-4.2.1.tgz", + "integrity": "sha512-g3UB796vUFIY90VIv/WX3L2c8CS2MdWUww3CNrYmqza1Fg0DURc2K/O4YrnklBdQarSJ/y8JnJYDGc+1iumQjg==", + "dev": true + }, + "node_modules/karma": { + "version": "6.4.1", + "resolved": "https://registry.npmjs.org/karma/-/karma-6.4.1.tgz", + "integrity": "sha512-Cj57NKOskK7wtFWSlMvZf459iX+kpYIPXmkNUzP2WAFcA7nhr/ALn5R7sw3w+1udFDcpMx/tuB8d5amgm3ijaA==", + "dev": true, + "dependencies": { + "@colors/colors": "1.5.0", + "body-parser": "^1.19.0", + "braces": "^3.0.2", + "chokidar": "^3.5.1", + "connect": "^3.7.0", + "di": "^0.0.1", + "dom-serialize": "^2.2.1", + "glob": "^7.1.7", + "graceful-fs": "^4.2.6", + "http-proxy": "^1.18.1", + "isbinaryfile": "^4.0.8", + "lodash": "^4.17.21", + "log4js": "^6.4.1", + "mime": "^2.5.2", + "minimatch": "^3.0.4", + "mkdirp": "^0.5.5", + "qjobs": "^1.2.0", + "range-parser": "^1.2.1", + "rimraf": "^3.0.2", + "socket.io": "^4.4.1", + "source-map": "^0.6.1", + "tmp": "^0.2.1", + "ua-parser-js": "^0.7.30", + "yargs": "^16.1.1" + }, + "bin": { + "karma": "bin/karma" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/karma-chai": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/karma-chai/-/karma-chai-0.1.0.tgz", + "integrity": "sha512-mqKCkHwzPMhgTYca10S90aCEX9+HjVjjrBFAsw36Zj7BlQNbokXXCAe6Ji04VUMsxcY5RLP7YphpfO06XOubdg==", + "dev": true, + "peerDependencies": { + "chai": "*", + "karma": ">=0.10.9" + } + }, + "node_modules/karma-chrome-launcher": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/karma-chrome-launcher/-/karma-chrome-launcher-3.1.1.tgz", + "integrity": "sha512-hsIglcq1vtboGPAN+DGCISCFOxW+ZVnIqhDQcCMqqCp+4dmJ0Qpq5QAjkbA0X2L9Mi6OBkHi2Srrbmm7pUKkzQ==", + "dev": true, + "dependencies": { + "which": "^1.2.1" + } + }, + "node_modules/karma-chrome-launcher/node_modules/which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "which": "bin/which" + } + }, + "node_modules/karma-esbuild": { + "version": "2.2.5", + "resolved": "https://registry.npmjs.org/karma-esbuild/-/karma-esbuild-2.2.5.tgz", + "integrity": "sha512-+NiRmZhUm/MqOsL1cAu8+RmiOMvIxWDaeYDLBB5upxHF9Hh3Og8YH43EAmDan40pxt2FKDcOjupgqIe4Tx2szQ==", + "dev": true, + "dependencies": { + "chokidar": "^3.5.1", + "source-map": "0.6.1" + }, + "peerDependencies": { + "esbuild": ">=0.8.45" + } + }, + "node_modules/karma-firefox-launcher": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/karma-firefox-launcher/-/karma-firefox-launcher-2.1.2.tgz", + "integrity": "sha512-VV9xDQU1QIboTrjtGVD4NCfzIH7n01ZXqy/qpBhnOeGVOkG5JYPEm8kuSd7psHE6WouZaQ9Ool92g8LFweSNMA==", + "dev": true, + "dependencies": { + "is-wsl": "^2.2.0", + "which": "^2.0.1" + } + }, + "node_modules/karma-mocha": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/karma-mocha/-/karma-mocha-2.0.1.tgz", + "integrity": "sha512-Tzd5HBjm8his2OA4bouAsATYEpZrp9vC7z5E5j4C5Of5Rrs1jY67RAwXNcVmd/Bnk1wgvQRou0zGVLey44G4tQ==", + "dev": true, + "dependencies": { + "minimist": "^1.2.3" + } + }, + "node_modules/karma-mocha-reporter": { + "version": "2.2.5", + "resolved": "https://registry.npmjs.org/karma-mocha-reporter/-/karma-mocha-reporter-2.2.5.tgz", + "integrity": "sha512-Hr6nhkIp0GIJJrvzY8JFeHpQZNseuIakGac4bpw8K1+5F0tLb6l7uvXRa8mt2Z+NVwYgCct4QAfp2R2QP6o00w==", + "dev": true, + "dependencies": { + "chalk": "^2.1.0", + "log-symbols": "^2.1.0", + "strip-ansi": "^4.0.0" + }, + "peerDependencies": { + "karma": ">=0.13" + } + }, + "node_modules/karma-mocha-reporter/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/karma-mocha-reporter/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/karma-mocha-reporter/node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/karma-mocha-reporter/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/karma-mocha-reporter/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true + }, + "node_modules/karma-mocha-reporter/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/karma-mocha-reporter/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/karma-mocha-reporter/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/karma-mocha-reporter/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/karma-webkit-launcher": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/karma-webkit-launcher/-/karma-webkit-launcher-2.1.0.tgz", + "integrity": "sha512-S5eqhH0DIcuJFi27nC6eBxZ3MTrPnYybPthDU2Q8dfG0yFrXx8FqNDKSbRZsFFvAKJ55QVtYH1bbArd3ddI5Sg==", + "dev": true, + "dependencies": { + "is-ci": "^3.0.1", + "uuid": "^9.0.0" + }, + "peerDependenciesMeta": { + "playwright": { + "optional": true + } + } + }, + "node_modules/karma-webkit-launcher/node_modules/uuid": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.0.tgz", + "integrity": "sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==", + "dev": true, + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/karma/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/level": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/level/-/level-8.0.0.tgz", + "integrity": "sha512-ypf0jjAk2BWI33yzEaaotpq7fkOPALKAgDBxggO6Q9HGX2MRXn0wbP1Jn/tJv1gtL867+YOjOB49WaUF3UoJNQ==", + "dependencies": { + "browser-level": "^1.0.1", + "classic-level": "^1.2.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/level" + } + }, + "node_modules/level-supports": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/level-supports/-/level-supports-4.0.1.tgz", + "integrity": "sha512-PbXpve8rKeNcZ9C1mUicC9auIYFyGpkV9/i6g76tLgANwWhtG2v7I4xNBUlkn3lE2/dZF3Pi0ygYGtLc4RXXdA==", + "engines": { + "node": ">=12" + } + }, + "node_modules/level-transcoder": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/level-transcoder/-/level-transcoder-1.0.1.tgz", + "integrity": "sha512-t7bFwFtsQeD8cl8NIoQ2iwxA0CL/9IFw7/9gAjOonH0PWTTiRfY7Hq+Ejbsxh86tXobDQ6IOiddjNYIfOBs06w==", + "dependencies": { + "buffer": "^6.0.3", + "module-error": "^1.0.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/loader-runner": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.0.tgz", + "integrity": "sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==", + "dev": true, + "peer": true, + "engines": { + "node": ">=6.11.5" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" + }, + "node_modules/lodash.get": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", + "integrity": "sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==", + "dev": true + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true + }, + "node_modules/log-symbols": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-2.2.0.tgz", + "integrity": "sha512-VeIAFslyIerEJLXHziedo2basKbMKtTw3vfn5IzG0XTjhAVEJyNHnL2p7vc+wBDSdQuUpNw3M2u6xb9QsAY5Eg==", + "dev": true, + "dependencies": { + "chalk": "^2.0.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/log-symbols/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/log-symbols/node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/log-symbols/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/log-symbols/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true + }, + "node_modules/log-symbols/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/log-symbols/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/log-symbols/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/log4js": { + "version": "6.9.1", + "resolved": "https://registry.npmjs.org/log4js/-/log4js-6.9.1.tgz", + "integrity": "sha512-1somDdy9sChrr9/f4UlzhdaGfDR2c/SaD2a4T7qEkG4jTS57/B3qmnjLYePwQ8cqWnUHZI0iAKxMBpCZICiZ2g==", + "dev": true, + "dependencies": { + "date-format": "^4.0.14", + "debug": "^4.3.4", + "flatted": "^3.2.7", + "rfdc": "^1.3.0", + "streamroller": "^3.1.5" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/long": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/long/-/long-5.2.3.tgz", + "integrity": "sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q==" + }, + "node_modules/loupe": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.6.tgz", + "integrity": "sha512-RaPMZKiMy8/JruncMU5Bt6na1eftNoo++R4Y+N2FrxkDVTrGvcyzFTsaGif4QTeKESheMGegbhw6iUAq+5A8zA==", + "dev": true, + "dependencies": { + "get-func-name": "^2.0.0" + } + }, + "node_modules/lru-cache": { + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-9.1.2.tgz", + "integrity": "sha512-ERJq3FOzJTxBbFjZ7iDs+NiK4VI9Wz+RdrrAB8dio1oV+YvdPzUEE4QNiT2VD51DkIbCYRUUzCRkssXCHqSnKQ==", + "engines": { + "node": "14 || >=16.14" + } + }, + "node_modules/make-dir": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", + "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", + "dev": true, + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/md5.js": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz", + "integrity": "sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==", + "dev": true, + "dependencies": { + "hash-base": "^3.0.0", + "inherits": "^2.0.1", + "safe-buffer": "^5.1.2" + } + }, + "node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true, + "peer": true + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", + "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "dev": true, + "dependencies": { + "braces": "^3.0.2", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/miller-rabin": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/miller-rabin/-/miller-rabin-4.0.1.tgz", + "integrity": "sha512-115fLhvZVqWwHPbClyntxEVfVDfl9DLLTuJvq3g2O/Oxi8AiNouAHvDSzHS0viUJc+V5vm3eq91Xwqn9dp4jRA==", + "dev": true, + "dependencies": { + "bn.js": "^4.0.0", + "brorand": "^1.0.1" + }, + "bin": { + "miller-rabin": "bin/miller-rabin" + } + }, + "node_modules/miller-rabin/node_modules/bn.js": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", + "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", + "dev": true + }, + "node_modules/mime": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz", + "integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==", + "dev": true, + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dev": true, + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/minimalistic-assert": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", + "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==", + "dev": true + }, + "node_modules/minimalistic-crypto-utils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz", + "integrity": "sha512-JIYlbt6g8i5jKfJ3xz7rF0LXmv2TkDxBLUkiBeZ7bAx4GnnNMr8xFpGnOxn6GhTEHx3SjRrZEoU+j04prX1ktg==", + "dev": true + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/minipass": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-4.2.8.tgz", + "integrity": "sha512-fNzuVyifolSLFL4NzpF+wEF4qrgqaaKX0haXPQEdQ7NKAN+WecoKMHV09YcuL/DHxrUsYQOK3MiuDf7Ip2OXfQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/mkdirp": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "dev": true, + "dependencies": { + "minimist": "^1.2.6" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, + "node_modules/mocha": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.2.0.tgz", + "integrity": "sha512-IDY7fl/BecMwFHzoqF2sg/SHHANeBoMMXFlS9r0OXKDssYE1M5O43wUY/9BVPeIvfH2zmEbBfseqN9gBQZzXkg==", + "dev": true, + "dependencies": { + "ansi-colors": "4.1.1", + "browser-stdout": "1.3.1", + "chokidar": "3.5.3", + "debug": "4.3.4", + "diff": "5.0.0", + "escape-string-regexp": "4.0.0", + "find-up": "5.0.0", + "glob": "7.2.0", + "he": "1.2.0", + "js-yaml": "4.1.0", + "log-symbols": "4.1.0", + "minimatch": "5.0.1", + "ms": "2.1.3", + "nanoid": "3.3.3", + "serialize-javascript": "6.0.0", + "strip-json-comments": "3.1.1", + "supports-color": "8.1.1", + "workerpool": "6.2.1", + "yargs": "16.2.0", + "yargs-parser": "20.2.4", + "yargs-unparser": "2.0.0" + }, + "bin": { + "_mocha": "bin/_mocha", + "mocha": "bin/mocha.js" + }, + "engines": { + "node": ">= 14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mochajs" + } + }, + "node_modules/mocha/node_modules/glob": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", + "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", + "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" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/mocha/node_modules/glob/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/mocha/node_modules/log-symbols": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", + "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", + "dev": true, + "dependencies": { + "chalk": "^4.1.0", + "is-unicode-supported": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mocha/node_modules/minimatch": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.0.1.tgz", + "integrity": "sha512-nLDxIFRyhDblz3qMuq+SoRZED4+miJ/G+tdDrjkkkRnjAsBexeGpgjLEQ0blJy7rHhR2b93rhQY4SvyWu9v03g==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/mocha/node_modules/minimatch/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/mocha/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/mocha/node_modules/yargs-parser": { + "version": "20.2.4", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.4.tgz", + "integrity": "sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/module-error": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/module-error/-/module-error-1.0.2.tgz", + "integrity": "sha512-0yuvsqSCv8LbaOKhnsQ/T5JhyFlCYLPXK3U2sgV10zoKQwzs/MyfuQUOZQ1V/6OCOJsK/TRgNVrPuPDqtdMFtA==", + "engines": { + "node": ">=10" + } + }, + "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==" + }, + "node_modules/multibase": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/multibase/-/multibase-4.0.6.tgz", + "integrity": "sha512-x23pDe5+svdLz/k5JPGCVdfn7Q5mZVMBETiC+ORfO+sor9Sgs0smJzAjfTbM5tckeCqnaUuMYoz+k3RXMmJClQ==", + "deprecated": "This module has been superseded by the multiformats module", + "dependencies": { + "@multiformats/base-x": "^4.0.1" + }, + "engines": { + "node": ">=12.0.0", + "npm": ">=6.0.0" + } + }, + "node_modules/multiformats": { + "version": "11.0.2", + "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-11.0.2.tgz", + "integrity": "sha512-b5mYMkOkARIuVZCpvijFj9a6m5wMVLC7cf/jIPd5D/ARDOfLC5+IFkbgDXQgcU2goIsTD/O9NY4DI/Mt4OGvlg==", + "engines": { + "node": ">=16.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/multihashes": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/multihashes/-/multihashes-4.0.3.tgz", + "integrity": "sha512-0AhMH7Iu95XjDLxIeuCOOE4t9+vQZsACyKZ9Fxw2pcsRmlX4iCn1mby0hS0bb+nQOVpdQYWPpnyusw4da5RPhA==", + "dependencies": { + "multibase": "^4.0.1", + "uint8arrays": "^3.0.0", + "varint": "^5.0.2" + }, + "engines": { + "node": ">=12.0.0", + "npm": ">=6.0.0" + } + }, + "node_modules/multihashes/node_modules/multiformats": { + "version": "9.9.0", + "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-9.9.0.tgz", + "integrity": "sha512-HoMUjhH9T8DDBNT+6xzkrd9ga/XiBI4xLr58LJACwK6G3HTOPeMz4nB4KJs33L2BelrIJa7P0VuNaVF3hMYfjg==" + }, + "node_modules/multihashes/node_modules/uint8arrays": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-3.1.1.tgz", + "integrity": "sha512-+QJa8QRnbdXVpHYjLoTpJIdCTiw9Ir62nocClWuXIq2JIh4Uta0cQsTSpFL678p2CN8B+XSApwcU+pQEqVpKWg==", + "dependencies": { + "multiformats": "^9.4.2" + } + }, + "node_modules/multihashes/node_modules/varint": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/varint/-/varint-5.0.2.tgz", + "integrity": "sha512-lKxKYG6H03yCZUpAGOPOsMcGxd1RHCu1iKvEHYDPmTyq2HueGhD73ssNBqqQWfvYs04G9iUFRvmAVLW20Jw6ow==" + }, + "node_modules/murmurhash3js-revisited": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/murmurhash3js-revisited/-/murmurhash3js-revisited-3.0.0.tgz", + "integrity": "sha512-/sF3ee6zvScXMb1XFJ8gDsSnY+X8PbOyjIuBhtgis10W2Jx4ZjIhikUCIF9c4gpJxVnQIsPAFrSwTCuAjicP6g==", + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/nanoid": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.3.tgz", + "integrity": "sha512-p1sjXuopFs0xg+fPASzQ28agW1oHD7xDsd9Xkf3T15H3c/cifrFHVwrh74PdoklAPi+i7MdRsE47vm2r6JoB+w==", + "dev": true, + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/napi-macros": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/napi-macros/-/napi-macros-2.2.2.tgz", + "integrity": "sha512-hmEVtAGYzVQpCKdbQea4skABsdXW4RUh5t5mJ2zzqowJS2OyXZTU1KhDVFhx+NlWZ4ap9mqR9TcDO3LTTttd+g==" + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true + }, + "node_modules/natural-compare-lite": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare-lite/-/natural-compare-lite-1.4.0.tgz", + "integrity": "sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==", + "dev": true + }, + "node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/neo-async": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", + "dev": true, + "peer": true + }, + "node_modules/nise": { + "version": "5.1.4", + "resolved": "https://registry.npmjs.org/nise/-/nise-5.1.4.tgz", + "integrity": "sha512-8+Ib8rRJ4L0o3kfmyVCL7gzrohyDe0cMFTBa2d364yIrEGMEoetznKJx899YxjybU6bL9SQkYPSBBs1gyYs8Xg==", + "dev": true, + "dependencies": { + "@sinonjs/commons": "^2.0.0", + "@sinonjs/fake-timers": "^10.0.2", + "@sinonjs/text-encoding": "^0.7.1", + "just-extend": "^4.0.2", + "path-to-regexp": "^1.7.0" + } + }, + "node_modules/nise/node_modules/@sinonjs/commons": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-2.0.0.tgz", + "integrity": "sha512-uLa0j859mMrg2slwQYdO/AkrOfmH+X6LTVmNTS9CqexuE2IvVORIkSpJLqePAbEnKJ77aMmCwr1NUZ57120Xcg==", + "dev": true, + "dependencies": { + "type-detect": "4.0.8" + } + }, + "node_modules/node-fetch": { + "version": "2.6.7", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", + "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/node-gyp-build": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.6.0.tgz", + "integrity": "sha512-NTZVKn9IylLwUzaKjkas1e4u2DLNcV4rdYagA4PWdPwW87Bi7z+BznyKSRwS/761tV/lzCGXplWsiaMjLqP2zQ==", + "bin": { + "node-gyp-build": "bin.js", + "node-gyp-build-optional": "optional.js", + "node-gyp-build-test": "build-test.js" + } + }, + "node_modules/node-releases": { + "version": "2.0.13", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.13.tgz", + "integrity": "sha512-uYr7J37ae/ORWdZeQ1xxMJe3NtdmqMC/JZK+geofDrkLUApKRHPd18/TxtBOJ4A0/+uUIliorNrfYV6s1b02eQ==", + "dev": true, + "peer": true + }, + "node_modules/node-stdlib-browser": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/node-stdlib-browser/-/node-stdlib-browser-1.2.0.tgz", + "integrity": "sha512-VSjFxUhRhkyed8AtLwSCkMrJRfQ3e2lGtG3sP6FEgaLKBBbxM/dLfjRe1+iLhjvyLFW3tBQ8+c0pcOtXGbAZJg==", + "dev": true, + "dependencies": { + "assert": "^2.0.0", + "browser-resolve": "^2.0.0", + "browserify-zlib": "^0.2.0", + "buffer": "^5.7.1", + "console-browserify": "^1.1.0", + "constants-browserify": "^1.0.0", + "create-require": "^1.1.1", + "crypto-browserify": "^3.11.0", + "domain-browser": "^4.22.0", + "events": "^3.0.0", + "https-browserify": "^1.0.0", + "isomorphic-timers-promises": "^1.0.1", + "os-browserify": "^0.3.0", + "path-browserify": "^1.0.1", + "pkg-dir": "^5.0.0", + "process": "^0.11.10", + "punycode": "^1.4.1", + "querystring-es3": "^0.2.1", + "readable-stream": "^3.6.0", + "stream-browserify": "^3.0.0", + "stream-http": "^3.2.0", + "string_decoder": "^1.0.0", + "timers-browserify": "^2.0.4", + "tty-browserify": "0.0.1", + "url": "^0.11.0", + "util": "^0.12.4", + "vm-browserify": "^1.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/node-stdlib-browser/node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "node_modules/node-stdlib-browser/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dev": true, + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/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==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-is": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.5.tgz", + "integrity": "sha512-3cyDsyHgtmi7I7DfSSI2LDp6SK2lwvtbg0p0R1e0RvTqF5ceGx+K2dfSjm1bKDMVCFEDAQvy+o8c6a7VujOddw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "dev": true, + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/optionator": { + "version": "0.9.3", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz", + "integrity": "sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==", + "dev": true, + "dependencies": { + "@aashutoshrathi/word-wrap": "^1.2.3", + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/os-browserify": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/os-browserify/-/os-browserify-0.3.0.tgz", + "integrity": "sha512-gjcpUc3clBf9+210TRaDWbf+rZZZEshZ+DlXMRCeAjp0xhTrnQsKHypIy1J3d5hKdUzj69t708EHtU8P6bUn0A==", + "dev": true + }, + "node_modules/p-defer": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/p-defer/-/p-defer-4.0.0.tgz", + "integrity": "sha512-Vb3QRvQ0Y5XnF40ZUWW7JfLogicVh/EnA5gBIvKDJoYpeI82+1E3AlB9yOcKFS0AhHrWVnAQO39fbR0G99IVEQ==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-queue": { + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/p-queue/-/p-queue-7.3.4.tgz", + "integrity": "sha512-esox8CWt0j9EZECFvkFl2WNPat8LN4t7WWeXq73D9ha0V96qPRufApZi4ZhPwXAln1uVVal429HVVKPa2X0yQg==", + "dependencies": { + "eventemitter3": "^4.0.7", + "p-timeout": "^5.0.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-timeout": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-5.1.0.tgz", + "integrity": "sha512-auFDyzzzGZZZdHz3BtET9VEz0SE/uMEAx7uWfGPucfzEwwe/xH0iVeZibQmANYE/hp9T2+UUZT5m+BKyrDp3Ew==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pako": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", + "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", + "dev": true + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-asn1": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.6.tgz", + "integrity": "sha512-RnZRo1EPU6JBnra2vGHj0yhp6ebyjBZpmUCLHWiFhxlzvBCCpAuZ7elsBp1PVAbQN0/04VD/19rfzlBSwLstMw==", + "dev": true, + "dependencies": { + "asn1.js": "^5.2.0", + "browserify-aes": "^1.0.0", + "evp_bytestokey": "^1.0.0", + "pbkdf2": "^3.0.3", + "safe-buffer": "^5.1.1" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/path-browserify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-1.0.1.tgz", + "integrity": "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==", + "dev": true + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true + }, + "node_modules/path-scurry": { + "version": "1.10.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.10.1.tgz", + "integrity": "sha512-MkhCqzzBEpPvxxQ71Md0b1Kk51W01lrYvlMzSUaIzNsODdd7mqhiimSZlr+VegAz5Z6Vzt9Xg2ttE//XBhH3EQ==", + "dev": true, + "dependencies": { + "lru-cache": "^9.1.1 || ^10.0.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/path-scurry/node_modules/minipass": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.0.3.tgz", + "integrity": "sha512-LhbbwCfz3vsb12j/WkWQPZfKTsgqIe1Nf/ti1pKjYESGLHIVjWU96G9/ljLH4F9mWNVhlQOm0VySdAWzf05dpg==", + "dev": true, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/path-to-regexp": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.8.0.tgz", + "integrity": "sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA==", + "dev": true, + "dependencies": { + "isarray": "0.0.1" + } + }, + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/pathval": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz", + "integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/pbkdf2": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.1.2.tgz", + "integrity": "sha512-iuh7L6jA7JEGu2WxDwtQP1ddOpaJNC4KlDEFfdQajSGgGPNi4OyDc2R7QnbY2bR9QjBVGwgvTdNJZoE7RaxUMA==", + "dev": true, + "dependencies": { + "create-hash": "^1.1.2", + "create-hmac": "^1.1.4", + "ripemd160": "^2.0.1", + "safe-buffer": "^5.0.1", + "sha.js": "^2.4.8" + }, + "engines": { + "node": ">=0.12" + } + }, + "node_modules/picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", + "dev": true, + "peer": true + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pkg-dir": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-5.0.0.tgz", + "integrity": "sha512-NPE8TDbzl/3YQYY7CSS228s3g2ollTFnc+Qi3tqmqJp9Vg2ovUpixcJEo2HJScN2Ez+kEaal6y70c0ehqJBJeA==", + "dev": true, + "dependencies": { + "find-up": "^5.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/playwright": { + "version": "1.36.2", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.36.2.tgz", + "integrity": "sha512-4Fmlq3KWsl85Bl4InJw1NC21aeQV0iSZuFvTDcy1F8zVmXmgQRe89GxF8zMSRt/KIS+2tUolak7EXVl9aC+JdA==", + "dev": true, + "hasInstallScript": true, + "dependencies": { + "playwright-core": "1.36.2" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/playwright-core": { + "version": "1.36.2", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.36.2.tgz", + "integrity": "sha512-sQYZt31dwkqxOrP7xy2ggDfEzUxM1lodjhsQ3NMMv5uGTRDsLxU0e4xf4wwMkF2gplIxf17QMBCodSFgm6bFVQ==", + "dev": true, + "bin": { + "playwright-core": "cli.js" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/process": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", + "engines": { + "node": ">= 0.6.0" + } + }, + "node_modules/progress-events": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/progress-events/-/progress-events-1.0.0.tgz", + "integrity": "sha512-zIB6QDrSbPfRg+33FZalluFIowkbV5Xh1xSuetjG+rlC5he6u2dc6VQJ0TbMdlN3R1RHdpOqxEFMKTnQ+itUwA==", + "engines": { + "node": ">=16.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/protobufjs": { + "version": "7.2.4", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.2.4.tgz", + "integrity": "sha512-AT+RJgD2sH8phPmCf7OUZR8xGdcJRga4+1cOaXJ64hvcSkVhNcRHOwIxUatPH15+nj59WAGTDv3LSGZPEQbJaQ==", + "hasInstallScript": true, + "dependencies": { + "@protobufjs/aspromise": "^1.1.2", + "@protobufjs/base64": "^1.1.2", + "@protobufjs/codegen": "^2.0.4", + "@protobufjs/eventemitter": "^1.1.0", + "@protobufjs/fetch": "^1.1.0", + "@protobufjs/float": "^1.0.2", + "@protobufjs/inquire": "^1.1.0", + "@protobufjs/path": "^1.1.2", + "@protobufjs/pool": "^1.1.0", + "@protobufjs/utf8": "^1.1.0", + "@types/node": ">=13.7.0", + "long": "^5.0.0" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/protons-runtime": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/protons-runtime/-/protons-runtime-5.0.2.tgz", + "integrity": "sha512-eKppVrIS5dDh+Y61Yj4bDEOs2sQLQbQGIhr7EBiybPQhIMGBynzVXlYILPWl3Td1GDadobc8qevh5D+JwfG9bw==", + "dependencies": { + "protobufjs": "^7.0.0", + "uint8arraylist": "^2.4.3" + }, + "peerDependencies": { + "uint8arraylist": "^2.3.2" + } + }, + "node_modules/public-encrypt": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/public-encrypt/-/public-encrypt-4.0.3.tgz", + "integrity": "sha512-zVpa8oKZSz5bTMTFClc1fQOnyyEzpl5ozpi1B5YcvBrdohMjH2rfsBtyXcuNuwjsDIXmBYlF2N5FlJYhR29t8Q==", + "dev": true, + "dependencies": { + "bn.js": "^4.1.0", + "browserify-rsa": "^4.0.0", + "create-hash": "^1.1.0", + "parse-asn1": "^5.0.0", + "randombytes": "^2.0.1", + "safe-buffer": "^5.1.2" + } + }, + "node_modules/public-encrypt/node_modules/bn.js": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", + "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", + "dev": true + }, + "node_modules/punycode": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", + "integrity": "sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ==", + "dev": true + }, + "node_modules/qjobs": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/qjobs/-/qjobs-1.2.0.tgz", + "integrity": "sha512-8YOJEHtxpySA3fFDyCRxA+UUV+fA+rTWnuWvylOK/NCjhY+b4ocCtmu8TtsWb+mYeU+GCHf/S66KZF/AsteKHg==", + "dev": true, + "engines": { + "node": ">=0.9" + } + }, + "node_modules/qs": { + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", + "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", + "dev": true, + "dependencies": { + "side-channel": "^1.0.4" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/querystring-es3": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/querystring-es3/-/querystring-es3-0.2.1.tgz", + "integrity": "sha512-773xhDQnZBMFobEiztv8LIl70ch5MSF/jUQVlhwFyBILqq96anmoctVIYz+ZRp0qbCKATTn6ev02M3r7Ga5vqA==", + "dev": true, + "engines": { + "node": ">=0.4.x" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/rabin-wasm": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/rabin-wasm/-/rabin-wasm-0.1.5.tgz", + "integrity": "sha512-uWgQTo7pim1Rnj5TuWcCewRDTf0PEFTSlaUjWP4eY9EbLV9em08v89oCz/WO+wRxpYuO36XEHp4wgYQnAgOHzA==", + "dependencies": { + "@assemblyscript/loader": "^0.9.4", + "bl": "^5.0.0", + "debug": "^4.3.1", + "minimist": "^1.2.5", + "node-fetch": "^2.6.1", + "readable-stream": "^3.6.0" + }, + "bin": { + "rabin-wasm": "cli/bin.js" + } + }, + "node_modules/rabin-wasm/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/rambda": { + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/rambda/-/rambda-7.5.0.tgz", + "integrity": "sha512-y/M9weqWAH4iopRd7EHDEQQvpFPHj1AA3oHozE9tfITHUtTR7Z9PSlIRRG2l1GuW7sefC1cXFfIcF+cgnShdBA==", + "dev": true + }, + "node_modules/randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "dependencies": { + "safe-buffer": "^5.1.0" + } + }, + "node_modules/randomfill": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/randomfill/-/randomfill-1.0.4.tgz", + "integrity": "sha512-87lcbR8+MhcWcUiQ+9e+Rwx8MyR2P7qnt15ynUlbm3TU/fjbgz4GsvfSUDTemtCCtVCqb4ZcEFlyPNTh9bBTLw==", + "dev": true, + "dependencies": { + "randombytes": "^2.0.5", + "safe-buffer": "^5.1.0" + } + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", + "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", + "dev": true, + "dependencies": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/readable-stream": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.4.2.tgz", + "integrity": "sha512-Lk/fICSyIhodxy1IDK2HazkeGjSmezAWX2egdtJnYhtzKEsBPJowlI6F6LPb5tqIQILrMbx22S5o3GuJavPusA==", + "dependencies": { + "abort-controller": "^3.0.0", + "buffer": "^6.0.3", + "events": "^3.3.0", + "process": "^0.11.10", + "string_decoder": "^1.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/readable-web-to-node-stream": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/readable-web-to-node-stream/-/readable-web-to-node-stream-3.0.2.tgz", + "integrity": "sha512-ePeK6cc1EcKLEhJFt/AebMCLL+GgSKhuygrZ/GLaKZYEecIgIECf4UaUuaByiGtzckwR4ain9VzUh95T1exYGw==", + "dependencies": { + "readable-stream": "^3.6.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + } + }, + "node_modules/readable-web-to-node-stream/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "engines": { + "node": ">=0.10.0" + } + }, + "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/resolve": { + "version": "1.22.4", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.4.tgz", + "integrity": "sha512-PXNdCiPqDqeUou+w1C2eTQbNfxKSuMxqTCuvlmmMsk1NWHL5fRrhY6Pl0qEYYc6+QqGClco1Qj8XnjPego4wfg==", + "dev": true, + "dependencies": { + "is-core-module": "^2.13.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true, + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rfdc": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.3.0.tgz", + "integrity": "sha512-V2hovdzFbOi77/WajaSMXk2OLm+xNIeQdMMuB7icj7bk6zi2F8GGAxigcnDFpJHbNyNcgyJDiP+8nOrY5cZGrA==", + "dev": true + }, + "node_modules/rimraf": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-4.4.0.tgz", + "integrity": "sha512-X36S+qpCUR0HjXlkDe4NAOhS//aHH0Z+h8Ckf2auGJk3PTnx5rLmrHkwNdbVQuCSUhOyFrlRvFEllZOYE+yZGQ==", + "dev": true, + "dependencies": { + "glob": "^9.2.0" + }, + "bin": { + "rimraf": "dist/cjs/src/bin.js" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rimraf/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/rimraf/node_modules/glob": { + "version": "9.3.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-9.3.5.tgz", + "integrity": "sha512-e1LleDykUz2Iu+MTYdkSsuWX8lvAjAcs0Xef0lNIu0S2wOAzuTxCJtcd9S3cijlwYF18EsU3rzb8jPVobxDh9Q==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "minimatch": "^8.0.2", + "minipass": "^4.2.4", + "path-scurry": "^1.6.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rimraf/node_modules/minimatch": { + "version": "8.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-8.0.4.tgz", + "integrity": "sha512-W0Wvr9HyFXZRGIDgCicunpQ299OKXs9RgZfaukz4qAW/pJhcpUfupc9c+OObPOFueNy8VSrZgEmDtk6Kh4WzDA==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/ripemd160": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.2.tgz", + "integrity": "sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA==", + "dev": true, + "dependencies": { + "hash-base": "^3.0.0", + "inherits": "^2.0.1" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/run-parallel-limit": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/run-parallel-limit/-/run-parallel-limit-1.1.0.tgz", + "integrity": "sha512-jJA7irRNM91jaKc3Hcl1npHsFLOXOoTkPCUL1JEa1R82O2miplXXRaGdjW/KM/98YQWDhJLiSs793CnXfblJUw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true + }, + "node_modules/schema-utils": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", + "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", + "dev": true, + "peer": true, + "dependencies": { + "@types/json-schema": "^7.0.8", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/schema-utils/node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "peer": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/schema-utils/node_modules/ajv-keywords": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", + "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", + "dev": true, + "peer": true, + "peerDependencies": { + "ajv": "^6.9.1" + } + }, + "node_modules/schema-utils/node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "peer": true + }, + "node_modules/semver": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/semver/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/serialize-javascript": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz", + "integrity": "sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==", + "dev": true, + "dependencies": { + "randombytes": "^2.1.0" + } + }, + "node_modules/setimmediate": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", + "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==", + "dev": true + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "dev": true + }, + "node_modules/sha.js": { + "version": "2.4.11", + "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", + "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", + "dev": true, + "dependencies": { + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + }, + "bin": { + "sha.js": "bin.js" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/side-channel": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", + "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.0", + "get-intrinsic": "^1.0.2", + "object-inspect": "^1.9.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true + }, + "node_modules/sinon": { + "version": "15.0.2", + "resolved": "https://registry.npmjs.org/sinon/-/sinon-15.0.2.tgz", + "integrity": "sha512-PCVP63XZkg0/LOqQH5rEU4LILuvTFMb5tNxTHfs6VUMNnZz2XrnGSTZbAGITjzwQWbl/Bl/8hi4G3zZWjyBwHg==", + "dev": true, + "dependencies": { + "@sinonjs/commons": "^3.0.0", + "@sinonjs/fake-timers": "^10.0.2", + "@sinonjs/samsam": "^7.0.1", + "diff": "^5.1.0", + "nise": "^5.1.4", + "supports-color": "^7.2.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/sinon" + } + }, + "node_modules/sinon/node_modules/diff": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.1.0.tgz", + "integrity": "sha512-D+mk+qE8VC/PAUrlAU34N+VfXev0ghe5ywmpqrawphmVZc1bEfn56uo9qpyGp1p4xpzOHkSW4ztBd6L7Xx4ACw==", + "dev": true, + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/socket.io": { + "version": "4.7.2", + "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.7.2.tgz", + "integrity": "sha512-bvKVS29/I5fl2FGLNHuXlQaUH/BlzX1IN6S+NKLNZpBsPZIDH+90eQmCs2Railn4YUiww4SzUedJ6+uzwFnKLw==", + "dev": true, + "dependencies": { + "accepts": "~1.3.4", + "base64id": "~2.0.0", + "cors": "~2.8.5", + "debug": "~4.3.2", + "engine.io": "~6.5.2", + "socket.io-adapter": "~2.5.2", + "socket.io-parser": "~4.2.4" + }, + "engines": { + "node": ">=10.2.0" + } + }, + "node_modules/socket.io-adapter": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.5.2.tgz", + "integrity": "sha512-87C3LO/NOMc+eMcpcxUBebGjkpMDkNBS9tf7KJqcDsmL936EChtVva71Dw2q4tQcuVC+hAUy4an2NO/sYXmwRA==", + "dev": true, + "dependencies": { + "ws": "~8.11.0" + } + }, + "node_modules/socket.io-parser": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.4.tgz", + "integrity": "sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==", + "dev": true, + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.3.1" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-js": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", + "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-loader": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/source-map-loader/-/source-map-loader-4.0.1.tgz", + "integrity": "sha512-oqXpzDIByKONVY8g1NUPOTQhe0UTU5bWUl32GSkqK2LjJj0HmwTMVKxcUip0RgAYhY1mqgOxjbQM48a0mmeNfA==", + "dev": true, + "dependencies": { + "abab": "^2.0.6", + "iconv-lite": "^0.6.3", + "source-map-js": "^1.0.2" + }, + "engines": { + "node": ">= 14.15.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.72.1" + } + }, + "node_modules/source-map-loader/node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dev": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dev": true, + "peer": true, + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/sparse-array": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/sparse-array/-/sparse-array-1.3.2.tgz", + "integrity": "sha512-ZT711fePGn3+kQyLuv1fpd3rNSkNF8vd5Kv2D+qnOANeyKs3fx6bUMGWRPvgTTcYV64QMqZKZwcuaQSP3AZ0tg==" + }, + "node_modules/statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/stream-browserify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-3.0.0.tgz", + "integrity": "sha512-H73RAHsVBapbim0tU2JwwOiXUj+fikfiaoYAKHF3VJfA0pe2BCzkhAHBlLG6REzE+2WNZcxOXjK7lkso+9euLA==", + "dev": true, + "dependencies": { + "inherits": "~2.0.4", + "readable-stream": "^3.5.0" + } + }, + "node_modules/stream-browserify/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dev": true, + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/stream-http": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/stream-http/-/stream-http-3.2.0.tgz", + "integrity": "sha512-Oq1bLqisTyK3TSCXpPbT4sdeYNdmyZJv1LxpEm2vu1ZhK89kSE5YXwZc3cWk0MagGaKriBh9mCFbVGtO+vY29A==", + "dev": true, + "dependencies": { + "builtin-status-codes": "^3.0.0", + "inherits": "^2.0.4", + "readable-stream": "^3.6.0", + "xtend": "^4.0.2" + } + }, + "node_modules/stream-http/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dev": true, + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/streamroller": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/streamroller/-/streamroller-3.1.5.tgz", + "integrity": "sha512-KFxaM7XT+irxvdqSP1LGLgNWbYN7ay5owZ3r/8t77p+EtSUAfUgtl7be3xtqtOmGUl9K9YPO2ca8133RlTjvKw==", + "dev": true, + "dependencies": { + "date-format": "^4.0.14", + "debug": "^4.3.4", + "fs-extra": "^8.1.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/string_decoder/node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/tapable": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", + "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==", + "dev": true, + "peer": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/terser": { + "version": "5.19.2", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.19.2.tgz", + "integrity": "sha512-qC5+dmecKJA4cpYxRa5aVkKehYsQKc+AHeKl0Oe62aYjBL8ZA33tTljktDHJSaxxMnbI5ZYw+o/S2DxxLu8OfA==", + "dev": true, + "peer": true, + "dependencies": { + "@jridgewell/source-map": "^0.3.3", + "acorn": "^8.8.2", + "commander": "^2.20.0", + "source-map-support": "~0.5.20" + }, + "bin": { + "terser": "bin/terser" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/terser-webpack-plugin": { + "version": "5.3.9", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.9.tgz", + "integrity": "sha512-ZuXsqE07EcggTWQjXUj+Aot/OMcD0bMKGgF63f7UxYcu5/AJF53aIpK1YoP5xR9l6s/Hy2b+t1AM0bLNPRuhwA==", + "dev": true, + "peer": true, + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.17", + "jest-worker": "^27.4.5", + "schema-utils": "^3.1.1", + "serialize-javascript": "^6.0.1", + "terser": "^5.16.8" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.1.0" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "uglify-js": { + "optional": true + } + } + }, + "node_modules/terser-webpack-plugin/node_modules/serialize-javascript": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.1.tgz", + "integrity": "sha512-owoXEFjWRllis8/M1Q+Cw5k8ZH40e3zhp/ovX+Xr/vi1qj6QesbyXXViFbpNvWvPNAD62SutwEXavefrLJWj7w==", + "dev": true, + "peer": true, + "dependencies": { + "randombytes": "^2.1.0" + } + }, + "node_modules/test-exclude": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", + "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", + "dev": true, + "dependencies": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^7.1.4", + "minimatch": "^3.0.4" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", + "dev": true + }, + "node_modules/timers-browserify": { + "version": "2.0.12", + "resolved": "https://registry.npmjs.org/timers-browserify/-/timers-browserify-2.0.12.tgz", + "integrity": "sha512-9phl76Cqm6FhSX9Xe1ZUAMLtm1BLkKj2Qd5ApyWkXzsMRaA7dgr81kf4wJmQf/hAvg8EEyJxDo3du/0KlhPiKQ==", + "dev": true, + "dependencies": { + "setimmediate": "^1.0.4" + }, + "engines": { + "node": ">=0.6.0" + } + }, + "node_modules/tmp": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.1.tgz", + "integrity": "sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ==", + "dev": true, + "dependencies": { + "rimraf": "^3.0.0" + }, + "engines": { + "node": ">=8.17.0" + } + }, + "node_modules/tmp/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/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "dev": true, + "engines": { + "node": ">=0.6" + } + }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" + }, + "node_modules/tslib": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.1.tgz", + "integrity": "sha512-t0hLfiEKfMUoqhG+U1oid7Pva4bbDPHYfJNiB7BiIjRkj1pyC++4N3huJfqY6aRH6VTB0rvtzQwjM4K6qpfOig==" + }, + "node_modules/tsutils": { + "version": "3.21.0", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", + "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", + "dev": true, + "dependencies": { + "tslib": "^1.8.1" + }, + "engines": { + "node": ">= 6" + }, + "peerDependencies": { + "typescript": ">=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta" + } + }, + "node_modules/tsutils/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true + }, + "node_modules/tty-browserify": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/tty-browserify/-/tty-browserify-0.0.1.tgz", + "integrity": "sha512-C3TaO7K81YvjCgQH9Q1S3R3P3BtN3RIM8n+OvX4il1K1zgE8ZhI0op7kClgkxtutIE8hQrcrHBXvIheqKUUCxw==", + "dev": true + }, + "node_modules/tweetnacl": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-1.0.3.tgz", + "integrity": "sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw==", + "dev": true + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "dev": true, + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/typescript": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.1.6.tgz", + "integrity": "sha512-zaWCozRZ6DLEWAWFrVDz1H6FVXzUSfTy5FUMWsQlU8Ym5JP9eO4xkTIROFCQvhQf61z6O/G6ugw3SgAnvvm+HA==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/ua-parser-js": { + "version": "0.7.35", + "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.35.tgz", + "integrity": "sha512-veRf7dawaj9xaWEu9HoTVn5Pggtc/qj+kqTOFvNiN1l0YdxwC1kvel57UCjThjGa3BHBihE8/UJAHI+uQHmd/g==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/ua-parser-js" + }, + { + "type": "paypal", + "url": "https://paypal.me/faisalman" + } + ], + "engines": { + "node": "*" + } + }, + "node_modules/uint8arraylist": { + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/uint8arraylist/-/uint8arraylist-2.4.3.tgz", + "integrity": "sha512-oEVZr4/GrH87K0kjNce6z8pSCzLEPqHNLNR5sj8cJOySrTP8Vb/pMIbZKLJGhQKxm1TiZ31atNrpn820Pyqpow==", + "dependencies": { + "uint8arrays": "^4.0.2" + }, + "engines": { + "node": ">=16.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/uint8arrays": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-4.0.6.tgz", + "integrity": "sha512-4ZesjQhqOU2Ip6GPReIwN60wRxIupavL8T0Iy36BBHr2qyMrNxsPJvr7vpS4eFt8F8kSguWUPad6ZM9izs/vyw==", + "dependencies": { + "multiformats": "^12.0.1" + } + }, + "node_modules/uint8arrays/node_modules/multiformats": { + "version": "12.0.1", + "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-12.0.1.tgz", + "integrity": "sha512-s01wijBJoDUqESWSzePY0lvTw7J3PVO9x2Cc6ASI5AMZM2Gnhh7BC17+nlFhHKU7dDzaCaRfb+NiqNzOsgPUoQ==", + "engines": { + "node": ">=16.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/ulid": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/ulid/-/ulid-2.3.0.tgz", + "integrity": "sha512-keqHubrlpvT6G2wH0OEfSW4mquYRcbe/J8NMmveoQOjUqmo+hXtO+ORCpWhdbZ7k72UtY61BL7haGxW6enBnjw==", + "bin": { + "ulid": "bin/cli.js" + } + }, + "node_modules/universalify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", + "dev": true, + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.11.tgz", + "integrity": "sha512-dCwEFf0/oT85M1fHBg4F0jtLwJrutGoHSQXCh7u4o2t1drG+c0a9Flnqww6XUKSfQMPpJBRjU8d4RXB09qtvaA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "peer": true, + "dependencies": { + "escalade": "^3.1.1", + "picocolors": "^1.0.0" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/uri-js/node_modules/punycode": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz", + "integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==", + "engines": { + "node": ">=6" + } + }, + "node_modules/url": { + "version": "0.11.1", + "resolved": "https://registry.npmjs.org/url/-/url-0.11.1.tgz", + "integrity": "sha512-rWS3H04/+mzzJkv0eZ7vEDGiQbgquI1fGfOad6zKvgYQi1SzMmhl7c/DdRGxhaWrVH6z0qWITo8rpnxK/RfEhA==", + "dev": true, + "dependencies": { + "punycode": "^1.4.1", + "qs": "^6.11.0" + } + }, + "node_modules/util": { + "version": "0.12.5", + "resolved": "https://registry.npmjs.org/util/-/util-0.12.5.tgz", + "integrity": "sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA==", + "dev": true, + "dependencies": { + "inherits": "^2.0.3", + "is-arguments": "^1.0.4", + "is-generator-function": "^1.0.7", + "is-typed-array": "^1.1.3", + "which-typed-array": "^1.1.2" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" + }, + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "dev": true, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/v8-to-istanbul": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.1.0.tgz", + "integrity": "sha512-6z3GW9x8G1gd+JIIgQQQxXuiJtCXeAjp6RaPEPLv62mH3iPHPxV6W3robxtCzNErRo6ZwTmzWhsbNvjyEBKzKA==", + "dev": true, + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.12", + "@types/istanbul-lib-coverage": "^2.0.1", + "convert-source-map": "^1.6.0" + }, + "engines": { + "node": ">=10.12.0" + } + }, + "node_modules/varint": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/varint/-/varint-6.0.0.tgz", + "integrity": "sha512-cXEIW6cfr15lFv563k4GuVuW/fiwjknytD37jIOLSdSWuOI6WnO/oKwmP2FQTU2l01LP8/M5TSAJpzUaGe3uWg==" + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/vm-browserify": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vm-browserify/-/vm-browserify-1.1.2.tgz", + "integrity": "sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ==", + "dev": true + }, + "node_modules/void-elements": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/void-elements/-/void-elements-2.0.1.tgz", + "integrity": "sha512-qZKX4RnBzH2ugr8Lxa7x+0V6XD9Sb/ouARtiasEQCHB1EVU4NXtmHsDDrx1dO4ne5fc3J6EW05BP1Dl0z0iung==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/watchpack": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz", + "integrity": "sha512-Lcvm7MGST/4fup+ifyKi2hjyIAwcdI4HRgtvTpIUxBRhB+RFtUh8XtDOxUfctVCnhVi+QQj49i91OyvzkJl6cg==", + "dev": true, + "peer": true, + "dependencies": { + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.1.2" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" + }, + "node_modules/webpack": { + "version": "5.88.2", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.88.2.tgz", + "integrity": "sha512-JmcgNZ1iKj+aiR0OvTYtWQqJwq37Pf683dY9bVORwVbUrDhLhdn/PlO2sHsFHPkj7sHNQF3JwaAkp49V+Sq1tQ==", + "dev": true, + "peer": true, + "dependencies": { + "@types/eslint-scope": "^3.7.3", + "@types/estree": "^1.0.0", + "@webassemblyjs/ast": "^1.11.5", + "@webassemblyjs/wasm-edit": "^1.11.5", + "@webassemblyjs/wasm-parser": "^1.11.5", + "acorn": "^8.7.1", + "acorn-import-assertions": "^1.9.0", + "browserslist": "^4.14.5", + "chrome-trace-event": "^1.0.2", + "enhanced-resolve": "^5.15.0", + "es-module-lexer": "^1.2.1", + "eslint-scope": "5.1.1", + "events": "^3.2.0", + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.2.9", + "json-parse-even-better-errors": "^2.3.1", + "loader-runner": "^4.2.0", + "mime-types": "^2.1.27", + "neo-async": "^2.6.2", + "schema-utils": "^3.2.0", + "tapable": "^2.1.1", + "terser-webpack-plugin": "^5.3.7", + "watchpack": "^2.4.0", + "webpack-sources": "^3.2.3" + }, + "bin": { + "webpack": "bin/webpack.js" + }, + "engines": { + "node": ">=10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependenciesMeta": { + "webpack-cli": { + "optional": true + } + } + }, + "node_modules/webpack-sources": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.3.tgz", + "integrity": "sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==", + "dev": true, + "peer": true, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/which-typed-array": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.11.tgz", + "integrity": "sha512-qe9UWWpkeG5yzZ0tNYxDmd7vo58HDBc39mZ0xWWpolAGADdFOzkfamWLDxkOWcvHQKVmdTyQdLD4NOfjLWTKew==", + "dev": true, + "dependencies": { + "available-typed-arrays": "^1.0.5", + "call-bind": "^1.0.2", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/workerpool": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.2.1.tgz", + "integrity": "sha512-ILEIE97kDZvF9Wb9f6h5aXK4swSlKGUcOEGiIYb2OOu/IrDU9iwj0fD//SsA6E5ibwJxpEvhullJY4Sl4GcpAw==", + "dev": true + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true + }, + "node_modules/ws": { + "version": "8.11.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.11.0.tgz", + "integrity": "sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg==", + "dev": true, + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true } + } + }, + "node_modules/xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "dev": true, + "engines": { + "node": ">=0.4" + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "node_modules/yargs": { + "version": "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/yargs-parser": { + "version": "20.2.9", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", + "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs-unparser": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz", + "integrity": "sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==", + "dev": true, + "dependencies": { + "camelcase": "^6.0.0", + "decamelize": "^4.0.0", + "flat": "^5.0.2", + "is-plain-obj": "^2.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "packages/agent": { + "name": "@web5/agent", + "version": "0.1.7", + "license": "Apache-2.0", + "dependencies": { + "@tbd54566975/dwn-sdk-js": "0.2.1", + "@web5/common": "0.1.1", + "@web5/crypto": "0.1.6", + "@web5/dids": "0.1.9", + "level": "8.0.0", + "readable-stream": "4.4.2", + "readable-web-to-node-stream": "3.0.2" + }, + "devDependencies": { + "@playwright/test": "1.36.2", + "@types/chai": "4.3.0", + "@types/chai-as-promised": "7.1.5", + "@types/eslint": "8.37.0", + "@types/mocha": "10.0.1", + "@types/readable-stream": "4.0.0", + "@typescript-eslint/eslint-plugin": "5.59.0", + "@typescript-eslint/parser": "5.59.0", + "c8": "8.0.0", + "chai": "4.3.7", + "chai-as-promised": "7.1.1", + "esbuild": "0.16.17", + "eslint": "8.39.0", + "eslint-plugin-mocha": "10.1.0", + "karma": "6.4.1", + "karma-chai": "0.1.0", + "karma-chrome-launcher": "3.1.1", + "karma-esbuild": "2.2.5", + "karma-firefox-launcher": "2.1.2", + "karma-mocha": "2.0.1", + "karma-mocha-reporter": "2.2.5", + "karma-webkit-launcher": "2.1.0", + "mocha": "10.2.0", + "node-stdlib-browser": "1.2.0", + "playwright": "1.36.2", + "rimraf": "4.4.0", + "typescript": "5.1.6" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "packages/api": { + "name": "@web5/api", + "version": "0.8.0", + "license": "Apache-2.0", + "dependencies": { + "@tbd54566975/dwn-sdk-js": "0.2.1", + "@web5/agent": "0.1.7", + "@web5/crypto": "0.1.6", + "@web5/dids": "0.1.9", + "@web5/user-agent": "0.1.10", + "level": "8.0.0", + "ms": "2.1.3", + "readable-stream": "4.4.2", + "readable-web-to-node-stream": "3.0.2" + }, + "devDependencies": { + "@playwright/test": "1.36.2", + "@types/chai": "4.3.0", + "@types/chai-as-promised": "7.1.5", + "@types/eslint": "8.37.0", + "@types/mocha": "10.0.1", + "@types/ms": "0.7.31", + "@types/readable-stream": "4.0.0", + "@types/sinon": "10.0.15", + "@typescript-eslint/eslint-plugin": "5.59.0", + "@typescript-eslint/parser": "5.59.0", + "c8": "8.0.0", + "chai": "4.3.7", + "chai-as-promised": "7.1.1", + "esbuild": "0.16.17", + "eslint": "8.39.0", + "eslint-plugin-mocha": "10.1.0", + "karma": "6.4.1", + "karma-chai": "0.1.0", + "karma-chrome-launcher": "3.1.1", + "karma-esbuild": "2.2.5", + "karma-firefox-launcher": "2.1.2", + "karma-mocha": "2.0.1", + "karma-mocha-reporter": "2.2.5", + "karma-webkit-launcher": "2.1.0", + "mocha": "10.2.0", + "node-stdlib-browser": "1.2.0", + "playwright": "1.36.2", + "rimraf": "4.4.0", + "sinon": "15.0.2", + "source-map-loader": "4.0.1", + "typescript": "5.1.6" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "packages/common": { + "name": "@web5/common", + "version": "0.1.1", + "license": "Apache-2.0", + "dependencies": { + "level": "8.0.0", + "multiformats": "11.0.2" + }, + "devDependencies": { + "@playwright/test": "1.36.2", + "@types/chai": "4.3.0", + "@types/chai-as-promised": "7.1.5", + "@types/eslint": "8.37.0", + "@types/mocha": "10.0.1", + "@typescript-eslint/eslint-plugin": "5.59.0", + "@typescript-eslint/parser": "5.59.0", + "c8": "8.0.0", + "chai": "4.3.7", + "chai-as-promised": "7.1.1", + "esbuild": "0.16.17", + "eslint": "8.39.0", + "eslint-plugin-mocha": "10.1.0", + "karma": "6.4.1", + "karma-chai": "0.1.0", + "karma-chrome-launcher": "3.1.1", + "karma-esbuild": "2.2.5", + "karma-firefox-launcher": "2.1.2", + "karma-mocha": "2.0.1", + "karma-mocha-reporter": "2.2.5", + "karma-webkit-launcher": "2.1.0", + "mocha": "10.2.0", + "playwright": "1.36.2", + "rimraf": "4.4.0", + "typescript": "5.1.6" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "packages/credentials": { + "name": "@web5/credentials", + "version": "0.1.5", + "license": "Apache-2.0", + "devDependencies": { + "@playwright/test": "1.36.2", + "@types/chai": "4.3.0", + "@types/eslint": "8.37.0", + "@types/mocha": "10.0.1", + "@typescript-eslint/eslint-plugin": "5.59.0", + "@typescript-eslint/parser": "5.59.0", + "c8": "8.0.0", + "chai": "4.3.7", + "esbuild": "0.16.17", + "eslint": "8.39.0", + "eslint-plugin-mocha": "10.1.0", + "karma": "6.4.1", + "karma-chai": "0.1.0", + "karma-chrome-launcher": "3.1.1", + "karma-esbuild": "2.2.5", + "karma-firefox-launcher": "2.1.2", + "karma-mocha": "2.0.1", + "karma-mocha-reporter": "2.2.5", + "karma-webkit-launcher": "2.1.0", + "mocha": "10.2.0", + "playwright": "1.36.2", + "rimraf": "4.4.0", + "typescript": "5.1.6" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "packages/crypto": { + "name": "@web5/crypto", + "version": "0.1.6", + "license": "Apache-2.0", + "dependencies": { + "@noble/ciphers": "0.1.4", + "@noble/curves": "1.1.0", + "@noble/hashes": "1.3.1", + "@web5/common": "0.1.1" + }, + "devDependencies": { + "@playwright/test": "1.36.2", + "@types/chai": "4.3.0", + "@types/chai-as-promised": "7.1.5", + "@types/ed2curve": "0.2.2", + "@types/eslint": "8.37.0", + "@types/mocha": "10.0.1", + "@types/sinon": "10.0.15", + "@types/uuid": "9.0.1", + "@typescript-eslint/eslint-plugin": "5.59.0", + "@typescript-eslint/parser": "5.59.0", + "c8": "8.0.0", + "chai": "4.3.7", + "chai-as-promised": "7.1.1", + "esbuild": "0.16.17", + "eslint": "8.39.0", + "eslint-plugin-mocha": "10.1.0", + "karma": "6.4.1", + "karma-chai": "0.1.0", + "karma-chrome-launcher": "3.1.1", + "karma-esbuild": "2.2.5", + "karma-firefox-launcher": "2.1.2", + "karma-mocha": "2.0.1", + "karma-mocha-reporter": "2.2.5", + "karma-webkit-launcher": "2.1.0", + "mocha": "10.2.0", + "playwright": "1.36.2", + "rimraf": "4.4.0", + "sinon": "15.0.2", + "source-map-loader": "4.0.1", + "typescript": "5.1.6" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "packages/dids": { + "name": "@web5/dids", + "version": "0.1.9", + "license": "Apache-2.0", + "dependencies": { + "@decentralized-identity/ion-pow-sdk": "1.0.17", + "@decentralized-identity/ion-sdk": "1.0.1", + "@web5/common": "0.1.1", + "@web5/crypto": "0.1.6", + "canonicalize": "2.0.0", + "did-resolver": "4.1.0" + }, + "devDependencies": { + "@playwright/test": "1.36.2", + "@types/chai": "4.3.0", + "@types/chai-as-promised": "7.1.5", + "@types/eslint": "8.37.0", + "@types/mocha": "10.0.1", + "@types/sinon": "10.0.15", + "@typescript-eslint/eslint-plugin": "5.59.0", + "@typescript-eslint/parser": "5.59.0", + "c8": "8.0.0", + "chai": "4.3.7", + "chai-as-promised": "7.1.1", + "esbuild": "0.16.17", + "eslint": "8.39.0", + "eslint-plugin-mocha": "10.1.0", + "karma": "6.4.1", + "karma-chai": "0.1.0", + "karma-chrome-launcher": "3.1.1", + "karma-esbuild": "2.2.5", + "karma-firefox-launcher": "2.1.2", + "karma-mocha": "2.0.1", + "karma-mocha-reporter": "2.2.5", + "karma-webkit-launcher": "2.1.0", + "mocha": "10.2.0", + "playwright": "1.36.2", + "rimraf": "4.4.0", + "sinon": "15.0.2", + "source-map-loader": "4.0.1", + "typescript": "5.1.6" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "packages/identity-agent": { + "name": "@web5/identity-agent", + "version": "0.1.0", + "license": "Apache-2.0", + "dependencies": { + "@web5/agent": "0.1.7" + }, + "devDependencies": { + "@playwright/test": "1.36.2", + "@types/chai": "4.3.0", + "@types/chai-as-promised": "7.1.5", + "@types/eslint": "8.37.0", + "@types/mocha": "10.0.1", + "@typescript-eslint/eslint-plugin": "5.59.0", + "@typescript-eslint/parser": "5.59.0", + "c8": "8.0.0", + "chai": "4.3.7", + "chai-as-promised": "7.1.1", + "esbuild": "0.16.17", + "eslint": "8.39.0", + "eslint-plugin-mocha": "10.1.0", + "karma": "6.4.1", + "karma-chai": "0.1.0", + "karma-chrome-launcher": "3.1.1", + "karma-esbuild": "2.2.5", + "karma-firefox-launcher": "2.1.2", + "karma-mocha": "2.0.1", + "karma-mocha-reporter": "2.2.5", + "karma-webkit-launcher": "2.1.0", + "mocha": "10.2.0", + "node-stdlib-browser": "1.2.0", + "playwright": "1.36.2", + "rimraf": "4.4.0", + "typescript": "5.1.6" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "packages/proxy-agent": { + "name": "@web5/proxy-agent", + "version": "0.1.10", + "license": "Apache-2.0", + "dependencies": { + "@web5/agent": "0.1.7", + "@web5/common": "0.1.1", + "@web5/crypto": "0.1.6", + "@web5/dids": "0.1.9" + }, + "devDependencies": { + "@playwright/test": "1.36.2", + "@types/chai": "4.3.0", + "@types/chai-as-promised": "7.1.5", + "@types/eslint": "8.37.0", + "@types/mocha": "10.0.1", + "@typescript-eslint/eslint-plugin": "5.59.0", + "@typescript-eslint/parser": "5.59.0", + "c8": "8.0.0", + "chai": "4.3.7", + "chai-as-promised": "7.1.1", + "esbuild": "0.16.17", + "eslint": "8.39.0", + "eslint-plugin-mocha": "10.1.0", + "karma": "6.4.1", + "karma-chai": "0.1.0", + "karma-chrome-launcher": "3.1.1", + "karma-esbuild": "2.2.5", + "karma-firefox-launcher": "2.1.2", + "karma-mocha": "2.0.1", + "karma-mocha-reporter": "2.2.5", + "karma-webkit-launcher": "2.1.0", + "mocha": "10.2.0", + "node-stdlib-browser": "1.2.0", + "playwright": "1.36.2", + "rimraf": "4.4.0", + "typescript": "5.1.6" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "packages/user-agent": { + "name": "@web5/user-agent", + "version": "0.1.10", + "license": "Apache-2.0", + "dependencies": { + "@web5/agent": "0.1.7", + "@web5/common": "0.1.1", + "@web5/crypto": "0.1.6", + "@web5/dids": "0.1.9" + }, + "devDependencies": { + "@playwright/test": "1.36.2", + "@types/chai": "4.3.0", + "@types/chai-as-promised": "7.1.5", + "@types/eslint": "8.37.0", + "@types/mocha": "10.0.1", + "@typescript-eslint/eslint-plugin": "5.59.0", + "@typescript-eslint/parser": "5.59.0", + "c8": "8.0.0", + "chai": "4.3.7", + "chai-as-promised": "7.1.1", + "esbuild": "0.16.17", + "eslint": "8.39.0", + "eslint-plugin-mocha": "10.1.0", + "karma": "6.4.1", + "karma-chai": "0.1.0", + "karma-chrome-launcher": "3.1.1", + "karma-esbuild": "2.2.5", + "karma-firefox-launcher": "2.1.2", + "karma-mocha": "2.0.1", + "karma-mocha-reporter": "2.2.5", + "karma-webkit-launcher": "2.1.0", + "mocha": "10.2.0", + "node-stdlib-browser": "1.2.0", + "playwright": "1.36.2", + "rimraf": "4.4.0", + "typescript": "5.1.6" + }, + "engines": { + "node": ">=18.0.0" + } } + } } diff --git a/package.json b/package.json index f48b7308a..cb4be22d0 100644 --- a/package.json +++ b/package.json @@ -1,24 +1,25 @@ { - "workspaces": [ - "packages/common", - "packages/crypto", - "packages/web5-agent", - "packages/dids", - "packages/credentials", - "packages/web5-user-agent", - "packages/web5-proxy-agent", - "packages/web5" - ], - "scripts": { - "clean": "npx npkill -d $(pwd) -t node_modules && npx npkill -d $(pwd) -t dist", - "build": "npm run build --ws" - }, - "private": true, - "overrides": { - "c8": { - "istanbul-lib-report": { - "make-dir": "^4.0.0" - } + "workspaces": [ + "packages/common", + "packages/crypto", + "packages/dids", + "packages/credentials", + "packages/agent", + "packages/user-agent", + "packages/proxy-agent", + "packages/api", + "packages/identity-agent" + ], + "scripts": { + "clean": "npx npkill -d $(pwd) -t node_modules && npx npkill -d $(pwd)/packages -t dist", + "build": "npm run build --ws" + }, + "private": true, + "overrides": { + "c8": { + "istanbul-lib-report": { + "make-dir": "^4.0.0" } } -} \ No newline at end of file + } +} diff --git a/packages/web5-agent/.c8rc.json b/packages/agent/.c8rc.json similarity index 66% rename from packages/web5-agent/.c8rc.json rename to packages/agent/.c8rc.json index 1d1670b70..ab680f663 100644 --- a/packages/web5-agent/.c8rc.json +++ b/packages/agent/.c8rc.json @@ -5,12 +5,12 @@ ".js" ], "include": [ - "tests/compiled/**" + "tests/compiled/src/**" ], "exclude": [ - "tests/compiled/src/main.js", + "tests/compiled/src/index.js", "tests/compiled/src/types.js", - "tests/compiled/types/**" + "tests/compiled/src/types/**" ], "reporter": [ "cobertura", diff --git a/packages/web5-agent/.mocharc.json b/packages/agent/.mocharc.json similarity index 100% rename from packages/web5-agent/.mocharc.json rename to packages/agent/.mocharc.json diff --git a/packages/web5-agent/.vscode/launch.json b/packages/agent/.vscode/launch.json similarity index 100% rename from packages/web5-agent/.vscode/launch.json rename to packages/agent/.vscode/launch.json diff --git a/packages/web5-agent/.vscode/tasks.json b/packages/agent/.vscode/tasks.json similarity index 93% rename from packages/web5-agent/.vscode/tasks.json rename to packages/agent/.vscode/tasks.json index d40b145f8..fb5508769 100644 --- a/packages/web5-agent/.vscode/tasks.json +++ b/packages/agent/.vscode/tasks.json @@ -31,7 +31,7 @@ "type": "npm", "script": "build:tests:node", "options": { - "cwd": "${workspaceFolder:web5-agent}" + "cwd": "${workspaceFolder:agent}" } } ] diff --git a/packages/web5-user-agent/build/bundles.js b/packages/agent/build/bundles.js similarity index 92% rename from packages/web5-user-agent/build/bundles.js rename to packages/agent/build/bundles.js index c9119e67b..c29d47c0c 100644 --- a/packages/web5-user-agent/build/bundles.js +++ b/packages/agent/build/bundles.js @@ -11,6 +11,6 @@ esbuild.build({ esbuild.build({ ...browserConfig, format : 'iife', - globalName : 'Web5', + globalName : 'Web5Agent', outfile : 'dist/browser.js', }); \ No newline at end of file diff --git a/packages/web5-user-agent/build/esbuild-browser-config.cjs b/packages/agent/build/esbuild-browser-config.cjs similarity index 96% rename from packages/web5-user-agent/build/esbuild-browser-config.cjs rename to packages/agent/build/esbuild-browser-config.cjs index a3ddbd9f8..bd8bd99b1 100644 --- a/packages/web5-user-agent/build/esbuild-browser-config.cjs +++ b/packages/agent/build/esbuild-browser-config.cjs @@ -15,7 +15,7 @@ for (let lib in stdLibBrowser) { /** @type {import('esbuild').BuildOptions} */ module.exports = { - entryPoints : ['./src/main.ts'], + entryPoints : ['./src/index.ts'], bundle : true, format : 'esm', sourcemap : true, @@ -27,4 +27,4 @@ module.exports = { define : { 'global': 'globalThis', }, -}; +}; \ No newline at end of file diff --git a/packages/agent/karma.conf.cjs b/packages/agent/karma.conf.cjs new file mode 100644 index 000000000..aa89cbca8 --- /dev/null +++ b/packages/agent/karma.conf.cjs @@ -0,0 +1,86 @@ +/* eslint-disable @typescript-eslint/no-var-requires */ +// Karma is what we're using to run our tests in browser environments +// Karma does not support .mjs + +// playwright acts as a safari executable on windows and mac +const playwright = require('@playwright/test'); +const esbuildBrowserConfig = require('./build/esbuild-browser-config.cjs'); + +// use playwright chrome exec path as run target for chromium tests +process.env.CHROME_BIN = playwright.chromium.executablePath(); + +// use playwright webkit exec path as run target for safari tests +process.env.WEBKIT_HEADLESS_BIN = playwright.webkit.executablePath(); + +// use playwright firefox exec path as run target for firefox tests +process.env.FIREFOX_BIN = playwright.firefox.executablePath(); + +module.exports = function (config) { + config.set({ + plugins: [ + 'karma-chrome-launcher', + 'karma-firefox-launcher', + 'karma-webkit-launcher', + 'karma-esbuild', + 'karma-mocha', + 'karma-mocha-reporter', + ], + + // frameworks to use + // available frameworks: https://www.npmjs.com/search?q=keywords:karma-adapter + frameworks: ['mocha'], + + // Increase Mocha's default timeout of 2 seconds to prevent timeouts during GitHub CI runs. + client: { + mocha: { + timeout: 10000 // 10 seconds + } + }, + + + // list of files / patterns to load in the browser + files: [ + { pattern: 'tests/**/*.spec.ts', watched: false }, + ], + + // preprocess matching files before serving them to the browser + // available preprocessors: https://www.npmjs.com/search?q=keywords:karma-preprocessor + preprocessors: { + 'tests/**/*.spec.ts': ['esbuild'], + }, + + esbuild: esbuildBrowserConfig, + + // list of files / patterns to exclude + exclude: [], + + // test results reporter to use + // available reporters: https://www.npmjs.com/search?q=keywords:karma-reporter + reporters: ['mocha'], + + // web server port + port: 9876, + + // enable / disable colors in the output (reporters and logs) + colors: true, + + // level of logging + // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || + // config.LOG_INFO || config.LOG_DEBUG + logLevel: config.LOG_INFO, + + concurrency: 1, + + // start these browsers + // available browser launchers: https://www.npmjs.com/search?q=keywords:karma-launcher + browsers: ['ChromeHeadless', 'FirefoxHeadless', 'WebkitHeadless'], + + // Continuous Integration mode + // if true, Karma captures browsers, runs the tests and exits + singleRun: true, + + // Increase browser timeouts to avoid DISCONNECTED messages during GitHub CI runs. + browserDisconnectTimeout : 10000, // default 2000 + browserDisconnectTolerance : 1, // default 0 + }); +}; diff --git a/packages/web5-user-agent/package.json b/packages/agent/package.json similarity index 72% rename from packages/web5-user-agent/package.json rename to packages/agent/package.json index f29474774..228b0c77d 100644 --- a/packages/web5-user-agent/package.json +++ b/packages/agent/package.json @@ -1,13 +1,12 @@ { - "name": "@tbd54566975/web5-user-agent", - "version": "0.8.0", - "description": "Web5 User Agent", + "name": "@web5/agent", + "version": "0.1.7", "type": "module", - "main": "./dist/cjs/main.js", - "module": "./dist/esm/main.js", - "types": "./dist/types/main.d.ts", + "main": "./dist/cjs/index.js", + "module": "./dist/esm/index.js", + "types": "./dist/types/index.d.ts", "scripts": { - "clean": "rimraf dist tests/compiled", + "clean": "rimraf dist coverage tests/compiled", "build:esm": "rimraf dist/esm dist/types && npx tsc -p tsconfig.json", "build:cjs": "rimraf dist/cjs && tsc -p tsconfig.cjs.json && echo '{\"type\": \"commonjs\"}' > ./dist/cjs/package.json", "build:browser": "rimraf dist/browser.mjs dist/browser.js && node build/bundles.js", @@ -18,12 +17,12 @@ "test:node": "npm run build:tests:node && c8 mocha", "test:browser": "karma start karma.conf.cjs" }, - "homepage": "https://github.com/TBD54566975/web5-js/tree/main/packages/web5-user-agent#readme", + "homepage": "https://github.com/TBD54566975/web5-js/tree/main/packages/agent#readme", "bugs": "https://github.com/TBD54566975/web5-js/issues", "repository": { "type": "git", "url": "git+https://github.com/TBD54566975/web5-js", - "directory": "packages/web5-user-agent" + "directory": "packages/agent" }, "license": "Apache-2.0", "contributors": [ @@ -46,17 +45,19 @@ ], "exports": { ".": { - "import": "./dist/esm/main.js", - "require": "./dist/cjs/main.js", - "types": "./dist/types/main.d.ts" + "import": "./dist/esm/index.js", + "require": "./dist/cjs/index.js", + "types": "./dist/types/index.d.ts" } }, - "react-native": "./dist/esm/main.js", + "react-native": "./dist/esm/index.mjs", "keywords": [ "decentralized", "decentralized-applications", "decentralized-identity", "decentralized-web", + "vcs", + "verifiable credentials", "web5" ], "publishConfig": { @@ -66,25 +67,21 @@ "node": ">=18.0.0" }, "dependencies": { - "@decentralized-identity/ion-tools": "1.1.4", - "@tbd54566975/dids": "0.8.0", "@tbd54566975/dwn-sdk-js": "0.2.1", - "@tbd54566975/web5-agent": "0.8.0", - "abstract-level": "1.0.3", - "cross-fetch": "4.0.0", - "flat": "5.0.2", + "@web5/common": "0.1.1", + "@web5/crypto": "0.1.6", + "@web5/dids": "0.1.9", "level": "8.0.0", - "readable-web-to-node-stream": "3.0.2", - "uuid": "9.0.0" + "readable-stream": "4.4.2", + "readable-web-to-node-stream": "3.0.2" }, "devDependencies": { - "@faker-js/faker": "8.0.1", + "@playwright/test": "1.36.2", "@types/chai": "4.3.0", "@types/chai-as-promised": "7.1.5", - "@types/flat": "5.0.2", + "@types/eslint": "8.37.0", "@types/mocha": "10.0.1", - "@types/readable-stream": "2.3.15", - "@types/sinon": "10.0.15", + "@types/readable-stream": "4.0.0", "@typescript-eslint/eslint-plugin": "5.59.0", "@typescript-eslint/parser": "5.59.0", "c8": "8.0.0", @@ -105,8 +102,6 @@ "node-stdlib-browser": "1.2.0", "playwright": "1.36.2", "rimraf": "4.4.0", - "sinon": "15.0.2", - "source-map-loader": "4.0.1", "typescript": "5.1.6" } -} \ No newline at end of file +} diff --git a/packages/agent/src/app-data-store.ts b/packages/agent/src/app-data-store.ts new file mode 100644 index 000000000..b676f539f --- /dev/null +++ b/packages/agent/src/app-data-store.ts @@ -0,0 +1,374 @@ +import type { Web5Crypto } from '@web5/crypto'; +import type { KeyValueStore } from '@web5/common'; + +import { hkdf } from '@noble/hashes/hkdf'; +import { sha256 } from '@noble/hashes/sha256'; +import { sha512 } from '@noble/hashes/sha512'; +import { pbkdf2Async } from '@noble/hashes/pbkdf2'; +import { Convert, MemoryStore } from '@web5/common'; +import { CryptoKey, randomBytes, XChaCha20Poly1305 } from '@web5/crypto'; + +import type { DidKeySet } from '@web5/dids'; +import type { JweHeaderParams, PublicKeyJwk } from '@web5/crypto'; + +import { Jose } from '@web5/crypto'; +import { DidKeyMethod } from '@web5/dids'; + +export type AppDataBackup = { + /** + * A timestamp to record when the backup was made. + */ + dateCreated: string; + + /** + * The size of the backup data. + */ + size: number; + + /** + * Encrypted vault contents. + */ + data: string; +} + +export type AppDataStatus = { + /** + * Boolean indicating whether the data was successful. + */ + initialized: boolean; + + /** + * The timestamp of the last backup. + */ + lastBackup: string | undefined; + + /** + * The timestamp of the last restore. + */ + lastRestore: string | undefined; +} + +export type AppData = { + [key: string]: any; +} + +export interface AppDataStore { + /** + * Returns a promise that resolves to a string, which is the App DID. + */ + getDid(): Promise + + /** + * Returns a promise that resolves to a CryptoKey object, which + * represents the public key associated with the App DID. + */ + getPublicKey(): Promise + + /** + * Returns a promise that resolves to a CryptoKey object, which + * represents the private key associated with the App DID. + */ + getPrivateKey(): Promise + + /** + * Returns a promise that resolves to a AppDataStatus object, which + * provides information about the current status of the AppData instance. + */ + getStatus(): Promise + + /** + * Initializes the AppDataStore and returns a Promise that resolves + * to a boolean indicating whether the operation was successful. + */ + initialize(options: { passphrase: string, keyPair: Web5Crypto.CryptoKeyPair }): Promise; + + /** + * Creates an encrypted backup of the current state of `AppData` and + * returns a Promise that resolves to an `AppDataBackup` object. + */ + backup(options: { passphrase: string }): Promise; + + /** + * Restores `AppData` to the state in the provided `AppDataBackup` object. + * It requires a passphrase to decrypt the backup and returns a Promise that + * resolves to a boolean indicating whether the restore was successful. + */ + restore(options: { backup: AppDataBackup, passphrase: string }): Promise; + + /** + * Locks the `AppDataStore`, secured by a passphrase + * that must be entered to unlock. + */ + lock(): Promise; + + /** + * Attempts to unlock the `AppDataStore` with the provided + * passphrase. It returns a Promise that resolves to a + * boolean indicating whether the unlock was successful. + */ + unlock(options: { passphrase: string }): Promise; + + /** + * Attempts to change the passphrase of the `AppDataStore`. + * It requires the old passphrase for verification and returns + * a Promise that resolves to a boolean indicating whether the + * passphrase change was successful. + */ + changePassphrase(options: { oldPassphrase: string, newPassphrase: string }): Promise; +} + +export type AppDataVaultOptions = { + keyDerivationWorkFactor?: number; + store?: KeyValueStore; +} + +export class AppDataVault implements AppDataStore { + private _keyDerivationWorkFactor: number; + private _store: KeyValueStore; + private _vaultUnlockKey = new Uint8Array(); + + constructor(options?: AppDataVaultOptions) { + this._keyDerivationWorkFactor = options?.keyDerivationWorkFactor ?? 650_000; + this._store = options?.store ?? new MemoryStore(); + } + + async backup(options: { passphrase: string }): Promise { + console.log(options); + return null as any; + } + + async changePassphrase(options: { oldPassphrase: string, newPassphrase: string }): Promise { + console.log(options); + return null as any; + } + + private async generateVaultUnlockKey(options: { + passphrase: string, + salt: Uint8Array + }): Promise { + const { passphrase, salt } = options; + + /** The salt value derived in Step 3 and the passphrase entered by the + * end-user are inputs to the PBKDF2 algorithm to derive a 32-byte secret + * key that will be referred to as the Vault Unlock Key (VUK). */ + const vaultUnlockKey = await pbkdf2Async( + sha512, // hash function + passphrase, // password + salt, // salt + { + c : this._keyDerivationWorkFactor, // key derivation work factor + dkLen : 32 // derived key length, in bytes + } + ); + + return vaultUnlockKey; + } + + async getDid(): Promise { + // Get the Vault Key Set JWE from the data store. + const vaultKeySet = await this._store.get('vaultKeySet'); + + // Decode the Base64 URL encoded JWE protected header. + let [protectedHeaderB64U] = vaultKeySet.split('.'); + const protectedHeader = Convert.base64Url(protectedHeaderB64U).toObject() as JweHeaderParams; + + // Extract the public key in JWK format. + const publicKeyJwk = protectedHeader.wrappedKey as PublicKeyJwk; + + // Expand the public key to a did:key identifier. + const keySet: DidKeySet = { verificationMethodKeys: [{ publicKeyJwk, relationships: ['authentication'] }]}; + const { did } = await DidKeyMethod.create({ keySet }); + + return did; + } + + async getPublicKey(): Promise { + // Get the Vault Key Set JWE from the data store. + const vaultKeySet = await this._store.get('vaultKeySet'); + + // Decode the Base64 URL encoded JWE protected header. + let [protectedHeaderB64U] = vaultKeySet.split('.'); + const protectedHeader = Convert.base64Url(protectedHeaderB64U).toObject() as JweHeaderParams; + + // Convert the public key in JWK format to crypto key. + const publicKeyJwk = protectedHeader.wrappedKey as PublicKeyJwk; + const cryptoKey = await Jose.jwkToCryptoKey({ key: publicKeyJwk }); + + return cryptoKey; + } + + async getPrivateKey(): Promise { + // Get the Vault Key Set JWE from the data store. + const vaultKeySet = await this._store.get('vaultKeySet'); + + // Decode the Base64 URL encoded JWE content. + let [protectedHeaderB64U, encryptedKeyB64U, nonceB64U, _, tagB64U] = vaultKeySet.split('.'); + const protectedHeader = Convert.base64Url(protectedHeaderB64U).toObject() as JweHeaderParams; + const encryptedKey = Convert.base64Url(encryptedKeyB64U).toUint8Array(); + const nonce = Convert.base64Url(nonceB64U).toUint8Array(); + const tag = Convert.base64Url(tagB64U).toUint8Array(); + + // Decrypt the Identity Agent's private key material. + const privateKeyMaterial = await XChaCha20Poly1305.decrypt({ + additionalData : Convert.object(protectedHeader).toUint8Array(), + data : encryptedKey, + key : this._vaultUnlockKey, + nonce : nonce, + tag : tag + }); + + // Get the public key. + const publicKey = await this.getPublicKey(); + + // Create a private crypto key based off the parameters of the public key. + const privateKey = new CryptoKey( + publicKey.algorithm, + publicKey.extractable, + privateKeyMaterial, + 'private', + ['sign'] + ); + + return privateKey; + } + + async getStatus(): Promise { + try { + const appDataStatus = await this._store.get('appDataStatus'); + return JSON.parse(appDataStatus); + } catch(error: any) { + return { + initialized : false, + lastBackup : undefined, + lastRestore : undefined + }; + } + } + + async initialize(options: { + keyPair: Web5Crypto.CryptoKeyPair, + passphrase: string + }): Promise { + const { keyPair, passphrase } = options; + + const appDataStatus = await this.getStatus(); + + // Throw if the data vault was previously initialized. + if (appDataStatus.initialized === true) { + throw new Error(`Operation 'initialize' failed. Data vault already initialized.`); + } + + /** A non-secret static info value is combined with the Identity Agent's + * public key as input to a Hash-based Key Derivation Function (HKDF) + * to derive a new 32-byte salt. */ + const publicKey = keyPair.publicKey.material; + const saltInput = hkdf( + sha256, // hash function + publicKey, // input keying material + undefined, // no salt because public key is already random + 'vault_unlock_salt', // non-secret application specific information + 32 // derived key length, in bytes + ); + + /** + * Per RFC 7518, the salt value used with PBES2 should be of the format + * (UTF8(Alg) || 0x00 || Salt Input), where Alg is the "alg" (algorithm) + * Header Parameter value. This reduces the potential for a precomputed + * dictionary attack (also known as a rainbow table attack). + * @see {@link https://www.rfc-editor.org/rfc/rfc7518.html#section-4.8.1.1 | RFC 7518, Section 4.8.1.1} + */ + const algorithm = Convert.string('PBES2-HS512+XC20PKW').toUint8Array(); + const salt = new Uint8Array([...algorithm, 0x00, ...saltInput]); + + /** + * Generate a vault unlock key (VUK), which will be used as a + * key encryption key (KEK) for wrapping the private key */ + this._vaultUnlockKey = await this.generateVaultUnlockKey({ passphrase, salt }); + + /** Convert the public crypto key to JWK format to store within the JWE. */ + const wrappedKey = await Jose.cryptoKeyToJwk({ key: keyPair.publicKey }); + + /** Construct the JWE header. */ + const protectedHeader: JweHeaderParams = { + alg : 'PBES2-HS512+XC20PKW', + crit : ['wrappedKey'], + enc : 'XC20P', + p2c : this._keyDerivationWorkFactor, + p2s : Convert.uint8Array(salt).toBase64Url(), + wrappedKey : wrappedKey + }; + + /** 6. Encrypt the Identity Agent's private key with the derived VUK + * using XChaCha20-Poly1305 */ + const nonce = randomBytes(24); + const privateKey = keyPair.privateKey.material; + const { + ciphertext: privateKeyCiphertext, + tag: privateKeyTag } = await XChaCha20Poly1305.encrypt({ + additionalData : Convert.object(protectedHeader).toUint8Array(), + data : privateKey, + key : this._vaultUnlockKey, + nonce : nonce + }); + + /** 7. Serialize the Identity Agent's vault key set to a compact JWE, which + * includes the VUK salt and encrypted VUK (nonce, tag, and ciphertext). */ + const vaultKeySet = + Convert.object(protectedHeader).toBase64Url() + '.' + + Convert.uint8Array(privateKeyCiphertext).toBase64Url() + '.' + + Convert.uint8Array(nonce).toBase64Url() + '.' + + Convert.string('unused').toBase64Url() + '.' + + Convert.uint8Array(privateKeyTag).toBase64Url(); + + /** Store the vault key set in the AppDataStore. */ + await this._store.set('vaultKeySet', vaultKeySet); + + /** Set the vault to initialized. */ + appDataStatus.initialized = true; + await this.setStatus(appDataStatus); + } + + async lock(): Promise { + this._vaultUnlockKey.fill(0); + this._vaultUnlockKey = new Uint8Array(); + } + + async restore(options: { backup: AppDataBackup, passphrase: string }): Promise { + console.log(options); + return null as any; + } + + async setStatus(options: Partial): Promise { + // Get the current status values from the store, if any. + const appDataStatus = await this.getStatus(); + + // Update the status properties with new values specified, if any. + appDataStatus.initialized = options.initialized ?? appDataStatus.initialized; + appDataStatus.lastBackup = options.lastBackup ?? appDataStatus.lastBackup; + appDataStatus.lastRestore = options.lastRestore ?? appDataStatus.lastRestore; + + // Write the changes to the store. + await this._store.set('appDataStatus', JSON.stringify(appDataStatus)); + + return true; + } + + async unlock(options: { passphrase: string }): Promise { + const { passphrase } = options; + + // Get the vault key set from the store. + const vaultKeySet: string = await this._store.get('vaultKeySet'); + + // Decode the protected header. + let [protectedHeaderString] = vaultKeySet.split('.'); + const protectedHeader = Convert.base64Url(protectedHeaderString).toObject() as JweHeaderParams; + + // Derive the Vault Unlock Key (VUK). + if (protectedHeader.p2s !== undefined) { + const salt = Convert.base64Url(protectedHeader.p2s).toUint8Array(); + this._vaultUnlockKey = await this.generateVaultUnlockKey({ passphrase, salt }); + } + + return true; + } +} \ No newline at end of file diff --git a/packages/agent/src/did-manager.ts b/packages/agent/src/did-manager.ts new file mode 100644 index 000000000..b8130b0c7 --- /dev/null +++ b/packages/agent/src/did-manager.ts @@ -0,0 +1,371 @@ +import type { PublicKeyJwk, Web5Crypto } from '@web5/crypto'; +import type { + DidKeySet, + DidDocument, + DidMetadata, + PortableDid, + DidMethodApi, + DidIonCreateOptions, + DidKeyCreateOptions, +} from '@web5/dids'; + +import { Jose} from '@web5/crypto'; +import { utils } from '@web5/dids'; + +import type { Web5ManagedAgent } from './types/agent.js'; +import type { ManagedDidStore } from './store-managed-did.js'; + +import { DidStoreMemory } from './store-managed-did.js'; + +export type CreateDidMethodOptions = { + ion: DidIonCreateOptions; + key: DidKeyCreateOptions; +}; + +export type CreateDidOptions = CreateDidMethodOptions[M] & { + method: M; + alias?: string; + context?: string; + kms?: string; + metadata?: DidMetadata; +} + +export type ImportDidOptions = { + alias?: string; + context?: string; + did: PortableDid; + kms?: string; +} + +export interface ManagedDid extends PortableDid { + /** + * An alternate identifier used to identify the DID. + * This property can be used to associate a DID with an external identifier. + */ + alias?: string; + + /** + * DID Method name. + */ + method: string; +} + +export type DidManagerOptions = { + agent?: Web5ManagedAgent; + didMethods: DidMethodApi[]; + store?: ManagedDidStore; +} + +export type DidIonGenerateKeySetOptions = { /* empty */ } +export type DidKeyGenerateKeySetOptions = { /* empty */ } + +export type GenerateKeySetOptions = { + ion: DidIonGenerateKeySetOptions; + key: DidKeyGenerateKeySetOptions; +}; + +export class DidManager { + /** + * Holds the instance of a `Web5ManagedAgent` that represents the current + * execution context for the `KeyManager`. This agent is utilized + * to interact with other Web5 agent components. It's vital + * to ensure this instance is set to correctly contextualize + * operations within the broader Web5 agent framework. + */ + private _agent?: Web5ManagedAgent; + private _didMethods: Map = new Map(); + private _store: ManagedDidStore; + + constructor(options: DidManagerOptions) { + const { agent, didMethods, store } = options; + this._agent = agent; + this._store = store ?? new DidStoreMemory(); + + if (!didMethods) { + throw new TypeError(`DidManager: Required parameter missing: 'didMethods'`); + } + + for (const didMethod of didMethods) { + this._didMethods.set(didMethod.methodName, didMethod); + } + } + + /** + * Retrieves the `Web5ManagedAgent` execution context. + * If the `agent` instance proprety is undefined, it will throw an error. + * + * @returns The `Web5ManagedAgent` instance that represents the current execution + * context. + * + * @throws Will throw an error if the `agent` instance property is undefined. + */ + get agent(): Web5ManagedAgent { + if (this._agent === undefined) { + throw new Error('DidManager: Unable to determine agent execution context.'); + } + + return this._agent; + } + + set agent(agent: Web5ManagedAgent) { + this._agent = agent; + } + + async create(options: CreateDidOptions): Promise { + let { alias, keySet, kms, metadata, method, context, ...methodOptions } = options; + + // Get the DID method implementation. + const didMethod = this.getMethod(method); + + // If keySet not given, generate a DID method specific key set. + if (keySet?.verificationMethodKeys === undefined) { + keySet = await didMethod.generateKeySet(); + } + + /** Import key set to KeyManager, or if already in KeyManager, retrieve the + * public key. */ + keySet = await this.importOrGetKeySet({ keySet, kms }); + + // Create a DID. + const did = await didMethod.create({ ...methodOptions, keySet }); + + // Set the KeyManager alias for each key to the DID Document primary ID. + await this.updateKeySet({ + canonicalId : did.canonicalId, + didDocument : did.document, + keySet + }); + + // Merged given metadata and format as a ManagedDid. + const mergedMetadata = { ...metadata, ...did.metadata }; + const managedDid = { alias, method, ...did, metadata: mergedMetadata }; + + /** If context is undefined, then the DID will be stored under the + * tenant of the created DID. Otherwise, the DID record will + * be stored under the tenant of the specified context. */ + context ??= managedDid.did; + + // Store the ManagedDid in the store. + await this._store.importDid({ did: managedDid, agent: this.agent, context }); + + return managedDid; + } + + async getDefaultSigningKey(options: { + did: string + }): Promise { + const { did } = options; + + // Resolve the DID to a DID Document. + const { didDocument } = await this.agent.didResolver.resolve(did); + + // Get the DID method implementation. + const parsedDid = utils.parseDid({ didUrl: did }); + + if (!(didDocument && parsedDid)) { + throw new Error(`DidManager: Unable to resolve: ${did}`); + } + + const didMethod = this.getMethod(parsedDid.method); + + // Retrieve the DID method specific default signing key. + const verificationMethodId = await didMethod.getDefaultSigningKey({ didDocument }); + + return verificationMethodId; + } + + async get(options: { + didRef: string, + context?: string + }): Promise { + let did: ManagedDid | undefined; + const { context, didRef } = options; + + // Try to get DID by ID. + did = await this._store.getDid({ did: didRef, agent: this.agent, context }); + if (did) return did; + + // Try to find DID by alias. + did = await this._store.findDid({ alias: didRef, agent: this.agent, context }); + if (did) return did; + + return undefined; + } + + async import(options: ImportDidOptions): Promise { + let { alias, context, did, kms } = options; + + if (did.keySet === undefined) { + throw new Error(`Portable DID is missing required property: 'keySet'`); + } + + // Verify the DID method is supported. + const parsedDid = utils.parseDid({ didUrl: did.did }); + if (!parsedDid) { + throw new Error(`DidManager: Unable to resolve: ${did}`); + } + const { method } = parsedDid; + this.getMethod(method); + + /** Import key set to KeyManager, or if already in KeyManager, retrieve the + * public key. */ + const keySet = await this.importOrGetKeySet({ keySet: did.keySet, kms }); + + // Set the KeyManager alias for each key to the DID Document primary ID. + await this.updateKeySet({ + canonicalId : did.canonicalId, + didDocument : did.document, + keySet + }); + + // Format the PortableDid and given input as a ManagedDid. + const managedDid = { alias, method, ...did, keySet }; + + /** If context is undefined, then the DID will be stored under the + * tenant of the imported DID. Otherwise, the DID record will + * be stored under the tenant of the specified context. */ + context ??= managedDid.did; + + // Store the ManagedDid in the store. + await this._store.importDid({ did: managedDid, agent: this.agent, context }); + + return managedDid; + } + + /** + * Retrieves a `DidMethodApi` instance associated with a specific method + * name. This method uses the method name to access the `didMethods` map + * and returns the corresponding `DidMethodApi` instance. If a method + * name is provided that does not exist within the `didMethods` map, it + * will throw an error. + * + * @param methodName - A string representing the name of the method for + * which the corresponding `DidMethodApi` instance is to be retrieved. + * + * @returns The `DidMethodApi` instance that corresponds to the provided + * method name. If no `DidMethodApi` instance corresponds to the provided + * method name, an error is thrown. + * + * @throws Will throw an error if the provided method name does not + * correspond to any `DidMethodApi` instance within the `didMethods` map. + */ + private getMethod(methodName: string): DidMethodApi { + const didMethod = this._didMethods.get(methodName); + + if (didMethod === undefined) { + throw new Error(`The DID method '${methodName}' is not supported`); + } + + return didMethod; + } + + private async importOrGetKeySet(options: { + keySet: DidKeySet, + kms: string | undefined + }): Promise { + const { kms } = options; + + // Get the agent instance. + const agent = this.agent; + + // Make a deep copy of the key set to prevent side effects. + const keySet = structuredClone(options.keySet); + + for (let key of keySet.verificationMethodKeys!) { + /** + * The key has no `keyManagerId` value, indicating it is not present in + * the KeyManager store. Import each key into KeyManager. + */ + if (key.keyManagerId === undefined) { + if ('publicKeyJwk' in key && 'privateKeyJwk' in key + && key.publicKeyJwk && key.privateKeyJwk) { + // Import key pair to KeyManager. + const publicKey = await Jose.jwkToCryptoKey({ key: key.publicKeyJwk }); + const privateKey = await Jose.jwkToCryptoKey({ key: key.privateKeyJwk! }); + const importedKeyPair = await agent.keyManager.importKey({ + privateKey : { kms: kms, ...privateKey, material: privateKey.material }, + publicKey : { kms: kms, ...publicKey, material: publicKey.material } + }); + // Store the UUID assigned by KeyManager. + key.keyManagerId = importedKeyPair.privateKey.id; + // Delete the private key. + delete key.privateKeyJwk; + + } else if ('publicKeyJwk' in key && key.publicKeyJwk) { + // Import only public key. + const publicKey = await Jose.jwkToCryptoKey({ key: key.publicKeyJwk }); + const importedPublicKey = await agent.keyManager.importKey({ + kms: kms, ...publicKey, material: publicKey.material + }); + // Store the UUID assigned by KeyManager. + key.keyManagerId = importedPublicKey.id; + + } else { + throw new Error(`Required parameter(s) missing: 'publicKeyJwk', and optionally, 'privateKeyJwk`); + } + + /** + * The key does have a `keyManagerId` value so retrieve the public key + * from the KeyManager store. + */ + } else { + const keyOrKeyPair = await agent.keyManager.getKey({ keyRef: key.keyManagerId }); + if (!keyOrKeyPair) throw new Error(`Key with ID '${key.keyManagerId} not found.`); + const publicKey = 'publicKey' in keyOrKeyPair ? keyOrKeyPair.publicKey : keyOrKeyPair; + // Convert public key from CryptoKey to JWK format. + key.publicKeyJwk = await Jose.cryptoKeyToJwk({ key: publicKey as Web5Crypto.CryptoKey }) as PublicKeyJwk; + } + } + + return keySet; + } + + /** + * Set the KeyManager alias for each key to the DID primary ID. + * + * If defined, use the `canonicalId` as the primary ID for the + * DID subject. Otherwise, use the `id` property from the topmost + * map of the DID document. + * + * @see {@link https://www.w3.org/TR/did-core/#did-subject | DID Subject} + * @see {@link https://www.w3.org/TR/did-core/#dfn-canonicalid | DID Document Metadata} + */ + private async updateKeySet(options: { + canonicalId?: string, + didDocument: DidDocument, + keySet: DidKeySet + }) { + const { canonicalId, didDocument, keySet, } = options; + + // Get the agent instance. + const agent = this.agent; + + // DID primary ID is the canonicalId, if present, or the DID document `id`. + const didPrimaryId = canonicalId ?? didDocument.id; + + for (let keyPair of keySet.verificationMethodKeys!) { + /** Compute the multibase ID for the JWK in case the DID method uses + * publicKeyMultibase format. */ + const publicKeyMultibase = await Jose.jwkToMultibaseId({ key: keyPair.publicKeyJwk! }); + + // Find the verification method ID of the key in the DID document. + const methodId = utils.getVerificationMethodIds({ + didDocument, + publicKeyJwk: keyPair.publicKeyJwk, + publicKeyMultibase + }); + + if (!(methodId && methodId.includes('#'))) { + throw new Error('DidManager: Unable to update key set due to malformed verification method ID'); + } + + /** Construct the key alias given the DID's primary ID and the key's + * verification method ID. */ + const [, fragment] = methodId.split('#'); + const keyAlias = `${didPrimaryId}#${fragment}`; + + // Set the KeyManager alias to the method ID. + await agent.keyManager.updateKey({ keyRef: keyPair.keyManagerId!, alias: keyAlias }); + } + } +} \ No newline at end of file diff --git a/packages/agent/src/dwn-manager.ts b/packages/agent/src/dwn-manager.ts new file mode 100644 index 000000000..ad30553ae --- /dev/null +++ b/packages/agent/src/dwn-manager.ts @@ -0,0 +1,519 @@ +import type { + GenericMessage, + SignatureInput, + MessagesGetReply, + RecordsReadReply, + UnionMessageReply, + EncryptionProperty, + RecordsWriteMessage, + RecordsWriteOptions, + PrivateJwk as DwnPrivateKeyJwk, +} from '@tbd54566975/dwn-sdk-js'; + +import { Jose } from '@web5/crypto'; +import { Readable } from 'readable-stream'; +import { DidResolver, utils as didUtils } from '@web5/dids'; +import { Convert, removeUndefinedProperties } from '@web5/common'; + +import { + EventLogLevel, + DataStoreLevel, + MessageStoreLevel, +} from '@tbd54566975/dwn-sdk-js/stores'; +import { + Cid, + Dwn, + Message, + EventsGet, + DataStream, + RecordsRead, + MessagesGet, + RecordsWrite, + RecordsQuery, + DwnMethodName, + RecordsDelete, + ProtocolsQuery, + DwnInterfaceName, + ProtocolsConfigure, +} from '@tbd54566975/dwn-sdk-js'; + +import type { DwnRpcRequest, DwnResponse,ProcessDwnRequest, SendDwnRequest, Web5ManagedAgent } from './types/agent.js'; + +import { isManagedKeyPair } from './utils.js'; +import { blobToIsomorphicNodeReadable, webReadableToIsomorphicNodeReadable } from './utils.js'; + +export type GeneralJws = { + payload: string + signatures: SignatureEntry[] +}; + +export type SignatureEntry = { + protected: string + signature: string +}; + +export type RecordsWriteAuthorizationPayload = { + recordId: string; + contextId?: string; + descriptorCid: string; + attestationCid?: string; + encryptionCid?: string; +}; + +type DwnMessage = { + message: any; + data?: Blob; +} + +const dwnMessageCreators = { + [DwnInterfaceName.Events + DwnMethodName.Get] : EventsGet, + [DwnInterfaceName.Messages + DwnMethodName.Get] : MessagesGet, + [DwnInterfaceName.Records + DwnMethodName.Read] : RecordsRead, + [DwnInterfaceName.Records + DwnMethodName.Query] : RecordsQuery, + [DwnInterfaceName.Records + DwnMethodName.Write] : RecordsWrite, + [DwnInterfaceName.Records + DwnMethodName.Delete] : RecordsDelete, + [DwnInterfaceName.Protocols + DwnMethodName.Query] : ProtocolsQuery, + [DwnInterfaceName.Protocols + DwnMethodName.Configure] : ProtocolsConfigure, +}; + +export type DwnManagerOptions = { + agent?: Web5ManagedAgent; + didResolver?: DidResolver; + dwn: Dwn; +} + +export class DwnManager { + /** + * Holds the instance of a `Web5ManagedAgent` that represents the current + * execution context for the `KeyManager`. This agent is utilized + * to interact with other Web5 agent components. It's vital + * to ensure this instance is set to correctly contextualize + * operations within the broader Web5 agent framework. + */ + private _agent?: Web5ManagedAgent; + private _dwn: Dwn; + + constructor(options: DwnManagerOptions) { + this._agent = options.agent; + this._dwn = options.dwn; + } + + /** + * Retrieves the `Web5ManagedAgent` execution context. + * If the `agent` instance proprety is undefined, it will throw an error. + * + * @returns The `Web5ManagedAgent` instance that represents the current execution + * context. + * + * @throws Will throw an error if the `agent` instance property is undefined. + */ + get agent(): Web5ManagedAgent { + if (this._agent === undefined) { + throw new Error('DidManager: Unable to determine agent execution context.'); + } + + return this._agent; + } + + set agent(agent: Web5ManagedAgent) { + this._agent = agent; + } + + get dwn(): Dwn { + return this._dwn; + } + + public static async create(options?: Partial) { + options ??= { }; + + if (options.dwn === undefined) { + const messageStore = new MessageStoreLevel(); + const dataStore = new DataStoreLevel(); + const eventLog = new EventLogLevel(); + + options.dwn = await Dwn.create({ + dataStore, + // @ts-expect-error because `dwn-sdk-js` has a incompatible DidResolver declaration. + didResolver: options.didResolver, + eventLog, + messageStore, + }); + } + + return new DwnManager(options as DwnManagerOptions); + } + + public async processRequest(request: ProcessDwnRequest): Promise { + const { message, dataStream } = await this.constructDwnMessage({ request }); + + let reply: UnionMessageReply; + if (request.store !== false) { + reply = await this._dwn.processMessage(request.target, message, dataStream); + } else { + reply = { status: { code: 202, detail: 'Accepted' }}; + } + + return { + reply, + message : message, + messageCid : await Message.getCid(message) + }; + } + + public async sendRequest(request: SendDwnRequest): Promise { + const dwnRpcRequest: Partial = { targetDid: request.target }; + let messageData: Blob | Readable | ReadableStream | undefined; + + if ('messageCid' in request) { + const { message, data } = await this.getDwnMessage({ + author : request.author, + messageCid : request.messageCid, + messageType : request.messageType + }); + dwnRpcRequest.message = message; + messageData = data; + + } else { + const { message } = await this.constructDwnMessage({ request }); + dwnRpcRequest.message = message; + messageData = request.dataStream; + } + + if (messageData) { + dwnRpcRequest.data = messageData; + } + + const { didDocument, didResolutionMetadata } = await this.agent.didResolver.resolve(request.target); + if (!didDocument) { + const errorCode = `${didResolutionMetadata?.error}: ` ?? ''; + const defaultMessage = `Unable to resolve target DID: ${request.target}`; + const errorMessage = didResolutionMetadata?.errorMessage ?? defaultMessage; + throw new Error(`DwnManager: ${errorCode}${errorMessage}`); + } + + const [ service ] = didUtils.getServices({ didDocument, id: '#dwn' }); + if (!service) { + throw new Error(`DwnManager: DID Document of '${request.target}' has no service endpoints with ID '#dwn'`); + } + + if (!didUtils.isDwnServiceEndpoint(service.serviceEndpoint)) { + throw new Error(`DwnManager: Malformed '#dwn' service endpoint. Expected array of node addresses.`); + } + const dwnEndpointUrls = service.serviceEndpoint.nodes; + + let dwnReply; + let errorMessages = []; + + // try sending to author's publicly addressable dwn's until first request succeeds. + for (let dwnUrl of dwnEndpointUrls) { + dwnRpcRequest.dwnUrl = dwnUrl; + + try { + dwnReply = await this.agent.rpcClient.sendDwnRequest(dwnRpcRequest as DwnRpcRequest); + break; + } catch(error: unknown) { + const message = (error instanceof Error) ? error.message : 'Uknown error'; + errorMessages.push({ url: dwnUrl, message }); + } + } + + if (!dwnReply) { + throw new Error(JSON.stringify(errorMessages)); + } + + return { + message : dwnRpcRequest.message, + messageCid : await Message.getCid(dwnRpcRequest.message), + reply : dwnReply, + }; + } + + private async constructDwnMessage(options: { + request: ProcessDwnRequest + }) { + const { request } = options; + + let readableStream: Readable | undefined; + + // TODO: Consider refactoring to move data transformations imposed by fetch() limitations to the HTTP transport-related methods. + if (request.messageType === 'RecordsWrite') { + const messageOptions = request.messageOptions as RecordsWriteOptions; + + if (request.dataStream && !messageOptions.data) { + const { dataStream } = request; + let isomorphicNodeReadable: Readable; + + if (dataStream instanceof Blob) { + isomorphicNodeReadable = blobToIsomorphicNodeReadable(dataStream); + readableStream = blobToIsomorphicNodeReadable(dataStream); + + } else if (dataStream instanceof ReadableStream) { + const [ forCid, forProcessMessage ] = dataStream.tee(); + isomorphicNodeReadable = webReadableToIsomorphicNodeReadable(forCid); + readableStream = webReadableToIsomorphicNodeReadable(forProcessMessage); + } + + // @ts-ignore + messageOptions.dataCid = await Cid.computeDagPbCidFromStream(isomorphicNodeReadable); + // @ts-ignore + messageOptions.dataSize ??= isomorphicNodeReadable['bytesRead']; + } + } + + const signingKeyId = await this.getAuthorSigningKeyId({ did: request.author }); + + // ! TODO: Remove this once DWN SDK supports external signers. + const dwnSignatureInput = this.getTempSignatureInput({ signingKeyId }); + + // TODO: Figure out how to narrow this type. + const messageCreateInput = { + ...request.messageOptions, + authorizationSignatureInput: dwnSignatureInput + }; + + const messageCreator = dwnMessageCreators[request.messageType]; + + // ! TODO: START Remove this monkey patch (MP) as soon as the DWN SDK supports external signers. + // MP Step 1: Store the original methods. + const originalCreateAuthorization = RecordsWrite.createAuthorization; + const originalSignAsAuthorization = Message.signAsAuthorization; + // MP Step 2: Replace the methods. + RecordsWrite.createAuthorization = ( + recordId: string, + contextId: string | undefined, + descriptorCid: string, + attestation: GeneralJws | undefined, + encryption: EncryptionProperty | undefined, + ) => this.createAuthorization(recordId, contextId, descriptorCid, attestation, encryption, signingKeyId); + Message.signAsAuthorization = ( + descriptor: GenericMessage['descriptor'], + signatureInput: SignatureInput, + permissionsGrantId?: string, + ) => this.signAsAuthorization(descriptor, signingKeyId, permissionsGrantId); + // MP Step 3: Call the method that required monkey patching. + const dwnMessage = await messageCreator.create(messageCreateInput as any); + // MP Step 4: Restore the original methods. + RecordsWrite.createAuthorization = originalCreateAuthorization; + Message.signAsAuthorization = originalSignAsAuthorization; + // ! TODO: END Remove this monkey patch (MP) as soon as the DWN SDK supports external signers. + + return { message: dwnMessage.toJSON(), dataStream: readableStream }; + } + + private async getAuthorSigningKeyId(options: { + did: string + }): Promise { + const { did } = options; + + // Get the agent instance. + const agent = this.agent; + + // Get the method-specific default signing key. + const signingKeyId = await agent.didManager.getDefaultSigningKey({ did }); + + if (!signingKeyId) { + throw new Error (`DwnManager: Unable to determine signing key for author: '${did}'`); + } + + return signingKeyId; + } + + private async getDwnMessage(options: { + author: string, + messageType: string, + messageCid: string + }): Promise { + const { author, messageType, messageCid } = options; + + const signingKeyId = await this.getAuthorSigningKeyId({ did: author }); + + // ! TODO: START Remove this monkey patch (MP) as soon as the DWN SDK supports external signers. + const dwnSignatureInput = this.getTempSignatureInput({ signingKeyId }); + // MP Step 1: Store the original methods. + const originalSignAsAuthorization = Message.signAsAuthorization; + // MP Step 2: Replace the methods. + Message.signAsAuthorization = ( + descriptor: GenericMessage['descriptor'], + signatureInput: SignatureInput, + permissionsGrantId?: string, + ) => this.signAsAuthorization(descriptor, signingKeyId, permissionsGrantId); + // MP Step 3: Call the method that required monkey patching. + const messagesGet = await MessagesGet.create({ + authorizationSignatureInput : dwnSignatureInput, + messageCids : [messageCid] + }); + // MP Step 4: Restore the original methods. + Message.signAsAuthorization = originalSignAsAuthorization; + // ! TODO: END Remove this monkey patch (MP) as soon as the DWN SDK supports external signers. + + const result: MessagesGetReply = await this._dwn.processMessage(author, messagesGet.toJSON()); + + if (!(result.messages && result.messages.length === 1)) { + throw new Error('TODO: figure out error message'); + } + + const [ messageEntry ] = result.messages; + + let { message } = messageEntry; + if (!message) { + throw new Error('TODO: message not found'); + } + + let dwnMessage: DwnMessage = { message }; + + /** If the message is a RecordsWrite, either data will be present, OR + * we have to fetch it using a RecordsRead. */ + if (messageType === 'RecordsWrite') { + const { encodedData } = messageEntry; + const writeMessage = message as RecordsWriteMessage; + + if (encodedData) { + const dataBytes = Convert.base64Url(encodedData).toUint8Array(); + dwnMessage.data = new Blob([dataBytes]); + } else { + const recordsRead = await RecordsRead.create({ + authorizationSignatureInput : dwnSignatureInput, + recordId : writeMessage.recordId + }); + + const reply = await this._dwn.processMessage(author, recordsRead.toJSON()) as RecordsReadReply; + + if (reply.status.code >= 400) { + const { status: { code, detail } } = reply; + throw new Error(`(${code}) Failed to read data associated with record ${writeMessage.recordId}. ${detail}}`); + } else if (reply.record) { + const dataBytes = await DataStream.toBytes(reply.record.data); + dwnMessage.data = new Blob([dataBytes]); + } + } + } + + return dwnMessage; + } + + /** + * The following methods are a temporary workaround that should be removed + * once DWN SDK implements support for an external signer. + * + * - createAuthorization() + * - createJws() + * - getTempSignatureInput() + * - signAsAuthorization() + */ + + private async createAuthorization( + recordId: string, + contextId: string | undefined, + descriptorCid: string, + attestation: GeneralJws | undefined, + encryption: EncryptionProperty | undefined, + signingKeyId: string, + ): Promise { + const authorizationPayload: RecordsWriteAuthorizationPayload = { + recordId, + descriptorCid + }; + + const attestationCid = attestation ? await Cid.computeCid(attestation) : undefined; + const encryptionCid = encryption ? await Cid.computeCid(encryption) : undefined; + + if (contextId !== undefined) { authorizationPayload.contextId = contextId; } // assign `contextId` only if it is defined + if (attestationCid !== undefined) { authorizationPayload.attestationCid = attestationCid; } // assign `attestationCid` only if it is defined + if (encryptionCid !== undefined) { authorizationPayload.encryptionCid = encryptionCid; } // assign `encryptionCid` only if it is defined + + const authorizationPayloadU8A = Convert.object(authorizationPayload).toUint8Array(); + + const jws = await this.createJws(authorizationPayloadU8A, [signingKeyId]); + + return jws; + } + + private async createJws(payload: Uint8Array, signingKeyIds: string[] = []): Promise { + // Get the agent instance. + const agent = this.agent; + + // Begin constructing a JWS. + const jws: GeneralJws = { + payload : Convert.uint8Array(payload).toBase64Url(), + signatures : [] + }; + + for (const signingKeyId of signingKeyIds) { + + /** DID keys stored in KeyManager use the canonicalId as an alias, so + * normalize the signing key ID before attempting to retrieve the key. **/ + const parsedDid = didUtils.parseDid({ didUrl: signingKeyId }); + if (!parsedDid) throw new Error(`DidIonMethod: Unable to parse DID: ${signingKeyId}`); + const normalizedDid = parsedDid.did.split(':', 3).join(':'); + const normalizedSigningKeyId = `${normalizedDid}#${parsedDid.fragment}`; + + // Attempt to retrieve the signing key. + const signingKey = await agent.keyManager.getKey({ keyRef: normalizedSigningKeyId }); + + if (!isManagedKeyPair(signingKey)) { + throw new Error (`DwnManager: Signing key not found for author: '${normalizedDid}'`); + } + + // Get the JWS alg parameter given the key pair. + const { alg } = Jose.webCryptoToJose(signingKey.privateKey.algorithm); + + // Construct the JWS protected header. + const protectedHeader = Convert.object( + { alg, kid: signingKeyId } + ).toBase64Url(); + + // Concatenate the dot-separated header and payload and convert to bytes. + const headerAndPayload = Convert.string( + `${protectedHeader}.${jws.payload}` + ).toUint8Array(); + + // Sign the JWS. + const signatureBytes = await agent.keyManager.sign({ + algorithm : signingKey.privateKey.algorithm, + data : headerAndPayload, + keyRef : signingKey.privateKey.id + }); + const signature = Convert.uint8Array(signatureBytes).toBase64Url(); + + jws.signatures.push({ protected: protectedHeader, signature }); + } + + return jws; + } + + private getTempSignatureInput({ signingKeyId }: { signingKeyId: string }): SignatureInput { + const privateJwk: DwnPrivateKeyJwk = { + alg : 'placeholder', + d : 'placeholder', + crv : 'Ed25519', + kty : 'placeholder', + x : 'placeholder' + }; + + const protectedHeader = { + alg : 'placeholder', + kid : signingKeyId + }; + + const dwnSignatureInput: SignatureInput = { privateJwk, protectedHeader }; + + return dwnSignatureInput; + } + + private async signAsAuthorization( + descriptor: GenericMessage['descriptor'], + signingKeyId: string, + permissionsGrantId?: string + ): Promise { + const descriptorCid = await Cid.computeCid(descriptor); + + const authorizationPayload = { descriptorCid, permissionsGrantId }; + removeUndefinedProperties(authorizationPayload); + + const authorizationPayloadU8A = Convert.object(authorizationPayload).toUint8Array(); + + const jws = await this.createJws(authorizationPayloadU8A, [signingKeyId]); + + return jws; + } +} \ No newline at end of file diff --git a/packages/agent/src/identity-manager.ts b/packages/agent/src/identity-manager.ts new file mode 100644 index 000000000..fef975527 --- /dev/null +++ b/packages/agent/src/identity-manager.ts @@ -0,0 +1,165 @@ +import type { PortableDid } from '@web5/dids'; +import type { Web5ManagedAgent } from './types/agent.js'; +import type { CreateDidMethodOptions, ManagedDid } from './did-manager.js'; +import type { ManagedIdentityStore } from './store-managed-identity.js'; + +import { IdentityStoreMemory } from './store-managed-identity.js'; + +type CreateWithDid = Required> + & Pick + +type CreateWithDidMethod = Pick & { + didMethod: M; + didOptions?: CreateDidMethodOptions[M]; +} + +type DidMethod = keyof CreateDidMethodOptions; + +export type CreateIdentityOptions = { + did?: PortableDid; + didMethod?: any; + didOptions?: any; + context?: string; + kms?: string; + name: string; +} + +export type IdentityManagerOptions = { + agent?: Web5ManagedAgent; + store?: ManagedIdentityStore; +} + +export type ImportIdentityOptions = { + context?: string; + did?: PortableDid; + identity: ManagedIdentity; + kms?: string; +} + +export interface ManagedIdentity { + did: string; + name: string; +} + +export class IdentityManager { + /** + * Holds the instance of a `Web5ManagedAgent` that represents the current + * execution context for the `KeyManager`. This agent is utilized + * to interact with other Web5 agent components. It's vital + * to ensure this instance is set to correctly contextualize + * operations within the broader Web5 agent framework. + */ + private _agent?: Web5ManagedAgent; + private _store: ManagedIdentityStore; + + constructor(options?: IdentityManagerOptions) { + const { agent, store } = options ?? {}; + this._agent = agent; + this._store = store ?? new IdentityStoreMemory(); + } + + /** + * Retrieves the `Web5ManagedAgent` execution context. + * If the `agent` instance proprety is undefined, it will throw an error. + * + * @returns The `Web5ManagedAgent` instance that represents the current execution + * context. + * + * @throws Will throw an error if the `agent` instance property is undefined. + */ + get agent(): Web5ManagedAgent { + if (this._agent === undefined) { + throw new Error('IdentityManager: Unable to determine agent execution context.'); + } + + return this._agent; + } + + set agent(agent: Web5ManagedAgent) { + this._agent = agent; + } + + async create(options: CreateWithDidMethod): Promise; + async create(options: CreateWithDid): Promise; + async create(options: CreateIdentityOptions): Promise { + let { context, did, didMethod, didOptions, kms, name } = options; + + if (!(didMethod ? !did : did)) { + throw new Error(`Either 'did' or 'didMethod' must be defined, but not both.`); + } + + let managedDid: ManagedDid | undefined; + + // Get the agent instance. + const agent = this.agent; + + if (didMethod) { + // Create new DID and generate key set. + managedDid = await agent.didManager.create({ method: didMethod, context, kms, ...didOptions }); + + } else if (did) { + // Import given DID and key set. + managedDid = await agent.didManager.import({ did, context, kms }); + } + + if (managedDid === undefined) { + throw new Error('IdentityManager: Unable to generate or import DID.'); + } + + // Create a ManagedIdentity. + const identity: ManagedIdentity = { + did : managedDid.did, + name : name + }; + + /** If context is undefined, then the Identity will be stored under the + * tenant of the created DID. Otherwise, the Identity records will + * be stored under the tenant of the specified context. */ + context ??= identity.did; + + // Store the ManagedIdentity in the store. + await this._store.importIdentity({ identity, agent, context }); + + return identity; + } + + async get(options: { + did: string, + context?: string + }): Promise { + const { context, did } = options; + + const identity = this._store.getIdentity({ did, agent: this.agent, context }); + + return identity; + } + + async import(options: ImportIdentityOptions): Promise { + let { context, did, identity, kms } = options; + + // Get the agent instance. + const agent = this.agent; + + // If provided, import the given DID and key set. + if (did) { + await agent.didManager.import({ did, context, kms }); + } + + /** If context is undefined, then the Identity will be stored under the + * tenant of the imported DID. Otherwise, the Identity record will + * be stored under the tenant of the specified context. */ + context ??= identity.did; + + // Store the ManagedIdentity in the store. + await this._store.importIdentity({ identity, agent, context }); + + return identity; + } + + async list(options?: { context?: string }): Promise { + const { context } = options ?? {}; + const identities = this._store.listIdentities({ agent: this.agent, context }); + + return identities; + } +} \ No newline at end of file diff --git a/packages/agent/src/index.ts b/packages/agent/src/index.ts new file mode 100644 index 000000000..3b38d6cdb --- /dev/null +++ b/packages/agent/src/index.ts @@ -0,0 +1,17 @@ +export type * from './types/agent.js'; +export type * from './types/managed-key.js'; + +export * from './app-data-store.js'; +export * from './did-manager.js'; +export * from './dwn-manager.js'; +export * from './identity-manager.js'; +export * from './json-rpc.js'; +export * from './key-manager.js'; +export * from './kms-local.js'; +export * from './rpc-client.js'; +export * from './store-managed-did.js'; +export * from './store-managed-key.js'; +export * from './store-managed-identity.js'; +export * from './utils.js'; + +export * from './test-managed-agent.js'; \ No newline at end of file diff --git a/packages/web5-agent/src/json-rpc.ts b/packages/agent/src/json-rpc.ts similarity index 100% rename from packages/web5-agent/src/json-rpc.ts rename to packages/agent/src/json-rpc.ts diff --git a/packages/agent/src/key-manager.ts b/packages/agent/src/key-manager.ts new file mode 100644 index 000000000..f01e2de1b --- /dev/null +++ b/packages/agent/src/key-manager.ts @@ -0,0 +1,302 @@ +import type { + ManagedKey, + PortableKey, + SignOptions, + CryptoManager, + VerifyOptions, + DecryptOptions, + EncryptOptions, + ManagedKeyPair, + GenerateKeyType, + ManagedKeyStore, + ImportKeyOptions, + UpdateKeyOptions, + DeriveBitsOptions, + PortableKeyPair, + GenerateKeyOptions, + KeyManagementSystem, + GenerateKeyOptionTypes, +} from './types/managed-key.js'; + +import { Web5ManagedAgent } from './types/agent.js'; +import { LocalKms } from './kms-local.js'; +import { isManagedKey, isManagedKeyPair } from './utils.js'; +import { KeyStoreMemory, PrivateKeyStoreMemory } from './store-managed-key.js'; + +export type KmsMap = { + [name: string]: KeyManagementSystem; +} + +export type KeyManagerOptions = { + agent?: Web5ManagedAgent; + kms?: KmsMap; + store?: ManagedKeyStore; +} + +/** + * KeyManager + * + * This class orchestrates implementations of {@link KeyManagementSystem}, + * using a ManagedKeyStore to remember the link between a key reference, + * its metadata, and the respective key management system that provides the + * actual cryptographic capabilities. + * + * The methods of this class are used automatically by other Web5 Agent + * components to perform their required cryptographic operations using + * the managed keys. + * + * @public + */ +export class KeyManager implements CryptoManager { + /** + * Holds the instance of a `Web5ManagedAgent` that represents the current + * execution context for the `KeyManager`. This agent is utilized + * to interact with other Web5 agent components. It's vital + * to ensure this instance is set to correctly contextualize + * operations within the broader Web5 agent framework. + */ + private _agent?: Web5ManagedAgent; + // ManagedKey to use for signing DWN messages with DWN-backed store. + private _defaultSigningKey?: ManagedKeyPair; + // KMS name to KeyManagementSystem mapping. + private _kms: Map; + // Store for managed key metadata. + private _store: ManagedKeyStore; + + constructor(options?: KeyManagerOptions) { + let { agent, kms, store } = options ?? { }; + this._agent = agent; + this._store = store ?? new KeyStoreMemory(); + + kms ??= this.useMemoryKms(); + this._kms = new Map(Object.entries(kms)) ; + } + + /** + * Retrieves the `Web5ManagedAgent` execution context. + * If the `agent` instance proprety is undefined, it will throw an error. + * + * @returns The `Web5ManagedAgent` instance that represents the current execution + * context. + * + * @throws Will throw an error if the `agent` instance property is undefined. + */ + get agent(): Web5ManagedAgent { + if (this._agent === undefined) { + throw new Error('KeyManager: Unable to determine agent execution context.'); + } + + return this._agent; + } + + set agent(agent: Web5ManagedAgent) { + this._agent = agent; + this._kms.forEach((kms) => { + kms.agent = agent; + }); + } + + async decrypt(options: DecryptOptions): Promise { + let { keyRef, ...decryptOptions } = options; + + const key = await this.getKey({ keyRef }); + + if (!isManagedKey(key)) { + throw new Error(`Key not found: '${keyRef}'`); + } + + const kmsName = key.kms; + const kms = this.getKms(kmsName); + + const keyId = key.id; + const plaintext = await kms.decrypt({ keyRef: keyId, ...decryptOptions }); + + return plaintext; + } + + async deriveBits(options: DeriveBitsOptions): Promise { + const { baseKeyRef, ...deriveBitsOptions } = options; + + const ownKeyPair = await this.getKey({ keyRef: baseKeyRef }); + + if (!isManagedKeyPair(ownKeyPair)) { + throw new Error(`Key not found: '${baseKeyRef}'`); + } + + const kmsName = ownKeyPair.privateKey.kms; + const kms = this.getKms(kmsName); + + const ownKeyId = ownKeyPair.privateKey.id; + const sharedSecret = kms.deriveBits({ baseKeyRef: ownKeyId, ...deriveBitsOptions }); + + return sharedSecret; + } + + async encrypt(options: EncryptOptions): Promise { + let { keyRef, ...encryptOptions } = options; + + const key = await this.getKey({ keyRef }); + + if (!isManagedKey(key)) { + throw new Error(`Key not found: '${keyRef}'`); + } + + const kmsName = key.kms; + const kms = this.getKms(kmsName); + + const keyId = key.id; + const ciphertext = await kms.encrypt({ keyRef: keyId, ...encryptOptions }); + + return ciphertext; + } + + async generateKey(options: GenerateKeyOptions & { kms?: string }): Promise> { + const { kms: kmsName, ...generateKeyOptions } = options; + + const kms = this.getKms(kmsName); + + const keyOrKeyPair = await kms.generateKey(generateKeyOptions); + + // Store the ManagedKey or ManagedKeyPair in KeyManager's key store. + await this._store.importKey({ key: keyOrKeyPair, agent: this.agent }); + + return keyOrKeyPair; + } + + async getKey({ keyRef }: { keyRef: string }): Promise { + let keyOrKeyPair: ManagedKey | ManagedKeyPair | undefined; + + // First, check to see if the requested key is the default signing key. + const defaultSigningKeyId = this._defaultSigningKey?.publicKey.id; + const defaultSigningKeyAlias = this._defaultSigningKey?.publicKey.alias; + if (keyRef === defaultSigningKeyId || keyRef === defaultSigningKeyAlias) { + return this._defaultSigningKey; + } + + // Try to get key by ID. + keyOrKeyPair = await this._store.getKey({ id: keyRef, agent: this.agent }); + if (keyOrKeyPair) return keyOrKeyPair; + + // Try to find key by alias. + keyOrKeyPair = await this._store.findKey({ alias: keyRef, agent: this.agent }); + if (keyOrKeyPair) return keyOrKeyPair; + + return undefined; + } + + async importKey(options: PortableKeyPair): Promise; + async importKey(options: PortableKey): Promise; + async importKey(options: ImportKeyOptions): Promise { + const kmsName = ('privateKey' in options) ? options.privateKey.kms : options.kms; + const kms = this.getKms(kmsName); + + // Store the ManagedKey or ManagedKeyPair in the given KMS. + const importedKeyOrKeyPair = await kms.importKey(options); + + // Store the ManagedKey or ManagedKeyPair in KeyManager's key store. + await this._store.importKey({ key: importedKeyOrKeyPair, agent: this.agent }); + + return importedKeyOrKeyPair; + } + + listKms() { + return Array.from(this._kms.keys()); + } + + async setDefaultSigningKey({ key }: { key: PortableKeyPair }) { + const kmsName = key.privateKey.kms; + const kms = this.getKms(kmsName); + + // Store the default signing key pair in an in-memory KMS. + const importedDefaultSigningKey = await kms.importKey(key); + + // Set the in-memory key to be KeyManager's default signing key. + this._defaultSigningKey = importedDefaultSigningKey; + } + + async sign(options: SignOptions): Promise { + const { keyRef, ...signOptions } = options; + + const keyPair = await this.getKey({ keyRef }); + + if (!isManagedKeyPair(keyPair)) { + throw new Error(`Key not found: '${keyRef}'`); + } + + const kmsName = keyPair.privateKey.kms; + const kms = this.getKms(kmsName); + + const keyId = keyPair.privateKey.id; + const signature = await kms.sign({ keyRef: keyId, ...signOptions }); + + return signature; + } + + async updateKey(options: UpdateKeyOptions): Promise { + const { keyRef, alias, metadata } = options; + + const keyOrKeyPair = await this.getKey({ keyRef }); + + if (!keyOrKeyPair) { + throw new Error(`Key not found: '${keyRef}'`); + } + + const { id: keyId, kms: kmsName } = (isManagedKeyPair(keyOrKeyPair)) + ? { ...keyOrKeyPair.publicKey } + : { ...keyOrKeyPair }; + + // Update the ManagedKey or ManagedKeyPair in the given KMS. + const kms = this.getKms(kmsName); + const kmsUpdated = await kms.updateKey(options); + + if (!kmsUpdated) return false; + + // Since the KMS was successfully updated, update the KeyManager store. + return await this._store.updateKey({ id: keyId, alias, metadata, agent: this.agent }); + } + + async verify(options: VerifyOptions): Promise { + let { keyRef, ...verifyOptions } = options; + + const keyPair = await this.getKey({ keyRef }); + + if (!isManagedKeyPair(keyPair)) { + throw new Error(`Key not found: '${keyRef}'`); + } + + const kmsName = keyPair.publicKey.kms; + const kms = this.getKms(kmsName); + + const keyId = keyPair.publicKey.id; + const isValid = await kms.verify({ keyRef: keyId, ...verifyOptions }); + + return isValid; + } + + private getKms(name: string | undefined): KeyManagementSystem { + // For developer convenience, if a KMS name isn't specified and KeyManager only has + // one KMS defined, use it. Otherwise, an exception will be thrown. + name ??= (this._kms.size === 1) ? this._kms.keys().next().value : ''; + + const kms = this._kms.get(name!); + + if (!kms) { + throw Error(`Unknown key management system: '${name}'`); + } + + return kms; + } + + private useMemoryKms(): KmsMap { + // Instantiate in-memory store for KMS key metadata and public keys. + const keyStore = new KeyStoreMemory(); + + // Instantiate in-memory store for KMS private keys. + const privateKeyStore = new PrivateKeyStoreMemory(); + + // Instantiate local KMS using in-memory key stores. + const kms = new LocalKms({ kmsName: 'memory', keyStore, privateKeyStore }); + + return { memory: kms }; + } +} \ No newline at end of file diff --git a/packages/crypto/src/kms-local/kms-local.ts b/packages/agent/src/kms-local.ts similarity index 54% rename from packages/crypto/src/kms-local/kms-local.ts rename to packages/agent/src/kms-local.ts index db1ac61d3..fda11b348 100644 --- a/packages/crypto/src/kms-local/kms-local.ts +++ b/packages/agent/src/kms-local.ts @@ -1,59 +1,120 @@ -import type { RequireOnly } from '@tbd54566975/common'; -import type { AlgorithmImplementation, AlgorithmImplementations } from './supported-algorithms.js'; -import type { +import type { Web5Crypto } from '@web5/crypto'; +import type { RequireOnly } from '@web5/common'; + +import { + EcdhAlgorithm, + EcdsaAlgorithm, + EdDsaAlgorithm, + AesCtrAlgorithm, + CryptoAlgorithm, + isCryptoKeyPair, + checkRequiredProperty, +} from '@web5/crypto'; + +import { ManagedKey, - Web5Crypto, + PortableKey, SignOptions, VerifyOptions, - ImportableKey, DecryptOptions, EncryptOptions, ManagedKeyPair, + ManagedKeyStore, GenerateKeyType, + PortableKeyPair, ImportKeyOptions, + UpdateKeyOptions, DeriveBitsOptions, - ImportableKeyPair, + ManagedPrivateKey, GenerateKeyOptions, KeyManagementSystem, GenerateKeyOptionTypes, -} from '../types/index.js'; +} from './types/managed-key.js'; -import { Convert } from '@tbd54566975/common'; +import { isManagedKey, isManagedKeyPair } from './utils.js'; +import { KeyStoreMemory, PrivateKeyStoreMemory } from './store-managed-key.js'; +import { Web5ManagedAgent } from './types/agent.js'; -import { CryptoAlgorithm } from '../algorithms-api/index.js'; -import { defaultAlgorithms } from './supported-algorithms.js'; -import { KmsKeyStore, KmsPrivateKeyStore } from './key-store.js'; -import { checkRequiredProperty, isCryptoKeyPair, isManagedKey, isManagedKeyPair } from '../utils-new.js'; +export type AlgorithmImplementation = typeof CryptoAlgorithm & { new(): CryptoAlgorithm; }; +export type AlgorithmImplementations = { + [algorithmName: string]: AlgorithmImplementation; +}; export type KmsOptions = { + agent?: Web5ManagedAgent; cryptoAlgorithms?: AlgorithmImplementations; + keyStore?: ManagedKeyStore; + kmsName: string; + privateKeyStore?: ManagedKeyStore; } -export class LocalKms implements KeyManagementSystem { - private name: string; - private keyStore: KmsKeyStore; - private privateKeyStore: KmsPrivateKeyStore; - private supportedAlgorithms: Map = new Map(); +// Map key operations to algorithm specs to implementations. +export const defaultAlgorithms: AlgorithmImplementations = { + 'AES-CTR' : AesCtrAlgorithm, + ECDH : EcdhAlgorithm, + ECDSA : EcdsaAlgorithm, + EdDSA : EdDsaAlgorithm, +}; - constructor(kmsName: string, keyStore: KmsKeyStore, privateKeyStore: KmsPrivateKeyStore, options: KmsOptions = {}) { - this.name = kmsName; - this.keyStore = keyStore; - this.privateKeyStore = privateKeyStore; +export class LocalKms implements KeyManagementSystem { + /** + * Holds the instance of a `Web5ManagedAgent` that represents the current + * execution context for the `KeyManager`. This agent is utilized + * to interact with other Web5 agent components. It's vital + * to ensure this instance is set to correctly contextualize + * operations within the broader Web5 agent framework. + */ + private _agent?: Web5ManagedAgent; + private _name: string; + private _keyStore: ManagedKeyStore; + private _privateKeyStore: ManagedKeyStore; + private _supportedAlgorithms: Map = new Map(); + + constructor(options: KmsOptions) { + const { agent, kmsName, keyStore, privateKeyStore } = options; + this._agent = agent; + this._name = kmsName; + this._keyStore = keyStore ?? new KeyStoreMemory(); + this._privateKeyStore = privateKeyStore ?? new PrivateKeyStoreMemory(); // Merge the default and custom algorithms and register with the KMS. const cryptoAlgorithms = {...defaultAlgorithms, ...options.cryptoAlgorithms}; this.registerSupportedAlgorithms(cryptoAlgorithms); } - async decrypt(options: DecryptOptions): Promise { + /** + * Retrieves the `Web5ManagedAgent` execution context. + * If the `agent` instance proprety is undefined, it will throw an error. + * + * @returns The `Web5ManagedAgent` instance that represents the current execution + * context. + * + * @throws Will throw an error if the `agent` instance property is undefined. + */ + get agent(): Web5ManagedAgent { + if (this._agent === undefined) { + throw new Error('KeyManager: Unable to determine agent execution context.'); + } + + return this._agent; + } + + set agent(agent: Web5ManagedAgent) { + this._agent = agent; + } + + async decrypt(options: DecryptOptions): Promise { const { algorithm, data, keyRef } = options; // Retrieve the ManagedKey from the KMS key metadata store. const key = await this.getKey({ keyRef }); if (isManagedKey(key)) { - const privateManagedKey = await this.privateKeyStore.getKey({ id: key.id }); + const privateManagedKey = await this._privateKeyStore.getKey({ + id : key.id, + agent : this.agent + }); if (privateManagedKey !== undefined) { // Construct a CryptoKey object from the key metadata and private key material. @@ -70,14 +131,17 @@ export class LocalKms implements KeyManagementSystem { throw new Error(`Operation failed: 'decrypt'. Key not found: ${keyRef}`); } - async deriveBits(options: DeriveBitsOptions): Promise { + async deriveBits(options: DeriveBitsOptions): Promise { let { algorithm, baseKeyRef, length } = options; // Retrieve the ManagedKeyPair from the KMS key metadata store. const ownKeyPair = await this.getKey({ keyRef: baseKeyRef }); if (isManagedKeyPair(ownKeyPair)) { - const privateManagedKey = await this.privateKeyStore.getKey({ id: ownKeyPair.privateKey.id }); + const privateManagedKey = await this._privateKeyStore.getKey({ + id : ownKeyPair.privateKey.id, + agent : this.agent + }); if (privateManagedKey !== undefined) { // Construct a CryptoKey object from the key metadata and private key material. @@ -94,14 +158,17 @@ export class LocalKms implements KeyManagementSystem { throw new Error(`Operation failed: 'deriveBits'. Key not found: ${baseKeyRef}`); } - async encrypt(options: EncryptOptions): Promise { + async encrypt(options: EncryptOptions): Promise { const { algorithm, data, keyRef } = options; // Retrieve the ManagedKey from the KMS key metadata store. const key = await this.getKey({ keyRef }); if (isManagedKey(key)) { - const privateManagedKey = await this.privateKeyStore.getKey({ id: key.id }); + const privateManagedKey = await this._privateKeyStore.getKey({ + id : key.id, + agent : this.agent + }); if (privateManagedKey !== undefined) { // Construct a CryptoKey object from the key metadata and private key material. @@ -132,29 +199,37 @@ export class LocalKms implements KeyManagementSystem { let managedKeyOrKeyPair: GenerateKeyType; if (isCryptoKeyPair(cryptoKey)) { const privateKeyType = cryptoKey.privateKey.type as Web5Crypto.PrivateKeyType; - const id = await this.privateKeyStore.importKey({ key: { material: cryptoKey.privateKey.handle, type: privateKeyType} }); - const privateKey = this.toManagedKey({ ...cryptoKey.privateKey, id, alias, metadata }); - const publicKey = this.toManagedKey({ ...cryptoKey.publicKey, material: cryptoKey.publicKey.handle, id, alias, metadata }); - managedKeyOrKeyPair = { privateKey, publicKey } as GenerateKeyType; + const id = await this._privateKeyStore.importKey({ + key : { material: cryptoKey.privateKey.material, type: privateKeyType}, + agent : this.agent + }); + const managedKeyPair: ManagedKeyPair = { + privateKey : this.toManagedKey({ ...cryptoKey.privateKey, id, alias, metadata }), + publicKey : this.toManagedKey({ ...cryptoKey.publicKey, material: cryptoKey.publicKey.material, id, alias, metadata }) + }; + managedKeyOrKeyPair = managedKeyPair as GenerateKeyType; } else { const keyType = cryptoKey.type as Web5Crypto.PrivateKeyType; - const id = await this.privateKeyStore.importKey({ key: { material: cryptoKey.handle, type: keyType } }); + const id = await this._privateKeyStore.importKey({ + key : { material: cryptoKey.material, type: keyType }, + agent : this.agent + }); managedKeyOrKeyPair = this.toManagedKey({ ...cryptoKey, id, alias, metadata }) as GenerateKeyType; } // Store the ManagedKey or ManagedKeyPair in the KMS key store. - await this.keyStore.importKey({ key: managedKeyOrKeyPair }); + await this._keyStore.importKey({ key: managedKeyOrKeyPair, agent: this.agent }); return managedKeyOrKeyPair; } async getKey(options: { keyRef: string }): Promise { - const keyOrKeyPair = this.keyStore.getKey({ id: options.keyRef }); + const keyOrKeyPair = this._keyStore.getKey({ id: options.keyRef, agent: this.agent }); return keyOrKeyPair; } - async importKey(options: ImportableKeyPair): Promise; - async importKey(options: ImportableKey): Promise; + async importKey(options: PortableKeyPair): Promise; + async importKey(options: PortableKey): Promise; async importKey(options: ImportKeyOptions): Promise { if ('privateKey' in options) { @@ -164,14 +239,15 @@ export class LocalKms implements KeyManagementSystem { throw new Error(`Import failed due to private and public key mismatch`); if (!(privateKey.type === 'private' && publicKey.type === 'public')) throw new TypeError(`Out of range: '${privateKey.type}, ${publicKey.type}'. Must be 'private, public'`); - privateKey.material = Convert.bufferSource(privateKey.material).toArrayBuffer(); - publicKey.material = Convert.bufferSource(publicKey.material).toArrayBuffer(); - const id = await this.privateKeyStore.importKey({ key: { material: privateKey.material, type: privateKey.type } }); + const id = await this._privateKeyStore.importKey({ + key : { material: privateKey.material, type: privateKey.type }, + agent : this.agent + }); const managedKeyPair = { - privateKey : this.toManagedKey({ ...privateKey, material: undefined, id }), + privateKey : this.toManagedKey({ ...privateKey, id, material: undefined }), publicKey : this.toManagedKey({ ...publicKey, material: publicKey.material, id }) }; - await this.keyStore.importKey({ key: managedKeyPair }); + await this._keyStore.importKey({ key: managedKeyPair, agent: this.agent }); return managedKeyPair; } @@ -179,30 +255,33 @@ export class LocalKms implements KeyManagementSystem { switch (keyType) { case 'private': { // Asymmetric private key import. - let { material } = options; - material = Convert.bufferSource(material).toArrayBuffer(); - const id = await this.privateKeyStore.importKey({ key: { material, type: keyType } }); + const material = options.material; + const id = await this._privateKeyStore.importKey({ + key : { material, type: keyType }, + agent : this.agent + }); const privateManagedKey = this.toManagedKey({ ...options, material: undefined, id }); - await this.keyStore.importKey({ key: privateManagedKey }); + await this._keyStore.importKey({ key: privateManagedKey, agent: this.agent }); return privateManagedKey; } case 'public': { // Asymmetric public key import. - let { material } = options; - material = Convert.bufferSource(material).toArrayBuffer(); - const privateManagedKey = this.toManagedKey({ ...options, material, id: 'placeholder' }); - privateManagedKey.id = await this.keyStore.importKey({ key: privateManagedKey }); - return privateManagedKey; + const material = options.material; + const publicManagedKey = this.toManagedKey({ ...options, material, id: '' }); + publicManagedKey.id = await this._keyStore.importKey({ key: publicManagedKey, agent: this.agent }); + return publicManagedKey; } case 'secret': { // Symmetric secret key import. - let { material } = options; - material = Convert.bufferSource(material).toArrayBuffer(); - const id = await this.privateKeyStore.importKey({ key: { material, type: keyType } }); + const material = options.material; + const id = await this._privateKeyStore.importKey({ + key : { material, type: keyType }, + agent : this.agent + }); const secretManagedKey = this.toManagedKey({ ...options, material: undefined, id }); - await this.keyStore.importKey({ key: secretManagedKey }); + await this._keyStore.importKey({ key: secretManagedKey, agent: this.agent }); return secretManagedKey; } @@ -211,14 +290,17 @@ export class LocalKms implements KeyManagementSystem { } } - async sign(options: SignOptions): Promise { + async sign(options: SignOptions): Promise { const { algorithm, data, keyRef } = options; // Retrieve the ManagedKeyPair from the KMS key metadata store. const keyPair = await this.getKey({ keyRef }); if (isManagedKeyPair(keyPair)) { - const privateManagedKey = await this.privateKeyStore.getKey({ id: keyPair.privateKey.id }); + const privateManagedKey = await this._privateKeyStore.getKey({ + id : keyPair.privateKey.id, + agent : this.agent + }); if (privateManagedKey !== undefined) { // Construct a CryptoKey object from the key metadata and private key material. @@ -235,6 +317,23 @@ export class LocalKms implements KeyManagementSystem { throw new Error(`Operation failed: 'sign'. Key not found: ${keyRef}`); } + async updateKey(options: UpdateKeyOptions): Promise { + const { keyRef, alias, metadata } = options; + + const keyOrKeyPair = await this.getKey({ keyRef }); + + if (!keyOrKeyPair) { + throw new Error(`Key not found: '${keyRef}'`); + } + + const keyId = (isManagedKeyPair(keyOrKeyPair)) + ? keyOrKeyPair.publicKey.id + : keyOrKeyPair.id; + + // Update the KMS key metadata store. + return this._keyStore.updateKey({ id: keyId, alias, metadata, agent: this.agent }); + } + async verify(options: VerifyOptions): Promise { const { algorithm, data, keyRef, signature } = options; @@ -242,8 +341,14 @@ export class LocalKms implements KeyManagementSystem { const keyPair = await this.getKey({ keyRef }); if (isManagedKeyPair(keyPair)) { + if (keyPair.publicKey.material === undefined) { + throw new Error(`Required property missing: 'material'`); + } // Construct a CryptoKey object from the key metadata and private key material. - const publicCryptoKey = this.toCryptoKey({ ...keyPair.publicKey }); + const publicCryptoKey = this.toCryptoKey({ + ...keyPair.publicKey, + material: keyPair.publicKey.material + }); // Verify the signature and data. const cryptoAlgorithm = this.getAlgorithm(algorithm); @@ -257,7 +362,7 @@ export class LocalKms implements KeyManagementSystem { private getAlgorithm(algorithmIdentifier: Web5Crypto.AlgorithmIdentifier): CryptoAlgorithm { checkRequiredProperty({ property: 'name', inObject: algorithmIdentifier }); - const algorithm = this.supportedAlgorithms.get(algorithmIdentifier.name.toUpperCase()); + const algorithm = this._supportedAlgorithms.get(algorithmIdentifier.name.toUpperCase()); if (algorithm === undefined) { throw new Error(`The algorithm '${algorithmIdentifier.name}' is not supported`); @@ -270,19 +375,18 @@ export class LocalKms implements KeyManagementSystem { for (const [name, implementation] of Object.entries(cryptoAlgorithms)) { // Add the algorithm name and its implementation to the supported algorithms map, // upper-cased to allow for case-insensitive. - this.supportedAlgorithms.set(name.toUpperCase(), implementation); + this._supportedAlgorithms.set(name.toUpperCase(), implementation); } } - private toCryptoKey(managedKey: ManagedKey): Web5Crypto.CryptoKey { - if (!managedKey.material) { - throw new Error(`Required property missing: 'material'`); - } + private toCryptoKey(managedKey: + RequireOnly + ): Web5Crypto.CryptoKey { const cryptoKey: Web5Crypto.CryptoKey = { algorithm : managedKey.algorithm, extractable : managedKey.extractable, - handle : managedKey.material, + material : managedKey.material, type : managedKey.type, usages : managedKey.usages }; @@ -290,13 +394,13 @@ export class LocalKms implements KeyManagementSystem { return cryptoKey; } - private toManagedKey(options: Omit & RequireOnly): ManagedKey { + private toManagedKey(options: Omit & RequireOnly): ManagedKey { const managedKey: ManagedKey = { id : options.id, algorithm : options.algorithm, alias : options.alias, extractable : options.extractable, - kms : this.name, + kms : this._name, material : (options.type === 'public') ? options.material : undefined, metadata : options.metadata, state : 'Enabled', diff --git a/packages/agent/src/rpc-client.ts b/packages/agent/src/rpc-client.ts new file mode 100644 index 000000000..e3e9ade6a --- /dev/null +++ b/packages/agent/src/rpc-client.ts @@ -0,0 +1,110 @@ +import type { JsonRpcResponse } from './json-rpc.js'; +import type { DwnRpc, DwnRpcRequest, DwnRpcResponse } from './types/agent.js'; + +import { randomUuid } from '@web5/crypto'; +import { createJsonRpcRequest, parseJson } from './json-rpc.js'; + +/** + * Client used to communicate with Dwn Servers + */ +export class Web5RpcClient implements DwnRpc { + private transportClients: Map; + + constructor(clients: DwnRpc[] = []) { + this.transportClients = new Map(); + + // include http client as default. can be overwritten for 'http:' or 'https:' if instantiator provides + // their own. + clients = [new HttpDwnRpcClient(), ...clients]; + + for (let client of clients) { + for (let transportScheme of client.transportProtocols) { + this.transportClients.set(transportScheme, client); + } + } + } + + get transportProtocols(): string[] { + return Array.from(this.transportClients.keys()); + } + + sendDwnRequest(request: DwnRpcRequest): Promise { + // will throw if url is invalid + const url = new URL(request.dwnUrl); + + const transportClient = this.transportClients.get(url.protocol); + if (!transportClient) { + const error = new Error(`no ${url.protocol} transport client available`); + error.name = 'NO_TRANSPORT_CLIENT'; + + throw error; + } + + return transportClient.sendDwnRequest(request); + } +} + +// TODO: move to dwn-server repo. i wrote this here for expediency + +/** + * Http client that can be used to communicate with Dwn Servers + */ +class HttpDwnRpcClient implements DwnRpc { + get transportProtocols() { return ['http:', 'https:']; } + + async sendDwnRequest(request: DwnRpcRequest): Promise { + const requestId = randomUuid(); + const jsonRpcRequest = createJsonRpcRequest(requestId, 'dwn.processMessage', { + target : request.targetDid, + message : request.message + }); + + const fetchOpts = { + method : 'POST', + headers : { + 'dwn-request': JSON.stringify(jsonRpcRequest) + } + }; + + if (request.data) { + // @ts-expect-error TODO: REMOVE + fetchOpts.headers['content-type'] = 'application/octet-stream'; + // @ts-expect-error TODO: REMOVE + fetchOpts['body'] = request.data; + } + + const resp = await fetch(request.dwnUrl, fetchOpts); + let dwnRpcResponse: JsonRpcResponse; + + // check to see if response is in header first. if it is, that means the response is a ReadableStream + let dataStream; + const { headers } = resp; + if (headers.has('dwn-response')) { + // @ts-expect-error TODO: REMOVE + const jsonRpcResponse = parseJson(headers.get('dwn-response')) as JsonRpcResponse; + + if (jsonRpcResponse == null) { + throw new Error(`failed to parse json rpc response. dwn url: ${request.dwnUrl}`); + } + + dataStream = resp.body; + dwnRpcResponse = jsonRpcResponse; + } else { + // TODO: wonder if i need to try/catch this? + const responseBody = await resp.text(); + dwnRpcResponse = JSON.parse(responseBody); + } + + if (dwnRpcResponse.error) { + const { code, message } = dwnRpcResponse.error; + throw new Error(`(${code}) - ${message}`); + } + + const { reply } = dwnRpcResponse.result; + if (dataStream) { + reply['record']['data'] = dataStream; + } + + return reply as DwnRpcResponse; + } +} \ No newline at end of file diff --git a/packages/agent/src/store-managed-did.ts b/packages/agent/src/store-managed-did.ts new file mode 100644 index 000000000..7fead29f1 --- /dev/null +++ b/packages/agent/src/store-managed-did.ts @@ -0,0 +1,299 @@ +import type { RecordsWriteMessage } from '@tbd54566975/dwn-sdk-js'; + +import { Convert } from '@web5/common'; + +import type { Web5ManagedAgent } from './types/agent.js'; +import type { ManagedDid } from './did-manager.js'; + +export interface ManagedDidStore { + deleteDid(options: { did: string, agent?: Web5ManagedAgent, context?: string }): Promise + getDid(options: { did: string, agent?: Web5ManagedAgent, context?: string }): Promise + findDid(options: { did: string, agent?: Web5ManagedAgent, context?: string }): Promise + findDid(options: { alias: string, agent?: Web5ManagedAgent, context?: string }): Promise + importDid(options: { did: ManagedDid, agent?: Web5ManagedAgent, context?: string }): Promise + listDids(options?: { agent?: Web5ManagedAgent, context?: string }): Promise +} + +/** + * + */ +export class DidStoreDwn implements ManagedDidStore { + private _didRecordProperties = { + dataFormat : 'application/json', + schema : 'https://identity.foundation/schemas/web5/managed-did' + }; + + async deleteDid(options: { + agent: Web5ManagedAgent, + context?: string, + did: string + }): Promise { + const { agent, context, did } = options; + + // Determine which DID to use to author DWN messages. + const authorDid = await this.getAuthor({ agent, context, did }); + + // Query the DWN for all stored DID objects. + const { reply: queryReply} = await agent.dwnManager.processRequest({ + author : authorDid, + target : authorDid, + messageType : 'RecordsQuery', + messageOptions : { + filter: { ...this._didRecordProperties } + } + }); + + // Loop through all of the entries and try to find a match. + let matchingRecordId: string | undefined; + for (const record of queryReply.entries ?? []) { + if (record.encodedData) { + const storedDid = Convert.base64Url(record.encodedData).toObject() as ManagedDid; + if (storedDid && storedDid.did === did) { + matchingRecordId = (record as RecordsWriteMessage).recordId ; + break; + } + } + } + + // Return undefined if the specified DID was not found in the store. + if (!matchingRecordId) return false; + + // If a record for the specified DID was found, attempt to delete it. + const { reply: { status } } = await agent.dwnManager.processRequest({ + author : authorDid, + target : authorDid, + messageType : 'RecordsDelete', + messageOptions : { + recordId: matchingRecordId + } + }); + + // If the DID was successfully deleted, return true; + if (status.code === 202) return true; + + // If the DID could not be deleted, return false; + return false; + } + + async findDid(options: { agent: Web5ManagedAgent, context?: string, did: string }): Promise; + async findDid(options: { agent: Web5ManagedAgent, context?: string, alias: string }): Promise; + async findDid(options: { agent: Web5ManagedAgent, alias: string, context?: string, did: string }): Promise { + const { agent, alias, context, did } = options; + + // Determine which DID to use to author DWN messages. + const authorDid = await this.getAuthor({ agent, context, did }); + + // Query the DWN for all stored DID objects. + const { reply: queryReply} = await agent.dwnManager.processRequest({ + author : authorDid, + target : authorDid, + messageType : 'RecordsQuery', + messageOptions : { + filter: { ...this._didRecordProperties } + } + }); + + // Loop through all of the entries and return a match, if found. + for (const record of queryReply.entries ?? []) { + if (record.encodedData) { + const storedDid = Convert.base64Url(record.encodedData).toObject() as ManagedDid; + if (storedDid && storedDid.did === did) return storedDid; + if (storedDid && storedDid.alias === alias) return storedDid; + } + } + + // Return undefined if no matches were found. + return undefined; + } + + async getDid(options: { + agent: Web5ManagedAgent, + context?: string, + did: string + }): Promise { + const { agent, context, did } = options; + + // Determine which DID to use to author DWN messages. + const authorDid = await this.getAuthor({ agent, context, did }); + + // Query the DWN for all stored DID objects. + const { reply: queryReply} = await agent.dwnManager.processRequest({ + author : authorDid, + target : authorDid, + messageType : 'RecordsQuery', + messageOptions : { filter: { ...this._didRecordProperties } } + }); + + // Loop through all of the entries and return a match, if found. + for (const record of queryReply.entries ?? []) { + if (record.encodedData) { + const storedDid = Convert.base64Url(record.encodedData).toObject() as ManagedDid; + if (storedDid && storedDid.did === did) return storedDid; + } + } + + // Return undefined if no matches were found. + return undefined; + } + + async importDid(options: { + agent: Web5ManagedAgent, + context?: string, + did: ManagedDid + }) { + const { agent, context, did: importDid } = options; + + // Determine which DID to use to author DWN messages. + const authorDid = await this.getAuthor({ agent, context, did: importDid.did }); + + // Check if the DID being imported is already present in the store. + const duplicateFound = await this.getDid({ agent, context, did: importDid.did }); + if (duplicateFound) { + throw new Error(`DidStoreDwn: DID with ID already exists: '${importDid.did}'`); + } + + // Encode the ManagedDid as bytes. + const importDidU8A = Convert.object(importDid).toUint8Array(); + + const writeReply = await agent.dwnManager.processRequest({ + // const { reply: { status } } = await agent.dwnManager.processRequest({ + author : authorDid, + target : authorDid, + messageType : 'RecordsWrite', + messageOptions : { ...this._didRecordProperties }, + dataStream : new Blob([importDidU8A]) + }); + + // If the write fails, throw an error. + if (writeReply.reply.status.code !== 202) { + // if (status.code !== 202) { + console.log('AUTHOR:', authorDid); + console.log(JSON.stringify(writeReply.message, null, 2)); + console.log(JSON.stringify(writeReply.reply, null, 2)); + throw new Error('DidStoreDwn: Failed to write imported DID to store.'); + } + } + + async listDids(options: { + agent: Web5ManagedAgent, + context?: string + }): Promise { + const { agent, context } = options; + + // Determine which DID to use to author DWN messages. + const authorDid = await this.getAuthor({ agent, context }); + + // Query the DWN for all stored DID objects. + const { reply: queryReply} = await agent.dwnManager.processRequest({ + author : authorDid, + target : authorDid, + messageType : 'RecordsQuery', + messageOptions : { + filter: { ...this._didRecordProperties } + } + }); + + // Loop through all of the entries and accumulate the DID objects. + let storedDids: ManagedDid[] = []; + for (const record of queryReply.entries ?? []) { + if (record.encodedData) { + const storedDid = Convert.base64Url(record.encodedData).toObject() as ManagedDid; + storedDids.push(storedDid); + } + } + + return storedDids; + } + + private async getAuthor(options: { + context?: string, + did?: string, + agent: Web5ManagedAgent + }): Promise { + const { context, did, agent } = options; + + // If `context` is specified, DWN messages will be signed by this DID. + if (context) return context; + + // If Agent has an agentDid, use it to sign DWN messages. + if (agent.agentDid) return agent.agentDid; + + // If `context`, `agent.agentDid`, and `did` are undefined, throw error. + if (!did) { + throw new Error(`DidStoreDwn: Agent property 'agentDid' is undefined.`); + } + + /** Lacking a context and agentDid DID, check whether KeyManager has + * a key pair for the given `did` value.*/ + const signingKeyId = await agent.didManager.getDefaultSigningKey({ did }); + const keyPair = (signingKeyId) + ? await agent.keyManager.getKey({ keyRef: signingKeyId }) + : undefined; + + // If a key pair is found, use the `did` to sign messages. + if (keyPair) return did; + + // If all else fails, throw an error. + throw new Error(`DidStoreDwn: Agent property 'agentDid' is undefined and no keys were found for: '${did}'`); + } +} + +/** + * + */ +export class DidStoreMemory implements ManagedDidStore { + /** + * A private field that contains the Map used as the in-memory key-value store. + */ + private store: Map = new Map(); + + async deleteDid({ did }: { did: string; }): Promise { + if (this.store.has(did)) { + // DID with given identifier exists so proceed with delete. + this.store.delete(did); + return true; + } + + // DID with given identifier not present so delete operation not possible. + return false; + } + + async getDid({ did }: { did: string; }): Promise { + return this.store.get(did); + } + + async findDid(options: { did: string }): Promise; + async findDid(options: { alias: string }): Promise; + async findDid(options: { alias?: string, did?: string}): Promise { + let { alias, did } = options; + + // Get DID by identifier. + if (did) return this.store.get(did); + + if (alias) { + // Search through the store to find a matching entry + for (const did of this.store.values()) { + if (did.alias === alias) return did; + } + } + + return undefined; + } + + async importDid(options: { did: ManagedDid }) { + const { did: importDid } = options; + + if (this.store.has(importDid.did)) { + // DID with given identifier already exists so import operation cannot proceed. + throw new Error(`DidStoreMemory: DID with ID already exists: '${importDid.did}'`); + } + + // Make a deep copy of the DID so that the object stored does not share the same references as the input. + const clonedDid = structuredClone(importDid); + this.store.set(importDid.did, clonedDid); + } + + async listDids(): Promise { + return Array.from(this.store.values()); + } +} \ No newline at end of file diff --git a/packages/agent/src/store-managed-identity.ts b/packages/agent/src/store-managed-identity.ts new file mode 100644 index 000000000..cfc8f9d06 --- /dev/null +++ b/packages/agent/src/store-managed-identity.ts @@ -0,0 +1,242 @@ +import { Convert } from '@web5/common'; + +import type { Web5ManagedAgent } from './types/agent.js'; +import type { ManagedIdentity } from './identity-manager.js'; +import type { RecordsWriteMessage } from '@tbd54566975/dwn-sdk-js'; + +export interface ManagedIdentityStore { + deleteIdentity(options: { did: string, agent?: Web5ManagedAgent, context?: string }): Promise + getIdentity(options: { did: string, agent?: Web5ManagedAgent, context?: string }): Promise + importIdentity(options: { identity: ManagedIdentity, agent?: Web5ManagedAgent, context?: string }): Promise + listIdentities(options?: { agent?: Web5ManagedAgent, context?: string }): Promise +} + +/** + * + */ +export class IdentityStoreDwn implements ManagedIdentityStore { + private _identityRecordProperties = { + dataFormat : 'application/json', + schema : 'https://identity.foundation/schemas/web5/managed-identity' + }; + + async deleteIdentity(options: { + agent: Web5ManagedAgent, + context?: string, + did: string + }): Promise { + const { agent, context, did } = options; + + // Determine which DID to use to author DWN messages. + const authorDid = await this.getAuthor({ agent, context, did }); + + // Query the DWN for all stored Identity objects. + const { reply: queryReply} = await agent.dwnManager.processRequest({ + author : authorDid, + target : authorDid, + messageType : 'RecordsQuery', + messageOptions : { + filter: { ...this._identityRecordProperties } + } + }); + + // Loop through all of the entries and try to find a match. + let matchingRecordId: string | undefined; + for (const record of queryReply.entries ?? []) { + if (record.encodedData) { + const storedIdentity = Convert.base64Url(record.encodedData).toObject() as ManagedIdentity; + if (storedIdentity && storedIdentity.did === did) { + matchingRecordId = (record as RecordsWriteMessage).recordId ; + break; + } + } + } + + // Return undefined if the specified Identity was not found in the store. + if (!matchingRecordId) return false; + + // If a record for the specified Identity was found, attempt to delete it. + const { reply: { status } } = await agent.dwnManager.processRequest({ + author : authorDid, + target : authorDid, + messageType : 'RecordsDelete', + messageOptions : { + recordId: matchingRecordId + } + }); + + // If the Identity was successfully deleted, return true; + if (status.code === 202) return true; + + // If the Identity could not be deleted, return false; + return false; + } + + async getIdentity(options: { + agent: Web5ManagedAgent, + context?: string, + did: string + }): Promise { + const { agent, context, did } = options; + + // Determine which DID to use to author DWN messages. + const authorDid = await this.getAuthor({ agent, context, did }); + + // Query the DWN for all stored Identity objects. + const { reply: queryReply} = await agent.dwnManager.processRequest({ + author : authorDid, + target : authorDid, + messageType : 'RecordsQuery', + messageOptions : { filter: { ...this._identityRecordProperties } } + }); + + // Loop through all of the entries and return a match, if found. + for (const record of queryReply.entries ?? []) { + if (record.encodedData) { + const storedIdentity = Convert.base64Url(record.encodedData).toObject() as ManagedIdentity; + if (storedIdentity && storedIdentity.did === did) return storedIdentity; + } + } + + // Return undefined if no matches were found. + return undefined; + } + + async importIdentity(options: { + agent: Web5ManagedAgent, + context?: string, + identity: ManagedIdentity + }) { + const { agent, context, identity } = options; + + // Determine which DID to use to author DWN messages. + const authorDid = await this.getAuthor({ agent, context, did: identity.did }); + + // Check if the Identity being imported is already present in the store. + const duplicateFound = await this.getIdentity({ agent, context, did: identity.did }); + if (duplicateFound) { + throw new Error(`IdentityStoreDwn: Identity with DID already exists: '${identity.did}'`); + } + + // Encode the ManagedIdentity as bytes. + const identityU8A = Convert.object(identity).toUint8Array(); + + const { reply: { status } } = await agent.dwnManager.processRequest({ + author : authorDid, + target : authorDid, + messageType : 'RecordsWrite', + messageOptions : { ...this._identityRecordProperties }, + dataStream : new Blob([identityU8A]) + }); + + // If the write fails, throw an error. + if (status.code !== 202) { + throw new Error('IdentityStoreDwn: Failed to write imported identity to store.'); + } + } + + async listIdentities(options: { + agent: Web5ManagedAgent, + context?: string + }): Promise { + const { agent, context } = options; + + // Determine which DID to use to author DWN messages. + const authorDid = await this.getAuthor({ agent, context }); + + // Query the DWN for all stored Identity objects. + const { reply: queryReply} = await agent.dwnManager.processRequest({ + author : authorDid, + target : authorDid, + messageType : 'RecordsQuery', + messageOptions : { + filter: { ...this._identityRecordProperties } + } + }); + + // Loop through all of the entries and accumulate the Identity objects. + let storedIdentities: ManagedIdentity[] = []; + for (const record of queryReply.entries ?? []) { + if (record.encodedData) { + const storedIdentity = Convert.base64Url(record.encodedData).toObject() as ManagedIdentity; + storedIdentities.push(storedIdentity); + } + } + + return storedIdentities; + } + + private async getAuthor(options: { + context?: string, + did?: string, + agent: Web5ManagedAgent + }): Promise { + const { context, did, agent } = options; + + // If `context` is specified, DWN messages will be signed by this DID. + if (context) return context; + + // If Agent has an agentDid, use it to sign DWN messages. + if (agent.agentDid) return agent.agentDid; + + // If `context`, `agent.agentDid`, and `did` are undefined, throw error. + if (!did) { + throw new Error(`DidStoreDwn: Agent property 'agentDid' is undefined.`); + } + + /** Lacking a context and agentDid DID, check whether KeyManager has + * a key pair for the given `did` value.*/ + const signingKeyId = await agent.didManager.getDefaultSigningKey({ did }); + const keyPair = (signingKeyId) + ? await agent.keyManager.getKey({ keyRef: signingKeyId }) + : undefined; + + // If a key pair is found, use the `did` to sign messages. + if (keyPair) return did; + + // If all else fails, throw an error. + throw new Error(`IdentityStoreDwn: Agent property 'agentDid' is undefined and no keys were found for: '${did}'`); + } +} + +/** + * + */ +export class IdentityStoreMemory implements ManagedIdentityStore { + /** + * A private field that contains the Map used as the in-memory key-value store. + */ + private store: Map = new Map(); + + async deleteIdentity({ did }: { did: string; }): Promise { + if (this.store.has(did)) { + // Identity with given DID exists so proceed with delete. + this.store.delete(did); + return true; + } + + // Identity with given DID not present so delete operation not possible. + return false; + } + + async getIdentity({ did }: { did: string; }): Promise { + return this.store.get(did); + } + + async importIdentity(options: { identity: ManagedIdentity }) { + const { identity } = options; + + if (this.store.has(identity.did)) { + // Identity with given identifier already exists so import operation cannot proceed. + throw new Error(`IdentityStoreMemory: Identity with DID already exists: '${identity.did}'`); + } + + // Make a deep copy of the Identity so that the object stored does not share the same references as the input. + const clonedIdentity = structuredClone(identity); + this.store.set(identity.did, clonedIdentity); + } + + async listIdentities(): Promise { + return Array.from(this.store.values()); + } +} \ No newline at end of file diff --git a/packages/agent/src/store-managed-key.ts b/packages/agent/src/store-managed-key.ts new file mode 100644 index 000000000..8e1a3486c --- /dev/null +++ b/packages/agent/src/store-managed-key.ts @@ -0,0 +1,739 @@ +import type { RecordsWriteMessage, RecordsWriteOptions } from '@tbd54566975/dwn-sdk-js'; + +import { randomUuid } from '@web5/crypto'; +import { Convert, removeEmptyObjects, removeUndefinedProperties } from '@web5/common'; + +import type { ManagedKeyPair, ManagedKeyStore, ManagedPrivateKey } from './types/managed-key.js'; + +import { DwnResponse, Web5ManagedAgent } from './types/agent.js'; +import { isManagedKeyPair } from './utils.js'; +import { ManagedKey } from './types/managed-key.js'; + +type EncodedPrivateKey = Omit & { + // Key material, encoded as Base64Url. + material: string; +} + +type EncodedKey = Omit & { + // Key material, encoded as Base64Url. + material?: string; +} + +type EncodedKeyPair = { + privateKey: EncodedKey; + publicKey: EncodedKey; +} + +/** + * An implementation of `ManagedKeyStore` that stores key metadata and + * public key material in a DWN. + * + * An instance of this class can be used by `KeyManager` or + * an implementation of `KeyManagementSystem`. + */ +export class KeyStoreDwn implements ManagedKeyStore { + private _keyRecordProperties = { + dataFormat : 'application/json', + schema : 'https://identity.foundation/schemas/web5/managed-key' + }; + + constructor(options?: { schema: string }) { + const { schema } = options ?? {}; + if (schema) { + this._keyRecordProperties.schema = schema; + } + } + + async deleteKey(options: { + agent: Web5ManagedAgent, + context?: string, + id: string + }): Promise { + const { agent, context, id } = options; + + // Determine which DID to use to author DWN messages. + const authorDid = await this.getAuthor({ agent, context }); + + // Query the DWN for all stored key objects. + const { reply: queryReply} = await this.getKeyRecords(agent, context); + + // Loop through all of the entries and try to find a match. + let matchingRecordId: string | undefined; + for (const record of queryReply.entries ?? []) { + if (record.encodedData) { + const storedKey = this.decodeKey(record.encodedData); + const storedKeyId = isManagedKeyPair(storedKey) ? storedKey.publicKey.id : storedKey.id; + if (storedKey && storedKeyId === id) { + matchingRecordId = (record as RecordsWriteMessage).recordId ; + break; + } + } + } + + // Return undefined if the specified key was not found in the store. + if (!matchingRecordId) return false; + + // If a record for the specified key was found, attempt to delete it. + const { reply: { status } } = await agent.dwnManager.processRequest({ + author : authorDid, + target : authorDid, + messageType : 'RecordsDelete', + messageOptions : { + recordId: matchingRecordId + } + }); + + // If the key was successfully deleted, return true; + if (status.code === 202) return true; + + // If the key could not be deleted, return false; + return false; + } + + async findKey(options: { id: string, agent: Web5ManagedAgent, context?: string }): Promise; + async findKey(options: { alias: string, agent: Web5ManagedAgent, context?: string }): Promise; + async findKey(options: { agent: Web5ManagedAgent, alias?: string, context?: string, id?: string }): Promise { + const { agent, alias, context, id } = options; + + // Query the DWN for all stored managed key objects. + const { reply: queryReply} = await this.getKeyRecords(agent, context); + + // Loop through all of the entries and return a match, if found. + for (const record of queryReply.entries ?? []) { + if (record.encodedData) { + const storedKey = this.decodeKey(record.encodedData); + if (isManagedKeyPair(storedKey)) { + if (storedKey.publicKey.id === id) return storedKey; + if (storedKey.publicKey.alias === alias) return storedKey; + } else { + if (storedKey.id === id) return storedKey; + if (storedKey.alias === alias) return storedKey; + } + } + } + + // Return undefined if no matches were found. + return undefined; + } + + async getKey(options: { + agent: Web5ManagedAgent, + context?: string, + id: string + }): Promise { + const { agent, context, id } = options; + + // Query the DWN for all stored managed key objects. + const { reply: queryReply} = await this.getKeyRecords(agent, context); + + // Loop through all of the entries and return a match, if found. + for (const record of queryReply.entries ?? []) { + if (record.encodedData) { + const storedKey = this.decodeKey(record.encodedData); + const storedKeyId = isManagedKeyPair(storedKey) ? storedKey.publicKey.id : storedKey.id; + if (storedKeyId === id) return storedKey; + } + } + + // Return undefined if no matches were found. + return undefined; + } + + async importKey(options: { + agent: Web5ManagedAgent, + context?: string, + key: ManagedKey | ManagedKeyPair + }): Promise { + const { agent, context, key } = options; + + let keyId: string; + if (isManagedKeyPair(key)) { + keyId = key.publicKey.id; + } else { + // If an ID wasn't specified, generate one. + if (!key.id) { + key.id = randomUuid(); + } + keyId = key.id; + } + + // Determine which DID to use to author DWN messages. + const authorDid = await this.getAuthor({ agent, context }); + + // Check if the key being imported is already present in the store. + const duplicateFound = await this.getKey({ agent, context, id: keyId }); + if (duplicateFound) { + throw new Error(`KeyStoreDwn: Key with ID already exists: '${keyId}'`); + } + + // Encode the managed key or key pair as bytes. + const encodedKey = this.encodeKey(key); + + const { reply: { status } } = await agent.dwnManager.processRequest({ + author : authorDid, + target : authorDid, + messageType : 'RecordsWrite', + messageOptions : { ...this._keyRecordProperties }, + dataStream : new Blob([encodedKey]) + }); + + // If the write fails, throw an error. + if (status.code !== 202) { + throw new Error('DidStoreDwn: Failed to write imported DID to store.'); + } + + return keyId; + } + + async listKeys(options: { + agent: Web5ManagedAgent, + context?: string + }): Promise<(ManagedKey | ManagedKeyPair)[]> { + const { agent, context } = options; + + // Query the DWN for all stored managed key objects. + const { reply: queryReply} = await this.getKeyRecords(agent, context); + + // Loop through all of the entries and accumulate the key objects. + let storedKeys: (ManagedKey | ManagedKeyPair)[] = []; + for (const record of queryReply.entries ?? []) { + if (record.encodedData) { + const storedKey = this.decodeKey(record.encodedData); + storedKeys.push(storedKey); + } + } + + return storedKeys; + } + + async updateKey(options: { + agent: Web5ManagedAgent, + context?: string + } & Pick): Promise { + const { agent, context, id } = options; + const propertyUpdates = { alias: options.alias, metadata: options.metadata }; + + // Determine which DID to use to author DWN messages. + const authorDid = await this.getAuthor({ agent, context }); + + // Query the DWN for all stored managed key objects. + const { reply: queryReply} = await this.getKeyRecords(agent, context); + + // Confirm the key being updated is already present in the store. + let keyToUpdate: ManagedKey | ManagedKeyPair | undefined; + let recordToUpdate: RecordsWriteMessage | undefined; + for (const entry of queryReply.entries ?? []) { + const { encodedData, ...record } = entry; + if (encodedData) { + const storedKey = this.decodeKey(encodedData); + const storedKeyId = isManagedKeyPair(storedKey) ? storedKey.publicKey.id : storedKey.id; + if (storedKey && storedKeyId === id) { + keyToUpdate = storedKey; + recordToUpdate = record as RecordsWriteMessage ; + break; + } + } + } + + // Key with given ID not present so update operation cannot proceed. + if (!recordToUpdate || !keyToUpdate) return false; + + // Make a deep copy of the update properties to ensure all nested objects do not share references. + removeUndefinedProperties(propertyUpdates); + removeEmptyObjects(propertyUpdates); + const clonedUpdates = structuredClone(propertyUpdates); + + // Update the given properties of the key. + if (isManagedKeyPair(keyToUpdate)) { + keyToUpdate.privateKey = { ...keyToUpdate.privateKey, ...clonedUpdates }; + keyToUpdate.publicKey = { ...keyToUpdate.publicKey, ...clonedUpdates }; + } else { + keyToUpdate = { ...keyToUpdate, ...clonedUpdates }; + } + + // Encode the updated key or key pair as bytes. + const updatedKeyBytes = this.encodeKey(keyToUpdate); + + // Assemble the update messsage, including record ID and context ID, if any. + let messageOptions = { ...recordToUpdate.descriptor } as Partial; + messageOptions.contextId = recordToUpdate.contextId; + messageOptions.recordId = recordToUpdate.recordId; + + /** Remove properties from the update messageOptions to let the DWN SDK + * auto-fill. Otherwisse, you will get 409 Conflict errors. */ + delete messageOptions.dataCid; + delete messageOptions.dataSize; + delete messageOptions.data; + delete messageOptions.messageTimestamp; + + // Overwrite the entry in the store with the updated object. + const { reply: { status } } = await agent.dwnManager.processRequest({ + author : authorDid, + target : authorDid, + messageType : 'RecordsWrite', + messageOptions, + dataStream : new Blob([updatedKeyBytes]) + }); + + // If the write fails, throw an error. + if (status.code !== 202) { + throw new Error('DidStoreDwn: Failed to write updated key to store.'); + } + + return true; + } + + private decodeKey(keyEncodedData: string): ManagedKey | ManagedKeyPair { + const encodedKey = Convert.base64Url(keyEncodedData).toObject() as EncodedKey | EncodedKeyPair; + + if ('publicKey' in encodedKey) { + const privateKeyMaterial = encodedKey.privateKey.material + ? Convert.base64Url(encodedKey.privateKey.material).toUint8Array() + : undefined; + + const publicKeyMaterial = encodedKey.publicKey.material + ? Convert.base64Url(encodedKey.publicKey.material).toUint8Array() + : undefined; + + const managedKeyPair = { + privateKey : { ...encodedKey.privateKey, material: privateKeyMaterial }, + publicKey : { ...encodedKey.publicKey, material: publicKeyMaterial} + } as ManagedKeyPair; + + return managedKeyPair; + + } else { + const material = encodedKey.material + ? Convert.base64Url(encodedKey.material).toUint8Array() + : undefined; + + const managedKey = { ...encodedKey, material } as ManagedKey; + + return managedKey; + } + } + + private encodeKey(managedKey: ManagedKey | ManagedKeyPair): Uint8Array { + let encodedKey: EncodedKey | EncodedKeyPair; + + if (isManagedKeyPair(managedKey)) { + const privateKeyMaterial = managedKey.privateKey.material + ? Convert.uint8Array(managedKey.privateKey.material).toBase64Url() + : undefined; + + const publicKeyMaterial = managedKey.publicKey.material + ? Convert.uint8Array(managedKey.publicKey.material).toBase64Url() + : undefined; + + encodedKey = { + privateKey : { ...managedKey.privateKey, material: privateKeyMaterial }, + publicKey : { ...managedKey.publicKey, material: publicKeyMaterial } + }; + + } else { + const material = managedKey.material + ? Convert.uint8Array(managedKey.material).toBase64Url() + : undefined; + + encodedKey = { ...managedKey, material }; + } + + const keyBytes = Convert.object(encodedKey).toUint8Array(); + + return keyBytes; + } + + private async getAuthor(options: { + agent: Web5ManagedAgent, + context?: string + }): Promise { + const { agent, context } = options; + + // If `context` is specified, DWN messages will be signed by this DID. + if (context) return context; + + // If Agent has an agentDid, use it to sign DWN messages. + if (agent.agentDid) return agent.agentDid; + + // If `context` and `agent.agentDid`are undefined, throw error. + throw new Error(`KeyStoreDwn: Agent property 'agentDid' is undefined and no context was specified.`); + } + + private async getKeyRecords(agent: Web5ManagedAgent, context?: string): Promise { + // Determine which DID to use to author DWN messages. + const authorDid = await this.getAuthor({ agent, context }); + + const dwnResponse = await agent.dwnManager.processRequest({ + author : authorDid, + target : authorDid, + messageType : 'RecordsQuery', + messageOptions : { + filter: { ...this._keyRecordProperties } + } + }); + + return dwnResponse; + } +} + +/** + * An implementation of `ManagedKeyStore` that stores key metadata and + * public key material in memory. + * + * An instance of this class can be used by `KeyManager` or + * an implementation of `KeyManagementSystem`. + */ +export class KeyStoreMemory implements ManagedKeyStore { + /** + * A private field that contains the Map used as the in-memory key-value store. + */ + private store: Map = new Map(); + + async deleteKey({ id }: { id: string }): Promise { + if (this.store.has(id)) { + // Key with given ID exists so proceed with delete. + this.store.delete(id); + return true; + } + + // Key with given ID not present so delete operation not possible. + return false; + } + + async findKey(options: { id: string }): Promise; + async findKey(options: { alias: string }): Promise; + async findKey(options: { alias?: string, id?: string }): Promise { + let { alias, id } = options; + + // Get key by ID. + if (id) return this.store.get(id); + + if (alias) { + // Search through the store to find a matching entry. + for (const key of await this.listKeys()) { + if ('alias' in key && key.alias === alias) return key; + if ('publicKey' in key && key.publicKey.alias === alias) return key; + } + } + + return undefined; + } + + async getKey({ id }: { id: string }): Promise { + return this.store.get(id); + } + + async importKey({ key }: { key: ManagedKey | ManagedKeyPair }): Promise { + let id: string; + if (isManagedKeyPair(key)) { + id = key.publicKey.id; + } else { + // If an ID wasn't specified, generate one. + if (!key.id) { + key.id = randomUuid(); + } + id = key.id; + } + + if (this.store.has(id)) { + // Key with given ID already exists so import operation cannot proceed. + throw new Error(`KeyStoreMemory: Key with ID already exists: '${id}'`); + } + + // Make a deep copy of the key so that the object stored does not share the same references as the input key. + const clonedKey = structuredClone(key); + this.store.set(id, clonedKey); + + return id; + } + + async listKeys(): Promise<(ManagedKey | ManagedKeyPair)[]> { + return Array.from(this.store.values()); + } + + async updateKey(options: + Pick + ): Promise { + const id = options.id; + const propertyUpdates = { alias: options.alias, metadata: options.metadata }; + + const keyExists = this.store.has(id); + if (!keyExists) { + // Key with given ID not present so update operation cannot proceed. + return false; + } + + // Retrieve the current value of the key from the store. + let key = await this.getKey({ id }) as ManagedKey | ManagedKeyPair; + + // Make a deep copy of the update properties to ensure all nested objects do not share references. + removeUndefinedProperties(propertyUpdates); + removeEmptyObjects(propertyUpdates); + const clonedUpdates = structuredClone(propertyUpdates); + + // Update the given properties of the key. + if (isManagedKeyPair(key)) { + key.privateKey = { ...key.privateKey, ...clonedUpdates }; + key.publicKey = { ...key.publicKey, ...clonedUpdates }; + } else { + key = { ...key, ...clonedUpdates, id: key.id }; + } + + // Overwrite the entry in the store with the updated object. + this.store.set(id, key); + + return true; + } +} + +/** + * An implementation of `ManagedKeyStore` that stores private key + * material in a DWN. + * + * An instance of this class can be used by an implementation of + * `KeyManagementSystem`. + */ +export class PrivateKeyStoreDwn implements ManagedKeyStore { + private _keyRecordProperties = { + dataFormat : 'application/json', + schema : 'https://identity.foundation/schemas/web5/kms-private-key' + }; + + async deleteKey(options: { + agent: Web5ManagedAgent, + context?: string, + id: string + }): Promise { + const { agent, context, id } = options; + + // Determine which DID to use to author DWN messages. + const authorDid = await this.getAuthor({ agent, context }); + + // Query the DWN for all stored key objects. + const { reply: queryReply} = await this.getKeyRecords(agent, context); + + // Loop through all of the entries and try to find a match. + let matchingRecordId: string | undefined; + for (const record of queryReply.entries ?? []) { + if (record.encodedData) { + const storedKey = this.decodeKey(record.encodedData); + if (storedKey && storedKey.id === id) { + matchingRecordId = (record as RecordsWriteMessage).recordId ; + break; + } + } + } + + // Return undefined if the specified key was not found in the store. + if (!matchingRecordId) return false; + + // If a record for the specified key was found, attempt to delete it. + const { reply: { status } } = await agent.dwnManager.processRequest({ + author : authorDid, + target : authorDid, + messageType : 'RecordsDelete', + messageOptions : { + recordId: matchingRecordId + } + }); + + // If the key was successfully deleted, return true; + if (status.code === 202) return true; + + // If the key could not be deleted, return false; + return false; + } + + async findKey(): Promise { + throw new Error(`PrivateKeyStoreDwn: Method not implemented: 'findKey'`); + } + + async getKey(options: { + agent: Web5ManagedAgent, + context?: string, + id: string + }): Promise { + const { agent, context, id } = options; + + // Query the DWN for all stored key objects. + const { reply: queryReply} = await this.getKeyRecords(agent, context); + + // Loop through all of the entries and return a match, if found. + for (const record of queryReply.entries ?? []) { + if (record.encodedData) { + const storedKey = this.decodeKey(record.encodedData); + if (storedKey.id === id) return storedKey; + } + } + + // Return undefined if no matches were found. + return undefined; + } + + async importKey(options: { + agent: Web5ManagedAgent, + context?: string, + key: Omit + }): Promise { + const { agent, context, key } = options; + + if (!key.material) throw new TypeError(`Required parameter missing: 'material'`); + if (!key.type) throw new TypeError(`Required parameter missing: 'type'`); + + // Determine which DID to use to author DWN messages. + const authorDid = await this.getAuthor({ agent, context }); + + // Encode the managed key or key pair as bytes. + const id = randomUuid(); // Generate a random ID. + const encodedPrivateKey = this.encodeKey({...key, id }); + + const { reply: { status } } = await agent.dwnManager.processRequest({ + author : authorDid, + target : authorDid, + messageType : 'RecordsWrite', + messageOptions : { ...this._keyRecordProperties }, + dataStream : new Blob([encodedPrivateKey]) + }); + + // If the write fails, throw an error. + if (status.code !== 202) { + throw new Error('PrivateKeyStoreDwn: Failed to write imported DID to store.'); + } + + return id; + } + + async listKeys(options: { + agent: Web5ManagedAgent, + context?: string + }): Promise { + const { agent, context } = options; + + // Query the DWN for all stored key objects. + const { reply: queryReply} = await this.getKeyRecords(agent, context); + + // Loop through all of the entries and accumulate the key objects. + let storedKeys: ManagedPrivateKey[] = []; + for (const record of queryReply.entries ?? []) { + if (record.encodedData) { + const storedKey = this.decodeKey(record.encodedData); + storedKeys.push(storedKey); + } + } + + return storedKeys; + } + + async updateKey(): Promise { + throw new Error(`PrivateKeyStoreMemory: Method not implemented: 'updateKey'`); + } + + private decodeKey(keyEncodedData: string): ManagedPrivateKey { + const encodedKey = Convert.base64Url(keyEncodedData).toObject() as EncodedPrivateKey; + + const privateKey = { + ...encodedKey, + material: Convert.base64Url(encodedKey.material).toUint8Array() + } as ManagedPrivateKey; + + return privateKey; + } + + private encodeKey(privateKey: ManagedPrivateKey): Uint8Array { + const encodedKey = { + ...privateKey, + material: Convert.uint8Array(privateKey.material).toBase64Url() + } as EncodedPrivateKey; + + const keyBytes = Convert.object(encodedKey).toUint8Array(); + + return keyBytes; + } + + private async getAuthor(options: { + agent: Web5ManagedAgent, + context?: string + }): Promise { + const { agent, context } = options; + + // If `context` is specified, DWN messages will be signed by this DID. + if (context) return context; + + // If Agent has an agentDid, use it to sign DWN messages. + if (agent.agentDid) return agent.agentDid; + + // If `context` and `agent.agentDid`are undefined, throw error. + throw new Error(`PrivateKeyStoreDwn: Agent property 'agentDid' is undefined and no context was specified.`); + } + + private async getKeyRecords(agent: Web5ManagedAgent, context?: string): Promise { + // Determine which DID to use to author DWN messages. + const authorDid = await this.getAuthor({ agent, context }); + + const dwnResponse = await agent.dwnManager.processRequest({ + author : authorDid, + target : authorDid, + messageType : 'RecordsQuery', + messageOptions : { + filter: { ...this._keyRecordProperties } + } + }); + + return dwnResponse; + } +} + +/** + * An implementation of `ManagedKeyStore` that stores private key + * material in memory. + * + * An instance of this class can be used by an implementation of + * `KeyManagementSystem`. + */ +export class PrivateKeyStoreMemory implements ManagedKeyStore { + /** + * A private field that contains the Map used as the in-memory key-value store. + */ + private store: Map = new Map(); + + async deleteKey({ id }: { id: string }): Promise { + if (this.store.has(id)) { + // Key with given ID exists so proceed with delete. + this.store.delete(id); + return true; + } + + // Key with given ID not present so delete operation not possible. + return false; + } + + async findKey(): Promise { + throw new Error(`PrivateKeyStoreMemory: Method not implemented: 'findKey'`); + } + + async getKey({ id }: { id: string }): Promise { + return this.store.get(id); + } + + async importKey({ key }: { key: Omit }): Promise { + if (!key.material) throw new TypeError(`Required parameter missing: 'material'`); + if (!key.type) throw new TypeError(`Required parameter missing: 'type'`); + + // Make a deep copy of the key so that the object stored does not share the same references as the input key. + // The private key material is transferred to the new object, making the original obj.material unusable. + const clonedKey = structuredClone(key, { transfer: [key.material.buffer] }) as ManagedPrivateKey; + + clonedKey.id = randomUuid(); + this.store.set(clonedKey.id, clonedKey); + + return clonedKey.id; + } + + async listKeys(): Promise { + return Array.from(this.store.values()); + } + + async updateKey(): Promise { + throw new Error(`PrivateKeyStoreMemory: Method not implemented: 'updateKey'`); + } +} \ No newline at end of file diff --git a/packages/agent/src/test-managed-agent.ts b/packages/agent/src/test-managed-agent.ts new file mode 100644 index 000000000..6b69cb0df --- /dev/null +++ b/packages/agent/src/test-managed-agent.ts @@ -0,0 +1,243 @@ + +import { Jose } from '@web5/crypto'; +import { Dwn } from '@tbd54566975/dwn-sdk-js'; +import { DidIonMethod, DidKeyMethod, DidResolver } from '@web5/dids'; +import { KeyValueStore, LevelStore, MemoryStore } from '@web5/common'; +import { MessageStoreLevel, DataStoreLevel, EventLogLevel } from '@tbd54566975/dwn-sdk-js/stores'; + +import type { Web5ManagedAgent } from './types/agent.js'; + +import { LocalKms } from './kms-local.js'; +import { DidManager } from './did-manager.js'; +import { DwnManager } from './dwn-manager.js'; +import { KeyManager } from './key-manager.js'; +import { AppDataVault } from './app-data-store.js'; +import { cryptoToPortableKeyPair } from './utils.js'; +import { IdentityManager } from './identity-manager.js'; +import { DidStoreDwn, DidStoreMemory } from './store-managed-did.js'; +import { IdentityStoreDwn, IdentityStoreMemory } from './store-managed-identity.js'; +import { KeyStoreDwn, KeyStoreMemory, PrivateKeyStoreDwn, PrivateKeyStoreMemory } from './store-managed-key.js'; +import { Web5RpcClient } from './rpc-client.js'; + +type CreateMethodOptions = { + agentClass: new (options: any) => Web5ManagedAgent + agentStores?: 'dwn' | 'memory'; + testDataLocation?: string; +} + +type TestManagedAgentOptions = { + agent: Web5ManagedAgent + + agentStores: 'dwn' | 'memory'; + appDataStore: KeyValueStore; + dwn: Dwn; + dwnDataStore: DataStoreLevel; + dwnEventLog: EventLogLevel; + dwnMessageStore: MessageStoreLevel; +} + +export class TestManagedAgent { + agent: Web5ManagedAgent; + + agentStores: 'dwn' | 'memory'; + appDataStore: KeyValueStore; + dwn: Dwn; + dwnDataStore: DataStoreLevel; + dwnEventLog: EventLogLevel; + dwnMessageStore: MessageStoreLevel; + + constructor(options: TestManagedAgentOptions) { + this.agent = options.agent; + this.agentStores = options.agentStores; + this.appDataStore = options.appDataStore; + this.dwn = options.dwn; + this.dwnDataStore = options.dwnDataStore; + this.dwnEventLog = options.dwnEventLog; + this.dwnMessageStore = options.dwnMessageStore; + } + + async clearStorage(): Promise { + this.agent.agentDid = undefined; + await this.appDataStore.clear(); + await this.dwnDataStore.clear(); + await this.dwnEventLog.clear(); + await this.dwnMessageStore.clear(); + + /** Easiest way to start with fresh in-memory stores is to + * re-instantiate all of the managed agent components */ + if (this.agentStores === 'memory') { + const { didManager, identityManager, keyManager } = TestManagedAgent.useMemoryStorage({ agent: this.agent }); + this.agent.didManager = didManager; + this.agent.identityManager = identityManager; + this.agent.keyManager = keyManager; + } + } + + async closeStorage(): Promise { + await this.appDataStore.close(); + await this.dwnDataStore.close(); + await this.dwnEventLog.close(); + await this.dwnMessageStore.close(); + } + + static async create(options: CreateMethodOptions): Promise { + let { agentClass, agentStores, testDataLocation } = options; + + agentStores ??= 'memory'; + testDataLocation ??= '__TESTDATA__'; + const testDataPath = (path: string) => `${testDataLocation}/${path}`; + + const { appData, appDataStore, didManager, identityManager, keyManager } = (agentStores === 'memory') + ? TestManagedAgent.useMemoryStorage() + : TestManagedAgent.useDiskStorage({ testDataLocation }); + + // Instantiate DID resolver. + const didMethodApis = [DidIonMethod, DidKeyMethod]; + const didResolver = new DidResolver({ didResolvers: didMethodApis }); + + // Instantiate custom stores to use with DWN instance. + const dwnDataStore = new DataStoreLevel({ blockstoreLocation: testDataPath('DATASTORE') }); + const dwnEventLog = new EventLogLevel({ location: testDataPath('EVENTLOG') }); + const dwnMessageStore = new MessageStoreLevel({ + blockstoreLocation : testDataPath('MESSAGESTORE'), + indexLocation : testDataPath('INDEX') + }); + + // Instantiate custom DWN instance. + const dwn = await Dwn.create({ + eventLog : dwnEventLog, + dataStore : dwnDataStore, + // @ts-expect-error because the Web5.js DidResolver implementation doesn't have the dump() method. + didResolver : didResolver, + messageStore : dwnMessageStore + }); + + // Instantiate a DwnManager using the custom DWN instance. + const dwnManager = new DwnManager({ dwn }); + + // Instantiate an RPC Client. + const rpcClient = new Web5RpcClient(); + + const agent = new agentClass({ + agentDid: '', + appData, + didManager, + didResolver, + dwnManager, + identityManager, + keyManager, + rpcClient, + }); + + return new TestManagedAgent({ + agent, + agentStores, + appDataStore, + dwn, + dwnDataStore, + dwnEventLog, + dwnMessageStore, + }); + } + + async createAgentDid(): Promise { + // Create an a DID and key set for the Agent. + const agentDid = await DidKeyMethod.create({ keyAlgorithm: 'Ed25519' }); + const privateCryptoKey = await Jose.jwkToCryptoKey({ key: agentDid.keySet.verificationMethodKeys![0].privateKeyJwk! }); + const publicCryptoKey = await Jose.jwkToCryptoKey({ key: agentDid.keySet.verificationMethodKeys![0].publicKeyJwk! }); + const agentSigningKey = { privateKey: privateCryptoKey, publicKey: publicCryptoKey }; + + // Set the DID as the default signing key. + const alias = await this.agent.didManager.getDefaultSigningKey({ did: agentDid.did }); + const defaultSigningKey = cryptoToPortableKeyPair({ cryptoKeyPair: agentSigningKey, keyData: { alias, kms: 'memory' } }); + await this.agent.keyManager.setDefaultSigningKey({ key: defaultSigningKey }); + + // Set the DID as the Agent's DID. + this.agent.agentDid = agentDid.did; + } + + private static useDiskStorage(options: { agent?: Web5ManagedAgent, testDataLocation: string }) { + const { agent, testDataLocation } = options; + const testDataPath = (path: string) => `${testDataLocation}/${path}`; + + const appDataStore = new LevelStore(testDataPath('APPDATA')); + const appData = new AppDataVault({ + keyDerivationWorkFactor : 1, + store : appDataStore + }); + + const didManager = new DidManager({ + agent, + didMethods : [DidIonMethod, DidKeyMethod], + store : new DidStoreDwn() + }); + + const identityManager = new IdentityManager({ + agent, + store: new IdentityStoreDwn() + }); + + const localKmsDwn = new LocalKms({ + agent, + kmsName : 'local', + keyStore : new KeyStoreDwn({ schema: 'https://identity.foundation/schemas/web5/kms-key' }), + privateKeyStore : new PrivateKeyStoreDwn() + }); + const localKmsMemory = new LocalKms({ + agent, + kmsName: 'memory' + }); + const keyManager = new KeyManager({ + agent, + kms: { + local : localKmsDwn, + memory : localKmsMemory + }, + store: new KeyStoreDwn({ schema: 'https://identity.foundation/schemas/web5/managed-key' }) + }); + + return { appData, appDataStore, didManager, identityManager, keyManager }; + } + + private static useMemoryStorage(options?: { agent: Web5ManagedAgent }) { + const { agent } = options ?? {}; + + const appDataStore = new MemoryStore(); + const appData = new AppDataVault({ + keyDerivationWorkFactor : 1, + store : appDataStore + }); + + const didManager = new DidManager({ + agent, + didMethods : [DidIonMethod, DidKeyMethod], + store : new DidStoreMemory() + }); + + const identityManager = new IdentityManager({ + agent, + store: new IdentityStoreMemory() + }); + + const localKmsDwn = new LocalKms({ + agent, + kmsName : 'local', + keyStore : new KeyStoreMemory(), + privateKeyStore : new PrivateKeyStoreMemory() + }); + const localKmsMemory = new LocalKms({ + agent, + kmsName: 'memory' + }); + const keyManager = new KeyManager({ + agent, + kms: { + local : localKmsDwn, + memory : localKmsMemory + }, + store: new KeyStoreMemory() + }); + + return { appData, appDataStore, didManager, identityManager, keyManager }; + } +} \ No newline at end of file diff --git a/packages/agent/src/types/agent.ts b/packages/agent/src/types/agent.ts new file mode 100644 index 000000000..60aa700d4 --- /dev/null +++ b/packages/agent/src/types/agent.ts @@ -0,0 +1,147 @@ +import type { Readable } from 'readable-stream'; +import type { + EventsGetMessage, + UnionMessageReply, + MessagesGetMessage, + RecordsQueryMessage, + RecordsWriteMessage, + RecordsDeleteMessage, + ProtocolsQueryMessage, + ProtocolsConfigureMessage, +} from '@tbd54566975/dwn-sdk-js'; + +import { DidResolver } from '@web5/dids'; + +import { DidManager } from '../did-manager.js'; +import { DwnManager } from '../dwn-manager.js'; +import { KeyManager } from '../key-manager.js'; +import { AppDataStore } from '../app-data-store.js'; +import { IdentityManager } from '../identity-manager.js'; + +/** + * DID Types + */ + +export type ProcessDidRequest = { /** empty */ } +export type SendDidRequest = { /** empty */ } +export type DidResponse = { /** empty */ } + +/** + * DWN Types + */ +export type DwnMessages = { + 'EventsGet': EventsGetMessage; + 'MessagesGet': MessagesGetMessage; + 'RecordsWrite': RecordsWriteMessage; + 'RecordsQuery': RecordsQueryMessage; + 'RecordsDelete': RecordsDeleteMessage; + 'ProtocolsQuery': ProtocolsQueryMessage; + 'ProtocolsConfigure': ProtocolsConfigureMessage; +}; + +export type DwnMessageType = keyof DwnMessages; + +export type DwnRequest = { + author: string; + target: string; + messageType: string; +} + +/** + * TODO: add JSDoc + */ +export type ProcessDwnRequest = DwnRequest & { + dataStream?: Blob | ReadableStream | Readable; + messageOptions: unknown; + store?: boolean; +}; + +export type SendDwnRequest = DwnRequest & (ProcessDwnRequest | { messageCid: string }) + +/** + * TODO: add JSDoc + */ +export type DwnResponse = { + message?: unknown; + messageCid?: string; + reply: UnionMessageReply; +}; + +/** + * TODO: add JSDoc + */ +export type SendDwnResponse = DwnRpcResponse; + +export interface SerializableDwnMessage { + toJSON(): string; +} + + +// TODO: move what's below to dwn-server repo. i wrote this here for expediency + +/** + * interface that can be implemented to communicate with Dwn Servers + */ +export interface DwnRpc { + /** + * TODO: add jsdoc + */ + get transportProtocols(): string[] + + /** + * TODO: add jsdoc + * @param request + */ + sendDwnRequest(request: DwnRpcRequest): Promise +} + +/** + * TODO: add jsdoc + */ +export type DwnRpcRequest = { + data?: any; + dwnUrl: string; + message: SerializableDwnMessage | any; + targetDid: string; +} + +/** + * TODO: add jsdoc + */ +export type DwnRpcResponse = UnionMessageReply; + + +/** + * Verifiable Credential Types + */ + +export type ProcessVcRequest = { /** empty */ } +export type SendVcRequest = { /** empty */ } +export type VcResponse = { /** empty */ } + +/** + * Web5 Agent Types + */ +export interface Web5Agent { + agentDid: string | undefined; + processDidRequest(request: ProcessDidRequest): Promise + sendDidRequest(request: SendDidRequest): Promise; + processDwnRequest(request: ProcessDwnRequest): Promise + sendDwnRequest(request: SendDwnRequest): Promise; + processVcRequest(request: ProcessVcRequest): Promise + sendVcRequest(request: SendVcRequest): Promise; +} + +export interface Web5ManagedAgent extends Web5Agent { + appData: AppDataStore; + didManager: DidManager; + didResolver: DidResolver; + dwnManager: DwnManager; + identityManager: IdentityManager; + keyManager: KeyManager; + rpcClient: DwnRpc; + + firstLaunch(): Promise; + initialize(options: { passphrase: string }): Promise; + start(options: { passphrase: string }): Promise; +} \ No newline at end of file diff --git a/packages/agent/src/types/managed-key.ts b/packages/agent/src/types/managed-key.ts new file mode 100644 index 000000000..86f0aa84a --- /dev/null +++ b/packages/agent/src/types/managed-key.ts @@ -0,0 +1,442 @@ +import type { Web5Crypto } from '@web5/crypto'; +import type { RequireOnly } from '@web5/common'; + +import { Web5ManagedAgent } from './agent.js'; + +export interface CryptoManager { + agent: Web5ManagedAgent; + + decrypt(options: DecryptOptions): Promise; + + deriveBits(options: DeriveBitsOptions): Promise; + + encrypt(options: EncryptOptions): Promise; + + /** + * Generate a new ManagedKey within a CryptoManager implementation. + */ + generateKey(options: GenerateKeyOptions): Promise>; + + /** + * Retrieves detailed information about a ManagedKey or ManagedKeyPair object. + * + * @param options - The options for retrieving the key. + * @param options.keyRef - The reference identifier for the key. Can specify the id or alias property of the key. + * @returns A promise that resolves to either a ManagedKey or ManagedKeyPair object. + */ + getKey(options: { keyRef: string }): Promise; + + importKey(options: PortableKeyPair): Promise; + importKey(options: PortableKey): Promise; + importKey(options: ImportKeyOptions): Promise; + + sign(options: SignOptions): Promise; + + updateKey(options: UpdateKeyOptions): Promise; + + verify(options: VerifyOptions): Promise; +} + +/** + * Input arguments for implementations of the CryptoManager interface + * {@link CryptoManager.encrypt | encrypt} method. + * + * @public + */ +export type DecryptOptions = { + /** + * An object defining the cipher algorithm to use and its parameters. + */ + algorithm: Web5Crypto.AlgorithmIdentifier | Web5Crypto.AesCtrOptions | Web5Crypto.AesGcmOptions; + + /** + * A Uint8Array object containing the data to be decrypted + * (also known as the ciphertext). + */ + data: Uint8Array; + + /** + * An identifier of the ManagedKey to be used for decryption. + * You can use the id or alias property of the key. + */ + keyRef: string; +} + +/** + * Input arguments for implementations of the CryptoManager interface + * {@link CryptoManager.deriveBits | deriveBits} method. + * + * @public + */ +export type DeriveBitsOptions = { + + /** + * An object defining the derivation algorithm to use and its parameters. + */ + algorithm: Web5Crypto.AlgorithmIdentifier | Web5Crypto.EcdhDeriveKeyOptions; + + /** + * An identifier of the ManagedKey that will be the input to the + * derivation algorithm. + * + * If the algorithm is ECDH, this identifier will refer to an ECDH key pair. + * For PBKDF2, it might be a password. + * For HDKF, it might be the shared secret output of an ECDH key agreement operation. + */ + baseKeyRef: string; + + /** + * A number representing the number of bits to derive. To be compatible with + * all browsers, the number should be a multiple of 8. + */ + length?: number; +} + +/** + * Input arguments for implementations of the CryptoManager interface + * {@link CryptoManager.encrypt | encrypt} method. + * + * @public + */ +export type EncryptOptions = { + /** + * An object defining the cipher algorithm to use and its parameters. + */ + algorithm: Web5Crypto.AlgorithmIdentifier | Web5Crypto.AesCtrOptions | Web5Crypto.AesGcmOptions; + + /** + * An Uint8Array object containing the data to be encrypted + * (also known as the plaintext). + */ + data: Uint8Array; + + /** + * An identifier of the ManagedKey to be used for encryption. + * You can use the id or alias property of the key. + */ + keyRef: string; +} + +export type GenerateKeyOptions = { + algorithm: T; + alias?: string; + extractable?: boolean; + keyUsages: Web5Crypto.KeyUsage[]; + metadata?: KeyMetadata; +}; + +export type GenerateKeyOptionTypes = + | Web5Crypto.AlgorithmIdentifier + // | RsaHashedGenerateKeyOptions + | Web5Crypto.AesGenerateKeyOptions + | Web5Crypto.EcdsaGenerateKeyOptions + | Web5Crypto.EdDsaGenerateKeyOptions + // | HmacGenerateKeyOptions + // | Pbkdf2Params; + +export type GenerateKeyType = T extends Web5Crypto.EcGenerateKeyOptions ? ManagedKeyPair : + T extends Web5Crypto.AesGenerateKeyOptions /*| HmacGenerateKeyOptions | Pbkdf2Params*/ ? ManagedKey : + T extends Web5Crypto.AlgorithmIdentifier ? ManagedKey | ManagedKeyPair : + never; + +export type PortableKey = + RequireOnly< + ManagedKey, + 'algorithm' | 'extractable' | 'type' | 'usages', + 'id' | 'material' | 'state' + > + & { material: Uint8Array; }; + +export interface PortableKeyPair { + privateKey: PortableKey; + publicKey: PortableKey; +} + +export type ImportKeyOptions = + | PortableKey + | PortableKeyPair + +/** + * Base interface to be implemented by key management systems. + */ +export type KeyManagementSystem = CryptoManager; + +/** + * KeyMetadata + * + * Implementations of KeyManagementSystem can populate this object with KMS platform + * specific data about each key. + * + * This property can also be used to add various tags to the keys under management. + */ +export type KeyMetadata = { + /** + * Additional properties of any type. + */ + [key: string]: any; +} + +/** + * KeyState + * + * The read-only `state` property of the `ManagedKey` interface indicates the + * status of the ManagedKey. + * + * It can have the following string values: + * + * "Enabled": The key is ready for use. + * + * "Disabled": The key may not be used, but the key material is still available, + * and the key can be placed back into the Enabled state. + * + * "PendingCreation": The key is still being created. It may not be used, + * enabled, disabled, or destroyed yet. The KMS will + * automatically change the state to enabled as soon + * as the key is ready. + * + * "PendingDeletion": The key is scheduled for deletion. It can be placed back + * into the Disabled state up until the time of deletion + * using the CancelKeyDeletion() method. Once the key has + * been deleted, any ciphertext encrypted with this key + * is no longer recoverable. Minimum and maximum waiting + * periods are defined by each KMS implementation. + * + * "PendingImport": The key is still being imported. It may not be used, enabled, + * disabled, or deleted yet. The KMS will automatically change + * the state to Enabled once the key is ready. + * + * "PendingUpdate": The key is still being updated. It may not be used, enabled, + * disabled, or deleted until the update process completes. + * The KMS will automatically change the state to Enabled + * once the key is ready. + */ +export type KeyState = 'Enabled' | 'Disabled' | 'PendingCreation' | 'PendingDeletion' | 'PendingImport' | 'PendingUpdate'; + +/** + * ManagedKey + * + * A ManagedKey represents a cryptographic key used by a cipher for + * encryption or decryption or an algorithm for signing or verification. + */ +export interface ManagedKey { + /** + * A unique identifier for the Key, autogenerated by a KMS. + */ + id: string; + + /** + * An object detailing the algorithm for which the key can be used along + * with additional algorithm-specific parameters. + */ + algorithm: Web5Crypto.GenerateKeyOptions; + + /** + * An alternate identifier used to identify the key in a KMS. + * This property can be used to associate a DID document key ID with a ManagedKey. + */ + alias?: string; + + /** + * A boolean value that is `true` if the key can be exported and `false` if not. + */ + extractable: boolean; + + /** + * Name of a registered key management system. + */ + kms: string; + + /** + * Key material as a raw binary data buffer. + */ + material?: Uint8Array; + + /** + * Optional. Additional Key metadata. + */ + metadata?: KeyMetadata; + + /** + * A registered string value specifying the algorithm and any algorithm + * specific parameters. + * Supported algorithms vary by KMS. + */ + spec?: string; + + /** + * The current status of the ManagedKey. + */ + state: KeyState; + + /** + * The type of key. + */ + type: Web5Crypto.KeyType; + + /** + * Indicates which cryptographic operations are permissible to be used with this key. + */ + usages: Web5Crypto.KeyUsage[]; +} + +/** + * Represents information about a managed key. + * Private or secret key material is NOT present. + * + */ +export type ManagedKeyInfo = Omit; + +export type ManagedKeyOptions = Omit + +/** ManagedKeyPair + * + * A ManagedKeyPair represents a key pair for an asymmetric cryptography algorithm, + * also known as a public-key algorithm. + * + * A ManagedKeyPair object can be obtained using `generateKey()`, when the + * selected algorithm is one of the asymmetric algorithms: ECDSA or ECDH. + */ +export interface ManagedKeyPair { + /** + * A ManagedKey object representing the private key. For encryption and + * decryption algorithms, this key is used to decrypt. For signing and + * verification algorithms it is used to sign. + */ + privateKey: ManagedKey; + + /** + * A ManagedKey object representing the public key. For encryption and + * decryption algorithms, this key is used to encrypt. For signing and + * verification algorithms it is used to verify signatures. + */ + publicKey: ManagedKey; +} + +/** + * ManagedKeyStore + * + * This interface should be implemented to provide platform specific + * implementations that are usable by KeyManager and implementations + * of KeyManagementSystem. + * + * Implementations of this class can be used to store: + * ManagedKey and ManagedKeyPair + * or: + * ManagedPrivateKey + * objects. + * + * @public + */ +export interface ManagedKeyStore { + deleteKey(options: { id: K, agent?: Web5ManagedAgent, context?: string }): Promise + findKey(options: { id: K, agent?: Web5ManagedAgent, context?: string }): Promise; + findKey(options: { alias: K, agent?: Web5ManagedAgent, context?: string }): Promise; + getKey(options: { id: K, agent?: Web5ManagedAgent, context?: string }): Promise + importKey(options: { key: Omit, agent?: Web5ManagedAgent, context?: string }): Promise + listKeys(options?: { agent?: Web5ManagedAgent, context?: string }): Promise + updateKey(options: { id: K, agent?: Web5ManagedAgent, context?: string } & Partial): Promise +} + +/** + * Represents a private key. + * + * The `alias` is used to refer to the key material which is stored as the hex encoding of the raw byte array + * (`privateKeyHex`). + * + * The `type` refers to the type of key that is represented. + * + * @public + */ +export interface ManagedPrivateKey { + /** + * A unique identifier for the Key, autogenerated by a KMS. + */ + id: string + + /** + * Key material as raw binary data. + */ + material: Uint8Array; + + /** + * The type of key. + */ + type: Web5Crypto.PrivateKeyType; +} + +/** + * Input arguments for implementations of the CryptoManager interface {@link CryptoManager.sign | sign} method. + * + * @public + */ +export type SignOptions = { + /** + * An object that specifies the signature algorithm to use and its parameters. + */ + algorithm: Web5Crypto.AlgorithmIdentifier | Web5Crypto.EcdsaOptions | Web5Crypto.EdDsaOptions; + + /** + * An Uint8Array object containing the data to be signed. + */ + data: Uint8Array; + + /** + * An identifier of the ManagedKey to sign with. + * You can use the id or alias property of the key. + */ + keyRef: string; +} + +/** + * Input arguments for implementations of the CryptoManager interface + * {@link CryptoManager.updateKey | updateKey} method. + * + * @public + */ +export type UpdateKeyOptions = { + /** + * An alternate identifier used to identify the key in a KMS. + * This property can be used to associate a DID document key ID with a ManagedKey. + */ + alias?: string; + + /** + * An identifier of the ManagedKey to be used for decryption. + * You can use the id or alias property of the key. + */ + keyRef: string; + + /** + * Optional. Additional Key metadata. + */ + metadata?: KeyMetadata; +} + +/** + * Input arguments for implementations of the CryptoManager interface + * {@link CryptoManager.verify | verify} method. + * + * @public + */ +export type VerifyOptions = { + /** + * An object that specifies the algorithm to use and its parameters. + */ + algorithm: Web5Crypto.AlgorithmIdentifier | Web5Crypto.EcdsaOptions | Web5Crypto.EdDsaOptions; + + /** + * An Uint8Array object containing the data whose signature is to be verified. + */ + data: Uint8Array; + + /** + * An identifier of the ManagedKey to sign with. + * You can use the id or alias property of the key. + */ + keyRef: string; + + /** + * A Uint8Array containing the signature to verify. + */ + signature: Uint8Array; +} \ No newline at end of file diff --git a/packages/agent/src/utils.ts b/packages/agent/src/utils.ts new file mode 100644 index 000000000..c441d6e8e --- /dev/null +++ b/packages/agent/src/utils.ts @@ -0,0 +1,190 @@ +import type { JsonWebKey, Web5Crypto } from '@web5/crypto'; + +import { Jose } from '@web5/crypto'; +import { RequireOnly } from '@web5/common'; +import { Readable } from 'readable-stream'; +import { ReadableWebToNodeStream } from 'readable-web-to-node-stream'; + +import { ManagedKey, ManagedKeyPair, PortableKey, PortableKeyPair } from './types/managed-key.js'; + +export function blobToIsomorphicNodeReadable(blob: Blob): Readable { + return webReadableToIsomorphicNodeReadable(blob.stream() as ReadableStream); +} + +export function cryptoToManagedKey(options: { + cryptoKey: Web5Crypto.CryptoKey, + keyData: RequireOnly + }): ManagedKey { + const { cryptoKey, keyData } = options; + + const managedKey: ManagedKey = { + id : keyData.id ?? '', + algorithm : cryptoKey.algorithm, + alias : keyData.alias, + extractable : cryptoKey.extractable, + kms : keyData.kms, + material : (cryptoKey.type === 'public') ? cryptoKey.material : undefined, + metadata : keyData.metadata, + state : 'Enabled', + type : cryptoKey.type, + usages : cryptoKey.usages + }; + + return managedKey; +} + +export function cryptoToManagedKeyPair(options: { + cryptoKeyPair: Web5Crypto.CryptoKeyPair, + keyData: RequireOnly + }): ManagedKeyPair { + const { cryptoKeyPair, keyData } = options; + + const privateKey = cryptoKeyPair.privateKey; + const publicKey = cryptoKeyPair.publicKey; + + const managedKeyPair = { + privateKey: { + id : keyData.id ?? '', + algorithm : privateKey.algorithm, + alias : keyData.alias, + extractable : privateKey.extractable, + kms : keyData.kms, + metadata : keyData.metadata, + state : keyData.state, + type : privateKey.type, + usages : privateKey.usages + }, + + publicKey: { + id : keyData.id ?? '', + algorithm : publicKey.algorithm, + alias : keyData.alias, + extractable : publicKey.extractable, + kms : keyData.kms, + material : publicKey.material, + metadata : keyData.metadata, + state : keyData.state, + type : publicKey.type, + usages : publicKey.usages + }, + }; + + return managedKeyPair; +} + +export function cryptoToPortableKey(options: { + cryptoKey: Web5Crypto.CryptoKey, + keyData: RequireOnly + }): PortableKey { + const { cryptoKey, keyData } = options; + + const portableKey = { + id : keyData.id ?? '', + algorithm : cryptoKey.algorithm, + alias : keyData.alias, + extractable : cryptoKey.extractable, + kms : keyData.kms, + material : cryptoKey.material, + metadata : keyData.metadata, + type : cryptoKey.type, + usages : cryptoKey.usages + }; + + return portableKey; +} + +export function cryptoToPortableKeyPair(options: { + cryptoKeyPair: Web5Crypto.CryptoKeyPair, + keyData: RequireOnly + }): PortableKeyPair { + const { cryptoKeyPair, keyData } = options; + + const privateKey = cryptoKeyPair.privateKey; + const publicKey = cryptoKeyPair.publicKey; + + const portableKeyPair = { + privateKey: { + id : keyData.id ?? '', + algorithm : privateKey.algorithm, + alias : keyData.alias, + extractable : privateKey.extractable, + kms : keyData.kms, + material : privateKey.material, + metadata : keyData.metadata, + type : privateKey.type, + usages : privateKey.usages + }, + + publicKey: { + id : keyData.id ?? '', + algorithm : publicKey.algorithm, + alias : keyData.alias, + extractable : publicKey.extractable, + kms : keyData.kms, + material : publicKey.material, + metadata : keyData.metadata, + type : publicKey.type, + usages : publicKey.usages + }, + }; + + return portableKeyPair; +} + +/** + * Type guard function to check if the given key is a ManagedKey. + * + * @param key The key to check. + * @returns True if the key is a ManagedKeyPair, false otherwise. + */ +export function isManagedKey(key: ManagedKey | ManagedKeyPair | undefined): key is ManagedKey { + return key !== undefined && 'algorithm' in key && 'extractable' in key && 'type' in key && 'usages' in key; +} + +/** + * Type guard function to check if the given key is a ManagedKeyPair. + * + * @param key The key to check. + * @returns True if the key is a ManagedKeyPair, false otherwise. + */ +export function isManagedKeyPair(key: ManagedKey | ManagedKeyPair | undefined): key is ManagedKeyPair { + return key !== undefined && 'privateKey' in key && 'publicKey' in key; +} + +export async function managedKeyToJwk({ key }: { + key: RequireOnly +}): Promise { + if (key.material === undefined) { + throw new Error(`Could not convert to JWK: 'material' is undefined.`); + } + + const cryptoKey: Web5Crypto.CryptoKey = { + algorithm : key.algorithm, + extractable : key.extractable, + material : key.material, + type : key.type, + usages : key.usages + }; + + const jwk = await Jose.cryptoKeyToJwk({ key: cryptoKey }); + + return jwk; +} + +export function managedToCryptoKey({ key }: { + key: RequireOnly +}): Web5Crypto.CryptoKey { + const cryptoKey: Web5Crypto.CryptoKey = { + algorithm : key.algorithm, + extractable : key.extractable, + material : key.material, + type : key.type, + usages : key.usages + }; + + return cryptoKey; +} + +export function webReadableToIsomorphicNodeReadable(webReadable: ReadableStream) { + return new ReadableWebToNodeStream(webReadable); +} \ No newline at end of file diff --git a/packages/agent/tests/app-data-vault.spec.ts b/packages/agent/tests/app-data-vault.spec.ts new file mode 100644 index 000000000..92fb57b7b --- /dev/null +++ b/packages/agent/tests/app-data-vault.spec.ts @@ -0,0 +1,139 @@ +import type { Web5Crypto } from '@web5/crypto'; + +import chai, { expect } from 'chai'; +import chaiAsPromised from 'chai-as-promised'; +import { LevelStore, MemoryStore } from '@web5/common'; +import { CryptoKey, EdDsaAlgorithm } from '@web5/crypto'; + +import { AppDataVault } from '../src/app-data-store.js'; + +chai.use(chaiAsPromised); + +const testConfigurations = [ + { + name : 'MemoryStore', + dataStore : new MemoryStore() + }, + { + name : 'LevelStore', + dataStore : new LevelStore() + } +]; + +describe('AppDataVault', () => { + testConfigurations.forEach((test) => { + describe(`with ${test.name}`, () => { + + let dataVault: AppDataVault; + let dataStore = test.dataStore; + + before(() => { + dataVault = new AppDataVault({ store: dataStore, keyDerivationWorkFactor: 1 }); + }); + + beforeEach(() => { + dataStore.clear(); + }); + + afterEach(() => { + dataStore.clear(); + }); + + after(() => { + dataStore.close(); + }); + + describe('getPrivateKey()', () => { + let keyPair: Web5Crypto.CryptoKeyPair; + + beforeEach(async () => { + // Initialize and pre-populate the app data vault with a key pair. + const passphrase = 'dumbbell-krakatoa-ditty'; + keyPair = await EdDsaAlgorithm.create().generateKey({ + algorithm : { name: 'EdDSA', namedCurve: 'Ed25519' }, + extractable : true, + keyUsages : ['sign', 'verify'] + }); + await dataVault.initialize({ keyPair, passphrase }); + }); + + it('returns a private CryptoKey', async () => { + const privateKey = await dataVault.getPrivateKey(); + expect(privateKey).to.have.keys(['algorithm', 'extractable', 'type', 'usages']); + expect(privateKey).to.have.property('type', 'private'); + expect(privateKey.material).to.deep.equal(keyPair.privateKey.material); + }); + }); + + describe('getPublicKey()', () => { + let keyPair: Web5Crypto.CryptoKeyPair; + + beforeEach(async () => { + // Initialize and pre-populate the app data vault with a key pair. + const passphrase = 'dumbbell-krakatoa-ditty'; + keyPair = await EdDsaAlgorithm.create().generateKey({ + algorithm : { name: 'EdDSA', namedCurve: 'Ed25519' }, + extractable : true, + keyUsages : ['sign', 'verify'] + }); + await dataVault.initialize({ keyPair, passphrase }); + }); + + it('returns a public CryptoKey', async () => { + const publicKey = await dataVault.getPublicKey(); + expect(publicKey).to.have.keys(['algorithm', 'extractable', 'type', 'usages']); + expect(publicKey).to.have.property('type', 'public'); + expect(publicKey.material).to.deep.equal(keyPair.publicKey.material); + }); + }); + + describe('getStatus()', () => { + it('should return initialized=false when first instantiated', async () => { + const vaultStatus = await dataVault.getStatus(); + expect(vaultStatus.initialized).to.be.false; + expect(vaultStatus.lastBackup).to.be.undefined; + expect(vaultStatus.lastRestore).to.be.undefined; + }); + + it('should return initialized=true after initialization', async () => { + // Mock initialization having been completed. + dataStore.set('appDataStatus', { initialized: true }); + + const vaultStatus = await dataVault.getStatus(); + expect(vaultStatus.initialized).to.be.false; + expect(vaultStatus.lastBackup).to.be.undefined; + expect(vaultStatus.lastRestore).to.be.undefined; + }); + }); + + describe('initialize()', () => { + let keyPair = { + privateKey : new CryptoKey({ name: 'EdDSA', namedCurve: 'Ed25519' }, true, new Uint8Array(32), 'private', ['sign']), + publicKey : new CryptoKey({ name: 'EdDSA', namedCurve: 'Ed25519' }, true, new Uint8Array(32), 'public', ['verify']) + }; + + it('should initialize data vault when first instantiated', async () => { + let vaultStatus = await dataVault.getStatus(); + expect(vaultStatus.initialized).to.be.false; + + const passphrase = 'dumbbell-krakatoa-ditty'; + await dataVault.initialize({ keyPair, passphrase }); + vaultStatus = await dataVault.getStatus(); + expect(vaultStatus.initialized).to.be.true; + }); + + it('should throw if attempted on initialized data vault', async () => { + const vaultStatus = await dataVault.getStatus(); + expect(vaultStatus.initialized).to.be.false; + + const passphrase = 'dumbbell-krakatoa-ditty'; + await dataVault.initialize({ keyPair, passphrase }); + + await expect( + dataVault.initialize({ keyPair, passphrase }) + ).to.eventually.be.rejectedWith(Error, 'vault already initialized'); + }); + }); + }); + }); +}); \ No newline at end of file diff --git a/packages/agent/tests/did-manager.spec.ts b/packages/agent/tests/did-manager.spec.ts new file mode 100644 index 000000000..f13936ad7 --- /dev/null +++ b/packages/agent/tests/did-manager.spec.ts @@ -0,0 +1,815 @@ +import type { PrivateKeyJwk, PublicKeyJwk, Web5Crypto } from '@web5/crypto'; +import type { DidKeySet, PortableDid } from '@web5/dids'; + +import { expect } from 'chai'; +import { DidKeyMethod } from '@web5/dids'; +import { Jose, EdDsaAlgorithm } from '@web5/crypto'; + +import type { ManagedDid } from '../src/did-manager.js'; +import type { Web5ManagedAgent } from '../src/types/agent.js'; + +import { TestAgent } from './utils/test-agent.js'; +import { DidManager } from '../src/did-manager.js'; +import { TestManagedAgent } from '../src/test-managed-agent.js'; +import { DidStoreDwn, DidStoreMemory } from '../src/store-managed-did.js'; + +describe('DidManager', () => { + + describe('constructor', () => { + it('accepts an array of DID method implementations', () => { + expect( + new DidManager({ didMethods: [DidKeyMethod] }) + ).to.not.throw; + }); + + it('throws an exception if didMethods input is missing', () => { + expect(() => + // @ts-expect-error because an empty object is intentionally specified to trigger the error. + new DidManager({}) + ).to.throw(TypeError, `Required parameter missing: 'didMethods'`); + }); + }); + + describe('get agent', () => { + it(`returns the 'agent' instance property`, async () => { + // @ts-expect-error because we are only mocking a single property. + const mockAgent: Web5ManagedAgent = { + agentDid: 'did:method:abc123' + }; + const didManager = new DidManager({ didMethods: [DidKeyMethod], agent: mockAgent }); + const agent = didManager.agent; + expect(agent).to.exist; + expect(agent.agentDid).to.equal('did:method:abc123'); + }); + + it(`throws an error if the 'agent' instance property is undefined`, () => { + const didManager = new DidManager({ didMethods: [DidKeyMethod] }); + expect(() => + didManager.agent + ).to.throw(Error, 'Unable to determine agent execution context'); + }); + }); + + const agentStoreTypes = ['dwn', 'memory'] as const; + agentStoreTypes.forEach((agentStoreType) => { + + describe(`with ${agentStoreType} data stores`, () => { + let testAgent: TestManagedAgent; + + before(async () => { + testAgent = await TestManagedAgent.create({ + agentClass : TestAgent, + agentStores : agentStoreType + }); + }); + + beforeEach(async () => { + await testAgent.clearStorage(); + await testAgent.createAgentDid(); + }); + + after(async () => { + await testAgent.clearStorage(); + await testAgent.closeStorage(); + }); + + describe('create()', () => { + it('creates a did:key ManagedDid with keys if keySet is not given', async () => { + // Create a ManagedDid. + const managedDid = await testAgent.agent.didManager.create({ + method : 'key', + kms : 'local' + }); + + // Verify the result. + expect(managedDid).to.have.property('alias'); + expect(managedDid).to.have.property('did'); + expect(managedDid).to.have.property('document'); + expect(managedDid).to.have.property('metadata'); + expect(managedDid).to.have.property('method'); + }); + + it('creates a did:ion ManagedDid with keys if keySet is not given', async () => { + // Create a ManagedDid. + const managedDid = await testAgent.agent.didManager.create({ + method : 'ion', + kms : 'local' + }); + + // Verify the result. + expect(managedDid).to.have.property('alias'); + expect(managedDid).to.have.property('did'); + expect(managedDid).to.have.property('document'); + expect(managedDid).to.have.property('metadata'); + expect(managedDid).to.have.property('method'); + }).timeout(100000); + + it('adds generated keys to KeyManager if keySet is not given', async () => { + // Create a ManagedDid. + const managedDid = await testAgent.agent.didManager.create({ method: 'key', kms: 'local' }); + + // Attempt to retrieve the ManagedKeyPair from the KeyManager. + const signingKeyId = await testAgent.agent.didManager.getDefaultSigningKey({ did: managedDid.did }); + if (!signingKeyId) throw new Error('Type guard'); + const storedKeyPair = await testAgent.agent.keyManager.getKey({ keyRef: signingKeyId }); + + // Verify the key was found. + expect(storedKeyPair).to.exist; + expect(storedKeyPair).to.have.property('privateKey'); + expect(storedKeyPair).to.have.property('publicKey'); + }); + + it('updates KeyManager alias if keySet previously stored in KeyManager', async () => { + // Generate an Ed25519 signing key pair. + const keyPair = await testAgent.agent.keyManager.generateKey({ + algorithm : { name: 'EdDSA', namedCurve: 'Ed25519' }, + extractable : true, + keyUsages : ['sign', 'verify'], + kms : 'local' + }); + + // Create a ManagedDid using the keySet. + const keySet: DidKeySet = { + verificationMethodKeys: [{ + keyManagerId : keyPair.publicKey.id, + relationships : ['authentication'] + }] + }; + const managedDid = await testAgent.agent.didManager.create({ method: 'key', keySet, kms: 'local' }); + + // Attempt to retrieve the ManagedKeyPair from the KeyManager. + const signingKeyId = await testAgent.agent.didManager.getDefaultSigningKey({ did: managedDid.did }); + if (!signingKeyId) throw new Error('Type guard'); + const storedKeyPair = await testAgent.agent.keyManager.getKey({ keyRef: signingKeyId }); + + // Verify the key was found. + expect(storedKeyPair).to.exist; + expect(storedKeyPair).to.have.property('privateKey'); + expect(storedKeyPair).to.have.property('publicKey'); + }); + + it('updates KeyManager alias if keySet key pair not present in KeyManager', async () => { + // Generate an Ed25519 signing key pair. + const keyPair = await new EdDsaAlgorithm().generateKey({ + algorithm : { name: 'EdDSA', namedCurve: 'Ed25519' }, + extractable : true, + keyUsages : ['sign', 'verify'] + }); + + // Convert the key pair to JSON Web Key format. + const publicKeyJwk = await Jose.cryptoKeyToJwk({ key: keyPair.publicKey as Web5Crypto.CryptoKey }) as PublicKeyJwk; + const privateKeyJwk = await Jose.cryptoKeyToJwk({ key: keyPair.privateKey as Web5Crypto.CryptoKey }) as PrivateKeyJwk; + + // Create a ManagedDid using the keySet. + const keySet: DidKeySet = { + verificationMethodKeys: [{ + privateKeyJwk, + publicKeyJwk, + relationships: ['authentication'] + }] + }; + const managedDid = await testAgent.agent.didManager.create({ method: 'key', keySet, kms: 'local' }); + + // Attempt to retrieve the ManagedKeyPair from the KeyManager. + const signingKeyId = await testAgent.agent.didManager.getDefaultSigningKey({ did: managedDid.did }); + if (!signingKeyId) throw new Error('Type guard'); + const storedKeyPair = await testAgent.agent.keyManager.getKey({ keyRef: signingKeyId }); + + // Verify the key was found. + expect(storedKeyPair).to.exist; + expect(storedKeyPair).to.have.property('privateKey'); + expect(storedKeyPair).to.have.property('publicKey'); + }); + + it('updates KeyManager alias if keySet public key not present in KeyManager', async () => { + // Generate an Ed25519 signing key pair. + const keyPair = await new EdDsaAlgorithm().generateKey({ + algorithm : { name: 'EdDSA', namedCurve: 'Ed25519' }, + extractable : true, + keyUsages : ['sign', 'verify'] + }); + + // Convert the public key to JSON Web Key format. + const publicKeyJwk = await Jose.cryptoKeyToJwk({ key: keyPair.publicKey as Web5Crypto.CryptoKey }) as PublicKeyJwk; + + // Create a ManagedDid using the keySet. + const keySet: DidKeySet = { + verificationMethodKeys: [{ + publicKeyJwk, + relationships: ['authentication'] + }] + }; + const managedDid = await testAgent.agent.didManager.create({ + method : 'key', + keySet, + kms : 'local', + context : testAgent.agent.agentDid + }); + + // Attempt to retrieve the ManagedKey from the KeyManager. + const signingKeyId = await testAgent.agent.didManager.getDefaultSigningKey({ did: managedDid.did }); + if (!signingKeyId) throw new Error('Type guard'); + const storedPublicKey = await testAgent.agent.keyManager.getKey({ keyRef: signingKeyId }); + + // Verify the key was found. + expect(storedPublicKey).to.exist; + expect(storedPublicKey).to.have.property('type', 'public'); + }); + + it('throws an exception if keySet is missing publicKeyJwk and not present in KeyManager', async () => { + const keySet: DidKeySet = { verificationMethodKeys: [{ relationships: ['authentication'] }] + }; + + await expect( + testAgent.agent.didManager.create({ method: 'key', keySet }) + ).to.eventually.be.rejectedWith(Error, 'Required parameter(s) missing'); + }); + + it('throws an exception if keySet with privateKeyJwk is missing publicKeyJwk and not present in KeyManager', async () => { + // Generate an Ed25519 signing key pair. + const keyPair = await new EdDsaAlgorithm().generateKey({ + algorithm : { name: 'EdDSA', namedCurve: 'Ed25519' }, + extractable : true, + keyUsages : ['sign', 'verify'] + }); + + // Convert private key to JWK format. + const privateKeyJwk = await Jose.cryptoKeyToJwk({ key: keyPair.privateKey as Web5Crypto.CryptoKey }) as PrivateKeyJwk; + + const keySet: DidKeySet = { verificationMethodKeys: [{ privateKeyJwk, relationships: ['authentication'] }] + }; + + await expect( + testAgent.agent.didManager.create({ method: 'key', keySet }) + ).to.eventually.be.rejectedWith(Error, 'Required parameter(s) missing'); + }); + + // Tests that should only run for DWN-backed stores that provide multi-tenancy. + if (agentStoreType === 'dwn') { + it('creates DIDs under the tenant of the new DID, by default', async () => { + // Create a ManagedDid. + const managedDid = await testAgent.agent.didManager.create({ + method : 'key', + kms : 'local' + }); + + // Verify that the DID was NOT stored under the Agent's tenant. + let storedDid = await testAgent.agent.didManager.get({ didRef: managedDid.did }); + expect(storedDid).to.not.exist; + + // Verify that the DID WAS stored under the new DID's tenant. + storedDid = await testAgent.agent.didManager.get({ didRef: managedDid.did, context: managedDid.did }); + expect(storedDid).to.exist; + }); + + it('creates DIDs under the context of the specified DID', async () => { + // Create a ManagedDid. + const managedDid = await testAgent.agent.didManager.create({ + method : 'key', + kms : 'local', + context : testAgent.agent.agentDid + }); + + // Verify that the DID WAS stored under the Agent's tenant. + let storedDid = await testAgent.agent.didManager.get({ didRef: managedDid.did }); + expect(storedDid).to.exist; + + // Verify that the DID was NOT stored under the new DID's tenant. + storedDid = await testAgent.agent.didManager.get({ didRef: managedDid.did, context: managedDid.did }); + expect(storedDid).to.not.exist; + }); + } + }); + + describe('delete()', () => { + xit('should be implemented'); + }); + + describe('export()', () => { + xit('should be implemented'); + }); + + describe('import()', () => { + it('imports did:key DID and key set', async () => { + // Create did:key DID with key set to use to attempt import. + const portableDid = await DidKeyMethod.create({ keyAlgorithm: 'Ed25519' }); + + // Attempt to import the DID with DidManager under the Agent's context. + const managedDid = await testAgent.agent.didManager.import({ + did : portableDid, + kms : 'local', + context : testAgent.agent.agentDid + }); + + // Try to retrieve the DID from the DidManager store to verify it was imported. + const storedDid = await testAgent.agent.didManager.get({ didRef: managedDid.did }); + + if (storedDid === undefined) throw new Error('Type guard unexpectedly threw'); // Type guard. + expect(storedDid.did).to.equal(portableDid.did); + expect(storedDid.document).to.deep.equal(portableDid.document); + }); + + it('supports importing multiple DIDs to the same Identity/tenant', async () => { + // Create and import the first DID. + const did1 = await DidKeyMethod.create(); + const did1Import = await testAgent.agent.didManager.import({ + did : did1, + kms : 'local', + context : testAgent.agent.agentDid + }); + + // Create and import a second DID. + const did2 = await DidKeyMethod.create(); + const did2Import = await testAgent.agent.didManager.import({ + did : did2, + kms : 'local', + context : testAgent.agent.agentDid + }); + + // Verify that DID 1 WAS stored under the Agent's tenant. + let storedDid1 = await testAgent.agent.didManager.get({ didRef: did1Import.did }); + expect(storedDid1).to.exist; + expect(storedDid1?.did).to.equal(did1.did); + + // Verify that DID 2 WAS stored under the Agent's tenant. + let storedDid2 = await testAgent.agent.didManager.get({ didRef: did2Import.did }); + expect(storedDid2).to.exist; + expect(storedDid2?.did).to.equal(did2.did); + }); + + it('does not return private key JWK after import', async () => { + // Create did:key DID with key set to use to attempt import. + const portableDid = await DidKeyMethod.create({ keyAlgorithm: 'Ed25519' }); + + // Attempt to import the DID with DidManager. + const managedDid = await testAgent.agent.didManager.import({ did: portableDid, kms: 'local' }); + + // Verify private key material is not returned. + if (managedDid.keySet.verificationMethodKeys === undefined) throw new Error('Type guard unexpectedly threw'); // Type guard. + for (const key of managedDid.keySet.verificationMethodKeys) { + expect(key.privateKeyJwk).to.not.exist; + } + }); + + it('does not mutate DID input during import', async () => { + // Create did:key DID with key set to use to attempt import. + const portableDid = await DidKeyMethod.create({ keyAlgorithm: 'Ed25519' }); + + // Create a deep clone to use to check for side effects. + const portableDidClone = structuredClone(portableDid); + + // Import the DID with DidManager. + await testAgent.agent.didManager.import({ did: portableDid, kms: 'local' }); + + // Verify the input object was not mutated during import. + expect(portableDid).to.deep.equal(portableDidClone); + }); + + // Tests that should only run for DWN-backed stores that provide multi-tenancy. + if (agentStoreType === 'dwn') { + it('imports DIDs under the tenant of the new DID, by default', async () => { + // Create did:key DID with key set to use to attempt import. + const portableDid = await DidKeyMethod.create({ keyAlgorithm: 'Ed25519' }); + + // Attempt to import the DID with DidManager. + const managedDid = await testAgent.agent.didManager.import({ + did : portableDid, + kms : 'local' + }); + + // Verify that the DID was NOT stored under the Agent's tenant. + let storedDid = await testAgent.agent.didManager.get({ didRef: managedDid.did }); + expect(storedDid).to.not.exist; + + // Verify that the DID WAS stored under the new DID's tenant. + storedDid = await testAgent.agent.didManager.get({ didRef: managedDid.did, context: managedDid.did }); + expect(storedDid).to.exist; + }); + + it('imports DIDs under the context of the specified DID', async () => { + // Create did:key DID with key set to use to attempt import. + const portableDid = await DidKeyMethod.create({ keyAlgorithm: 'Ed25519' }); + + // Attempt to import the DID with DidManager. + const managedDid = await testAgent.agent.didManager.import({ + did : portableDid, + kms : 'local', + context : testAgent.agent.agentDid + }); + + // Verify that the DID was stored under the Agent's tenant. + let storedDid = await testAgent.agent.didManager.get({ didRef: managedDid.did }); + expect(storedDid).to.exist; + + // Verify that the DID was NOT stored under the new DID's tenant. + storedDid = await testAgent.agent.didManager.get({ didRef: managedDid.did, context: managedDid.did }); + expect(storedDid).to.not.exist; + }); + } + }); + + describe('update()', () => { + xit('should be implemented'); + }); + }); + }); +}); + +describe('DidStoreDwn', () => { + let didStoreDwn: DidStoreDwn; + let testAgent: TestAgent; + let testManagedDid: ManagedDid; + + before(async () => { + testAgent = await TestAgent.create(); + }); + + beforeEach(async () => { + didStoreDwn = new DidStoreDwn(); + + const didManager = new DidManager({ + didMethods : [DidKeyMethod], + store : didStoreDwn, + agent : testAgent + }); + + testAgent.didManager = didManager; + + // Create did:key DID with key set. + const portableDid = await DidKeyMethod.create({ keyAlgorithm: 'Ed25519' }); + testManagedDid = { ...portableDid, method: 'key' }; + }); + + afterEach(async () => { + await testAgent.clearStorage(); + }); + + after(async () => { + await testAgent.closeStorage(); + }); + + describe('deleteDid()', () => { + let portableDid: PortableDid; + + beforeEach(async () => { + // Create a DID for the test. + portableDid = await DidKeyMethod.create(); + }); + + it('should delete DID and return true if DID exists', async () => { + // Import the first DID and set as the Agent DID. + await testAgent.didManager.import({ did: portableDid }); + testAgent.agentDid = portableDid.did; + + // Test deleting the DID and validate the result. + const deleteResult = await didStoreDwn.deleteDid({ did: portableDid.did, agent: testAgent }); + expect(deleteResult).to.be.true; + + // Verify the DID is no longer in the store. + const storedDid = await didStoreDwn.getDid({ did: portableDid.did, agent: testAgent }); + expect(storedDid).to.be.undefined; + }); + + it('should return false if DID does not exist', async () => { + // Import the first DID and set as the Agent DID. + await testAgent.didManager.import({ did: portableDid }); + testAgent.agentDid = portableDid.did; + + // Test deleting the DID. + const deleteResult = await didStoreDwn.deleteDid({ did: 'non-existent', agent: testAgent }); + + // Validate the DID was not deleted. + expect(deleteResult).to.be.false; + }); + + it('throws an error if Agent DID is undefined and no keys exist for specified DID', async () => { + await expect( + didStoreDwn.deleteDid({ did: 'did:key:z6Mkt3UrUJXwrMzzBwt6XZ91aZYWk2GKvZbSgkZoEGdrRnB5', agent: testAgent }) + ).to.eventually.be.rejectedWith(Error, `Agent property 'agentDid' is undefined and no keys were found for`); + }); + }); + + describe('findDid()', () => { + let portableDid: PortableDid; + + beforeEach(async () => { + // Create a DID for the test. + portableDid = await DidKeyMethod.create(); + }); + + it('should return a DID by identifier if it exists', async () => { + // Import the DID to use for the test. + const importedDid = await testAgent.didManager.import({ did: portableDid, alias: 'social' }); + testAgent.agentDid = importedDid.did; + + // Test finding the DID. + const storedDid = await didStoreDwn.findDid({ did: importedDid.did, agent: testAgent}); + + // Verify the DID is in the store. + if (!storedDid) throw Error(); // Type guard. + expect(storedDid.did).to.equal(importedDid.did); + }); + + it('should return a DID by alias if it exists', async () => { + // Import the DID to use for the test. + const importedDid = await testAgent.didManager.import({ did: portableDid, alias: 'social' }); + testAgent.agentDid = importedDid.did; + + // Test finding the DID. + const storedDid = await didStoreDwn.findDid({ alias: 'social', agent: testAgent}); + + // Verify the DID is in the store. + if (!storedDid) throw Error(); // Type guard. + expect(storedDid.did).to.equal(importedDid.did); + }); + + it('should return undefined when attempting to get a non-existent DID', async () => { + // Import the first DID and set as the Agent DID. + const importedDid = await testAgent.didManager.import({ did: portableDid, alias: 'social' }); + testAgent.agentDid = importedDid.did; + + // Test finding the DID by ID. + expect( + await didStoreDwn.findDid({ did: 'non-existent-did', agent: testAgent }) + ).to.be.undefined; + + // Test finding the DID by alias. + expect( + await didStoreDwn.findDid({ alias: 'non-existent-did', agent: testAgent }) + ).to.be.undefined; + }); + + it('throws an error if Agent DID is undefined and no keys exist for specified DID', async () => { + await expect( + didStoreDwn.findDid({ did: 'did:key:z6Mkt3UrUJXwrMzzBwt6XZ91aZYWk2GKvZbSgkZoEGdrRnB5', agent: testAgent }) + ).to.eventually.be.rejectedWith(Error, `Agent property 'agentDid' is undefined and no keys were found for`); + }); + + it('throws an error if Agent DID is undefined when searching by alias', async () => { + await expect( + didStoreDwn.findDid({ alias: 'external-id', agent: testAgent }) + ).to.eventually.be.rejectedWith(Error, `Agent property 'agentDid' is undefined`); + }); + }); + + describe('getDid()', () => { + it('should return a DID by identifier if it exists', async () => { + // Create did:key DID with key set. + const portableDid = await DidKeyMethod.create(); + + // Import the DID to the DidManager DWN store. + const importedDid = await testAgent.didManager.import({ did: portableDid }); + + // Set the Agent's DID to the imported DID. + testAgent.agentDid = importedDid.did; + + // Test getting the DID. + const storedDid = await didStoreDwn.getDid({ did: portableDid.did, agent: testAgent }); + + // Verify the DID is in the store. + if (storedDid === undefined) throw new Error('Type guard unexpectedly threw'); // Type guard. + expect(storedDid.did).to.equal(importedDid.did); + expect(storedDid.method).to.equal(importedDid.method); + expect(storedDid.document).to.deep.equal(importedDid.document); + }); + + it('should return undefined when attempting to get a non-existent DID', async () => { + // Create did:key DID with key set. + const portableDid = await DidKeyMethod.create(); + + // Import the DID to the DidManager DWN store. + const importedDid = await testAgent.didManager.import({ did: portableDid }); + + // Set the Agent's DID to the imported DID. + testAgent.agentDid = importedDid.did; + + // Test getting the DID. + const storedDid = await didStoreDwn.getDid({ did: 'non-existent', agent: testAgent }); + + // Verify the result is undefined. + expect(storedDid).to.be.undefined; + }); + + it('throws an error if Agent DID is undefined and no keys exist for specified DID', async () => { + await expect( + didStoreDwn.getDid({ did: 'did:key:z6MkmRUyE6ywoYV2zL9Nus2YuFchpnTGzPXToZWDbdag6tvB', agent: testAgent }) + ).to.eventually.be.rejectedWith(Error, `Agent property 'agentDid' is undefined and no keys were found for`); + }); + }); + + describe('importDid()', () => { + it('imports did:key DID and key set', async () => { + // Create did:key DID with key set. + const portableDid = await DidKeyMethod.create({ keyAlgorithm: 'Ed25519' }); + const managedDid = { ...portableDid, method: 'key' }; + + // Import the key set into KeyManager. + // @ts-expect-error because we're accessing a private method. + managedDid.keySet = await testAgent.didManager.importOrGetKeySet({ + keySet : managedDid.keySet, + kms : 'memory' + }); + + // Set the alias for each key to the DID document method ID. + // @ts-expect-error because we're accessing a private method. + await testAgent.didManager.updateKeySet({ + canonicalId : managedDid.canonicalId, + didDocument : managedDid.document, + keySet : managedDid.keySet + }); + + // Import the first DID into the store. + await didStoreDwn.importDid({ + did : managedDid, + agent : testAgent + }); + + // Set the Agent's DID to the imported DID. + testAgent.agentDid = managedDid.did; + + // Try to retrieve the DID from the DidManager store to verify it was imported. + const storedDid = await didStoreDwn.getDid({ did: portableDid.did, agent: testAgent }); + + // Verify the DID is in the store. + if (storedDid === undefined) throw new Error('Type guard unexpectedly threw'); // Type guard. + expect(storedDid.did).to.equal(managedDid.did); + expect(storedDid.method).to.equal(managedDid.method); + expect(storedDid.document).to.deep.equal(managedDid.document); + }); + + it('throws an error when attempting to import a DID that already exists', async () => { + // Create did:key DID with key set. + const portableDid = await DidKeyMethod.create({ keyAlgorithm: 'Ed25519' }); + const managedDid = { ...portableDid, method: 'key' }; + + // Import the key set into KeyManager. + // @ts-expect-error because we're accessing a private method. + managedDid.keySet = await testAgent.didManager.importOrGetKeySet({ + keySet : managedDid.keySet, + kms : 'memory' + }); + + // Set the alias for each key to the DID document method ID. + // @ts-expect-error because we're accessing a private method. + await testAgent.didManager.updateKeySet({ + canonicalId : managedDid.canonicalId, + didDocument : managedDid.document, + keySet : managedDid.keySet + }); + + // Import the first DID into the store. + await didStoreDwn.importDid({ + did : managedDid, + agent : testAgent + }); + + // Set the Agent's DID to the imported DID. + testAgent.agentDid = managedDid.did; + + // Try to import the same key again. + await expect( + didStoreDwn.importDid({ + did : managedDid, + agent : testAgent + }) + ).to.eventually.be.rejectedWith(Error, 'DID with ID already exists'); + }); + + it('authors multiple imports with the same Agent DID', async () => { + // Create and import the Agent DID which will be used to author all record writes. + const portableAgentDid = await DidKeyMethod.create(); + await testAgent.didManager.import({ did: portableAgentDid }); + testAgent.agentDid = portableAgentDid.did; + + // Create two did:key DIDs with key sets to test import. + const portableDid2 = await DidKeyMethod.create(); + const managedDid2 = { ...portableDid2, method: 'key' }; + const portableDid3 = await DidKeyMethod.create(); + const managedDid3 = { ...portableDid3, method: 'key' }; + + // Import the two DIDs. + await didStoreDwn.importDid({ did: managedDid2, agent: testAgent }); + await didStoreDwn.importDid({ did: managedDid3, agent: testAgent }); + + // Get each DID and verify that all three were written under the Agent's DID tenant. + const storedDid1 = await didStoreDwn.getDid({ did: portableAgentDid.did, agent: testAgent }); + const storedDid2 = await didStoreDwn.getDid({ did: portableDid2.did, agent: testAgent }); + const storedDid3 = await didStoreDwn.getDid({ did: portableDid3.did, agent: testAgent }); + if (!(storedDid1 && storedDid2 && storedDid3)) throw new Error('Type guard unexpectedly threw'); // Type guard. + expect(storedDid1.did).to.equal(portableAgentDid.did); + expect(storedDid2.did).to.equal(portableDid2.did); + expect(storedDid3.did).to.equal(portableDid3.did); + }); + + it('throws an error if Agent DID is undefined and no keys exist for imported DID', async () => { + await expect( + didStoreDwn.importDid({ did: testManagedDid, agent: testAgent }) + ).to.eventually.be.rejectedWith(Error, `Agent property 'agentDid' is undefined and no keys were found for`); + }); + }); + + describe('listDids()', () => { + it('should return an array of all DIDs in the store', async () => { + // Create three did:key DIDs with key sets. + const portableDid1 = await DidKeyMethod.create(); + const portableDid2 = await DidKeyMethod.create(); + const portableDid3 = await DidKeyMethod.create(); + + // Import the first DID and set as the Agent DID. + const importedDid1 = await testAgent.didManager.import({ did: portableDid1 }); + testAgent.agentDid = importedDid1.did; + + // Import the other two DIDs under the same DID context. + const importedDid2 = await testAgent.didManager.import({ did: portableDid2, context: testAgent.agentDid }); + const importedDid3 = await testAgent.didManager.import({ did: portableDid3, context: testAgent.agentDid }); + + // List DIDs and verify the result. + const storedDids = await didStoreDwn.listDids({ agent: testAgent }); + expect(storedDids).to.have.length(3); + const importedDids = [importedDid1.did, importedDid2.did, importedDid3.did]; + for (const storedDid of storedDids) { + expect(importedDids).to.include(storedDid.did); + } + }); + + it('throws an error if Agent DID is undefined', async () => { + await expect( + didStoreDwn.listDids({ agent: testAgent }) + ).to.eventually.be.rejectedWith(Error, `Agent property 'agentDid' is undefined`); + }); + }); +}); + +describe('DidStoreMemory', () => { + let didStore: DidStoreMemory; + let testAgent: TestAgent; + let testManagedDid: ManagedDid; + + before(async () => { + testAgent = await TestAgent.create(); + }); + + beforeEach(async () => { + didStore = new DidStoreMemory(); + + const didManager = new DidManager({ + didMethods : [DidKeyMethod], + store : didStore, + agent : testAgent + }); + + testAgent.didManager = didManager; + + // Create did:key DID with key set. + const portableDid = await DidKeyMethod.create({ keyAlgorithm: 'Ed25519' }); + testManagedDid = { ...portableDid, method: 'key' }; + }); + + afterEach(async () => { + await testAgent.clearStorage(); + }); + + after(async () => { + await testAgent.closeStorage(); + }); + + describe('deleteIdentity()', () => { + it('should delete identity and return true if key exists', async () => { + // Import the identity. + await didStore.importDid({ did: testManagedDid }); + + // Test deleting the key and validate the result. + const deleteResult = await didStore.deleteDid({ did: testManagedDid.did }); + expect(deleteResult).to.be.true; + + // Verify the key is no longer in the store. + const storedKey = await didStore.getDid({ did: testManagedDid.did }); + expect(storedKey).to.be.undefined; + }); + + it('should return false if key does not exist', async () => { + // Test deleting the key. + const nonExistentId = '1234'; + const deleteResult = await didStore.deleteDid({ did: nonExistentId }); + + // Validate the key was not deleted. + expect(deleteResult).to.be.false; + }); + }); + + describe('getDid()', () => { + xit('tests needed'); + }); + + describe('findDid()', () => { + xit('tests needed'); + }); + + describe('importDid', () => { + xit('tests needed'); + }); + + describe('listDids', () => { + xit('tests needed'); + }); +}); \ No newline at end of file diff --git a/packages/agent/tests/dwn-manager.spec.ts b/packages/agent/tests/dwn-manager.spec.ts new file mode 100644 index 000000000..1a5354483 --- /dev/null +++ b/packages/agent/tests/dwn-manager.spec.ts @@ -0,0 +1,431 @@ +import chai, { expect } from 'chai'; +import chaiAsPromised from 'chai-as-promised'; +import { Convert } from '@web5/common'; +import { + Dwn, + RecordsRead, + RecordsReadReply, + RecordsQueryReply, + UnionMessageReply, + RecordsQueryMessage, + RecordsWriteMessage, + RecordsDeleteMessage, +} from '@tbd54566975/dwn-sdk-js'; + +import { TestAgent } from './utils/test-agent.js'; +import { DwnManager } from '../src/dwn-manager.js'; +import { ManagedIdentity } from '../src/identity-manager.js'; +import { TestManagedAgent } from '../src/test-managed-agent.js'; + +// NOTE: @noble/secp256k1 requires globalThis.crypto polyfill for node.js <=18: https://github.com/paulmillr/noble-secp256k1/blob/main/README.md#usage +// Remove when we move off of node.js v18 to v20, earliest possible time would be Oct 2023: https://github.com/nodejs/release#release-schedule +import { webcrypto } from 'node:crypto'; + +// @ts-ignore +if (!globalThis.crypto) globalThis.crypto = webcrypto; + +chai.use(chaiAsPromised); + +// let dwnNodes: string[] = ['https://dwn.tbddev.org/dwn0']; +let dwnNodes: string[] = ['http://localhost:3000']; + +describe('DwnManager', () => { + + describe('constructor', () => { + it('accepts a custom DWN instance', async () => { + const mockDwn = ({} as unknown) as Dwn; + + // Instantiate DWN Manager with custom DWN instance. + const dwnManager = await DwnManager.create({ dwn: mockDwn }); + + expect(dwnManager).to.exist; + // @ts-expect-error because a private property is being accessed. + expect(dwnManager._dwn).to.exist; + }); + }); + + describe('get agent', () => { + it(`returns the 'agent' instance property`, () => { + // @ts-expect-error because we are only mocking a single property. + const mockAgent: Web5ManagedAgent = { + agentDid: 'did:method:abc123' + }; + const mockDwn = ({} as unknown) as Dwn; + const dwnManager = new DwnManager({ agent: mockAgent, dwn: mockDwn }); + const agent = dwnManager.agent; + expect(agent).to.exist; + expect(agent.agentDid).to.equal('did:method:abc123'); + }); + + it(`throws an error if the 'agent' instance property is undefined`, async () => { + const mockDwn = ({} as unknown) as Dwn; + const dwnManager = await DwnManager.create({ dwn: mockDwn }); + expect(() => + dwnManager.agent + ).to.throw(Error, 'Unable to determine agent execution context'); + }); + }); + + describe(`with dwn data stores`, () => { + let testAgent: TestManagedAgent; + + before(async () => { + testAgent = await TestManagedAgent.create({ + agentClass : TestAgent, + agentStores : 'dwn' + }); + }); + + after(async () => { + await testAgent.clearStorage(); + await testAgent.closeStorage(); + }); + + describe('processRequest()', () => { + let identity: ManagedIdentity; + + beforeEach(async () => { + await testAgent.clearStorage(); + await testAgent.createAgentDid(); + // Creates a new Identity to author the DWN messages. + identity = await testAgent.agent.identityManager.create({ + name : 'Alice', + didMethod : 'key', + kms : 'local' + }); + }); + + xit('handles EventsGet'); + xit('handles MessagesGet'); + xit('handles ProtocolsConfigure'); + xit('handles ProtocolsQuery'); + + it('handles RecordsDelete messages', async () => { + // Create test data to write. + const dataBytes = Convert.string('Hello, world!').toUint8Array(); + + // Write a record that can be deleted. + let { message, reply: { status: writeStatus } } = await testAgent.agent.dwnManager.processRequest({ + author : identity.did, + target : identity.did, + messageType : 'RecordsWrite', + messageOptions : { + dataFormat : 'text/plain', + schema : 'https://schemas.xyz/example' + }, + dataStream: new Blob([dataBytes]) + }); + expect(writeStatus.code).to.equal(202); + const writeMessage = message as RecordsWriteMessage; + + // Attempt to process the RecordsRead. + const deleteResponse = await testAgent.agent.dwnManager.processRequest({ + author : identity.did, + target : identity.did, + messageType : 'RecordsDelete', + messageOptions : { + recordId: writeMessage.recordId + } + }); + + // Verify the response. + expect(deleteResponse).to.have.property('message'); + expect(deleteResponse).to.have.property('messageCid'); + expect(deleteResponse).to.have.property('reply'); + + const deleteMessage = deleteResponse.message as RecordsDeleteMessage; + expect(deleteMessage).to.have.property('authorization'); + expect(deleteMessage).to.have.property('descriptor'); + + const deleteReply = deleteResponse.reply as UnionMessageReply; + expect(deleteReply).to.have.property('status'); + expect(deleteReply.status.code).to.equal(202); + }); + + it('handles RecordsQuery messages', async () => { + // Create test data to write. + const dataBytes = Convert.string('Hello, world!').toUint8Array(); + + // Write a record that can be queried for. + let { message, reply: { status: writeStatus } } = await testAgent.agent.dwnManager.processRequest({ + author : identity.did, + target : identity.did, + messageType : 'RecordsWrite', + messageOptions : { + dataFormat : 'text/plain', + schema : 'https://schemas.xyz/example' + }, + dataStream: new Blob([dataBytes]) + }); + expect(writeStatus.code).to.equal(202); + const writeMessage = message as RecordsWriteMessage; + + // Attempt to process the RecordsQuery. + const queryResponse = await testAgent.agent.dwnManager.processRequest({ + author : identity.did, + target : identity.did, + messageType : 'RecordsQuery', + messageOptions : { + filter: { + schema: 'https://schemas.xyz/example' + } + } + }); + + // Verify the response. + expect(queryResponse).to.have.property('message'); + expect(queryResponse).to.have.property('messageCid'); + expect(queryResponse).to.have.property('reply'); + + const queryMessage = queryResponse.message as RecordsQueryMessage; + expect(queryMessage).to.have.property('authorization'); + expect(queryMessage).to.have.property('descriptor'); + + const queryReply = queryResponse.reply as RecordsQueryReply; + expect(queryReply).to.have.property('status'); + expect(queryReply.status.code).to.equal(200); + expect(queryReply.entries).to.exist; + expect(queryReply.entries).to.have.length(1); + expect(queryReply.entries?.[0]).to.have.property('descriptor'); + expect(queryReply.entries?.[0]).to.have.property('encodedData'); + expect(queryReply.entries?.[0]).to.have.property('recordId', writeMessage.recordId); + }); + + it('handles RecordsRead messages', async () => { + // Create test data to write. + const dataBytes = Convert.string('Hello, world!').toUint8Array(); + + // Write a record that can be read. + let { message, reply: { status: writeStatus } } = await testAgent.agent.dwnManager.processRequest({ + author : identity.did, + target : identity.did, + messageType : 'RecordsWrite', + messageOptions : { + dataFormat : 'text/plain', + schema : 'https://schemas.xyz/example' + }, + dataStream: new Blob([dataBytes]) + }); + expect(writeStatus.code).to.equal(202); + const writeMessage = message as RecordsWriteMessage; + + // Attempt to process the RecordsRead. + const readResponse = await testAgent.agent.dwnManager.processRequest({ + author : identity.did, + target : identity.did, + messageType : 'RecordsRead', + messageOptions : { + recordId: writeMessage.recordId + } + }); + + // Verify the response. + expect(readResponse).to.have.property('message'); + expect(readResponse).to.have.property('messageCid'); + expect(readResponse).to.have.property('reply'); + + const readMessage = readResponse.message; + expect(readMessage).to.have.property('authorization'); + expect(readMessage).to.have.property('descriptor'); + + const readReply = readResponse.reply as RecordsReadReply; + expect(readReply).to.have.property('status'); + expect(readReply.status.code).to.equal(200); + expect(readReply).to.have.property('record'); + expect(readReply.record).to.have.property('data'); + expect(readReply.record).to.have.property('descriptor'); + expect(readReply.record).to.have.property('recordId', writeMessage.recordId); + }); + + it('handles RecordsWrite messages', async () => { + // Create test data to write. + const dataBytes = Convert.string('Hello, world!').toUint8Array(); + + // Attempt to process the RecordsWrite + let writeResponse = await testAgent.agent.dwnManager.processRequest({ + author : identity.did, + target : identity.did, + messageType : 'RecordsWrite', + messageOptions : { + dataFormat: 'text/plain' + }, + dataStream: new Blob([dataBytes]) + }); + + // Verify the response. + expect(writeResponse).to.have.property('message'); + expect(writeResponse).to.have.property('messageCid'); + expect(writeResponse).to.have.property('reply'); + + const writeMessage = writeResponse.message as RecordsWriteMessage; + expect(writeMessage).to.have.property('authorization'); + expect(writeMessage).to.have.property('descriptor'); + expect(writeMessage).to.have.property('recordId'); + + const writeReply = writeResponse.reply; + expect(writeReply).to.have.property('status'); + expect(writeReply.status.code).to.equal(202); + }); + }); + + describe('sendDwnRequest()', () => { + let identity: ManagedIdentity; + + before(async () => { + await testAgent.createAgentDid(); + + const services = [{ + id : 'dwn', + type : 'DecentralizedWebNode', + serviceEndpoint : { + encryptionKeys : ['#dwn-enc'], + nodes : dwnNodes, + signingKeys : ['#dwn-sig'] + } + }]; + + // Creates a new Identity to author the DWN messages. + identity = await testAgent.agent.identityManager.create({ + name : 'Alice', + didMethod : 'ion', + didOptions : { services }, + kms : 'local' + }); + }); + + after(async () => { + await testAgent.clearStorage(); + }); + + it('throws an exception if target DID cannot be resolved', async () => { + await expect( + testAgent.agent.sendDwnRequest({ + author : identity.did, + target : 'did:test:abc123', + messageType : 'RecordsQuery', + messageOptions : { + filter: { + schema: 'https://schemas.xyz/example' + } + } + }) + ).to.eventually.be.rejectedWith(Error, 'DwnManager: methodNotSupported: Method not supported: test'); + }); + + it('throws an exception if target DID has no #dwn service endpoints', async () => { + const identity = await testAgent.agent.identityManager.create({ + name : 'Alice', + didMethod : 'ion', + didOptions : { services: [] }, + kms : 'local' + }); + + await expect( + testAgent.agent.sendDwnRequest({ + author : identity.did, + target : identity.did, + messageType : 'RecordsQuery', + messageOptions : { + filter: { + schema: 'https://schemas.xyz/example' + } + } + }) + ).to.eventually.be.rejectedWith(Error, `has no service endpoints with ID '#dwn'`); + }); + + it('handles RecordsDelete Messages', async () => { + const response = await testAgent.agent.sendDwnRequest({ + author : identity.did, + target : identity.did, + messageType : 'RecordsDelete', + messageOptions : { + recordId: 'abcd123' + } + }); + + expect(response.reply).to.exist; + expect(response.reply.status).to.exist; + expect(response.reply.status.code).to.equal(404); + }); + + it('handles RecordsQuery Messages', async () => { + const response = await testAgent.agent.sendDwnRequest({ + author : identity.did, + target : identity.did, + messageType : 'RecordsQuery', + messageOptions : { + filter: { + schema: 'https://schemas.xyz/example' + } + } + }); + + expect(response.reply).to.exist; + expect(response.message).to.exist; + expect(response.messageCid).to.exist; + expect(response.reply.status).to.exist; + expect(response.reply.entries).to.exist; + expect(response.reply.status.code).to.equal(200); + }); + + it('handles RecordsRead Messages', async () => { + const dataBytes = Convert.string('Hi').toUint8Array(); + + let response = await testAgent.agent.sendDwnRequest({ + author : identity.did, + target : identity.did, + messageType : 'RecordsWrite', + messageOptions : { + dataFormat : 'text/plain', + data : dataBytes + }, + dataStream: new Blob([dataBytes]) + }); + + const message = response.message as RecordsWriteMessage; + + response = await testAgent.agent.sendDwnRequest({ + author : identity.did, + target : identity.did, + messageType : 'RecordsRead', + messageOptions : { + recordId: message.recordId + } + }); + + expect(response.reply.status.code).to.equal(200); + expect(response.message).to.exist; + + const readMessage = response.message as RecordsRead['message']; + expect(readMessage.descriptor.method).to.equal('Read'); + expect(readMessage.descriptor.interface).to.equal('Records'); + + const readReply = response.reply as RecordsReadReply; + expect(readReply.record).to.exist; + + const record = readReply.record as unknown as RecordsWriteMessage & { data: ReadableStream }; + expect(record.recordId).to.equal(message.recordId); + + expect(record.data).to.exist; + expect(record.data instanceof ReadableStream).to.be.true; + + const { value } = await record.data.getReader().read(); + expect(dataBytes).to.eql(value); + }); + + it('throws an error when DwnRequest fails validation', async () => { + await expect( + testAgent.agent.sendDwnRequest({ + author : identity.did, + target : identity.did, + messageType : 'RecordsQuery', + messageOptions : { + filter: true + } + }) + ).to.eventually.be.rejectedWith(Error, '/descriptor/filter: must NOT have fewer than 1 properties'); + }); + }); + }); +}); \ No newline at end of file diff --git a/packages/agent/tests/identity-manager.spec.ts b/packages/agent/tests/identity-manager.spec.ts new file mode 100644 index 000000000..809e41c80 --- /dev/null +++ b/packages/agent/tests/identity-manager.spec.ts @@ -0,0 +1,772 @@ +import { expect } from 'chai'; +import { DidKeyMethod } from '@web5/dids'; + +import type { ManagedDid } from '../src/did-manager.js'; +import type { Web5ManagedAgent } from '../src/types/agent.js'; +import type { ManagedIdentity } from '../src/identity-manager.js'; + +import { TestAgent } from './utils/test-agent.js'; +import { IdentityManager } from '../src/identity-manager.js'; +import { TestManagedAgent } from '../src/test-managed-agent.js'; +import { IdentityStoreDwn, IdentityStoreMemory } from '../src/store-managed-identity.js'; + +describe('IdentityManager', () => { + describe('get agent', () => { + it(`returns the 'agent' instance property`, async () => { + // @ts-expect-error because we are only mocking a single property. + const mockAgent: Web5ManagedAgent = { + agentDid: 'did:method:abc123' + }; + const identityManager = new IdentityManager({ agent: mockAgent }); + const agent = identityManager.agent; + expect(agent).to.exist; + expect(agent.agentDid).to.equal('did:method:abc123'); + }); + + it(`throws an error if the 'agent' instance property is undefined`, () => { + const identityManager = new IdentityManager(); + expect(() => + identityManager.agent + ).to.throw(Error, 'Unable to determine agent execution context'); + }); + }); + + const agentStoreTypes = ['dwn', 'memory'] as const; + agentStoreTypes.forEach((agentStoreType) => { + + describe(`with ${agentStoreType} data stores`, () => { + let testAgent: TestManagedAgent; + + before(async () => { + testAgent = await TestManagedAgent.create({ + agentClass : TestAgent, + agentStores : agentStoreType + }); + }); + + beforeEach(async () => { + await testAgent.clearStorage(); + await testAgent.createAgentDid(); + }); + + afterEach(async () => { + await testAgent.clearStorage(); + }); + + after(async () => { + await testAgent.clearStorage(); + await testAgent.closeStorage(); + }); + + describe('create()', () => { + it('creates a ManagedIdentity with new DID and keys', async () => { + // Create a ManagedIdentity. + const managedIdentity = await testAgent.agent.identityManager.create({ + didMethod : 'key', + name : 'Alice', + kms : 'local' + }); + + // Verify the ManagedIdentity was created with the expected properties. + expect(managedIdentity).to.have.property('did'); + expect(managedIdentity).to.have.property('name'); + + // Confirm the DID was stored in the DidManager store. + const managedDid = await testAgent.agent.didManager.get({ + didRef : managedIdentity.did, + context : managedIdentity.did + }); + expect(managedDid).to.exist; + + // Confirm the keys were stored in the KeyManager store. + if (managedDid === undefined) throw new Error(); + const signingKeyId = await testAgent.agent.didManager.getDefaultSigningKey({ + did: managedDid.did + }); + if (!signingKeyId) throw new Error('Type guard'); + const keyPair = await testAgent.agent.keyManager.getKey({ keyRef: signingKeyId }); + expect(keyPair).to.exist; + expect(keyPair).to.have.property('privateKey'); + expect(keyPair).to.have.property('publicKey'); + }); + + it('creates a ManagedIdentity using an existing DID and key set', async () => { + // Create did:key DID with key set to use to attempt import. + const portableDid = await DidKeyMethod.create({ keyAlgorithm: 'Ed25519' }); + + // Create a ManagedIdentity. + const managedIdentity = await testAgent.agent.identityManager.create({ + did : portableDid, + name : 'Alice', + kms : 'local' + }); + + // Verify the ManagedIdentity was created with the expected properties. + expect(managedIdentity).to.have.property('did', portableDid.did); + expect(managedIdentity).to.have.property('name'); + + // Confirm the DID was stored in the DidManager store. + const managedDid = await testAgent.agent.didManager.get({ + didRef : managedIdentity.did, + context : managedIdentity.did + }); + expect(managedDid).to.exist; + + // Confirm the keys were stored in the KeyManager store. + if (managedDid === undefined) throw new Error('Type guard unexpectedly threw'); // Type guard. + const signingKeyId = await testAgent.agent.didManager.getDefaultSigningKey({ + did: managedDid.did + }); + if (!signingKeyId) throw new Error('Type guard'); + const keyPair = await testAgent.agent.keyManager.getKey({ + keyRef: signingKeyId + }); + expect(keyPair).to.exist; + expect(keyPair).to.have.property('privateKey'); + expect(keyPair).to.have.property('publicKey'); + }); + + // Tests that should only run for DWN-backed stores that provide multi-tenancy. + if (agentStoreType === 'dwn') { + it('creates Identities under the tenant of the new Identity, by default', async () => { + // Create a ManagedDid. + const managedIdentity = await testAgent.agent.identityManager.create({ + didMethod : 'key', + name : 'Alice', + kms : 'local' + }); + + // Verify that the Identity was NOT stored under the Agent's tenant. + let storedIdentity = await testAgent.agent.identityManager.get({ did: managedIdentity.did }); + expect(storedIdentity).to.not.exist; + + // Verify that the Identity WAS stored under the new Identity's tenant. + storedIdentity = await testAgent.agent.identityManager.get({ did: managedIdentity.did, context: managedIdentity.did }); + expect(storedIdentity).to.exist; + }); + + it('creates Identities under the context of the specified DID', async () => { + // Create a ManagedDid. + const managedDid = await testAgent.agent.identityManager.create({ + didMethod : 'key', + name : 'Alice', + kms : 'local', + context : testAgent.agent.agentDid + }); + + // Verify that the Identity WAS stored under the Agent's tenant. + let storedIdentity = await testAgent.agent.identityManager.get({ did: managedDid.did }); + expect(storedIdentity).to.exist; + + // Verify that the Identity was NOT stored under the new Identity's tenant. + storedIdentity = await testAgent.agent.identityManager.get({ did: managedDid.did, context: managedDid.did }); + expect(storedIdentity).to.not.exist; + }); + + it('supports creating a new Identity and importing to Agent tenant', async () => { + // Create a ManagedDid, stored under the new Identity's tenant. + const managedIdentity = await testAgent.agent.identityManager.create({ + didMethod : 'key', + name : 'Alice', + kms : 'local' + }); + + // Attempt to import just the Identity (not DID/keys) to the Agent tenant. + const importedIdentity = await testAgent.agent.identityManager.import({ + context : testAgent.agent.agentDid, + identity : managedIdentity, + kms : 'local' + }); + expect(importedIdentity).to.deep.equal(managedIdentity); + + // Verify that the Identity is stored under the Agent's tenant. + let storedIdentity = await testAgent.agent.identityManager.get({ did: managedIdentity.did }); + expect(storedIdentity).to.exist; + + // Verify that the Identity is also stored under the new Identity's tenant. + storedIdentity = await testAgent.agent.identityManager.get({ did: managedIdentity.did, context: managedIdentity.did }); + expect(storedIdentity).to.exist; + + // Verify the DID ONLY exists under the new Identity's tenant. + let storedDidAgent = await testAgent.agent.didManager.get({ didRef: managedIdentity.did }); + let storedDidNewIdentity = await testAgent.agent.didManager.get({ didRef: managedIdentity.did, context: managedIdentity.did }); + expect(storedDidAgent).to.not.exist; + expect(storedDidNewIdentity).to.exist; + }); + } + }); + + describe('export()', () => { + xit('should be implemented'); + }); + + describe('import()', () => { + it('imports Identity, DID and key set', async () => { + // Create did:key DID with key set to use to attempt import. + const portableDid = await DidKeyMethod.create({ keyAlgorithm: 'Ed25519' }); + + // Create ManagedIdentity to use to attempt import. + const managedIdentity: ManagedIdentity = { + did : portableDid.did, + name : 'Test' + }; + + // Attempt to import the Identity. + const importedIdentity = await testAgent.agent.identityManager.import({ + did : portableDid, + identity : managedIdentity, + kms : 'local' + }); + expect(importedIdentity).to.deep.equal(managedIdentity); + }); + + it('supports importing Identity without DID', async () => { + // Define a DID to use for the test. + const did = 'did:key:z6MkwcqjW8kmk23GVYHRdyfNr4e7eEoKs3MyuGia1TeSd9hk'; + + // Create ManagedIdentity to use to attempt import. + const managedIdentity: ManagedIdentity = { did, name: 'Test' }; + + // Attempt to import the Identity. + const importedIdentity = await testAgent.agent.identityManager.import({ + context : testAgent.agent.agentDid, + identity : managedIdentity, + kms : 'local' + }); + expect(importedIdentity).to.deep.equal(managedIdentity); + + // Verify that Identity WAS stored under the Agent's tenant. + let storedIdentity = await testAgent.agent.identityManager.get({ did: importedIdentity.did }); + expect(storedIdentity).to.exist; + expect(storedIdentity?.did).to.equal(did); + + // Verify no DID exists matching the specified identifier. + let storedDid = await testAgent.agent.didManager.get({ didRef: did }); + expect(storedDid).to.not.exist; + }); + + it('supports importing multiple Identities to the same Identity/tenant', async () => { + // Create and import the first Identity and DID. + const portableDid1 = await DidKeyMethod.create({ keyAlgorithm: 'Ed25519' }); + const managedIdentity1: ManagedIdentity = { did: portableDid1.did, name: 'Test' }; + const importedIdentity1 = await testAgent.agent.identityManager.import({ + context : testAgent.agent.agentDid, + did : portableDid1, + identity : managedIdentity1, + kms : 'local' + }); + + // Create and import a second DID. + const portableDid2 = await DidKeyMethod.create({ keyAlgorithm: 'Ed25519' }); + const managedIdentity2: ManagedIdentity = { did: portableDid2.did, name: 'Test' }; + const importedIdentity2 = await testAgent.agent.identityManager.import({ + context : testAgent.agent.agentDid, + did : portableDid2, + identity : managedIdentity2, + kms : 'local' + }); + + // Verify that Identity 1 WAS stored under the Agent's tenant. + let storedIdentity1 = await testAgent.agent.identityManager.get({ did: importedIdentity1.did }); + expect(storedIdentity1).to.exist; + expect(storedIdentity1?.did).to.equal(portableDid1.did); + + // Verify that Identity 2 WAS stored under the Agent's tenant. + let storedIdentity2 = await testAgent.agent.identityManager.get({ did: importedIdentity2.did }); + expect(storedIdentity2).to.exist; + expect(storedIdentity2?.did).to.equal(portableDid2.did); + }); + + // Tests that should only run for DWN-backed stores that provide multi-tenancy. + if (agentStoreType === 'dwn') { + it('imports Identities under the tenant of the new Identity, by default', async () => { + // Create did:key DID with key set to use to attempt import. + const portableDid = await DidKeyMethod.create({ keyAlgorithm: 'Ed25519' }); + + // Create ManagedIdentity to use to attempt import. + const managedIdentity: ManagedIdentity = { + did : portableDid.did, + name : 'Test' + }; + + // Attempt to import the Identity. + const importedIdentity = await testAgent.agent.identityManager.import({ + did : portableDid, + identity : managedIdentity, + kms : 'local' + }); + expect(importedIdentity).to.deep.equal(managedIdentity); + + // Verify that the Identity was NOT stored under the Agent's tenant. + let storedIdentity = await testAgent.agent.identityManager.get({ did: managedIdentity.did }); + expect(storedIdentity).to.not.exist; + + // Verify that the Identity WAS stored under the new Identity's tenant. + storedIdentity = await testAgent.agent.identityManager.get({ did: managedIdentity.did, context: managedIdentity.did }); + expect(storedIdentity).to.exist; + }); + + it('imports Identities under the context of the specified DID', async () => { + // Create did:key DID with key set to use to attempt import. + const portableDid = await DidKeyMethod.create({ keyAlgorithm: 'Ed25519' }); + + // Create ManagedIdentity to use to attempt import. + const managedIdentity: ManagedIdentity = { + did : portableDid.did, + name : 'Test' + }; + + // Attempt to import the Identity. + const importedIdentity = await testAgent.agent.identityManager.import({ + context : testAgent.agent.agentDid, + did : portableDid, + identity : managedIdentity, + kms : 'local' + }); + expect(importedIdentity).to.deep.equal(managedIdentity); + + // Verify that the Identity was stored under the Agent's tenant. + let storedIdentity = await testAgent.agent.identityManager.get({ did: managedIdentity.did }); + expect(storedIdentity).to.exist; + + // Verify that the Identity was NOT stored under the new Identity's tenant. + storedIdentity = await testAgent.agent.identityManager.get({ did: managedIdentity.did, context: managedIdentity.did }); + expect(storedIdentity).to.not.exist; + }); + + it('throws error if importing Identity without DID if keys for DID not present', async () => { + // Define a DID to use for the test. + const did = 'did:key:z6MkwcqjW8kmk23GVYHRdyfNr4e7eEoKs3MyuGia1TeSd9hk'; + + // Create ManagedIdentity to use to attempt import. + const managedIdentity: ManagedIdentity = { did, name: 'Test' }; + + // Attempt to import the Identity. + await expect( + testAgent.agent.identityManager.import({ + identity : managedIdentity, + kms : 'local' + }) + ).to.eventually.be.rejectedWith(Error, `Signing key not found for author: '${did}'`); + }); + } + }); + + describe('list()', () => { + it('should return an array of all identities', async () => { + // Create three new identities all under the Agent's tenant. + const managedIdentity1 = await testAgent.agent.identityManager.create({ + didMethod : 'key', + name : 'Alice', + kms : 'local', + context : testAgent.agent.agentDid + }); + const managedIdentity2 = await testAgent.agent.identityManager.create({ + didMethod : 'key', + name : 'Alice', + kms : 'local', + context : testAgent.agent.agentDid + }); + const managedIdentity3 = await testAgent.agent.identityManager.create({ + didMethod : 'key', + name : 'Alice', + kms : 'local', + context : testAgent.agent.agentDid + }); + + // List identities and verify the result. + const storedIdentities = await testAgent.agent.identityManager.list(); + expect(storedIdentities).to.have.length(3); + + const createdIdentities = [managedIdentity1.did, managedIdentity2.did, managedIdentity3.did]; + for (const storedIdentity of storedIdentities) { + expect(createdIdentities).to.include(storedIdentity.did); + } + }); + + it('should return an empty array if the store contains no Identities', async () => { + // List identities and verify the result is empty. + const storedIdentities = await testAgent.agent.identityManager.list(); + expect(storedIdentities).to.be.empty; + }); + }); + }); + }); +}); + +describe('IdentityStoreDwn', () => { + let identityStore: IdentityStoreDwn; + let testAgent: TestAgent; + let testManagedDid: ManagedDid; + let testManagedIdentity: ManagedIdentity; + + before(async () => { + testAgent = await TestAgent.create(); + }); + + beforeEach(async () => { + identityStore = new IdentityStoreDwn(); + const identityManager = new IdentityManager({ store: identityStore, agent: testAgent }); + testAgent.identityManager = identityManager; + + // Create did:key DID with key set. + const portableDid = await DidKeyMethod.create({ keyAlgorithm: 'Ed25519' }); + testManagedDid = { ...portableDid, method: 'key' }; + + // Import the key set into KeyManager. + // @ts-expect-error because we're accessing a private method. + testManagedDid.keySet = await testAgent.didManager.importOrGetKeySet({ + keySet : testManagedDid.keySet, + kms : 'memory' + }); + + // Set the alias for each key to the DID document method ID. + // @ts-expect-error because we're accessing a private method. + await testAgent.didManager.updateKeySet({ + canonicalId : testManagedDid.canonicalId, + didDocument : testManagedDid.document, + keySet : testManagedDid.keySet + }); + + // Create a ManagedIdentity to use for tests. + testManagedIdentity = { did: portableDid.did, name: 'Test' }; + }); + + afterEach(async () => { + await testAgent.clearStorage(); + }); + + after(async () => { + await testAgent.closeStorage(); + }); + + describe('deleteIdentity()', () => { + it('should delete Identity and return true if Identity exists', async () => { + // Import the first Identity and set as the Agent DID. + await identityStore.importIdentity({ + identity : testManagedIdentity, + agent : testAgent + }); + testAgent.agentDid = testManagedIdentity.did; + + // Test deleting the DID and validate the result. + const deleteResult = await identityStore.deleteIdentity({ did: testManagedIdentity.did, agent: testAgent }); + expect(deleteResult).to.be.true; + + // Verify the DID is no longer in the store. + const storedDid = await identityStore.getIdentity({ did: testManagedIdentity.did, agent: testAgent }); + expect(storedDid).to.be.undefined; + }); + + it('should return false if Identity does not exist', async () => { + // Import the first Identity and set as the Agent DID. + await identityStore.importIdentity({ + identity : testManagedIdentity, + agent : testAgent + }); + testAgent.agentDid = testManagedIdentity.did; + + // Test deleting the DID. + const deleteResult = await identityStore.deleteIdentity({ did: 'non-existent', agent: testAgent }); + + // Validate the DID was not deleted. + expect(deleteResult).to.be.false; + }); + + it('throws an error if Agent DID is undefined and no keys exist for specified DID', async () => { + await expect( + identityStore.deleteIdentity({ did: 'did:key:z6Mkt3UrUJXwrMzzBwt6XZ91aZYWk2GKvZbSgkZoEGdrRnB5', agent: testAgent }) + ).to.eventually.be.rejectedWith(Error, `Agent property 'agentDid' is undefined and no keys were found for`); + }); + }); + + describe('getIdentity()', () => { + it('should return an Identity by DID if it exists', async () => { + // Import the first Identity and set as the Agent DID. + await identityStore.importIdentity({ + identity : testManagedIdentity, + agent : testAgent + }); + testAgent.agentDid = testManagedIdentity.did; + + // Try to retrieve the Identity from the IdentityManager store to verify it was imported. + const storedIdentity = await identityStore.getIdentity({ did: testManagedIdentity.did, agent: testAgent }); + + // Verify the Identity is in the store. + if (storedIdentity === undefined) throw new Error('Type guard unexpectedly threw'); // Type guard. + expect(storedIdentity.did).to.equal(testManagedIdentity.did); + expect(storedIdentity.name).to.equal(testManagedIdentity.name); + }); + + it('should return undefined when attempting to get a non-existent Identity', async () => { + // Import the first Identity and set as the Agent DID. + await identityStore.importIdentity({ + identity : testManagedIdentity, + agent : testAgent + }); + testAgent.agentDid = testManagedIdentity.did; + + // Try to retrieve a non-existent Identity from the IdentityManager store. + const storedIdentity = await identityStore.getIdentity({ did: 'non-existant', agent: testAgent }); + + // Verify the result is undefined. + expect(storedIdentity).to.be.undefined; + }); + + it('throws an error if Agent DID is undefined and no keys exist for specified DID', async () => { + await expect( + identityStore.getIdentity({ + did : 'did:key:z6MkmRUyE6ywoYV2zL9Nus2YuFchpnTGzPXToZWDbdag6tvB', + agent : testAgent + }) + ).to.eventually.be.rejectedWith(Error, `Agent property 'agentDid' is undefined and no keys were found for`); + }); + }); + + describe('importIdentity()', () => { + it('should import an identity that does not already exist', async () => { + // Import the first Identity and set as the Agent DID. + await identityStore.importIdentity({ + identity : testManagedIdentity, + agent : testAgent + }); + testAgent.agentDid = testManagedIdentity.did; + + // Try to retrieve the Identity from the IdentityManager store to verify it was imported. + const storedIdentity = await identityStore.getIdentity({ did: testManagedIdentity.did, agent: testAgent }); + + // Verify the Identity is in the store. + if (storedIdentity === undefined) throw new Error('Type guard unexpectedly threw'); // Type guard. + expect(storedIdentity.did).to.equal(testManagedIdentity.did); + expect(storedIdentity.name).to.equal(testManagedIdentity.name); + }); + + it('throws an error when attempting to import an identity that already exists', async () => { + // Import the first Identity and set as the Agent DID. + await identityStore.importIdentity({ + identity : testManagedIdentity, + agent : testAgent + }); + testAgent.agentDid = testManagedIdentity.did; + + // Try to import the same key again. + await expect( + identityStore.importIdentity({ + identity : testManagedIdentity, + agent : testAgent + }) + ).to.eventually.be.rejectedWith(Error, 'Identity with DID already exists'); + }); + + it('should author multiple imports with the same Agent DID', async () => { + // Define multiple identities to be added. + const testIdentity2 = { did: 'did:key:456', name: 'Family' }; + const testIdentity3 = { did: 'did:key:789', name: 'Social' }; + + // Import the first Identity and set as the Agent DID. + await identityStore.importIdentity({ + identity : testManagedIdentity, + agent : testAgent + }); + + // Set the Agent's DID to the imported Identity's DID. + testAgent.agentDid = testManagedIdentity.did; + + // Import the other two DIDs. + await identityStore.importIdentity({ + identity : testIdentity2, + agent : testAgent + }); + await identityStore.importIdentity({ + identity : testIdentity3, + agent : testAgent + }); + + // Get each Identity and verify that all three were written using the same author DID. + const storedDid1 = await identityStore.getIdentity({ did: testManagedIdentity.did, agent: testAgent }); + const storedDid2 = await identityStore.getIdentity({ did: testIdentity2.did, agent: testAgent }); + const storedDid3 = await identityStore.getIdentity({ did: testIdentity3.did, agent: testAgent }); + if (!(storedDid1 && storedDid2 && storedDid3)) throw new Error('Type guard unexpectedly threw'); // Type guard. + expect(storedDid1.did).to.equal(testManagedIdentity.did); + expect(storedDid2.did).to.equal(testIdentity2.did); + expect(storedDid3.did).to.equal(testIdentity3.did); + }); + + it('throws an error if Agent DID is undefined and no keys exist for imported DID', async () => { + const testIdentity = { did: 'did:key:z6MkmRUyE6ywoYV2zL9Nus2YuFchpnTGzPXToZWDbdag6tvB', name: 'Test' }; + await expect( + identityStore.importIdentity({ identity: testIdentity, agent: testAgent }) + ).to.eventually.be.rejectedWith(Error, `Agent property 'agentDid' is undefined and no keys were found for`); + }); + }); + + describe('listIdentities()', () => { + it('should return an array of all DIDs in the store', async () => { + // Define multiple identities to be added. + const testIdentity2 = { did: 'did:key:456', name: 'Family' }; + const testIdentity3 = { did: 'did:key:789', name: 'Social' }; + + // Import the first Identity and set as the Agent DID. + await identityStore.importIdentity({ + identity : testManagedIdentity, + agent : testAgent + }); + + // Set the Agent's DID to the imported Identity's DID. + testAgent.agentDid = testManagedIdentity.did; + + // Import the other two DIDs. + await identityStore.importIdentity({ + identity : testIdentity2, + agent : testAgent + }); + await identityStore.importIdentity({ + identity : testIdentity3, + agent : testAgent + }); + + // List DIDs and verify the result. + const storedDids = await identityStore.listIdentities({ agent: testAgent }); + expect(storedDids).to.have.length(3); + const importedDids = [testManagedIdentity.did, testIdentity2.did, testIdentity3.did]; + for (const storedDid of storedDids) { + expect(importedDids).to.include(storedDid.did); + } + }); + + it('throws an error if Agent DID is undefined', async () => { + await expect( + identityStore.listIdentities({ agent: testAgent }) + ).to.eventually.be.rejectedWith(Error, `Agent property 'agentDid' is undefined`); + }); + }); +}); + +describe('IdentityStoreMemory', () => { + let identityStore: IdentityStoreMemory; + let testAgent: TestAgent; + let testManagedDid: ManagedDid; + let testManagedIdentity: ManagedIdentity; + + before(async () => { + testAgent = await TestAgent.create(); + }); + + beforeEach(async () => { + identityStore = new IdentityStoreMemory(); + const identityManager = new IdentityManager({ store: identityStore, agent: testAgent }); + testAgent.identityManager = identityManager; + + // Create did:key DID with key set. + const portableDid = await DidKeyMethod.create({ keyAlgorithm: 'Ed25519' }); + testManagedDid = { ...portableDid, method: 'key' }; + + // Create a ManagedIdentity to use for tests. + testManagedIdentity = { did: portableDid.did, name: 'Test' }; + }); + + afterEach(async () => { + await testAgent.clearStorage(); + }); + + after(async () => { + await testAgent.closeStorage(); + }); + + describe('deleteIdentity()', () => { + it('should delete identity and return true if identity exists', async () => { + // Import the identity. + await identityStore.importIdentity({ identity: { did: testManagedDid.did, name: 'Test' } }); + + // Test deleting the identity and validate the result. + const deleteResult = await identityStore.deleteIdentity({ did: testManagedDid.did }); + expect(deleteResult).to.be.true; + + // Verify the identity is no longer in the store. + const storedIdentity = await identityStore.getIdentity({ did: testManagedDid.did }); + expect(storedIdentity).to.be.undefined; + }); + + it('should return false if identity does not exist', async () => { + // Test deleting the identity. + const nonExistentId = '1234'; + const deleteResult = await identityStore.deleteIdentity({ did: nonExistentId }); + + // Validate the identity was not deleted. + expect(deleteResult).to.be.false; + }); + }); + + describe('getIdentity()', () => { + it('should return a identity by DID if it exists', async () => { + // Import the identity. + await identityStore.importIdentity({ identity: { did: testManagedDid.did, name: 'Test' } }); + + // Test getting the identity. + const storedIdentity = await identityStore.getIdentity({ did: testManagedDid.did }); + + // Verify the identity is in the store. + if (!storedIdentity) throw Error(); // Type guard. + expect(testManagedDid.did).to.equal(storedIdentity.did); + }); + + it('should return undefined when attempting to get a non-existent identity', async () => { + // Test getting the identity. + const storedIdentity = await identityStore.getIdentity({ did: 'non-existent-identity' }); + + // Verify the identity is not in the store. + expect(storedIdentity).to.be.undefined; + }); + }); + + describe('importIdentity', () => { + it('should import an identity that does not already exist', async () => { + // Test importing the identity and validate the result. + await identityStore.importIdentity({ identity: { did: testManagedDid.did, name: 'Test' } }); + + // Verify the identity is present in the identity store. + const storedIdentity = await identityStore.getIdentity({ did: testManagedDid.did }); + if (!storedIdentity) throw Error(); // Type guard. + expect(testManagedDid.did).to.equal(storedIdentity.did); + }); + + it('throws an error when attempting to import an identity that already exists', async () => { + // Import the identity. + await identityStore.importIdentity({ identity: { did: testManagedDid.did, name: 'Test' } }); + + // Set the Agent's DID to the imported Identity's DID. + testAgent.agentDid = testManagedIdentity.did; + + // Try to import the same Identity again. + await expect( + identityStore.importIdentity({ identity: testManagedIdentity }) + ).to.eventually.be.rejectedWith(Error, 'Identity with DID already exists'); + }); + }); + + describe('listIdentities', () => { + it('should return an array of all identities in the store', async () => { + // Define multiple identities to be added. + const testIdentities = [ + { did: 'did:key:123', name: 'Career' }, + { did: 'did:key:456', name: 'Family' }, + { did: 'did:key:789', name: 'Social' } + ]; + + // Import the identities into the store. + for (let identity of testIdentities) { + await identityStore.importIdentity({ identity }); + } + + // List identities and verify the result. + const storedIdentities = await identityStore.listIdentities(); + expect(storedIdentities).to.deep.equal(testIdentities); + }); + + it('should return an empty array if the store contains no identities', async () => { + // List identities and verify the result is empty. + const storedIdentities = await identityStore.listIdentities(); + expect(storedIdentities).to.be.empty; + }); + }); +}); \ No newline at end of file diff --git a/packages/crypto/tests/key-manager.spec.ts b/packages/agent/tests/key-manager.spec.ts similarity index 68% rename from packages/crypto/tests/key-manager.spec.ts rename to packages/agent/tests/key-manager.spec.ts index 277977959..40fddc221 100644 --- a/packages/crypto/tests/key-manager.spec.ts +++ b/packages/agent/tests/key-manager.spec.ts @@ -1,64 +1,63 @@ -import type { KeyManagerOptions } from '../src/key-manager/index.js'; -import type { ManagedKey, ManagedKeyPair, ManagedPrivateKey, Web5Crypto } from '../src/types/index.js'; +import type { Web5Crypto } from '@web5/crypto'; +import sinon from 'sinon'; import { expect } from 'chai'; -import { MemoryStore } from '@tbd54566975/common'; -import { KeyManager, KeyManagerStore } from '../src/key-manager/index.js'; -import { LocalKms, KmsKeyStore, KmsPrivateKeyStore } from '../src/kms-local/index.js'; +import type { KeyManagerOptions } from '../src/key-manager.js'; +import type { ManagedKey, ManagedKeyPair } from '../src/types/managed-key.js'; + +import { LocalKms } from '../src/kms-local.js'; +import { TestAgent } from './utils/test-agent.js'; +import { KeyManager } from '../src/key-manager.js'; +import { KeyStoreMemory } from '../src/store-managed-key.js'; describe('KeyManager', () => { let keyManager: KeyManager; - let keyManagerStore: KeyManagerStore; let localKms: LocalKms; - let kmsKeyStore: KmsKeyStore; - let kmsPrivateKeyStore: KmsPrivateKeyStore; - - beforeEach(() => { - // Instantiate in-memory store for KMS key metadata and public keys. - const kmsMemoryStore = new MemoryStore(); - kmsKeyStore = new KmsKeyStore(kmsMemoryStore); + let testAgent: TestAgent; - // Instantiate in-memory store for KMS private keys. - const memoryPrivateKeyStore = new MemoryStore(); - kmsPrivateKeyStore = new KmsPrivateKeyStore(memoryPrivateKeyStore); + before(async () => { + testAgent = await TestAgent.create(); + }); - // Instantiate local KMS using key stores. - localKms = new LocalKms('local', kmsKeyStore, kmsPrivateKeyStore); + beforeEach(() => { + // Instantiate local KMS using in-memory key stores. + localKms = new LocalKms({ kmsName: 'memory', agent: testAgent }); - // Instantiate in-memory store for KeyManager key metadata. - const kmMemoryStore = new MemoryStore(); - keyManagerStore = new KeyManagerStore({ store: kmMemoryStore }); + // Instantiate KeyManager with in-memory KMS and store. + keyManager = new KeyManager({ kms: { memory: localKms }, agent: testAgent }); + }); - const options: KeyManagerOptions = { - store : keyManagerStore, - kms : { local: localKms }, - }; + afterEach(async () => { + await testAgent.clearStorage(); + }); - keyManager = new KeyManager(options); + after(async () => { + await testAgent.closeStorage(); }); describe('constructor', () => { - it('throws an exception if store and kms inputs are missing', async () => { - // @ts-expect-error because KeyManager is intentionally instantiated without required properties. - expect(() => new KeyManager()).to.throw(TypeError); - }); - - it('throws an exception if store is missing', async () => { - // @ts-expect-error because KeyManager is intentionally instantiated without required properties. - expect(() => new KeyManager({ kms: { local: localKms } })).to.throw(TypeError, 'Required parameter was missing'); + it('will use an in-memory store and local KMS if store and kms inputs are missing', async () => { + expect(() => new KeyManager()).to.not.throw; + const kmsList = keyManager.listKms(); + expect(kmsList[0]).to.equal('memory'); }); it('will use a local KMS if kms is not specified', async () => { - keyManager = new KeyManager({ store: keyManagerStore }); + const store = new KeyStoreMemory(); + keyManager = new KeyManager({ store }); const kmsList = keyManager.listKms(); - expect(kmsList[0]).to.equal('local'); + expect(kmsList[0]).to.equal('memory'); }); }); describe('instances', () => { - it('should not be possible to externally access the KeyManager store', async () => { + xit('should not be possible to externally access the KeyManager store', async () => { + // TODO: This test should be re-enabled once we can use #private variables + // which won't be possible until after support for CJS is dropped. + // Dropping support for CJS is dependent on Electron supporting ESM. + /** * Note: It isn't possible to test that trying to access keyMgr.#store will throw a SyntaxError. * In JavaScript, a SyntaxError is thrown when parsing code before it is executed. This makes it @@ -71,6 +70,65 @@ describe('KeyManager', () => { }); }); + describe('get agent', () => { + it(`returns the 'agent' instance property`, () => { + const agent = testAgent.keyManager.agent; + expect(agent).to.exist; + }); + + it(`throws an error if the 'agent' instance property is undefined`, () => { + const keyManager = new KeyManager(); + expect(() => + keyManager.agent + ).to.throw(Error, 'Unable to determine agent execution context'); + }); + }); + + describe('with two KMS', () => { + beforeEach(() => { + // Instantiate two local KMSs using in-memory key stores. + const localKms1 = new LocalKms({ kmsName: 'one', agent: testAgent }); + const localKms2 = new LocalKms({ kmsName: 'two', agent: testAgent }); + + // Instantiate KeyManager with both KMS instances. + keyManager = new KeyManager({ + kms: { + one : localKms1, + two : localKms2 + } + }); + + // Set the KeyManager's agent instance. + keyManager.agent = testAgent; + }); + + it('should store keys in the specified KMS', async () => { + const keyPair1 = await keyManager.generateKey({ + algorithm : { name: 'ECDSA', namedCurve: 'secp256k1' }, + keyUsages : ['sign', 'verify'], + kms : 'one' + }); + + const keyPair2 = await keyManager.generateKey({ + algorithm : { name: 'ECDSA', namedCurve: 'secp256k1' }, + keyUsages : ['sign', 'verify'], + kms : 'two' + }); + + // Verify that key pair 1 was stored with specified KMS name. + const storedKeyPair1 = await keyManager.getKey({ keyRef: keyPair1.publicKey.id }); + if (!storedKeyPair1) throw new Error('Type guard unexpectedly threw'); // Type guard. + if (!('publicKey' in storedKeyPair1)) throw new Error('Type guard unexpectedly threw'); // Type guard. + expect(storedKeyPair1.publicKey.kms).to.equal('one'); + + // Verify that key pair 2 was stored with specified KMS name. + const storedKeyPair2 = await keyManager.getKey({ keyRef: keyPair2.publicKey.id }); + if (!storedKeyPair2) throw new Error('Type guard unexpectedly threw'); // Type guard. + if (!('publicKey' in storedKeyPair2)) throw new Error('Type guard unexpectedly threw'); // Type guard. + expect(storedKeyPair2.publicKey.kms).to.equal('two'); + }); + }); + describe('decrypt()', () => { let key: ManagedKey; @@ -86,59 +144,39 @@ describe('KeyManager', () => { const plaintext = await keyManager.decrypt({ algorithm: { name : 'AES-CTR', - counter : new ArrayBuffer(16), + counter : new Uint8Array(16), length : 128 }, keyRef : key.id, data : new Uint8Array([1, 2, 3, 4]) }); - expect(plaintext).to.be.instanceOf(ArrayBuffer); + expect(plaintext).to.be.instanceOf(Uint8Array); expect(plaintext.byteLength).to.equal(4); }); - it('accepts input data as ArrayBuffer, DataView, and TypedArray', async () => { - const algorithm = { name: 'AES-CTR', counter: new ArrayBuffer(16), length: 128 }; + it('accepts input data as Uint8Array', async () => { + const algorithm = { name: 'AES-CTR', counter: new Uint8Array(16), length: 128 }; const dataU8A = new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8]); - let plaintext: ArrayBuffer; - - // ArrayBuffer - const dataArrayBuffer = dataU8A.buffer; - plaintext = await keyManager.decrypt({ algorithm, keyRef: key.id, data: dataArrayBuffer }); - expect(plaintext).to.be.instanceOf(ArrayBuffer); - - // DataView - const dataView = new DataView(dataArrayBuffer); - plaintext = await keyManager.decrypt({ algorithm, keyRef: key.id, data: dataView }); - expect(plaintext).to.be.instanceOf(ArrayBuffer); + let plaintext: Uint8Array; // TypedArray - Uint8Array plaintext = await keyManager.decrypt({ algorithm, keyRef: key.id, data: dataU8A }); - expect(plaintext).to.be.instanceOf(ArrayBuffer); - - // TypedArray - Int32Array - const dataI32A = new Int32Array([10, 20, 30, 40]); - plaintext = await keyManager.decrypt({ algorithm, keyRef: key.id, data: dataI32A }); - expect(plaintext).to.be.instanceOf(ArrayBuffer); - - // TypedArray - Uint32Array - const dataU32A = new Uint32Array([8, 7, 6, 5, 4, 3, 2, 1]); - plaintext = await keyManager.decrypt({ algorithm, keyRef: key.id, data: dataU32A }); - expect(plaintext).to.be.instanceOf(ArrayBuffer); + expect(plaintext).to.be.instanceOf(Uint8Array); }); it('decrypts data with AES-CTR', async () => { const plaintext = await keyManager.decrypt({ algorithm: { name : 'AES-CTR', - counter : new ArrayBuffer(16), + counter : new Uint8Array(16), length : 128 }, keyRef : key.id, data : new Uint8Array([1, 2, 3, 4]) }); - expect(plaintext).to.be.instanceOf(ArrayBuffer); + expect(plaintext).to.be.instanceOf(Uint8Array); expect(plaintext.byteLength).to.equal(4); }); @@ -146,7 +184,7 @@ describe('KeyManager', () => { await expect(keyManager.decrypt({ algorithm: { name : 'AES-CTR', - counter : new ArrayBuffer(16), + counter : new Uint8Array(16), length : 128 }, keyRef : 'non-existent-key', @@ -158,7 +196,7 @@ describe('KeyManager', () => { describe('deriveBits()', () => { let otherPartyPublicKey: ManagedKey; let otherPartyPublicCryptoKey: Web5Crypto.CryptoKey; - let ownPrivateKey: ManagedKey; + let ownKeyPair: ManagedKeyPair; beforeEach(async () => { const otherPartyKeyPair = await keyManager.generateKey({ @@ -171,43 +209,42 @@ describe('KeyManager', () => { otherPartyPublicCryptoKey = { algorithm : otherPartyPublicKey.algorithm, extractable : otherPartyPublicKey.extractable, - handle : otherPartyPublicKey.material!, + material : otherPartyPublicKey.material!, type : otherPartyPublicKey.type, usages : otherPartyPublicKey.usages }; - const ownKeyPair = await keyManager.generateKey({ + ownKeyPair = await keyManager.generateKey({ algorithm : { name: 'ECDH', namedCurve: 'secp256k1' }, extractable : false, keyUsages : ['deriveBits', 'deriveKey'] }); - ownPrivateKey = ownKeyPair.privateKey; }); it('generates shared secrets', async () => { const sharedSecret = await keyManager.deriveBits({ algorithm : { name: 'ECDH', publicKey: otherPartyPublicCryptoKey }, - baseKeyRef : ownPrivateKey.id + baseKeyRef : ownKeyPair.privateKey.id }); - expect(sharedSecret).to.be.an('ArrayBuffer'); + expect(sharedSecret).to.be.an('Uint8Array'); expect(sharedSecret.byteLength).to.equal(32); }); it(`accepts 'id' as a baseKey reference`, async () => { const sharedSecret = await keyManager.deriveBits({ algorithm : { name: 'ECDH', publicKey: otherPartyPublicCryptoKey }, - baseKeyRef : ownPrivateKey.id + baseKeyRef : ownKeyPair.privateKey.id }); - expect(sharedSecret).to.be.an('ArrayBuffer'); + expect(sharedSecret).to.be.an('Uint8Array'); expect(sharedSecret.byteLength).to.equal(32); }); it('generates ECDH secp256k1 shared secrets', async () => { const sharedSecret = await keyManager.deriveBits({ algorithm : { name: 'ECDH', publicKey: otherPartyPublicCryptoKey }, - baseKeyRef : ownPrivateKey.id + baseKeyRef : ownKeyPair.privateKey.id }); - expect(sharedSecret).to.be.an('ArrayBuffer'); + expect(sharedSecret).to.be.an('Uint8Array'); expect(sharedSecret.byteLength).to.equal(32); }); @@ -222,21 +259,20 @@ describe('KeyManager', () => { otherPartyPublicCryptoKey = { algorithm : otherPartyPublicKey.algorithm, extractable : otherPartyPublicKey.extractable, - handle : otherPartyPublicKey.material!, + material : otherPartyPublicKey.material!, type : otherPartyPublicKey.type, usages : otherPartyPublicKey.usages }; - const ownKeyPair = await keyManager.generateKey({ + ownKeyPair = await keyManager.generateKey({ algorithm : { name: 'ECDH', namedCurve: 'X25519' }, extractable : false, keyUsages : ['deriveBits'] }); - ownPrivateKey = ownKeyPair.privateKey; const sharedSecret = await keyManager.deriveBits({ algorithm : { name: 'ECDH', publicKey: otherPartyPublicCryptoKey }, - baseKeyRef : ownPrivateKey.id + baseKeyRef : ownKeyPair.privateKey.id }); expect(sharedSecret.byteLength).to.equal(32); }); @@ -264,59 +300,39 @@ describe('KeyManager', () => { const ciphertext = await keyManager.encrypt({ algorithm: { name : 'AES-CTR', - counter : new ArrayBuffer(16), + counter : new Uint8Array(16), length : 128 }, keyRef : key.id, data : new Uint8Array([1, 2, 3, 4]) }); - expect(ciphertext).to.be.instanceOf(ArrayBuffer); + expect(ciphertext).to.be.instanceOf(Uint8Array); expect(ciphertext.byteLength).to.equal(4); }); - it('accepts input data as ArrayBuffer, DataView, and TypedArray', async () => { - const algorithm = { name: 'AES-CTR', counter: new ArrayBuffer(16), length: 128 }; + it('accepts input data as Uint8Array', async () => { + const algorithm = { name: 'AES-CTR', counter: new Uint8Array(16), length: 128 }; const dataU8A = new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8]); - let ciphertext: ArrayBuffer; - - // ArrayBuffer - const dataArrayBuffer = dataU8A.buffer; - ciphertext = await keyManager.encrypt({ algorithm, keyRef: key.id, data: dataArrayBuffer }); - expect(ciphertext).to.be.instanceOf(ArrayBuffer); - - // DataView - const dataView = new DataView(dataArrayBuffer); - ciphertext = await keyManager.encrypt({ algorithm, keyRef: key.id, data: dataView }); - expect(ciphertext).to.be.instanceOf(ArrayBuffer); + let ciphertext: Uint8Array; // TypedArray - Uint8Array ciphertext = await keyManager.encrypt({ algorithm, keyRef: key.id, data: dataU8A }); - expect(ciphertext).to.be.instanceOf(ArrayBuffer); - - // TypedArray - Int32Array - const dataI32A = new Int32Array([10, 20, 30, 40]); - ciphertext = await keyManager.encrypt({ algorithm, keyRef: key.id, data: dataI32A }); - expect(ciphertext).to.be.instanceOf(ArrayBuffer); - - // TypedArray - Uint32Array - const dataU32A = new Uint32Array([8, 7, 6, 5, 4, 3, 2, 1]); - ciphertext = await keyManager.encrypt({ algorithm, keyRef: key.id, data: dataU32A }); - expect(ciphertext).to.be.instanceOf(ArrayBuffer); + expect(ciphertext).to.be.instanceOf(Uint8Array); }); it('encrypts data with AES-CTR', async () => { const ciphertext = await keyManager.encrypt({ algorithm: { name : 'AES-CTR', - counter : new ArrayBuffer(16), + counter : new Uint8Array(16), length : 128 }, keyRef : key.id, data : new Uint8Array([1, 2, 3, 4]) }); - expect(ciphertext).to.be.instanceOf(ArrayBuffer); + expect(ciphertext).to.be.instanceOf(Uint8Array); expect(ciphertext.byteLength).to.equal(4); }); @@ -324,7 +340,7 @@ describe('KeyManager', () => { await expect(keyManager.encrypt({ algorithm: { name : 'AES-CTR', - counter : new ArrayBuffer(16), + counter : new Uint8Array(16), length : 128 }, keyRef : 'non-existent-key', @@ -347,8 +363,8 @@ describe('KeyManager', () => { // Check values that are identical for both keys in the pair. expect(keys.privateKey.algorithm.name).to.equal('ECDSA'); expect(keys.publicKey.algorithm.name).to.equal('ECDSA'); - expect(keys.privateKey.kms).to.equal('local'); - expect(keys.publicKey.kms).to.equal('local'); + expect(keys.privateKey.kms).to.equal('memory'); + expect(keys.publicKey.kms).to.equal('memory'); expect(keys.privateKey.spec).to.be.undefined; expect(keys.publicKey.spec).to.be.undefined; expect(keys.privateKey.state).to.equal('Enabled'); @@ -360,7 +376,7 @@ describe('KeyManager', () => { expect(keys.privateKey.usages).to.deep.equal(['sign']); // Check values unique to the public key. - expect(keys.publicKey.material).to.be.an.instanceOf(ArrayBuffer); + expect(keys.publicKey.material).to.be.an.instanceOf(Uint8Array); expect(keys.publicKey.type).to.equal('public'); expect(keys.publicKey.usages).to.deep.equal(['verify']); }); @@ -468,12 +484,12 @@ describe('KeyManager', () => { }); describe('getKey()', function() { - it('returns the key if it exists in the store', async function() { + it('returns the key by ID if it exists in the store', async function() { // Prepopulate the store with a key. const importedPrivateKey = await keyManager.importKey({ algorithm : { name: 'ECDSA', namedCurve: 'secp256k1' }, extractable : true, - kms : 'local', + kms : 'memory', material : new Uint8Array([1, 2, 3, 4]), type : 'private', usages : ['sign'], @@ -484,6 +500,25 @@ describe('KeyManager', () => { expect(storedPrivateKey).to.deep.equal(importedPrivateKey); }); + it('should return a key by alias if it exists', async () => { + // Prepopulate the store with a key. + const importedPrivateKey = await keyManager.importKey({ + algorithm : { name: 'ECDSA', namedCurve: 'secp256k1' }, + alias : 'did:method:abc123', + extractable : true, + kms : 'memory', + material : new Uint8Array([1, 2, 3, 4]), + type : 'private', + usages : ['sign'], + }); + + // Test finding the key. + const storedPrivateKey = await keyManager.getKey({ keyRef: 'did:method:abc123' }); + + // Verify the key is in the store. + expect(storedPrivateKey).to.deep.equal(importedPrivateKey); + }); + it('should return undefined if the key does not exist in the store', async function() { const keyRef = 'non-existent-key'; @@ -500,7 +535,7 @@ describe('KeyManager', () => { privateKey: { algorithm : { name: 'ECDSA', namedCurve: 'secp256k1' }, extractable : true, - kms : 'local', + kms : 'memory', material : new Uint8Array([1, 2, 3, 4]), type : 'private', usages : ['sign'], @@ -508,7 +543,7 @@ describe('KeyManager', () => { publicKey: { algorithm : { name: 'ECDSA', namedCurve: 'secp256k1' }, extractable : true, - kms : 'local', + kms : 'memory', material : new Uint8Array([1, 2, 3, 4]), type : 'public', usages : ['verify'], @@ -527,8 +562,8 @@ describe('KeyManager', () => { // Check values that are identical for both storedKeyPair in the pair. expect(storedKeyPair.privateKey.algorithm.name).to.equal('ECDSA'); expect(storedKeyPair.publicKey.algorithm.name).to.equal('ECDSA'); - expect(storedKeyPair.privateKey.kms).to.equal('local'); - expect(storedKeyPair.publicKey.kms).to.equal('local'); + expect(storedKeyPair.privateKey.kms).to.equal('memory'); + expect(storedKeyPair.publicKey.kms).to.equal('memory'); expect(storedKeyPair.privateKey.spec).to.be.undefined; expect(storedKeyPair.publicKey.spec).to.be.undefined; expect(storedKeyPair.privateKey.state).to.equal('Enabled'); @@ -540,7 +575,7 @@ describe('KeyManager', () => { expect(storedKeyPair.privateKey.usages).to.deep.equal(['sign']); // Check values unique to the public key. - expect(storedKeyPair.publicKey.material).to.be.an.instanceOf(ArrayBuffer); + expect(storedKeyPair.publicKey.material).to.be.an.instanceOf(Uint8Array); expect(storedKeyPair.publicKey.type).to.equal('public'); expect(storedKeyPair.publicKey.usages).to.deep.equal(['verify']); }); @@ -550,12 +585,12 @@ describe('KeyManager', () => { const importedPrivateKey = await keyManager.importKey({ algorithm : { name: 'ECDSA', namedCurve: 'secp256k1' }, extractable : true, - kms : 'local', + kms : 'memory', material : new Uint8Array([1, 2, 3, 4]), type : 'private', usages : ['sign'], }); - expect(importedPrivateKey.kms).to.equal('local'); + expect(importedPrivateKey.kms).to.equal('memory'); expect(importedPrivateKey).to.exist; // Verify the key is present in the key store. @@ -564,7 +599,7 @@ describe('KeyManager', () => { // Validate the expected values. expect(storedPrivateKey.algorithm.name).to.equal('ECDSA'); - expect(storedPrivateKey.kms).to.equal('local'); + expect(storedPrivateKey.kms).to.equal('memory'); expect(storedPrivateKey.spec).to.be.undefined; expect(storedPrivateKey.state).to.equal('Enabled'); expect(storedPrivateKey.material).to.be.undefined; @@ -577,12 +612,12 @@ describe('KeyManager', () => { const importedPublicKey = await keyManager.importKey({ algorithm : { name: 'ECDSA', namedCurve: 'secp256k1' }, extractable : true, - kms : 'local', + kms : 'memory', material : new Uint8Array([1, 2, 3, 4]), type : 'public', usages : ['verify'], }); - expect(importedPublicKey.kms).to.equal('local'); + expect(importedPublicKey.kms).to.equal('memory'); expect(importedPublicKey).to.exist; // Verify the key is present in the key store. @@ -591,10 +626,10 @@ describe('KeyManager', () => { // Validate the expected values. expect(storedPublicKey.algorithm.name).to.equal('ECDSA'); - expect(storedPublicKey.kms).to.equal('local'); + expect(storedPublicKey.kms).to.equal('memory'); expect(storedPublicKey.spec).to.be.undefined; expect(storedPublicKey.state).to.equal('Enabled'); - expect(storedPublicKey.material).to.be.an.instanceOf(ArrayBuffer); + expect(storedPublicKey.material).to.be.an.instanceOf(Uint8Array); expect(storedPublicKey.type).to.equal('public'); expect(storedPublicKey.usages).to.deep.equal(['verify']); }); @@ -604,12 +639,12 @@ describe('KeyManager', () => { const importedSecretKey = await keyManager.importKey({ algorithm : { name: 'AES-CTR', length: 128 }, extractable : true, - kms : 'local', + kms : 'memory', material : new Uint8Array([1, 2, 3, 4]), type : 'secret', usages : ['encrypt', 'decrypt'], }); - expect(importedSecretKey.kms).to.equal('local'); + expect(importedSecretKey.kms).to.equal('memory'); expect(importedSecretKey).to.exist; // Verify the key is present in the key store. @@ -618,7 +653,7 @@ describe('KeyManager', () => { // Validate the expected values. expect(storedSecretKey.algorithm.name).to.equal('AES-CTR'); - expect(storedSecretKey.kms).to.equal('local'); + expect(storedSecretKey.kms).to.equal('memory'); expect(storedSecretKey.spec).to.be.undefined; expect(storedSecretKey.state).to.equal('Enabled'); expect(storedSecretKey.material).to.be.undefined; @@ -635,7 +670,7 @@ describe('KeyManager', () => { algorithm : { name: 'ECDSA', namedCurve: 'secp256k1' }, extractable : true, id : '1234', - kms : 'local', + kms : 'memory', material : new Uint8Array([1, 2, 3, 4]), type : 'private', usages : ['sign'], @@ -649,7 +684,7 @@ describe('KeyManager', () => { algorithm : { name: 'ECDSA', namedCurve: 'secp256k1' }, extractable : true, id : '1234', - kms : 'local', + kms : 'memory', material : new Uint8Array([1, 2, 3, 4]), type : 'public', usages : ['sign'], @@ -664,7 +699,7 @@ describe('KeyManager', () => { algorithm : { name: 'ECDSA', namedCurve: 'secp256k1' }, extractable : true, id : '1234', - kms : 'local', + kms : 'memory', material : new Uint8Array([1, 2, 3, 4]), type : 'private', usages : ['sign'], @@ -673,7 +708,7 @@ describe('KeyManager', () => { algorithm : { name: 'ECDSA', namedCurve: 'secp256k1' }, extractable : true, id : '1234', - kms : 'local', + kms : 'memory', material : new Uint8Array([1, 2, 3, 4]), type : 'public', usages : ['verify'], @@ -690,7 +725,7 @@ describe('KeyManager', () => { const importedPrivateKey = await keyManager.importKey({ algorithm : { name: 'ECDSA', namedCurve: 'secp256k1' }, extractable : true, - kms : 'local', + kms : 'memory', material : new Uint8Array([1, 2, 3, 4]), type : 'private', usages : ['sign'], @@ -703,13 +738,13 @@ describe('KeyManager', () => { const importedPrivateKey = await keyManager.importKey({ algorithm : { name: 'ECDSA', namedCurve: 'secp256k1' }, extractable : true, - kms : 'local', + kms : 'memory', material : new Uint8Array([1, 2, 3, 4]), type : 'public', usages : ['verify'], }); expect(importedPrivateKey.material).to.exist; - expect(importedPrivateKey.material).to.be.an.instanceOf(ArrayBuffer); + expect(importedPrivateKey.material).to.be.an.instanceOf(Uint8Array); }); it('throws an error if public and private keys are swapped', async () => { @@ -718,7 +753,7 @@ describe('KeyManager', () => { privateKey: { algorithm : { name: 'ECDSA', namedCurve: 'secp256k1' }, extractable : true, - kms : 'local', + kms : 'memory', material : new Uint8Array([1, 2, 3, 4]), type : 'public', usages : ['verify'], @@ -726,7 +761,7 @@ describe('KeyManager', () => { publicKey: { algorithm : { name: 'ECDSA', namedCurve: 'secp256k1' }, extractable : true, - kms : 'local', + kms : 'memory', material : new Uint8Array([1, 2, 3, 4]), type : 'private', usages : ['sign'], @@ -737,16 +772,15 @@ describe('KeyManager', () => { describe('listKms()', function() { it('should return an empty array if no KMSs are specified', function() { - const keyManager = new KeyManager({ store: keyManagerStore, kms: {}, }); + const keyManager = new KeyManager({ kms: {}, }); const kmsList = keyManager.listKms(); expect(kmsList).to.be.an('array').that.is.empty; }); it('should return the names of all KMSs present', function() { const keyManager = new KeyManager({ - store : keyManagerStore, // @ts-expect-error because dummy KMS objects are intentionally used as input. - kms : { 'dummy1': {}, 'dummy2': {} } + kms: { 'dummy1': {}, 'dummy2': {} } }); const kmsList = keyManager.listKms(); expect(kmsList).to.be.an('array').that.includes.members(['dummy1', 'dummy2']); @@ -767,11 +801,11 @@ describe('KeyManager', () => { data : new Uint8Array([51, 52, 53]), }); - expect(signature).to.be.instanceOf(ArrayBuffer); + expect(signature).to.be.instanceOf(Uint8Array); expect(signature.byteLength).to.equal(64); }); - it('accepts input data as ArrayBuffer, DataView, and TypedArray', async () => { + it('accepts input data as Uint8Array', async () => { const algorithm = { name: 'ECDSA', hash: 'SHA-256' }; const dataU8A = new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8]); const keyPair = await keyManager.generateKey({ @@ -780,31 +814,11 @@ describe('KeyManager', () => { keyUsages : ['sign', 'verify'] }); const key = keyPair.privateKey; - let signature: ArrayBuffer; - - // ArrayBuffer - const dataArrayBuffer = dataU8A.buffer; - signature = await keyManager.sign({ algorithm, keyRef: key.id, data: dataArrayBuffer }); - expect(signature).to.be.instanceOf(ArrayBuffer); - - // DataView - const dataView = new DataView(dataArrayBuffer); - signature = await keyManager.sign({ algorithm, keyRef: key.id, data: dataView }); - expect(signature).to.be.instanceOf(ArrayBuffer); + let signature: Uint8Array; // TypedArray - Uint8Array signature = await keyManager.sign({ algorithm, keyRef: key.id, data: dataU8A }); - expect(signature).to.be.instanceOf(ArrayBuffer); - - // TypedArray - Int32Array - const dataI32A = new Int32Array([10, 20, 30, 40]); - signature = await keyManager.sign({ algorithm, keyRef: key.id, data: dataI32A }); - expect(signature).to.be.instanceOf(ArrayBuffer); - - // TypedArray - Uint32Array - const dataU32A = new Uint32Array([8, 7, 6, 5, 4, 3, 2, 1]); - signature = await keyManager.sign({ algorithm, keyRef: key.id, data: dataU32A }); - expect(signature).to.be.instanceOf(ArrayBuffer); + expect(signature).to.be.instanceOf(Uint8Array); }); it('generates ECDSA secp256k1 signatures', async () => { @@ -820,7 +834,7 @@ describe('KeyManager', () => { data : new Uint8Array([51, 52, 53]), }); - expect(signature).to.be.instanceOf(ArrayBuffer); + expect(signature).to.be.instanceOf(Uint8Array); expect(signature.byteLength).to.equal(64); }); @@ -837,7 +851,7 @@ describe('KeyManager', () => { data : new Uint8Array([51, 52, 53]), }); - expect(signature).to.be.instanceOf(ArrayBuffer); + expect(signature).to.be.instanceOf(Uint8Array); expect(signature.byteLength).to.equal(64); }); @@ -850,6 +864,74 @@ describe('KeyManager', () => { }); }); + describe('updateKey()', () => { + let testKey: ManagedKey; + let testKeyPair: ManagedKeyPair; + + beforeEach(async () => { + testKey = await keyManager.generateKey({ + algorithm : { name: 'AES-CTR', length: 128 }, + alias : 'test-key', + extractable : false, + keyUsages : ['encrypt', 'decrypt'], + metadata : { foo: 'bar'} + }); + + testKeyPair = await keyManager.generateKey({ + algorithm : { name: 'ECDSA', namedCurve: 'secp256k1' }, + alias : 'test-key-pair', + extractable : false, + keyUsages : ['sign', 'verify'], + metadata : { foo: 'bar'} + }); + }); + + it('should update a key by ID', async () => { + // Attempt to update the key's alias. + const newAlias = 'did:method:new'; + const updateResult = await keyManager.updateKey({ keyRef: testKey.id, alias: newAlias }); + + // Verify that the alias property was updated. + expect(updateResult).to.be.true; + const storedKey = await keyManager.getKey({ keyRef: testKey.id }); + expect(storedKey).to.have.property('alias', newAlias); + }); + + it('should update a key pair by ID', async () => { + // Attempt to update the key's alias. + const newAlias = 'did:method:new'; + const updateResult = await keyManager.updateKey({ keyRef: testKeyPair.publicKey.id, alias: newAlias }); + + // Verify that the alias property was updated. + expect(updateResult).to.be.true; + const storedKey = await keyManager.getKey({ keyRef: testKeyPair.publicKey.id }); + if (!('privateKey' in storedKey!)) throw new Error('Expected ManagedKeyPair and not ManagedKey'); + expect(storedKey.publicKey).to.have.property('alias', newAlias); + }); + + it('throws an error when key reference is not found', async () => { + await expect( + keyManager.updateKey({ keyRef: 'non-existent', alias: 'new-alias' }) + ).to.eventually.be.rejectedWith(Error, 'Key not found'); + }); + + it('returns false if the update operation failed', async () => { + // Stub the `updateKey()` method of LocalKms to simulate a failed update. + sinon.stub(localKms, 'updateKey').returns(Promise.resolve(false)); + + // Attempt to update the key's alias. + const updateResult = await keyManager.updateKey({ keyRef: testKey.id, alias: '' }); + + // Remove the instance method stub. + sinon.restore(); + + // Verify that the update failed. + expect(updateResult).to.be.false; + const storedKey = await keyManager.getKey({ keyRef: testKey.id }); + expect(storedKey).to.deep.equal(testKey); + }); + }); + describe('verify()', () => { it('returns a boolean result', async () => { const dataU8A = new Uint8Array([51, 52, 53]); @@ -876,7 +958,7 @@ describe('KeyManager', () => { expect(isValid).to.be.true; }); - it('accepts input data as ArrayBuffer, DataView, and TypedArray', async () => { + it('accepts input data as Uint8Array', async () => { const algorithm = { name: 'ECDSA', hash: 'SHA-256' }; const dataU8A = new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8]); const keyPair = await keyManager.generateKey({ @@ -884,37 +966,13 @@ describe('KeyManager', () => { extractable : false, keyUsages : ['sign', 'verify'] }); - let signature: ArrayBuffer; + let signature: Uint8Array; let isValid: boolean; - // ArrayBuffer - const dataArrayBuffer = dataU8A.buffer; - signature = await keyManager.sign({ algorithm, keyRef: keyPair.privateKey.id, data: dataArrayBuffer }); - isValid = await keyManager.verify({ algorithm, keyRef: keyPair.publicKey.id, signature, data: dataArrayBuffer }); - expect(isValid).to.be.true; - - // DataView - const dataView = new DataView(dataArrayBuffer); - signature = await keyManager.sign({ algorithm, keyRef: keyPair.privateKey.id, data: dataView }); - isValid = await keyManager.verify({ algorithm, keyRef: keyPair.publicKey.id, signature, data: dataView }); - expect(isValid).to.be.true; - // TypedArray - Uint8Array signature = await keyManager.sign({ algorithm, keyRef: keyPair.privateKey.id, data: dataU8A }); isValid = await keyManager.verify({ algorithm, keyRef: keyPair.publicKey.id, signature, data: dataU8A }); expect(isValid).to.be.true; - - // TypedArray - Int32Array - const dataI32A = new Int32Array([10, 20, 30, 40]); - signature = await keyManager.sign({ algorithm, keyRef: keyPair.privateKey.id, data: dataI32A }); - isValid = await keyManager.verify({ algorithm, keyRef: keyPair.publicKey.id, signature, data: dataI32A }); - expect(isValid).to.be.true; - - // TypedArray - Uint32Array - const dataU32A = new Uint32Array([8, 7, 6, 5, 4, 3, 2, 1]); - signature = await keyManager.sign({ algorithm, keyRef: keyPair.privateKey.id, data: dataU32A }); - isValid = await keyManager.verify({ algorithm, keyRef: keyPair.publicKey.id, signature, data: dataU32A }); - expect(isValid).to.be.true; }); it('verifies ECDSA secp256k1 signatures', async () => { @@ -953,27 +1011,26 @@ describe('KeyManager', () => { await expect(keyManager.verify({ algorithm : { name: 'ECDSA', hash: 'SHA-256' }, keyRef : 'non-existent-key', - signature : (new Uint8Array([51, 52, 53])).buffer, + signature : (new Uint8Array([51, 52, 53])), data : new Uint8Array([51, 52, 53]) })).to.eventually.be.rejectedWith(Error, 'Key not found'); }); }); - describe('#getKms()', () => { + describe('getKms()', () => { it(`if 'kms' is not specified and there is only one, use it automatically`, async () => { const key = await keyManager.generateKey({ algorithm : { name: 'EdDSA', namedCurve: 'Ed25519' }, keyUsages : ['sign', 'verify'] }); - expect(key.privateKey.kms).to.equal('local'); + expect(key.privateKey.kms).to.equal('memory'); }); it(`throws an error if 'kms' is not specified and there is more than 1`, async () => { // Instantiate KeyManager with two KMSs. const options: KeyManagerOptions = { - store : keyManagerStore, - kms : { + kms: { one : localKms, two : localKms }, @@ -994,118 +1051,4 @@ describe('KeyManager', () => { })).to.eventually.be.rejectedWith(Error, 'Unknown key management system'); }); }); -}); - -describe('KeyManagerStore', () => { - let keyManagerStore: KeyManagerStore; - let testKey: ManagedKey; - - beforeEach(() => { - const memoryStore = new MemoryStore(); - - keyManagerStore = new KeyManagerStore({ store: memoryStore }); - - testKey = { - id : 'testKey', - algorithm : { name: 'AES', length: 256 }, - extractable : true, - kms : 'testKms', - state : 'Enabled', - type : 'secret', - usages : ['encrypt', 'decrypt'], - }; - }); - - describe('deleteKey()', () => { - it('should delete key and return true if key exists', async () => { - // Import the key. - await keyManagerStore.importKey({ key: testKey }); - - // Test deleting the key and validate the result. - const deleteResult = await keyManagerStore.deleteKey({ id: testKey.id }); - expect(deleteResult).to.be.true; - - // Verify the key is no longer in the store. - const storedKey = await keyManagerStore.getKey({ id: testKey.id }); - expect(storedKey).to.be.undefined; - }); - - it('should return false if key does not exist', async () => { - // Test deleting the key. - const nonExistentId = '1234'; - const deleteResult = await keyManagerStore.deleteKey({ id: nonExistentId }); - - // Validate the key was not deleted. - expect(deleteResult).to.be.false; - }); - }); - - describe('getKey()', () => { - it('should return a key if it exists', async () => { - // Import the key. - await keyManagerStore.importKey({ key: testKey }); - - // Test getting the key. - const storedKey = await keyManagerStore.getKey({ id: testKey.id }); - - // Verify the key is in the store. - expect(storedKey).to.deep.equal(testKey); - }); - - it('should return undefined when attempting to get a non-existent key', async () => { - // Test getting the key. - const storedKey = await keyManagerStore.getKey({ id: 'non-existent-key' }); - - // Verify the key is no longer in the store. - expect(storedKey).to.be.undefined; - }); - }); - - describe('importKey()', () => { - it('should import a key that does not already exist', async () => { - // Test importing the key and validate the result. - const importResult = await keyManagerStore.importKey({ key: testKey }); - expect(importResult).to.be.true; - - // Verify the key is present in the key store. - const storedKey = await keyManagerStore.getKey({ id: testKey.id }); - expect(storedKey).to.deep.equal(testKey); - }); - - it('should throw an error when attempting to import a key that already exists', async () => { - // Import the key and validate the result. - const importResult = await keyManagerStore.importKey({ key: testKey }); - expect(importResult).to.be.true; - - // Test importing the key again and assert it throws an error. - const importKey = keyManagerStore.importKey({ key: testKey }); - await expect(importKey).to.eventually.be.rejectedWith(Error, 'Key with ID already exists'); - }); - }); - - describe('listKeys()', () => { - it('should return an array of all keys in the store', async () => { - // Define multiple keys to be added. - const testKeys = [ - { ...testKey, ...{ id: 'key-1' }}, - { ...testKey, ...{ id: 'key-2' }}, - { ...testKey, ...{ id: 'key-3' }} - ]; - - // Import the keys into the store. - for (let key of testKeys) { - await keyManagerStore.importKey({ key }); - } - - // List keys and verify the result. - const storedKeys = await keyManagerStore.listKeys(); - expect(storedKeys).to.deep.equal(testKeys); - }); - - it('should return an empty array if the store contains no keys', async () => { - // List keys and verify the result is empty. - const storedKeys = await keyManagerStore.listKeys(); - expect(storedKeys).to.be.empty; - }); - }); }); \ No newline at end of file diff --git a/packages/crypto/tests/kms-local.spec.ts b/packages/agent/tests/kms-local.spec.ts similarity index 63% rename from packages/crypto/tests/kms-local.spec.ts rename to packages/agent/tests/kms-local.spec.ts index c116f92f4..cac125b92 100644 --- a/packages/crypto/tests/kms-local.spec.ts +++ b/packages/agent/tests/kms-local.spec.ts @@ -1,23 +1,59 @@ -import type { ManagedKey, ManagedKeyPair, ManagedPrivateKey, Web5Crypto } from '../src/types/index.js'; +import type { Web5Crypto } from '@web5/crypto'; import sinon from 'sinon'; import chai, { expect } from 'chai'; import chaiAsPromised from 'chai-as-promised'; -import { MemoryStore } from '@tbd54566975/common'; -import { LocalKms, KmsKeyStore, KmsPrivateKeyStore } from '../src/kms-local/index.js'; +import type { ManagedKey, ManagedKeyPair } from '../src/types/managed-key.js'; + +import { LocalKms } from '../src/kms-local.js'; +import { TestAgent } from './utils/test-agent.js'; +import { KeyStoreMemory, PrivateKeyStoreMemory } from '../src/store-managed-key.js'; chai.use(chaiAsPromised); describe('LocalKms', () => { let kms: LocalKms; + let kmsKeyStore: KeyStoreMemory; + let testAgent: TestAgent; + + before(async () => { + testAgent = await TestAgent.create(); + }); beforeEach(() => { - const memoryKeyStore = new MemoryStore(); - const kmsKeyStore = new KmsKeyStore(memoryKeyStore); - const memoryPrivateKeyStore = new MemoryStore(); - const kmsPrivateKeyStore = new KmsPrivateKeyStore(memoryPrivateKeyStore); - kms = new LocalKms('local', kmsKeyStore, kmsPrivateKeyStore); + kmsKeyStore = new KeyStoreMemory(); + kms = new LocalKms({ kmsName: 'memory', keyStore: kmsKeyStore, agent: testAgent }); + }); + + afterEach(async () => { + await testAgent.clearStorage(); + }); + + after(async () => { + await testAgent.closeStorage(); + }); + + describe('constructor', () => { + it('uses in-memory stores by default if not specified', () => { + expect( + new LocalKms({ kmsName: 'test' }) + ).to.not.throw; + }); + + it('uses keyStore instance, if given', () => { + const keyStore = new KeyStoreMemory(); + expect( + new LocalKms({ kmsName: 'test', keyStore }) + ).to.not.throw; + }); + + it('uses privateKeyStore instance, if given', () => { + const privateKeyStore = new PrivateKeyStoreMemory(); + expect( + new LocalKms({ kmsName: 'test', privateKeyStore }) + ).to.not.throw; + }); }); describe('decrypt()', () => { @@ -35,59 +71,39 @@ describe('LocalKms', () => { const plaintext = await kms.decrypt({ algorithm: { name : 'AES-CTR', - counter : new ArrayBuffer(16), + counter : new Uint8Array(16), length : 128 }, keyRef : key.id, data : new Uint8Array([1, 2, 3, 4]) }); - expect(plaintext).to.be.instanceOf(ArrayBuffer); + expect(plaintext).to.be.instanceOf(Uint8Array); expect(plaintext.byteLength).to.equal(4); }); - it('accepts input data as ArrayBuffer, DataView, and TypedArray', async () => { - const algorithm = { name: 'AES-CTR', counter: new ArrayBuffer(16), length: 128 }; + it('accepts input data as Uint8Array', async () => { + const algorithm = { name: 'AES-CTR', counter: new Uint8Array(16), length: 128 }; const dataU8A = new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8]); - let plaintext: ArrayBuffer; - - // ArrayBuffer - const dataArrayBuffer = dataU8A.buffer; - plaintext = await kms.decrypt({ algorithm, keyRef: key.id, data: dataArrayBuffer }); - expect(plaintext).to.be.instanceOf(ArrayBuffer); - - // DataView - const dataView = new DataView(dataArrayBuffer); - plaintext = await kms.decrypt({ algorithm, keyRef: key.id, data: dataView }); - expect(plaintext).to.be.instanceOf(ArrayBuffer); + let plaintext: Uint8Array; // TypedArray - Uint8Array plaintext = await kms.decrypt({ algorithm, keyRef: key.id, data: dataU8A }); - expect(plaintext).to.be.instanceOf(ArrayBuffer); - - // TypedArray - Int32Array - const dataI32A = new Int32Array([10, 20, 30, 40]); - plaintext = await kms.decrypt({ algorithm, keyRef: key.id, data: dataI32A }); - expect(plaintext).to.be.instanceOf(ArrayBuffer); - - // TypedArray - Uint32Array - const dataU32A = new Uint32Array([8, 7, 6, 5, 4, 3, 2, 1]); - plaintext = await kms.decrypt({ algorithm, keyRef: key.id, data: dataU32A }); - expect(plaintext).to.be.instanceOf(ArrayBuffer); + expect(plaintext).to.be.instanceOf(Uint8Array); }); it('decrypts data with AES-CTR', async () => { const plaintext = await kms.decrypt({ algorithm: { name : 'AES-CTR', - counter : new ArrayBuffer(16), + counter : new Uint8Array(16), length : 128 }, keyRef : key.id, data : new Uint8Array([1, 2, 3, 4]) }); - expect(plaintext).to.be.instanceOf(ArrayBuffer); + expect(plaintext).to.be.instanceOf(Uint8Array); expect(plaintext.byteLength).to.equal(4); }); @@ -95,7 +111,7 @@ describe('LocalKms', () => { await expect(kms.decrypt({ algorithm: { name : 'AES-CTR', - counter : new ArrayBuffer(16), + counter : new Uint8Array(16), length : 128 }, keyRef : 'non-existent-key', @@ -120,7 +136,7 @@ describe('LocalKms', () => { otherPartyPublicCryptoKey = { algorithm : otherPartyPublicKey.algorithm, extractable : otherPartyPublicKey.extractable, - handle : otherPartyPublicKey.material!, + material : otherPartyPublicKey.material!, type : otherPartyPublicKey.type, usages : otherPartyPublicKey.usages }; @@ -138,7 +154,7 @@ describe('LocalKms', () => { algorithm : { name: 'ECDH', publicKey: otherPartyPublicCryptoKey }, baseKeyRef : ownPrivateKey.id }); - expect(sharedSecret).to.be.an('ArrayBuffer'); + expect(sharedSecret).to.be.an('Uint8Array'); expect(sharedSecret.byteLength).to.equal(32); }); @@ -147,7 +163,7 @@ describe('LocalKms', () => { algorithm : { name: 'ECDH', publicKey: otherPartyPublicCryptoKey }, baseKeyRef : ownPrivateKey.id }); - expect(sharedSecret).to.be.an('ArrayBuffer'); + expect(sharedSecret).to.be.an('Uint8Array'); expect(sharedSecret.byteLength).to.equal(32); }); @@ -157,7 +173,7 @@ describe('LocalKms', () => { baseKeyRef : ownPrivateKey.id, length : 64 }); - expect(sharedSecret).to.be.an('ArrayBuffer'); + expect(sharedSecret).to.be.an('Uint8Array'); expect(sharedSecret.byteLength).to.equal(64 / 8); }); @@ -166,7 +182,7 @@ describe('LocalKms', () => { algorithm : { name: 'ECDH', publicKey: otherPartyPublicCryptoKey }, baseKeyRef : ownPrivateKey.id }); - expect(sharedSecret).to.be.an('ArrayBuffer'); + expect(sharedSecret).to.be.an('Uint8Array'); expect(sharedSecret.byteLength).to.equal(32); }); @@ -175,7 +191,7 @@ describe('LocalKms', () => { algorithm : { name: 'ECDH', publicKey: otherPartyPublicCryptoKey }, baseKeyRef : ownPrivateKey.id }); - expect(sharedSecret).to.be.an('ArrayBuffer'); + expect(sharedSecret).to.be.an('Uint8Array'); expect(sharedSecret.byteLength).to.equal(32); }); @@ -190,7 +206,7 @@ describe('LocalKms', () => { otherPartyPublicCryptoKey = { algorithm : otherPartyPublicKey.algorithm, extractable : otherPartyPublicKey.extractable, - handle : otherPartyPublicKey.material!, + material : otherPartyPublicKey.material!, type : otherPartyPublicKey.type, usages : otherPartyPublicKey.usages }; @@ -232,59 +248,39 @@ describe('LocalKms', () => { const ciphertext = await kms.encrypt({ algorithm: { name : 'AES-CTR', - counter : new ArrayBuffer(16), + counter : new Uint8Array(16), length : 128 }, keyRef : key.id, data : new Uint8Array([1, 2, 3, 4]) }); - expect(ciphertext).to.be.instanceOf(ArrayBuffer); + expect(ciphertext).to.be.instanceOf(Uint8Array); expect(ciphertext.byteLength).to.equal(4); }); - it('accepts input data as ArrayBuffer, DataView, and TypedArray', async () => { - const algorithm = { name: 'AES-CTR', counter: new ArrayBuffer(16), length: 128 }; + it('accepts input data as Uint8Array', async () => { + const algorithm = { name: 'AES-CTR', counter: new Uint8Array(16), length: 128 }; const dataU8A = new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8]); - let ciphertext: ArrayBuffer; - - // ArrayBuffer - const dataArrayBuffer = dataU8A.buffer; - ciphertext = await kms.encrypt({ algorithm, keyRef: key.id, data: dataArrayBuffer }); - expect(ciphertext).to.be.instanceOf(ArrayBuffer); - - // DataView - const dataView = new DataView(dataArrayBuffer); - ciphertext = await kms.encrypt({ algorithm, keyRef: key.id, data: dataView }); - expect(ciphertext).to.be.instanceOf(ArrayBuffer); + let ciphertext: Uint8Array; // TypedArray - Uint8Array ciphertext = await kms.encrypt({ algorithm, keyRef: key.id, data: dataU8A }); - expect(ciphertext).to.be.instanceOf(ArrayBuffer); - - // TypedArray - Int32Array - const dataI32A = new Int32Array([10, 20, 30, 40]); - ciphertext = await kms.encrypt({ algorithm, keyRef: key.id, data: dataI32A }); - expect(ciphertext).to.be.instanceOf(ArrayBuffer); - - // TypedArray - Uint32Array - const dataU32A = new Uint32Array([8, 7, 6, 5, 4, 3, 2, 1]); - ciphertext = await kms.encrypt({ algorithm, keyRef: key.id, data: dataU32A }); - expect(ciphertext).to.be.instanceOf(ArrayBuffer); + expect(ciphertext).to.be.instanceOf(Uint8Array); }); it('encrypts data with AES-CTR', async () => { const ciphertext = await kms.encrypt({ algorithm: { name : 'AES-CTR', - counter : new ArrayBuffer(16), + counter : new Uint8Array(16), length : 128 }, keyRef : key.id, data : new Uint8Array([1, 2, 3, 4]) }); - expect(ciphertext).to.be.instanceOf(ArrayBuffer); + expect(ciphertext).to.be.instanceOf(Uint8Array); expect(ciphertext.byteLength).to.equal(4); }); @@ -292,7 +288,7 @@ describe('LocalKms', () => { await expect(kms.encrypt({ algorithm: { name : 'AES-CTR', - counter : new ArrayBuffer(16), + counter : new Uint8Array(16), length : 128 }, keyRef : 'non-existent-key', @@ -315,8 +311,8 @@ describe('LocalKms', () => { // Check values that are identical for both keys in the pair. expect(keys.privateKey.algorithm.name).to.equal('ECDSA'); expect(keys.publicKey.algorithm.name).to.equal('ECDSA'); - expect(keys.privateKey.kms).to.equal('local'); - expect(keys.publicKey.kms).to.equal('local'); + expect(keys.privateKey.kms).to.equal('memory'); + expect(keys.publicKey.kms).to.equal('memory'); expect(keys.privateKey.spec).to.be.undefined; expect(keys.publicKey.spec).to.be.undefined; expect(keys.privateKey.state).to.equal('Enabled'); @@ -328,7 +324,7 @@ describe('LocalKms', () => { expect(keys.privateKey.usages).to.deep.equal(['sign']); // Check values unique to the public key. - expect(keys.publicKey.material).to.be.an.instanceOf(ArrayBuffer); + expect(keys.publicKey.material).to.be.an.instanceOf(Uint8Array); expect(keys.publicKey.type).to.equal('public'); expect(keys.publicKey.usages).to.deep.equal(['verify']); }); @@ -470,8 +466,10 @@ describe('LocalKms', () => { // Check values that are identical for both storedKeyPair in the pair. expect(storedKeyPair.privateKey.algorithm.name).to.equal('ECDSA'); expect(storedKeyPair.publicKey.algorithm.name).to.equal('ECDSA'); - expect(storedKeyPair.privateKey.kms).to.equal('local'); - expect(storedKeyPair.publicKey.kms).to.equal('local'); + expect(storedKeyPair.privateKey.id).to.not.equal(''); + expect(storedKeyPair.publicKey.id).to.not.equal(''); + expect(storedKeyPair.privateKey.kms).to.equal('memory'); + expect(storedKeyPair.publicKey.kms).to.equal('memory'); expect(storedKeyPair.privateKey.spec).to.be.undefined; expect(storedKeyPair.publicKey.spec).to.be.undefined; expect(storedKeyPair.privateKey.state).to.equal('Enabled'); @@ -483,7 +481,7 @@ describe('LocalKms', () => { expect(storedKeyPair.privateKey.usages).to.deep.equal(['sign']); // Check values unique to the public key. - expect(storedKeyPair.publicKey.material).to.be.an.instanceOf(ArrayBuffer); + expect(storedKeyPair.publicKey.material).to.be.an.instanceOf(Uint8Array); expect(storedKeyPair.publicKey.type).to.equal('public'); expect(storedKeyPair.publicKey.usages).to.deep.equal(['verify']); }); @@ -498,7 +496,7 @@ describe('LocalKms', () => { type : 'private', usages : ['sign'], }); - expect(importedPrivateKey.kms).to.equal('local'); + expect(importedPrivateKey.kms).to.equal('memory'); expect(importedPrivateKey).to.exist; // Verify the key is present in the key store. @@ -507,7 +505,8 @@ describe('LocalKms', () => { // Validate the expected values. expect(storedPrivateKey.algorithm.name).to.equal('ECDSA'); - expect(storedPrivateKey.kms).to.equal('local'); + expect(storedPrivateKey.id).to.not.equal(''); + expect(storedPrivateKey.kms).to.equal('memory'); expect(storedPrivateKey.spec).to.be.undefined; expect(storedPrivateKey.state).to.equal('Enabled'); expect(storedPrivateKey.material).to.be.undefined; @@ -525,7 +524,7 @@ describe('LocalKms', () => { type : 'public', usages : ['verify'], }); - expect(importedPublicKey.kms).to.equal('local'); + expect(importedPublicKey.kms).to.equal('memory'); expect(importedPublicKey).to.exist; // Verify the key is present in the key store. @@ -534,10 +533,11 @@ describe('LocalKms', () => { // Validate the expected values. expect(storedPublicKey.algorithm.name).to.equal('ECDSA'); - expect(storedPublicKey.kms).to.equal('local'); + expect(storedPublicKey.id).to.not.equal(''); + expect(storedPublicKey.kms).to.equal('memory'); expect(storedPublicKey.spec).to.be.undefined; expect(storedPublicKey.state).to.equal('Enabled'); - expect(storedPublicKey.material).to.be.an.instanceOf(ArrayBuffer); + expect(storedPublicKey.material).to.be.an.instanceOf(Uint8Array); expect(storedPublicKey.type).to.equal('public'); expect(storedPublicKey.usages).to.deep.equal(['verify']); }); @@ -547,12 +547,12 @@ describe('LocalKms', () => { const importedSecretKey = await kms.importKey({ algorithm : { name: 'AES-CTR', length: 128 }, extractable : true, - kms : 'local', + kms : 'memory', material : new Uint8Array([1, 2, 3, 4]), type : 'secret', usages : ['encrypt', 'decrypt'], }); - expect(importedSecretKey.kms).to.equal('local'); + expect(importedSecretKey.kms).to.equal('memory'); expect(importedSecretKey).to.exist; // Verify the key is present in the key store. @@ -561,7 +561,7 @@ describe('LocalKms', () => { // Validate the expected values. expect(storedSecretKey.algorithm.name).to.equal('AES-CTR'); - expect(storedSecretKey.kms).to.equal('local'); + expect(storedSecretKey.kms).to.equal('memory'); expect(storedSecretKey.spec).to.be.undefined; expect(storedSecretKey.state).to.equal('Enabled'); expect(storedSecretKey.material).to.be.undefined; @@ -581,7 +581,7 @@ describe('LocalKms', () => { type : 'private', usages : ['sign'], }); - expect(importedPrivateKey.kms).to.equal('local'); + expect(importedPrivateKey.kms).to.equal('memory'); }); it(`ignores the 'id' property and overwrites with internally generated unique identifier`, async () => { @@ -665,7 +665,7 @@ describe('LocalKms', () => { usages : ['verify'], }); expect(importedPrivateKey.material).to.exist; - expect(importedPrivateKey.material).to.be.an.instanceOf(ArrayBuffer); + expect(importedPrivateKey.material).to.be.an.instanceOf(Uint8Array); }); it('throws an error if public and private keys are swapped', async () => { @@ -722,7 +722,7 @@ describe('LocalKms', () => { await expect(kms.importKey({ algorithm : { name: 'AES-CTR', length: 128 }, extractable : true, - kms : 'local', + kms : 'memory', material : new Uint8Array([1, 2, 3, 4]), type : 'invalid', usages : ['encrypt', 'decrypt'], @@ -744,11 +744,11 @@ describe('LocalKms', () => { data : new Uint8Array([51, 52, 53]), }); - expect(signature).to.be.instanceOf(ArrayBuffer); + expect(signature).to.be.instanceOf(Uint8Array); expect(signature.byteLength).to.equal(64); }); - it('accepts input data as ArrayBuffer, DataView, and TypedArray', async () => { + it('accepts input data as Uint8Array', async () => { const algorithm = { name: 'ECDSA', hash: 'SHA-256' }; const dataU8A = new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8]); const keyPair = await kms.generateKey({ @@ -757,31 +757,11 @@ describe('LocalKms', () => { keyUsages : ['sign', 'verify'] }); const key = keyPair.privateKey; - let signature: ArrayBuffer; - - // ArrayBuffer - const dataArrayBuffer = dataU8A.buffer; - signature = await kms.sign({ algorithm, keyRef: key.id, data: dataArrayBuffer }); - expect(signature).to.be.instanceOf(ArrayBuffer); - - // DataView - const dataView = new DataView(dataArrayBuffer); - signature = await kms.sign({ algorithm, keyRef: key.id, data: dataView }); - expect(signature).to.be.instanceOf(ArrayBuffer); + let signature: Uint8Array; // TypedArray - Uint8Array signature = await kms.sign({ algorithm, keyRef: key.id, data: dataU8A }); - expect(signature).to.be.instanceOf(ArrayBuffer); - - // TypedArray - Int32Array - const dataI32A = new Int32Array([10, 20, 30, 40]); - signature = await kms.sign({ algorithm, keyRef: key.id, data: dataI32A }); - expect(signature).to.be.instanceOf(ArrayBuffer); - - // TypedArray - Uint32Array - const dataU32A = new Uint32Array([8, 7, 6, 5, 4, 3, 2, 1]); - signature = await kms.sign({ algorithm, keyRef: key.id, data: dataU32A }); - expect(signature).to.be.instanceOf(ArrayBuffer); + expect(signature).to.be.instanceOf(Uint8Array); }); it('generates ECDSA secp256k1 signatures', async () => { @@ -797,7 +777,7 @@ describe('LocalKms', () => { data : new Uint8Array([51, 52, 53]), }); - expect(signature).to.be.instanceOf(ArrayBuffer); + expect(signature).to.be.instanceOf(Uint8Array); expect(signature.byteLength).to.equal(64); }); @@ -814,7 +794,7 @@ describe('LocalKms', () => { data : new Uint8Array([51, 52, 53]), }); - expect(signature).to.be.instanceOf(ArrayBuffer); + expect(signature).to.be.instanceOf(Uint8Array); expect(signature.byteLength).to.equal(64); }); @@ -827,6 +807,75 @@ describe('LocalKms', () => { }); }); + describe('updateKey()', () => { + let testKey: ManagedKey; + let testKeyPair: ManagedKeyPair; + + beforeEach(async () => { + testKey = await kms.generateKey({ + algorithm : { name: 'AES-CTR', length: 128 }, + alias : 'test-key', + extractable : false, + keyUsages : ['encrypt', 'decrypt'], + metadata : { foo: 'bar'} + }); + + testKeyPair = await kms.generateKey({ + algorithm : { name: 'ECDSA', namedCurve: 'secp256k1' }, + alias : 'test-key-pair', + extractable : false, + keyUsages : ['sign', 'verify'], + metadata : { foo: 'bar'} + }); + }); + + it('should update a key by ID', async () => { + // Attempt to update the key's alias. + const newAlias = 'did:method:new'; + const updateResult = await kms.updateKey({ keyRef: testKey.id, alias: newAlias }); + + // Verify that the alias property was updated. + expect(updateResult).to.be.true; + const storedKey = await kms.getKey({ keyRef: testKey.id }); + expect(storedKey).to.have.property('alias', newAlias); + }); + + it('should update a key pair by ID', async () => { + // Attempt to update the key's alias. + const newAlias = 'did:method:new'; + const updateResult = await kms.updateKey({ keyRef: testKeyPair.publicKey.id, alias: newAlias }); + + // Verify that the alias property was updated. + expect(updateResult).to.be.true; + const storedKey = await kms.getKey({ keyRef: testKeyPair.publicKey.id }); + if (!('privateKey' in storedKey!)) throw new Error('Expected ManagedKeyPair and not ManagedKey'); + expect(storedKey.publicKey).to.have.property('alias', newAlias); + }); + + it('throws an error when key reference is not found', async () => { + await expect( + kms.updateKey({ keyRef: 'non-existent', alias: 'new-alias' }) + ).to.eventually.be.rejectedWith(Error, 'Key not found'); + }); + + it('returns false if the update operation failed', async () => { + /** Stub the `updateKey()` method of the kmsKeyStore instance of + * KeyStoreMemory to simulate a failed update. */ + sinon.stub(kmsKeyStore, 'updateKey').returns(Promise.resolve(false)); + + // Attempt to update the key's alias. + const updateResult = await kms.updateKey({ keyRef: testKey.id, alias: '' }); + + // Remove the instance method stub. + sinon.restore(); + + // Verify that the update failed. + expect(updateResult).to.be.false; + const storedKey = await kms.getKey({ keyRef: testKey.id }); + expect(storedKey).to.deep.equal(testKey); + }); + }); + describe('verify()', () => { it('returns a boolean result', async () => { const dataU8A = new Uint8Array([51, 52, 53]); @@ -853,7 +902,7 @@ describe('LocalKms', () => { expect(isValid).to.be.true; }); - it('accepts input data as ArrayBuffer, DataView, and TypedArray', async () => { + it('accepts input data as Uint8Array', async () => { const algorithm = { name: 'ECDSA', hash: 'SHA-256' }; const dataU8A = new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8]); const keyPair = await kms.generateKey({ @@ -861,37 +910,13 @@ describe('LocalKms', () => { extractable : false, keyUsages : ['sign', 'verify'] }); - let signature: ArrayBuffer; + let signature: Uint8Array; let isValid: boolean; - // ArrayBuffer - const dataArrayBuffer = dataU8A.buffer; - signature = await kms.sign({ algorithm, keyRef: keyPair.privateKey.id, data: dataArrayBuffer }); - isValid = await kms.verify({ algorithm, keyRef: keyPair.publicKey.id, signature, data: dataArrayBuffer }); - expect(isValid).to.be.true; - - // DataView - const dataView = new DataView(dataArrayBuffer); - signature = await kms.sign({ algorithm, keyRef: keyPair.privateKey.id, data: dataView }); - isValid = await kms.verify({ algorithm, keyRef: keyPair.publicKey.id, signature, data: dataView }); - expect(isValid).to.be.true; - // TypedArray - Uint8Array signature = await kms.sign({ algorithm, keyRef: keyPair.privateKey.id, data: dataU8A }); isValid = await kms.verify({ algorithm, keyRef: keyPair.publicKey.id, signature, data: dataU8A }); expect(isValid).to.be.true; - - // TypedArray - Int32Array - const dataI32A = new Int32Array([10, 20, 30, 40]); - signature = await kms.sign({ algorithm, keyRef: keyPair.privateKey.id, data: dataI32A }); - isValid = await kms.verify({ algorithm, keyRef: keyPair.publicKey.id, signature, data: dataI32A }); - expect(isValid).to.be.true; - - // TypedArray - Uint32Array - const dataU32A = new Uint32Array([8, 7, 6, 5, 4, 3, 2, 1]); - signature = await kms.sign({ algorithm, keyRef: keyPair.privateKey.id, data: dataU32A }); - isValid = await kms.verify({ algorithm, keyRef: keyPair.publicKey.id, signature, data: dataU32A }); - expect(isValid).to.be.true; }); it('verifies ECDSA secp256k1 signatures', async () => { @@ -930,60 +955,19 @@ describe('LocalKms', () => { await expect(kms.verify({ algorithm : { name: 'ECDSA', hash: 'SHA-256' }, keyRef : 'non-existent-key', - signature : (new Uint8Array([51, 52, 53])).buffer, + signature : (new Uint8Array([51, 52, 53])), data : new Uint8Array([51, 52, 53]) })).to.eventually.be.rejectedWith(Error, 'Key not found'); }); - }); - - describe('#getAlgorithm', function() { - /** - * We can't directly test private methods, but we can indirectly - * test their behavior through the methods that use them. Since - * #getAlgorithm() is used in the generateKey() method, we can - * test this methods with known algorithm names. - */ - it('does not throw an error when a supported algorithm is specified', async () => { - await expect(kms.generateKey({ - algorithm : { name: 'ECDSA', namedCurve: 'secp256k1' }, - keyUsages : ['sign', 'verify'] - })).to.eventually.be.fulfilled; - }); - - it('throws error when an unsupported algorithm is specified', async () => { - await expect(kms.generateKey({ - algorithm : { name: 'not-valid' }, - keyUsages : [] - })).to.eventually.be.rejectedWith(Error, 'is not supported'); - }); - }); - - describe('#toCryptoKey', function() { - /** - * We can't directly test private methods, but we can indirectly - * test their behavior through the methods that use them. Since - * #toCryptoKey() is used in the verify() method, we can - * test this methods with known algorithm names. - */ - - let getKeyStub: sinon.SinonStub; - let kms: LocalKms; - let kmsKeyStore: KmsKeyStore; - beforeEach(() => { - const kmsMemoryStore = new MemoryStore(); - kmsKeyStore = new KmsKeyStore(kmsMemoryStore); - const kmsPrivateMemoryStore = new MemoryStore(); - const kmsPrivateKeyStore = new KmsPrivateKeyStore(kmsPrivateMemoryStore); - kms = new LocalKms('local', kmsKeyStore, kmsPrivateKeyStore); - }); + it('throws error when public key material is missing', async () => { + let getKeyStub: sinon.SinonStub; + let kms: LocalKms; + let kmsKeyStore: KeyStoreMemory; - afterEach(() => { - // Restore the original KmsKeyStore getKey() method after each test. - getKeyStub.restore(); - }); + kmsKeyStore = new KeyStoreMemory(); + kms = new LocalKms({ kmsName: 'memory', keyStore: kmsKeyStore, agent: testAgent }); - it('throws error when key material is missing', async () => { const keyPair = await kms.generateKey({ algorithm : { name: 'ECDSA', namedCurve: 'secp256k1' }, extractable : false, @@ -992,275 +976,38 @@ describe('LocalKms', () => { getKeyStub = sinon.stub(kmsKeyStore, 'getKey'); getKeyStub.returns(Promise.resolve({ privateKey: {}, publicKey: {} })); + await expect(kms.verify({ algorithm : { name: 'ECDSA', hash: 'SHA-256' }, keyRef : keyPair.publicKey.id, - signature : new ArrayBuffer(64), + signature : new Uint8Array(64), data : new Uint8Array([51, 52, 53]) })).to.eventually.be.rejectedWith(Error, `Required property missing: 'material'`); - }); - }); -}); -describe('KmsKeyStore', () => { - let kmsKeyStore: KmsKeyStore; - let testKey: ManagedKey; - - beforeEach(() => { - const memoryStore = new MemoryStore(); - - kmsKeyStore = new KmsKeyStore(memoryStore); - - testKey = { - id : 'testKey', - algorithm : { name: 'AES', length: 256 }, - extractable : true, - kms : 'testKms', - state : 'Enabled', - type : 'secret', - usages : ['encrypt', 'decrypt'], - }; - }); - - describe('deleteKey()', () => { - it('should delete key and return true if key exists', async () => { - // Import the key. - await kmsKeyStore.importKey({ key: testKey }); - - // Test deleting the key and validate the result. - const deleteResult = await kmsKeyStore.deleteKey({ id: testKey.id }); - expect(deleteResult).to.be.true; - - // Verify the key is no longer in the store. - const storedKey = await kmsKeyStore.getKey({ id: testKey.id }); - expect(storedKey).to.be.undefined; - }); - - it('should return false if key does not exist', async () => { - // Test deleting the key. - const nonExistentId = '1234'; - const deleteResult = await kmsKeyStore.deleteKey({ id: nonExistentId }); - - // Validate the key was not deleted. - expect(deleteResult).to.be.false; - }); - }); - - describe('getKey()', () => { - it('should return a key if it exists', async () => { - // Import the key. - await kmsKeyStore.importKey({ key: testKey }); - - // Test getting the key. - const storedKey = await kmsKeyStore.getKey({ id: testKey.id }); - - // Verify the key is in the store. - expect(storedKey).to.deep.equal(testKey); - }); - - it('should return undefined when attempting to get a non-existent key', async () => { - // Test getting the key. - const storedKey = await kmsKeyStore.getKey({ id: 'non-existent-key' }); - - // Verify the key is no longer in the store. - expect(storedKey).to.be.undefined; - }); - }); - - describe('importKey()', () => { - it('should import a key that does not already exist', async () => { - // Test importing the key and validate the result. - const importResult = await kmsKeyStore.importKey({ key: testKey }); - expect(importResult).to.equal(testKey.id); - expect(importResult).to.be.a.string; - - // Verify the key is present in the key store. - const storedKey = await kmsKeyStore.getKey({ id: testKey.id }); - expect(storedKey).to.deep.equal(testKey); - }); - - it('should generate and return an ID if one is not provided', async () => { - const testKey = { - algorithm : { name: 'AES', length: 256 }, - extractable : true, - kms : 'testKms', - state : 'Enabled', - type : 'secret', - usages : ['encrypt', 'decrypt'], - }; - - // Test importing the key and validate the result. - // @ts-expect-error because the ID property was intentionally omitted from the key object to be imported. - const importResult = await kmsKeyStore.importKey({ key: testKey }); - expect(importResult).to.be.a.string; - - // Verify the key is present in the key store. - const storedKey = await kmsKeyStore.getKey({ id: importResult }) as ManagedKey; - expect(storedKey.id).to.equal(importResult); - }); - - it('should throw an error when attempting to import a key that already exists', async () => { - // Import the key and validate the result. - const importResult = await kmsKeyStore.importKey({ key: testKey }); - expect(importResult).to.equal(testKey.id); - - // Test importing the key and assert it throws an error. - const importKey = kmsKeyStore.importKey({ key: testKey }); - await expect(importKey).to.eventually.be.rejectedWith(Error, 'Key with ID already exists'); - }); - }); - - describe('listKeys()', () => { - it('should return an array of all keys in the store', async () => { - // Define multiple keys to be added. - const testKeys = [ - { ...testKey, ...{ id: 'key-1' }}, - { ...testKey, ...{ id: 'key-2' }}, - { ...testKey, ...{ id: 'key-3' }} - ]; - - // Import the keys into the store. - for (let key of testKeys) { - await kmsKeyStore.importKey({ key }); - } - - // List keys and verify the result. - const storedKeys = await kmsKeyStore.listKeys(); - expect(storedKeys).to.deep.equal(testKeys); - }); - - it('should return an empty array if the store contains no keys', async () => { - // List keys and verify the result is empty. - const storedKeys = await kmsKeyStore.listKeys(); - expect(storedKeys).to.be.empty; - }); - }); -}); - -describe('KmsPrivateKeyStore', () => { - let kmsPrivateKeyStore: KmsPrivateKeyStore; - let testKey: Omit; - let keyMaterial: ArrayBuffer; - - beforeEach(() => { - const memoryStore = new MemoryStore(); - - kmsPrivateKeyStore = new KmsPrivateKeyStore(memoryStore); - - keyMaterial = (new Uint8Array([1, 2, 3])).buffer; - testKey = { - material : (new Uint8Array([1, 2, 3])).buffer, - type : 'private', - }; - }); - - describe('deleteKey()', () => { - it('should delete key and return true if key exists', async () => { - // Import the key and get back the assigned ID. - const id = await kmsPrivateKeyStore.importKey({ key: testKey }); - - // Test deleting the key and validate the result. - const deleteResult = await kmsPrivateKeyStore.deleteKey({ id }); - expect(deleteResult).to.be.true; - - // Verify the key is no longer in the store. - const storedKey = await kmsPrivateKeyStore.getKey({ id }); - expect(storedKey).to.be.undefined; - }); - - it('should return false if key does not exist', async () => { - // Test deleting the key. - const deleteResult = await kmsPrivateKeyStore.deleteKey({ id: 'non-existent-key' }); - - // Validate the key was deleted. - expect(deleteResult).to.be.false; - }); - }); - - describe('getKey()', () => { - it('sshould return a key if it exists', async () => { - // Import the key. - const id = await kmsPrivateKeyStore.importKey({ key: testKey }); - - // Test getting the key. - const storedKey = await kmsPrivateKeyStore.getKey({ id }); - - // Verify the key is in the store. - expect(storedKey).to.deep.equal({ id, material: keyMaterial, type: 'private' }); - }); - - it('should return undefined if the specified key does not exist', async () => { - // Test getting the key. - const storedKey = await kmsPrivateKeyStore.getKey({ id: 'non-existent-key' }); - - // Verify the key is no longer in the store. - expect(storedKey).to.be.undefined; - }); - }); - - describe('importKey()', () => { - it('should import a private key and return its ID', async () => { - // Test importing the key. - const id = await kmsPrivateKeyStore.importKey({ key: testKey }); - - // Validate the returned id. - expect(id).to.be.a('string'); - - // Verify the key is present in the private key store. - const storedKey = await kmsPrivateKeyStore.getKey({ id }); - expect(storedKey).to.deep.equal({ id, material: keyMaterial, type: 'private' }); - }); - - it('should permanently transfer the private key material', async () => { - // Test importing the key. - await kmsPrivateKeyStore.importKey({ key: testKey }); - - // Verify that attempting to access the key material after import triggers an error. - // Chrome, Firefox, Node.js, and Firefox report different error messages but all contain 'detached'. - expect(() => new Uint8Array(testKey.material)).to.throw(TypeError, 'detached'); - }); - - it('should throw an error if required parameters are missing', async () => { - // Missing 'material'. - const keyMissingMaterial = { type: 'private' }; - await expect(kmsPrivateKeyStore.importKey({ - // @ts-expect-error because the material property is intentionally omitted to trigger an error. - key: keyMissingMaterial - })).to.eventually.be.rejectedWith(TypeError, `Required parameter was missing: 'material'`); - - // Missing 'type'. - const keyMissingType = { material: new ArrayBuffer(8) }; - await expect(kmsPrivateKeyStore.importKey({ - // @ts-expect-error because the type property is intentionally omitted to trigger an error. - key: keyMissingType - })).to.eventually.be.rejectedWith(TypeError, `Required parameter was missing: 'type'`); + // Restore the original KmsKeyStore getKey() method after each test. + getKeyStub.restore(); }); }); - describe('listKeys()', function() { - it('should return an array of all keys in the store', async function() { - // Define multiple keys to be added. - const testKeys = [ - { ...testKey, material: (new Uint8Array([1, 2, 3])).buffer}, - { ...testKey, material: (new Uint8Array([1, 2, 3])).buffer}, - { ...testKey, material: (new Uint8Array([1, 2, 3])).buffer} - ]; - - // Import the keys into the store. - const expectedTestKeys: ManagedPrivateKey[] = []; - for (let key of testKeys) { - const id = await kmsPrivateKeyStore.importKey({ key }); - expectedTestKeys.push({ id, material: keyMaterial, type: 'private', }); - } - - const storedKeys = await kmsPrivateKeyStore.listKeys(); - expect(storedKeys).to.deep.equal(expectedTestKeys); + describe('getAlgorithm', function() { + /** + * We can't directly test private methods, but we can indirectly + * test their behavior through the methods that use them. Since + * #getAlgorithm() is used in the generateKey() method, we can + * test this methods with known algorithm names. + */ + it('does not throw an error when a supported algorithm is specified', async () => { + await expect(kms.generateKey({ + algorithm : { name: 'ECDSA', namedCurve: 'secp256k1' }, + keyUsages : ['sign', 'verify'] + })).to.eventually.be.fulfilled; }); - it('should return an empty array if the store contains no keys', async function() { - // List keys and verify the result is empty. - const storedKeys = await kmsPrivateKeyStore.listKeys(); - expect(storedKeys).to.be.empty; + it('throws error when an unsupported algorithm is specified', async () => { + await expect(kms.generateKey({ + algorithm : { name: 'not-valid' }, + keyUsages : [] + })).to.eventually.be.rejectedWith(Error, 'is not supported'); }); }); }); \ No newline at end of file diff --git a/packages/agent/tests/store-managed-key.spec.ts b/packages/agent/tests/store-managed-key.spec.ts new file mode 100644 index 000000000..5009a9944 --- /dev/null +++ b/packages/agent/tests/store-managed-key.spec.ts @@ -0,0 +1,1396 @@ +import type { PortableDid } from '@web5/dids'; +import type { Web5Crypto } from '@web5/crypto'; + +import chai, { expect } from 'chai'; +import { DidKeyMethod } from '@web5/dids'; +import chaiAsPromised from 'chai-as-promised'; +import { EdDsaAlgorithm, Jose } from '@web5/crypto'; + +import type { ManagedKey, ManagedKeyPair, ManagedPrivateKey } from '../src/types/managed-key.js'; + +import { LocalKms } from '../src/kms-local.js'; +import { TestAgent } from './utils/test-agent.js'; +import { KeyManager } from '../src/key-manager.js'; +import { cryptoToPortableKeyPair, isManagedKeyPair } from '../src/utils.js'; +import { + KeyStoreDwn, + KeyStoreMemory, + PrivateKeyStoreDwn, + PrivateKeyStoreMemory +} from '../src/store-managed-key.js'; + +chai.use(chaiAsPromised); + +describe('KeyStoreDwn', () => { + const testConfigurations = [ + { + name : 'KeyManager', + keyStoreDwn : new KeyStoreDwn({ schema: 'https://identity.foundation/schemas/web5/managed-key' }), + keyStoreMemory : new KeyStoreMemory(), + }, + { + name : 'LocalKms', + keyStoreDwn : new KeyStoreDwn({ schema: 'https://identity.foundation/schemas/web5/kms-key' }), + keyStoreMemory : new KeyStoreMemory(), + } + ]; + + let agentSigningKey: Web5Crypto.CryptoKeyPair; + let agentDid: PortableDid; + let testAgent: TestAgent; + + before(async () => { + testAgent = await TestAgent.create(); + }); + + beforeEach(async () => { + agentDid = await DidKeyMethod.create({ keyAlgorithm: 'Ed25519' }); + const privateCryptoKey = await Jose.jwkToCryptoKey({ key: agentDid.keySet.verificationMethodKeys![0].privateKeyJwk! }); + const publicCryptoKey = await Jose.jwkToCryptoKey({ key: agentDid.keySet.verificationMethodKeys![0].publicKeyJwk! }); + + agentSigningKey = { + privateKey : privateCryptoKey, + publicKey : publicCryptoKey + }; + }); + + afterEach(async () => { + await testAgent.clearStorage(); + }); + + after(async () => { + await testAgent.closeStorage(); + }); + + testConfigurations.forEach((testConfiguration) => { + describe(`with ${testConfiguration.name}`, () => { + let keyStoreDwn: KeyStoreDwn; + let kmsKeyStoreMemory: KeyStoreMemory; + let kmsPrivateKeyStoreMemory: PrivateKeyStoreMemory; + + beforeEach(async () => { + // Instantiate a local KMS with in-memory key stores. + kmsKeyStoreMemory = new KeyStoreMemory(); + const localKmsMemory = new LocalKms({ + kmsName : 'memory', + keyStore : kmsKeyStoreMemory + }); + + kmsPrivateKeyStoreMemory = new PrivateKeyStoreMemory(); + keyStoreDwn = testConfiguration.keyStoreDwn; + + if (testConfiguration.name === 'KeyManager') { + const localKmsDwn = new LocalKms({ + kmsName : 'local', + keyStore : testConfiguration.keyStoreMemory, + privateKeyStore : kmsPrivateKeyStoreMemory + }); + + const keyManager = new KeyManager({ + kms: { + local : localKmsDwn, + memory : localKmsMemory + }, + store: testConfiguration.keyStoreDwn + }); + keyManager.agent = testAgent; + testAgent.keyManager = keyManager; + } + + if (testConfiguration.name === 'LocalKms') { + const localKmsDwn = new LocalKms({ + kmsName : 'local', + keyStore : testConfiguration.keyStoreDwn, + privateKeyStore : kmsPrivateKeyStoreMemory + }); + + const keyManager = new KeyManager({ + kms: { + local : localKmsDwn, + memory : localKmsMemory + }, + store: testConfiguration.keyStoreMemory + }); + keyManager.agent = testAgent; + testAgent.keyManager = keyManager; + } + + // Convert the CryptoKeyPair object to a PortableKeyPair. + const defaultSigningKey = cryptoToPortableKeyPair({ + cryptoKeyPair : agentSigningKey, + keyData : { + alias : await testAgent.didManager.getDefaultSigningKey({ did: agentDid.did }), + kms : 'memory' + } + }); + + // Import the Agent's signing key pair to the in-memory KMS key stores. + await testAgent.keyManager.setDefaultSigningKey({ key: defaultSigningKey }); + + // Set the Agent's DID + testAgent.agentDid = agentDid.did; + }); + + describe('deleteKey()', () => { + let importedKeyPairId: string; + + beforeEach(async () => { + // Generate a key pair to import. + const randomKeyPair = await new EdDsaAlgorithm().generateKey({ + algorithm : { name: 'EdDSA', namedCurve: 'Ed25519' }, + extractable : true, + keyUsages : ['sign', 'verify'] + }); + + // Import the private key to the in-memory private key store. + const importedPrivateKeyId = await kmsPrivateKeyStoreMemory.importKey({ key: { + material : randomKeyPair.privateKey.material, + type : 'private' + }}); + + // And finally, attempt to import the key into the DWN-backed key store. + importedKeyPairId = await keyStoreDwn.importKey({ + agent : testAgent, + key : { + privateKey : { ...randomKeyPair.privateKey, id: importedPrivateKeyId, kms: 'local', state: 'Enabled' }, + publicKey : { ...randomKeyPair.publicKey, id: importedPrivateKeyId, kms: 'local', material: randomKeyPair.publicKey.material, state: 'Enabled' } + } + }); + }); + + it('deletes key and returns true if key exists', async () => { + // Test deleting the key and validate the result. + const deleteResult = await keyStoreDwn.deleteKey({ id: importedKeyPairId, agent: testAgent }); + expect(deleteResult).to.be.true; + + // Verify the key is no longer in the store. + const storedKey = await keyStoreDwn.getKey({ id: importedKeyPairId, agent: testAgent }); + expect(storedKey).to.be.undefined; + }); + + it('returns false if key does not exist', async () => { + // Test deleting the key. + const deleteResult = await keyStoreDwn.deleteKey({ id: 'non-existent', agent: testAgent }); + + // Validate the key was not deleted. + expect(deleteResult).to.be.false; + }); + + it('throws an error if Agent DID is undefined and no context was specified', async () => { + // Unset the Agent DID. + testAgent.agentDid = undefined; + await expect( + keyStoreDwn.deleteKey({ id: importedKeyPairId, agent: testAgent }) + ).to.eventually.be.rejectedWith(Error, `Agent property 'agentDid' is undefined and no context was specified`); + }); + }); + + describe('findKey()', () => { + let importedKeyPairId: string; + + beforeEach(async () => { + // Generate a key pair to import. + const randomKeyPair = await new EdDsaAlgorithm().generateKey({ + algorithm : { name: 'EdDSA', namedCurve: 'Ed25519' }, + extractable : true, + keyUsages : ['sign', 'verify'] + }); + + // Import the private key to the in-memory private key store. + const importedPrivateKeyId = await kmsPrivateKeyStoreMemory.importKey({ key: { + material : randomKeyPair.privateKey.material, + type : 'private' + }}); + + // And finally, attempt to import the key into the DWN-backed key store. + importedKeyPairId = await keyStoreDwn.importKey({ + agent : testAgent, + key : { + privateKey : { ...randomKeyPair.privateKey, id: importedPrivateKeyId, kms: 'local', alias: 'external-id', state: 'Enabled' }, + publicKey : { ...randomKeyPair.publicKey, id: importedPrivateKeyId, kms: 'local', alias: 'external-id', material: randomKeyPair.publicKey.material, state: 'Enabled' } + } + }); + }); + + it('returns a key by ID if it exists', async () => { + // Test finding the key. + const storedKey = await keyStoreDwn.findKey({ id: importedKeyPairId, agent: testAgent}); + + // Verify the key is in the store. + if (!(storedKey && 'publicKey' in storedKey)) throw Error(); // Type guard. + expect(storedKey.publicKey.id).to.equal(importedKeyPairId); + }); + + it('returns a key by alias if it exists', async () => { + // Test finding the key. + const storedKey = await keyStoreDwn.findKey({ alias: 'external-id', agent: testAgent}); + + // Verify the key is in the store. + if (!(storedKey && 'publicKey' in storedKey)) throw Error(); // Type guard. + expect(storedKey.publicKey.id).to.equal(importedKeyPairId); + }); + + it('returns undefined when attempting to get a non-existent key', async () => { + // Test finding the key by ID. + expect( + await keyStoreDwn.findKey({ id: 'non-existent-did', agent: testAgent }) + ).to.be.undefined; + + // Test finding the key by alias. + expect( + await keyStoreDwn.findKey({ alias: 'non-existent-did', agent: testAgent }) + ).to.be.undefined; + }); + + it('throws an error if Agent DID is undefined and no context was specified', async () => { + // Unset the Agent DID. + testAgent.agentDid = undefined; + await expect( + keyStoreDwn.findKey({ id: importedKeyPairId, agent: testAgent }) + ).to.eventually.be.rejectedWith(Error, `Agent property 'agentDid' is undefined and no context was specified`); + }); + + it('throws an error if Agent DID is undefined when searching by alias', async () => { + // Unset the Agent DID. + testAgent.agentDid = undefined; + await expect( + keyStoreDwn.findKey({ alias: 'external-id', agent: testAgent }) + ).to.eventually.be.rejectedWith(Error, `Agent property 'agentDid' is undefined`); + }); + }); + + describe('getKey()', () => { + let importedKeyPairId: string; + + beforeEach(async () => { + // Generate a key pair to import. + const randomKeyPair = await new EdDsaAlgorithm().generateKey({ + algorithm : { name: 'EdDSA', namedCurve: 'Ed25519' }, + extractable : true, + keyUsages : ['sign', 'verify'] + }); + + // Import the private key to the in-memory private key store. + const importedPrivateKeyId = await kmsPrivateKeyStoreMemory.importKey({ key: { + material : randomKeyPair.privateKey.material, + type : 'private' + }}); + + // And finally, attempt to import the key into the DWN-backed key store. + importedKeyPairId = await keyStoreDwn.importKey({ + agent : testAgent, + key : { + privateKey : { ...randomKeyPair.privateKey, id: importedPrivateKeyId, kms: 'local', alias: 'external-id', state: 'Enabled' }, + publicKey : { ...randomKeyPair.publicKey, id: importedPrivateKeyId, kms: 'local', alias: 'external-id', material: randomKeyPair.publicKey.material, state: 'Enabled' } + } + }); + }); + + it('returns a key by ID if it exists', async () => { + // Test getting the key. + const storedKey = await keyStoreDwn.getKey({ id: importedKeyPairId, agent: testAgent }); + + // Verify the key is in the store. + if (!(storedKey && 'publicKey' in storedKey)) throw Error(); // Type guard. + expect(storedKey.publicKey.id).to.equal(importedKeyPairId); + }); + + it('returns undefined when attempting to get a non-existent DID', async () => { + // Test getting the key. + const storedKey = await keyStoreDwn.getKey({ id: 'non-existent', agent: testAgent }); + + // Verify the result is undefined. + expect(storedKey).to.be.undefined; + }); + + it('throws an error if Agent DID is undefined and no context was specified', async () => { + // Unset the Agent DID. + testAgent.agentDid = undefined; + await expect( + keyStoreDwn.getKey({ id: importedKeyPairId, agent: testAgent }) + ).to.eventually.be.rejectedWith(Error, `Agent property 'agentDid' is undefined and no context was specified`); + }); + }); + + describe('importKey()', () => { + it('imports a key after Agent signing key is stored by in-memory KMS', async () => { + // Generate a key pair to import. + const randomKeyPair = await new EdDsaAlgorithm().generateKey({ + algorithm : { name: 'EdDSA', namedCurve: 'Ed25519' }, + extractable : true, + keyUsages : ['sign', 'verify'] + }); + + // Import the private key to the in-memory private key store. + const importedPrivateKeyId = await kmsPrivateKeyStoreMemory.importKey({ key: { + material : randomKeyPair.privateKey.material, + type : 'private' + }}); + + // And finally, attempt to import the key into the DWN-backed key store. + const importedKeyPairId = await keyStoreDwn.importKey({ + agent : testAgent, + key : { + privateKey : { ...randomKeyPair.privateKey, id: importedPrivateKeyId, kms: 'local', state: 'Enabled' }, + publicKey : { ...randomKeyPair.publicKey, id: importedPrivateKeyId, kms: 'local', material: randomKeyPair.publicKey.material, state: 'Enabled' } + } + }); + + // Verify the key is present in the DWN-backed key store. + const storedKeyPair = await keyStoreDwn.getKey({ id: importedKeyPairId, agent: testAgent }); + if (!isManagedKeyPair(storedKeyPair)) throw new Error('Type guard unexpectedly threw'); // Type guard. + expect(storedKeyPair.publicKey.id).to.equal(importedKeyPairId); + expect(storedKeyPair.privateKey.id).to.equal(importedKeyPairId); + expect(storedKeyPair.publicKey.kms).to.equal('local'); + expect(storedKeyPair.privateKey.kms).to.equal('local'); + }); + + it('uses the specified ID for the imported key', async () => { + const testKey: ManagedKey = { + id : 'test123', + algorithm : { name: 'AES', length: 256 }, + extractable : true, + kms : 'testKms', + state : 'Enabled', + type : 'secret', + usages : ['encrypt', 'decrypt'], + }; + + // Test importing the key and validate the result. + const importedKeyId = await keyStoreDwn.importKey({ key: testKey, agent: testAgent }); + expect(importedKeyId).to.equal(testKey.id); + }); + + it('generates and return an ID if one is not provided', async () => { + const testKey = { + algorithm : { name: 'AES', length: 256 }, + extractable : true, + kms : 'testKms', + state : 'Enabled', + type : 'secret', + usages : ['encrypt', 'decrypt'], + }; + + // Test importing the key and validate the result. + // @ts-expect-error because the ID property was intentionally omitted from the key object to be imported. + const importedKeyId = await keyStoreDwn.importKey({ key: testKey, agent: testAgent }); + expect(importedKeyId).to.be.a.string; + + // Verify the key is present in the key store. + const storedKey = await keyStoreDwn.getKey({ id: importedKeyId, agent: testAgent }) as ManagedKey; + expect(storedKey.id).to.equal(importedKeyId); + }); + + it('throws an error when attempting to import a key that already exists', async () => { + const testKey: ManagedKey = { + id : 'test123', + algorithm : { name: 'AES', length: 256 }, + extractable : true, + kms : 'testKms', + state : 'Enabled', + type : 'secret', + usages : ['encrypt', 'decrypt'], + }; + + // Test importing the key and validate the result. + const importedKeyId = await keyStoreDwn.importKey({ key: testKey, agent: testAgent }); + expect(importedKeyId).to.equal(testKey.id); + + // Test importing the same key again and assert it throws an error. + await expect( + keyStoreDwn.importKey({ key: testKey, agent: testAgent }) + ).to.eventually.be.rejectedWith(Error, 'Key with ID already exists'); + }); + + it('throws an error if Agent DID is undefined and no context was specified', async () => { + // Unset the Agent DID. + testAgent.agentDid = undefined; + await expect( + keyStoreDwn.importKey({ key: { id: undefined } as any, agent: testAgent }) + ).to.eventually.be.rejectedWith(Error, `Agent property 'agentDid' is undefined and no context was specified`); + }); + }); + + describe('listKeys()', () => { + it('returns an array of all keys in the store', async () => { + // Create key pair to import. + const randomKeyPair = await new EdDsaAlgorithm().generateKey({ + algorithm : { name: 'EdDSA', namedCurve: 'Ed25519' }, + extractable : true, + keyUsages : ['sign', 'verify'] + }); + const portableKeyPair = cryptoToPortableKeyPair({ + cryptoKeyPair : randomKeyPair, + keyData : { kms: 'local' } + }); + + // Import the key material three times. + const importedKey1 = await testAgent.keyManager.importKey(structuredClone(portableKeyPair)); + const importedKey2 = await testAgent.keyManager.importKey(structuredClone(portableKeyPair)); + const importedKey3 = await testAgent.keyManager.importKey(structuredClone(portableKeyPair)); + + // List keys and verify the result. + const storedKeys = await keyStoreDwn.listKeys({ agent: testAgent }); + expect(storedKeys).to.have.length(3); + const importedKeys = [importedKey1.publicKey.id, importedKey2.publicKey.id, importedKey3.publicKey.id]; + for (const storedKey of storedKeys) { + if (!isManagedKeyPair(storedKey)) throw Error(); // Type guard. + expect(importedKeys).to.include(storedKey.publicKey.id); + } + }); + + it('should return an empty array if the store contains no keys', async function() { + // List keys and verify the result is empty. + const storedKeys = await keyStoreDwn.listKeys({ agent: testAgent }); + expect(storedKeys).to.be.empty; + }); + + it('throws an error if Agent DID is undefined and no context was specified', async () => { + // Unset the Agent DID. + testAgent.agentDid = undefined; + await expect( + keyStoreDwn.listKeys({ agent: testAgent }) + ).to.eventually.be.rejectedWith(Error, `Agent property 'agentDid' is undefined and no context was specified`); + }); + }); + + describe('updateKey()', () => { + let importedKeyPairId: string; + + beforeEach(async () => { + // Generate a key pair to import. + const randomKeyPair = await new EdDsaAlgorithm().generateKey({ + algorithm : { name: 'EdDSA', namedCurve: 'Ed25519' }, + extractable : true, + keyUsages : ['sign', 'verify'] + }); + + // Import the private key to the in-memory private key store. + const importedPrivateKeyId = await kmsPrivateKeyStoreMemory.importKey({ key: { + material : randomKeyPair.privateKey.material, + type : 'private' + }}); + + // And finally, attempt to import the key into the DWN-backed key store. + importedKeyPairId = await keyStoreDwn.importKey({ + agent : testAgent, + key : { + privateKey : { ...randomKeyPair.privateKey, id: importedPrivateKeyId, kms: 'local', state: 'Enabled' }, + publicKey : { ...randomKeyPair.publicKey, id: importedPrivateKeyId, kms: 'local', material: randomKeyPair.publicKey.material, state: 'Enabled' } + } + }); + }); + + it('updates key alias and return true if key exists', async () => { + // Test updating the key and validate the result. + const updateResult = await keyStoreDwn.updateKey({ id: importedKeyPairId, alias: 'new-alias', agent: testAgent }); + expect(updateResult).to.be.true; + + const storedKey = await keyStoreDwn.getKey({ id: importedKeyPairId, agent: testAgent }); + if (!isManagedKeyPair(storedKey)) throw new Error('Type guard unexpectedly threw'); // Type guard. + expect(storedKey.privateKey.alias).to.equal('new-alias'); + }); + + it('updates key metadata and return true if key exists', async () => { + // Test updating the key and validate the result. + const updateResult = await keyStoreDwn.updateKey({ id: importedKeyPairId, metadata: { foo: 'bar' }, agent: testAgent }); + expect(updateResult).to.be.true; + + const storedKey = await keyStoreDwn.getKey({ id: importedKeyPairId, agent: testAgent }); + if (!isManagedKeyPair(storedKey)) throw new Error('Type guard unexpectedly threw'); // Type guard. + expect(storedKey.privateKey.metadata).to.deep.equal({ foo: 'bar' }); + }); + + it('returns false when attempting to update a non-existent key', async () => { + // Test updating the key and validate the result. + const updateResult = await keyStoreDwn.updateKey({ id: 'non-existent', metadata: { foo: 'bar' }, agent: testAgent }); + expect(updateResult).to.be.false; + }); + }); + + describe('data integrity', () => { + it('imports and gets stored key metadata without any alterations', async () => { + const randomKeyPair = await new EdDsaAlgorithm().generateKey({ + algorithm : { name: 'EdDSA', namedCurve: 'Ed25519' }, + extractable : true, + keyUsages : ['sign', 'verify'] + }); + const originalPublicKey = structuredClone(randomKeyPair.publicKey.material); + const originalPrivateKey = structuredClone(randomKeyPair.privateKey.material); + + // Import the private key to the in-memory private key store. + const importedPrivateKeyId = await kmsPrivateKeyStoreMemory.importKey({ + key: { + material : randomKeyPair.privateKey.material, + type : 'private' + } + }); + + // Import the key into the key store. + const importedKeyPairId = await keyStoreDwn.importKey({ + agent : testAgent, + key : { + privateKey : { ...randomKeyPair.privateKey, id: importedPrivateKeyId, kms: 'local', alias: 'external-id', state: 'Enabled' }, + publicKey : { ...randomKeyPair.publicKey, id: importedPrivateKeyId, kms: 'local', alias: 'external-id', material: randomKeyPair.publicKey.material, state: 'Enabled' } + } + }); + + // Retrieve the private key. + const storedPrivateKey = await kmsPrivateKeyStoreMemory.getKey({ id: importedPrivateKeyId }); + expect(storedPrivateKey?.material).to.deep.equal(originalPrivateKey); + + // Retrieve the key metadata. + const storedKey = await keyStoreDwn.getKey({ id: importedKeyPairId, agent: testAgent }) as ManagedKeyPair; + expect(storedKey?.publicKey.material).to.deep.equal(originalPublicKey); + }); + + it('finds key without any alterations', async () => { + const randomKeyPair = await new EdDsaAlgorithm().generateKey({ + algorithm : { name: 'EdDSA', namedCurve: 'Ed25519' }, + extractable : true, + keyUsages : ['sign', 'verify'] + }); + const originalPublicKey = structuredClone(randomKeyPair.publicKey.material); + + // Import the private key to the in-memory private key store. + const importedPrivateKeyId = await kmsPrivateKeyStoreMemory.importKey({ + key: { + material : randomKeyPair.privateKey.material, + type : 'private' + } + }); + + // Import the key into the key store. + const importedKeyPairId = await keyStoreDwn.importKey({ + agent : testAgent, + key : { + privateKey : { ...randomKeyPair.privateKey, id: importedPrivateKeyId, kms: 'local', alias: 'external-id', state: 'Enabled' }, + publicKey : { ...randomKeyPair.publicKey, id: importedPrivateKeyId, kms: 'local', alias: 'external-id', material: randomKeyPair.publicKey.material, state: 'Enabled' } + } + }); + + // Find the key. + const storedKey = await keyStoreDwn.findKey({ id: importedKeyPairId, agent: testAgent }) as ManagedKeyPair; + expect(storedKey?.publicKey.material).to.deep.equal(originalPublicKey); + }); + + it('updates keys without any alterations', async () => { + const randomKeyPair = await new EdDsaAlgorithm().generateKey({ + algorithm : { name: 'EdDSA', namedCurve: 'Ed25519' }, + extractable : true, + keyUsages : ['sign', 'verify'] + }); + const originalPublicKey = structuredClone(randomKeyPair.publicKey.material); + + // Import the private key to the in-memory private key store. + const importedPrivateKeyId = await kmsPrivateKeyStoreMemory.importKey({ + key: { + material : randomKeyPair.privateKey.material, + type : 'private' + } + }); + + // Import the key into the key store. + const importedKeyPairId = await keyStoreDwn.importKey({ + agent : testAgent, + key : { + privateKey : { ...randomKeyPair.privateKey, id: importedPrivateKeyId, kms: 'local', alias: 'external-id', state: 'Enabled' }, + publicKey : { ...randomKeyPair.publicKey, id: importedPrivateKeyId, kms: 'local', alias: 'external-id', material: randomKeyPair.publicKey.material, state: 'Enabled' } + } + }); + + // Update the key. + const updateResult = await keyStoreDwn.updateKey({ id: importedKeyPairId, alias: 'new-alias', agent: testAgent }); + expect(updateResult).to.be.true; + + // Retrieve the key. + const storedKey = await keyStoreDwn.getKey({ id: importedKeyPairId, agent: testAgent }); + if (!isManagedKeyPair(storedKey)) throw new Error('Type guard unexpectedly threw'); // Type guard. + expect(storedKey?.publicKey.material).to.deep.equal(originalPublicKey); + expect(storedKey?.publicKey.alias).to.equal('new-alias'); + }); + }); + }); + }); +}); + +describe('KeyStoreMemory', () => { + let keyStore: KeyStoreMemory; + let testKey: ManagedKey; + let testKeyPair: ManagedKeyPair; + + beforeEach(() => { + keyStore = new KeyStoreMemory(); + + testKey = { + id : 'testKey', + alias : 'did:method:abc123', + algorithm : { name: 'AES', length: 256 }, + extractable : true, + kms : 'testKms', + state : 'Enabled', + type : 'secret', + usages : ['encrypt', 'decrypt'], + }; + + testKeyPair = { + privateKey: { + ...testKey, + alias : 'did:method:def456', + id : 'testKeyPair', + type : 'private', + }, + + publicKey: { + ...testKey, + alias : 'did:method:def456', + id : 'testKeyPair', + type : 'public', + } + }; + }); + + describe('deleteKey()', () => { + it('should delete key and return true if key exists', async () => { + // Import the key. + await keyStore.importKey({ key: testKey }); + + // Test deleting the key and validate the result. + const deleteResult = await keyStore.deleteKey({ id: testKey.id }); + expect(deleteResult).to.be.true; + + // Verify the key is no longer in the store. + const storedKey = await keyStore.getKey({ id: testKey.id }); + expect(storedKey).to.be.undefined; + }); + + it('should return false if key does not exist', async () => { + // Test deleting the key. + const nonExistentId = '1234'; + const deleteResult = await keyStore.deleteKey({ id: nonExistentId }); + + // Validate the key was not deleted. + expect(deleteResult).to.be.false; + }); + }); + + describe('findKey()', () => { + + beforeEach(async () => { + // Import the key. + await keyStore.importKey({ key: testKey }); + }); + + it('should return a key by ID if it exists', async () => { + // Test finding the key. + const storedKey = await keyStore.findKey({ id: testKey.id }); + + // Verify the key is in the store. + expect(storedKey).to.deep.equal(testKey); + }); + + it('should return a key by alias if it exists', async () => { + // Test finding the key. + const storedKey = await keyStore.findKey({ alias: 'did:method:abc123' }); + + // Verify the key is in the store. + expect(storedKey).to.deep.equal(testKey); + }); + + it('should return a key pair by alias if it exists', async () => { + // Import the key pair. + await keyStore.importKey({ key: testKeyPair }); + + // Test finding the key pair. + const storedKey = await keyStore.findKey({ alias: 'did:method:def456' }); + + // Verify the key is in the store. + expect(storedKey).to.deep.equal(testKeyPair); + }); + + it('should return undefined when attempting to get a non-existent key', async () => { + // Test finding the key by ID. + expect( + await keyStore.findKey({ id: 'non-existent-key' }) + ).to.be.undefined; + + // Test finding the key by alias. + expect( + await keyStore.findKey({ alias: 'non-existent-key' }) + ).to.be.undefined; + }); + }); + + describe('getKey()', () => { + it('should return a key if it exists', async () => { + // Import the key. + await keyStore.importKey({ key: testKey }); + + // Test getting the key. + const storedKey = await keyStore.getKey({ id: testKey.id }); + + // Verify the key is in the store. + expect(storedKey).to.deep.equal(testKey); + }); + + it('should return a key pair by ID if it exists', async () => { + // Import the key pair. + await keyStore.importKey({ key: testKeyPair }); + + // Test finding the key pair. + const storedKey = await keyStore.getKey({ id: testKeyPair.publicKey.id }); + + // Verify the key is in the store. + expect(storedKey).to.deep.equal(testKeyPair); + }); + + it('should return undefined when attempting to get a non-existent key', async () => { + // Test getting the key. + const storedKey = await keyStore.getKey({ id: 'non-existent-key' }); + + // Verify the key is no longer in the store. + expect(storedKey).to.be.undefined; + }); + }); + + describe('importKey()', () => { + it('should import a key that does not already exist', async () => { + // Test importing the key and validate the result. + const importResult = await keyStore.importKey({ key: testKey }); + expect(importResult).to.equal(testKey.id); + expect(importResult).to.be.a.string; + + // Verify the key is present in the key store. + const storedKey = await keyStore.getKey({ id: testKey.id }); + expect(storedKey).to.deep.equal(testKey); + }); + + it('should generate and return an ID if one is not provided', async () => { + // @ts-expect-error because the ID property was intentionally omitted from the key object to be imported. + const testKey: ManagedKey = { + algorithm : { name: 'AES', length: 256 }, + extractable : true, + kms : 'testKms', + state : 'Enabled', + type : 'secret', + usages : ['encrypt', 'decrypt'], + }; + + // Test importing the key and validate the result. + const importResult = await keyStore.importKey({ key: testKey }); + expect(importResult).to.be.a.string; + + // Verify the key is present in the key store. + const storedKey = await keyStore.getKey({ id: importResult }) as ManagedKey; + expect(storedKey.id).to.equal(importResult); + }); + + it('should throw an error when attempting to import a key that already exists', async () => { + // Import the key and validate the result. + const importResult = await keyStore.importKey({ key: testKey }); + expect(importResult).to.equal(testKey.id); + + // Test importing the key and assert it throws an error. + const importKey = keyStore.importKey({ key: testKey }); + await expect(importKey).to.eventually.be.rejectedWith(Error, 'Key with ID already exists'); + }); + }); + + describe('listKeys()', () => { + it('should return an array of all keys in the store', async () => { + // Define multiple keys to be added. + const testKeys = [ + { ...testKey, ...{ id: 'key-1' }}, + { ...testKey, ...{ id: 'key-2' }}, + { ...testKey, ...{ id: 'key-3' }} + ]; + + // Import the keys into the store. + for (let key of testKeys) { + await keyStore.importKey({ key }); + } + + // List keys and verify the result. + const storedKeys = await keyStore.listKeys(); + expect(storedKeys).to.deep.equal(testKeys); + }); + + it('should return an empty array if the store contains no keys', async () => { + // List keys and verify the result is empty. + const storedKeys = await keyStore.listKeys(); + expect(storedKeys).to.be.empty; + }); + }); + + describe('updateKey()', () => { + it('should update the alias for a key when given', async () => { + // Import a key so we have something to update. + const importResult = await keyStore.importKey({ key: testKey }); + expect(importResult).to.equal(testKey.id); + + // Attempt to update the key. + const newAlias = 'did:method:new'; + const updateResult = await keyStore.updateKey({ id: testKey.id, alias: newAlias }); + + // Verify that the alias property was updated. + expect(updateResult).to.be.true; + const storedKey = await keyStore.getKey({ id: testKey.id }); + expect(storedKey).to.have.property('alias', newAlias); + }); + + it('should update the metadata for a key when given', async () => { + // Import a key so we have something to update. + const importResult = await keyStore.importKey({ key: testKey }); + expect(importResult).to.equal(testKey.id); + + // Attempt to update the key. + const newMetadata = { foo: 'bar' }; + const updateResult = await keyStore.updateKey({ id: testKey.id, metadata: newMetadata }); + + // Verify that the metadata property was updated. + expect(updateResult).to.be.true; + const storedKey = await keyStore.getKey({ id: testKey.id }); + if ('privateKey' in storedKey!) throw new Error('Expected ManagedKey and not ManagedKeyPair'); + expect(storedKey!.metadata).to.deep.equal(newMetadata); + }); + + it('should update the alias and metadata for a key when given', async () => { + // Import a key so we have something to update. + const importResult = await keyStore.importKey({ key: testKey }); + expect(importResult).to.equal(testKey.id); + + // Attempt to update the key. + const newAlias = 'did:method:new'; + const newMetadata = { foo: 'bar' }; + const updateResult = await keyStore.updateKey({ + id : testKey.id, + alias : newAlias, + metadata : newMetadata + }); + + // Verify that the alias and metadata properties were updated. + expect(updateResult).to.be.true; + const storedKey = await keyStore.getKey({ id: testKey.id }); + if ('privateKey' in storedKey!) throw new Error('Expected ManagedKey and not ManagedKeyPair'); + expect(storedKey).to.have.property('alias', newAlias); + expect(storedKey!.metadata).to.deep.equal(newMetadata); + }); + + it('should update the alias and metadata for a key pair when given', async () => { + // Import a key pair so we have something to update. + const importResult = await keyStore.importKey({ key: testKeyPair }); + expect(importResult).to.equal(testKeyPair.publicKey.id); + + // Attempt to update the key pair. + const newAlias = 'did:method:new'; + const newMetadata = { foo: 'bar' }; + const updateResult = await keyStore.updateKey({ + id : testKeyPair.publicKey.id, + alias : newAlias, + metadata : newMetadata + }); + + // Verify that the alias and metadata properties were updated. + expect(updateResult).to.be.true; + const storedKeyPair = await keyStore.getKey({ id: testKeyPair.publicKey.id }); + if (!('publicKey' in storedKeyPair!)) throw new Error('Expected ManagedKeyPair and not ManagedKey'); + expect(storedKeyPair!.publicKey).to.have.property('alias', newAlias); + expect(storedKeyPair!.publicKey.metadata).to.deep.equal(newMetadata); + }); + + it('should not ovewrite key properties if given values are undefined', async () => { + // Import a key so we have something to update. + const importResult = await keyStore.importKey({ key: testKey }); + expect(importResult).to.equal(testKey.id); + + // Attempt to update the key. + const newAlias = undefined; + const newMetadata = { /* empty */ }; + const updateResult = await keyStore.updateKey({ + id : testKey.id, + alias : newAlias, + metadata : newMetadata + }); + + // Verify that no properties were updated. + expect(updateResult).to.be.true; + const storedKey = await keyStore.getKey({ id: testKey.id }); + expect(storedKey).to.deep.equal(testKey); + }); + + it('should return false when attempting to update a non-existent key', async () => { + // Attempt to update a non-existent key. + const updateResult = await keyStore.updateKey({ + id : 'non-existent', + alias : 'did:method:a1', + metadata : { foo: 'bar' } + }); + + // Verify that the update operation was not successful. + expect(updateResult).to.be.false; + }); + }); +}); + +describe('PrivateKeyStoreDwn', () => { + let agentDid: PortableDid; + let agentSigningKey: Web5Crypto.CryptoKeyPair; + let keyMaterial: Uint8Array; + let kmsKeyStore: KeyStoreDwn; + let kmsPrivateKeyStore: PrivateKeyStoreDwn; + let testAgent: TestAgent; + let testKey: Omit; + + before(async () => { + testAgent = await TestAgent.create(); + }); + + beforeEach(async () => { + agentDid = await DidKeyMethod.create({ keyAlgorithm: 'Ed25519' }); + const privateCryptoKey = await Jose.jwkToCryptoKey({ key: agentDid.keySet.verificationMethodKeys![0].privateKeyJwk! }); + const publicCryptoKey = await Jose.jwkToCryptoKey({ key: agentDid.keySet.verificationMethodKeys![0].publicKeyJwk! }); + + agentSigningKey = { + privateKey : privateCryptoKey, + publicKey : publicCryptoKey + }; + + // Instantiate a local KMS with in-memory key stores. + const localKmsMemory = new LocalKms({ kmsName: 'memory' }); + + // Instantiate a local KMS with DWN-backed key stores. + kmsKeyStore = new KeyStoreDwn({ schema: 'https://identity.foundation/schemas/web5/kms-key' }); + kmsPrivateKeyStore = new PrivateKeyStoreDwn(); + const localKmsDwn = new LocalKms({ + kmsName : 'local', + keyStore : kmsKeyStore, + privateKeyStore : kmsPrivateKeyStore + }); + + // Insntiate KeyManager with in-memory and DWN-backed KMSs. + const keyManagerStore = new KeyStoreDwn({ schema: 'https://identity.foundation/schemas/web5/managed-key' }); + const keyManager = new KeyManager({ + kms: { + local : localKmsDwn, + memory : localKmsMemory + }, + store: keyManagerStore + }); + // Set the agent context for KeyManager and all KMSs. + keyManager.agent = testAgent; + + // Replace the TestAgent's KeyManager instance with the custom instance. + testAgent.keyManager = keyManager; + + // Convert the CryptoKeyPair object to a PortableKeyPair. + const defaultSigningKey = cryptoToPortableKeyPair({ + cryptoKeyPair : agentSigningKey, + keyData : { + alias : await testAgent.didManager.getDefaultSigningKey({ did: agentDid.did }), + kms : 'memory' + } + }); + + // Import the Agent's signing key pair to the in-memory KMS key stores. + await testAgent.keyManager.setDefaultSigningKey({ key: defaultSigningKey }); + + // Set the Agent's DID + testAgent.agentDid = agentDid.did; + + // Key to use for testing. + keyMaterial = new Uint8Array([1, 2, 3]); + testKey = { + material : new Uint8Array([1, 2, 3]), + type : 'private', + }; + }); + + afterEach(async () => { + await testAgent.clearStorage(); + }); + + after(async () => { + await testAgent.closeStorage(); + }); + + describe('deleteKey()', () => { + let importedKeyPairId: string; + + beforeEach(async () => { + // Generate a key pair to import. + const randomKeyPair = await new EdDsaAlgorithm().generateKey({ + algorithm : { name: 'EdDSA', namedCurve: 'Ed25519' }, + extractable : true, + keyUsages : ['sign', 'verify'] + }); + + // Import the private key to the in-memory private key store. + const importedPrivateKeyId = await kmsPrivateKeyStore.importKey({ + agent : testAgent, + key : { + material : randomKeyPair.privateKey.material, + type : 'private' + } + }); + + // And finally, attempt to import the key into the DWN-backed key store. + importedKeyPairId = await kmsKeyStore.importKey({ + agent : testAgent, + key : { + privateKey : { ...randomKeyPair.privateKey, id: importedPrivateKeyId, kms: 'local', state: 'Enabled' }, + publicKey : { ...randomKeyPair.publicKey, id: importedPrivateKeyId, kms: 'local', material: randomKeyPair.publicKey.material, state: 'Enabled' } + } + }); + }); + + it('deletes key and returns true if key exists', async () => { + // Import the key and get back the assigned ID. + const id = await kmsPrivateKeyStore.importKey({ key: testKey, agent: testAgent }); + + // Test deleting the key and validate the result. + const deleteResult = await kmsPrivateKeyStore.deleteKey({ id, agent: testAgent }); + expect(deleteResult).to.be.true; + + // Verify the key is no longer in the store. + const storedKey = await kmsPrivateKeyStore.getKey({ id, agent: testAgent }); + expect(storedKey).to.be.undefined; + }); + + it('returns false if key does not exist', async () => { + // Test deleting the key. + const deleteResult = await kmsPrivateKeyStore.deleteKey({ id: 'non-existent-key', agent: testAgent }); + + // Validate the key was deleted. + expect(deleteResult).to.be.false; + }); + + it('throws an error if Agent DID is undefined and no context was specified', async () => { + // Unset the Agent DID. + testAgent.agentDid = undefined; + await expect( + kmsPrivateKeyStore.deleteKey({ id: importedKeyPairId, agent: testAgent }) + ).to.eventually.be.rejectedWith(Error, `Agent property 'agentDid' is undefined and no context was specified`); + }); + }); + + describe('findKey()', async () => { + it('throws a not implemented error', async () => { + await expect( + kmsPrivateKeyStore.findKey() + ).to.eventually.be.rejectedWith(Error, 'Method not implemented'); + }); + }); + + describe('getKey()', () => { + let importedPrivateKeyId: string; + + beforeEach(async () => { + // Import the private key to the in-memory private key store. + importedPrivateKeyId = await kmsPrivateKeyStore.importKey({ + agent : testAgent, + key : testKey + }); + }); + + it('should return a key if it exists', async () => { + // Test getting the key. + const storedKey = await kmsPrivateKeyStore.getKey({ id: importedPrivateKeyId, agent: testAgent }); + + // Verify the key is in the store. + expect(storedKey).to.deep.equal({ id: importedPrivateKeyId, material: keyMaterial, type: 'private' }); + }); + + it('should return undefined if the specified key does not exist', async () => { + // Test getting the key. + const storedKey = await kmsPrivateKeyStore.getKey({ id: 'non-existent-key', agent: testAgent }); + + // Verify the key is no longer in the store. + expect(storedKey).to.be.undefined; + }); + + it('throws an error if Agent DID is undefined and no context was specified', async () => { + // Unset the Agent DID. + testAgent.agentDid = undefined; + await expect( + kmsPrivateKeyStore.getKey({ id: importedPrivateKeyId, agent: testAgent }) + ).to.eventually.be.rejectedWith(Error, `Agent property 'agentDid' is undefined and no context was specified`); + }); + }); + + describe('importKey()', () => { + it('should import a private key and return its ID', async () => { + // Import the private key to the in-memory private key store. + const importedPrivateKeyId = await kmsPrivateKeyStore.importKey({ + agent : testAgent, + key : testKey + }); + + // Validate the returned id. + expect(importedPrivateKeyId).to.be.a('string'); + + // Verify the key is present in the private key store. + const storedKey = await kmsPrivateKeyStore.getKey({ id: importedPrivateKeyId, agent: testAgent }); + expect(storedKey).to.deep.equal({ id: importedPrivateKeyId, material: keyMaterial, type: 'private' }); + }); + + it('should throw an error if required parameters are missing', async () => { + // Missing 'material'. + const keyMissingMaterial = { type: 'private' }; + await expect(kmsPrivateKeyStore.importKey({ + agent : testAgent, + // @ts-expect-error because the material property is intentionally omitted to trigger an error. + key : keyMissingMaterial + })).to.eventually.be.rejectedWith(TypeError, `Required parameter missing: 'material'`); + + // Missing 'type'. + const keyMissingType = { material: new Uint8Array(8) }; + await expect(kmsPrivateKeyStore.importKey({ + agent : testAgent, + // @ts-expect-error because the type property is intentionally omitted to trigger an error. + key : keyMissingType + })).to.eventually.be.rejectedWith(TypeError, `Required parameter missing: 'type'`); + }); + + it('throws an error if Agent DID is undefined and no context was specified', async () => { + // Unset the Agent DID. + testAgent.agentDid = undefined; + await expect( + kmsPrivateKeyStore.importKey({ key: testKey, agent: testAgent }) + ).to.eventually.be.rejectedWith(Error, `Agent property 'agentDid' is undefined and no context was specified`); + }); + }); + + describe('listKeys()', function() { + it('should return an array of all keys in the store', async function() { + // Define multiple keys to be added. + const testKeys = [ + { ...testKey, material: (new Uint8Array([1, 2, 3]))}, + { ...testKey, material: (new Uint8Array([1, 2, 3]))}, + { ...testKey, material: (new Uint8Array([1, 2, 3]))} + ]; + + // Import the keys into the store. + const importedKeys: Map = new Map(); + for (let key of testKeys) { + const id = await kmsPrivateKeyStore.importKey({ key, agent: testAgent }); + importedKeys.set(id, { ...key, id }); + } + + const storedKeys = await kmsPrivateKeyStore.listKeys({ agent: testAgent }); + expect(storedKeys).to.have.length(3); + for (const storedKey of storedKeys) { + expect(importedKeys.get(storedKey.id)).to.deep.equal(storedKey); + } + }); + + it('should return an empty array if the store contains no keys', async function() { + // List keys and verify the result is empty. + const storedKeys = await kmsPrivateKeyStore.listKeys({ agent: testAgent }); + expect(storedKeys).to.be.empty; + }); + + it('throws an error if Agent DID is undefined and no context was specified', async () => { + // Unset the Agent DID. + testAgent.agentDid = undefined; + await expect( + kmsPrivateKeyStore.listKeys({ agent: testAgent }) + ).to.eventually.be.rejectedWith(Error, `Agent property 'agentDid' is undefined and no context was specified`); + }); + }); + + describe('updateKey()', async () => { + it('throws a not implemented error', async () => { + await expect( + kmsPrivateKeyStore.updateKey() + ).to.eventually.be.rejectedWith(Error, 'Method not implemented'); + }); + }); + + describe('data integrity', () => { + it('imports and gets stored private key data without any alterations', async () => { + // Generate a key pair to import. + const randomKeyPair = await new EdDsaAlgorithm().generateKey({ + algorithm : { name: 'EdDSA', namedCurve: 'Ed25519' }, + extractable : true, + keyUsages : ['sign', 'verify'] + }); + + // Import the private key to the in-memory private key store. + const importedPrivateKeyId = await kmsPrivateKeyStore.importKey({ + agent : testAgent, + key : { + material : randomKeyPair.privateKey.material, + type : 'private' + } + }); + + // Retrieve the private key. + const storedPrivateKey = await kmsPrivateKeyStore.getKey({ id: importedPrivateKeyId, agent: testAgent }); + expect(storedPrivateKey?.material).to.deep.equal(randomKeyPair.privateKey.material); + }); + }); + + describe('with LocalKms', () => { + it('imports private key data', async () => { + // Generate a key pair to import. + const randomKeyPair = await new EdDsaAlgorithm().generateKey({ + algorithm : { name: 'EdDSA', namedCurve: 'Ed25519' }, + extractable : true, + keyUsages : ['sign', 'verify'] + }); + const portableKeyPair = cryptoToPortableKeyPair({ + cryptoKeyPair : randomKeyPair, + keyData : { + kms: 'local' + } + }); + const importedKeyPair = await testAgent.keyManager.importKey(portableKeyPair); + if (!isManagedKeyPair(importedKeyPair)) throw new Error('Type guard unexpectedly threw'); // Type guard. + expect(importedKeyPair.privateKey.id).to.be.a.string; + expect(importedKeyPair.publicKey.id).to.be.a.string; + expect(importedKeyPair.publicKey.material).to.deep.equal(randomKeyPair.publicKey.material); + }); + }); +}); + +describe('PrivateKeyStoreMemory', () => { + let kmsPrivateKeyStore: PrivateKeyStoreMemory; + let testKey: Omit; + let keyMaterial: Uint8Array; + + beforeEach(() => { + kmsPrivateKeyStore = new PrivateKeyStoreMemory(); + + keyMaterial = (new Uint8Array([1, 2, 3])); + testKey = { + material : (new Uint8Array([1, 2, 3])), + type : 'private', + }; + }); + + describe('deleteKey()', () => { + it('should delete key and return true if key exists', async () => { + // Import the key and get back the assigned ID. + const id = await kmsPrivateKeyStore.importKey({ key: testKey }); + + // Test deleting the key and validate the result. + const deleteResult = await kmsPrivateKeyStore.deleteKey({ id }); + expect(deleteResult).to.be.true; + + // Verify the key is no longer in the store. + const storedKey = await kmsPrivateKeyStore.getKey({ id }); + expect(storedKey).to.be.undefined; + }); + + it('should return false if key does not exist', async () => { + // Test deleting the key. + const deleteResult = await kmsPrivateKeyStore.deleteKey({ id: 'non-existent-key' }); + + // Validate the key was deleted. + expect(deleteResult).to.be.false; + }); + }); + + describe('findKey()', async () => { + it('throws a not implemented error', async () => { + await expect( + kmsPrivateKeyStore.findKey() + ).to.eventually.be.rejectedWith(Error, 'Method not implemented'); + }); + }); + + describe('getKey()', () => { + it('should return a key if it exists', async () => { + // Import the key. + const id = await kmsPrivateKeyStore.importKey({ key: testKey }); + + // Test getting the key. + const storedKey = await kmsPrivateKeyStore.getKey({ id }); + + // Verify the key is in the store. + expect(storedKey).to.deep.equal({ id, material: keyMaterial, type: 'private' }); + }); + + it('should return undefined if the specified key does not exist', async () => { + // Test getting the key. + const storedKey = await kmsPrivateKeyStore.getKey({ id: 'non-existent-key' }); + + // Verify the key is no longer in the store. + expect(storedKey).to.be.undefined; + }); + }); + + describe('importKey()', () => { + it('should import a private key and return its ID', async () => { + // Test importing the key. + const id = await kmsPrivateKeyStore.importKey({ key: testKey }); + + // Validate the returned id. + expect(id).to.be.a('string'); + + // Verify the key is present in the private key store. + const storedKey = await kmsPrivateKeyStore.getKey({ id }); + expect(storedKey).to.deep.equal({ id, material: keyMaterial, type: 'private' }); + }); + + it('should permanently transfer the private key material', async () => { + // Test importing the key. + await kmsPrivateKeyStore.importKey({ key: testKey }); + + // Verify that attempting to access the key material after import triggers an error. + // Chrome, Firefox, Node.js, and Firefox report different error messages but all contain 'detached'. + expect(() => new Uint8Array(testKey.material)).to.throw(TypeError, 'detached'); + }); + + it('should throw an error if required parameters are missing', async () => { + // Missing 'material'. + const keyMissingMaterial = { type: 'private' }; + await expect(kmsPrivateKeyStore.importKey({ + // @ts-expect-error because the material property is intentionally omitted to trigger an error. + key: keyMissingMaterial + })).to.eventually.be.rejectedWith(TypeError, `Required parameter missing: 'material'`); + + // Missing 'type'. + const keyMissingType = { material: new Uint8Array(8) }; + await expect(kmsPrivateKeyStore.importKey({ + // @ts-expect-error because the type property is intentionally omitted to trigger an error. + key: keyMissingType + })).to.eventually.be.rejectedWith(TypeError, `Required parameter missing: 'type'`); + }); + }); + + describe('listKeys()', function() { + it('should return an array of all keys in the store', async function() { + // Define multiple keys to be added. + const testKeys = [ + { ...testKey, material: (new Uint8Array([1, 2, 3]))}, + { ...testKey, material: (new Uint8Array([1, 2, 3]))}, + { ...testKey, material: (new Uint8Array([1, 2, 3]))} + ]; + + // Import the keys into the store. + const expectedTestKeys: ManagedPrivateKey[] = []; + for (let key of testKeys) { + const id = await kmsPrivateKeyStore.importKey({ key }); + expectedTestKeys.push({ id, material: keyMaterial, type: 'private', }); + } + + const storedKeys = await kmsPrivateKeyStore.listKeys(); + expect(storedKeys).to.deep.equal(expectedTestKeys); + }); + + it('should return an empty array if the store contains no keys', async function() { + // List keys and verify the result is empty. + const storedKeys = await kmsPrivateKeyStore.listKeys(); + expect(storedKeys).to.be.empty; + }); + }); + + describe('updateKey()', async () => { + it('throws a not implemented error', async () => { + await expect( + kmsPrivateKeyStore.updateKey() + ).to.eventually.be.rejectedWith(Error, 'Method not implemented'); + }); + }); +}); \ No newline at end of file diff --git a/packages/web5-agent/tests/tsconfig.json b/packages/agent/tests/tsconfig.json similarity index 100% rename from packages/web5-agent/tests/tsconfig.json rename to packages/agent/tests/tsconfig.json diff --git a/packages/agent/tests/utils/test-agent.ts b/packages/agent/tests/utils/test-agent.ts new file mode 100644 index 000000000..0870d0868 --- /dev/null +++ b/packages/agent/tests/utils/test-agent.ts @@ -0,0 +1,191 @@ +import { Dwn } from '@tbd54566975/dwn-sdk-js'; +import { MessageStoreLevel, DataStoreLevel, EventLogLevel } from '@tbd54566975/dwn-sdk-js/stores'; + +import type { AppDataStore } from '../../src/app-data-store.js'; +import type { + VcResponse, + DidResponse, + DwnResponse, + SendVcRequest, + SendDidRequest, + SendDwnRequest, + ProcessVcRequest, + Web5ManagedAgent, + ProcessDidRequest, + ProcessDwnRequest, + DwnRpc, +} from '../../src/types/agent.js'; + +import { DidKeyMethod, DidResolver } from '@web5/dids'; + +import { LocalKms } from '../../src/kms-local.js'; +import { DidManager } from '../../src/did-manager.js'; +import { DwnManager } from '../../src/dwn-manager.js'; +import { KeyManager } from '../../src/key-manager.js'; +import { Web5RpcClient } from '../../src/rpc-client.js'; +import { AppDataVault } from '../../src/app-data-store.js'; +import { IdentityManager } from '../../src/identity-manager.js'; + +type CreateMethodOptions = { + testDataLocation?: string; +} + +type TestAgentOptions = { + appData: AppDataStore; + didManager: DidManager; + didResolver: DidResolver; + dwnManager: DwnManager; + identityManager: IdentityManager; + keyManager: KeyManager; + rpcClient: DwnRpc; + + dwn: Dwn; + dwnDataStore: DataStoreLevel; + dwnEventLog: EventLogLevel; + dwnMessageStore: MessageStoreLevel; +} + +export class TestAgent implements Web5ManagedAgent { + agentDid: string | undefined; + appData: AppDataStore; + didManager: DidManager; + didResolver: DidResolver; + dwnManager: DwnManager; + identityManager: IdentityManager; + keyManager: KeyManager; + rpcClient: DwnRpc; + + /** + * DWN-related properties. + */ + dwn: Dwn; + dwnDataStore: DataStoreLevel; + dwnEventLog: EventLogLevel; + dwnMessageStore: MessageStoreLevel; + + constructor(options: TestAgentOptions) { + this.appData = options.appData; + this.didManager = options.didManager; + this.didResolver = options.didResolver; + this.dwnManager = options.dwnManager; + this.identityManager = options.identityManager; + this.keyManager = options.keyManager; + this.rpcClient = options.rpcClient; + + // Set this agent to be the default agent for each component. + this.didManager.agent = this; + this.dwnManager.agent = this; + this.identityManager.agent = this; + this.keyManager.agent = this; + + // TestAgent-specific properties. + this.dwn = options.dwn; + this.dwnDataStore = options.dwnDataStore; + this.dwnEventLog = options.dwnEventLog; + this.dwnMessageStore = options.dwnMessageStore; + } + + async clearStorage(): Promise { + this.agentDid = undefined; + await this.dwnDataStore.clear(); + await this.dwnEventLog.clear(); + await this.dwnMessageStore.clear(); + } + + async closeStorage(): Promise { + await this.dwnDataStore.close(); + await this.dwnEventLog.close(); + await this.dwnMessageStore.close(); + } + + static async create(options: CreateMethodOptions = {}): Promise { + let { testDataLocation } = options; + + testDataLocation ??= '__TESTDATA__'; + const testDataPath = (path: string) => `${testDataLocation}/${path}`; + + // Instantiate custom stores to use with DWN instance. + const dwnDataStore = new DataStoreLevel({ blockstoreLocation: testDataPath('DATASTORE') }); + const dwnEventLog = new EventLogLevel({ location: testDataPath('EVENTLOG') }); + const dwnMessageStore = new MessageStoreLevel({ + blockstoreLocation : testDataPath('MESSAGESTORE'), + indexLocation : testDataPath('INDEX') + }); + + // Instantiate components with default in-memory stores. + const appData = new AppDataVault({ keyDerivationWorkFactor: 1 }); + const didManager = new DidManager({ didMethods: [DidKeyMethod] }); + const identityManager = new IdentityManager(); + const kms = { + memory: new LocalKms({ kmsName: 'memory' }) + }; + const keyManager = new KeyManager({ kms }); + + // Instantiate DID resolver. + const didMethodApis = [DidKeyMethod]; + const didResolver = new DidResolver({ didResolvers: didMethodApis }); + + // Instantiate custom DWN instance. + const dwn = await Dwn.create({ + eventLog : dwnEventLog, + dataStore : dwnDataStore, + messageStore : dwnMessageStore + }); + + // Instantiate a DwnManager using the custom DWN instance. + const dwnManager = new DwnManager({ dwn }); + + // Instantiate an RPC Client. + const rpcClient = new Web5RpcClient(); + + return new TestAgent({ + appData, + didManager, + didResolver, + dwn, + dwnDataStore, + dwnEventLog, + dwnMessageStore, + dwnManager, + identityManager, + keyManager, + rpcClient + }); + } + + async firstLaunch(): Promise { + throw new Error('Not implemented'); + } + + async initialize(_options: { passphrase: string; }): Promise { + throw new Error('Not implemented'); + } + + async processDidRequest(_request: ProcessDidRequest): Promise { + throw new Error('Not implemented'); + } + + async processDwnRequest(request: ProcessDwnRequest): Promise { + return this.dwnManager.processRequest(request); + } + + async processVcRequest(_request: ProcessVcRequest): Promise { + throw new Error('Not implemented'); + } + + async sendDidRequest(_request: SendDidRequest): Promise { + throw new Error('Not implemented'); + } + + async sendDwnRequest(request: SendDwnRequest): Promise { + return this.dwnManager.sendRequest(request); + } + + async sendVcRequest(_request: SendVcRequest): Promise { + throw new Error('Not implemented'); + } + + async start(_options: { passphrase: string; }): Promise { + throw new Error('Not implemented'); + } +} \ No newline at end of file diff --git a/packages/web5/tsconfig.cjs.json b/packages/agent/tsconfig.cjs.json similarity index 81% rename from packages/web5/tsconfig.cjs.json rename to packages/agent/tsconfig.cjs.json index 0384273d6..7a6f9c0c0 100644 --- a/packages/web5/tsconfig.cjs.json +++ b/packages/agent/tsconfig.cjs.json @@ -10,7 +10,8 @@ "outDir": "dist/cjs", "declaration": false, "declarationMap": false, - "declarationDir": null + "declarationDir": null, + "downlevelIteration": true }, "include": [ "src" diff --git a/packages/web5-agent/tsconfig.json b/packages/agent/tsconfig.json similarity index 95% rename from packages/web5-agent/tsconfig.json rename to packages/agent/tsconfig.json index 1d4154f54..b29e4e7e1 100644 --- a/packages/web5-agent/tsconfig.json +++ b/packages/agent/tsconfig.json @@ -22,7 +22,6 @@ "src", ], "exclude": [ - "node_modules", - "dist" + "node_modules" ] } \ No newline at end of file diff --git a/packages/web5-proxy-agent/.c8rc.json b/packages/api/.c8rc.json similarity index 66% rename from packages/web5-proxy-agent/.c8rc.json rename to packages/api/.c8rc.json index 1d1670b70..ab680f663 100644 --- a/packages/web5-proxy-agent/.c8rc.json +++ b/packages/api/.c8rc.json @@ -5,12 +5,12 @@ ".js" ], "include": [ - "tests/compiled/**" + "tests/compiled/src/**" ], "exclude": [ - "tests/compiled/src/main.js", + "tests/compiled/src/index.js", "tests/compiled/src/types.js", - "tests/compiled/types/**" + "tests/compiled/src/types/**" ], "reporter": [ "cobertura", diff --git a/packages/web5-proxy-agent/.mocharc.json b/packages/api/.mocharc.json similarity index 100% rename from packages/web5-proxy-agent/.mocharc.json rename to packages/api/.mocharc.json diff --git a/packages/web5/.vscode/launch.json b/packages/api/.vscode/launch.json similarity index 100% rename from packages/web5/.vscode/launch.json rename to packages/api/.vscode/launch.json diff --git a/packages/web5/.vscode/tasks.json b/packages/api/.vscode/tasks.json similarity index 94% rename from packages/web5/.vscode/tasks.json rename to packages/api/.vscode/tasks.json index 73ebbe310..0c7b4f4b9 100644 --- a/packages/web5/.vscode/tasks.json +++ b/packages/api/.vscode/tasks.json @@ -34,7 +34,7 @@ "$tsc" ], "options": { - "cwd": "${workspaceFolder:web5}" + "cwd": "${workspaceFolder:api}" } }, ] diff --git a/packages/web5-agent/LICENSE b/packages/api/LICENSE similarity index 100% rename from packages/web5-agent/LICENSE rename to packages/api/LICENSE diff --git a/packages/api/README.md b/packages/api/README.md new file mode 100644 index 000000000..df8a44011 --- /dev/null +++ b/packages/api/README.md @@ -0,0 +1,440 @@ +# Web5 JS SDK + +[![NPM](https://img.shields.io/npm/v/@web5/web5.svg?style=flat-square&logo=npm&logoColor=FFFFFF&color=FFEC19&santize=true)](https://www.npmjs.com/package/@web5/web5) +[![Build Status](https://img.shields.io/github/actions/workflow/status/TBD54566975/web5-js/tests-ci.yml?branch=main&logo=github&label=ci&logoColor=FFFFFF&style=flat-square)](https://github.com/TBD54566975/web5-js/actions/workflows/tests-ci.yml) +[![Coverage](https://img.shields.io/codecov/c/gh/frankhinek/test-web5-js/main?logo=codecov&logoColor=FFFFFF&style=flat-square&token=YI87CKF1LI)](https://codecov.io/github/TBD54566975/web5-js) +[![License](https://img.shields.io/npm/l/@web5/web5.svg?style=flat-square&color=24f2ff&logo=apache&logoColor=FFFFFF&santize=true)](https://github.com/TBD54566975/web5-js/blob/main/LICENSE) +[![Chat](https://img.shields.io/badge/chat-on%20discord-7289da.svg?style=flat-square&color=9a1aff&logo=discord&logoColor=FFFFFF&sanitize=true)](https://discord.com/channels/937858703112155166/969272658501976117) + +Making developing with Web5 components at least 5 times easier to work with. + +> ⚠️ WEB5 JS SDK IS CURRENTLY IN TECH PREVIEW ⚠️ + +The SDK is currently still under active development, but having entered the Tech Preview phase there is now a drive to avoid unnecessary changes unless backwards compatibility is provided. Additional functionality will be added in the lead up to 1.0 final, and modifications will be made to address issues and community feedback. + +## Table of Contents + +- [Introduction](#introduction) +- [Installation](#installation) +- [Usage](#usage) +- [API Documentation](#api-documentation) + - [Web5.connect](#web5connectoptions) + - [web5.dwn.records.query](#web5dwnrecordsqueryrequest) + - [web5.dwn.records.create](#web5dwnrecordscreaterequest) + - [web5.dwn.records.write](#web5dwnrecordswriterequest) + - [web5.dwn.records.read](#web5dwnrecordsreadrequest) + - [web5.dwn.records.delete](#web5dwnrecordsdeleterequest) + - [web5.dwn.protocols.configure](#web5dwnprotocolsconfigurerequest) + - [web5.dwn.protocols.query](#web5dwnprotocolsqueryrequest) + - [web5.did.create](#web5didcreatemethod-options) +- [Project Resources](#project-resources) + +## Introduction + +Web5 consists of the following components: + +- Decentralized Identifiers +- Verifiable Credentials +- DWeb Node personal datastores + +The SDK sets out to gather the most oft used functionality from all three of these +pillar technologies to provide a simple library that is as close to effortless as +possible. + +## Installation + +_NPM_ + +```yaml +npm install @web5/api +``` + +_CDNs_ + +```yaml +https://unpkg.com/@web5/api@0.7.11/dist/browser.js +``` + +```yaml +https://cdn.jsdelivr.net/npm/@web5/api@0.7.11/dist/browser.mjs +``` + +## Usage + +### Importing the SDK + +```javascript +import { Web5 } from "@web5/api"; +``` + +or + +```javascript +import { Web5 } from CDN_LINK_HERE; +``` + +### Additional Steps + +This SDK relies indirectly on the [`@noble/ed25519`](https://github.com/paulmillr/noble-ed25519#usage) +and [`@noble/secp256k1`](https://github.com/paulmillr/noble-secp256k1#usage) packages. Therefore, +in certain environments, you'll need to perform additional steps to make it work. + +- Node.js <= 18 + +```js +// node.js 18 and earlier, needs globalThis.crypto polyfill +import { webcrypto } from "node:crypto"; +// @ts-ignore +if (!globalThis.crypto) globalThis.crypto = webcrypto; +``` + +- React Native: + +```js +// If you're on react native. React Native needs crypto.getRandomValues polyfill and sha512 +import "react-native-get-random-values"; +import { hmac } from "@noble/hashes/hmac"; +import { sha256 } from "@noble/hashes/sha256"; +import { sha512 } from "@noble/hashes/sha512"; +ed.etc.sha512Sync = (...m) => sha512(ed.etc.concatBytes(...m)); +ed.etc.sha512Async = (...m) => Promise.resolve(ed.etc.sha512Sync(...m)); + +secp.etc.hmacSha256Sync = (k, ...m) => + hmac(sha256, k, secp.etc.concatBytes(...m)); +secp.etc.hmacSha256Async = (k, ...m) => + Promise.resolve(secp.etc.hmacSha256Sync(k, ...m)); +``` + +## API Documentation + +### **`Web5.connect(options)`** + +Enables an app to request connection to a user's local identity app (like a desktop or mobile agent - work is underway for reference apps of each), or generate an in-app DID to represent the user (e.g. if the user does not have an identity app). + +> **NOTE:** The outputs of this method invocation will be used throughout the other API methods below. + +```javascript +const { web5, did: myDid } = await Web5.connect(); +``` + +#### **`options`** _(optional)_ + +An object which may specify any of the following properties: + +- **`techPreview`** - _`object`_ _(optional)_: an object that specifies configuration parameters that are relevant during the Tech Preview period of Web5 JS and may be deprecated in the future with advance notice. + + - **`dwnEndpoints`** - _`array`_ _(optional)_: a list of DWeb Node endpoints to define in the DID created and returned by `Web5.connect()`. If this property is omitted, during the Tech Preview two nodes will be included by default (e.g., `['https://dwn.tbddev.org/dwn0', 'https://dwn.tbddev.org/dwn3']`). + + For example: + + ```typescript + const { web5, did: myDid } = await Web5.connect({ + techPreview: { + dwnEndpoints: ["https://dwn.your-domain.org/"], + }, + }); + ``` + + + +#### **Response** + +An invocation of `Web5.connect()` produces the following items in response: + +- **`web5`** - _`Web5 instance`_: A class instance that enables access to a locally running DWeb Node, DID interaction methods, and other capabilities related to the connected DID. +- **`did`** - _`string`_: The DID that was created or attained connection to. + +### **`Record` instances from responses** + +Every modifying method (`create`, `write`, etc.) and the `entries` from queries return an instance of a `Record` class, which is a representation of the Record(s) being referenced. + +Each `Record` instance has the following instance properties: `id`, `attestation`, `contextId`, `dataFormat`, `dateCreated`, `encryption`, `interface`, `method`, `parentId`, `protocol`, `protocolPath`, `recipient`, `schema`, `dataCid`, `dataSize`, `dateModified`, `datePublished`, and `published`. + +> **Note** The **`id`** property is a unique identifier based on the record entry's composition. All entries across all records are deterministically unique. + +Each `Record` instance has the following instance methods: + +- **`data`** - _`object`_: an object with the following convenience methods that read out the data of the record entry in the following formats: + - **`text`** - _`function`_: produces a textual representation of the data. + - **`json`** - _`function`_: if the value is JSON data, this method will return a parsed JSON object. + - **`stream`** - _`function`_: returns the raw stream of bytes for the data. +- **`send`** - _`function`_: sends the record the instance represents to the DWeb Node endpoints of a provided DID. +- **`update`** - _`function`_: takes in a new request object matching the expected method signature of a `write` and overwrites the record. This is a convenience method that allows you to easily overwrite records with less verbosity. +- **`delete`** - _`function`_: generates a `delete` entry tombstone for the record. This is a convenience method that allows you to easily delete records with less verbosity. + +### **`web5.dwn.records.query(request)`** + +Method for querying either the locally connected DWeb Node or any remote DWeb Node specified in the `from` property. + +```javascript +// This invocation will query the user's own DWeb Nodes +const { records } = await web5.dwn.records.query({ + message: { + filter: { + schema: "https://schema.org/Playlist", + dataFormat: "application/json", + }, + }, +}); + +console.log(records); // an array of record entries from Bob's DWeb Nodes + +// This invocation will query Bob's DWeb Nodes +const { records } = await web5.dwn.records.query({ + from: "did:example:bob", + message: { + filter: { + protocol: "https://music.org/protocol", + schema: "https://schema.org/Playlist", + dataFormat: "application/json", + }, + }, +}); + +console.log(records); // an array of record entries from Bob's DWeb Nodes +``` + +#### **Request** + +The query `request` contains the following properties: + +- **`from`** - _`DID string`_ (_optional_): the decentralized identifier of the DWeb Node the query will fetch results from. +- **`message`** - _`object`_: the properties of the DWeb Node Message Descriptor that will be used to construct a valid record query: + - **`filter`** - _`object`_: properties against which results of the query will be filtered: + - **`protocol`** - _`URI string`_ (_optional_): the URI of the protocol bucket in which to query. + - **`schema`** - _`URI string`_ (_optional_): the URI of the schema bucket in which to query. + - **`dataFormat`** - _`Media Type string`_ (_optional_): the IANA string corresponding with the format of the data to filter for. See IANA's Media Type list here: https://www.iana.org/assignments/media-types/media-types.xhtml + +### **`web5.dwn.records.create(request)`** + +Method for creating a new record and storing it in the user's local DWeb Node, remote DWeb Nodes, or another party's DWeb Nodes (if permitted). + +```javascript +// this creates a record and stores it in the user's local DWeb Node +const { record } = await web5.dwn.records.create({ + data: "Hello World!", + message: { + dataFormat: "text/plain", + }, +}); + +console.log(await record.data.text()); // logs "Hello World!" +const { status } = await record.send(myDid); // send the record to the user's remote DWeb Nodes +const { status } = await record.send("did:example:bob"); // send the newly generated record to Bob's DWeb Nodes + +// this creates a record, but does not store it in the user's local DWeb Node +const { record } = await web5.dwn.records.create({ + store: false, + data: "Hello again, World!", + message: { + dataFormat: "text/plain", + }, +}); + +const { status } = await record.send("did:example:bob"); // send the newly generated record to Bob's DWeb Nodes +``` + +#### **Request** + +The `create` request object is composed as follows: + +- **`store`** - _`boolean`_ (_optional_): tells the create function whether or not to store the record in the user's local DWeb Node. (you might pass `false` if you didn't want to retain a copy of the record for yourself) +- **`data`** - _`text|object|file|blob`_: the data payload of the record. +- **`message`** - _`object`_: The properties of the DWeb Node Message Descriptor that will be used to construct a valid record query: + - **`protocol`** - _`URI string`_ (_optional_): the URI of the protocol under which the record will be bucketed. + - **`schema`** - _`URI string`_ (_optional_): the URI of the schema under which the record will be bucketed. + - **`dataFormat`** - _`Media Type string`_ (_optional_): the IANA string corresponding with the format of the data the record will be bucketed. See IANA's Media Type list here: https://www.iana.org/assignments/media-types/media-types.xhtml + +### **`web5.dwn.records.write(request)`** + +The `create()` method is an alias for `write()` and both can take the same request object properties. + +### **`web5.dwn.records.read(request)`** + +Method for reading a record stored in the user's local DWeb Node, remote DWeb Nodes, or another party's DWeb Nodes (if permitted). + +```javascript +// Reads the indicated record from the user's DWeb Nodes +const { record } = await web5.dwn.records.read({ + message: { + recordId: "bfw35evr6e54c4cqa4c589h4cq3v7w4nc534c9w7h5", + }, +}); + +console.log(await record.data.text()); // assuming the record is a text payload, logs the text + +// Reads the indicated record from Bob's DWeb Nodes +const { record } = await web5.dwn.records.read({ + from: "did:example:bob", + message: { + recordId: "bfw35evr6e54c4cqa4c589h4cq3v7w4nc534c9w7h5", + }, +}); + +console.log(await record.data.text()); // assuming the record is a text payload, logs the text +``` + +#### **Request** + +The `read` request object is composed as follows: + +- **`from`** - _`DID string`_ (_optional_): The DID of the DWeb Node the read request will fetch the indicated record from. +- **`message`** - _`object`_: The properties of the DWeb Node Message Descriptor that will be used to construct a valid DWeb Node message. + - **`recordId`** - _`string`_: the required record ID string that identifies the record data you are fetching. + +### **`web5.dwn.records.delete(request)`** + +Method for deleting a record stored in the user's local DWeb Node, remote DWeb Nodes, or another party's DWeb Nodes (if permitted). + +```javascript +// Deletes the indicated record from the user's DWeb Node +const { record } = await web5.dwn.records.delete({ + message: { + recordId: "bfw35evr6e54c4cqa4c589h4cq3v7w4nc534c9w7h5", + }, +}); + +// Deletes the indicated record from Bob's DWeb Node +const { record } = await web5.dwn.records.delete({ + from: "did:example:bob", + message: { + recordId: "bfw35evr6e54c4cqa4c589h4cq3v7w4nc534c9w7h5", + }, +}); +``` + +#### **Request** + +The `delete` request object is composed as follows: + +- **`from`** - _`DID string`_ (_optional_): The DID of the DWeb Node the delete tombstone will be sent to. +- **`message`** - _`object`_: The properties of the DWeb Node Message Descriptor that will be used to construct a valid DWeb Node message. + - **`recordId`** - _`string`_: the required record ID string that identifies the record being deleted. + +### **`web5.dwn.protocols.configure(request)`** + +Method for configuring a protocol definition in the DWeb Node of the user's local DWeb Node, remote DWeb Nodes, or another party's DWeb Nodes (if permitted). + +```javascript +const { protocol } = await web5.dwn.protocols.configure({ + message: { + definition: { + protocol: "https://photos.org/protocol", + types: { + album: { + schema: "https://photos.org/protocol/album", + dataFormats: ["application/json"], + }, + photo: { + schema: "https://photos.org/protocols/photo", + dataFormats: ["application/json"], + }, + binaryImage: { + dataFormats: ["image/png", "jpeg", "gif"], + }, + }, + structure: { + album: { + $actions: [ + { + who: "recipient", + can: "read", + }, + ], + }, + photo: { + $actions: [ + { + who: "recipient", + can: "read", + }, + ], + binaryImage: { + $actions: [ + { + who: "author", + of: "photo", + can: "write", + }, + ], + }, + }, + }, + }, + }, +}); + +protocol.send(myDid); // sends the protocol configuration to the user's other DWeb Nodes. +``` + +#### **Request** + +The `configure` request object is composed as follows: + +- **`message`** - _`object`_: The properties of the DWeb Node Message Descriptor that will be used to construct a valid DWeb Node message. + - **`definition`** - _`object`_: an object that defines the enforced composition of the protocol. + - **`protocol`** - _`URI string`_: a URI that represents the protocol being configured. + - **`types`** - _`object`_: an object that defines the records that can be used in the `structure` graph of the `definition` object. The following properties are optional constraints you can set for the type being defined: + - **`schema`** - _`URI string`_ (_optional_): the URI of the schema under which the record will be bucketed. + - **`dataFormats`** - _`Media Type string[]`_ (_optional_): Array of the IANA strings corresponding with the formats of the data the record will be bucketed. See IANA's Media Type list here: https://www.iana.org/assignments/media-types/media-types.xhtml + - **`structure`** - _`object`_: an object that defines the structure of a protocol, including data relationships and constraints on which entities can perform various activities. Fields under the `structure` object of the Protocol definition are expected to be either type references matching those defined in the `types` object. The type structures are recursive, so types form a graph and each type can have within it further attached types or the following rule statements that are all denoted with the prefix `$`: + - **`$actions`** - _`array`_: one or more rule objects that expose various allowed actions to actors (`author`, `recipient`), composed as follows: + - **`who`** - _`string`_: the actor (`author`, `recipient`) that is being permitted to invoke a given action. + - **`of`** - _`string`_: the protocol path that refers to the record subject. Using the above example protocol, the protocol path to `binaryImage` would be `photo/binaryImage`. + - **`can`** - _`string`_: the action being permitted by the rule. + +### **`web5.dwn.protocols.query(request)`** + +Method for querying a DID's DWeb Nodes for the presence of a protocol. This method is useful in detecting what protocols a given DID has installed to enable interaction over the protocol. + +```javascript +const { protocols } = await web5.dwn.protocols.query({ + message: { + filter: { + protocol: "https://music.org/protocol", + }, + }, +}); + +console.log(protocols); // logs an array of protocol configurations installed on the user's own DWeb Node + +const { protocols } = await web5.dwn.protocols.query({ + from: "did:example:bob", + message: { + filter: { + protocol: "https://music.org/protocol", + }, + }, +}); + +console.log(protocols); // logs an array of protocol configurations installed on Bob's DWeb Node +``` + +#### **Request** + +The query `request` must contain the following: + +- **`from`** - _`DID string`_ (_optional_): the decentralized identifier of the DWeb Node the query will fetch results from. +- **`message`** - _`object`_: The properties of the DWeb Node Message Descriptor that will be used to construct a valid record query: + - **`filter`** - _`object`_ (_optional_): properties against which results of the query will be filtered: + - **`protocol`** - _`URI string`_ (_optional_): the URI of the protocol bucket in which to query. + +### **`web5.did.create(method, options)`** + +The `create` method under the `did` object enables generation of DIDs for a supported set of DID Methods ('ion'|'key'). The output is method-specific, and handles things like key generation and assembly of DID Documents that can be published to DID networks. + +> NOTE: You do not usually need to manually invoke this, as the `Web5.connect()` method already acquires a DID for the user (either by direct creation or connection to an identity agent app). + +```javascript +const myDid = await Web5.did.create("ion"); +``` + +## Project Resources + +| Resource | Description | +| ----------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------- | +| [CODEOWNERS](https://github.com/TBD54566975/web5-js/blob/main/CODEOWNERS) | Outlines the project lead(s) | +| [CODE_OF_CONDUCT.md](https://github.com/TBD54566975/web5-js/blob/main/CODE_OF_CONDUCT.md) | Expected behavior for project contributors, promoting a welcoming environment | +| [CONTRIBUTING.md](https://github.com/TBD54566975/web5-js/blob/main/CONTRIBUTING.md) | Developer guide to build, test, run, access CI, chat, discuss, file issues | +| [GOVERNANCE.md](https://github.com/TBD54566975/web5-js/blob/main/GOVERNANCE.md) | Project governance | +| [LICENSE](./LICENSE) | Apache License, Version 2.0 | diff --git a/packages/web5/build/bundles.js b/packages/api/build/bundles.js similarity index 100% rename from packages/web5/build/bundles.js rename to packages/api/build/bundles.js diff --git a/packages/web5/build/esbuild-browser-config.cjs b/packages/api/build/esbuild-browser-config.cjs similarity index 96% rename from packages/web5/build/esbuild-browser-config.cjs rename to packages/api/build/esbuild-browser-config.cjs index a3ddbd9f8..bd8bd99b1 100644 --- a/packages/web5/build/esbuild-browser-config.cjs +++ b/packages/api/build/esbuild-browser-config.cjs @@ -15,7 +15,7 @@ for (let lib in stdLibBrowser) { /** @type {import('esbuild').BuildOptions} */ module.exports = { - entryPoints : ['./src/main.ts'], + entryPoints : ['./src/index.ts'], bundle : true, format : 'esm', sourcemap : true, @@ -27,4 +27,4 @@ module.exports = { define : { 'global': 'globalThis', }, -}; +}; \ No newline at end of file diff --git a/packages/web5/karma.conf.cjs b/packages/api/karma.conf.cjs similarity index 100% rename from packages/web5/karma.conf.cjs rename to packages/api/karma.conf.cjs diff --git a/packages/web5/package.json b/packages/api/package.json similarity index 81% rename from packages/web5/package.json rename to packages/api/package.json index 3a6d3b2e8..78d251ce0 100644 --- a/packages/web5/package.json +++ b/packages/api/package.json @@ -1,11 +1,11 @@ { - "name": "@tbd54566975/web5", + "name": "@web5/api", "version": "0.8.0", "description": "SDK for accessing the features and capabilities of Web5", "type": "module", - "main": "./dist/cjs/main.js", - "module": "./dist/esm/main.js", - "types": "./dist/types/main.d.ts", + "main": "./dist/cjs/index.js", + "module": "./dist/esm/index.js", + "types": "./dist/types/index.d.ts", "scripts": { "clean": "rimraf dist tests/compiled", "build:esm": "rimraf dist/esm dist/types && npx tsc -p tsconfig.json", @@ -18,12 +18,12 @@ "test:node": "npm run build:tests:node && c8 mocha", "test:browser": "karma start karma.conf.cjs" }, - "homepage": "https://github.com/TBD54566975/web5-js/tree/main/packages/web5#readme", + "homepage": "https://github.com/TBD54566975/web5-js/tree/main/packages/api#readme", "bugs": "https://github.com/TBD54566975/web5-js/issues", "repository": { "type": "git", "url": "git+https://github.com/TBD54566975/web5-js", - "directory": "packages/web5" + "directory": "packages/api" }, "license": "Apache-2.0", "contributors": [ @@ -46,12 +46,12 @@ ], "exports": { ".": { - "import": "./dist/esm/main.js", - "require": "./dist/cjs/main.js", - "types": "./dist/types/main.d.ts" + "import": "./dist/esm/index.js", + "require": "./dist/cjs/index.js", + "types": "./dist/types/index.d.ts" } }, - "react-native": "./dist/esm/main.js", + "react-native": "./dist/esm/index.js", "keywords": [ "decentralized", "decentralized-applications", @@ -70,24 +70,24 @@ "node": ">=18.0.0" }, "dependencies": { - "@decentralized-identity/ion-tools": "1.1.4", - "@tbd54566975/crypto": "0.8.0", - "@tbd54566975/dids": "0.8.0", "@tbd54566975/dwn-sdk-js": "0.2.1", - "@tbd54566975/web5-agent": "0.8.0", - "@tbd54566975/web5-proxy-agent": "0.8.0", - "@tbd54566975/web5-user-agent": "0.8.0", + "@web5/agent": "0.1.7", + "@web5/crypto": "0.1.6", + "@web5/dids": "0.1.9", + "@web5/user-agent": "0.1.10", "level": "8.0.0", "ms": "2.1.3", + "readable-stream": "4.4.2", "readable-web-to-node-stream": "3.0.2" }, "devDependencies": { + "@playwright/test": "1.36.2", "@types/chai": "4.3.0", "@types/chai-as-promised": "7.1.5", "@types/eslint": "8.37.0", "@types/mocha": "10.0.1", "@types/ms": "0.7.31", - "@types/readable-stream": "2.3.15", + "@types/readable-stream": "4.0.0", "@types/sinon": "10.0.15", "@typescript-eslint/eslint-plugin": "5.59.0", "@typescript-eslint/parser": "5.59.0", @@ -113,4 +113,4 @@ "source-map-loader": "4.0.1", "typescript": "5.1.6" } -} \ No newline at end of file +} diff --git a/packages/api/src/did-api.ts b/packages/api/src/did-api.ts new file mode 100644 index 000000000..d28474556 --- /dev/null +++ b/packages/api/src/did-api.ts @@ -0,0 +1,101 @@ +import type { Web5Agent } from '@web5/agent'; + +// import type { +// DidKeyOptions, +// DidIonCreateOptions, +// DidMethodApi, +// DidMethodCreator, +// DidMethodResolver, +// DidResolverCache, +// DidResolutionResult, +// DidState +// } from '@web5/dids'; + + + +// import { DidResolver } from '@web5/dids'; + +// // Map method names to option types +// type CreateMethodOptions = { +// ion: DidIonCreateOptions; +// key: DidKeyOptions; +// }; + +// // A conditional type for inferring options based on the method name +// type CreateOptions = CreateMethodOptions[M]; + +// export type DidApiOptions = { +// didMethodApis: DidMethodApi[]; +// cache?: DidResolverCache; +// } +export class DidApi { + // private didResolver: DidResolver; + // private methodCreatorMap: Map = new Map(); + + // /** + // * returns the DID resolver created by this api. useful in scenarios where you want to pass around + // * the same resolver so that you can leverage the resolver's cache + // */ + // get resolver() { + // return this.didResolver; + // } + + private agent: Web5Agent; + private connectedDid: string; + + constructor(options: { agent: Web5Agent, connectedDid: string }) { + this.agent = options.agent; + this.connectedDid = options.connectedDid; + } + + // constructor(options: DidApiOptions) { + // const { didMethodApis, cache } = options; + + // this.didResolver = new DidResolver({ methodResolvers: options.didMethodApis, cache }); + + // for (let methodApi of didMethodApis) { + // this.methodCreatorMap.set(methodApi.methodName, methodApi); + // } + // } + + // /** + // * Creates a DID of the method provided + // * @param method - the method of DID to create + // * @param options - method-specific options + // * @returns the created DID + // */ + // create(method: M, options?: CreateOptions): Promise { + // const didMethodCreator = this.methodCreatorMap.get(method); + // if (!didMethodCreator) { + // throw new Error(`no creator available for ${method}`); + // } + + // return didMethodCreator.create(options); + // } + + // /** + // * Resolves the provided DID + // * @param did - the did to resolve + // * @see {@link https://www.w3.org/TR/did-core/#did-resolution | DID Resolution} + // * @returns DID Resolution Result + // */ + // resolve(did: string): Promise { + // return this.didResolver.resolve(did); + // } + + // /** + // * can be used to add different did method resolvers + // * @param _resolver + // */ + // addMethodResolver(_resolver: DidMethodResolver) { + // throw new Error('not yet implemented'); + // } + + // /** + // * can be used to add differed did method creators + // * @param _creator + // */ + // addMethodCreator(_creator: DidMethodCreator) { + // throw new Error('not yet implemented'); + // } +} \ No newline at end of file diff --git a/packages/web5/src/did-resolution-cache.ts b/packages/api/src/did-resolution-cache.ts similarity index 96% rename from packages/web5/src/did-resolution-cache.ts rename to packages/api/src/did-resolution-cache.ts index 641cde6bf..de4a7d792 100644 --- a/packages/web5/src/did-resolution-cache.ts +++ b/packages/api/src/did-resolution-cache.ts @@ -1,4 +1,4 @@ -import type { DidResolutionResult, DidResolverCache } from '@tbd54566975/dids'; +import type { DidResolutionResult, DidResolverCache } from '@web5/dids'; import ms from 'ms'; import { Level } from 'level'; diff --git a/packages/api/src/dwn-api.ts b/packages/api/src/dwn-api.ts new file mode 100644 index 000000000..f465bb6e1 --- /dev/null +++ b/packages/api/src/dwn-api.ts @@ -0,0 +1,370 @@ +import type { Web5Agent } from '@web5/agent'; +import type { + UnionMessageReply, + RecordsReadOptions, + RecordsQueryOptions, + RecordsWriteMessage, + RecordsWriteOptions, + RecordsDeleteOptions, + ProtocolsQueryOptions, + RecordsQueryReplyEntry, + ProtocolsConfigureMessage, + ProtocolsConfigureOptions, + ProtocolsConfigureDescriptor, +} from '@tbd54566975/dwn-sdk-js'; + +import { isEmptyObject } from '@web5/common'; +import { DwnInterfaceName, DwnMethodName } from '@tbd54566975/dwn-sdk-js'; + +import { Record } from './record.js'; +import { Protocol } from './protocol.js'; +import { dataToBlob } from './utils.js'; + +export type ProtocolsConfigureRequest = { + message: Omit; +} + +export type ProtocolsConfigureResponse = { + status: UnionMessageReply['status']; + protocol?: Protocol; +} + +export type ProtocolsQueryReplyEntry = { + descriptor: ProtocolsConfigureDescriptor; +}; + +export type ProtocolsQueryRequest = { + from?: string; + message: Omit +} + +export type ProtocolsQueryResponse = { + protocols: Protocol[]; + status: UnionMessageReply['status']; +} + +export type RecordsCreateRequest = RecordsWriteRequest; + +export type RecordsCreateResponse = RecordsWriteResponse; + +export type RecordsCreateFromRequest = { + author: string; + data: unknown; + message?: Omit; + record: Record; +} + +export type RecordsDeleteRequest = { + from?: string; + message: Omit; +} + +export type RecordsDeleteResponse = { + status: UnionMessageReply['status']; +}; + +export type RecordsQueryRequest = { + /** The from property indicates the DID to query from and return results. */ + from?: string; + message: Omit; +} + +export type RecordsQueryResponse = { + status: UnionMessageReply['status']; + records?: Record[] +}; + +export type RecordsReadRequest = { + /** The from property indicates the DID to read from and return results fro. */ + from?: string; + message: Omit; +} + +export type RecordsReadResponse = { + status: UnionMessageReply['status']; + record: Record; +}; + +export type RecordsWriteRequest = { + data: unknown; + message?: Omit, 'authorizationSignatureInput'>; + store?: boolean; +} + +export type RecordsWriteResponse = { + status: UnionMessageReply['status']; + record?: Record +}; + +/** + * TODO: Document class. + */ +export class DwnApi { + private agent: Web5Agent; + private connectedDid: string; + + constructor(options: { agent: Web5Agent, connectedDid: string }) { + this.agent = options.agent; + this.connectedDid = options.connectedDid; + } + + /** + * TODO: Document namespace. + */ + get protocols() { + return { + /** + * TODO: Document method. + */ + configure: async (request: ProtocolsConfigureRequest): Promise => { + const agentResponse = await this.agent.processDwnRequest({ + target : this.connectedDid, + author : this.connectedDid, + messageOptions : request.message, + messageType : DwnInterfaceName.Protocols + DwnMethodName.Configure + }); + + const { message, messageCid, reply: { status }} = agentResponse; + const response: ProtocolsConfigureResponse = { status }; + + if (status.code < 300) { + const metadata = { author: this.connectedDid, messageCid }; + response.protocol = new Protocol(this.agent, message as ProtocolsConfigureMessage, metadata); + } + + return response; + }, + + /** + * TODO: Document method. + */ + query: async (request: ProtocolsQueryRequest): Promise => { + const agentResponse = await this.agent.processDwnRequest({ + author : this.connectedDid, + messageOptions : request.message, + messageType : DwnInterfaceName.Protocols + DwnMethodName.Query, + target : this.connectedDid + }); + + const { reply: { entries, status } } = agentResponse; + // const protocols = entries as ProtocolsQueryReplyEntry[]; + + const protocols = entries.map((entry: ProtocolsQueryReplyEntry) => { + const metadata = { author: this.connectedDid, }; + + return new Protocol(this.agent, entry, metadata); + }); + + return { protocols, status }; + } + }; + } + + /** + * TODO: Document namespace. + */ + get records() { + return { + /** + * TODO: Document method. + */ + create: async (request: RecordsCreateRequest): Promise => { + return this.records.write(request); + }, + + /** + * TODO: Document method. + */ + createFrom: async (request: RecordsCreateFromRequest): Promise => { + const { author: inheritedAuthor, ...inheritedProperties } = request.record.toJSON(); + + // Remove target from inherited properties since target is being explicitly defined in method parameters. + delete inheritedProperties.target; + + + // If `data` is being updated then `dataCid` and `dataSize` must not be present. + if (request.data !== undefined) { + delete inheritedProperties.dataCid; + delete inheritedProperties.dataSize; + } + + // If `published` is set to false, ensure that `datePublished` is undefined. Otherwise, DWN SDK's schema validation + // will throw an error if `published` is false but `datePublished` is set. + if (request.message?.published === false && inheritedProperties.datePublished !== undefined) { + delete inheritedProperties.datePublished; + delete inheritedProperties.published; + } + + // If the request changes the `author` or message `descriptor` then the deterministic `recordId` will change. + // As a result, we will discard the `recordId` if either of these changes occur. + if (!isEmptyObject(request.message) || (request.author && request.author !== inheritedAuthor)) { + delete inheritedProperties.recordId; + } + + return this.records.write({ + data : request.data, + message : { + ...inheritedProperties, + ...request.message, + }, + }); + }, + + /** + * TODO: Document method. + */ + delete: async (request: RecordsDeleteRequest): Promise => { + const agentRequest = { + author : this.connectedDid, + messageOptions : request.message, + messageType : DwnInterfaceName.Records + DwnMethodName.Delete, + target : request.from || this.connectedDid + }; + + let agentResponse; + + if (request.from) { + agentResponse = await this.agent.sendDwnRequest(agentRequest); + } else { + agentResponse = await this.agent.processDwnRequest(agentRequest); + } + + //! TODO: (Frank -> Moe): This quirk is the result of how 4XX errors are being returned by `dwn-server` + //! When DWN SDK returns 404, agentResponse is { status: { code: 404 }} and that's it. + //! Need to decide how to resolve. + let status; + if (agentResponse.reply) { + ({ reply: { status } } = agentResponse); + } else { + ({ status } = agentResponse); + } + + return { status }; + }, + + /** + * TODO: Document method. + */ + query: async (request: RecordsQueryRequest): Promise => { + const agentRequest = { + author : this.connectedDid, + messageOptions : request.message, + messageType : DwnInterfaceName.Records + DwnMethodName.Query, + target : request.from || this.connectedDid + }; + + let agentResponse; + + if (request.from) { + agentResponse = await this.agent.sendDwnRequest(agentRequest); + } else { + agentResponse = await this.agent.processDwnRequest(agentRequest); + } + + const { reply: { entries, status } } = agentResponse; + + const records = entries.map((entry: RecordsQueryReplyEntry) => { + const recordOptions = { + author : this.connectedDid, + target : this.connectedDid, + ...entry as RecordsWriteMessage + }; + const record = new Record(this.agent, recordOptions); + return record; + }); + + return { records, status }; + }, + + /** + * TODO: Document method. + */ + read: async (request: RecordsReadRequest): Promise => { + const agentRequest = { + author : this.connectedDid, + messageOptions : request.message, + messageType : DwnInterfaceName.Records + DwnMethodName.Read, + target : request.from || this.connectedDid + }; + + let agentResponse; + + if (request.from) { + agentResponse = await this.agent.sendDwnRequest(agentRequest); + } else { + agentResponse = await this.agent.processDwnRequest(agentRequest); + } + + //! TODO: (Frank -> Moe): This quirk is the result of how 4XX errors are being returned by `dwn-server` + //! When DWN SDK returns 404, agentResponse is { status: { code: 404 }} and that's it. + //! Need to decide how to resolve. + let responseRecord; + let status; + if (agentResponse.reply) { + ({ reply: { record: responseRecord, status } } = agentResponse); + } else { + ({ status } = agentResponse); + } + + let record: Record; + if (200 <= status.code && status.code <= 299) { + const recordOptions = { + author : this.connectedDid, + target : this.connectedDid, + ...responseRecord, + }; + + record = new Record(this.agent, recordOptions); + } + + return { record, status }; + }, + + /** + * TODO: Document method. + * + * As a convenience, the Record instance returned will cache a copy of the data if the + * data size, in bytes, is less than the DWN 'max data size allowed to be encoded' + * parameter of 10KB. This is done to maintain consistency with other DWN methods, + * like RecordsQuery, that include relatively small data payloads when returning + * RecordsWrite message properties. Regardless of data size, methods such as + * `record.data.stream()` will return the data when called even if it requires fetching + * from the DWN datastore. + */ + write: async (request: RecordsWriteRequest): Promise => { + const messageOptions: Partial = { + ...request.message + }; + + const { dataBlob, dataFormat } = dataToBlob(request.data, messageOptions.dataFormat); + messageOptions.dataFormat = dataFormat; + + const agentResponse = await this.agent.processDwnRequest({ + author : this.connectedDid, + dataStream : dataBlob, + messageOptions, + messageType : DwnInterfaceName.Records + DwnMethodName.Write, + store : request.store, + target : this.connectedDid + }); + + const { message, reply: { status } } = agentResponse; + const responseMessage = message as RecordsWriteMessage; + + let record: Record; + if (200 <= status.code && status.code <= 299) { + const recordOptions = { + author : this.connectedDid, + encodedData : dataBlob, + target : this.connectedDid, + ...responseMessage, + }; + + record = new Record(this.agent, recordOptions); + } + + return { record, status }; + }, + }; + } +} \ No newline at end of file diff --git a/packages/api/src/index.ts b/packages/api/src/index.ts new file mode 100644 index 000000000..8bf80c7d6 --- /dev/null +++ b/packages/api/src/index.ts @@ -0,0 +1 @@ +export * from './web5.js'; \ No newline at end of file diff --git a/packages/api/src/protocol.ts b/packages/api/src/protocol.ts new file mode 100644 index 000000000..9763916a8 --- /dev/null +++ b/packages/api/src/protocol.ts @@ -0,0 +1,40 @@ +import type { Web5Agent } from '@web5/agent'; +import type { ProtocolsConfigure } from '@tbd54566975/dwn-sdk-js'; + +// TODO: export ProtocolsConfigureMessage from dwn-sdk-js +export type ProtocolsConfigureMessage = ProtocolsConfigure['message']; +type ProtocolMetadata = { + author: string; + messageCid?: string; +}; + +export class Protocol { + private _agent: Web5Agent; + private _metadata: ProtocolMetadata; + private _protocolsConfigureMessage: ProtocolsConfigureMessage; + + get definition() { + return this._protocolsConfigureMessage.descriptor.definition; + } + + constructor(agent: Web5Agent, protocolsConfigureMessage: ProtocolsConfigureMessage, metadata: ProtocolMetadata) { + this._agent = agent; + this._metadata = metadata; + this._protocolsConfigureMessage = protocolsConfigureMessage; + } + + toJSON() { + return this._protocolsConfigureMessage; + } + + async send(target: string) { + const { reply } = await this._agent.sendDwnRequest({ + messageType : 'ProtocolsConfigure', + author : this._metadata.author, + target : target, + messageCid : this._metadata.messageCid + }); + + return { status: reply.status }; + } +} \ No newline at end of file diff --git a/packages/web5/src/record.ts b/packages/api/src/record.ts similarity index 83% rename from packages/web5/src/record.ts rename to packages/api/src/record.ts index 684b7a8f4..1af6fe69e 100644 --- a/packages/web5/src/record.ts +++ b/packages/api/src/record.ts @@ -1,5 +1,5 @@ +import type { Web5Agent } from '@web5/agent'; import type { Readable } from 'readable-stream'; -import type { Web5Agent } from '@tbd54566975/web5-agent'; import type { RecordsReadReply, RecordsWriteDescriptor, RecordsWriteMessage, RecordsWriteOptions } from '@tbd54566975/dwn-sdk-js'; import { ReadableWebToNodeStream } from 'readable-web-to-node-stream'; @@ -15,23 +15,32 @@ export type RecordOptions = RecordsWriteMessage & { data?: Readable | ReadableStream; }; -export type RecordModel = RecordsWriteDescriptor & Omit & { - author: string; - recordId?: string; - target: string; -} +export type RecordModel = RecordsWriteDescriptor + & Omit + & { + author: string; + recordId?: string; + target: string; + } export type RecordUpdateOptions = { data?: unknown; dataCid?: RecordsWriteDescriptor['dataCid']; dataSize?: RecordsWriteDescriptor['dataSize']; - messageTimestamp?: RecordsWriteDescriptor['messageTimestamp']; + dateModified?: RecordsWriteDescriptor['messageTimestamp']; datePublished?: RecordsWriteDescriptor['datePublished']; published?: RecordsWriteDescriptor['published']; } /** * TODO: Document class. + * + * Note: The `messageTimestamp` of the most recent RecordsWrite message is + * logically equivalent to the date/time at which a Record was most + * recently modified. Since this Record class implementation is + * intended to simplify the developer experience of working with + * logical records (and not individual DWN messages) the + * `messageTimestamp` is mapped to `dateModified`. */ export class Record implements RecordModel { // mutable properties @@ -39,6 +48,7 @@ export class Record implements RecordModel { target: string; isDeleted = false; + private _agent: Web5Agent; private _attestation?: RecordsWriteMessage['attestation']; private _contextId?: string; private _descriptor: RecordsWriteDescriptor; @@ -46,7 +56,6 @@ export class Record implements RecordModel { private _encryption?: RecordsWriteMessage['encryption']; private _readableStream?: Readable | Promise; private _recordId: string; - private _web5Agent: Web5Agent; // Immutable DWN Record properties. get attestation(): RecordsWriteMessage['attestation'] { return this._attestation; } @@ -66,13 +75,13 @@ export class Record implements RecordModel { // Mutable DWN Record properties. get dataCid() { return this._descriptor.dataCid; } get dataSize() { return this._descriptor.dataSize; } - get messageTimestamp() { return this._descriptor.messageTimestamp; } - get dateModified() { return this.messageTimestamp; } + get dateModified() { return this._descriptor.messageTimestamp; } get datePublished() { return this._descriptor.datePublished; } + get messageTimestamp() { return this._descriptor.messageTimestamp; } get published() { return this._descriptor.published; } - constructor(web5Agent: Web5Agent, options: RecordOptions) { - this._web5Agent = web5Agent; + constructor(agent: Web5Agent, options: RecordOptions) { + this._agent = agent; // Store the target and author DIDs that were used to create the message to use for subsequent reads, etc. this.author = options.author; @@ -107,7 +116,7 @@ export class Record implements RecordModel { // `encodedData` will be set if the Record was instantiated by dwn.records.create()/write(). // `readableStream` will be set if Record was instantiated by dwn.records.read(). // If neither of the above are true, then the record must be fetched from the DWN. - this._readableStream = this._web5Agent.processDwnRequest({ + this._readableStream = this._agent.processDwnRequest({ author : this.author, messageOptions : { recordId: this.id }, messageType : DwnInterfaceName.Records + DwnMethodName.Read, @@ -127,7 +136,7 @@ export class Record implements RecordModel { this._encodedData = new Blob([dataBytes], { type: this.dataFormat }); } - // Explicitly cast _encodedData as a Blob since if non-null, it has been converted from string to Blob. + // Explicitly cast `encodedData` as a Blob since, if non-null, it has been converted from string to Blob. const dataBlob = this._encodedData as Blob; // eslint-disable-next-line @typescript-eslint/no-this-alias @@ -169,7 +178,7 @@ export class Record implements RecordModel { if (this.isDeleted) throw new Error('Operation failed: Attempted to call `delete()` on a record that has already been deleted.'); // Attempt to delete the record from the DWN. - const agentResponse = await this._web5Agent.processDwnRequest({ + const agentResponse = await this._agent.processDwnRequest({ author : this.author, messageOptions : { recordId: this.id }, messageType : DwnInterfaceName.Records + DwnMethodName.Delete, @@ -192,7 +201,7 @@ export class Record implements RecordModel { async send(target: string): Promise { if (this.isDeleted) throw new Error('Operation failed: Attempted to call `send()` on a record that has already been deleted.'); - const { reply: { status } } = await this._web5Agent.sendDwnRequest({ + const { reply: { status } } = await this._agent.sendDwnRequest({ messageType : DwnInterfaceName.Records + DwnMethodName.Write, author : this.author, dataStream : await this.data.blob(), @@ -259,13 +268,17 @@ export class Record implements RecordModel { async update(options: RecordUpdateOptions = {}) { if (this.isDeleted) throw new Error('Operation failed: Attempted to call `update()` on a record that has already been deleted.'); + // Map Record class `dateModified` property to DWN SDK `messageTimestamp`. + const { dateModified, ...updateOptions } = options as Partial & RecordUpdateOptions; + updateOptions.messageTimestamp = dateModified; + // Begin assembling update message. - let updateMessage = { ...this._descriptor, ...options } as Partial; + let updateMessage = {...this._descriptor, ...updateOptions } as Partial; let dataBlob: Blob; if (options.data !== undefined) { // If `data` is being updated then `dataCid` and `dataSize` must be undefined and the `data` property is passed as - // a top-level property to `web5Agent.processDwnRequest()`. + // a top-level property to `agent.processDwnRequest()`. delete updateMessage.dataCid; delete updateMessage.dataSize; delete updateMessage.data; @@ -277,10 +290,10 @@ export class Record implements RecordModel { const mutableDescriptorProperties = new Set(['data', 'dataCid', 'dataSize', 'dateModified', 'datePublished', 'published']); Record.verifyPermittedMutation(Object.keys(options), mutableDescriptorProperties); - // If a new `dateModified` was not provided, remove it from the updateMessage to let the DWN SDK auto-fill. - // This is necessary because otherwise DWN SDK throws an Error 409 Conflict due to attempting to overwrite a record - // when the `dateModified` timestamps are identical. - if (options.messageTimestamp === undefined) { + // If a new `dateModified` was not provided, remove the equivalent `messageTimestamp` property from from the + // updateMessage to let the DWN SDK auto-fill. This is necessary because otherwise DWN SDK throws an + // Error 409 Conflict due to attempting to overwrite a record when the `messageTimestamp` values are identical. + if (options.dateModified === undefined) { delete updateMessage.messageTimestamp; } @@ -298,7 +311,7 @@ export class Record implements RecordModel { ...updateMessage }; - const agentResponse = await this._web5Agent.processDwnRequest({ + const agentResponse = await this._agent.processDwnRequest({ author : this.author, dataStream : dataBlob, messageOptions, @@ -314,9 +327,9 @@ export class Record implements RecordModel { mutableDescriptorProperties.forEach(property => { this._descriptor[property] = responseMessage.descriptor[property]; }); - // Only cache data if `dataSize` is less than DWN 'max data size allowed to be encoded'. + // Cache data. if (options.data !== undefined) { - this._encodedData = dataBlob; // Clear `encodedData` in case it was previously set. + this._encodedData = dataBlob; } } @@ -333,7 +346,7 @@ export class Record implements RecordModel { /** * TODO: Document method. */ - static isReadableWebStream(stream) { + private static isReadableWebStream(stream) { // TODO: Improve robustness of the check modeled after node:stream. return typeof stream._read !== 'function'; } @@ -341,7 +354,7 @@ export class Record implements RecordModel { /** * TODO: Document method. */ - private static verifyPermittedMutation(propertiesToMutate: string[], mutableDescriptorProperties: Set) { + private static verifyPermittedMutation(propertiesToMutate: Iterable, mutableDescriptorProperties: Set) { for (const property of propertiesToMutate) { if (!mutableDescriptorProperties.has(property)) { throw new Error(`${property} is an immutable property. Its value cannot be changed.`); diff --git a/packages/api/src/tech-preview.ts b/packages/api/src/tech-preview.ts new file mode 100644 index 000000000..1488a1ce7 --- /dev/null +++ b/packages/api/src/tech-preview.ts @@ -0,0 +1,55 @@ +import { utils as didUtils } from '@web5/dids'; + +/** + * Dynamically selects up to 2 DWN endpoints that are provided + * by default during the Tech Preview period. + */ +export async function getTechPreviewDwnEndpoints(): Promise { + let response: Response; + try { + response = await fetch('https://dwn.tbddev.org/.well-known/did.json'); + if (!response.ok) { + throw new Error(`HTTP Error: ${response.status} ${response.statusText}`); + } + } catch(error: any) { + console.warn('failed to get tech preview dwn endpoints:', error.message); + return []; + } + + const didDocument = await response.json(); + const [ dwnService ] = didUtils.getServices({ didDocument, id: '#dwn', type: 'DecentralizedWebNode' }); + + // allocate up to 2 nodes for a user. + const techPreviewEndpoints = new Set(); + + if ('serviceEndpoint' in dwnService + && !Array.isArray(dwnService.serviceEndpoint) + && typeof dwnService.serviceEndpoint !== 'string' + && Array.isArray(dwnService.serviceEndpoint.nodes)) { + const dwnUrls = dwnService.serviceEndpoint.nodes; + + const numNodesToAllocate = Math.min(dwnUrls.length, 2); + + for (let attempts = 0; attempts < dwnUrls.length && techPreviewEndpoints.size < numNodesToAllocate; attempts += 1) { + const nodeIdx = getRandomInt(0, dwnUrls.length); + const dwnUrl = dwnUrls[nodeIdx]; + + try { + const healthCheck = await fetch(`${dwnUrl}/health`); + if (healthCheck.ok) { + techPreviewEndpoints.add(dwnUrl); + } + } catch(error: unknown) { + // Ignore healthcheck failures and try the next node. + } + } + } + + return Array.from(techPreviewEndpoints); +} + +function getRandomInt(min, max) { + min = Math.ceil(min); + max = Math.floor(max); + return Math.floor(Math.random() * (max - min)) + min; +} \ No newline at end of file diff --git a/packages/api/src/utils.ts b/packages/api/src/utils.ts new file mode 100644 index 000000000..18048903f --- /dev/null +++ b/packages/api/src/utils.ts @@ -0,0 +1,27 @@ +import { Convert, universalTypeOf } from '@web5/common'; + +/** + * Set/detect the media type and return the data as bytes. + */ +export const dataToBlob = (data: any, dataFormat?: string) => { + let dataBlob: Blob; + + // Check for Object or String, and if neither, assume bytes. + const detectedType = universalTypeOf(data); + if (dataFormat === 'text/plain' || detectedType === 'String') { + dataBlob = new Blob([data], { type: 'text/plain' }); + } else if (dataFormat === 'application/json' || detectedType === 'Object') { + const dataBytes = Convert.object(data).toUint8Array(); + dataBlob = new Blob([dataBytes], { type: 'application/json' }); + } else if (detectedType === 'Uint8Array' || detectedType === 'ArrayBuffer') { + dataBlob = new Blob([data], { type: 'application/octet-stream' }); + } else if (detectedType === 'Blob') { + dataBlob = data; + } else { + throw new Error('data type not supported.'); + } + + dataFormat = dataFormat || dataBlob.type || 'application/octet-stream'; + + return { dataBlob, dataFormat }; +}; \ No newline at end of file diff --git a/packages/api/src/vc-api.ts b/packages/api/src/vc-api.ts new file mode 100644 index 000000000..7dd6620e1 --- /dev/null +++ b/packages/api/src/vc-api.ts @@ -0,0 +1,16 @@ +import type { Web5Agent } from '@web5/agent'; + +export class VcApi { + private agent: Web5Agent; + private connectedDid: string; + + constructor(options: { agent: Web5Agent, connectedDid: string }) { + this.agent = options.agent; + this.connectedDid = options.connectedDid; + } + + async create() { + // TODO: implement + throw new Error('Not implemented.'); + } +} \ No newline at end of file diff --git a/packages/api/src/web5.ts b/packages/api/src/web5.ts new file mode 100644 index 000000000..a4056b4a8 --- /dev/null +++ b/packages/api/src/web5.ts @@ -0,0 +1,161 @@ +import type { Web5Agent } from '@web5/agent'; +// import type { SyncManager } from '@tbd54566975/web5-user-agent'; +// import type { DidState, DidMethodApi, DidResolverCache, DwnServiceEndpoint } from '@tbd54566975/dids'; +// import type { DidState } from '@tbd54566975/dids'; + +// import ms from 'ms'; + +// import { Web5ProxyAgent } from '@tbd54566975/web5-proxy-agent'; +// import { Dwn } from '@tbd54566975/dwn-sdk-js'; +// import { Web5UserAgent, ProfileApi, SyncApi } from '@tbd54566975/web5-user-agent'; +import { Web5UserAgent } from '@web5/user-agent'; +// import { DidIonApi, DidKeyApi, utils as didUtils } from '@tbd54566975/dids'; + +import { VcApi } from './vc-api.js'; +import { DwnApi } from './dwn-api.js'; +import { DidApi } from './did-api.js'; +// import { AppStorage } from './app-storage.js'; +// import { getRandomInt } from './utils.js'; +// import { DidResolutionCache } from './did-resolution-cache.js'; + +/** + * overrides to defaults configured for technical preview phase + */ +// export type TechPreviewOptions = { +// /** overrides default dwnEndpoints provided for technical preview. see `Web5.#enqueueNextSync` */ +// dwnEndpoints?: string[]; +// } + +/** + * optional overrides that can be provided when calling {@link Web5.connect} + */ +export type Web5ConnectOptions = { + /** a custom {@link Web5Agent}. Defaults to creating an embedded {@link Web5UserAgent} if one isnt provided */ + agent?: Web5Agent; + /** additional {@link DidMethodApi}s that can be used to create and resolve DID methods. defaults to did:key and did:ion */ + // didMethodApis?: DidMethodApi[]; + /** custom cache used to store DidResolutionResults. defaults to a {@link DidResolutionCache} */ + // didResolutionCache?: DidResolverCache; + /** overrides to defaults configured for technical preview phase. See {@link TechPreviewOptions} */ + // techPreview?: TechPreviewOptions; +} + +/** + * @see {@link Web5ConnectOptions} + */ +type Web5Options = { + agent: Web5Agent; + // appStorage?: AppStorage; + connectedDid: string; +}; + +export class Web5 { + // appStorage: AppStorage; + did: DidApi; + dwn: DwnApi; + vc: VcApi; + private connectedDid: string; + + // private static APP_DID_KEY = 'WEB5_APP_DID'; + + constructor(options: Web5Options) { + const { agent, connectedDid } = options; + this.connectedDid = connectedDid; + this.did = new DidApi({ agent, connectedDid }); + this.dwn = new DwnApi({ agent, connectedDid }); + this.vc = new VcApi({ agent, connectedDid }); + // this.appStorage ||= new AppStorage(); + } + + /** + * Connects to a {@link Web5Agent}. defaults to creating an embedded {@link Web5UserAgent} if one isn't provided + * @param options - optional overrides + * @returns + */ + static async connect(options: Web5ConnectOptions = {}) { + let { agent } = options; + + // load app's did + // const appStorage = new AppStorage(); + // const cachedAppDidState = await appStorage.get(Web5.APP_DID_KEY); + // let appDidState: DidState; + + // if (cachedAppDidState) { + // appDidState = JSON.parse(cachedAppDidState); + // } else { + // appDidState = await this.did.create('key'); + // appStorage.set(Web5.APP_DID_KEY, JSON.stringify(appDidState)); + // } + + // // TODO: sniff to see if remote agent is available + // // TODO: if available,connect to remote agent using Web5ProxyAgent + + // // fall back to instantiating local agent + // const profileApi = new ProfileApi(); + // let [ profile ] = await profileApi.listProfiles(); + + // options.didMethodApis ??= []; + + // // override default cache used by `Web5.did` + // Web5.did = new DidApi({ + // didMethodApis : [new DidIonApi(), new DidKeyApi(), ...options.didMethodApis], + // cache : options.didResolutionCache || new DidResolutionCache() + // }); + + // const dwn = await Dwn.create(); + // const syncManager = new SyncApi({ + // profileManager : profileApi, + // didResolver : Web5.did.resolver, // share the same resolver to share the same underlying cache + // dwn : dwn + // }); + + // if (!profile) { + // const dwnUrls = options.techPreview?.dwnEndpoints || await Web5.getTechPreviewDwnEndpoints(); + // const ionCreateOptions = await DidIonApi.generateDwnConfiguration(dwnUrls); + // const defaultProfileDid = await this.did.create('ion', ionCreateOptions); + + // // setting id & name as the app's did to make migration easier + // profile = await profileApi.createProfile({ + // name : appDidState.id, + // did : defaultProfileDid, + // connections : [appDidState.id], + // }); + + // await syncManager.registerProfile(profile.did.id); + // } + + // const agent = await Web5UserAgent.create({ + // profileManager : profileApi, + // didResolver : Web5.did.resolver, // share the same resolver to share the same underlying cache + // syncManager : syncManager, + // dwn : dwn, + // }); + + // const connectedDid = profile.did.id; + // const web5 = new Web5({ appStorage: appStorage, agent: agent, connectedDid }); + + // Web5.#enqueueNextSync(syncManager, ms('2m')); + + if (agent === undefined) { + agent = await Web5UserAgent.create(); + } + + + const web5 = new Web5({ agent, connectedDid: 'did:key:123' }); + + return { web5, did: 'did:key:123' }; + } + + // static #enqueueNextSync(syncManager: SyncManager, delay = 1_000) { + // setTimeout(async () => { + // try { + // await syncManager.push(); + // await syncManager.pull(); + + // return this.#enqueueNextSync(syncManager, delay); + // } catch(e) { + // console.error('Sync failed due to error: ', e); + // } + // }, delay); + // } +} \ No newline at end of file diff --git a/packages/web5/tests/did-resolution-cache.spec.ts b/packages/api/tests-old/did-resolution-cache.spec.ts similarity index 100% rename from packages/web5/tests/did-resolution-cache.spec.ts rename to packages/api/tests-old/did-resolution-cache.spec.ts diff --git a/packages/web5/tests/chai-plugins.d.ts b/packages/api/tests/chai-plugins.d.ts similarity index 100% rename from packages/web5/tests/chai-plugins.d.ts rename to packages/api/tests/chai-plugins.d.ts diff --git a/packages/api/tests/fixtures/protocol-definitions/email.json b/packages/api/tests/fixtures/protocol-definitions/email.json new file mode 100644 index 000000000..91ca1b942 --- /dev/null +++ b/packages/api/tests/fixtures/protocol-definitions/email.json @@ -0,0 +1,48 @@ +{ + "protocol": "http://email-protocol.xyz", + "published": false, + "types": { + "email": { + "schema": "email", + "dataFormats": ["text/plain"] + } + }, + "structure": { + "email": { + "$actions": [ + { + "who": "anyone", + "can": "write" + }, + { + "who": "author", + "of": "email", + "can": "read" + }, + { + "who": "recipient", + "of": "email", + "can": "read" + } + ], + "email": { + "$actions": [ + { + "who": "anyone", + "can": "write" + }, + { + "who": "author", + "of": "email/email", + "can": "read" + }, + { + "who": "recipient", + "of": "email/email", + "can": "read" + } + ] + } + } + } +} diff --git a/packages/api/tests/record.spec.ts b/packages/api/tests/record.spec.ts new file mode 100644 index 000000000..5897baa12 --- /dev/null +++ b/packages/api/tests/record.spec.ts @@ -0,0 +1,1015 @@ +import type { + RecordsWriteMessage, + PublicJwk as DwnPublicKeyJwk, + PrivateJwk as DwnPrivateKeyJwk, +} from '@tbd54566975/dwn-sdk-js'; + +import chai, { expect } from 'chai'; +import chaiAsPromised from 'chai-as-promised'; +import { RecordsWrite } from '@tbd54566975/dwn-sdk-js'; +import { ManagedIdentity, TestManagedAgent } from '@web5/agent'; + +import { Record } from '../src/record.js'; +import { DwnApi } from '../src/dwn-api.js'; +import { dataToBlob } from '../src/utils.js'; +import { TestUserAgent } from './utils/test-user-agent.js'; +import { TestDataGenerator } from './utils/test-data-generator.js'; + +chai.use(chaiAsPromised); + +// NOTE: @noble/secp256k1 requires globalThis.crypto polyfill for node.js <=18: https://github.com/paulmillr/noble-secp256k1/blob/main/README.md#usage +// Remove when we move off of node.js v18 to v20, earliest possible time would be Oct 2023: https://github.com/nodejs/release#release-schedule +// NOTE: @noble/secp256k1 requires globalThis.crypto polyfill for node.js <=18: https://github.com/paulmillr/noble-secp256k1/blob/main/README.md#usage +// Remove when we move off of node.js v18 to v20, earliest possible time would be Oct 2023: https://github.com/nodejs/release#release-schedule +import { webcrypto } from 'node:crypto'; +// @ts-ignore +if (!globalThis.crypto) globalThis.crypto = webcrypto; + +// TODO: Come up with a better way of resolving the TS errors. +type RecordsWriteTest = RecordsWrite & RecordsWriteMessage; + +describe('Record', () => { + let dataText: string; + let dataBlob: Blob; + let dataFormat: string; + let dwn: DwnApi; + let identity: ManagedIdentity; + let testAgent: TestManagedAgent; + + before(async () => { + testAgent = await TestManagedAgent.create({ + agentClass : TestUserAgent, + agentStores : 'memory' + }); + + dataText = TestDataGenerator.randomString(100); + ({ dataBlob, dataFormat } = dataToBlob(dataText)); + }); + + beforeEach(async () => { + await testAgent.clearStorage(); + + // Create an Agent DID. + await testAgent.createAgentDid(); + + // Create a new Identity to author DWN messages. + const identity = await testAgent.agent.identityManager.create({ + name : 'Test', + didMethod : 'key', + kms : 'local' + }); + + // Instantiate DwnApi. + dwn = new DwnApi({ agent: testAgent.agent, connectedDid: identity.did }); + }); + + after(async () => { + await testAgent.clearStorage(); + await testAgent.closeStorage(); + }); + + // it('should retain all defined properties', async () => { + // // RecordOptions properties + // const author = didAllKeys; + // const target = didAllKeys; + + // const profile = await testAgent.profileApi.getProfile(didAllKeys); + + // // Retrieve `#dwn` service entry. + // const [ didDwnService ] = didUtils.getServices(profile!.did.didDocument as DidDocument, { id: '#dwn' }); + + // // Retrieve first record encryption key from the #dwn serviceEndpoint. + // const recordEncryptionKeyId = typeof didDwnService!.serviceEndpoint !== 'string' ? didDwnService!.serviceEndpoint!.encryptionKeys![0] : undefined; + // const encryptionKeyId = `${profile?.did.id}${recordEncryptionKeyId}`; + // const encryptionPublicKeyJwk = profile!.did.keys.find(key => key.id === encryptionKeyId)!.publicKeyJwk; + + // // Retrieve first message authorization key from the #dwn serviceEndpoint. + // const messageAuthorizationKeyId = typeof didDwnService!.serviceEndpoint !== 'string' ? didDwnService!.serviceEndpoint!.messageAuthorizationKeys![0] : undefined; + // const authorizationKeyId = `${profile?.did.id}${messageAuthorizationKeyId}`; + // const authorizationPrivateKeyJwk = profile!.did.keys.find(key => key.id === authorizationKeyId)!.privateKeyJwk; + + // // Retrieve first message attestation key from the #dwn serviceEndpoint. + // const messageAttestationKeyId = typeof didDwnService!.serviceEndpoint !== 'string' ? didDwnService!.serviceEndpoint!.messageAttestationKeys![0] : undefined; + // const attestationKeyId = `${profile?.did.id}${messageAttestationKeyId}`; + // const attestationPrivateKeyJwk = profile!.did.keys.find(key => key.id === attestationKeyId)!.privateKeyJwk; + + // // RecordsWriteMessage properties that can be pre-defined + // const attestation = [{ + // privateJwk : attestationPrivateKeyJwk as DwnPrivateKeyJwk, + // protectedHeader : { + // alg : attestationPrivateKeyJwk.alg as string, + // kid : attestationKeyId + // } + // }]; + + // const authorization = { + // privateJwk : authorizationPrivateKeyJwk as DwnPrivateKeyJwk, + // protectedHeader : { + // alg : authorizationPrivateKeyJwk.alg as string, + // kid : authorizationKeyId + // } + // }; + + // const encryptionInput = { + // initializationVector : TestDataGenerator.randomBytes(16), + // key : TestDataGenerator.randomBytes(32), + // keyEncryptionInputs : [ + // { + // derivationScheme : KeyDerivationScheme.ProtocolPath, + // publicKey : encryptionPublicKeyJwk as DwnPublicKeyJwk, + // publicKeyId : recordEncryptionKeyId + // }, + // { + // derivationScheme : KeyDerivationScheme.Schemas, + // publicKey : encryptionPublicKeyJwk as DwnPublicKeyJwk, + // publicKeyId : recordEncryptionKeyId + // }, + // ] + // }; + + // // RecordsWriteDescriptor properties that can be pre-defined + // const protocol = 'http://example.org/chat/protocol'; + // const protocolPath = 'message'; + // const recipient = didAllKeys; + // const published = true; + // const schema = 'http://example.org/chat/schema/message'; + + // // Create a parent record to reference in the RecordsWriteMessage used for validation + // const parentRecorsWrite = await RecordsWrite.create({ + // authorizationSignatureInput : authorization, + // data : new Uint8Array(await dataBlob.arrayBuffer()), + // dataFormat, + // protocol, + // protocolPath, + // schema, + // }) as RecordsWriteTest; + + // // Create a RecordsWriteMessage + // const recordsWrite = await RecordsWrite.create({ + // attestationSignatureInputs : attestation, + // authorizationSignatureInput : authorization, + // data : new Uint8Array(await dataBlob.arrayBuffer()), + // dataFormat, + // // encryptionInput, + // parentId : parentRecorsWrite.recordId, + // protocol, + // protocolPath, + // published, + // recipient, + // schema, + // }) as RecordsWriteTest; + + // // Create record using test RecordsWriteMessage. + // const record = new Record(testAgent.agent, { + // ...recordsWrite.message, + // encodedData: dataBlob, + // target, + // author, + // }); + + // // Retained Record properties + // expect(record.author).to.equal(author); + // expect(record.target).to.equal(target); + + // // Retained RecordsWriteMessage top-level properties + // expect(record.contextId).to.equal(recordsWrite.message.contextId); + // expect(record.id).to.equal(recordsWrite.message.recordId); + // expect(record.encryption).to.not.be.undefined; + // expect(record.encryption).to.deep.equal(recordsWrite.message.encryption); + // expect(record.encryption!.keyEncryption.find(key => key.derivationScheme === KeyDerivationScheme.ProtocolPath)); + // expect(record.encryption!.keyEncryption.find(key => key.derivationScheme === KeyDerivationScheme.Schemas)); + // expect(record.attestation).to.not.be.undefined; + // expect(record.attestation).to.have.property('signatures'); + + // // Retained RecordsWriteDescriptor properties + // expect(record.protocol).to.equal(protocol); + // expect(record.protocolPath).to.equal(protocolPath); + // expect(record.recipient).to.equal(recipient); + // expect(record.schema).to.equal(schema); + // expect(record.parentId).to.equal(parentRecorsWrite.recordId); + // expect(record.dataCid).to.equal(recordsWrite.message.descriptor.dataCid); + // expect(record.dataSize).to.equal(recordsWrite.message.descriptor.dataSize); + // expect(record.dateCreated).to.equal(recordsWrite.message.descriptor.dateCreated); + // expect(record.dateModified).to.equal(recordsWrite.message.descriptor.messageTimestamp); + // expect(record.published).to.equal(published); + // expect(record.datePublished).to.equal(recordsWrite.message.descriptor.datePublished); + // expect(record.dataFormat).to.equal(dataFormat); + // }); + + describe('record.update', () => { + it('updates a record', async () => { + const { status, record } = await dwn.records.write({ + data : 'Hello, world!', + message : { + schema : 'foo/bar', + dataFormat : 'text/plain' + } + }); + + const dataCidBeforeDataUpdate = record!.dataCid; + + expect(status.code).to.equal(202); + expect(record).to.not.be.undefined; + + const updateResult = await record!.update({ data: 'bye' }); + expect(updateResult.status.code).to.equal(202); + + const readResult = await dwn.records.read({ + message: { + recordId: record!.id + } + }); + + expect(readResult.status.code).to.equal(200); + expect(readResult.record).to.not.be.undefined; + + expect(readResult.record.dataCid).to.not.equal(dataCidBeforeDataUpdate); + expect(readResult.record.dataCid).to.equal(record!.dataCid); + + const updatedData = await record!.data.text(); + expect(updatedData).to.equal('bye'); + }); + + it('throws an exception when an immutable property is modified', async () => { + const { status, record } = await dwn.records.write({ + data : 'Hello, world!', + message : { + schema : 'foo/bar', + dataFormat : 'text/plain' + } + }); + + expect(status.code).to.equal(202); + expect(record).to.not.be.undefined; + + // @ts-expect-error because this test intentionally specifies an immutable property that is not present in RecordUpdateOptions. + await expect(record!.update({ dataFormat: 'application/json' })).to.eventually.be.rejectedWith('is an immutable property. Its value cannot be changed.'); + }); + }); + + describe('record.data', () => { + describe('blob()', () => { + it('returns small data payloads after dwn.records.write()', async () => { + // Generate data that is less than the encoded data limit to ensure that the data will not have to be fetched + // with a RecordsRead when record.data.blob() is executed. + const dataJson = TestDataGenerator.randomJson(500); + const inputDataBytes = new TextEncoder().encode(JSON.stringify(dataJson)); + + // Write the 500B record to agent-connected DWN. + const { record, status } = await dwn.records.write({ data: dataJson }); + + expect(status.code).to.equal(202); + + // Confirm that the size, in bytes, of the data read as a Blob matches the original input data. + const readDataBlob = await record!.data.blob(); + expect(readDataBlob.size).to.equal(inputDataBytes.length); + + // Convert the Blob into an array and ensure it matches the input data byte for byte. + const readDataBytes = new Uint8Array(await readDataBlob.arrayBuffer()); + expect(readDataBytes).to.deep.equal(inputDataBytes); + }); + + it('returns small data payloads after dwn.records.read()', async () => { + // Generate data that is less than the encoded data limit to ensure that the data will not have to be fetched + // with a RecordsRead when record.data.blob() is executed. + const dataJson = TestDataGenerator.randomJson(500); + const inputDataBytes = new TextEncoder().encode(JSON.stringify(dataJson)); + + // Write the 500B record to agent-connected DWN. + const { record, status } = await dwn.records.write({ data: dataJson }); + + expect(status.code).to.equal(202); + + // Read the record that was just created. + const { record: readRecord, status: readRecordStatus } = await dwn.records.read({ message: { recordId: record!.id }}); + + expect(readRecordStatus.code).to.equal(200); + + // Confirm that the size, in bytes, of the data read as a Blob matches the original input data. + const readDataBlob = await readRecord.data.blob(); + expect(readDataBlob.size).to.equal(inputDataBytes.length); + + // Convert the Blob into an array and ensure it matches the input data byte for byte. + const readDataBytes = new Uint8Array(await readDataBlob.arrayBuffer()); + expect(readDataBytes).to.deep.equal(inputDataBytes); + }); + + it('returns large data payloads after dwn.records.write()', async () => { + // Generate data that exceeds the DWN encoded data limit to ensure that the data will have to be fetched + // with a RecordsRead when record.data.blob() is executed. + const dataJson = TestDataGenerator.randomJson(11_000); + const inputDataBytes = new TextEncoder().encode(JSON.stringify(dataJson)); + + // Write the 11KB record to agent-connected DWN. + const { record, status } = await dwn.records.write({ data: dataJson }); + + expect(status.code).to.equal(202); + + // Confirm that the size, in bytes, of the data read as a Blob matches the original input data. + const readDataBlob = await record!.data.blob(); + expect(readDataBlob.size).to.equal(inputDataBytes.length); + + // Convert the Blob into an array and ensure it matches the input data byte for byte. + const readDataBytes = new Uint8Array(await readDataBlob.arrayBuffer()); + expect(readDataBytes).to.deep.equal(inputDataBytes); + }); + + it('returns large data payloads after dwn.records.read()', async () => { + // Generate data that exceeds the DWN encoded data limit to ensure that the data will have to be fetched + // with a RecordsRead when record.data.blob() is executed. + const dataJson = TestDataGenerator.randomJson(11_000); + const inputDataBytes = new TextEncoder().encode(JSON.stringify(dataJson)); + + // Write the 11KB record to agent-connected DWN. + const { record, status } = await dwn.records.write({ data: dataJson }); + + expect(status.code).to.equal(202); + + // Read the record that was just created. + const { record: readRecord, status: readRecordStatus } = await dwn.records.read({ message: { recordId: record!.id }}); + + expect(readRecordStatus.code).to.equal(200); + + // Confirm that the size, in bytes, of the data read as a Blob matches the original input data. + const readDataBlob = await readRecord.data.blob(); + expect(readDataBlob.size).to.equal(inputDataBytes.length); + + // Convert the Blob into an array and ensure it matches the input data byte for byte. + const readDataBytes = new Uint8Array(await readDataBlob.arrayBuffer()); + expect(readDataBytes).to.deep.equal(inputDataBytes); + }); + }); + + describe('json()', () => { + it('returns small data payloads after dwn.records.write()', async () => { + // Generate data that is less than the encoded data limit to ensure that the data will not have to be fetched + // with a RecordsRead when record.data.json() is executed. + const dataJson = TestDataGenerator.randomJson(500); + const inputDataBytes = new TextEncoder().encode(JSON.stringify(dataJson)); + + // Write the 500B record to agent-connected DWN. + const { record, status } = await dwn.records.write({ data: dataJson }); + + expect(status.code).to.equal(202); + + // Confirm that the size, in bytes, of the data read as JSON matches the original input data. + const readDataJson = await record!.data.json(); + const readDataBytes = new TextEncoder().encode(JSON.stringify(readDataJson)); + expect(readDataBytes.length).to.equal(inputDataBytes.length); + + // Ensure the JSON returned matches the input data, byte for byte. + expect(readDataBytes).to.deep.equal(inputDataBytes); + }); + + it('returns small data payloads after dwn.records.read()', async () => { + // Generate data that is less than the encoded data limit to ensure that the data will not have to be fetched + // with a RecordsRead when record.data.json() is executed. + const dataJson = TestDataGenerator.randomJson(500); + const inputDataBytes = new TextEncoder().encode(JSON.stringify(dataJson)); + + // Write the 500B record to agent-connected DWN. + const { record, status } = await dwn.records.write({ data: dataJson }); + + expect(status.code).to.equal(202); + + // Read the record that was just created. + const { record: readRecord, status: readRecordStatus } = await dwn.records.read({ message: { recordId: record!.id }}); + + expect(readRecordStatus.code).to.equal(200); + + // Confirm that the size, in bytes, of the data read as JSON matches the original input data. + const readDataJson = await readRecord!.data.json(); + const readDataBytes = new TextEncoder().encode(JSON.stringify(readDataJson)); + expect(readDataBytes.length).to.equal(inputDataBytes.length); + + // Ensure the JSON returned matches the input data, byte for byte. + expect(readDataBytes).to.deep.equal(inputDataBytes); + }); + + it('returns large data payloads after dwn.records.write()', async () => { + // Generate data that exceeds the DWN encoded data limit to ensure that the data will have to be fetched + // with a RecordsRead when record.data.json() is executed. + const dataJson = TestDataGenerator.randomJson(11_000); + const inputDataBytes = new TextEncoder().encode(JSON.stringify(dataJson)); + + // Write the 11KB record to agent-connected DWN. + const { record, status } = await dwn.records.write({ data: dataJson }); + + expect(status.code).to.equal(202); + + // Confirm that the size, in bytes, of the data read as JSON matches the original input data. + const readDataJson = await record!.data.json(); + const readDataBytes = new TextEncoder().encode(JSON.stringify(readDataJson)); + expect(readDataBytes.length).to.equal(inputDataBytes.length); + + // Ensure the JSON returned matches the input data, byte for byte. + expect(readDataBytes).to.deep.equal(inputDataBytes); + }); + + it('returns large data payloads after dwn.records.read()', async () => { + // Generate data that exceeds the DWN encoded data limit to ensure that the data will have to be fetched + // with a RecordsRead when record.data.json() is executed. + const dataJson = TestDataGenerator.randomJson(11_000); + const inputDataBytes = new TextEncoder().encode(JSON.stringify(dataJson)); + + // Write the 11KB record to agent-connected DWN. + const { record, status } = await dwn.records.write({ data: dataJson }); + + expect(status.code).to.equal(202); + + // Read the record that was just created. + const { record: readRecord, status: readRecordStatus } = await dwn.records.read({ message: { recordId: record!.id }}); + + expect(readRecordStatus.code).to.equal(200); + + // Confirm that the size, in bytes, of the data read as JSON matches the original input data. + const readDataJson = await readRecord!.data.json(); + const readDataBytes = new TextEncoder().encode(JSON.stringify(readDataJson)); + expect(readDataBytes.length).to.equal(inputDataBytes.length); + + // Ensure the JSON returned matches the input data, byte for byte. + expect(readDataBytes).to.deep.equal(inputDataBytes); + }); + }); + + describe('text()', () => { + it('returns small data payloads after dwn.records.write()', async () => { + // Generate data that is less than the encoded data limit to ensure that the data will not have to be fetched + // with a RecordsRead when record.data.text() is executed. + const dataText = TestDataGenerator.randomString(500); + + // Write the 500B record to agent-connected DWN. + const { record, status } = await dwn.records.write({ data: dataText }); + + expect(status.code).to.equal(202); + + // Confirm that the length of the data read as text matches the original input data. + const readDataText = await record!.data.text(); + expect(readDataText.length).to.equal(dataText.length); + + // Ensure the text returned matches the input data, char for char. + expect(readDataText).to.deep.equal(dataText); + }); + + it('returns small data payloads after dwn.records.read()', async () => { + // Generate data that is less than the encoded data limit to ensure that the data will not have to be fetched + // with a RecordsRead when record.data.text() is executed. + const dataText = TestDataGenerator.randomString(500); + + // Write the 500B record to agent-connected DWN. + const { record, status } = await dwn.records.write({ data: dataText }); + + expect(status.code).to.equal(202); + + // Read the record that was just created. + const { record: readRecord, status: readRecordStatus } = await dwn.records.read({ message: { recordId: record!.id }}); + + expect(readRecordStatus.code).to.equal(200); + + // Confirm that the length of the data read as text matches the original input data. + const readDataText = await readRecord!.data.text(); + expect(readDataText.length).to.equal(dataText.length); + + // Ensure the text returned matches the input data, char for char. + expect(readDataText).to.deep.equal(dataText); + }); + + it('returns large data payloads after dwn.records.write()', async () => { + // Generate data that exceeds the DWN encoded data limit to ensure that the data will have to be fetched + // with a RecordsRead when record.data.text() is executed. + const dataText = TestDataGenerator.randomString(11_000); + + // Write the 11KB record to agent-connected DWN. + const { record, status } = await dwn.records.write({ data: dataText }); + + expect(status.code).to.equal(202); + + // Confirm that the length of the data read as text matches the original input data. + const readDataText = await record!.data.text(); + expect(readDataText.length).to.equal(dataText.length); + + // Ensure the text returned matches the input data, char for char. + expect(readDataText).to.deep.equal(dataText); + }); + + it('returns large data payloads after dwn.records.read()', async () => { + // Generate data that exceeds the DWN encoded data limit to ensure that the data will have to be fetched + // with a RecordsRead when record.data.text() is executed. + const dataText = TestDataGenerator.randomString(11_000); + + // Write the 11KB record to agent-connected DWN. + const { record, status } = await dwn.records.write({ data: dataText }); + + expect(status.code).to.equal(202); + + // Read the record that was just created. + const { record: readRecord, status: readRecordStatus } = await dwn.records.read({ message: { recordId: record!.id }}); + + expect(readRecordStatus.code).to.equal(200); + + // Confirm that the length of the data read as text matches the original input data. + const readDataText = await readRecord!.data.text(); + expect(readDataText.length).to.equal(dataText.length); + + // Ensure the text returned matches the input data, char for char. + expect(readDataText).to.deep.equal(dataText); + }); + }); + }); + + describe('record.delete', () => { + it('deletes the record', async () => { + const { status, record } = await dwn.records.write({ + data : 'Hello, world!', + message : { + schema : 'foo/bar', + dataFormat : 'text/plain' + } + }); + + expect(status.code).to.equal(202); + expect(record).to.not.be.undefined; + + const deleteResult = await record!.delete(); + expect(deleteResult.status.code).to.equal(202); + + const queryResult = await dwn.records.query({ + message: { + filter: { + recordId: record!.id + } + } + }); + + expect(queryResult.status.code).to.equal(200); + expect(queryResult.records!.length).to.equal(0); + }); + + it('throws an exception when delete is called twice', async () => { + const { status, record } = await dwn.records.write({ + data : 'Hello, world!', + message : { + schema : 'foo/bar', + dataFormat : 'text/plain' + } + }); + + expect(status.code).to.equal(202); + expect(record).to.not.be.undefined; + + let deleteResult = await record!.delete(); + expect(deleteResult.status.code).to.equal(202); + + await expect(record!.delete()).to.eventually.be.rejectedWith('Operation failed'); + }); + }); + + // describe('send()', () => { + // it(`writes records to remote DWNs for your own DID`, async () => { + // const dataString = 'Hello, world!'; + + // // Alice writes a message to her agent connected DWN. + // const { status: aliceEmailStatus, record: aliceEmailRecord } = await dwn.records.write({ + // data : dataString, + // message : { + // schema: 'email', + // } + // }); + + // expect(aliceEmailStatus.code).to.equal(202); + // expect(await aliceEmailRecord?.data.text()).to.equal(dataString); + + // // Query Alice's agent connected DWN for `email` schema records. + // const aliceAgentQueryResult = await dwn.records.query({ + // message: { + // filter: { + // schema: 'email' + // } + // } + // }); + + // expect(aliceAgentQueryResult.status.code).to.equal(200); + // expect(aliceAgentQueryResult!.records).to.have.length(1); + // const [ aliceAgentEmailRecord ] = aliceAgentQueryResult!.records!; + // expect(await aliceAgentEmailRecord.data.text()).to.equal(dataString); + + // // Attempt to write the record to Alice's remote DWN. + // const { status } = await aliceEmailRecord!.send(didAllKeys); + // expect(status.code).to.equal(202); + + // // Query Alices's remote DWN for `email` schema records. + // const aliceRemoteQueryResult = await dwn.records.query({ + // from : didAllKeys, + // message : { + // filter: { + // schema: 'email' + // } + // } + // }); + + // expect(aliceRemoteQueryResult.status.code).to.equal(200); + // expect(aliceRemoteQueryResult.records).to.exist; + // expect(aliceRemoteQueryResult.records!.length).to.equal(1); + // const [ aliceRemoteEmailRecord ] = aliceAgentQueryResult!.records!; + // expect(await aliceRemoteEmailRecord.data.text()).to.equal(dataString); + // }); + + // it(`writes records to remote DWNs for someone else's DID`, async () => { + // const dataString = 'Hello, world!'; + + // // install a protocol for alice + // let { protocol: aliceProtocol, status: aliceStatus } = await dwn.protocols.configure({ + // message: { + // definition: emailProtocolDefinition + // } + // }); + + // expect(aliceStatus.code).to.equal(202); + // expect(aliceProtocol).to.exist; + + // const { status: alicePushStatus } = await aliceProtocol!.send(didAllKeys); + // expect(alicePushStatus.code).to.equal(202); + + // // install a protocol for bob + // testProfileOptions = await testProfile.ion.with.dwn.service.and.authorization.encryption.attestation.keys(); + // const { did: bobDid } = await testAgent.createProfile(testProfileOptions); + // const bobDwn = new DwnApi(testAgent.agent, bobDid); + + // const { protocol: bobProtocol, status: bobStatus } = await bobDwn.protocols.configure({ + // message: { + // definition: emailProtocolDefinition + // } + // }); + + // expect(bobStatus.code).to.equal(202); + // expect(bobProtocol).to.exist; + + // const { status: bobPushStatus } = await bobProtocol!.send(bobDid); + // expect(bobPushStatus.code).to.equal(202); + + // // alice writes a message to her own dwn + // const { status: aliceEmailStatus, record: aliceEmailRecord } = await dwn.records.write({ + // data : dataString, + // message : { + // protocol : emailProtocolDefinition.protocol, + // protocolPath : 'email', + // schema : 'email', + // } + // }); + + // expect(aliceEmailStatus.code).to.equal(202); + + // const { status } = await aliceEmailRecord!.send(bobDid); + // expect(status.code).to.equal(202); + + // // Query Bob's remote DWN for `email` schema records. + // const bobQueryResult = await bobDwn.records.query({ + // from : bobDid, + // message : { + // filter: { + // schema: 'email' + // } + // } + // }); + + // expect(bobQueryResult.status.code).to.equal(200); + // expect(bobQueryResult.records).to.exist; + // expect(bobQueryResult.records!.length).to.equal(1); + // const [ bobRemoteEmailRecord ] = bobQueryResult!.records!; + // expect(await bobRemoteEmailRecord.data.text()).to.equal(dataString); + // }); + + // describe('with `store: false`', () => { + // it('writes records to your own remote DWN but not your agent DWN', async () => { + // // Alice writes a message to her agent DWN with `store: false`. + // const dataString = 'Hello, world!'; + // const writeResult = await dwn.records.write({ + // store : false, + // data : dataString, + // message : { + // dataFormat: 'text/plain' + // } + // }); + + // // Confirm that the request was accepted and a Record instance was returned. + // expect(writeResult.status.code).to.equal(202); + // expect(writeResult.status.detail).to.equal('Accepted'); + // expect(writeResult.record).to.exist; + // expect(await writeResult.record?.data.text()).to.equal(dataString); + + // // Query Alice's agent DWN for `text/plain` records. + // const queryResult = await dwn.records.query({ + // message: { + // filter: { + // dataFormat: 'text/plain' + // } + // } + // }); + + // // Confirm no `email` schema records were written. + // expect(queryResult.status.code).to.equal(200); + // expect(queryResult.records).to.exist; + // expect(queryResult.records!.length).to.equal(0); + + // // Alice writes the message to her remote DWN. + // const { status } = await writeResult.record!.send(didAllKeys); + // expect(status.code).to.equal(202); + + // // Query Alice's remote DWN for `plain/text` records. + // const aliceRemoteQueryResult = await dwn.records.query({ + // from : didAllKeys, + // message : { + // filter: { + // dataFormat: 'text/plain' + // } + // } + // }); + + // // Confirm `email` schema record was written to Alice's remote DWN. + // expect(aliceRemoteQueryResult.status.code).to.equal(200); + // expect(aliceRemoteQueryResult.records).to.exist; + // expect(aliceRemoteQueryResult.records!.length).to.equal(1); + // const [ aliceRemoteEmailRecord ] = aliceRemoteQueryResult!.records!; + // expect(await aliceRemoteEmailRecord.data.text()).to.equal(dataString); + // }); + + // it(`writes records to someone else's remote DWN but not your agent DWN`, async () => { + // // Install a protocol on Alice's agent connected DWN. + // let { protocol: aliceProtocol, status: aliceStatus } = await dwn.protocols.configure({ + // message: { + // definition: emailProtocolDefinition + // } + // }); + + // expect(aliceStatus.code).to.equal(202); + // expect(aliceProtocol).to.exist; + + // // Install the protocol on Alice's remote DWN. + // const { status: alicePushStatus } = await aliceProtocol!.send(didAllKeys); + // expect(alicePushStatus.code).to.equal(202); + + // // Create a second profile for Bob. + // testProfileOptions = await testProfile.ion.with.dwn.service.and.authorization.encryption.attestation.keys(); + // const { did: bobDid } = await testAgent.createProfile(testProfileOptions); + // const bobDwn = new DwnApi(testAgent.agent, bobDid); + + // // Install a protocol on Bob's agent connected DWN. + // const { protocol: bobProtocol, status: bobStatus } = await bobDwn.protocols.configure({ + // message: { + // definition: emailProtocolDefinition + // } + // }); + + // expect(bobStatus.code).to.equal(202); + // expect(bobProtocol).to.exist; + + // // Install the protocol on Bob's remote DWN. + // const { status: bobPushStatus } = await bobProtocol!.send(bobDid); + // expect(bobPushStatus.code).to.equal(202); + + // // Alice writes a message to her agent DWN with `store: false`. + // const dataString = 'Hello, world!'; + // const writeResult = await dwn.records.write({ + // store : false, + // data : dataString, + // message : { + // protocol : emailProtocolDefinition.protocol, + // protocolPath : 'email', + // schema : 'email', + // } + // }); + + // // Confirm that the request was accepted and a Record instance was returned. + // expect(writeResult.status.code).to.equal(202); + // expect(writeResult.status.detail).to.equal('Accepted'); + // expect(writeResult.record).to.exist; + // expect(await writeResult.record?.data.text()).to.equal(dataString); + + // // Query Alice's agent DWN for `email` schema records. + // const queryResult = await dwn.records.query({ + // message: { + // filter: { + // schema: 'email' + // } + // } + // }); + + // // Confirm no `email` schema records were written. + // expect(queryResult.status.code).to.equal(200); + // expect(queryResult.records).to.exist; + // expect(queryResult.records!.length).to.equal(0); + + // // Alice writes the message to Bob's remote DWN. + // const { status } = await writeResult.record!.send(bobDid); + // expect(status.code).to.equal(202); + + // // Query Bobs's remote DWN for `email` schema records. + // const bobQueryResult = await bobDwn.records.query({ + // from : bobDid, + // message : { + // filter: { + // dataFormat: 'text/plain' + // } + // } + // }); + + // // Confirm `email` schema record was written to Bob's remote DWN. + // expect(bobQueryResult.status.code).to.equal(200); + // expect(bobQueryResult.records).to.exist; + // expect(bobQueryResult.records!.length).to.equal(1); + // const [ bobRemoteEmailRecord ] = bobQueryResult!.records!; + // expect(await bobRemoteEmailRecord.data.text()).to.equal(dataString); + // }); + + // it('has no effect if `store: true`', async () => { + // // Alice writes a message to her agent DWN with `store: true`. + // const dataString = 'Hello, world!'; + // const writeResult = await dwn.records.write({ + // store : true, + // data : dataString, + // message : { + // dataFormat: 'text/plain' + // } + // }); + + // // Confirm that the request was accepted and a Record instance was returned. + // expect(writeResult.status.code).to.equal(202); + // expect(writeResult.status.detail).to.equal('Accepted'); + // expect(writeResult.record).to.exist; + // expect(await writeResult.record?.data.text()).to.equal(dataString); + + // // Query Alice's agent DWN for `text/plain` records. + // const queryResult = await dwn.records.query({ + // message: { + // filter: { + // dataFormat: 'text/plain' + // } + // } + // }); + + // // Confirm the `email` schema records was written. + // expect(queryResult.status.code).to.equal(200); + // expect(queryResult.records).to.exist; + // expect(queryResult.records!.length).to.equal(1); + // const [ aliceAgentRecord ] = queryResult!.records!; + // expect(await aliceAgentRecord.data.text()).to.equal(dataString); + + // // Alice writes the message to her remote DWN. + // const { status } = await writeResult.record!.send(didAllKeys); + // expect(status.code).to.equal(202); + + // // Query Alice's remote DWN for `plain/text` records. + // const aliceRemoteQueryResult = await dwn.records.query({ + // from : didAllKeys, + // message : { + // filter: { + // dataFormat: 'text/plain' + // } + // } + // }); + + // // Confirm `email` schema record was written to Alice's remote DWN. + // expect(aliceRemoteQueryResult.status.code).to.equal(200); + // expect(aliceRemoteQueryResult.records).to.exist; + // expect(aliceRemoteQueryResult.records!.length).to.equal(1); + // const [ aliceRemoteEmailRecord ] = aliceRemoteQueryResult!.records!; + // expect(await aliceRemoteEmailRecord.data.text()).to.equal(dataString); + // }); + // }); + // }); + + // describe('toJSON()', () => { + // it('should return all defined properties', async () => { + // // RecordOptions properties + // const author = didAllKeys; + // const target = didAllKeys; + + // const profile = await testAgent.profileApi.getProfile(didAllKeys); + + // // Retrieve `#dwn` service entry. + // const [ didDwnService ] = didUtils.getServices(profile!.did.didDocument as DidDocument, { id: '#dwn' }); + + // // Retrieve first record encryption key from the #dwn serviceEndpoint. + // const recordEncryptionKeyId = typeof didDwnService!.serviceEndpoint !== 'string' ? didDwnService!.serviceEndpoint!.encryptionKeys![0] : undefined; + // const encryptionKeyId = `${profile?.did.id}${recordEncryptionKeyId}`; + // const encryptionPublicKeyJwk = profile!.did.keys.find(key => key.id === encryptionKeyId)!.publicKeyJwk; + + // // Retrieve first message authorization key from the #dwn serviceEndpoint. + // const messageAuthorizationKeyId = typeof didDwnService!.serviceEndpoint !== 'string' ? didDwnService!.serviceEndpoint!.messageAuthorizationKeys![0] : undefined; + // const authorizationKeyId = `${profile?.did.id}${messageAuthorizationKeyId}`; + // const authorizationPrivateKeyJwk = profile!.did.keys.find(key => key.id === authorizationKeyId)!.privateKeyJwk; + + // // Retrieve first message attestation key from the #dwn serviceEndpoint. + // const messageAttestationKeyId = typeof didDwnService!.serviceEndpoint !== 'string' ? didDwnService!.serviceEndpoint!.messageAttestationKeys![0] : undefined; + // const attestationKeyId = `${profile?.did.id}${messageAttestationKeyId}`; + // const attestationPrivateKeyJwk = profile!.did.keys.find(key => key.id === attestationKeyId)!.privateKeyJwk; + + // // RecordsWriteMessage properties that can be pre-defined + // const attestation = [{ + // privateJwk : attestationPrivateKeyJwk as DwnPrivateKeyJwk, + // protectedHeader : { + // alg : attestationPrivateKeyJwk.alg as string, + // kid : attestationKeyId + // } + // }]; + + // const authorization = { + // privateJwk : authorizationPrivateKeyJwk as DwnPrivateKeyJwk, + // protectedHeader : { + // alg : authorizationPrivateKeyJwk.alg as string, + // kid : authorizationKeyId + // } + // }; + + // const encryptionInput = { + // initializationVector : TestDataGenerator.randomBytes(16), + // key : TestDataGenerator.randomBytes(32), + // keyEncryptionInputs : [ + // { + // derivationScheme : KeyDerivationScheme.ProtocolPath, + // publicKey : encryptionPublicKeyJwk as DwnPublicKeyJwk, + // publicKeyId : recordEncryptionKeyId + // }, + // { + // derivationScheme : KeyDerivationScheme.Schemas, + // publicKey : encryptionPublicKeyJwk as DwnPublicKeyJwk, + // publicKeyId : recordEncryptionKeyId + // }, + // ] + // }; + + // // RecordsWriteDescriptor properties that can be pre-defined + // const protocol = 'http://example.org/chat/protocol'; + // const protocolPath = 'message'; + // const recipient = didAllKeys; + // const published = true; + // const schema = 'http://example.org/chat/schema/message'; + + // // Create a parent record to reference in the RecordsWriteMessage used for validation + // const parentRecorsWrite = await RecordsWrite.create({ + // protocol, + // protocolPath, + // schema, + // data : new Uint8Array(await dataBlob.arrayBuffer()), + // dataFormat, + // authorizationSignatureInput : authorization, + // }) as RecordsWriteTest; + + // // Create a RecordsWriteMessage + // const recordsWrite = await RecordsWrite.create({ + // protocol, + // protocolPath, + // recipient, + // schema, + // parentId : parentRecorsWrite.recordId, + // data : new Uint8Array(await dataBlob.arrayBuffer()), + // published, + // dataFormat, + // attestationSignatureInputs : attestation, + // authorizationSignatureInput : authorization, + // encryptionInput, + // }) as RecordsWriteTest; + + // // Create record using test RecordsWriteMessage. + // const record = new Record(testAgent.agent, { + // ...recordsWrite.message, + // encodedData: dataBlob, + // target, + // author, + // }); + + // // Call toJSON() method. + // const recordJson = record.toJSON(); + + // // Retained Record properties. + // expect(recordJson.author).to.equal(author); + // expect(recordJson.target).to.equal(target); + + // // Retained RecordsWriteMessage top-level properties. + // expect(record.contextId).to.equal(recordsWrite.message.contextId); + // expect(record.id).to.equal(recordsWrite.message.recordId); + // expect(record.encryption).to.not.be.undefined; + // expect(record.encryption).to.deep.equal(recordsWrite.message.encryption); + // expect(record.encryption!.keyEncryption.find(key => key.derivationScheme === KeyDerivationScheme.ProtocolPath)); + // expect(record.encryption!.keyEncryption.find(key => key.derivationScheme === KeyDerivationScheme.Schemas)); + // expect(record.attestation).to.not.be.undefined; + // expect(record.attestation).to.have.property('signatures'); + + // // Retained RecordsWriteDescriptor properties. + // expect(recordJson.interface).to.equal(DwnInterfaceName.Records); + // expect(recordJson.method).to.equal(DwnMethodName.Write); + // expect(recordJson.protocol).to.equal(protocol); + // expect(recordJson.protocolPath).to.equal(protocolPath); + // expect(recordJson.recipient).to.equal(recipient); + // expect(recordJson.schema).to.equal(schema); + // expect(recordJson.parentId).to.equal(parentRecorsWrite.recordId); + // expect(recordJson.dataCid).to.equal(recordsWrite.message.descriptor.dataCid); + // expect(recordJson.dataSize).to.equal(recordsWrite.message.descriptor.dataSize); + // expect(recordJson.dateCreated).to.equal(recordsWrite.message.descriptor.dateCreated); + // expect(recordJson.dateModified).to.equal(recordsWrite.message.descriptor.messageTimestamp); + // expect(recordJson.published).to.equal(published); + // expect(recordJson.datePublished).to.equal(recordsWrite.message.descriptor.datePublished); + // expect(recordJson.dataFormat).to.equal(dataFormat); + // }); + // }); +}); \ No newline at end of file diff --git a/packages/api/tests/tech-preview.spec.ts b/packages/api/tests/tech-preview.spec.ts new file mode 100644 index 000000000..bb9d20206 --- /dev/null +++ b/packages/api/tests/tech-preview.spec.ts @@ -0,0 +1,162 @@ +import sinon from 'sinon'; +import chai, { expect } from 'chai'; + +import { chaiUrl } from './utils/chai-plugins.js'; +import { getTechPreviewDwnEndpoints } from '../src/tech-preview.js'; + +chai.use(chaiUrl); + +describe('Tech Preview', () => { + describe('getTechPreviewDwnEndpoints()', () => { + let fetchStub: sinon.SinonStub; + let mockDwnEndpoints: Array; + + let tbdWellKnownOkResponse = { + status : 200, + statusText : 'OK', + ok : true, + json : async () => Promise.resolve({ + id : 'did:web:dwn.tbddev.org', + service : [ + { + id : '#dwn', + serviceEndpoint : { + nodes: mockDwnEndpoints + }, + type: 'DecentralizedWebNode' + } + ] + }) + }; + + let tbdWellKnownBadResponse = { + status : 400, + statusText : 'Bad Request', + ok : false + }; + + let dwnServerHealthOkResponse = { + status : 200, + statusText : 'OK', + ok : true, + json : async () => Promise.resolve({ok: true}) + }; + + let dwnServerHealthBadResponse = { + status : 400, + statusText : 'Bad Request', + ok : false + }; + + beforeEach(() => { + mockDwnEndpoints = [ + 'https://dwn.tbddev.test/dwn0', + 'https://dwn.tbddev.test/dwn1', + 'https://dwn.tbddev.test/dwn2', + 'https://dwn.tbddev.test/dwn3', + 'https://dwn.tbddev.test/dwn4', + 'https://dwn.tbddev.test/dwn5', + 'https://dwn.tbddev.test/dwn6' + ]; + + fetchStub = sinon.stub(globalThis as any, 'fetch'); + + fetchStub.callsFake((url) => { + if (url === 'https://dwn.tbddev.org/.well-known/did.json') { + return Promise.resolve(tbdWellKnownOkResponse); + } else if (url.endsWith('/health')) { + return Promise.resolve(dwnServerHealthOkResponse); + } + }); + }); + + afterEach(() => { + fetchStub.restore(); + }); + + it('returns an array', async () => { + const dwnEndpoints = await getTechPreviewDwnEndpoints(); + + expect(dwnEndpoints).to.be.an('array'); + }); + + it('returns valid DWN endpoint URLs', async () => { + const dwnEndpoints = await getTechPreviewDwnEndpoints(); + + // There must be at one URL to check or else this test always passes. + expect(dwnEndpoints).to.have.length.greaterThan(0); + + dwnEndpoints.forEach(endpoint => { + expect(endpoint).to.be.a.url; + expect(mockDwnEndpoints).to.include(endpoint); + }); + }); + + it('returns 2 DWN endpoints if at least 2 are healthy', async function() { + const promises = Array(50).fill(0).map(() => getTechPreviewDwnEndpoints()); + + const results = await Promise.all(promises); + + results.forEach(result => { + expect(result).to.be.an('array').that.has.lengthOf(2); + }); + }); + + it('returns 1 DWN endpoints if only 1 is healthy', async function() { + mockDwnEndpoints = [ + 'https://dwn.tbddev.test/dwn0' + ]; + + const promises = Array(50).fill(0).map(() => getTechPreviewDwnEndpoints()); + + const results = await Promise.all(promises); + + results.forEach(result => { + expect(result).to.be.an('array').that.has.lengthOf(1); + }); + }); + + it('returns 0 DWN endpoints if none are healthy', async function() { + // Stub fetch to simulate dwn.tbddev.org responding but all of the hosted DWN Server reporting not healthy. + fetchStub.restore(); + fetchStub = sinon.stub(globalThis as any, 'fetch'); + fetchStub.callsFake((url) => { + if (url === 'https://dwn.tbddev.org/.well-known/did.json') { + return Promise.resolve(tbdWellKnownOkResponse); + } else if (url.endsWith('/health')) { + return Promise.resolve(dwnServerHealthBadResponse); + } + }); + + const dwnEndpoints = await getTechPreviewDwnEndpoints(); + + expect(dwnEndpoints).to.be.an('array').that.has.lengthOf(0); + }); + + it('returns 0 DWN endpoints if dwn.tbddev.org is not responding', async function() { + // Stub fetch to simulate dwn.tbddev.org responding but all of the hosted DWN Server reporting not healthy. + fetchStub.restore(); + fetchStub = sinon.stub(globalThis as any, 'fetch'); + fetchStub.callsFake((url) => { + if (url === 'https://dwn.tbddev.org/.well-known/did.json') { + return Promise.resolve(tbdWellKnownBadResponse); + } + }); + + const dwnEndpoints = await getTechPreviewDwnEndpoints(); + + expect(dwnEndpoints).to.be.an('array').that.has.lengthOf(0); + }); + + it('returns 0 DWN endpoints if fetching dwn.tbddev.org throws an exception', async function() { + // Stub fetch to simulate fetching dwn.tbddev.org throwing an exception. + fetchStub.restore(); + fetchStub = sinon.stub(globalThis as any, 'fetch'); + fetchStub.withArgs('https://dwn.tbddev.org/.well-known/did.json').rejects(new Error('Network error')); + + const dwnEndpoints = await getTechPreviewDwnEndpoints(); + + expect(dwnEndpoints).to.be.an('array').that.has.lengthOf(0); + }); + }); +}); \ No newline at end of file diff --git a/packages/web5-proxy-agent/tests/tsconfig.json b/packages/api/tests/tsconfig.json similarity index 100% rename from packages/web5-proxy-agent/tests/tsconfig.json rename to packages/api/tests/tsconfig.json diff --git a/packages/api/tests/utils.spec.ts b/packages/api/tests/utils.spec.ts new file mode 100644 index 000000000..b2bfb1811 --- /dev/null +++ b/packages/api/tests/utils.spec.ts @@ -0,0 +1,58 @@ +import { expect } from 'chai'; + +import { dataToBlob } from '../src/utils.js'; + +describe('Web5 API Utils', () => { + describe('dataToBlob()', () => { + it('should handle text data with explicit format', async () => { + const result = dataToBlob('Hello World', 'text/plain'); + expect(result.dataBlob.type).to.equal('text/plain'); + expect(result.dataFormat).to.equal('text/plain'); + const output = await result.dataBlob.text(); + expect(output).to.equal('Hello World'); + }); + + it('should handle text data with detected type', async () => { + const result = dataToBlob('Hello World'); + expect(result.dataBlob.type).to.equal('text/plain'); + expect(result.dataFormat).to.equal('text/plain'); + const output = await result.dataBlob.text(); + expect(output).to.equal('Hello World'); + }); + + it('should handle JSON data with explicit format', async () => { + const result = dataToBlob({ key: 'value' }, 'application/json'); + expect(result.dataBlob.type).to.equal('application/json'); + expect(result.dataFormat).to.equal('application/json'); + }); + + it('should handle JSON data with detected type', () => { + const result = dataToBlob({ key: 'value' }); + expect(result.dataBlob.type).to.equal('application/json'); + expect(result.dataFormat).to.equal('application/json'); + }); + + it('should handle Uint8Array data', () => { + const result = dataToBlob(new Uint8Array([1, 2, 3])); + expect(result.dataBlob.type).to.equal('application/octet-stream'); + expect(result.dataFormat).to.equal('application/octet-stream'); + }); + + it('should handle ArrayBuffer data', () => { + const result = dataToBlob(new ArrayBuffer(3)); + expect(result.dataBlob.type).to.equal('application/octet-stream'); + expect(result.dataFormat).to.equal('application/octet-stream'); + }); + + it('should handle Blob data', () => { + const blob = new Blob(['data'], { type: 'custom/type' }); + const result = dataToBlob(blob); + expect(result.dataBlob.type).to.equal('custom/type'); + expect(result.dataFormat).to.equal('custom/type'); + }); + + it('should throw an error for unsupported data types', () => { + expect(() => dataToBlob(42)).to.throw('data type not supported.'); + }); + }); +}); \ No newline at end of file diff --git a/packages/web5/tests/test-utils/chai-plugins.ts b/packages/api/tests/utils/chai-plugins.ts similarity index 100% rename from packages/web5/tests/test-utils/chai-plugins.ts rename to packages/api/tests/utils/chai-plugins.ts diff --git a/packages/api/tests/utils/test-data-generator.ts b/packages/api/tests/utils/test-data-generator.ts new file mode 100644 index 000000000..3d65e4002 --- /dev/null +++ b/packages/api/tests/utils/test-data-generator.ts @@ -0,0 +1,71 @@ +import { randomBytes } from '@web5/crypto'; + +export class TestDataGenerator { + /** + * Generates a random byte array of given length. + */ + static randomBytes(length: number): Uint8Array { + return randomBytes(length); + } + + /** + * Generate a random number of given length. + */ + static randomDigits(length: number): number { + let result = 0; + for(let i = 0; i < length; i++) { + result = result * 10 + Math.floor(Math.random() * 10); + } + return result; + } + + /** + * Generates a random JavaScript object of given length in bytes. + */ + static randomJson(length: number): object { + // Start with an empty object. + let obj = {}; + + // Generate properties until the JSON string is close to the desired length. + let lessThanDesiredLength = true; + while (lessThanDesiredLength) { + // Use a random key and value to avoid any optimization that might occur + // from using the same key and value repeatedly. + let key = Math.random().toString(36).substring(2); + let value = Math.random().toString(36).substring(2); + + // Calculate the size of the new property (including quotes and colon). + let propertySize = key.length + value.length + 4; + + // If the new property fits within the desired size, add it to the object. + const currentLength = JSON.stringify(obj).length; + + if (currentLength + propertySize <= length) { + obj[key] = value; + } else if (length - currentLength > 5) { + const padLength = length - currentLength - 4; + obj[0] = TestDataGenerator.randomDigits(padLength); + lessThanDesiredLength = false; + } else { + lessThanDesiredLength = false; + } + } + + return obj; + } + + /** + * Generates a random alpha-numeric string of given length. + */ + static randomString(length: number): string { + const charset = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; + + // Select characters randomly. + let randomChars: string[] = []; + for (let i = 0; i < length; i++) { + randomChars.push(charset.charAt(Math.floor(Math.random() * charset.length))); + } + + return randomChars.join(''); + } +} \ No newline at end of file diff --git a/packages/api/tests/utils/test-user-agent.ts b/packages/api/tests/utils/test-user-agent.ts new file mode 100644 index 000000000..9caf5d8c2 --- /dev/null +++ b/packages/api/tests/utils/test-user-agent.ts @@ -0,0 +1,196 @@ +import type { + DwnRpc, + VcResponse, + DidResponse, + DwnResponse, + AppDataStore, + SendVcRequest, + SendDidRequest, + SendDwnRequest, + ProcessVcRequest, + Web5ManagedAgent, + ProcessDidRequest, + ProcessDwnRequest, +} from '@web5/agent'; + +import { Dwn } from '@tbd54566975/dwn-sdk-js'; +import { + DidResolver, + DidKeyMethod, +} from '@web5/dids'; +import { + EventLogLevel, + DataStoreLevel, + MessageStoreLevel, +} from '@tbd54566975/dwn-sdk-js/stores'; +import { + LocalKms, + DidManager, + DwnManager, + KeyManager, + AppDataVault, + Web5RpcClient, + IdentityManager, +} from '@web5/agent'; + +type CreateMethodOptions = { + testDataLocation?: string; +} + +type TestUserAgentOptions = { + appData: AppDataStore; + didManager: DidManager; + didResolver: DidResolver; + dwnManager: DwnManager; + identityManager: IdentityManager; + keyManager: KeyManager; + rpcClient: DwnRpc; + + dwn: Dwn; + dwnDataStore: DataStoreLevel; + dwnEventLog: EventLogLevel; + dwnMessageStore: MessageStoreLevel; +} + +export class TestUserAgent implements Web5ManagedAgent { + agentDid: string | undefined; + appData: AppDataStore; + didManager: DidManager; + didResolver: DidResolver; + dwnManager: DwnManager; + identityManager: IdentityManager; + keyManager: KeyManager; + rpcClient: DwnRpc; + + /** + * DWN-related properties. + */ + dwn: Dwn; + dwnDataStore: DataStoreLevel; + dwnEventLog: EventLogLevel; + dwnMessageStore: MessageStoreLevel; + + constructor(options: TestUserAgentOptions) { + this.appData = options.appData; + this.didManager = options.didManager; + this.didResolver = options.didResolver; + this.dwnManager = options.dwnManager; + this.identityManager = options.identityManager; + this.keyManager = options.keyManager; + this.rpcClient = options.rpcClient; + + // Set this agent to be the default agent for each component. + this.didManager.agent = this; + this.dwnManager.agent = this; + this.identityManager.agent = this; + this.keyManager.agent = this; + + // TestUserAgent-specific properties. + this.dwn = options.dwn; + this.dwnDataStore = options.dwnDataStore; + this.dwnEventLog = options.dwnEventLog; + this.dwnMessageStore = options.dwnMessageStore; + } + + async clearStorage(): Promise { + this.agentDid = undefined; + await this.dwnDataStore.clear(); + await this.dwnEventLog.clear(); + await this.dwnMessageStore.clear(); + } + + async closeStorage(): Promise { + await this.dwnDataStore.close(); + await this.dwnEventLog.close(); + await this.dwnMessageStore.close(); + } + + static async create(options: CreateMethodOptions = {}): Promise { + const testDataLocation = options.testDataLocation ?? '__TESTDATA__'; + const testDataPath = (path: string) => `${testDataLocation}/${path}`; + + // Instantiate custom stores to use with DWN instance. + const dwnDataStore = new DataStoreLevel({ blockstoreLocation: testDataPath('DATASTORE') }); + const dwnEventLog = new EventLogLevel({ location: testDataPath('EVENTLOG') }); + const dwnMessageStore = new MessageStoreLevel({ + blockstoreLocation : testDataPath('MESSAGESTORE'), + indexLocation : testDataPath('INDEX') + }); + + // Instantiate components with default in-memory stores. + const appData = new AppDataVault({ keyDerivationWorkFactor: 1 }); + const didManager = new DidManager({ didMethods: [DidKeyMethod] }); + const identityManager = new IdentityManager(); + const kms = { + memory: new LocalKms({ kmsName: 'memory' }) + }; + const keyManager = new KeyManager({ kms }); + + // Instantiate DID resolver. + const didMethodApis = [DidKeyMethod]; + const didResolver = new DidResolver({ didResolvers: didMethodApis }); + + // Instantiate custom DWN instance. + const dwn = await Dwn.create({ + eventLog : dwnEventLog, + dataStore : dwnDataStore, + messageStore : dwnMessageStore + }); + + // Instantiate a DwnManager using the custom DWN instance. + const dwnManager = new DwnManager({ dwn }); + + // Instantiate an RPC Client. + const rpcClient = new Web5RpcClient(); + + return new TestUserAgent({ + appData, + didManager, + didResolver, + dwn, + dwnDataStore, + dwnEventLog, + dwnMessageStore, + dwnManager, + identityManager, + keyManager, + rpcClient, + }); + } + + async firstLaunch(): Promise { + throw new Error('Not implemented'); + } + + async initialize(_options: { passphrase: string; }): Promise { + throw new Error('Not implemented'); + } + + async processDidRequest(_request: ProcessDidRequest): Promise { + throw new Error('Not implemented'); + } + + async processDwnRequest(request: ProcessDwnRequest): Promise { + return this.dwnManager.processRequest(request); + } + + async processVcRequest(_request: ProcessVcRequest): Promise { + throw new Error('Not implemented'); + } + + async sendDidRequest(_request: SendDidRequest): Promise { + throw new Error('Not implemented'); + } + + async sendDwnRequest(request: SendDwnRequest): Promise { + return this.dwnManager.sendRequest(request); + } + + async sendVcRequest(_request: SendVcRequest): Promise { + throw new Error('Not implemented'); + } + + async start(_options: { passphrase: string; }): Promise { + throw new Error('Not implemented'); + } +} \ No newline at end of file diff --git a/packages/api/tests/web5-did.spec.ts b/packages/api/tests/web5-did.spec.ts new file mode 100644 index 000000000..a86fb090b --- /dev/null +++ b/packages/api/tests/web5-did.spec.ts @@ -0,0 +1,38 @@ +import { TestManagedAgent } from '@web5/agent'; + +import { DidApi } from '../src/did-api.js'; +import { TestUserAgent } from './utils/test-user-agent.js'; + +describe('web5.did', () => { + let did: DidApi; + let testAgent: TestManagedAgent; + + before(async () => { + testAgent = await TestManagedAgent.create({ + agentClass : TestUserAgent, + agentStores : 'memory' + }); + }); + + beforeEach(async () => { + await testAgent.clearStorage(); + + // Create an Agent DID. + await testAgent.createAgentDid(); + + // Create a new Identity to author DWN messages. + const identity = await testAgent.agent.identityManager.create({ + name : 'Test', + didMethod : 'key', + kms : 'local' + }); + + // Instantiate DwnApi. + did = new DidApi({ agent: testAgent.agent, connectedDid: identity.did }); + }); + + after(async () => { + await testAgent.clearStorage(); + await testAgent.closeStorage(); + }); +}); \ No newline at end of file diff --git a/packages/api/tests/web5-dwn.spec.ts b/packages/api/tests/web5-dwn.spec.ts new file mode 100644 index 000000000..3e75961ee --- /dev/null +++ b/packages/api/tests/web5-dwn.spec.ts @@ -0,0 +1,418 @@ +import { expect } from 'chai'; +import { TestManagedAgent } from '@web5/agent'; + +import { DwnApi } from '../src/dwn-api.js'; +import { TestUserAgent } from './utils/test-user-agent.js'; +import emailProtocolDefinition from './fixtures/protocol-definitions/email.json' assert { type: 'json' }; + +// let dwnNodes: string[] = ['https://dwn.tbddev.org/dwn0']; +let dwnNodes: string[] = ['http://localhost:3000']; + +describe('web5.dwn', () => { + let dwn: DwnApi; + let testAgent: TestManagedAgent; + + before(async () => { + testAgent = await TestManagedAgent.create({ + agentClass : TestUserAgent, + agentStores : 'memory' + }); + }); + + beforeEach(async () => { + await testAgent.clearStorage(); + + // Create an Agent DID. + await testAgent.createAgentDid(); + + const services = [{ + id : 'dwn', + type : 'DecentralizedWebNode', + serviceEndpoint : { + encryptionKeys : ['#dwn-enc'], + nodes : dwnNodes, + signingKeys : ['#dwn-sig'] + } + }]; + + // Creates a new Identity to author the DWN messages. + const identity = await testAgent.agent.identityManager.create({ + name : 'Alice', + didMethod : 'ion', + didOptions : { services }, + kms : 'local' + }); + // Create a new Identity to author DWN messages. + // const identity = await testAgent.agent.identityManager.create({ + // name : 'Test', + // didMethod : 'ion', + // kms : 'local' + // }); + + // Instantiate DwnApi. + dwn = new DwnApi({ agent: testAgent.agent, connectedDid: identity.did }); + }); + + after(async () => { + await testAgent.clearStorage(); + await testAgent.closeStorage(); + }); + + describe('protocols', () => { + describe('configure', () => { + describe('agent', () => { + it('writes a protocol definition', async () => { + const response = await dwn.protocols.configure({ + message: { + definition: emailProtocolDefinition + } + }); + + expect(response.status.code).to.equal(202); + expect(response.status.detail).to.equal('Accepted'); + }); + }); + + describe('from: did', () => { + xit('test needed'); + }); + }); + + describe('query', () => { + describe('agent', () => { + it('should return protocols matching the query', async () => { + // Write a protocols configure to the connected agent's DWN. + const configureResponse = await dwn.protocols.configure({ + message: { + definition: emailProtocolDefinition + } + }); + expect(configureResponse.status.code).to.equal(202); + expect(configureResponse.status.detail).to.equal('Accepted'); + + // Query for the protocol just configured. + const queryResponse = await dwn.protocols.query({ + message: { + filter: { + protocol: emailProtocolDefinition.protocol + } + } + }); + + expect(queryResponse.status.code).to.equal(200); + expect(queryResponse.protocols.length).to.equal(1); + expect(queryResponse.protocols[0].definition).to.have.property('types'); + expect(queryResponse.protocols[0].definition).to.have.property('protocol'); + expect(queryResponse.protocols[0].definition.protocol).to.equal(emailProtocolDefinition.protocol); + expect(queryResponse.protocols[0].definition).to.have.property('structure'); + }); + }); + + describe('from: did', () => { + xit('returns empty protocols array when no protocols match the filter provided', async () => { + // // Create a new DID to represent an external entity who has a remote DWN server defined in their DID document. + // const ionCreateOptions = await testProfile.ionCreateOptions.services.dwn.authorization.keys(); + // const { id: bobDid } = await testAgent.didIon.create(ionCreateOptions); + + // // Query for the protocol just configured. + // const response = await dwn.protocols.query({ + // from : bobDid, + // message : { + // filter: { + // protocol: 'https://doesnotexist.com/protocol' + // } + // } + // }); + + // expect(response.status.code).to.equal(200); + // expect(response.protocols).to.exist; + // expect(response.protocols.length).to.equal(0); + }); + }); + }); + }); + + describe('records', () => { + describe('write', () => { + describe('agent', () => { + it('writes a record with string data', async () => { + const dataString = 'Hello, world!Hello, world!'; + const result = await dwn.records.write({ + data : dataString, + message : { + schema : 'foo/bar', + dataFormat : 'text/plain' + } + }); + + expect(result.status.code).to.equal(202); + expect(result.status.detail).to.equal('Accepted'); + expect(result.record).to.exist; + expect(await result.record?.data.text()).to.equal(dataString); + }); + + it('writes a record with JSON data', async () => { + const dataJson = { hello: 'world!'}; + const result = await dwn.records.write({ + data : dataJson, + message : { + schema : 'foo/bar', + dataFormat : 'application/json' + } + }); + + expect(result.status.code).to.equal(202); + expect(result.status.detail).to.equal('Accepted'); + expect(result.record).to.exist; + expect(await result.record?.data.json()).to.deep.equal(dataJson); + }); + }); + + describe('agent store: false', () => { + it('does not persist record to agent DWN', async () => { + const dataString = 'Hello, world!'; + const writeResult = await dwn.records.write({ + store : false, + data : dataString, + message : { + schema : 'foo/bar', + dataFormat : 'text/plain' + } + }); + + expect(writeResult.status.code).to.equal(202); + expect(writeResult.status.detail).to.equal('Accepted'); + expect(writeResult.record).to.exist; + expect(await writeResult.record?.data.text()).to.equal(dataString); + + const queryResult = await dwn.records.query({ + message: { + filter: { + schema: 'foo/bar' + } + } + }); + + expect(queryResult.status.code).to.equal(200); + expect(queryResult.records).to.exist; + expect(queryResult.records!.length).to.equal(0); + }); + + it('has no effect if `store: true`', async () => { + const dataString = 'Hello, world!'; + const writeResult = await dwn.records.write({ + store : true, + data : dataString, + message : { + schema : 'foo/bar', + dataFormat : 'text/plain' + } + }); + + expect(writeResult.status.code).to.equal(202); + expect(writeResult.status.detail).to.equal('Accepted'); + expect(writeResult.record).to.exist; + expect(await writeResult.record?.data.text()).to.equal(dataString); + + const queryResult = await dwn.records.query({ + message: { + filter: { + schema: 'foo/bar' + } + } + }); + + expect(queryResult.status.code).to.equal(200); + expect(queryResult.records).to.exist; + expect(queryResult.records!.length).to.equal(1); + expect(queryResult.records![0].id).to.equal(writeResult.record!.id); + expect(await queryResult.records![0].data.text()).to.equal(dataString); + }); + }); + }); + + describe('query', () => { + describe('agent', () => { + it('returns an array of records that match the filter provided', async () => { + const writeResult = await dwn.records.write({ + data : 'Hello, world!', + message : { + schema : 'foo/bar', + dataFormat : 'text/plain' + } + }); + + expect(writeResult.status.code).to.equal(202); + expect(writeResult.status.detail).to.equal('Accepted'); + expect(writeResult.record).to.exist; + + const result = await dwn.records.query({ + message: { + filter: { + schema: 'foo/bar' + } + } + }); + + expect(result.status.code).to.equal(200); + expect(result.records).to.exist; + expect(result.records!.length).to.equal(1); + expect(result.records![0].id).to.equal(writeResult.record!.id); + }); + }); + + describe('from: did', () => { + xit('returns empty records array when no records match the filter provided', async () => { + // // Create a new DID to represent an external entity who has a remote DWN server defined in their DID document. + // const ionCreateOptions = await testProfile.ionCreateOptions.services.dwn.authorization.keys(); + // const { id: bobDid } = await testAgent.didIon.create(ionCreateOptions); + + // // Attempt to query Bob's DWN using the ID of a record that does not exist. + // const result = await dwn.records.query({ + // from : bobDid, + // message : { + // filter: { + // recordId: 'abcd1234' + // } + // } + // }); + + // // Confirm that the record does not currently exist on Bob's DWN. + // expect(result.status.code).to.equal(200); + // expect(result.records).to.exist; + // expect(result.records!.length).to.equal(0); + }); + }); + }); + + describe('read', () => { + describe('agent', () => { + it('returns a record', async () => { + const writeResult = await dwn.records.write({ + data : 'Hello, world!', + message : { + schema : 'foo/bar', + dataFormat : 'text/plain' + } + }); + + expect(writeResult.status.code).to.equal(202); + expect(writeResult.status.detail).to.equal('Accepted'); + expect(writeResult.record).to.exist; + + const result = await dwn.records.read({ + message: { + recordId: writeResult.record!.id + } + }); + + expect(result.status.code).to.equal(200); + expect(result.record.id).to.equal(writeResult.record!.id); + }); + + it('returns a 404 when a record cannot be found', async () => { + const writeResult = await dwn.records.write({ + data : 'Hello, world!', + message : { + schema : 'foo/bar', + dataFormat : 'text/plain' + } + }); + + expect(writeResult.status.code).to.equal(202); + expect(writeResult.status.detail).to.equal('Accepted'); + expect(writeResult.record).to.exist; + + await writeResult.record!.delete(); + + const result = await dwn.records.read({ + message: { + recordId: writeResult.record!.id + } + }); + + expect(result.status.code).to.equal(404); + expect(result.record).to.not.exist; + }); + }); + + describe('from: did', () => { + xit('returns undefined record when requested record does not exit', async () => { + // // Generate a recordId that will not be present on the did endpoint being read from. + // const { record, status } = await dwn.records.write({ data: 'hi' }); + // expect(status.code).to.equal(202); + + // // Create a new DID to represent an external entity who has a remote DWN server defined in their DID document. + // const ionCreateOptions = await testProfile.ionCreateOptions.services.dwn.authorization.keys(); + // const { id: bobDid } = await testAgent.didIon.create(ionCreateOptions); + + // // Attempt to read a record from Bob's DWN using the ID of a record that only exists in the connected agent's DWN. + // const result = await dwn.records.read({ + // from : bobDid, + // message : { + // recordId: record!.id + // } + // }); + + // // Confirm that the record does not currently exist on Bob's DWN. + // expect(result.status.code).to.equal(404); + // expect(result.record).to.be.undefined; + }); + }); + }); + + describe('delete', () => { + describe('agent', () => { + it('deletes a record', async () => { + const writeResult = await dwn.records.write({ + data : 'Hello, world!', + message : { + schema : 'foo/bar', + dataFormat : 'text/plain' + } + }); + + expect(writeResult.status.code).to.equal(202); + expect(writeResult.record).to.not.be.undefined; + + const deleteResult = await dwn.records.delete({ + message: { + recordId: writeResult.record!.id + } + }); + + expect(deleteResult.status.code).to.equal(202); + }); + + it('returns a 404 when the specified record does not exist', async () => { + let deleteResult = await dwn.records.delete({ + message: { + recordId: 'abcd1234' + } + }); + expect(deleteResult.status.code).to.equal(404); + }); + }); + + describe('from: did', () => { + xit('returns a 401 when authentication or authorization fails', async () => { + // // Create a new DID to represent an external entity who has a remote DWN server defined in their DID document. + // const ionCreateOptions = await testProfile.ionCreateOptions.services.dwn.authorization.keys(); + // const { id: bobDid } = await testAgent.didIon.create(ionCreateOptions); + + // // Attempt to delete a record from Bob's DWN specifying a recordId that does not exist. + // const deleteResult = await dwn.records.delete({ + // from : bobDid, + // message : { + // recordId: 'abcd1234' + // } + // }); + + // //! TODO: Once record.send() has been implemented, add another test to write a record + // //! and test a delete to confirm that authn/authz pass and a 202 is returned. + // expect(deleteResult.status.code).to.equal(401); + }); + }); + }); + }); +}); \ No newline at end of file diff --git a/packages/api/tests/web5-vc.spec.ts b/packages/api/tests/web5-vc.spec.ts new file mode 100644 index 000000000..860f905c6 --- /dev/null +++ b/packages/api/tests/web5-vc.spec.ts @@ -0,0 +1,50 @@ +import { expect } from 'chai'; +import { TestManagedAgent } from '@web5/agent'; + +import { VcApi } from '../src/vc-api.js'; +import { TestUserAgent } from './utils/test-user-agent.js'; + +describe('web5.vc', () => { + let vc: VcApi; + let testAgent: TestManagedAgent; + + before(async () => { + testAgent = await TestManagedAgent.create({ + agentClass : TestUserAgent, + agentStores : 'memory' + }); + }); + + beforeEach(async () => { + await testAgent.clearStorage(); + + // Create an Agent DID. + await testAgent.createAgentDid(); + + // Create a new Identity to author DWN messages. + const identity = await testAgent.agent.identityManager.create({ + name : 'Test', + didMethod : 'key', + kms : 'local' + }); + + // Instantiate VcApi. + vc = new VcApi({ agent: testAgent.agent, connectedDid: identity.did }); + }); + + after(async () => { + await testAgent.clearStorage(); + await testAgent.closeStorage(); + }); + + describe('create()', () => { + it('is not implemented', async () => { + try { + await vc.create(); + expect.fail('Expected method to throw, but it did not.'); + } catch(e) { + expect(e.message).to.include('Not implemented.'); + } + }); + }); +}); \ No newline at end of file diff --git a/packages/api/tests/web5.spec.ts b/packages/api/tests/web5.spec.ts new file mode 100644 index 000000000..ebde22249 --- /dev/null +++ b/packages/api/tests/web5.spec.ts @@ -0,0 +1,42 @@ +import { expect } from 'chai'; +import { TestManagedAgent } from '@web5/agent'; + +import { Web5 } from '../src/web5.js'; +import { TestUserAgent } from './utils/test-user-agent.js'; + +describe('Web5', () => { + let testAgent: TestManagedAgent; + + before(async () => { + testAgent = await TestManagedAgent.create({ + agentClass : TestUserAgent, + agentStores : 'memory' + }); + }); + + beforeEach(async () => { + await testAgent.clearStorage(); + await testAgent.createAgentDid(); + }); + + after(async () => { + await testAgent.clearStorage(); + await testAgent.closeStorage(); + }); + + it('instantiates Web5 API with provided Web5Agent and connectedDid', async () => { + // Create a new Identity. + const socialIdentity = await testAgent.agent.identityManager.create({ + name : 'Social', + didMethod : 'key', + kms : 'local' + }); + + // Instantiates Web5 instance with test agent and new Identity's DID. + const web5 = new Web5({ agent: testAgent.agent, connectedDid: socialIdentity.did }); + expect(web5).to.exist; + expect(web5).to.have.property('did'); + expect(web5).to.have.property('dwn'); + expect(web5).to.have.property('vc'); + }); +}); \ No newline at end of file diff --git a/packages/api/tsconfig.cjs.json b/packages/api/tsconfig.cjs.json new file mode 100644 index 000000000..7a6f9c0c0 --- /dev/null +++ b/packages/api/tsconfig.cjs.json @@ -0,0 +1,19 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "lib": [ + "DOM", + "ES5", + ], + "target": "ES5", + "module": "CommonJS", + "outDir": "dist/cjs", + "declaration": false, + "declarationMap": false, + "declarationDir": null, + "downlevelIteration": true + }, + "include": [ + "src" + ] +} \ No newline at end of file diff --git a/packages/web5/tsconfig.json b/packages/api/tsconfig.json similarity index 55% rename from packages/web5/tsconfig.json rename to packages/api/tsconfig.json index 86ee091a3..334d87aa4 100644 --- a/packages/web5/tsconfig.json +++ b/packages/api/tsconfig.json @@ -1,5 +1,6 @@ { "compilerOptions": { + // "strict": true, "lib": [ "DOM", "ES6" @@ -11,11 +12,16 @@ "declarationMap": true, "declarationDir": "dist/types", "outDir": "dist/esm", + // `NodeNext` will throw compilation errors if relative import paths are missing file extension + // reference: https://devblogs.microsoft.com/typescript/announcing-typescript-4-7/#ecmascript-module-support-in-node-js "moduleResolution": "NodeNext", "esModuleInterop": true, "resolveJsonModule": true }, "include": [ - "src" + "src", + ], + "exclude": [ + "node_modules" ] } \ No newline at end of file diff --git a/packages/common/.c8rc.json b/packages/common/.c8rc.json index 1d1670b70..ab680f663 100644 --- a/packages/common/.c8rc.json +++ b/packages/common/.c8rc.json @@ -5,12 +5,12 @@ ".js" ], "include": [ - "tests/compiled/**" + "tests/compiled/src/**" ], "exclude": [ - "tests/compiled/src/main.js", + "tests/compiled/src/index.js", "tests/compiled/src/types.js", - "tests/compiled/types/**" + "tests/compiled/src/types/**" ], "reporter": [ "cobertura", diff --git a/packages/common/build/cjs-bundle.js b/packages/common/build/cjs-bundle.js index 968bf88cf..367b90104 100644 --- a/packages/common/build/cjs-bundle.js +++ b/packages/common/build/cjs-bundle.js @@ -15,12 +15,12 @@ for (const dependency in packageJson.dependencies) { } esbuild.build({ - entryPoints : [ './src/main.ts' ], + entryPoints : [ './src/index.ts' ], bundle : true, external : excludeList, format : 'cjs', sourcemap : true, platform : 'node', - outfile : 'dist/cjs/main.js', + outfile : 'dist/cjs/index.js', allowOverwrite : true }); \ No newline at end of file diff --git a/packages/common/build/esbuild-browser-config.cjs b/packages/common/build/esbuild-browser-config.cjs index ae37c97f0..8fd899321 100644 --- a/packages/common/build/esbuild-browser-config.cjs +++ b/packages/common/build/esbuild-browser-config.cjs @@ -1,6 +1,6 @@ /** @type {import('esbuild').BuildOptions} */ module.exports = { - entryPoints : ['./src/main.ts'], + entryPoints : ['./src/index.ts'], bundle : true, format : 'esm', sourcemap : true, @@ -10,4 +10,4 @@ module.exports = { define : { 'global': 'globalThis', }, -}; +}; \ No newline at end of file diff --git a/packages/common/package.json b/packages/common/package.json index f23b0ed4b..9a3ca92ec 100644 --- a/packages/common/package.json +++ b/packages/common/package.json @@ -1,10 +1,10 @@ { - "name": "@tbd54566975/common", - "version": "0.8.0", + "name": "@web5/common", + "version": "0.1.1", "type": "module", - "main": "./dist/cjs/main.js", - "module": "./dist/esm/main.js", - "types": "./dist/types/main.d.ts", + "main": "./dist/cjs/index.js", + "module": "./dist/esm/index.js", + "types": "./dist/types/index.d.ts", "scripts": { "clean": "rimraf dist coverage tests/compiled", "build:esm": "rimraf dist/esm dist/types && npx tsc -p tsconfig.json", @@ -45,12 +45,12 @@ ], "exports": { ".": { - "import": "./dist/esm/main.js", - "require": "./dist/cjs/main.js", - "types": "./dist/types/main.d.ts" + "import": "./dist/esm/index.js", + "require": "./dist/cjs/index.js", + "types": "./dist/types/index.d.ts" } }, - "react-native": "./dist/esm/main.js", + "react-native": "./dist/esm/index.js", "keywords": [ "decentralized", "decentralized-applications", @@ -67,16 +67,20 @@ "node": ">=18.0.0" }, "dependencies": { + "level": "8.0.0", "multiformats": "11.0.2" }, "devDependencies": { + "@playwright/test": "1.36.2", "@types/chai": "4.3.0", + "@types/chai-as-promised": "7.1.5", "@types/eslint": "8.37.0", "@types/mocha": "10.0.1", "@typescript-eslint/eslint-plugin": "5.59.0", "@typescript-eslint/parser": "5.59.0", "c8": "8.0.0", "chai": "4.3.7", + "chai-as-promised": "7.1.1", "esbuild": "0.16.17", "eslint": "8.39.0", "eslint-plugin-mocha": "10.1.0", diff --git a/packages/common/src/convert.ts b/packages/common/src/convert.ts index ed6871cea..294648582 100644 --- a/packages/common/src/convert.ts +++ b/packages/common/src/convert.ts @@ -96,11 +96,11 @@ export class Convert { } case 'Hex': { - return Convert.hex(this.data).toUint8Array().buffer; + return this.toUint8Array().buffer; } case 'String': { - return Convert.string(this.data).toUint8Array().buffer; + return this.toUint8Array().buffer; } case 'Uint8Array': { @@ -141,6 +141,11 @@ export class Convert { return base64url.baseEncode(u8a); } + case 'BufferSource': { + const u8a = this.toUint8Array(); + return base64url.baseEncode(u8a); + } + case 'Object': { const string = JSON.stringify(this.data); const u8a = textEncoder.encode(string); @@ -168,7 +173,12 @@ export class Convert { switch (this.format) { case 'ArrayBuffer': { - const u8a = new Uint8Array(this.data); + const u8a = this.toUint8Array(); + return Convert.uint8Array(u8a).toHex(); + } + + case 'Base64Url': { + const u8a = this.toUint8Array(); return Convert.uint8Array(u8a).toHex(); } @@ -181,7 +191,7 @@ export class Convert { } default: - throw new TypeError(`Conversion from ${this.format} to Object is not supported.`); + throw new TypeError(`Conversion from ${this.format} to Hex is not supported.`); } } diff --git a/packages/common/src/main.ts b/packages/common/src/index.ts similarity index 84% rename from packages/common/src/main.ts rename to packages/common/src/index.ts index 4767c3399..204ddeb1c 100644 --- a/packages/common/src/main.ts +++ b/packages/common/src/index.ts @@ -2,5 +2,6 @@ export type * from './types.js'; export * from './convert.js'; export * from './multicodec.js'; +export * from './object.js'; export * from './stores.js'; export * from './type-utils.js'; \ No newline at end of file diff --git a/packages/common/src/multicodec.ts b/packages/common/src/multicodec.ts index 98fa89034..18af88de6 100644 --- a/packages/common/src/multicodec.ts +++ b/packages/common/src/multicodec.ts @@ -54,9 +54,8 @@ export class Multicodec { }): Uint8Array { let { code, data, name } = options; - // Either code or name must be specified, but not both. if (!(name ? !code : code)) { - throw new Error(`Exactly one of 'name' or 'code' must be defined.`); + throw new Error(`Either 'name' or 'code' must be defined, but not both.`); } // If code was given, confirm it exists, or lookup code by name. @@ -64,7 +63,7 @@ export class Multicodec { // Throw error if a registered Codec wasn't found. if (code === undefined) { - throw new Error(`Multicodec not found: ${name ?? code}`); + throw new Error(`Unsupported multicodec: ${options.name ?? options.code}`); } // Create a new array to store the prefix and input data. @@ -94,6 +93,48 @@ export class Multicodec { return code; } + /** + * Get the Multicodec code from given Multicodec name. + * + * @param options - The options for getting the codec code. + * @param options.name - The name to lookup. + * @returns - The Multicodec code as a number. + */ + public static getCodeFromName(options: { + name: string + }): MulticodecCode { + const { name } = options; + + // Throw error if a registered Codec wasn't found. + const code = Multicodec.nameToCode.get(name); + if (code === undefined) { + throw new Error(`Unsupported multicodec: ${name}`); + } + + return code; + } + + /** + * Get the Multicodec name from given Multicodec code. + * + * @param options - The options for getting the codec name. + * @param options.name - The code to lookup. + * @returns - The Multicodec name as a string. + */ + public static getNameFromCode(options: { + code: MulticodecCode + }): string { + const { code } = options; + + // Throw error if a registered Codec wasn't found. + const name = Multicodec.codeToName.get(code); + if (name === undefined) { + throw new Error(`Unsupported multicodec: ${code}`); + } + + return name; + } + /** * Registers a new codec in the Multicodec class. * @@ -112,10 +153,17 @@ export class Multicodec { */ public static removePrefix(options: { prefixedData: Uint8Array - }): Uint8Array { + }): { code: MulticodecCode, name: string, data: Uint8Array } { const { prefixedData } = options; - const [_, codeByteLength] = varint.decode(prefixedData); - return prefixedData.slice(codeByteLength); + const [code, codeByteLength] = varint.decode(prefixedData); + + // Throw error if a registered Codec wasn't found. + const name = Multicodec.codeToName.get(code); + if (name === undefined) { + throw new Error(`Unsupported multicodec: ${code}`); + } + + return { code, data: prefixedData.slice(codeByteLength), name }; } } diff --git a/packages/common/src/object.ts b/packages/common/src/object.ts new file mode 100644 index 000000000..7d88fdfda --- /dev/null +++ b/packages/common/src/object.ts @@ -0,0 +1,43 @@ +/** + * Checks whether the given object has any properties. + */ +export function isEmptyObject(obj: unknown): boolean { + if (typeof obj !== 'object' || obj === null) { + return false; + } + + if (Object.getOwnPropertySymbols(obj).length > 0) { + return false; + } + + return Object.keys(obj).length === 0; +} + +/** + * Recursively removes all properties with an empty object or array as its value from the given object. + */ +export function removeEmptyObjects(obj: Record): void { + Object.keys(obj).forEach(key => { + if (typeof(obj[key]) === 'object') { + // recursive remove empty object or array properties in nested objects + removeEmptyObjects(obj[key] as Record); + } + + if (isEmptyObject(obj[key])) { + delete obj[key]; + } + }); +} + +/** + * Recursively removes all properties with `undefined` as its value from the given object. + */ +export function removeUndefinedProperties(obj: Record): void { + Object.keys(obj).forEach(key => { + if (obj[key] === undefined) { + delete obj[key]; + } else if (typeof(obj[key]) === 'object') { + removeUndefinedProperties(obj[key] as Record); // recursive remove `undefined` properties in nested objects + } + }); +} \ No newline at end of file diff --git a/packages/common/src/stores.ts b/packages/common/src/stores.ts index c9008b20e..1f4e9b090 100644 --- a/packages/common/src/stores.ts +++ b/packages/common/src/stores.ts @@ -1,5 +1,36 @@ +import { Level } from 'level'; + import type { KeyValueStore } from './types.js'; +export class LevelStore implements KeyValueStore { + private store: Level; + + constructor(location = 'DATASTORE') { + this.store = new Level(location); + } + + async clear(): Promise { + await this.store.clear(); + } + + async close(): Promise { + await this.store.close(); + } + + async delete(key: string): Promise { + await this.store.del(key); + return true; + } + + async get(key: string): Promise { + return await this.store.get(key); + } + + async set(key: string, value: any): Promise { + await this.store.put(key, value); + } +} + /** * The `MemoryStore` class is an implementation of * `KeyValueStore` that holds data in memory. @@ -35,11 +66,11 @@ export class MemoryStore implements KeyValueStore { } /** - * This operation is not supported by `MemoryStore` - * and will throw an error if called. + * This operation is no-op for `MemoryStore` + * and will log a warning if called. */ async close(): Promise { - throw new Error('MemoryStore does not support the close() method.'); + /** no-op */ } /** diff --git a/packages/common/tests/convert.spec.ts b/packages/common/tests/convert.spec.ts index 462fd70af..8e6b3d21f 100644 --- a/packages/common/tests/convert.spec.ts +++ b/packages/common/tests/convert.spec.ts @@ -96,6 +96,14 @@ describe('Convert', () =>{ expect(result).to.deep.equal(output); }); + it('to: Hex', () => { + // Test Vector 1. + let input = 'eyJmb28iOiJiYXIifQ'; + let output = '7b22666f6f223a22626172227d'; + const result = Convert.base64Url(input).toHex(); + expect(result).to.deep.equal(output); + }); + it('to: Object', () => { // Test Vector 1. let input = 'eyJmb28iOiJiYXIifQ'; @@ -178,6 +186,43 @@ describe('Convert', () =>{ expect (() => Convert.bufferSource(inputT8).toArrayBuffer()).to.throw(TypeError, 'value is not of type'); }); + it('to: Base64Url', () => { + // Test Vector 1 - BufferSource is Uint8Array. + let inputT1 = new Uint8Array([102, 111, 111]); + let outputT1 = 'Zm9v'; + let resultT1 = Convert.bufferSource(inputT1).toBase64Url(); + expect(resultT1).to.deep.equal(outputT1); + + // Test Vector 2 - BufferSource is ArrayBuffer. + let inputT2 = (new Uint8Array([50, 51, 52, 53])).buffer; + let outputT2 = 'MjM0NQ'; + let resultT2 = Convert.bufferSource(inputT2).toBase64Url(); + expect(resultT2).to.deep.equal(outputT2); + + // Test Vector 3 - BufferSource is DataView. + let inputT3 = new DataView((new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8, 9, 0])).buffer); + let outputT3 = 'AQIDBAUGBwgJAA'; + let resultT3 = Convert.bufferSource(inputT3).toBase64Url(); + expect(resultT3).to.deep.equal(outputT3); + + // Test Vector 4 - BufferSource is an unsigned, 16-bit Typed Array. + let inputT4 = new Uint16Array([299, 298, 297]); + let outputT4 = 'KwEqASkB'; + let resultT4 = Convert.bufferSource(inputT4).toBase64Url(); + expect(resultT4).to.deep.equal(outputT4); + + // Test Vector 5 - BufferSource is a signed, 32-bit Typed Array. + let inputT5 = new Int32Array([1111, 1000, 2000]); + let outputT5 = 'VwQAAOgDAADQBwAA'; + let resultT5 = Convert.bufferSource(inputT5).toBase64Url(); + expect(resultT5).to.deep.equal(outputT5); + + // Test Vector 6 - BufferSource is Uint8Array. + let inputT6 = 'not BufferSource type'; + // @ts-expect-error because incorrect input data type is intentionally being used to trigger error. + expect (() => Convert.bufferSource(inputT6).toBase64Url()).to.throw(TypeError, 'value is not of type'); + }); + it('to: Uint8Array', () => { // Test Vector 1 - BufferSource is Uint8Array. let inputT1 = new Uint8Array([102, 111, 111]); diff --git a/packages/common/tests/multicodec.spec.ts b/packages/common/tests/multicodec.spec.ts index 5990a3765..1f35fba07 100644 --- a/packages/common/tests/multicodec.spec.ts +++ b/packages/common/tests/multicodec.spec.ts @@ -53,23 +53,23 @@ describe('Multicodec', () => { it('throws an error when code and name input data missing', () => { expect( () => Multicodec.addPrefix({ data: new Uint8Array(0) }) - ).to.throw(Error, `Exactly one of 'name' or 'code' must be defined.`); + ).to.throw(Error, `Either 'name' or 'code' must be defined, but not both.`); }); it('throws an error when both code and name specified', () => { expect( () => Multicodec.addPrefix({ code: 0x99999, name: 'non-existent', data: new Uint8Array(0) }) - ).to.throw(Error, `Exactly one of 'name' or 'code' must be defined.`); + ).to.throw(Error, `Either 'name' or 'code' must be defined, but not both.`); }); it('throws an error when codec not found', () => { expect( () => Multicodec.addPrefix({ code: 0x99999, data: new Uint8Array(0) }) - ).to.throw(Error, 'Multicodec not found'); + ).to.throw(Error, 'Unsupported multicodec: 629145'); expect( () => Multicodec.addPrefix({ name: 'non-existent', data: new Uint8Array(0) }) - ).to.throw(Error, 'Multicodec not found'); + ).to.throw(Error, 'Unsupported multicodec: non-existent'); }); }); @@ -86,17 +86,30 @@ describe('Multicodec', () => { }); describe('removePrefix()', () => { - it('returns Uint8Array with prefixed codec removed', () => { + it('returns code, name, and data', () => { + const input = new Uint8Array([0xed, 0x01, 0, 1, 2, 3]); + + const { code, data, name } = Multicodec.removePrefix({ prefixedData: input }); + + expect(code).to.be.a('Number'); + expect(data).to.be.a('Uint8Array'); + expect(name).to.be.a('String'); + }); + + it('returns data as Uint8Array with prefixed codec removed', () => { const input = new Uint8Array([0xed, 0x01, 0, 1, 2, 3]); const output = new Uint8Array([0, 1, 2, 3]); - const data = Multicodec.removePrefix({ prefixedData: input }); + const { data } = Multicodec.removePrefix({ prefixedData: input }); expect(data).to.be.a('Uint8Array'); expect(data).to.deep.equal(output); }); it('passes Multicodec test vectors', () => { + Multicodec.registerCodec({ code: 0x3ffff, name: 'test-vector-3' }); + Multicodec.registerCodec({ code: 0x3fffff, name: 'test-vector-4' }); + // Test vectors. const testVectors: [ArrayLike, ArrayLike][] = [ [[0xed, 0x01, 0, 1], [0, 1]], @@ -108,10 +121,22 @@ describe('Multicodec', () => { testVectors.forEach(([input, output]) => { const prefixedData = new Uint8Array(input); const [_, codeByteLength] = varint.decode(prefixedData); - const data = Multicodec.removePrefix({ prefixedData }); + const { data } = Multicodec.removePrefix({ prefixedData }); expect(data.byteLength).to.equal(prefixedData.byteLength - codeByteLength); expect(data).to.deep.equal(new Uint8Array(output)); }); }); + + it('throws an error when codec not found', () => { + const prefix = new Uint8Array([100, 100]); + const data = new Uint8Array([1, 2, 3, 4, 5]); + const dataWithPrefix = new Uint8Array(prefix.byteLength + data.byteLength); + dataWithPrefix.set(prefix, 0); + dataWithPrefix.set(data, prefix.length); + + expect( + () => Multicodec.removePrefix({ prefixedData: dataWithPrefix }) + ).to.throw(Error, 'Unsupported multicodec: 100'); + }); }); }); \ No newline at end of file diff --git a/packages/common/tests/object.spec.ts b/packages/common/tests/object.spec.ts new file mode 100644 index 000000000..58470795f --- /dev/null +++ b/packages/common/tests/object.spec.ts @@ -0,0 +1,88 @@ +import { expect } from 'chai'; + +import { isEmptyObject, removeEmptyObjects, removeUndefinedProperties } from '../src/object.js'; + + +describe('isEmptyObject()', () => { + it('should return true for an empty object', () => { + expect(isEmptyObject({})).to.be.true; + }); + + it('should return false for a non-empty object', () => { + expect(isEmptyObject({ key: 'value' })).to.be.false; + }); + + it('should return false for null', () => { + expect(isEmptyObject(null)).to.be.false; + }); + + it('should return true for an object with no prototype', () => { + expect(isEmptyObject(Object.create(null))).to.be.true; + }); + + it('should return false for an object with no prototype but containing properties', () => { + const obj = Object.create(null); + obj.key = 'value'; + expect(isEmptyObject(obj)).to.be.false; + }); + + it('should return false for an object with symbol properties', () => { + const symbol = Symbol('key'); + const obj = { [symbol]: 'value' }; + expect(isEmptyObject(obj)).to.be.false; + }); + + it('should return false for a non-object (number)', () => { + expect(isEmptyObject(42)).to.be.false; + }); + + it('should return false for a non-object (string)', () => { + expect(isEmptyObject('text')).to.be.false; + }); + + it('should return true for an object that inherits properties but has none of its own', () => { + const parent = { parentKey: 'value' }; + const child = Object.create(parent); + expect(isEmptyObject(child)).to.be.true; + }); +}); + +describe('removeEmptyObjects()', () => { + it('should remove all empty objects', () => { + const mockObject = { + foo : {}, + bar : { baz: {} }, + buzz : 'hello' + }; + + const expectedResult = { buzz: 'hello' }; + + removeEmptyObjects(mockObject); + + expect(mockObject).to.deep.equal(expectedResult); + }); +}); + +describe('removeUndefinedProperties()', () => { + it('should remove all `undefined` properties of a nested object', () => { + const mockObject = { + a : true, + b : undefined, + c : { + a : 0, + b : undefined, + } + }; + + const expectedResult = { + a : true, + c : { + a: 0 + } + }; + + removeUndefinedProperties(mockObject); + + expect(mockObject).to.deep.equal(expectedResult); + }); +}); \ No newline at end of file diff --git a/packages/common/tests/stores.spec.ts b/packages/common/tests/stores.spec.ts index a1c48017d..e6e23041f 100644 --- a/packages/common/tests/stores.spec.ts +++ b/packages/common/tests/stores.spec.ts @@ -25,10 +25,10 @@ describe('MemoryStore', () => { }); describe('close()', () => { - it('should throw an error when trying to close the store', async () => { + it('should no-op when trying to close the store', async () => { await expect( memoryStore.close() - ).to.be.rejectedWith('MemoryStore does not support the close() method.'); + ).to.be.fulfilled; }); }); diff --git a/packages/common/tests/type-utils.spec.ts b/packages/common/tests/type-utils.spec.ts index 29552e3c5..009f587e7 100644 --- a/packages/common/tests/type-utils.spec.ts +++ b/packages/common/tests/type-utils.spec.ts @@ -26,6 +26,10 @@ describe('universalTypeOf()', () => { expect(universalTypeOf(new ArrayBuffer(2))).to.equal('ArrayBuffer'); }); + it('should correctly identify Blob', () => { + expect(universalTypeOf(new Blob(['foo']))).to.equal('Blob'); + }); + it('should correctly identify Boolean', () => { expect(universalTypeOf(true)).to.equal('Boolean'); }); diff --git a/packages/common/tsconfig.json b/packages/common/tsconfig.json index 3147ea9c6..b29e4e7e1 100644 --- a/packages/common/tsconfig.json +++ b/packages/common/tsconfig.json @@ -20,5 +20,8 @@ }, "include": [ "src", + ], + "exclude": [ + "node_modules" ] } \ No newline at end of file diff --git a/packages/credentials/.c8rc.json b/packages/credentials/.c8rc.json index 1d1670b70..ab680f663 100644 --- a/packages/credentials/.c8rc.json +++ b/packages/credentials/.c8rc.json @@ -5,12 +5,12 @@ ".js" ], "include": [ - "tests/compiled/**" + "tests/compiled/src/**" ], "exclude": [ - "tests/compiled/src/main.js", + "tests/compiled/src/index.js", "tests/compiled/src/types.js", - "tests/compiled/types/**" + "tests/compiled/src/types/**" ], "reporter": [ "cobertura", diff --git a/packages/credentials/.vscode/launch.json b/packages/credentials/.vscode/launch.json index e2927b1de..bc398db79 100644 --- a/packages/credentials/.vscode/launch.json +++ b/packages/credentials/.vscode/launch.json @@ -11,7 +11,9 @@ "runtimeExecutable": "${workspaceFolder:root}/node_modules/.bin/mocha", "console": "internalConsole", "preLaunchTask": "build tests", - "skipFiles": ["/**"] + "skipFiles": [ + "/**" + ] } ] } \ No newline at end of file diff --git a/packages/credentials/build/bundles.js b/packages/credentials/build/bundles.js index 8137e7c71..a092adefb 100644 --- a/packages/credentials/build/bundles.js +++ b/packages/credentials/build/bundles.js @@ -11,6 +11,6 @@ esbuild.build({ esbuild.build({ ...browserConfig, format : 'iife', - globalName : 'Web5Ssi', + globalName : 'Web5Credentials', outfile : 'dist/browser.js', }); \ No newline at end of file diff --git a/packages/credentials/build/esbuild-browser-config.cjs b/packages/credentials/build/esbuild-browser-config.cjs index ae37c97f0..8fd899321 100644 --- a/packages/credentials/build/esbuild-browser-config.cjs +++ b/packages/credentials/build/esbuild-browser-config.cjs @@ -1,6 +1,6 @@ /** @type {import('esbuild').BuildOptions} */ module.exports = { - entryPoints : ['./src/main.ts'], + entryPoints : ['./src/index.ts'], bundle : true, format : 'esm', sourcemap : true, @@ -10,4 +10,4 @@ module.exports = { define : { 'global': 'globalThis', }, -}; +}; \ No newline at end of file diff --git a/packages/credentials/package.json b/packages/credentials/package.json index 60fc8bca7..8135c19c3 100644 --- a/packages/credentials/package.json +++ b/packages/credentials/package.json @@ -1,11 +1,11 @@ { - "name": "@tbd54566975/credentials", - "version": "0.8.0", + "name": "@web5/credentials", + "version": "0.1.5", "description": "Verifiable Credentials", "type": "module", - "main": "./dist/cjs/main.js", - "module": "./dist/esm/main.js", - "types": "./dist/types/main.d.ts", + "main": "./dist/cjs/index.js", + "module": "./dist/esm/index.js", + "types": "./dist/types/index.d.ts", "scripts": { "clean": "rimraf dist coverage tests/compiled", "build:esm": "rimraf dist/esm dist/types && npx tsc -p tsconfig.json", @@ -46,12 +46,12 @@ ], "exports": { ".": { - "import": "./dist/esm/main.js", - "require": "./dist/cjs/main.js", - "types": "./dist/types/main.d.ts" + "import": "./dist/esm/index.js", + "require": "./dist/cjs/index.js", + "types": "./dist/types/index.d.ts" } }, - "react-native": "./dist/esm/main.js", + "react-native": "./dist/esm/index.js", "keywords": [ "decentralized", "decentralized-applications", @@ -69,6 +69,7 @@ }, "dependencies": {}, "devDependencies": { + "@playwright/test": "1.36.2", "@types/chai": "4.3.0", "@types/eslint": "8.37.0", "@types/mocha": "10.0.1", diff --git a/packages/credentials/src/main.ts b/packages/credentials/src/index.ts similarity index 100% rename from packages/credentials/src/main.ts rename to packages/credentials/src/index.ts diff --git a/packages/credentials/tests/example.spec.ts b/packages/credentials/tests/example.spec.ts index d5882cbaf..734da9934 100644 --- a/packages/credentials/tests/example.spec.ts +++ b/packages/credentials/tests/example.spec.ts @@ -1,7 +1,7 @@ import { expect } from 'chai'; describe('example', () => { - it('needs more tests', () => { + it('tests needed', () => { expect(true).to.be.true; }); }); \ No newline at end of file diff --git a/packages/credentials/tsconfig.json b/packages/credentials/tsconfig.json index 3147ea9c6..b29e4e7e1 100644 --- a/packages/credentials/tsconfig.json +++ b/packages/credentials/tsconfig.json @@ -20,5 +20,8 @@ }, "include": [ "src", + ], + "exclude": [ + "node_modules" ] } \ No newline at end of file diff --git a/packages/crypto/.c8rc.json b/packages/crypto/.c8rc.json index 9763ced85..ab680f663 100644 --- a/packages/crypto/.c8rc.json +++ b/packages/crypto/.c8rc.json @@ -5,13 +5,12 @@ ".js" ], "include": [ - "tests/compiled/**" + "tests/compiled/src/**" ], "exclude": [ - "tests/compiled/tests/**", - "tests/compiled/src/main.js", - "tests/compiled/src/types/**", - "tests/compiled/types/**" + "tests/compiled/src/index.js", + "tests/compiled/src/types.js", + "tests/compiled/src/types/**" ], "reporter": [ "cobertura", diff --git a/packages/crypto/.mocharc.json b/packages/crypto/.mocharc.json index 61014ff76..5aa8c5bfe 100644 --- a/packages/crypto/.mocharc.json +++ b/packages/crypto/.mocharc.json @@ -1,5 +1,5 @@ { - "enable-source-maps": true, - "exit": true, - "spec": ["tests/compiled/tests/**/*.spec.js"] - } \ No newline at end of file + "enable-source-maps": true, + "exit": true, + "spec": ["tests/compiled/**/*.spec.js"] +} \ No newline at end of file diff --git a/packages/crypto/.vscode/launch.json b/packages/crypto/.vscode/launch.json index e2927b1de..bc398db79 100644 --- a/packages/crypto/.vscode/launch.json +++ b/packages/crypto/.vscode/launch.json @@ -11,7 +11,9 @@ "runtimeExecutable": "${workspaceFolder:root}/node_modules/.bin/mocha", "console": "internalConsole", "preLaunchTask": "build tests", - "skipFiles": ["/**"] + "skipFiles": [ + "/**" + ] } ] } \ No newline at end of file diff --git a/packages/crypto/build/esbuild-browser-config.cjs b/packages/crypto/build/esbuild-browser-config.cjs index ae37c97f0..8fd899321 100644 --- a/packages/crypto/build/esbuild-browser-config.cjs +++ b/packages/crypto/build/esbuild-browser-config.cjs @@ -1,6 +1,6 @@ /** @type {import('esbuild').BuildOptions} */ module.exports = { - entryPoints : ['./src/main.ts'], + entryPoints : ['./src/index.ts'], bundle : true, format : 'esm', sourcemap : true, @@ -10,4 +10,4 @@ module.exports = { define : { 'global': 'globalThis', }, -}; +}; \ No newline at end of file diff --git a/packages/crypto/package.json b/packages/crypto/package.json index 4f1150624..3bc5f20b3 100644 --- a/packages/crypto/package.json +++ b/packages/crypto/package.json @@ -1,11 +1,11 @@ { - "name": "@tbd54566975/crypto", - "version": "0.8.0", + "name": "@web5/crypto", + "version": "0.1.6", "description": "TBD crypto library", "type": "module", - "main": "./dist/cjs/main.js", - "module": "./dist/esm/main.js", - "types": "./dist/types/main.d.ts", + "main": "./dist/cjs/index.js", + "module": "./dist/esm/index.js", + "types": "./dist/types/index.d.ts", "scripts": { "clean": "rimraf dist tests/compiled", "build:esm": "rimraf dist/esm dist/types && npx tsc -p tsconfig.json", @@ -46,15 +46,15 @@ ], "exports": { ".": { - "import": "./dist/esm/main.js", - "require": "./dist/cjs/main.js", - "types": "./dist/types/main.d.ts" + "import": "./dist/esm/index.js", + "require": "./dist/cjs/index.js", + "types": "./dist/types/index.d.ts" } }, "browser": { "node:crypto": false }, - "react-native": "./dist/esm/main.js", + "react-native": "./dist/esm/index.js", "keywords": [ "cryptography", "ed25519", @@ -68,13 +68,13 @@ "node": ">=18.0.0" }, "dependencies": { - "@noble/ciphers": "0.1.3", + "@noble/ciphers": "0.1.4", "@noble/curves": "1.1.0", "@noble/hashes": "1.3.1", - "@tbd54566975/common": "0.8.0", - "ed2curve": "0.3.0" + "@web5/common": "0.1.1" }, "devDependencies": { + "@playwright/test": "1.36.2", "@types/chai": "4.3.0", "@types/chai-as-promised": "7.1.5", "@types/ed2curve": "0.2.2", diff --git a/packages/crypto/src/algorithms-api/aes/base.ts b/packages/crypto/src/algorithms-api/aes/base.ts index 96b579e5d..ad29f93ea 100644 --- a/packages/crypto/src/algorithms-api/aes/base.ts +++ b/packages/crypto/src/algorithms-api/aes/base.ts @@ -1,9 +1,9 @@ -import type { Web5Crypto } from '../../types/index.js'; +import { universalTypeOf } from '@web5/common'; -import { universalTypeOf } from '@tbd54566975/common'; +import type { Web5Crypto } from '../../types/web5-crypto.js'; +import { checkRequiredProperty } from '../../utils.js'; import { CryptoAlgorithm } from '../crypto-algorithm.js'; -import { checkRequiredProperty } from '../../utils-new.js'; import { InvalidAccessError, OperationError } from '../errors.js'; export abstract class BaseAesAlgorithm extends CryptoAlgorithm { @@ -35,11 +35,11 @@ export abstract class BaseAesAlgorithm extends CryptoAlgorithm { keyUsages: Web5Crypto.KeyUsage[] }): Promise; - public override async deriveBits(): Promise { + public override async deriveBits(): Promise { throw new InvalidAccessError(`Requested operation 'deriveBits' is not valid for ${this.name} keys.`); } - public override async sign(): Promise { + public override async sign(): Promise { throw new InvalidAccessError(`Requested operation 'sign' is not valid for ${this.name} keys.`); } diff --git a/packages/crypto/src/algorithms-api/aes/ctr.ts b/packages/crypto/src/algorithms-api/aes/ctr.ts index 379b87ffe..18b2c958b 100644 --- a/packages/crypto/src/algorithms-api/aes/ctr.ts +++ b/packages/crypto/src/algorithms-api/aes/ctr.ts @@ -1,10 +1,10 @@ -import type { Web5Crypto } from '../../types/index.js'; +import { universalTypeOf } from '@web5/common'; -import { universalTypeOf } from '@tbd54566975/common'; +import type { Web5Crypto } from '../../types/web5-crypto.js'; import { BaseAesAlgorithm } from './base.js'; import { OperationError } from '../errors.js'; -import { checkRequiredProperty } from '../../utils-new.js'; +import { checkRequiredProperty } from '../../utils.js'; export abstract class BaseAesCtrAlgorithm extends BaseAesAlgorithm { @@ -21,9 +21,9 @@ export abstract class BaseAesCtrAlgorithm extends BaseAesAlgorithm { this.checkAlgorithmName({ algorithmName: algorithm.name }); // The algorithm object must contain a counter property. checkRequiredProperty({ property: 'counter', inObject: algorithm }); - // The counter must an ArrayBuffer, DataView, or TypedArray. - if (!(universalTypeOf(algorithm.counter) === 'ArrayBuffer' || ArrayBuffer.isView(algorithm.counter))) { - throw new TypeError(`Algorithm 'counter' is not of type: ArrayBuffer, DataView, or TypedArray.`); + // The counter must a Uint8Array. + if (!(universalTypeOf(algorithm.counter) === 'Uint8Array')) { + throw new TypeError(`Algorithm 'counter' is not of type: Uint8Array.`); } // The initial value of the counter block must be 16 bytes long (the AES block size). if (algorithm.counter.byteLength !== 16) { diff --git a/packages/crypto/src/algorithms-api/crypto-algorithm.ts b/packages/crypto/src/algorithms-api/crypto-algorithm.ts index f8f4154b6..7f962aaf9 100644 --- a/packages/crypto/src/algorithms-api/crypto-algorithm.ts +++ b/packages/crypto/src/algorithms-api/crypto-algorithm.ts @@ -1,4 +1,4 @@ -import type { Web5Crypto } from '../types/index.js'; +import type { Web5Crypto } from '../types/web5-crypto.js'; import { InvalidAccessError, NotSupportedError } from './errors.js'; @@ -19,7 +19,7 @@ export abstract class CryptoAlgorithm { }): void { const { algorithmName } = options; if (algorithmName === undefined) { - throw new TypeError(`Required argument missing: 'algorithmName'`); + throw new TypeError(`Required parameter missing: 'algorithmName'`); } if (algorithmName !== this.name) { throw new NotSupportedError(`Algorithm not supported: '${algorithmName}'`); @@ -40,7 +40,7 @@ export abstract class CryptoAlgorithm { }): void { const { keyAlgorithmName } = options; if (keyAlgorithmName === undefined) { - throw new TypeError(`Required argument missing: 'keyAlgorithmName'`); + throw new TypeError(`Required parameter missing: 'keyAlgorithmName'`); } if (keyAlgorithmName && keyAlgorithmName !== this.name) { throw new InvalidAccessError(`Algorithm '${this.name}' does not match the provided '${keyAlgorithmName}' key.`); @@ -53,7 +53,7 @@ export abstract class CryptoAlgorithm { }): void { const { keyType, allowedKeyType } = options; if (keyType === undefined || allowedKeyType === undefined) { - throw new TypeError(`One or more required arguments missing: 'keyType, allowedKeyType'`); + throw new TypeError(`One or more required parameters missing: 'keyType, allowedKeyType'`); } if (keyType && keyType !== allowedKeyType) { throw new InvalidAccessError(`Requested operation is not valid for the provided '${keyType}' key.`); @@ -66,7 +66,7 @@ export abstract class CryptoAlgorithm { }): void { const { keyUsages, allowedKeyUsages } = options; if (!(keyUsages && keyUsages.length > 0)) { - throw new TypeError(`required parameter was missing or empty: 'keyUsages'`); + throw new TypeError(`Required parameter missing or empty: 'keyUsages'`); } const allowedUsages = (Array.isArray(allowedKeyUsages)) ? allowedKeyUsages : [...allowedKeyUsages.privateKey, ...allowedKeyUsages.publicKey]; if (!keyUsages.every(usage => allowedUsages.includes(usage))) { @@ -91,20 +91,20 @@ export abstract class CryptoAlgorithm { public abstract decrypt(options: { algorithm: Web5Crypto.AlgorithmIdentifier | Web5Crypto.AesCtrOptions | Web5Crypto.AesGcmOptions, key: Web5Crypto.CryptoKey, - data: BufferSource - }): Promise; + data: Uint8Array + }): Promise; public abstract deriveBits(options: { algorithm: Web5Crypto.AlgorithmIdentifier | Web5Crypto.EcdhDeriveKeyOptions, baseKey: Web5Crypto.CryptoKey, length: number | null - }): Promise; + }): Promise; public abstract encrypt(options: { algorithm: Web5Crypto.AlgorithmIdentifier | Web5Crypto.AesCtrOptions | Web5Crypto.AesGcmOptions, key: Web5Crypto.CryptoKey, - data: BufferSource - }): Promise; + data: Uint8Array + }): Promise; public abstract generateKey(options: { algorithm: Partial, @@ -115,13 +115,13 @@ export abstract class CryptoAlgorithm { public abstract sign(options: { algorithm: Web5Crypto.AlgorithmIdentifier | Web5Crypto.EcdsaOptions | Web5Crypto.EdDsaOptions, key: Web5Crypto.CryptoKey, - data: BufferSource - }): Promise; + data: Uint8Array + }): Promise; public abstract verify(options: { algorithm: Web5Crypto.AlgorithmIdentifier | Web5Crypto.EcdsaOptions | Web5Crypto.EdDsaOptions, key: Web5Crypto.CryptoKey, - signature: ArrayBuffer, - data: BufferSource + signature: Uint8Array, + data: Uint8Array }): Promise; } \ No newline at end of file diff --git a/packages/crypto/src/algorithms-api/crypto-key.ts b/packages/crypto/src/algorithms-api/crypto-key.ts index 8ffe3fec4..91d2477dc 100644 --- a/packages/crypto/src/algorithms-api/crypto-key.ts +++ b/packages/crypto/src/algorithms-api/crypto-key.ts @@ -1,16 +1,16 @@ -import type { Web5Crypto } from '../types/index.js'; +import type { Web5Crypto } from '../types/web5-crypto.js'; export class CryptoKey implements Web5Crypto.CryptoKey { public algorithm: Web5Crypto.GenerateKeyOptions; public extractable: boolean; - public handle: ArrayBuffer; + public material: Uint8Array; public type: Web5Crypto.KeyType; public usages: Web5Crypto.KeyUsage[]; - constructor (algorithm: Web5Crypto.GenerateKeyOptions, extractable: boolean, handle: ArrayBuffer, type: Web5Crypto.KeyType, usages: Web5Crypto.KeyUsage[]) { + constructor (algorithm: Web5Crypto.GenerateKeyOptions, extractable: boolean, material: Uint8Array, type: Web5Crypto.KeyType, usages: Web5Crypto.KeyUsage[]) { this.algorithm = algorithm; this.extractable = extractable; - this.handle = handle; + this.material = material; this.type = type; this.usages = usages; @@ -46,10 +46,10 @@ export class CryptoKey implements Web5Crypto.CryptoKey { // this is the "key material" used internally // it is not enumerable, but we need it to be // accessible by algorithm implementations - handle: { + material: { enumerable : false, writable : false, - value : handle + value : material } }); } diff --git a/packages/crypto/src/algorithms-api/ec/base.ts b/packages/crypto/src/algorithms-api/ec/base.ts index 2943034cd..de47f285d 100644 --- a/packages/crypto/src/algorithms-api/ec/base.ts +++ b/packages/crypto/src/algorithms-api/ec/base.ts @@ -1,8 +1,8 @@ -import type { Web5Crypto } from '../../types/index.js'; +import type { Web5Crypto } from '../../types/web5-crypto.js'; import { InvalidAccessError } from '../errors.js'; import { CryptoAlgorithm } from '../crypto-algorithm.js'; -import { checkValidProperty, checkRequiredProperty } from '../../utils-new.js'; +import { checkValidProperty, checkRequiredProperty } from '../../utils.js'; export abstract class BaseEllipticCurveAlgorithm extends CryptoAlgorithm { @@ -23,11 +23,11 @@ export abstract class BaseEllipticCurveAlgorithm extends CryptoAlgorithm { this.checkKeyUsages({ keyUsages, allowedKeyUsages: this.keyUsages }); } - public override async decrypt(): Promise { + public override async decrypt(): Promise { throw new InvalidAccessError(`Requested operation 'decrypt' is not valid for ${this.name} keys.`); } - public override async encrypt(): Promise { + public override async encrypt(): Promise { throw new InvalidAccessError(`Requested operation 'encrypt' is not valid for ${this.name} keys.`); } diff --git a/packages/crypto/src/algorithms-api/ec/ecdh.ts b/packages/crypto/src/algorithms-api/ec/ecdh.ts index ca8eb3b53..4667c3830 100644 --- a/packages/crypto/src/algorithms-api/ec/ecdh.ts +++ b/packages/crypto/src/algorithms-api/ec/ecdh.ts @@ -1,8 +1,8 @@ -import type { Web5Crypto } from '../../types/index.js'; +import type { Web5Crypto } from '../../types/web5-crypto.js'; import { InvalidAccessError } from '../errors.js'; import { BaseEllipticCurveAlgorithm } from './base.js'; -import { checkRequiredProperty } from '../../utils-new.js'; +import { checkRequiredProperty } from '../../utils.js'; export abstract class BaseEcdhAlgorithm extends BaseEllipticCurveAlgorithm { @@ -43,7 +43,7 @@ export abstract class BaseEcdhAlgorithm extends BaseEllipticCurveAlgorithm { } } - public override async sign(): Promise { + public override async sign(): Promise { throw new InvalidAccessError(`Requested operation 'sign' is not valid for ${this.name} keys.`); } diff --git a/packages/crypto/src/algorithms-api/ec/ecdsa.ts b/packages/crypto/src/algorithms-api/ec/ecdsa.ts index 63d1d8fe7..cfec1b22c 100644 --- a/packages/crypto/src/algorithms-api/ec/ecdsa.ts +++ b/packages/crypto/src/algorithms-api/ec/ecdsa.ts @@ -1,8 +1,8 @@ -import type { Web5Crypto } from '../../types/index.js'; +import type { Web5Crypto } from '../../types/web5-crypto.js'; import { InvalidAccessError } from '../errors.js'; import { BaseEllipticCurveAlgorithm } from './base.js'; -import { checkValidProperty, checkRequiredProperty } from '../../utils-new.js'; +import { checkValidProperty, checkRequiredProperty } from '../../utils.js'; export abstract class BaseEcdsaAlgorithm extends BaseEllipticCurveAlgorithm { @@ -27,11 +27,11 @@ export abstract class BaseEcdsaAlgorithm extends BaseEllipticCurveAlgorithm { checkValidProperty({ property: algorithm.hash, allowedProperties: this.hashAlgorithms }); } - public override async deriveBits(): Promise { + public override async deriveBits(): Promise { throw new InvalidAccessError(`Requested operation 'deriveBits' is not valid for ${this.name} keys.`); } - public abstract sign(options: { algorithm: Web5Crypto.EcdsaOptions; key: Web5Crypto.CryptoKey; data: BufferSource; }): Promise; + public abstract sign(options: { algorithm: Web5Crypto.EcdsaOptions; key: Web5Crypto.CryptoKey; data: Uint8Array; }): Promise; - public abstract verify(options: { algorithm: Web5Crypto.EcdsaOptions; key: Web5Crypto.CryptoKey; signature: ArrayBuffer; data: BufferSource; }): Promise; + public abstract verify(options: { algorithm: Web5Crypto.EcdsaOptions; key: Web5Crypto.CryptoKey; signature: Uint8Array; data: Uint8Array; }): Promise; } \ No newline at end of file diff --git a/packages/crypto/src/algorithms-api/ec/eddsa.ts b/packages/crypto/src/algorithms-api/ec/eddsa.ts index b98f8e83a..75f302221 100644 --- a/packages/crypto/src/algorithms-api/ec/eddsa.ts +++ b/packages/crypto/src/algorithms-api/ec/eddsa.ts @@ -1,4 +1,4 @@ -import type { Web5Crypto } from '../../types/index.js'; +import type { Web5Crypto } from '../../types/web5-crypto.js'; import { InvalidAccessError } from '../errors.js'; import { BaseEllipticCurveAlgorithm } from './base.js'; @@ -20,11 +20,11 @@ export abstract class BaseEdDsaAlgorithm extends BaseEllipticCurveAlgorithm { this.checkAlgorithmName({ algorithmName: algorithm.name }); } - public override async deriveBits(): Promise { + public override async deriveBits(): Promise { throw new InvalidAccessError(`Requested operation 'deriveBits' is not valid for ${this.name} keys.`); } - public abstract sign(options: { algorithm: Web5Crypto.EdDsaOptions; key: Web5Crypto.CryptoKey; data: BufferSource; }): Promise; + public abstract sign(options: { algorithm: Web5Crypto.EdDsaOptions; key: Web5Crypto.CryptoKey; data: Uint8Array; }): Promise; - public abstract verify(options: { algorithm: Web5Crypto.EdDsaOptions; key: Web5Crypto.CryptoKey; signature: ArrayBuffer; data: BufferSource; }): Promise; + public abstract verify(options: { algorithm: Web5Crypto.EdDsaOptions; key: Web5Crypto.CryptoKey; signature: Uint8Array; data: Uint8Array; }): Promise; } \ No newline at end of file diff --git a/packages/crypto/src/crypto-algorithms/aes-ctr.ts b/packages/crypto/src/crypto-algorithms/aes-ctr.ts index 7b2719061..8567f830f 100644 --- a/packages/crypto/src/crypto-algorithms/aes-ctr.ts +++ b/packages/crypto/src/crypto-algorithms/aes-ctr.ts @@ -1,6 +1,6 @@ -import type { Web5Crypto } from '../types/index.js'; +import { universalTypeOf } from '@web5/common'; -import { universalTypeOf } from '@tbd54566975/common'; +import type { Web5Crypto } from '../types/web5-crypto.js'; import { AesCtr } from '../crypto-primitives/index.js'; import { BaseAesCtrAlgorithm, CryptoKey } from '../algorithms-api/index.js'; @@ -9,8 +9,8 @@ export class AesCtrAlgorithm extends BaseAesCtrAlgorithm { public async decrypt(options: { algorithm: Web5Crypto.AesCtrOptions, key: Web5Crypto.CryptoKey, - data: BufferSource - }): Promise { + data: Uint8Array + }): Promise { const { algorithm, key, data } = options; this.checkAlgorithmOptions({ algorithm, key }); @@ -20,7 +20,7 @@ export class AesCtrAlgorithm extends BaseAesCtrAlgorithm { const plaintext = AesCtr.decrypt({ counter : algorithm.counter, data : data, - key : key.handle, + key : key.material, length : algorithm.length }); @@ -30,8 +30,8 @@ export class AesCtrAlgorithm extends BaseAesCtrAlgorithm { public async encrypt(options: { algorithm: Web5Crypto.AesCtrOptions, key: Web5Crypto.CryptoKey, - data: BufferSource - }): Promise { + data: Uint8Array + }): Promise { const { algorithm, key, data } = options; this.checkAlgorithmOptions({ algorithm, key }); @@ -41,7 +41,7 @@ export class AesCtrAlgorithm extends BaseAesCtrAlgorithm { const ciphertext = AesCtr.encrypt({ counter : algorithm.counter, data : data, - key : key.handle, + key : key.material, length : algorithm.length }); @@ -59,7 +59,7 @@ export class AesCtrAlgorithm extends BaseAesCtrAlgorithm { const secretKey = await AesCtr.generateKey({ length: algorithm.length }); - if (universalTypeOf(secretKey) !== 'ArrayBuffer') { + if (universalTypeOf(secretKey) !== 'Uint8Array') { throw new Error('Operation failed to generate key.'); } diff --git a/packages/crypto/src/crypto-algorithms/ecdh.ts b/packages/crypto/src/crypto-algorithms/ecdh.ts index 30d6eb155..a87fa67be 100644 --- a/packages/crypto/src/crypto-algorithms/ecdh.ts +++ b/packages/crypto/src/crypto-algorithms/ecdh.ts @@ -1,6 +1,7 @@ -import type { BufferKeyPair, Web5Crypto } from '../types/index.js'; +import type { Web5Crypto } from '../types/web5-crypto.js'; +import type { BytesKeyPair } from '../types/crypto-key.js'; -import { isBufferKeyPair } from '../utils-new.js'; +import { isBytesKeyPair } from '../utils.js'; import { Secp256k1, X25519 } from '../crypto-primitives/index.js'; import { CryptoKey, BaseEcdhAlgorithm, OperationError } from '../algorithms-api/index.js'; @@ -11,7 +12,7 @@ export class EcdhAlgorithm extends BaseEcdhAlgorithm { algorithm: Web5Crypto.EcdhDeriveKeyOptions, baseKey: Web5Crypto.CryptoKey, length: number | null - }): Promise { + }): Promise { const { algorithm, baseKey, length } = options; this.checkAlgorithmOptions({ algorithm, baseKey }); @@ -20,15 +21,15 @@ export class EcdhAlgorithm extends BaseEcdhAlgorithm { // The public key must be allowed to be used for deriveBits operations. this.checkKeyUsages({ keyUsages: ['deriveBits'], allowedKeyUsages: algorithm.publicKey.usages }); - let sharedSecret: ArrayBuffer; + let sharedSecret: Uint8Array; const ownKeyAlgorithm = baseKey.algorithm as Web5Crypto.EcGenerateKeyOptions; // Type guard. switch (ownKeyAlgorithm.namedCurve) { case 'secp256k1': { - const ownPrivateKey = baseKey.handle; - const otherPartyPublicKey = algorithm.publicKey.handle; + const ownPrivateKey = baseKey.material; + const otherPartyPublicKey = algorithm.publicKey.material; sharedSecret = await Secp256k1.sharedSecret({ privateKey : ownPrivateKey, publicKey : otherPartyPublicKey @@ -37,8 +38,8 @@ export class EcdhAlgorithm extends BaseEcdhAlgorithm { } case 'X25519': { - const ownPrivateKey = baseKey.handle; - const otherPartyPublicKey = algorithm.publicKey.handle; + const ownPrivateKey = baseKey.material; + const otherPartyPublicKey = algorithm.publicKey.material; sharedSecret = await X25519.sharedSecret({ privateKey : ownPrivateKey, publicKey : otherPartyPublicKey @@ -80,7 +81,7 @@ export class EcdhAlgorithm extends BaseEcdhAlgorithm { this.checkGenerateKey({ algorithm, keyUsages }); - let keyPair: BufferKeyPair | undefined; + let keyPair: BytesKeyPair | undefined; let cryptoKeyPair: Web5Crypto.CryptoKeyPair; switch (algorithm.namedCurve) { @@ -100,7 +101,7 @@ export class EcdhAlgorithm extends BaseEcdhAlgorithm { // Default case not needed because checkGenerateKey() already validates the specified namedCurve is supported. } - if (!isBufferKeyPair(keyPair)) { + if (!isBytesKeyPair(keyPair)) { throw new Error('Operation failed to generate key pair.'); } diff --git a/packages/crypto/src/crypto-algorithms/ecdsa.ts b/packages/crypto/src/crypto-algorithms/ecdsa.ts index c661864fd..828a42a6a 100644 --- a/packages/crypto/src/crypto-algorithms/ecdsa.ts +++ b/packages/crypto/src/crypto-algorithms/ecdsa.ts @@ -1,6 +1,7 @@ -import type { BufferKeyPair, Web5Crypto } from '../types/index.js'; +import type { Web5Crypto } from '../types/web5-crypto.js'; +import type { BytesKeyPair } from '../types/crypto-key.js'; -import { isBufferKeyPair } from '../utils-new.js'; +import { isBytesKeyPair } from '../utils.js'; import { Secp256k1 } from '../crypto-primitives/index.js'; import { CryptoKey, BaseEcdsaAlgorithm } from '../algorithms-api/index.js'; export class EcdsaAlgorithm extends BaseEcdsaAlgorithm { @@ -16,7 +17,7 @@ export class EcdsaAlgorithm extends BaseEcdsaAlgorithm { this.checkGenerateKey({ algorithm, keyUsages }); - let keyPair: BufferKeyPair | undefined; + let keyPair: BytesKeyPair | undefined; let cryptoKeyPair: Web5Crypto.CryptoKeyPair; switch (algorithm.namedCurve) { @@ -29,7 +30,7 @@ export class EcdsaAlgorithm extends BaseEcdsaAlgorithm { // Default case not needed because checkGenerateKey() already validates the specified namedCurve is supported. } - if (!isBufferKeyPair(keyPair)) { + if (!isBytesKeyPair(keyPair)) { throw new Error('Operation failed to generate key pair.'); } @@ -44,8 +45,8 @@ export class EcdsaAlgorithm extends BaseEcdsaAlgorithm { public async sign(options: { algorithm: Web5Crypto.EcdsaOptions, key: Web5Crypto.CryptoKey, - data: BufferSource - }): Promise { + data: Uint8Array + }): Promise { const { algorithm, key, data } = options; this.checkAlgorithmOptions({ algorithm }); @@ -56,14 +57,14 @@ export class EcdsaAlgorithm extends BaseEcdsaAlgorithm { // The key must be allowed to be used for sign operations. this.checkKeyUsages({ keyUsages: ['sign'], allowedKeyUsages: key.usages }); - let signature: ArrayBuffer; + let signature: Uint8Array; const keyAlgorithm = key.algorithm as Web5Crypto.EcdsaGenerateKeyOptions; // Type guard. switch (keyAlgorithm.namedCurve) { case 'secp256k1': { - signature = await Secp256k1.sign({ hash: algorithm.hash, key: key.handle, data }); + signature = await Secp256k1.sign({ hash: algorithm.hash, key: key.material, data }); break; } @@ -77,8 +78,8 @@ export class EcdsaAlgorithm extends BaseEcdsaAlgorithm { public async verify(options: { algorithm: Web5Crypto.EcdsaOptions; key: Web5Crypto.CryptoKey; - signature: ArrayBuffer; - data: BufferSource; + signature: Uint8Array; + data: Uint8Array; }): Promise { const { algorithm, key, signature, data } = options; @@ -97,7 +98,7 @@ export class EcdsaAlgorithm extends BaseEcdsaAlgorithm { switch (keyAlgorithm.namedCurve) { case 'secp256k1': { - isValid = await Secp256k1.verify({ hash: algorithm.hash, key: key.handle, signature, data }); + isValid = await Secp256k1.verify({ hash: algorithm.hash, key: key.material, signature, data }); break; } diff --git a/packages/crypto/src/crypto-algorithms/eddsa.ts b/packages/crypto/src/crypto-algorithms/eddsa.ts index 2309d1d49..1932443ba 100644 --- a/packages/crypto/src/crypto-algorithms/eddsa.ts +++ b/packages/crypto/src/crypto-algorithms/eddsa.ts @@ -1,6 +1,7 @@ -import type { BufferKeyPair, Web5Crypto } from '../types/index.js'; +import type { Web5Crypto } from '../types/web5-crypto.js'; +import type { BytesKeyPair } from '../types/crypto-key.js'; -import { isBufferKeyPair } from '../utils-new.js'; +import { isBytesKeyPair } from '../utils.js'; import { Ed25519 } from '../crypto-primitives/index.js'; import { CryptoKey, BaseEdDsaAlgorithm } from '../algorithms-api/index.js'; @@ -16,7 +17,7 @@ export class EdDsaAlgorithm extends BaseEdDsaAlgorithm { this.checkGenerateKey({ algorithm, keyUsages }); - let keyPair: BufferKeyPair | undefined; + let keyPair: BytesKeyPair | undefined; let cryptoKeyPair: Web5Crypto.CryptoKeyPair; switch (algorithm.namedCurve) { @@ -28,7 +29,7 @@ export class EdDsaAlgorithm extends BaseEdDsaAlgorithm { // Default case not needed because checkGenerateKey() already validates the specified namedCurve is supported. } - if (!isBufferKeyPair(keyPair)) { + if (!isBytesKeyPair(keyPair)) { throw new Error('Operation failed to generate key pair.'); } @@ -43,8 +44,8 @@ export class EdDsaAlgorithm extends BaseEdDsaAlgorithm { public async sign(options: { algorithm: Web5Crypto.EdDsaOptions, key: Web5Crypto.CryptoKey, - data: BufferSource - }): Promise { + data: Uint8Array + }): Promise { const { algorithm, key, data } = options; this.checkAlgorithmOptions({ algorithm }); @@ -55,14 +56,14 @@ export class EdDsaAlgorithm extends BaseEdDsaAlgorithm { // The key must be allowed to be used for sign operations. this.checkKeyUsages({ keyUsages: ['sign'], allowedKeyUsages: key.usages }); - let signature: ArrayBuffer; + let signature: Uint8Array; const keyAlgorithm = key.algorithm as Web5Crypto.EdDsaGenerateKeyOptions; // Type guard. switch (keyAlgorithm.namedCurve) { case 'Ed25519': { - signature = await Ed25519.sign({ key: key.handle, data }); + signature = await Ed25519.sign({ key: key.material, data }); break; } @@ -76,8 +77,8 @@ export class EdDsaAlgorithm extends BaseEdDsaAlgorithm { public async verify(options: { algorithm: Web5Crypto.EdDsaOptions; key: Web5Crypto.CryptoKey; - signature: ArrayBuffer; - data: BufferSource; + signature: Uint8Array; + data: Uint8Array; }): Promise { const { algorithm, key, signature, data } = options; @@ -96,7 +97,7 @@ export class EdDsaAlgorithm extends BaseEdDsaAlgorithm { switch (keyAlgorithm.namedCurve) { case 'Ed25519': { - isValid = await Ed25519.verify({ key: key.handle, signature, data }); + isValid = await Ed25519.verify({ key: key.material, signature, data }); break; } diff --git a/packages/crypto/src/crypto-primitives/aes-ctr.ts b/packages/crypto/src/crypto-primitives/aes-ctr.ts index 08db00555..f218bdf63 100644 --- a/packages/crypto/src/crypto-primitives/aes-ctr.ts +++ b/packages/crypto/src/crypto-primitives/aes-ctr.ts @@ -6,7 +6,7 @@ import { crypto } from '@noble/hashes/crypto'; * operations. The class uses the Web Crypto API for cryptographic operations. * * All methods of this class are asynchronous and return Promises. They all - * use the ArrayBuffer type for keys and data, providing a consistent + * use the Uint8Array type for keys and data, providing a consistent * interface for working with binary data. * * Example usage: @@ -39,25 +39,28 @@ export class AesCtr { * @param options.data - The data to decrypt. * @param options.key - The key to use for decryption. * @param options.length - The length of the counter block in bits. - * @returns A Promise that resolves to the decrypted data as an ArrayBuffer. + * @returns A Promise that resolves to the decrypted data as a Uint8Array. */ public static async decrypt(options: { - counter: BufferSource, - data: BufferSource, - key: ArrayBuffer, + counter: Uint8Array, + data: Uint8Array, + key: Uint8Array, length: number - }): Promise { + }): Promise { const { counter, data, key, length } = options; const webCryptoKey = await this.importKey(key); - const ciphertext = await crypto.subtle.decrypt( + const plaintextBuffer = await crypto.subtle.decrypt( { name: 'AES-CTR', counter, length }, webCryptoKey, data ); - return ciphertext; + // Convert from ArrayBuffer to Uint8Array. + const plaintext = new Uint8Array(plaintextBuffer); + + return plaintext; } /** @@ -68,43 +71,46 @@ export class AesCtr { * @param options.data - The data to encrypt. * @param options.key - The key to use for encryption. * @param options.length - The length of the counter block in bits. - * @returns A Promise that resolves to the encrypted data as an ArrayBuffer. + * @returns A Promise that resolves to the encrypted data as a Uint8Array. */ public static async encrypt(options: { - counter: BufferSource, - data: BufferSource, - key: ArrayBuffer, + counter: Uint8Array, + data: Uint8Array, + key: Uint8Array, length: number - }): Promise { + }): Promise { const { counter, data, key, length } = options; const webCryptoKey = await this.importKey(key); - const plaintext = await crypto.subtle.encrypt( + const ciphertextBuffer = await crypto.subtle.encrypt( { name: 'AES-CTR', counter, length }, webCryptoKey, data ); - return plaintext; + // Convert from ArrayBuffer to Uint8Array. + const ciphertext = new Uint8Array(ciphertextBuffer); + + return ciphertext; } /** * Generates an AES key of a given length. * * @param length - The length of the key in bits. - * @returns A Promise that resolves to the generated key as an ArrayBuffer. + * @returns A Promise that resolves to the generated key as a Uint8Array. */ public static async generateKey(options: { length: number - }): Promise { + }): Promise { const { length } = options; // Generate the secret key. const lengthInBytes = length / 8; const secretKey = crypto.getRandomValues(new Uint8Array(lengthInBytes)); - return secretKey.buffer; + return secretKey; } /** @@ -113,10 +119,10 @@ export class AesCtr { * @param key - The raw key material. * @returns A Promise that resolves to a CryptoKey. */ - private static async importKey(key: ArrayBuffer): Promise { + private static async importKey(key: Uint8Array): Promise { return crypto.subtle.importKey( 'raw', - key, + key.buffer, { name: 'AES-CTR', length: key.byteLength * 8 }, true, ['encrypt', 'decrypt'] diff --git a/packages/crypto/src/crypto-primitives/aes-gcm.ts b/packages/crypto/src/crypto-primitives/aes-gcm.ts index f3e19fdf7..852bb15e7 100644 --- a/packages/crypto/src/crypto-primitives/aes-gcm.ts +++ b/packages/crypto/src/crypto-primitives/aes-gcm.ts @@ -7,7 +7,7 @@ import { crypto } from '@noble/hashes/crypto'; * cryptographic operations. * * All methods of this class are asynchronous and return Promises. They all - * use the ArrayBuffer type for keys and data, providing a consistent + * use the Uint8Array type for keys and data, providing a consistent * interface for working with binary data. * * Example usage: @@ -41,26 +41,30 @@ export class AesGcm { * @param options.iv - A unique initialization vector. * @param options.key - The key to use for decryption. * @param options.tagLength - This size of the authentication tag generated in bits. - * @returns A Promise that resolves to the decrypted data as an ArrayBuffer. + * @returns A Promise that resolves to the decrypted data as a Uint8Array. */ public static async decrypt(options: { - additionalData?: BufferSource, - data: BufferSource, - iv: BufferSource, - key: ArrayBuffer, + additionalData?: Uint8Array, + data: Uint8Array, + iv: Uint8Array, + key: Uint8Array, tagLength?: number - }): Promise { + }): Promise { const { additionalData, data, iv, key, tagLength } = options; const webCryptoKey = await this.importKey(key); + // Web browsers throw an error if additionalData is undefined. const algorithm = (additionalData === undefined) ? { name: 'AES-GCM', iv, tagLength } : { name: 'AES-GCM', additionalData, iv, tagLength }; - const ciphertext = await crypto.subtle.decrypt(algorithm, webCryptoKey, data); + const plaintextBuffer = await crypto.subtle.decrypt(algorithm, webCryptoKey, data); - return ciphertext; + // Convert from ArrayBuffer to Uint8Array. + const plaintext = new Uint8Array(plaintextBuffer); + + return plaintext; } /** @@ -72,44 +76,48 @@ export class AesGcm { * @param options.iv - A unique initialization vector. * @param options.key - The key to use for decryption. * @param options.tagLength - This size of the authentication tag generated in bits. - * @returns A Promise that resolves to the encrypted data as an ArrayBuffer. + * @returns A Promise that resolves to the encrypted data as a Uint8Array. */ public static async encrypt(options: { - additionalData?: BufferSource, - data: BufferSource, - iv: BufferSource, - key: ArrayBuffer, + additionalData?: Uint8Array, + data: Uint8Array, + iv: Uint8Array, + key: Uint8Array, tagLength?: number - }): Promise { + }): Promise { const { additionalData, data, iv, key, tagLength } = options; const webCryptoKey = await this.importKey(key); + // Web browsers throw an error if additionalData is undefined. const algorithm = (additionalData === undefined) ? { name: 'AES-GCM', iv, tagLength } : { name: 'AES-GCM', additionalData, iv, tagLength }; - const plaintext = await crypto.subtle.encrypt(algorithm, webCryptoKey, data); + const ciphertextBuffer = await crypto.subtle.encrypt(algorithm, webCryptoKey, data); - return plaintext; + // Convert from ArrayBuffer to Uint8Array. + const ciphertext = new Uint8Array(ciphertextBuffer); + + return ciphertext; } /** * Generates an AES key of a given length. * * @param length - The length of the key in bits. - * @returns A Promise that resolves to the generated key as an ArrayBuffer. + * @returns A Promise that resolves to the generated key as a Uint8Array. */ public static async generateKey(options: { length: number - }): Promise { + }): Promise { const { length } = options; // Generate the secret key. const lengthInBytes = length / 8; const secretKey = crypto.getRandomValues(new Uint8Array(lengthInBytes)); - return secretKey.buffer; + return secretKey; } /** @@ -118,10 +126,10 @@ export class AesGcm { * @param key - The raw key material. * @returns A Promise that resolves to a CryptoKey. */ - private static async importKey(key: ArrayBuffer): Promise { + private static async importKey(key: Uint8Array): Promise { return crypto.subtle.importKey( 'raw', - key, + key.buffer, { name: 'AES-GCM', length: key.byteLength * 8 }, true, ['encrypt', 'decrypt'] diff --git a/packages/crypto/src/crypto-primitives/concat-kdf.ts b/packages/crypto/src/crypto-primitives/concat-kdf.ts index b5debe003..20b739e2e 100644 --- a/packages/crypto/src/crypto-primitives/concat-kdf.ts +++ b/packages/crypto/src/crypto-primitives/concat-kdf.ts @@ -1,6 +1,6 @@ import { sha256 } from '@noble/hashes/sha256'; +import { Convert, universalTypeOf } from '@web5/common'; import { TypedArray, concatBytes } from '@noble/hashes/utils'; -import { Convert, universalTypeOf } from '@tbd54566975/common'; import { NotSupportedError } from '../algorithms-api/errors.js'; @@ -14,14 +14,14 @@ export type ConcatKdfOtherInfo = { * Information related to party U (initiator) involved in the key agreement * transaction. It could be a public key, identifier, or any other data. */ - partyUInfo: ArrayBuffer | string | TypedArray; + partyUInfo: string | TypedArray; /** * Information related to party V (receiver) involved in the key * agreement transaction. Similar to partyUInfo, it could be a * public key, identifier, etc. */ - partyVInfo: ArrayBuffer | string | TypedArray; + partyVInfo: string | TypedArray; /** * Optional field. It is usually used to ensure the uniqueness of the @@ -37,7 +37,7 @@ export type ConcatKdfOtherInfo = { * It is a secret value agreed upon by the entities who are party * to the key agreement. */ - suppPrivInfo?: ArrayBuffer | string | TypedArray; + suppPrivInfo?: string | TypedArray; } /** @@ -79,15 +79,15 @@ export class ConcatKdf { * @param options.keyDataLen - The desired length of the derived key in bits. * @param options.sharedSecret - The shared secret key to derive from. * @param options.otherInfo - Additional public information to use in key derivation. - * @returns The derived key as an ArrayBuffer. + * @returns The derived key as a Uint8Array. * * @throws {NotSupportedError} If the keyDataLen would require multiple rounds. */ public static async deriveKey(options: { keyDataLen: number; otherInfo: ConcatKdfOtherInfo, - sharedSecret: ArrayBuffer, - }): Promise { + sharedSecret: Uint8Array, + }): Promise { const { keyDataLen, sharedSecret } = options; // RFC 7518 Section 4.6.2 specifies using SHA-256 for ECDH key agreement: @@ -106,18 +106,15 @@ export class ConcatKdf { const counter = new Uint8Array(4); new DataView(counter.buffer).setUint32(0, roundCount); - // Specify input parameter Z, which is the shared secret output of ECDH. - const sharedSecretU8A = new Uint8Array(sharedSecret); - // Compute the OtherInfo bit-string. const otherInfo = ConcatKdf.computeOtherInfo(options.otherInfo); // Compute K(i) = H(counter || Z || OtherInfo) - // return concatBytes(counter, sharedSecretU8A, otherInfo); - const derivedKeyingMaterial = sha256(concatBytes(counter, sharedSecretU8A, otherInfo)); + // return concatBytes(counter, sharedSecretZ, otherInfo); + const derivedKeyingMaterial = sha256(concatBytes(counter, sharedSecret, otherInfo)); // Return the bit string of derived keying material of length keyDataLen bits. - return derivedKeyingMaterial.buffer.slice(0, keyDataLen / 8); + return derivedKeyingMaterial.slice(0, keyDataLen / 8); } /** diff --git a/packages/crypto/src/crypto-primitives/ed25519.ts b/packages/crypto/src/crypto-primitives/ed25519.ts index 516d06419..2ba767718 100644 --- a/packages/crypto/src/crypto-primitives/ed25519.ts +++ b/packages/crypto/src/crypto-primitives/ed25519.ts @@ -1,7 +1,6 @@ -import type { BufferKeyPair } from '../types/index.js'; +import type { BytesKeyPair } from '../types/crypto-key.js'; -import { Convert } from '@tbd54566975/common'; -import { ed25519 } from '@noble/curves/ed25519'; +import { ed25519, edwardsToMontgomeryPub, edwardsToMontgomeryPriv } from '@noble/curves/ed25519'; /** * The `Ed25519` class provides an interface for generating Ed25519 key pairs, @@ -10,7 +9,7 @@ import { ed25519 } from '@noble/curves/ed25519'; * The class uses the '@noble/curves' package for the cryptographic operations. * * The methods of this class are all asynchronous and return Promises. They all use - * the ArrayBuffer type for keys, signatures, and data, providing a consistent + * the Uint8Array type for keys, signatures, and data, providing a consistent * interface for working with binary data. * * Example usage: @@ -31,19 +30,73 @@ import { ed25519 } from '@noble/curves/ed25519'; * ``` */ export class Ed25519 { + + /** + * Converts an Ed25519 private key to its X25519 counterpart. + * + * Similar to the public key conversion, this method aids in transitioning + * from signing to encryption operations. By converting an Ed25519 private + * key to X25519 format, one can use the same key pair for both digital + * signatures and key exchange operations. + * + * @param options - The options for the conversion. + * @param options.privateKey - The Ed25519 private key to convert, represented as a Uint8Array. + * @returns A Promise that resolves to the X25519 private key as a Uint8Array. + */ + public static async convertPrivateKeyToX25519(options: { + privateKey: Uint8Array + }): Promise { + const { privateKey } = options; + + // Converts Ed25519 private key to X25519 private key. + const montgomeryPrivateKey = edwardsToMontgomeryPriv(privateKey); + + return montgomeryPrivateKey; + } + + /** + * Converts an Ed25519 public key to its X25519 counterpart. + * + * This method is useful when transitioning from signing to encryption + * operations, as Ed25519 and X25519 keys share the same mathematical + * foundation but serve different purposes. Ed25519 keys are used for + * digital signatures, while X25519 keys are used for key exchange in + * encryption protocols like Diffie-Hellman. + * + * @param options - The options for the conversion. + * @param options.publicKey - The Ed25519 public key to convert, represented as a Uint8Array. + * @returns A Promise that resolves to the X25519 public key as a Uint8Array. + */ + public static async convertPublicKeyToX25519(options: { + publicKey: Uint8Array + }): Promise { + const { publicKey } = options; + + // Verify Edwards public key is valid. + const isValid = await Ed25519.validatePublicKey({ key: publicKey }); + if (!isValid) { + throw new Error('Ed25519: Invalid public key.'); + } + + // Converts Ed25519 public key to X25519 public key. + const montgomeryPublicKey = edwardsToMontgomeryPub(publicKey); + + return montgomeryPublicKey; + } + /** * Generates an Ed25519 key pair. * - * @returns A Promise that resolves to an object containing the private and public keys as ArrayBuffers. + * @returns A Promise that resolves to an object containing the private and public keys as Uint8Array. */ - public static async generateKeyPair(): Promise { + public static async generateKeyPair(): Promise { // Generate the private key and compute its public key. const privateKey = ed25519.utils.randomPrivateKey(); const publicKey = ed25519.getPublicKey(privateKey); const keyPair = { - privateKey : privateKey.buffer, - publicKey : publicKey.buffer + privateKey : privateKey, + publicKey : publicKey }; return keyPair; @@ -54,18 +107,15 @@ export class Ed25519 { * * @param options - The options for the public key computation. * @param options.privateKey - The 32-byte private key from which to compute the public key. - * @returns A Promise that resolves to the computed 32-byte public key as an ArrayBuffer. + * @returns A Promise that resolves to the computed 32-byte public key as a Uint8Array. */ public static async getPublicKey(options: { - privateKey: ArrayBuffer - }): Promise { + privateKey: Uint8Array + }): Promise { let { privateKey } = options; - // Convert key material from ArrayBuffer to Uint8Array. - const privateKeyU8A = Convert.arrayBuffer(privateKey).toUint8Array(); - // Compute public key. - const publicKey = ed25519.getPublicKey(privateKeyU8A); + const publicKey = ed25519.getPublicKey(privateKey); return publicKey; } @@ -76,24 +126,55 @@ export class Ed25519 { * @param options - The options for the signing operation. * @param options.key - The private key to use for signing. * @param options.data - The data to sign. - * @returns A Promise that resolves to the signature as an ArrayBuffer. + * @returns A Promise that resolves to the signature as a Uint8Array. */ public static async sign(options: { - data: BufferSource, - key: ArrayBuffer - }): Promise { + data: Uint8Array, + key: Uint8Array + }): Promise { const { key, data } = options; - // Convert data from BufferSource to Uint8Array. - const dataU8A = Convert.bufferSource(data).toUint8Array(); + // Signature operation. + const signature = ed25519.sign(data, key); - // Convert private key material from ArrayBuffer to Uint8Array. - const privateKeyU8A = Convert.arrayBuffer(key).toUint8Array(); + return signature; + } - // Signature operation. - const signatureU8A = ed25519.sign(dataU8A, privateKeyU8A); + /** + * Validates a given public key to ensure that it corresponds to a + * valid point on the Ed25519 elliptic curve. + * + * This method decodes the Edwards points from the key bytes and + * asserts their validity on the curve. If the points are not valid, + * the method returns false. If the points are valid, the method + * returns true. + * + * Note: This method does not check whether the key corresponds to a + * known or authorized entity, or whether it has been compromised. + * It only checks the mathematical validity of the key. + * + * @param options - The options for the key validation. + * @param options.key - The key to validate, represented as a Uint8Array. + * @returns A Promise that resolves to a boolean indicating whether the key + * corresponds to a valid point on the Edwards curve. + */ + public static async validatePublicKey(options: { + key: Uint8Array + }): Promise { + const { key } = options; + + try { + // Decode Edwards points from key bytes. + const point = ed25519.ExtendedPoint.fromHex(key); + + // Check if points are on the Twisted Edwards curve. + point.assertValidity(); - return signatureU8A.buffer; + } catch(error: any) { + return false; + } + + return true; } /** @@ -106,23 +187,14 @@ export class Ed25519 { * @returns A Promise that resolves to a boolean indicating whether the signature is valid. */ public static async verify(options: { - data: BufferSource, - key: ArrayBuffer, - signature: ArrayBuffer + data: Uint8Array, + key: Uint8Array, + signature: Uint8Array }): Promise { const { key, signature, data } = options; - // Convert public key material from ArrayBuffer to Uint8Array. - const publicKeyU8A = Convert.arrayBuffer(key).toUint8Array(); - - // Convert signature from ArrayBuffer to Uint8Array. - const signatureU8A = Convert.arrayBuffer(signature).toUint8Array(); - - // Convert data from BufferSource to Uint8Array. - const dataU8A = Convert.bufferSource(data).toUint8Array(); - // Verify operation. - const isValid = ed25519.verify(signatureU8A, dataU8A, publicKeyU8A); + const isValid = ed25519.verify(signature, data, key); return isValid; } diff --git a/packages/crypto/src/crypto-primitives/index.ts b/packages/crypto/src/crypto-primitives/index.ts index 6bf83f66a..790070b1b 100644 --- a/packages/crypto/src/crypto-primitives/index.ts +++ b/packages/crypto/src/crypto-primitives/index.ts @@ -2,6 +2,7 @@ export * from './x25519.js'; export * from './aes-ctr.js'; export * from './aes-gcm.js'; export * from './ed25519.js'; +export * from './concat-kdf.js'; export * from './secp256k1.js'; export * from './xchacha20.js'; -export * from './concat-kdf.js'; \ No newline at end of file +export * from './xchacha20-poly1305.js'; \ No newline at end of file diff --git a/packages/crypto/src/crypto-primitives/secp256k1.ts b/packages/crypto/src/crypto-primitives/secp256k1.ts index 0c56dbdf9..2626eb4de 100644 --- a/packages/crypto/src/crypto-primitives/secp256k1.ts +++ b/packages/crypto/src/crypto-primitives/secp256k1.ts @@ -1,8 +1,8 @@ -import type { BufferKeyPair } from '../types/index.js'; +import type { BytesKeyPair } from '../types/crypto-key.js'; -import { Convert } from '@tbd54566975/common'; import { sha256 } from '@noble/hashes/sha256'; import { secp256k1 } from '@noble/curves/secp256k1'; +import { numberToBytesBE } from '@noble/curves/abstract/utils'; export type HashFunction = (data: Uint8Array) => Uint8Array; @@ -16,7 +16,7 @@ export type HashFunction = (data: Uint8Array) => Uint8Array; * for the signing and verification operations. * * The methods of this class are all asynchronous and return Promises. They all use - * the ArrayBuffer type for keys, signatures, and data, providing a consistent + * the Uint8Array type for keys, signatures, and data, providing a consistent * interface for working with binary data. * * Example usage: @@ -48,16 +48,50 @@ export class Secp256k1 { 'SHA-256': sha256 }; + /** + * Converts a public key between its compressed and uncompressed forms. + * + * Given a public key, this method can either compress or decompress it + * depending on the provided `compressedPublicKey` option. The conversion + * process involves decoding the Weierstrass points from the key bytes + * and then returning the key in the desired format. + * + * This is useful in scenarios where space is a consideration or when + * interfacing with systems that expect a specific public key format. + * + * @param options - The options for the public key conversion. + * @param options.publicKey - The original public key, represented as a Uint8Array. + * @param options.compressedPublicKey - A boolean indicating whether the output + * should be in compressed form. If true, the + * method returns the compressed form of the + * provided public key. If false, it returns + * the uncompressed form. + * + * @returns A Promise that resolves to the converted public key as a Uint8Array. + */ + public static async convertPublicKey(options: { + publicKey: Uint8Array, + compressedPublicKey: boolean + }): Promise { + let { publicKey, compressedPublicKey } = options; + + // Decode Weierstrass points from key bytes. + const point = secp256k1.ProjectivePoint.fromHex(publicKey); + + // Return either the compressed or uncompressed form of hte public key. + return point.toRawBytes(compressedPublicKey); + } + /** * Generates a secp256k1 key pair. * * @param options - Optional parameters for the key generation. * @param options.compressedPublicKey - If true, generates a compressed public key. Defaults to true. - * @returns A Promise that resolves to an object containing the private and public keys as ArrayBuffers. + * @returns A Promise that resolves to an object containing the private and public keys as Uint8Array. */ - public static async generateKeyPair( - options?: { compressedPublicKey?: boolean } - ): Promise { + public static async generateKeyPair(options?: { + compressedPublicKey?: boolean + }): Promise { let { compressedPublicKey } = options ?? { }; compressedPublicKey ??= true; // Default to compressed public key, matching the default of @noble/secp256k1. @@ -67,13 +101,53 @@ export class Secp256k1 { const publicKey = secp256k1.getPublicKey(privateKey, compressedPublicKey); const keyPair = { - privateKey : privateKey.buffer, - publicKey : publicKey.buffer + privateKey : privateKey, + publicKey : publicKey }; return keyPair; } + /** + * Returns the elliptic curve points (x and y coordinates) for a given secp256k1 key. + * + * In the case of a private key, the public key is first computed from the private key, + * then the x and y coordinates of the public key point on the elliptic curve are returned. + * + * In the case of a public key, the x and y coordinates of the key point on the elliptic + * curve are returned directly. + * + * The returned coordinates can be used to perform various operations on the elliptic curve, + * such as addition and multiplication of points, which can be used in various cryptographic + * schemes and protocols. + * + * @param options - The options for the operation. + * @param options.key - The key for which to get the elliptic curve points. + * Can be either a private key or a public key. + * The key should be passed as a Uint8Array. + * @returns A Promise that resolves to an object with properties 'x' and 'y', + * each being a Uint8Array representing the x and y coordinates of the key point on the elliptic curve. + */ + public static async getCurvePoints(options: { + key: Uint8Array + }): Promise<{ x: Uint8Array, y: Uint8Array }> { + let { key } = options; + + // If key is a private key, first compute the public key. + if (key.byteLength === 32) { + key = await Secp256k1.getPublicKey({ privateKey: key }); + } + + // Decode Weierstrass points from key bytes. + const point = secp256k1.ProjectivePoint.fromHex(key); + + // Get x- and y-coordinate values and convert to Uint8Array. + const x = numberToBytesBE(point.x, 32); + const y = numberToBytesBE(point.y, 32); + + return { x, y }; + } + /** * Computes the public key from a given private key. * If compressedPublicKey=true then the output is a 33-byte public key. @@ -82,20 +156,18 @@ export class Secp256k1 { * @param options - The options for the public key computation. * @param options.privateKey - The 32-byte private key from which to compute the public key. * @param options.compressedPublicKey - If true, returns a compressed public key. Defaults to true. - * @returns A Promise that resolves to the computed public key as an ArrayBuffer. + * @returns A Promise that resolves to the computed public key as a Uint8Array. */ - public static async getPublicKey( - options: { privateKey: ArrayBuffer, compressedPublicKey?: boolean } - ): Promise { + public static async getPublicKey(options: { + privateKey: Uint8Array, + compressedPublicKey?: boolean + }): Promise { let { privateKey, compressedPublicKey } = options; compressedPublicKey ??= true; // Default to compressed public key, matching the default of @noble/secp256k1. - // Convert key material from ArrayBuffer to Uint8Array. - const privateKeyU8A = Convert.arrayBuffer(privateKey).toUint8Array(); - // Compute public key. - const publicKey = secp256k1.getPublicKey(privateKeyU8A, compressedPublicKey); + const publicKey = secp256k1.getPublicKey(privateKey, compressedPublicKey); return publicKey; } @@ -116,21 +188,17 @@ export class Secp256k1 { */ public static async sharedSecret(options: { compressedSecret?: boolean, - privateKey: ArrayBuffer, - publicKey: ArrayBuffer - }): Promise { + privateKey: Uint8Array, + publicKey: Uint8Array + }): Promise { let { privateKey, publicKey } = options; - // Convert private and public key material from ArrayBuffer to Uint8Array. - const privateKeyU8A = Convert.arrayBuffer(privateKey).toUint8Array(); - const publicKeyU8A = Convert.arrayBuffer(publicKey).toUint8Array(); - // Compute the shared secret between the public and private keys. - const sharedSecret = secp256k1.getSharedSecret(privateKeyU8A, publicKeyU8A); + const sharedSecret = secp256k1.getSharedSecret(privateKey, publicKey); // Remove the leading byte that indicates the sign of the y-coordinate // of the point on the elliptic curve. See note above. - return sharedSecret.slice(1).buffer; + return sharedSecret.slice(1); } /** @@ -140,32 +208,85 @@ export class Secp256k1 { * @param options.data - The data to sign. * @param options.hash - The hash algorithm to use to generate a digest of the data. * @param options.key - The private key to use for signing. - * @returns A Promise that resolves to the signature as an ArrayBuffer. + * @returns A Promise that resolves to the signature as a Uint8Array. */ public static async sign(options: { - data: BufferSource, + data: Uint8Array, hash: string, - key: ArrayBuffer - }): Promise { + key: Uint8Array + }): Promise { const { data, hash, key } = options; - // Convert data from BufferSource to Uint8Array. - const dataU8A = Convert.bufferSource(data).toUint8Array(); - // Generate a digest of the data using the specified hash function. const hashFunction = this.hashAlgorithms[hash]; - const digest = hashFunction(dataU8A); - - // Convert private key material from ArrayBuffer to Uint8Array. - const privateKeyU8A = Convert.arrayBuffer(key).toUint8Array(); + const digest = hashFunction(data); // Signature operation returns a Signature instance with { r, s, recovery } properties. - const signatureObject = secp256k1.sign(digest, privateKeyU8A); + const signatureObject = secp256k1.sign(digest, key); // Convert Signature object to Uint8Array. - const signatureU8A = signatureObject.toCompactRawBytes(); + const signature = signatureObject.toCompactRawBytes(); - return signatureU8A.buffer; + return signature; + } + + /** + * Validates a given private key to ensure that it's a valid 32-byte number + * that is less than the secp256k1 curve's order. + * + * This method checks the byte length of the key and its numerical validity + * according to the secp256k1 curve's parameters. It doesn't verify whether + * the key corresponds to a known or authorized entity or whether it has + * been compromised. + * + * @param options - The options for the key validation. + * @param options.key - The private key to validate, represented as a Uint8Array. + * @returns A Promise that resolves to a boolean indicating whether the private + * key is a valid 32-byte number less than the secp256k1 curve's order. + */ + public static async validatePrivateKey(options: { + key: Uint8Array + }): Promise { + const { key } = options; + + return secp256k1.utils.isValidPrivateKey(key); + } + + /** + * Validates a given public key to ensure that it corresponds to a + * valid point on the secp256k1 elliptic curve. + * + * This method decodes the Weierstrass points from the key bytes and + * asserts their validity on the curve. If the points are not valid, + * the method returns false. If the points are valid, the method + * returns true. + * + * Note: This method does not check whether the key corresponds to a + * known or authorized entity, or whether it has been compromised. + * It only checks the mathematical validity of the key. + * + * @param options - The options for the key validation. + * @param options.key - The key to validate, represented as a Uint8Array. + * @returns A Promise that resolves to a boolean indicating whether the key + * corresponds to a valid point on the secp256k1 elliptic curve. + */ + public static async validatePublicKey(options: { + key: Uint8Array + }): Promise { + const { key } = options; + + try { + // Decode Weierstrass points from key bytes. + const point = secp256k1.ProjectivePoint.fromHex(key); + + // Check if points are on the Short Weierstrass curve. + point.assertValidity(); + + } catch(error: any) { + return false; + } + + return true; } /** @@ -179,28 +300,19 @@ export class Secp256k1 { * @returns A Promise that resolves to a boolean indicating whether the signature is valid. */ public static async verify(options: { - data: BufferSource, + data: Uint8Array, hash: string, - key: ArrayBuffer, - signature: ArrayBuffer + key: Uint8Array, + signature: Uint8Array }): Promise { const { data, hash, key, signature } = options; - // Convert public key material from ArrayBuffer to Uint8Array. - const publicKeyU8A = Convert.arrayBuffer(key).toUint8Array(); - - // Convert signature from ArrayBuffer to Uint8Array. - const signatureU8A = Convert.arrayBuffer(signature).toUint8Array(); - - // Convert data from BufferSource to Uint8Array. - const dataU8A = Convert.bufferSource(data).toUint8Array(); - // Generate a digest of the data using the specified hash function. const hashFunction = this.hashAlgorithms[hash]; - const digest = hashFunction(dataU8A); + const digest = hashFunction(data); // Verify operation. - const isValid = secp256k1.verify(signatureU8A, digest, publicKeyU8A); + const isValid = secp256k1.verify(signature, digest, key); return isValid; } diff --git a/packages/crypto/src/crypto-primitives/x25519.ts b/packages/crypto/src/crypto-primitives/x25519.ts index a9b935499..522b36bcd 100644 --- a/packages/crypto/src/crypto-primitives/x25519.ts +++ b/packages/crypto/src/crypto-primitives/x25519.ts @@ -1,6 +1,5 @@ -import type { BufferKeyPair } from '../types/index.js'; +import type { BytesKeyPair } from '../types/crypto-key.js'; -import { Convert } from '@tbd54566975/common'; import { x25519 } from '@noble/curves/ed25519'; /** @@ -9,7 +8,7 @@ import { x25519 } from '@noble/curves/ed25519'; * uses the '@noble/curves/ed25519' package for the cryptographic operations. * * All methods of this class are asynchronous and return Promises. They all use - * the ArrayBuffer type for keys and data, providing a consistent + * the Uint8Array type for keys and data, providing a consistent * interface for working with binary data. * * Example usage: @@ -27,16 +26,16 @@ export class X25519 { /** * Generates a key pair for X25519 (private and public key). * - * @returns A Promise that resolves to a BufferKeyPair object. + * @returns A Promise that resolves to a BytesKeyPair object. */ - public static async generateKeyPair(): Promise { + public static async generateKeyPair(): Promise { // Generate the private key and compute its public key. const privateKey = x25519.utils.randomPrivateKey(); const publicKey = x25519.getPublicKey(privateKey); const keyPair = { - privateKey : privateKey.buffer, - publicKey : publicKey.buffer + privateKey : privateKey, + publicKey : publicKey }; return keyPair; @@ -47,18 +46,15 @@ export class X25519 { * * @param options - The options for the public key computation operation. * @param options.privateKey - The private key used to compute the public key. - * @returns A Promise that resolves to the computed public key as an ArrayBuffer. + * @returns A Promise that resolves to the computed public key as a Uint8Array. */ public static async getPublicKey(options: { - privateKey: ArrayBuffer - }): Promise { + privateKey: Uint8Array + }): Promise { let { privateKey } = options; - // Convert key material from ArrayBuffer to Uint8Array. - const privateKeyU8A = Convert.arrayBuffer(privateKey).toUint8Array(); - // Compute public key. - const publicKey = x25519.getPublicKey(privateKeyU8A); + const publicKey = x25519.getPublicKey(privateKey); return publicKey; } @@ -70,20 +66,36 @@ export class X25519 { * @param options - The options for the shared secret computation operation. * @param options.privateKey - The private key of one party. * @param options.publicKey - The public key of the other party. - * @returns A Promise that resolves to the computed shared secret as an ArrayBuffer. + * @returns A Promise that resolves to the computed shared secret as a Uint8Array. */ public static async sharedSecret(options: { - privateKey: ArrayBuffer, - publicKey: ArrayBuffer - }): Promise { + privateKey: Uint8Array, + publicKey: Uint8Array + }): Promise { let { privateKey, publicKey } = options; - // Convert private and public key material from ArrayBuffer to Uint8Array. - const privateKeyU8A = Convert.arrayBuffer(privateKey).toUint8Array(); - const publicKeyU8A = Convert.arrayBuffer(publicKey).toUint8Array(); - const sharedSecret = x25519.getSharedSecret(privateKeyU8A, publicKeyU8A); + const sharedSecret = x25519.getSharedSecret(privateKey, publicKey); + + return sharedSecret; + } - return sharedSecret.buffer; + /** + * Note that this method is currently unimplemented because the @noble/curves + * library does not yet provide a mechanism for checking whether a point + * belongs to the Curve25519. Therefore, it currently throws an error whenever + * it is called. + * + * @param options - The options for the key validation operation. + * @param options.key - The key to validate. + * @throws {Error} If the method is called because it is not yet implemented. + * @returns A Promise that resolves to void. + */ + public static async validatePublicKey(_options: { + key: Uint8Array + }): Promise { + // TODO: Implement once/if @noble/curves library implements checking + // proper points on the Montgomery curve. + throw new Error(`Not implemented: 'validatePublicKey()'`); } } \ No newline at end of file diff --git a/packages/crypto/src/crypto-primitives/xchacha20-poly1305.ts b/packages/crypto/src/crypto-primitives/xchacha20-poly1305.ts new file mode 100644 index 000000000..5aeb4c012 --- /dev/null +++ b/packages/crypto/src/crypto-primitives/xchacha20-poly1305.ts @@ -0,0 +1,46 @@ +import { xchacha20_poly1305 } from '@noble/ciphers/chacha'; + +const TAG_LENGTH = 16; + +export class XChaCha20Poly1305 { + + public static async decrypt(options: { + additionalData?: Uint8Array, + data: Uint8Array, + key: Uint8Array, + nonce: Uint8Array, + tag: Uint8Array + }): Promise { + const { additionalData, data, key, nonce, tag } = options; + + const xc20p = xchacha20_poly1305(key, nonce, additionalData); + const ciphertext = new Uint8Array([...data, ...tag]); + const plaintext = xc20p.decrypt(ciphertext); + + return plaintext; + } + + public static async encrypt(options: { + additionalData?: Uint8Array, + data: Uint8Array, + key: Uint8Array, + nonce: Uint8Array + }): Promise<{ ciphertext: Uint8Array, tag: Uint8Array }> { + const { additionalData, data, key, nonce } = options; + + const xc20p = xchacha20_poly1305(key, nonce, additionalData); + const cipherOutput = xc20p.encrypt(data); + + const ciphertext = cipherOutput.subarray(0, -TAG_LENGTH); + const tag = cipherOutput.subarray(-TAG_LENGTH); + + return { ciphertext, tag }; + } + + public static async generateKey(): Promise { + // Generate the secret key. + const secretKey = crypto.getRandomValues(new Uint8Array(32)); + + return secretKey; + } +} \ No newline at end of file diff --git a/packages/crypto/src/crypto-primitives/xchacha20.ts b/packages/crypto/src/crypto-primitives/xchacha20.ts index 312de64dd..f1a9e916f 100644 --- a/packages/crypto/src/crypto-primitives/xchacha20.ts +++ b/packages/crypto/src/crypto-primitives/xchacha20.ts @@ -1,45 +1,34 @@ -import { Convert } from '@tbd54566975/common'; import { xchacha20 } from '@noble/ciphers/chacha'; export class XChaCha20 { public static async decrypt(options: { - data: BufferSource, - key: ArrayBuffer, - nonce: BufferSource - }): Promise { + data: Uint8Array, + key: Uint8Array, + nonce: Uint8Array + }): Promise { const { data, key, nonce } = options; - // Convert data, key material, and nonce to Uint8Array. - const dataU8A = Convert.bufferSource(data).toUint8Array(); - const keyU8A = Convert.arrayBuffer(key).toUint8Array(); - const nonceU8A = Convert.bufferSource(nonce).toUint8Array(); + const ciphertext = xchacha20(key, nonce, data); - const ciphertext = xchacha20(keyU8A, nonceU8A, dataU8A); - - return ciphertext.buffer; + return ciphertext; } public static async encrypt(options: { - data: BufferSource, - key: ArrayBuffer, - nonce: BufferSource - }): Promise { + data: Uint8Array, + key: Uint8Array, + nonce: Uint8Array + }): Promise { const { data, key, nonce } = options; - // Convert data, key material, and nonce to Uint8Array. - const dataU8A = Convert.bufferSource(data).toUint8Array(); - const keyU8A = Convert.arrayBuffer(key).toUint8Array(); - const nonceU8A = Convert.bufferSource(nonce).toUint8Array(); - - const plaintext = xchacha20(keyU8A, nonceU8A, dataU8A); + const plaintext = xchacha20(key, nonce, data); - return plaintext.buffer; + return plaintext; } - public static async generateKey(): Promise { + public static async generateKey(): Promise { // Generate the secret key. const secretKey = crypto.getRandomValues(new Uint8Array(32)); - return secretKey.buffer; + return secretKey; } } \ No newline at end of file diff --git a/packages/crypto/src/ed25519.ts b/packages/crypto/src/ed25519.ts deleted file mode 100644 index 6b0ccc8ba..000000000 --- a/packages/crypto/src/ed25519.ts +++ /dev/null @@ -1,87 +0,0 @@ -import type { KeyPair, Jwk, PublicKeyJwk, PrivateKeyJwk, KeyPairJwk } from './types.js'; - -import nacl from 'tweetnacl'; -import ed2curve from 'ed2curve'; -import { Convert } from '@tbd54566975/common'; - -// TODO: (not important) decide if we want to use tweetnacl or @noble/ed25519. is there a functional difference? -// dwn-sdk-js also has ed25519 cryptosuite stuff - -export function generateKeyPair(): KeyPair { - const ed25519KeyPair = nacl.sign.keyPair(); - - return { publicKey: ed25519KeyPair.publicKey, privateKey: ed25519KeyPair.secretKey }; -} - -export function deriveX25519KeyPair(ed25519KeyPair: KeyPair): KeyPair { - // for some reason tweetnacl chose the term `secretKey` instead of `privateKey` even though ed25519 is asymmetric - const x25519KeyPair = ed2curve.convertKeyPair({ publicKey: ed25519KeyPair.publicKey, secretKey: ed25519KeyPair.privateKey }); - - // apparently the return value of `convertKeyPair` can return null - if (!x25519KeyPair) { - throw new Error('failed to derive x25519 key pair.'); - } - - return { publicKey: x25519KeyPair.publicKey, privateKey: x25519KeyPair.secretKey }; -} - -export type JwkOverrides = { crv: 'Ed25519' | 'X25519' }; - -export function keyPairToJwk(keyPair: KeyPair, kid: string, overrides: JwkOverrides = { crv: 'Ed25519' }): KeyPairJwk { - const jwk: Jwk = { kty: 'OKP', crv: overrides.crv, kid }; - - const encodedPublicKey = Convert.uint8Array(keyPair.publicKey).toBase64Url(); - const publicKeyJwk: PublicKeyJwk = { ...jwk, x: encodedPublicKey }; - - const encodedSecretKey = Convert.uint8Array(keyPair.privateKey).toBase64Url(); - const privateKeyJwk: PrivateKeyJwk = { ...publicKeyJwk, d: encodedSecretKey }; - - return { publicKeyJwk, privateKeyJwk }; -} - -export type SignOptions = { - /** the data being signed */ - payload: Uint8Array; - /** the key being used to sign */ - privateKeyJwk: PrivateKeyJwk; -}; - -export function sign(options: SignOptions) { - const { payload, privateKeyJwk } = options; - const privateKeyBytes = Convert.base64Url(privateKeyJwk.d).toUint8Array(); - - if (privateKeyJwk.crv !== 'Ed25519') { - throw new Error('crv must be Ed25519'); - } - - const signedData = nacl.sign(payload, privateKeyBytes); - - return signedData.slice(0, nacl.sign.signatureLength); -} - -export type VerifyOptions = { - /** the signature to verify */ - signature: Uint8Array; - /** the payload that was signed */ - payload: Uint8Array; - /** the key to verify the signature with */ - publicKeyJwk: PublicKeyJwk; -} - -export async function verify(options: VerifyOptions) { - const { signature, payload, publicKeyJwk } = options; - const publicKeyBytes = Convert.base64Url(publicKeyJwk.x).toUint8Array(); - - if (publicKeyJwk.crv !== 'Ed25519') { - throw new Error('crv must be Ed25519'); - } - - const signedData = new Uint8Array(signature.length + payload.length); - signedData.set(signature); - signedData.set(payload, signature.length); - - const result = nacl.sign.open(signedData, publicKeyBytes); - - - return !!result; -} \ No newline at end of file diff --git a/packages/crypto/src/index.ts b/packages/crypto/src/index.ts new file mode 100644 index 000000000..5b5dc709a --- /dev/null +++ b/packages/crypto/src/index.ts @@ -0,0 +1,8 @@ +export type * from './types/crypto-key.js'; +export type * from './types/web5-crypto.js'; + +export * from './algorithms-api/index.js'; +export * from './crypto-algorithms/index.js'; +export * from './crypto-primitives/index.js'; +export * from './jose.js'; +export * from './utils.js'; \ No newline at end of file diff --git a/packages/crypto/src/jose.ts b/packages/crypto/src/jose.ts new file mode 100644 index 000000000..e80a949f5 --- /dev/null +++ b/packages/crypto/src/jose.ts @@ -0,0 +1,938 @@ +import { sha256 } from '@noble/hashes/sha256'; +import { Convert, Multicodec, MulticodecCode, removeUndefinedProperties } from '@web5/common'; + +import type { Web5Crypto } from './types/web5-crypto.js'; + +import { keyToMultibaseId } from './utils.js'; +import { CryptoKey } from './algorithms-api/index.js'; +import { Ed25519, Secp256k1, X25519 } from './crypto-primitives/index.js'; + +/** + * JSON Web Key Operations + * + * decrypt : Decrypt content and validate decryption, if applicable + * deriveBits : Derive bits not to be used as a key + * deriveKey : Derive key + * encrypt : Encrypt content + * sign : Compute digital signature or MAC + * unwrapKey : Decrypt key and validate decryption, if applicable + * verify : Verify digital signature or MAC + * wrapKey : Encrypt key + */ +export type JwkOperation = Web5Crypto.KeyUsage[] | string[]; + +/** + * JSON Web Key Use + * + * sig : Digital Signature or MAC + * enc : Encryption + */ +export type JwkUse = 'sig' | 'enc' | string; + +/** + * JSON Web Key Types + */ +export type JwkType = + /** + * Elliptic Curve + * Used with Elliptic Curve Digital Signature Algorithm (ECDSA) and Elliptic + * Curve Diffie-Hellman (ECDH), including secp256k1, P-256, P-384, and P-521. + */ + | 'EC' + /** + * RSA + * Widely used for encryption and digital signatures. RSA keys are used in + * various algorithms like RS256, RS384, RS512, etc. + */ + | 'RSA' + /** + * Octet sequence + * Used with symmetric signing (e.g., HMAC HS256, HS512, etc.) and + * symmetric encryption (e.g., A256CBC-HS512, A256GCM, etc.) algorithms. + */ + | 'oct' + /** + * Octet string key pairs (OKP) + * A type of public key that is used with algorithms such as EdDSA (Ed25519 and + * Ed448 curves) and ECDH (X25519 and X448 curves). + */ + | 'OKP' + +/** + * JSON Web Key Elliptic Curve + */ +export type JwkNamedCurves = + // P-256 Curve + | 'P-256' + // P-384 Curve + | 'P-384' + // P-521 Curve + | 'P-521' + // Ed25519 signature algorithm key pairs + | 'Ed25519' + // Ed448 signature algorithm key pairs + | 'Ed448' + // X25519 function key pairs + | 'X25519' + // X448 function key pairs + | 'X448' + // SECG secp256k1 curve + | 'secp256k1'; + +/** + * JSON Web Key Parameters + */ + +// Used with any "kty" (key type) value. +export type JwkParamsAnyKeyType = { + // The algorithm intended for use with the key + alg?: string; + // Extractable + ext?: 'true' | 'false'; + // Key Operations + key_ops?: JwkOperation; + // Key ID + kid?: string; + // Key Type + kty: JwkType; + // Public Key Use + use?: JwkUse; + // X.509 Certificate Chain + x5c?: string; + // X.509 Certificate SHA-1 Thumbprint + x5t?: string; + // X.509 Certificate SHA-256 Thumbprint + 'x5t#S256'?: string; + // X.509 URL + x5u?: string; +} + +// Used with "EC" (elliptic curve) public keys. +export type JwkParamsEcPublic = Omit & { + /** + * The algorithm intended for use with the key. + * ES256 : ECDSA using P-256 and SHA-256 + * ES256K : ECDSA using secp256k1 curve and SHA-256 + * ES384 : ECDSA using P-384 and SHA-384 + * ES512 : ECDSA using P-521 and SHA-512 + */ + alg?: 'ES256' | 'ES256K' | 'ES384' | 'ES512'; + + /** + * Elliptic Curve key pair. + */ + kty: 'EC'; + + /** + * The cryptographic curve used with the key. + * MUST be present for all EC public keys. + */ + crv: 'secp256k1' | 'P-256' | 'P-384' | 'P-521'; + + /** + * The x-coordinate for the Elliptic Curve point. + * Represented as the base64url encoding of the octet string + * representation of the coordinate. + * MUST be present for all EC public keys + */ + x: string; + + /** + * The y-coordinate for the Elliptic Curve point. + * Represented as the base64url encoding of the octet string + * representation of the coordinate. + * MUST be present only for secp256k1 public keys. + */ + y?: string; +} + +// Used with "EC" (elliptic curve) private keys. +export type JwkParamsEcPrivate = JwkParamsEcPublic & { + /** + * The d-coordinate for the Elliptic Curve point. + * Represented as the base64url encoding of the octet string + * representation of the coordinate. + * MUST be present for all EC private keys. + */ + d: string; +} + +// Used with "OKP" (octet key pair) public keys. +export type JwkParamsOkpPublic = + Omit & + Pick & { + /** + * The algorithm intended for use with the key. + * EdDSA: Edwards Curve Digital Signature Algorithm + */ + alg?: 'EdDSA'; + + /** + * The cryptographic curve used with the key. + * MUST be present for all OKP public keys. + */ + crv: 'Ed25519' | 'Ed448' | 'X25519' | 'X448'; + + /** + * Key type + * OKP (Octet Key Pair) is defined for public key algorithms that use octet + * strings as private and public keys. + */ + kty: 'OKP'; +} + +// Used with "OKP" (octet key pair) private keys. +export type JwkParamsOkpPrivate = JwkParamsOkpPublic & { + /** + * The d-coordinate for the Edwards Curve point. + * Represented as the base64url encoding of the octet string + * representation of the coordinate. + * MUST be present for all EC private keys. + */ + d: string; +}; + +// Used with "oct" (octet sequence) private keys. +export type JwkParamsOctPrivate = Omit & { + /** + * The algorithm intended for use with the key. + * Used with symmetric signing (e.g., HMAC HS256, etc.) and + * symmetric encryption (e.g., A256GCM, etc.) algorithms. + */ + alg?: + // AES CBC using 128-bit key + | 'A128CBC' + // AES CBC using 192-bit key + | 'A192CBC' + // AES CBC using 256-bit key + | 'A256CBC' + // AES CTR using 128-bit key + | 'A128CTR' + // AES CTR using 192-bit key + | 'A192CTR' + // AES CTR using 256-bit key + | 'A256CTR' + // AES GCM using a 128-bit key + | 'A128GCM' + // AES GCM using a 192-bit key + | 'A192GCM' + // AES GCM using a 256-bit key + | 'A256GCM' + // HMAC using SHA-256 + | 'HS256' + // HMAC using SHA-384 + | 'HS384' + // HMAC using SHA-512 + | 'HS512' + + /** + * The "k" (key value) parameter contains the value of the symmetric + * (or other single-valued) key. It is represented as the base64url + * encoding of the octet sequence containing the key value. + */ + k: string; + + /** + * Key type + * oct (Octet Sequence) is defined for symmetric encryption and + * symmetric signature algorithms. + */ + kty: 'oct'; +} + +// Used with "RSA" public keys. +export type JwkParamsRsaPublic = Omit & { + // Public exponent for RSA + e: string; + + /** + * Key type + * RSA is widely used for encryption and digital signatures. + */ + kty: 'RSA'; + + // Modulus for RSA + n: string; +}; + +// Used with "RSA" private keys. +export type JwkParamsRsaPrivate = JwkParamsRsaPublic & { + // Private exponent for RSA + d: string; + // First prime factor for RSA + p?: string; + // Second prime factor for RSA + q?: string; + // First factor's CRT exponent for RSA + dp?: string; + // Second factor's CRT exponent for RSA + dq?: string; + // First CRT coefficient for RSA + qi?: string; + // Other primes information (optional in RFC 7518) + oth?: { + r: string; + d: string; + t: string; + }[]; +}; + +export type PublicKeyJwk = JwkParamsEcPublic | JwkParamsOkpPublic | JwkParamsRsaPublic; + +export type PrivateKeyJwk = JwkParamsEcPrivate | JwkParamsOkpPrivate | JwkParamsOctPrivate | JwkParamsRsaPrivate; + +export type JwkKeyPair = { + publicKeyJwk: PublicKeyJwk; + privateKeyJwk: PrivateKeyJwk; +} + +export type JsonWebKey = PrivateKeyJwk | PublicKeyJwk; + +export interface JoseHeaderParams { + cty?: string; + jku?: string; + jwk?: PublicKeyJwk; + kid?: string; + typ?: string; + x5c?: string[]; + x5t?: string; + x5u?: string; +} + +export interface JwsHeaderParams extends JoseHeaderParams { + alg: + // HMAC using SHA-256 + | 'HS256' + // HMAC using SHA-384 + | 'HS384' + // HMAC using SHA-512 + | 'HS512' + // ECDSA using P-256 and SHA-256 + | 'ES256' + // ECDSA using secp256k1 curve and SHA-256 + | 'ES256K' + // ECDSA using P-384 and SHA-384 + | 'ES384' + // ECDSA using P-521 and SHA-512 + | 'ES512'; + + // Indicates that extensions to JOSE RFCs are being used + // that MUST be understood and processed. + crit?: string[] + + // Additional Public or Private Header Parameter names. + [key: string]: unknown +} + +export interface JweHeaderParams extends JoseHeaderParams { + alg: + // AES Key Wrap with default initial value using 128-bit key + | 'A128KW' + // AES Key Wrap with default initial value using 192-bit key + | 'A192KW' + // AES Key Wrap with default initial value using 256-bit key + | 'A256KW' + // Direct use of a shared symmetric key as the CEK + | 'dir' + // Elliptic Curve Diffie-Hellman Ephemeral Static key agreement using Concat KDF + | 'ECDH-ES' + // ECDH-ES using Concat KDF and CEK wrapped with "A128KW" + | 'ECDH-ES+A128KW' + // ECDH-ES using Concat KDF and CEK wrapped with "A192KW" + | 'ECDH-ES+A192KW' + // ECDH-ES using Concat KDF and CEK wrapped with "A256KW" + | 'ECDH-ES+A256KW' + // Key wrapping with AES GCM using 128-bit key + | 'A128GCMKW' + // Key wrapping with AES GCM using 192-bit key + | 'A192GCMKW' + // Key wrapping with AES GCM using 256-bit key + | 'A256GCMKW' + // PBES2 with HMAC SHA-256 and "A128KW" wrapping + | 'PBES2-HS256+A128KW' + // PBES2 with HMAC SHA-384 and "A192KW" wrapping + | 'PBES2-HS384+A192KW' + // PBES2 with HMAC SHA-512 and "A256KW" wrapping + | 'PBES2-HS512+A256KW' + // PBES2 with HMAC SHA-512 and "XC20PKW" wrapping + | 'PBES2-HS512+XC20PKW'; + + apu?: Uint8Array; + + apv?: Uint8Array; + + // Indicates that extensions to JOSE RFCs are being used + // that MUST be understood and processed. + crit?: string[] + + /** + * Cryptographic Algorithms for Content Encryption + * JWE uses cryptographic algorithms to encrypt and integrity-protect the + * plaintext and to integrity-protect the Additional Authenticated Data. + */ + enc: + // AES_128_CBC_HMAC_SHA_256 authenticated encryption algorithm, + // as defined in RFC 7518, Section 5.2.3 + | 'A128CBC-HS256' + // AES_192_CBC_HMAC_SHA_384 authenticated encryption algorithm, + // as defined in RFC 7518, Section 5.2.4 + | 'A192CBC-HS384' + // AES_256_CBC_HMAC_SHA_512 authenticated encryption algorithm, + // as defined in RFC 7518, Section 5.2.5 + | 'A256CBC-HS512' + // AES GCM using 128-bit key + | 'A128GCM' + // AES GCM using 192-bit key + | 'A192GCM' + // AES GCM using 256-bit key + | 'A256GCM' + // XChaCha20-Poly1305 authenticated encryption algorithm + | 'XC20P'; + + epk?: Uint8Array; + + iv?: Uint8Array; + + p2c?: number; + + p2s?: string; + + // Additional Public or Private Header Parameter names. + [key: string]: unknown +} + +const joseToWebCryptoMapping: { [key: string]: Web5Crypto.GenerateKeyOptions } = { + 'Ed25519' : { name: 'EdDSA', namedCurve: 'Ed25519' }, + 'Ed448' : { name: 'EdDSA', namedCurve: 'Ed448' }, + 'X25519' : { name: 'ECDH', namedCurve: 'X25519' }, + 'secp256k1:ES256K' : { name: 'ECDSA', namedCurve: 'secp256k1' }, + 'secp256k1' : { name: 'ECDH', namedCurve: 'secp256k1' }, + 'P-256' : { name: 'ECDSA', namedCurve: 'P-256' }, + 'P-384' : { name: 'ECDSA', namedCurve: 'P-384' }, + 'P-521' : { name: 'ECDSA', namedCurve: 'P-521' }, + 'A128CBC' : { name: 'AES-CBC', length: 128 }, + 'A192CBC' : { name: 'AES-CBC', length: 192 }, + 'A256CBC' : { name: 'AES-CBC', length: 256 }, + 'A128CTR' : { name: 'AES-CTR', length: 128 }, + 'A192CTR' : { name: 'AES-CTR', length: 192 }, + 'A256CTR' : { name: 'AES-CTR', length: 256 }, + 'A128GCM' : { name: 'AES-GCM', length: 128 }, + 'A192GCM' : { name: 'AES-GCM', length: 192 }, + 'A256GCM' : { name: 'AES-GCM', length: 256 }, + 'HS256' : { name: 'HMAC', hash: { name: 'SHA-256' } }, + 'HS384' : { name: 'HMAC', hash: { name: 'SHA-384' } }, + 'HS512' : { name: 'HMAC', hash: { name: 'SHA-512' } }, +}; + +const webCryptoToJoseMapping: { [key: string]: Partial } = { + 'EdDSA:Ed25519' : { alg: 'EdDSA', crv: 'Ed25519', kty: 'OKP' }, + 'EdDSA:Ed448' : { alg: 'EdDSA', crv: 'Ed448', kty: 'OKP' }, + 'ECDH:X25519' : { crv: 'X25519', kty: 'OKP' }, + 'ECDSA:secp256k1' : { alg: 'ES256K', crv: 'secp256k1', kty: 'EC' }, + 'ECDH:secp256k1' : { crv: 'secp256k1', kty: 'EC' }, + 'ECDSA:P-256' : { alg: 'ES256', crv: 'P-256', kty: 'EC' }, + 'ECDSA:P-384' : { alg: 'ES384', crv: 'P-384', kty: 'EC' }, + 'ECDSA:P-521' : { alg: 'ES512', crv: 'P-521', kty: 'EC' }, + 'AES-CBC:128' : { alg: 'A128CBC', kty: 'oct' }, + 'AES-CBC:192' : { alg: 'A192CBC', kty: 'oct' }, + 'AES-CBC:256' : { alg: 'A256CBC', kty: 'oct' }, + 'AES-CTR:128' : { alg: 'A128CTR', kty: 'oct' }, + 'AES-CTR:192' : { alg: 'A192CTR', kty: 'oct' }, + 'AES-CTR:256' : { alg: 'A256CTR', kty: 'oct' }, + 'AES-GCM:128' : { alg: 'A128GCM', kty: 'oct' }, + 'AES-GCM:192' : { alg: 'A192GCM', kty: 'oct' }, + 'AES-GCM:256' : { alg: 'A256GCM', kty: 'oct' }, + 'HMAC:SHA-256' : { alg: 'HS256', kty: 'oct' }, + 'HMAC:SHA-384' : { alg: 'HS384', kty: 'oct' }, + 'HMAC:SHA-512' : { alg: 'HS512', kty: 'oct' }, +}; + +const multicodecToJoseMapping: { [key: string]: JsonWebKey } = { + 'ed25519-pub' : { alg: 'EdDSA', crv: 'Ed25519', kty: 'OKP', x: '' }, + 'ed25519-priv' : { alg: 'EdDSA', crv: 'Ed25519', kty: 'OKP', x: '', d: '' }, + 'secp256k1-pub' : { alg: 'ES256K', crv: 'secp256k1', kty: 'EC', x: '', y: ''}, + 'secp256k1-priv' : { alg: 'ES256K', crv: 'secp256k1', kty: 'EC', x: '', y: '', d: '' }, + 'x25519-pub' : { crv: 'X25519', kty: 'OKP', x: '' }, + 'x25519-priv' : { crv: 'X25519', kty: 'OKP', x: '', d: '' }, +}; + +const joseToMulticodecMapping: { [key: string]: string } = { + 'Ed25519:public' : 'ed25519-pub', + 'Ed25519:private' : 'ed25519-priv', + 'secp256k1:public' : 'secp256k1-pub', + 'secp256k1:private' : 'secp256k1-priv', + 'X25519:public' : 'x25519-pub', + 'X25519:private' : 'x25519-priv', +}; + +export class Jose { + + public static async cryptoKeyToJwk(options: { + key: Web5Crypto.CryptoKey, + }): Promise { + const { algorithm, extractable, material, type, usages } = options.key; + + // Translate WebCrypto algorithm to JOSE format. + let jsonWebKey = Jose.webCryptoToJose(algorithm) as JsonWebKey; + + // Set extractable parameter. + jsonWebKey.ext = extractable ? 'true' : 'false'; + + // Set key use parameter. + jsonWebKey.key_ops = usages; + + jsonWebKey = await Jose.keyToJwk({ + keyMaterial : material, + keyType : type, + ...jsonWebKey + }); + + return { ...jsonWebKey }; + } + + public static async cryptoKeyToJwkPair(options: { + keyPair: Web5Crypto.CryptoKeyPair, + }): Promise { + const { keyPair } = options; + + // Convert public and private keys into JSON Web Key format. + const privateKeyJwk = await Jose.cryptoKeyToJwk({ key: keyPair.privateKey }) as PrivateKeyJwk; + const publicKeyJwk = await Jose.cryptoKeyToJwk({ key: keyPair.publicKey }) as PublicKeyJwk; + + // Assemble as a JWK key pair + const jwkKeyPair: JwkKeyPair = { privateKeyJwk, publicKeyJwk }; + + return { ...jwkKeyPair }; + } + + public static async joseToMulticodec(options: { + key: JsonWebKey + }): Promise<{ code?: MulticodecCode, name?: string }> { + const jsonWebKey = options.key; + + const params: string[] = []; + + if ('crv' in jsonWebKey) { + params.push(jsonWebKey.crv); + if ('d' in jsonWebKey) { + params.push('private'); + } else { + params.push('public'); + } + } + + const lookupKey = params.join(':'); + const name = joseToMulticodecMapping[lookupKey]; + + if (name === undefined) { + throw new Error(`Unsupported JOSE to Multicodec conversion: '${lookupKey}'`); + } + + const code = Multicodec.getCodeFromName({ name }); + + return { code, name }; + } + + public static joseToWebCrypto(options: + Partial + ): Web5Crypto.GenerateKeyOptions { + const params: string[] = []; + + /** + * All Elliptic Curve (EC) and Octet Key Pair (OKP) JSON Web Keys + * set a value for the "crv" parameter. + */ + if ('crv' in options && options.crv) { + params.push(options.crv); + // Special case for secp256k1. If alg is "ES256K", then ECDSA. Else ECDH. + if (options.crv === 'secp256k1' && options.alg === 'ES256K') { + params.push(options.alg); + } + + /** + * All Octet Sequence (oct) JSON Web Keys omit "crv" and + * set a value for the "alg" parameter. + */ + } else if (options.alg !== undefined) { + params.push(options.alg); + + } else { + throw new TypeError(`One or more parameters missing: 'alg' or 'crv'`); + } + + const lookupKey = params.join(':'); + const webCrypto = joseToWebCryptoMapping[lookupKey]; + + if (webCrypto === undefined) { + throw new Error(`Unsupported JOSE to WebCrypto conversion: '${lookupKey}'`); + } + + return { ...webCrypto }; + } + + /** + * Computes the thumbprint of a JSON Web Key (JWK) using the method + * specified in RFC 7638. This function accepts RSA, EC, OKP, and oct keys + * and returns the thumbprint as a base64url encoded SHA-256 hash of the + * JWK's required members, serialized and sorted lexicographically. + * + * Purpose: + * - Uniquely Identifying Keys: The thumbprint allows the unique + * identification of a specific JWK within a set of JWKs. It provides a + * deterministic way to generate a value that can be used as a key + * identifier (kid) or to match a specific key. + * + * - Simplifying Key Management: In systems where multiple keys are used, + * managing and identifying individual keys can become complex. The + * thumbprint method simplifies this by creating a standardized, unique + * identifier for each key. + * + * - Enabling Interoperability: By standardizing the method to compute a + * thumbprint, different systems can compute the same thumbprint value for + * a given JWK. This enables interoperability among systems that use JWKs. + * + * - Secure Comparison: The thumbprint provides a way to securely compare + * JWKs to determine if they are equivalent. + * + * @param jwk - The JSON Web Key for which the thumbprint will be computed. + * This must be an RSA, EC, OKP, or oct key. + * @returns The thumbprint as a base64url encoded string. + * @throws {Error} Throws an error if the provided key type is unsupported. + * + * @example + * const jwk: PublicKeyJwk = { + * 'kty': 'EC', + * 'crv': 'secp256k1', + * 'x': '61iPYuGefxotzBdQZtDvv6cWHZmXrTTscY-u7Y2pFZc', + * 'y': '88nPCVLfrAY9i-wg5ORcwVbHWC_tbeAd1JE2e0co0lU' + * }; + * + * const thumbprint = jwkThumbprint(jwk); + * console.log(`JWK thumbprint: ${thumbprint}`); + * + * @see {@link https://datatracker.ietf.org/doc/html/rfc7638|RFC 7638} for + * the specification of JWK thumbprint computation. + */ + public static async jwkThumbprint(options: { + key: JsonWebKey + }): Promise { + const { key } = options; + + /** Step 1 - Normalization: The JWK is normalized to include only specific + * members and in lexicographic order. + * */ + const keyType = key.kty; + let normalizedJwk: Partial; + if (keyType === 'EC') { + normalizedJwk = { crv: key.crv, kty: key.kty, x: key.x, y: key.y }; + } else if (keyType === 'oct') { + normalizedJwk = { k: key.k, kty: key.kty }; + } else if (keyType === 'OKP') { + normalizedJwk = { crv: key.crv, kty: key.kty, x: key.x }; + } else if (keyType === 'RSA') { + normalizedJwk = { e: key.e, kty: key.kty, n: key.n }; + } else { + throw new Error(`Unsupported key type: ${keyType}`); + } + removeUndefinedProperties(normalizedJwk); + + /** Step 2 - Serialization: The normalized JWK is serialized to a UTF-8 + * representation of its JSON encoding. */ + const serializedJwk = Jose.canonicalize(normalizedJwk); + + /** Step 3 - Digest Calculation: A cryptographic hash function + * (SHA-256 is recommended) is applied to the serialized JWK, + * resulting in the thumbprint. */ + const utf8Bytes = Convert.string(serializedJwk).toUint8Array(); + const digest = sha256(utf8Bytes); + + // Encode as Base64Url. + const thumbprint = Convert.uint8Array(digest).toBase64Url(); + + return thumbprint; + } + + public static async jwkToCryptoKey(options: { + key: JsonWebKey + }): Promise { + const jsonWebKey = options.key; + + const { keyMaterial, keyType } = await Jose.jwkToKey({ key: jsonWebKey }); + + // Translate JOSE format to WebCrypto algorithm. + let algorithm = Jose.joseToWebCrypto(jsonWebKey) as Web5Crypto.GenerateKeyOptions; + + // Set extractable parameter. + let extractable: boolean; + if ('ext' in jsonWebKey && jsonWebKey.ext !== undefined) { + extractable = jsonWebKey.ext === 'true' ? true : false; + } else { + throw new Error(`Conversion from JWK to CryptoKey failed. Required parameter missing: 'ext'`); + } + + // Set key use parameter. + let keyUsage: Web5Crypto.KeyUsage[]; + if ('key_ops' in jsonWebKey && jsonWebKey.key_ops !== undefined) { + keyUsage = jsonWebKey.key_ops as Web5Crypto.KeyUsage[]; + } else { + throw new Error(`Conversion from JWK to CryptoKey failed. Required parameter missing: 'key_ops'`); + } + + const cryptoKey = new CryptoKey( + algorithm, + extractable, + keyMaterial, + keyType, + keyUsage + ); + + return cryptoKey; + } + + public static async jwkToKey(options: { + key: JsonWebKey + }): Promise<{ keyMaterial: Uint8Array, keyType: Web5Crypto.KeyType }> { + const jsonWebKey = options.key; + + let keyMaterial: Uint8Array; + let keyType: Web5Crypto.KeyType; + + // Asymmetric private key ("EC" or "OKP" - Curve25519 or SECG curves). + if ('d' in jsonWebKey) { + keyMaterial = Convert.base64Url(jsonWebKey.d).toUint8Array(); + keyType = 'private'; + } + + // Asymmetric public key ("EC" - secp256k1, secp256r1, secp384r1, secp521r1). + else if ('y' in jsonWebKey && jsonWebKey.y) { + const prefix = new Uint8Array([0x04]); // Designates an uncompressed key. + const x = Convert.base64Url(jsonWebKey.x).toUint8Array(); + const y = Convert.base64Url(jsonWebKey.y).toUint8Array(); + + const publicKey = new Uint8Array([...prefix, ...x, ...y]); + keyMaterial = publicKey; + keyType = 'public'; + } + + // Asymmetric public key ("OKP" - Ed25519, X25519). + else if ('x' in jsonWebKey) { + keyMaterial = Convert.base64Url(jsonWebKey.x).toUint8Array(); + keyType = 'public'; + } + + // Symmetric encryption or signature key ("oct" - AES, HMAC) + else if ('k' in jsonWebKey) { + keyMaterial = Convert.base64Url(jsonWebKey.k).toUint8Array(); + keyType = 'private'; + } + + else { + throw new Error('Jose: Unknown JSON Web Key format.'); + } + + return { keyMaterial, keyType }; + } + + /** + * Note: All secp public keys are converted to compressed point encoding + * before the multibase identifier is computed. + * + * Per {@link https://github.com/multiformats/multicodec/blob/master/table.csv | Multicodec table}: + * public keys for Elliptic Curve cryptography algorithms (e.g., secp256k1, + * secp256k1r1, secp384r1, etc.) are always represented with compressed point + * encoding (e.g., secp256k1-pub, p256-pub, p384-pub, etc.). + * + * Per {@link https://datatracker.ietf.org/doc/html/rfc8812#name-jose-and-cose-secp256k1-cur | RFC 8812}: + * "As a compressed point encoding representation is not defined for JWK + * elliptic curve points, the uncompressed point encoding defined there + * MUST be used. The x and y values represented MUST both be exactly + * 256 bits, with any leading zeros preserved. + * + */ + public static async jwkToMultibaseId(options: { + key: JsonWebKey + }): Promise { + const jsonWebKey = options.key; + + // Convert the algorithm into Multicodec name format. + const { name: multicodecName } = await Jose.joseToMulticodec({ key: jsonWebKey }); + + // Decode the key as a raw binary data from the JWK. + let { keyMaterial } = await Jose.jwkToKey({ key: jsonWebKey }); + + // Convert secp256k1 public keys to compressed format. + if ('crv' in jsonWebKey && !('d' in jsonWebKey)) { + switch (jsonWebKey.crv) { + case 'secp256k1': { + keyMaterial = await Secp256k1.convertPublicKey({ + publicKey : keyMaterial, + compressedPublicKey : true + }); + break; + } + } + } + + // Compute the multibase identifier based on the provided key. + const multibaseId = keyToMultibaseId({ key: keyMaterial, multicodecName }); + + return multibaseId; + } + + public static async keyToJwk(options: + Partial & { + keyMaterial: Uint8Array, + keyType: Web5Crypto.KeyType, + }): Promise { + const { keyMaterial, keyType, ...jsonWebKeyOptions } = options; + + let jsonWebKey = { ...jsonWebKeyOptions } as JsonWebKey; + + /** + * All Elliptic Curve (EC) and Octet Key Pair (OKP) keys + * specify a "crv" (named curve) parameter. + */ + if ('crv' in jsonWebKey) { + switch (jsonWebKey.crv) { + + case 'Ed25519': { + const publicKey = (keyType === 'private') + ? await Ed25519.getPublicKey({ privateKey: keyMaterial }) + : keyMaterial; + jsonWebKey.x = Convert.uint8Array(publicKey).toBase64Url(); + jsonWebKey.kty ??= 'OKP'; + break; + } + + case 'X25519': { + const publicKey = (keyType === 'private') + ? await X25519.getPublicKey({ privateKey: keyMaterial }) + : keyMaterial; + jsonWebKey.x = Convert.uint8Array(publicKey).toBase64Url(); + jsonWebKey.kty ??= 'OKP'; + break; + } + + case 'secp256k1': { + const points = await Secp256k1.getCurvePoints({ key: keyMaterial }); + jsonWebKey.x = Convert.uint8Array(points.x).toBase64Url(); + jsonWebKey.y = Convert.uint8Array(points.y).toBase64Url(); + jsonWebKey.kty ??= 'EC'; + break; + } + + default: { + throw new Error(`Unsupported key to JWK conversion: ${jsonWebKey.crv}`); + } + } + + if (keyType === 'private') { + jsonWebKey = { + d: Convert.uint8Array(keyMaterial).toBase64Url(), + ...jsonWebKey + }; + } + } + + /** + * All Octet Sequence (oct) symmetric encryption and signature keys + * specify only an "alg" parameter. + */ + if (!('crv' in jsonWebKey) && jsonWebKey.kty === 'oct') { + jsonWebKey.k = Convert.uint8Array(keyMaterial).toBase64Url(); + } + + return { ...jsonWebKey }; + } + + public static async multicodecToJose(options: { + code?: MulticodecCode, + name?: string + }): Promise { + let { code, name } = options; + + // Either code or name must be specified, but not both. + if (!(name ? !code : code)) { + throw new Error(`Either 'name' or 'code' must be defined, but not both.`); + } + + // If name is undefined, lookup by code. + name = (name === undefined ) ? Multicodec.getNameFromCode({ code: code! }) : name; + + const lookupKey = name; + const jose = multicodecToJoseMapping[lookupKey]; + + if (jose === undefined) { + throw new Error(`Unsupported Multicodec to JOSE conversion: '${options.name ?? options.code}'`); + } + + return { ...jose }; + } + + public static webCryptoToJose(options: + Web5Crypto.GenerateKeyOptions + ): Partial { + const params: string[] = []; + + /** + * All WebCrypto algorithms have the "named" parameter. + */ + params.push(options.name); + + /** + * All Elliptic Curve (EC) WebCrypto algorithms + * set a value for the "namedCurve" parameter. + */ + if ('namedCurve' in options) { + params.push(options.namedCurve); + + /** + * All symmetric encryption (AES) WebCrypto algorithms + * set a value for the "length" parameter. + */ + } else if (options.length !== undefined) { + params.push(options.length.toString()); + + /** + * All symmetric signature (HMAC) WebCrypto algorithms + * set a value for the "hash" parameter. + */ + } else if ('hash' in options) { + params.push(options.hash.name); + + } else { + throw new TypeError(`One or more parameters missing: 'name', 'namedCurve', 'length', or 'hash'`); + } + + const lookupKey = params.join(':'); + const jose = webCryptoToJoseMapping[lookupKey]; + + if (jose === undefined) { + throw new Error(`Unsupported WebCrypto to JOSE conversion: '${lookupKey}'`); + } + + return { ...jose }; + } + + private static canonicalize(obj: { [key: string]: any }): string { + const sortedKeys = Object.keys(obj).sort(); + const sortedObj = sortedKeys.reduce<{ [key: string]: any }>((acc, key) => { + acc[key] = obj[key]; + return acc; + }, {}); + return JSON.stringify(sortedObj); + } +} + +type Constructable = new (...args: any[]) => object; + +export function CryptoKeyToJwkMixin(Base: T) { + return class extends Base { + public async toJwk(): Promise { + const jwk = Jose.cryptoKeyToJwk({ key: (this as unknown) as CryptoKey }); + return jwk; + } + }; +} + +export const CryptoKeyWithJwk = CryptoKeyToJwkMixin(CryptoKey); \ No newline at end of file diff --git a/packages/crypto/src/key-manager/index.ts b/packages/crypto/src/key-manager/index.ts deleted file mode 100644 index 7161e855a..000000000 --- a/packages/crypto/src/key-manager/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from './key-store.js'; -export * from './key-manager.js'; \ No newline at end of file diff --git a/packages/crypto/src/key-manager/key-manager.ts b/packages/crypto/src/key-manager/key-manager.ts deleted file mode 100644 index 677d245d4..000000000 --- a/packages/crypto/src/key-manager/key-manager.ts +++ /dev/null @@ -1,219 +0,0 @@ -import type { - ManagedKey, - SignOptions, - CryptoManager, - ImportableKey, - VerifyOptions, - DecryptOptions, - EncryptOptions, - ManagedKeyPair, - GenerateKeyType, - ImportKeyOptions, - DeriveBitsOptions, - ImportableKeyPair, - ManagedPrivateKey, - GenerateKeyOptions, - KeyManagementSystem, - GenerateKeyOptionTypes, -} from '../types/index.js'; - -import { MemoryStore } from '@tbd54566975/common'; - -import { KeyManagerStore } from './key-store.js'; -import { LocalKms, KmsKeyStore, KmsPrivateKeyStore } from '../kms-local/index.js'; -import { checkRequiredProperty, isManagedKey, isManagedKeyPair } from '../utils-new.js'; - -export type KmsMap = { - [name: string]: KeyManagementSystem; -} - -export type KeyManagerOptions = { - store: KeyManagerStore; - kms?: KmsMap; -} - -/** - * KeyManager - * - * This class orchestrates implementations of {@link KeyManagementSystem}, - * using a ManagedKeyStore to remember the link between a key reference, - * its metadata, and the respective key management system that provides the - * actual cryptographic capabilities. - * - * The methods of this class are used automatically by other Web5 Agent - * components, such as - * {@link @tbd54566975/web5#DidApi | DidApi} or - * {@link @tbd54566975/web5-user-agent#ProfileApi | ProfileApi} to - * perform their required cryptographic operations using the managed keys. - * - * @public - */ -export class KeyManager implements CryptoManager { - // KMS name to KeyManagementSystem mapping - private kms: Map; - // Store for managed key metadata. - private keyStore: KeyManagerStore; - - constructor(options: KeyManagerOptions) { - checkRequiredProperty({ property: 'store', inObject: options }); - this.keyStore = options.store; - - options.kms ??= this.useLocalKms(); - this.kms = new Map(Object.entries(options.kms)) ; - } - - async decrypt(options: DecryptOptions): Promise { - let { keyRef, ...decryptOptions } = options; - - const key = await this.getKey({ keyRef }); - - if (!isManagedKey(key)) { - throw new Error(`Key not found: '${keyRef}'.`); - } - - const kmsName = key.kms; - const kms = this.getKms(kmsName); - - const keyId = key.id; - const plaintext = await kms.decrypt({ keyRef: keyId, ...decryptOptions }); - - return plaintext; - } - - async deriveBits(options: DeriveBitsOptions): Promise { - const { baseKeyRef, ...deriveBitsOptions } = options; - - const ownKeyPair = await this.getKey({ keyRef: baseKeyRef }); - - if (!isManagedKeyPair(ownKeyPair)) { - throw new Error(`Key not found: '${baseKeyRef}'.`); - } - - const kmsName = ownKeyPair.privateKey.kms; - const kms = this.getKms(kmsName); - - const ownKeyId = ownKeyPair.privateKey.id; - const sharedSecret = kms.deriveBits({ baseKeyRef: ownKeyId, ...deriveBitsOptions }); - - return sharedSecret; - } - - async encrypt(options: EncryptOptions): Promise { - let { keyRef, ...encryptOptions } = options; - - const key = await this.getKey({ keyRef }); - - if (!isManagedKey(key)) { - throw new Error(`Key not found: '${keyRef}'.`); - } - - const kmsName = key.kms; - const kms = this.getKms(kmsName); - - const keyId = key.id; - const ciphertext = await kms.encrypt({ keyRef: keyId, ...encryptOptions }); - - return ciphertext; - } - - async generateKey(options: GenerateKeyOptions & { kms?: string }): Promise> { - const { kms: kmsName, ...generateKeyOptions } = options; - - const kms = this.getKms(kmsName); - - const keyOrKeyPair = await kms.generateKey(generateKeyOptions); - - // Store the ManagedKey or ManagedKeyPair in KeyManager's key store. - await this.keyStore.importKey({ key: keyOrKeyPair }); - - return keyOrKeyPair; - } - - async getKey(options: { keyRef: string; }): Promise { - const keyOrKeyPair = this.keyStore.getKey({ id: options.keyRef }); - return keyOrKeyPair; - } - - async importKey(options: ImportableKeyPair): Promise; - async importKey(options: ImportableKey): Promise; - async importKey(options: ImportKeyOptions): Promise { - const kmsName = ('privateKey' in options) ? options.privateKey.kms : options.kms; - const kms = this.getKms(kmsName); - - const importedKeyOrKeyPair = await kms.importKey(options); - - // Store the ManagedKey or ManagedKeyPair in KeyManager's key store. - await this.keyStore.importKey({ key: importedKeyOrKeyPair }); - - return importedKeyOrKeyPair; - } - - listKms() { - return Array.from(this.kms.keys()); - } - - async sign(options: SignOptions): Promise { - let { keyRef, ...signOptions } = options; - - const keyPair = await this.getKey({ keyRef }); - - if (!isManagedKeyPair(keyPair)) { - throw new Error(`Key not found: '${keyRef}'.`); - } - - const kmsName = keyPair.privateKey.kms; - const kms = this.getKms(kmsName); - - const keyId = keyPair.privateKey.id; - const signature = await kms.sign({ keyRef: keyId, ...signOptions }); - - return signature; - } - - async verify(options: VerifyOptions): Promise { - let { keyRef, ...verifyOptions } = options; - - const keyPair = await this.getKey({ keyRef }); - - if (!isManagedKeyPair(keyPair)) { - throw new Error(`Key not found: '${keyRef}'.`); - } - - const kmsName = keyPair.publicKey.kms; - const kms = this.getKms(kmsName); - - const keyId = keyPair.publicKey.id; - const isValid = await kms.verify({ keyRef: keyId, ...verifyOptions }); - - return isValid; - } - - private getKms(name: string | undefined): KeyManagementSystem { - // For developer convenience, if a KMS name isn't specified and KeyManager only has - // one KMS defined, use it. Otherwise, an exception will be thrown. - name ??= (this.kms.size === 1) ? this.kms.keys().next().value : ''; - - const kms = this.kms.get(name!); - - if (!kms) { - throw Error(`Unknown key management system: '${name}'`); - } - - return kms; - } - - private useLocalKms(): KmsMap { - // Instantiate local in-memory store for KMS key metadata and public keys. - const kmsMemoryStore = new MemoryStore(); - const kmsKeyStore = new KmsKeyStore(kmsMemoryStore); - - // Instantiate local in-memory store for KMS private keys. - const kmsPrivateMemoryStore = new MemoryStore(); - const kmsPrivateKeyStore = new KmsPrivateKeyStore(kmsPrivateMemoryStore); - - // Instantiate local KMS using key stores. - const kms = new LocalKms('local', kmsKeyStore, kmsPrivateKeyStore); - - return { local: kms }; - } -} \ No newline at end of file diff --git a/packages/crypto/src/key-manager/key-store.ts b/packages/crypto/src/key-manager/key-store.ts deleted file mode 100644 index 0533e3ecd..000000000 --- a/packages/crypto/src/key-manager/key-store.ts +++ /dev/null @@ -1,52 +0,0 @@ -import type { ManagedKeyStore, ManagedKey, ManagedKeyPair } from '../types/index.js'; - -import { MemoryStore } from '@tbd54566975/common'; - -import { isManagedKeyPair } from '../utils-new.js'; - -/** - * An implementation of `ManagedKeyStore` that stores key metadata and - * public key material in memory. - * - * An instance of this class can be used by `KeyManager`.` - * - * This class must be initialized with a {@link MemoryStore}, which serves - * as the key/value store. - */ -export class KeyManagerStore implements ManagedKeyStore { - private store: MemoryStore; - - constructor(options: { store: MemoryStore }) { - this.store = options.store; - } - - async deleteKey({ id }: { id: string }) { - if (await this.store.has(id)) { - await this.store.delete(id); - return true; - } else { - return false; - } - } - - async getKey({ id }: { id: string }): Promise { - return this.store.get(id); - } - - async importKey({ key }: { key: ManagedKey | ManagedKeyPair }): Promise { - const id = isManagedKeyPair(key) ? key.publicKey!.id : key.id; - if (await this.store.has(id)) { - throw new Error(`Key with ID already exists: '${id}'`); - } - - // Make a deep copy of the key so that the object stored does not share the same references as the input key. - const clonedKey = structuredClone(key); - await this.store.set(id, clonedKey ); - - return true; - } - - async listKeys(): Promise> { - return this.store.list(); - } -} \ No newline at end of file diff --git a/packages/crypto/src/kms-local/index.ts b/packages/crypto/src/kms-local/index.ts deleted file mode 100644 index 53239a171..000000000 --- a/packages/crypto/src/kms-local/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from './kms-local.js'; -export * from './key-store.js'; \ No newline at end of file diff --git a/packages/crypto/src/kms-local/key-store.ts b/packages/crypto/src/kms-local/key-store.ts deleted file mode 100644 index a65ec5504..000000000 --- a/packages/crypto/src/kms-local/key-store.ts +++ /dev/null @@ -1,109 +0,0 @@ -import type { ManagedKeyStore, ManagedKey, ManagedKeyPair, ManagedPrivateKey } from '../types/index.js'; - -import { MemoryStore } from '@tbd54566975/common'; - -import { randomUuid } from '../utils-new.js'; -import { isManagedKeyPair } from '../utils-new.js'; - -/** - * An implementation of `ManagedKeyStore` that stores key metadata and - * public key material in memory. - * - * An instance of this class can be used by an implementation of - * `KeyManagementSystem`. - * - * This class must be initialized with a {@link MemoryStore}, which serves - * as the key/value store. - */ -export class KmsKeyStore implements ManagedKeyStore { - private keyStore: MemoryStore; - - constructor(keyStore: MemoryStore) { - this.keyStore = keyStore; - } - - async deleteKey({ id }: { id: string }) { - if (await this.keyStore.has(id)) { - await this.keyStore.delete(id); - return true; - } else { - return false; - } - } - - async getKey({ id }: { id: string }): Promise { - return this.keyStore.get(id); - } - - async importKey({ key }: { key: ManagedKey | ManagedKeyPair }): Promise { - let id: string; - if (isManagedKeyPair(key)) { - id = key.publicKey.id; - } else { - key.id ??= randomUuid(); // If an ID wasn't specified, generate one. - id = key.id; - } - - if (await this.keyStore.has(id)) { - throw new Error(`Key with ID already exists: '${id}'`); - } - - // Make a deep copy of the key so that the object stored does not share the same references as the input key. - const clonedKey = structuredClone(key); - await this.keyStore.set(id, clonedKey); - return id; - } - - async listKeys(): Promise> { - return this.keyStore.list(); - } -} - -/** - * An implementation of `ManagedKeyStore` that stores private key - * material in memory. - * - * An instance of this class can be used by an implementation of - * `KeyManagementSystem`. - * - * This class must be initialized with a {@link MemoryStore}, which serves - * as the key/value store. - */ -export class KmsPrivateKeyStore implements ManagedKeyStore { - private keyStore: MemoryStore; - - constructor(keyStore: MemoryStore) { - this.keyStore = keyStore; - } - - async deleteKey({ id }: { id: string }) { - if (await this.keyStore.has(id)) { - await this.keyStore.delete(id); - return true; - } else { - return false; - } - } - - async getKey({ id }: { id: string }): Promise { - return this.keyStore.get(id); - } - - async importKey({ key }: { key: Omit }): Promise { - if (!key.material) throw new TypeError(`Required parameter was missing: 'material'`); - if (!key.type) throw new TypeError(`Required parameter was missing: 'type'`); - - // Make a deep copy of the key so that the object stored does not share the same references as the input key. - // The private key material is transferred to the new object, making the original obj.material unusable. - const clonedKey = structuredClone(key, { transfer: [key.material] }) as ManagedPrivateKey; - - clonedKey.id = randomUuid(); - await this.keyStore.set(clonedKey.id, clonedKey); - - return clonedKey.id; - } - - async listKeys(): Promise> { - return this.keyStore.list(); - } -} \ No newline at end of file diff --git a/packages/crypto/src/kms-local/supported-algorithms.ts b/packages/crypto/src/kms-local/supported-algorithms.ts deleted file mode 100644 index b052fd169..000000000 --- a/packages/crypto/src/kms-local/supported-algorithms.ts +++ /dev/null @@ -1,22 +0,0 @@ -import type { CryptoAlgorithm } from '../algorithms-api/index.js'; - -import { - EcdhAlgorithm, - EcdsaAlgorithm, - EdDsaAlgorithm, - AesCtrAlgorithm, -} from '../crypto-algorithms/index.js'; - -export type AlgorithmImplementation = typeof CryptoAlgorithm & { new(): CryptoAlgorithm; }; - -export type AlgorithmImplementations = { - [algorithmName: string]: AlgorithmImplementation; -}; - -// Map key operations to algorithm specs to implementations. -export const defaultAlgorithms: AlgorithmImplementations = { - 'AES-CTR' : AesCtrAlgorithm, - ECDH : EcdhAlgorithm, - ECDSA : EcdsaAlgorithm, - EdDSA : EdDsaAlgorithm, -}; \ No newline at end of file diff --git a/packages/crypto/src/main.ts b/packages/crypto/src/main.ts deleted file mode 100644 index 1fc111121..000000000 --- a/packages/crypto/src/main.ts +++ /dev/null @@ -1,11 +0,0 @@ -export type * from './types.js'; -export type * from './types/index.js'; - -export * as ed25519 from './ed25519.js'; - -export * from './utils-new.js'; -export * from './kms-local/index.js'; -export * from './key-manager/index.js'; -export * from './algorithms-api/index.js'; -export * from './crypto-algorithms/index.js'; -export * from './crypto-primitives/index.js'; \ No newline at end of file diff --git a/packages/crypto/src/types.ts b/packages/crypto/src/types.ts deleted file mode 100644 index 0617b4612..000000000 --- a/packages/crypto/src/types.ts +++ /dev/null @@ -1,47 +0,0 @@ -export type KeyPair = { - publicKey: Uint8Array; - privateKey: Uint8Array; -} - -export type KeyPairJwk = { - publicKeyJwk: PublicKeyJwk; - privateKeyJwk: PrivateKeyJwk; -}; - -export type Jwk = { - /** The "alg" (algorithm) parameter identifies the algorithm intended for use with the key. */ - alg?: string; - /** The "alg" (algorithm) parameter identifies the algorithm intended for use with the key. */ - kid?: string; - /** identifies the cryptographic algorithm family used with the key, such "EC". */ - kty: string; - - crv: string; -}; - -export type PublicKeyJwk = Jwk & { - /** The "crv" (curve) parameter identifies the cryptographic curve used with the key. - * MUST be present for all EC public keys - */ - crv: string; - /** - * the x coordinate for the Elliptic Curve point. - * Represented as the base64url encoding of the octet string representation of the coordinate. - * MUST be present for all EC public keys - */ - x: string; - /** - * the y coordinate for the Elliptic Curve point. - * Represented as the base64url encoding of the octet string representation of the coordinate. - */ - y?: string; -}; - -export type PrivateKeyJwk = PublicKeyJwk & { - /** - * the Elliptic Curve private key value. - * It is represented as the base64url encoding of the octet string representation of the private key value - * MUST be present to represent Elliptic Curve private keys. - */ - d: string; -}; \ No newline at end of file diff --git a/packages/crypto/src/types/crypto-key.ts b/packages/crypto/src/types/crypto-key.ts index bcd0f065b..864fc68cf 100644 --- a/packages/crypto/src/types/crypto-key.ts +++ b/packages/crypto/src/types/crypto-key.ts @@ -1,182 +1,4 @@ -import type { Web5Crypto } from './web5-crypto.js'; - -export interface BufferKeyPair { - privateKey: ArrayBuffer; - publicKey: ArrayBuffer; -} - -/** - * KeyMetadata - * - * Implementations of KeyManagementSystem can populate this object with KMS platform - * specific data about each key. - * - * This property can also be used to add various tags to the keys under management. - */ -export type KeyMetadata = { - /** - * Additional properties of any type. - */ - [key: string]: any; -} - -/** - * KeyState - * - * The read-only `state` property of the `ManagedKey` interface indicates the - * status of the ManagedKey. - * - * It can have the following string values: - * - * "Enabled": The key is ready for use. - * - * "Disabled": The key may not be used, but the key material is still available, - * and the key can be placed back into the Enabled state. - * - * "PendingCreation": The key is still being created. It may not be used, - * enabled, disabled, or destroyed yet. The KMS will - * automatically change the state to enabled as soon - * as the key is ready. - * - * "PendingDeletion": The key is scheduled for deletion. It can be placed back - * into the Disabled state up until the time of deletion - * using the CancelKeyDeletion() method. Once the key has - * been deleted, any ciphertext encrypted with this key - * is no longer recoverable. Minimum and maximum waiting - * periods are defined by each KMS implementation. - * - * "PendingImport": The key is still being imported. It may not be used, enabled, - * disabled, or deleted yet. The KMS will automatically change - * the state to Enabled once the key is ready. - * - * "PendingUpdate": The key is still being updated. It may not be used, enabled, - * disabled, or deleted until the update process completes. - * The KMS will automatically change the state to Enabled - * once the key is ready. - */ -export type KeyState = 'Enabled' | 'Disabled' | 'PendingCreation' | 'PendingDeletion' | 'PendingImport' | 'PendingUpdate'; - -/** ManagedKey - * - * A ManagedKey represents a cryptographic key used by a cipher for - * encryption or decryption or an algorithm for signing or verification. - */ -export interface ManagedKey { - /** - * A unique identifier for the Key, autogenerated by a KMS. - */ - id: string; - - /** - * An object detailing the algorithm for which the key can be used along - * with additional algorithm-specific parameters. - */ - algorithm: Web5Crypto.GenerateKeyOptions; - - /** - * An alternate identifier used to identify the key in a KMS. - * This property can be used to associate a DID document key ID with a ManagedKey. - */ - alias?: string; - - - /** - * A boolean value that is `true` if the key can be exported and `false` if not. - */ - extractable: boolean; - - /** - * Name of a registered key management system. - */ - kms: string; - - /** - * Optional. Key material as a raw binary data buffer. - */ - material?: ArrayBuffer; - - /** - * Optional. Additional Key metadata. - */ - metadata?: KeyMetadata; - - /** - * A registered string value specifying the algorithm and any algorithm - * specific parameters. - * Supported algorithms vary by KMS. - */ - spec?: string; - - /** - * The current status of the ManagedKey. - */ - state: KeyState; - - /** - * The type of key. - */ - type: Web5Crypto.KeyType; - - /** - * Indicates which cryptographic operations are permissible to be used with this key. - */ - usages: Web5Crypto.KeyUsage[]; -} - -/** - * Represents information about a managed key. - * Private or secret key material is NOT present. - * - */ -export type ManagedKeyInfo = Omit; - -/** ManagedKeyPair - * - * A ManagedKeyPair represents a key pair for an asymmetric cryptography algorithm, - * also known as a public-key algorithm. - * - * A ManagedKeyPair object can be obtained using `generateKey()`, when the - * selected algorithm is one of the asymmetric algorithms: ECDSA or ECDH. - */ -export interface ManagedKeyPair { - /** - * A ManagedKey object representing the private key. For encryption and - * decryption algorithms, this key is used to decrypt. For signing and - * verification algorithms it is used to sign. - */ - privateKey: ManagedKey; - - /** - * A ManagedKey object representing the public key. For encryption and - * decryption algorithms, this key is used to encrypt. For signing and - * verification algorithms it is used to verify signatures. - */ - publicKey: ManagedKey; -} - -/** - * Represents a private key. - * - * The `alias` is used to refer to the key material which is stored as the hex encoding of the raw byte array - * (`privateKeyHex`). - * - * The `type` refers to the type of key that is represented. - * - * @public - */ -export interface ManagedPrivateKey { - /** - * A unique identifier for the Key, autogenerated by a KMS. - */ - id: string - - /** - * Key material as a raw binary data buffer. - */ - material: ArrayBuffer; - - /** - * The type of key. - */ - type: Web5Crypto.PrivateKeyType; +export interface BytesKeyPair { + privateKey: Uint8Array; + publicKey: Uint8Array; } \ No newline at end of file diff --git a/packages/crypto/src/types/crypto-manager.ts b/packages/crypto/src/types/crypto-manager.ts deleted file mode 100644 index 895d56c7f..000000000 --- a/packages/crypto/src/types/crypto-manager.ts +++ /dev/null @@ -1,233 +0,0 @@ -import type { RequireOnly } from '@tbd54566975/common'; - -import type { Web5Crypto } from './web5-crypto.js'; -import type { KeyMetadata, ManagedKey, ManagedKeyPair } from './crypto-key.js'; - -export interface CryptoManager { - decrypt(options: DecryptOptions): Promise; - - deriveBits(options: DeriveBitsOptions): Promise; - - encrypt(options: EncryptOptions): Promise; - - /** - * Generate a new ManagedKey within a CryptoManager implementation. - */ - generateKey(options: GenerateKeyOptions): Promise>; - - /** - * Retrieves detailed information about a ManagedKey or ManagedKeyPair object. - * - * @param options - The options for retrieving the key. - * @param options.keyRef - The reference identifier for the key. Can specify the id or alias property of the key. - * @returns A promise that resolves to either a ManagedKey or ManagedKeyPair object. - */ - getKey(options: { keyRef: string }): Promise; - - importKey(options: ImportableKeyPair): Promise; - importKey(options: ImportableKey): Promise; - importKey(options: ImportKeyOptions): Promise; - - sign(options: SignOptions): Promise; - - verify(options: VerifyOptions): Promise; -} - -/** - * Input arguments for implementations of the CryptoManager interface - * {@link CryptoManager.encrypt | encrypt} method. - * - * @public - */ -export type DecryptOptions = { - /** - * An object defining the cipher algorithm to use and its parameters. - */ - algorithm: Web5Crypto.AlgorithmIdentifier | Web5Crypto.AesCtrOptions | Web5Crypto.AesGcmOptions; - - /** - * An ArrayBuffer, a TypedArray, or a DataView object containing the data - * to be decrypted (also known as the ciphertext). - */ - data: BufferSource; - - /** - * An identifier of the ManagedKey to be used for decryption. - * You can use the id or alias property of the key. - */ - keyRef: string; -} - -/** - * Input arguments for implementations of the CryptoManager interface - * {@link CryptoManager.deriveBits | deriveBits} method. - * - * @public - */ -export type DeriveBitsOptions = { - - /** - * An object defining the derivation algorithm to use and its parameters. - */ - algorithm: Web5Crypto.AlgorithmIdentifier | Web5Crypto.EcdhDeriveKeyOptions; - - /** - * An identifier of the ManagedKey that will be the input to the - * derivation algorithm. - * - * If the algorithm is ECDH, this identifier will refer to an ECDH key pair. - * For PBKDF2, it might be a password. - * For HDKF, it might be the shared secret output of an ECDH key agreement operation. - */ - baseKeyRef: string; - - /** - * A number representing the number of bits to derive. To be compatible with - * all browsers, the number should be a multiple of 8. - */ - length?: number; -} - -/** - * Input arguments for implementations of the CryptoManager interface - * {@link CryptoManager.encrypt | encrypt} method. - * - * @public - */ -export type EncryptOptions = { - /** - * An object defining the cipher algorithm to use and its parameters. - */ - algorithm: Web5Crypto.AlgorithmIdentifier | Web5Crypto.AesCtrOptions | Web5Crypto.AesGcmOptions; - - /** - * An ArrayBuffer, a TypedArray, or a DataView object containing the data - * to be encrypted (also known as the plaintext). - */ - data: BufferSource; - - /** - * An identifier of the ManagedKey to be used for encryption. - * You can use the id or alias property of the key. - */ - keyRef: string; -} - -export type GenerateKeyOptions = { - algorithm: T; - alias?: string; - extractable?: boolean; - keyUsages: Web5Crypto.KeyUsage[]; - metadata?: KeyMetadata; -}; - -export type GenerateKeyOptionTypes = - | Web5Crypto.AlgorithmIdentifier - // | RsaHashedGenerateKeyOptions - | Web5Crypto.AesGenerateKeyOptions - | Web5Crypto.EcdsaGenerateKeyOptions - | Web5Crypto.EdDsaGenerateKeyOptions - // | HmacGenerateKeyOptions - // | Pbkdf2Params; - -export type GenerateKeyType = T extends Web5Crypto.EcGenerateKeyOptions ? ManagedKeyPair : - T extends Web5Crypto.AesGenerateKeyOptions /*| HmacGenerateKeyOptions | Pbkdf2Params*/ ? ManagedKey : - T extends Web5Crypto.AlgorithmIdentifier ? ManagedKey | ManagedKeyPair : - never; - -export type ImportableKey = - RequireOnly< - ManagedKey, - 'algorithm' | 'extractable' | 'kms' | 'type' | 'usages', - 'id' | 'material' | 'state' - > - & { material: BufferSource; }; - -export interface ImportableKeyPair { - privateKey: ImportableKey; - publicKey: ImportableKey; -} - -export type ImportKeyOptions = - | ImportableKey - | ImportableKeyPair - -/** - * Base interface to be implemented by key management systems. - */ -export type KeyManagementSystem = CryptoManager; - -/** - * ManagedKeyStore - * - * This interface should be implemented to provide platform specific - * implementations that are usable by KeyManager and implementations - * of KeyManagementSystem. - * - * Implementations of this class can be used to store: - * ManagedKey and ManagedKeyPair - * or: - * ManagedPrivateKey - * objects. - * - * @public - */ -export interface ManagedKeyStore { - deleteKey(options: { id: K }): Promise - getKey(options: { id: K }): Promise - importKey(options: { key: Omit }): Promise - listKeys(options: unknown): Promise -} - -/** - * Input arguments for implementations of the CryptoManager interface {@link CryptoManager.sign | sign} method. - * - * @public - */ -export type SignOptions = { - /** - * An object that specifies the signature algorithm to use and its parameters. - */ - algorithm: Web5Crypto.AlgorithmIdentifier | Web5Crypto.EcdsaOptions | Web5Crypto.EdDsaOptions; - - /** - * An ArrayBuffer, a TypedArray, or a DataView object containing the data to be signed. - */ - data: BufferSource; - - /** - * An identifier of the ManagedKey to sign with. - * You can use the id or alias property of the key. - */ - keyRef: string; -} - -/** - * Input arguments for implementations of the CryptoManager interface - * {@link CryptoManager.verify | verify} method. - * - * @public - */ -export type VerifyOptions = { - /** - * An object that specifies the algorithm to use and its parameters. - */ - algorithm: Web5Crypto.AlgorithmIdentifier | Web5Crypto.EcdsaOptions | Web5Crypto.EdDsaOptions; - - /** - * An ArrayBuffer, a TypedArray, or a DataView object containing the data - * whose signature is to be verified. - */ - data: BufferSource; - - /** - * An identifier of the ManagedKey to sign with. - * You can use the id or alias property of the key. - */ - keyRef: string; - - /** - * A ArrayBuffer containing the signature to verify. - */ - signature: ArrayBuffer; -} \ No newline at end of file diff --git a/packages/crypto/src/types/index.ts b/packages/crypto/src/types/index.ts deleted file mode 100644 index 0edcbffad..000000000 --- a/packages/crypto/src/types/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export type * from './crypto-key.js'; -export type * from './web5-crypto.js'; -export type * from './crypto-manager.js'; \ No newline at end of file diff --git a/packages/crypto/src/types/web5-crypto.ts b/packages/crypto/src/types/web5-crypto.ts index ca7dcf458..03fa49f05 100644 --- a/packages/crypto/src/types/web5-crypto.ts +++ b/packages/crypto/src/types/web5-crypto.ts @@ -1,6 +1,6 @@ export namespace Web5Crypto { export interface AesCtrOptions extends Algorithm { - counter: BufferSource; + counter: Uint8Array; length: number; } @@ -9,8 +9,8 @@ export namespace Web5Crypto { } export interface AesGcmOptions extends Algorithm { - additionalData?: BufferSource; - iv: BufferSource; + additionalData?: Uint8Array; + iv: Uint8Array; tagLength?: number; } @@ -23,7 +23,7 @@ export namespace Web5Crypto { export interface CryptoKey { algorithm: Web5Crypto.GenerateKeyOptions; extractable: boolean; - handle: ArrayBuffer; + material: Uint8Array; type: KeyType; usages: KeyUsage[]; } @@ -53,7 +53,12 @@ export namespace Web5Crypto { export type EdDsaOptions = Algorithm - export type GenerateKeyOptions = AesGenerateKeyOptions | EcGenerateKeyOptions; + export type GenerateKeyOptions = AesGenerateKeyOptions | EcGenerateKeyOptions | HmacGenerateKeyOptions; + + export interface HmacGenerateKeyOptions extends Algorithm { + hash: AlgorithmIdentifier; + length?: number; + } export interface KeyAlgorithm { name: string; @@ -67,8 +72,8 @@ export namespace Web5Crypto { /** * KeyType * - * The read-only `type` property of the `ManagedKey` interface indicates which - * kind of key is represented by the object. + * The read-only `type` property indicates which kind of key + * is represented by the object. * * It can have the following string values: * @@ -81,8 +86,7 @@ export namespace Web5Crypto { /** * KeyUsage * - * The read-only usage property of the CryptoKey interface indicates what can be - * done with the key. + * The read-only usage property indicates what can be done with the key. * * An Array of strings from the following list: * diff --git a/packages/crypto/src/utils-new.ts b/packages/crypto/src/utils.ts similarity index 58% rename from packages/crypto/src/utils-new.ts rename to packages/crypto/src/utils.ts index c24dee818..5a9d3f165 100644 --- a/packages/crypto/src/utils-new.ts +++ b/packages/crypto/src/utils.ts @@ -1,7 +1,9 @@ -import type { BufferKeyPair, ManagedKey, ManagedKeyPair, Web5Crypto } from './types/index.js'; +// import type { BytesKeyPair, Web5Crypto } from './types/index.js'; +import type { Web5Crypto } from './types/web5-crypto.js'; +import type { BytesKeyPair } from './types/crypto-key.js'; -import { universalTypeOf } from '@tbd54566975/common'; -import { bytesToHex, randomBytes } from '@noble/hashes/utils'; +import { Convert, Multicodec, universalTypeOf } from '@web5/common'; +import { bytesToHex, randomBytes as nobleRandomBytes } from '@noble/hashes/utils'; /** * Checks whether the properties object provided contains the specified property. @@ -16,11 +18,11 @@ export function checkRequiredProperty(options: { inObject: object }): void { if (!options || options.property === undefined || options.inObject === undefined) { - throw new TypeError(`One or more required arguments missing: 'property, properties'`); + throw new TypeError(`One or more required parameters missing: 'property, properties'`); } const { property, inObject } = options; if (!(property in inObject)) { - throw new TypeError(`Required parameter was missing: '${property}'`); + throw new TypeError(`Required parameter missing: '${property}'`); } } @@ -36,7 +38,7 @@ export function checkValidProperty(options: { property: string, allowedProperties: Array | Map | Set }): void { if (!options || options.property === undefined || options.allowedProperties === undefined) { - throw new TypeError(`One or more required arguments missing: 'property, allowedProperties'`); + throw new TypeError(`One or more required parameters missing: 'property, allowedProperties'`); } const { property, allowedProperties } = options; if ( @@ -51,15 +53,15 @@ export function checkValidProperty(options: { /** * Type guard function to check if the given key is a raw key pair - * of ArrayBuffers. + * of Uint8Array typed arrays. * * @param key The key to check. - * @returns True if the key is a pair of key ArrayBuffers, false otherwise. + * @returns True if the key is a pair of Uint8Array typed arrays, false otherwise. */ -export function isBufferKeyPair(key: BufferKeyPair | undefined): key is BufferKeyPair { +export function isBytesKeyPair(key: BytesKeyPair | undefined): key is BytesKeyPair { return (key && 'privateKey' in key && 'publicKey' in key && - universalTypeOf(key.privateKey) === 'ArrayBuffer' && - universalTypeOf(key.publicKey) === 'ArrayBuffer') ? true : false; + universalTypeOf(key.privateKey) === 'Uint8Array' && + universalTypeOf(key.publicKey) === 'Uint8Array') ? true : false; } /** @@ -73,24 +75,50 @@ export function isCryptoKeyPair(key: Web5Crypto.CryptoKey | Web5Crypto.CryptoKey return key && 'privateKey' in key && 'publicKey' in key; } -/** - * Type guard function to check if the given key is a ManagedKey. - * - * @param key The key to check. - * @returns True if the key is a ManagedKeyPair, false otherwise. - */ -export function isManagedKey(key: ManagedKey | ManagedKeyPair | undefined): key is ManagedKey { - return key !== undefined && 'algorithm' in key && 'extractable' in key && 'type' in key && 'usages' in key; +export function keyToMultibaseId(options: { + key: Uint8Array, + multicodecCode?: number, + multicodecName?: string +}): string { + const { key, multicodecCode, multicodecName } = options; + const prefixedKey = Multicodec.addPrefix({ code: multicodecCode, data: key, name: multicodecName }); + const prefixedKeyB58 = Convert.uint8Array(prefixedKey).toBase58Btc(); + const multibaseKeyId = Convert.base58Btc(prefixedKeyB58).toMultibase(); + + return multibaseKeyId; +} + +export function multibaseIdToKey(options: { + multibaseKeyId: string +}): { key: Uint8Array, multicodecCode: number, multicodecName: string } { + const { multibaseKeyId } = options; + + const prefixedKeyB58 = Convert.multibase(multibaseKeyId).toBase58Btc(); + const prefixedKey = Convert.base58Btc(prefixedKeyB58).toUint8Array(); + const { code, data, name } = Multicodec.removePrefix({ prefixedData: prefixedKey }); + + return { key: data, multicodecCode: code, multicodecName: name }; } /** - * Type guard function to check if the given key is a ManagedKeyPair. + * Generates secure pseudorandom values of the specified length using + * `crypto.getRandomValues`, which defers to the operating system. * - * @param key The key to check. - * @returns True if the key is a ManagedKeyPair, false otherwise. + * This function is a wrapper around `randomBytes` from the '@noble/hashes' + * package. It's designed to be cryptographically strong, suitable for + * generating keys, initialization vectors, and other random values. + * + * @param bytesLength - The number of bytes to generate. + * @returns A Uint8Array containing the generated random bytes. + * + * @example + * const bytes = randomBytes(32); // Generates 32 random bytes + * + * @see {@link https://www.npmjs.com/package/@noble/hashes | @noble/hashes on NPM} + * for more information about the underlying implementation. */ -export function isManagedKeyPair(key: ManagedKey | ManagedKeyPair | undefined): key is ManagedKeyPair { - return key !== undefined && 'privateKey' in key && 'publicKey' in key; +export function randomBytes(bytesLength: number): Uint8Array { + return nobleRandomBytes(bytesLength); } /** diff --git a/packages/crypto/tests/algorithms-api.spec.ts b/packages/crypto/tests/algorithms-api.spec.ts index 80a7b1c2c..69592f949 100644 --- a/packages/crypto/tests/algorithms-api.spec.ts +++ b/packages/crypto/tests/algorithms-api.spec.ts @@ -1,6 +1,7 @@ -import type { Web5Crypto } from '../src/types/index.js'; +import type { Web5Crypto } from '../src/types/web5-crypto.js'; -import { expect } from 'chai'; +import chai, { expect } from 'chai'; +import chaiAsPromised from 'chai-as-promised'; import { CryptoKey, @@ -16,25 +17,27 @@ import { BaseEllipticCurveAlgorithm, } from '../src/algorithms-api/index.js'; +chai.use(chaiAsPromised); + describe('Algorithms API', () => { describe('CryptoAlgorithm', () => { class TestCryptoAlgorithm extends CryptoAlgorithm { public name = 'TestAlgorithm'; public keyUsages: KeyUsage[] = ['decrypt', 'deriveBits', 'deriveKey', 'encrypt', 'sign', 'unwrapKey', 'verify', 'wrapKey']; - public async decrypt(): Promise { + public async decrypt(): Promise { return null as any; } - public async deriveBits(): Promise { + public async deriveBits(): Promise { return null as any; } - public async encrypt(): Promise { + public async encrypt(): Promise { return null as any; } public async generateKey(): Promise { return { publicKey: {} as any, privateKey: {} as any }; } - public async sign(): Promise { + public async sign(): Promise { return null as any; } public async verify(): Promise { @@ -62,7 +65,7 @@ describe('Algorithms API', () => { }); it('throws an error if the algorithm name is missing', () => { - expect(() => alg.checkAlgorithmName({} as any)).to.throw(TypeError, 'Required argument missing'); + expect(() => alg.checkAlgorithmName({} as any)).to.throw(TypeError, 'Required parameter missing'); }); }); @@ -75,7 +78,7 @@ describe('Algorithms API', () => { usages : null }; expect(() => alg.checkCryptoKey({ - // @ts-expect-error because 'handle' property is intentionally omitted to support WebCrypto API CryptoKeys. + // @ts-expect-error because 'material' property is intentionally omitted to support WebCrypto API CryptoKeys. key: mockCryptoKey })).to.not.throw(); }); @@ -95,7 +98,7 @@ describe('Algorithms API', () => { describe('checkKeyAlgorithm()', () => { it('throws an error when keyAlgorithmName is undefined', async () => { - expect(() => alg.checkKeyAlgorithm({} as any)).to.throw(TypeError, 'Required argument missing'); + expect(() => alg.checkKeyAlgorithm({} as any)).to.throw(TypeError, 'Required parameter missing'); }); it('throws an error when keyAlgorithmName does not match', async () => { @@ -111,9 +114,9 @@ describe('Algorithms API', () => { describe('checkKeyType()', () => { it('throws an error when keyType or allowedKeyType is undefined', async () => { - expect(() => alg.checkKeyType({} as any)).to.throw(TypeError, 'One or more required arguments missing'); - expect(() => alg.checkKeyType({ keyType: 'public' } as any)).to.throw(TypeError, 'One or more required arguments missing'); - expect(() => alg.checkKeyType({ allowedKeyType: 'public' } as any)).to.throw(TypeError, 'One or more required arguments missing'); + expect(() => alg.checkKeyType({} as any)).to.throw(TypeError, 'One or more required parameters missing'); + expect(() => alg.checkKeyType({ keyType: 'public' } as any)).to.throw(TypeError, 'One or more required parameters missing'); + expect(() => alg.checkKeyType({ allowedKeyType: 'public' } as any)).to.throw(TypeError, 'One or more required parameters missing'); }); it('throws an error when keyType does not match allowedKeyType', async () => { @@ -131,8 +134,8 @@ describe('Algorithms API', () => { describe('checkKeyUsages()', () => { it('throws an error when keyUsages is undefined or empty', async () => { - expect(() => alg.checkKeyUsages({ allowedKeyUsages: ['sign'] } as any)).to.throw(TypeError, 'required parameter was missing or empty'); - expect(() => alg.checkKeyUsages({ keyUsages: [], allowedKeyUsages: ['sign'] })).to.throw(TypeError, 'required parameter was missing or empty'); + expect(() => alg.checkKeyUsages({ allowedKeyUsages: ['sign'] } as any)).to.throw(TypeError, 'Required parameter missing or empty'); + expect(() => alg.checkKeyUsages({ keyUsages: [], allowedKeyUsages: ['sign'] })).to.throw(TypeError, 'Required parameter missing or empty'); }); it('throws an error when keyUsages are not in allowedKeyUsages', async () => { @@ -159,10 +162,10 @@ describe('Algorithms API', () => { class TestAesAlgorithm extends BaseAesAlgorithm { public name = 'TestAlgorithm'; public keyUsages: KeyUsage[] = ['decrypt', 'encrypt']; - public async decrypt(): Promise { + public async decrypt(): Promise { return null as any; } - public async encrypt(): Promise { + public async encrypt(): Promise { return null as any; } public async generateKey(): Promise { @@ -196,7 +199,7 @@ describe('Algorithms API', () => { // @ts-expect-error because length was intentionally omitted. algorithm : { name: 'TestAlgorithm' }, keyUsages : ['encrypt'] - })).to.throw(TypeError, 'Required parameter was missing'); + })).to.throw(TypeError, 'Required parameter missing'); }); it('throws an error when the specified length is not a Number', () => { @@ -257,7 +260,7 @@ describe('Algorithms API', () => { let dataEncryptionKey: Web5Crypto.CryptoKey; beforeEach(() => { - dataEncryptionKey = new CryptoKey({ name: 'AES-CTR', length: 128 }, false, new ArrayBuffer(32), 'secret', ['encrypt', 'decrypt']); + dataEncryptionKey = new CryptoKey({ name: 'AES-CTR', length: 128 }, false, new Uint8Array(32), 'secret', ['encrypt', 'decrypt']); }); describe('checkAlgorithmOptions()', () => { @@ -265,7 +268,7 @@ describe('Algorithms API', () => { expect(() => alg.checkAlgorithmOptions({ algorithm: { name : 'AES-CTR', - counter : new ArrayBuffer(16), + counter : new Uint8Array(16), length : 128 }, key: dataEncryptionKey @@ -276,7 +279,7 @@ describe('Algorithms API', () => { expect(() => alg.checkAlgorithmOptions({ algorithm: { name : 'invalid-name', - counter : new ArrayBuffer(16), + counter : new Uint8Array(16), length : 128 }, key: dataEncryptionKey @@ -288,38 +291,17 @@ describe('Algorithms API', () => { expect(() => alg.checkAlgorithmOptions({ algorithm: { name : 'AES-CTR', length : 128 - }})).to.throw(TypeError, 'Required parameter was missing'); + }})).to.throw(TypeError, 'Required parameter missing'); }); - it('accepts counter as ArrayBuffer, DataView, and TypedArray', () => { - const dataU8A = new Uint8Array(16); + it('accepts counter as Uint8Array', () => { + const data = new Uint8Array(16); const algorithm: { name?: string, counter?: any, length?: number } = {}; algorithm.name = 'AES-CTR'; algorithm.length = 128; - // ArrayBuffer - algorithm.counter = dataU8A.buffer; - expect(() => alg.checkAlgorithmOptions({ - algorithm : algorithm as Web5Crypto.AesCtrOptions, - key : dataEncryptionKey - })).to.not.throw(); - - // DataView - algorithm.counter = new DataView(dataU8A.buffer); - expect(() => alg.checkAlgorithmOptions({ - algorithm : algorithm as Web5Crypto.AesCtrOptions, - key : dataEncryptionKey - })).to.not.throw(); - // TypedArray - Uint8Array - algorithm.counter = dataU8A; - expect(() => alg.checkAlgorithmOptions({ - algorithm : algorithm as Web5Crypto.AesCtrOptions, - key : dataEncryptionKey - })).to.not.throw(); - - // TypedArray - Int8Array - algorithm.counter = new Int8Array(16); + algorithm.counter = data; expect(() => alg.checkAlgorithmOptions({ algorithm : algorithm as Web5Crypto.AesCtrOptions, key : dataEncryptionKey @@ -342,7 +324,7 @@ describe('Algorithms API', () => { expect(() => alg.checkAlgorithmOptions({ algorithm: { name : 'AES-CTR', - counter : new ArrayBuffer(128), + counter : new Uint8Array(128), length : 128 }, key: dataEncryptionKey @@ -353,14 +335,14 @@ describe('Algorithms API', () => { // @ts-expect-error because lengthy property was intentionally omitted. expect(() => alg.checkAlgorithmOptions({ algorithm: { name : 'AES-CTR', - counter : new ArrayBuffer(16) - }})).to.throw(TypeError, `Required parameter was missing: 'length'`); + counter : new Uint8Array(16) + }})).to.throw(TypeError, `Required parameter missing: 'length'`); }); it('throws an error if length is not a Number', () => { expect(() => alg.checkAlgorithmOptions({ algorithm: { name : 'AES-CTR', - counter : new ArrayBuffer(16), + counter : new Uint8Array(16), // @ts-expect-error because length is being intentionally specified as a string instead of a number. length : '128' }})).to.throw(TypeError, 'is not of type'); @@ -370,7 +352,7 @@ describe('Algorithms API', () => { expect(() => alg.checkAlgorithmOptions({ algorithm: { name : 'AES-CTR', - counter : new ArrayBuffer(16), + counter : new Uint8Array(16), length : 0 }, key: dataEncryptionKey @@ -379,7 +361,7 @@ describe('Algorithms API', () => { expect(() => alg.checkAlgorithmOptions({ algorithm: { name : 'AES-CTR', - counter : new ArrayBuffer(16), + counter : new Uint8Array(16), length : 256 }, key: dataEncryptionKey @@ -390,40 +372,40 @@ describe('Algorithms API', () => { // @ts-expect-error because keyy property was intentionally omitted. expect(() => alg.checkAlgorithmOptions({ algorithm: { name : 'AES-CTR', - counter : new ArrayBuffer(16), + counter : new Uint8Array(16), length : 64 - }})).to.throw(TypeError, `Required parameter was missing: 'key'`); + }})).to.throw(TypeError, `Required parameter missing: 'key'`); }); it('throws an error if the given key is not valid', () => { // @ts-ignore-error because a required property is being intentionally deleted to trigger the check to throw. delete dataEncryptionKey.extractable; expect(() => alg.checkAlgorithmOptions({ - algorithm : { name: 'AES-CTR', counter: new ArrayBuffer(16), length: 64 }, + algorithm : { name: 'AES-CTR', counter: new Uint8Array(16), length: 64 }, key : dataEncryptionKey })).to.throw(TypeError, 'Object is not a CryptoKey'); }); it('throws an error if the algorithm of the key does not match', () => { - const dataEncryptionKey = new CryptoKey({ name: 'non-existent-algorithm', length: 128 }, false, new ArrayBuffer(32), 'secret', ['encrypt', 'decrypt']); + const dataEncryptionKey = new CryptoKey({ name: 'non-existent-algorithm', length: 128 }, false, new Uint8Array(32), 'secret', ['encrypt', 'decrypt']); expect(() => alg.checkAlgorithmOptions({ - algorithm : { name: 'AES-CTR', counter: new ArrayBuffer(16), length: 64 }, + algorithm : { name: 'AES-CTR', counter: new Uint8Array(16), length: 64 }, key : dataEncryptionKey })).to.throw(InvalidAccessError, 'does not match'); }); it('throws an error if a private key is specified as the key', () => { - const dataEncryptionKey = new CryptoKey({ name: 'AES-CTR', length: 128 }, false, new ArrayBuffer(32), 'private', ['encrypt', 'decrypt']); + const dataEncryptionKey = new CryptoKey({ name: 'AES-CTR', length: 128 }, false, new Uint8Array(32), 'private', ['encrypt', 'decrypt']); expect(() => alg.checkAlgorithmOptions({ - algorithm : { name: 'AES-CTR', counter: new ArrayBuffer(16), length: 64 }, + algorithm : { name: 'AES-CTR', counter: new Uint8Array(16), length: 64 }, key : dataEncryptionKey })).to.throw(InvalidAccessError, 'Requested operation is not valid'); }); it('throws an error if a public key is specified as the key', () => { - const dataEncryptionKey = new CryptoKey({ name: 'AES-CTR', length: 128 }, false, new ArrayBuffer(32), 'public', ['encrypt', 'decrypt']); + const dataEncryptionKey = new CryptoKey({ name: 'AES-CTR', length: 128 }, false, new Uint8Array(32), 'public', ['encrypt', 'decrypt']); expect(() => alg.checkAlgorithmOptions({ - algorithm : { name: 'AES-CTR', counter: new ArrayBuffer(16), length: 64 }, + algorithm : { name: 'AES-CTR', counter: new Uint8Array(16), length: 64 }, key : dataEncryptionKey })).to.throw(InvalidAccessError, 'Requested operation is not valid'); }); @@ -436,13 +418,13 @@ describe('Algorithms API', () => { public name = 'TestAlgorithm'; public namedCurves = ['curveA']; public keyUsages: KeyUsage[] = ['decrypt']; - public async deriveBits(): Promise { + public async deriveBits(): Promise { return null as any; } public async generateKey(): Promise { return { publicKey: {} as any, privateKey: {} as any }; } - public async sign(): Promise { + public async sign(): Promise { return null as any; } public async verify(): Promise { @@ -515,8 +497,8 @@ describe('Algorithms API', () => { let ownPrivateKey: Web5Crypto.CryptoKey; beforeEach(() => { - otherPartyPublicKey = new CryptoKey({ name: 'ECDH', namedCurve: 'X25519' }, false, new ArrayBuffer(32), 'public', ['deriveBits', 'deriveKey']); - ownPrivateKey = new CryptoKey({ name: 'ECDH', namedCurve: 'X25519' }, false, new ArrayBuffer(32), 'private', ['deriveBits', 'deriveKey']); + otherPartyPublicKey = new CryptoKey({ name: 'ECDH', namedCurve: 'X25519' }, false, new Uint8Array(32), 'public', ['deriveBits', 'deriveKey']); + ownPrivateKey = new CryptoKey({ name: 'ECDH', namedCurve: 'X25519' }, false, new Uint8Array(32), 'private', ['deriveBits', 'deriveKey']); }); it('does not throw with matching algorithm name and valid publicKey and baseKey', () => { @@ -538,7 +520,7 @@ describe('Algorithms API', () => { // @ts-expect-error because `publicKey` property is intentionally omitted. algorithm : { name: 'ECDH' }, baseKey : ownPrivateKey - })).to.throw(TypeError, `Required parameter was missing: 'publicKey'`); + })).to.throw(TypeError, `Required parameter missing: 'publicKey'`); }); it('throws an error if the given publicKey is not valid', () => { @@ -551,7 +533,7 @@ describe('Algorithms API', () => { }); it('throws an error if the algorithm of the publicKey does not match', () => { - const otherPartyPublicKey = new CryptoKey({ name: 'Nope', namedCurve: 'X25519' }, false, new ArrayBuffer(32), 'public', ['deriveBits', 'deriveKey']); + const otherPartyPublicKey = new CryptoKey({ name: 'Nope', namedCurve: 'X25519' }, false, new Uint8Array(32), 'public', ['deriveBits', 'deriveKey']); expect(() => alg.checkAlgorithmOptions({ algorithm : { name: 'ECDH', publicKey: otherPartyPublicKey }, baseKey : ownPrivateKey @@ -559,7 +541,7 @@ describe('Algorithms API', () => { }); it('throws an error if a private key is specified as the publicKey', () => { - const ecdhPrivateKey = new CryptoKey({ name: 'ECDH', namedCurve: 'X25519' }, false, new ArrayBuffer(32), 'private', ['deriveBits', 'deriveKey']); + const ecdhPrivateKey = new CryptoKey({ name: 'ECDH', namedCurve: 'X25519' }, false, new Uint8Array(32), 'private', ['deriveBits', 'deriveKey']); expect(() => alg.checkAlgorithmOptions({ algorithm : { name: 'ECDH', publicKey: ecdhPrivateKey }, baseKey : ownPrivateKey @@ -570,7 +552,7 @@ describe('Algorithms API', () => { // @ts-expect-error because `baseKey` property is intentionally omitted. expect(() => alg.checkAlgorithmOptions({ algorithm: { name: 'ECDH', publicKey: otherPartyPublicKey } - })).to.throw(TypeError, `Required parameter was missing: 'baseKey'`); + })).to.throw(TypeError, `Required parameter missing: 'baseKey'`); }); it('throws an error if the given baseKey is not valid', () => { @@ -583,7 +565,7 @@ describe('Algorithms API', () => { }); it('throws an error if the algorithm of the baseKey does not match', () => { - const ownPrivateKey = new CryptoKey({ name: 'non-existent-algorithm', namedCurve: 'X25519' }, false, new ArrayBuffer(32), 'private', ['deriveBits', 'deriveKey']); + const ownPrivateKey = new CryptoKey({ name: 'non-existent-algorithm', namedCurve: 'X25519' }, false, new Uint8Array(32), 'private', ['deriveBits', 'deriveKey']); expect(() => alg.checkAlgorithmOptions({ algorithm : { name: 'ECDH', publicKey: otherPartyPublicKey }, baseKey : ownPrivateKey @@ -591,7 +573,7 @@ describe('Algorithms API', () => { }); it('throws an error if a public key is specified as the baseKey', () => { - const ownPrivateKey = new CryptoKey({ name: 'ECDH', namedCurve: 'X25519' }, false, new ArrayBuffer(32), 'public', ['deriveBits', 'deriveKey']); + const ownPrivateKey = new CryptoKey({ name: 'ECDH', namedCurve: 'X25519' }, false, new Uint8Array(32), 'public', ['deriveBits', 'deriveKey']); expect(() => alg.checkAlgorithmOptions({ algorithm : { name: 'ECDH', publicKey: otherPartyPublicKey }, baseKey : ownPrivateKey @@ -599,7 +581,7 @@ describe('Algorithms API', () => { }); it('throws an error if the named curve of the public and base keys does not match', () => { - const ownPrivateKey = new CryptoKey({ name: 'ECDH', namedCurve: 'secp256k1' }, false, new ArrayBuffer(32), 'private', ['deriveBits', 'deriveKey']); + const ownPrivateKey = new CryptoKey({ name: 'ECDH', namedCurve: 'secp256k1' }, false, new Uint8Array(32), 'private', ['deriveBits', 'deriveKey']); expect(() => alg.checkAlgorithmOptions({ algorithm : { name: 'ECDH', publicKey: otherPartyPublicKey }, baseKey : ownPrivateKey @@ -648,11 +630,11 @@ describe('Algorithms API', () => { // @ts-expect-error because `hash` property is intentionally omitted. expect(() => alg.checkAlgorithmOptions({ algorithm: { name: 'ECDSA', - }})).to.throw(TypeError, 'Required parameter was missing'); + }})).to.throw(TypeError, 'Required parameter missing'); }); it('throws an error if the given hash algorithm is not supported', () => { - const ecdhPublicKey = new CryptoKey({ name: 'ECDH', namedCurve: 'X25519' }, false, new ArrayBuffer(32), 'public', ['deriveBits', 'deriveKey']); + const ecdhPublicKey = new CryptoKey({ name: 'ECDH', namedCurve: 'X25519' }, false, new Uint8Array(32), 'public', ['deriveBits', 'deriveKey']); // @ts-ignore-error because a required property is being intentionally deleted to trigger the check to throw. delete ecdhPublicKey.extractable; expect(() => alg.checkAlgorithmOptions({ algorithm: { diff --git a/packages/crypto/tests/crypto-algorithms.spec.ts b/packages/crypto/tests/crypto-algorithms.spec.ts index 5f8f10f9f..83c817542 100644 --- a/packages/crypto/tests/crypto-algorithms.spec.ts +++ b/packages/crypto/tests/crypto-algorithms.spec.ts @@ -1,9 +1,9 @@ -import type { Web5Crypto } from '../src/types/index.js'; +import type { Web5Crypto } from '../src/types/web5-crypto.js'; import sinon from 'sinon'; import chai, { expect } from 'chai'; +import { Convert } from '@web5/common'; import chaiAsPromised from 'chai-as-promised'; -import { Convert } from '@tbd54566975/common'; import { aesCtrTestVectors } from './fixtures/test-vectors/aes.js'; import { AesCtr, Ed25519, Secp256k1, X25519 } from '../src/crypto-primitives/index.js'; @@ -37,18 +37,18 @@ describe('Default Crypto Algorithm Implementations', () => { }); }); - it('returns plaintext as an ArrayBuffer', async () => { + it('returns plaintext as a Uint8Array', async () => { const plaintext = await aesCtr.decrypt({ algorithm: { name : 'AES-CTR', - counter : new ArrayBuffer(16), + counter : new Uint8Array(16), length : 128 }, key : secretCryptoKey, data : new Uint8Array([1, 2, 3, 4]) }); - expect(plaintext).to.be.instanceOf(ArrayBuffer); + expect(plaintext).to.be.instanceOf(Uint8Array); expect(plaintext.byteLength).to.equal(4); }); @@ -59,7 +59,7 @@ describe('Default Crypto Algorithm Implementations', () => { secretCryptoKey = new CryptoKey( { name: 'AES-CTR', length: 128 }, false, - Convert.hex(vector.key).toArrayBuffer(), + Convert.hex(vector.key).toUint8Array(), 'secret', ['encrypt', 'decrypt'] ); @@ -72,7 +72,7 @@ describe('Default Crypto Algorithm Implementations', () => { key : secretCryptoKey, data : Convert.hex(vector.ciphertext).toUint8Array() }); - expect(Convert.arrayBuffer(plaintext).toHex()).to.deep.equal(vector.data); + expect(Convert.uint8Array(plaintext).toHex()).to.deep.equal(vector.data); } }); @@ -80,28 +80,28 @@ describe('Default Crypto Algorithm Implementations', () => { const secretCryptoKey: Web5Crypto.CryptoKey = new CryptoKey( { name: 'AES-CTR', length: 128 }, false, - new ArrayBuffer(16), + new Uint8Array(16), 'secret', ['encrypt', 'decrypt'] ); // Invalid (algorithm name, counter, length) result in algorithm name check failing first. await expect(aesCtr.decrypt({ - algorithm : { name: 'foo', counter: new ArrayBuffer(64), length: 512 }, + algorithm : { name: 'foo', counter: new Uint8Array(64), length: 512 }, key : secretCryptoKey, data : new Uint8Array([1, 2, 3, 4]) })).to.eventually.be.rejectedWith(NotSupportedError, 'Algorithm not supported'); // Valid (algorithm name) + Invalid (counter, length) result counter check failing first. await expect(aesCtr.decrypt({ - algorithm : { name: 'AES-CTR', counter: new ArrayBuffer(64), length: 512 }, + algorithm : { name: 'AES-CTR', counter: new Uint8Array(64), length: 512 }, key : secretCryptoKey, data : new Uint8Array([1, 2, 3, 4]) })).to.eventually.be.rejectedWith(OperationError, `'counter' must have length`); // Valid (algorithm name, counter) + Invalid (length) result length check failing first. await expect(aesCtr.decrypt({ - algorithm : { name: 'AES-CTR', counter: new ArrayBuffer(16), length: 512 }, + algorithm : { name: 'AES-CTR', counter: new Uint8Array(16), length: 512 }, key : secretCryptoKey, data : new Uint8Array([1, 2, 3, 4]) })).to.eventually.be.rejectedWith(OperationError, `'length' should be in the range`); @@ -112,7 +112,7 @@ describe('Default Crypto Algorithm Implementations', () => { secretCryptoKey.usages = ['encrypt']; await expect(aesCtr.decrypt({ - algorithm : { name: 'AES-CTR', counter: new ArrayBuffer(16), length: 128 }, + algorithm : { name: 'AES-CTR', counter: new Uint8Array(16), length: 128 }, key : secretCryptoKey, data : new Uint8Array([1, 2, 3, 4]) })).to.eventually.be.rejectedWith(InvalidAccessError, 'is not valid for the provided key'); @@ -130,18 +130,18 @@ describe('Default Crypto Algorithm Implementations', () => { }); }); - it('returns ciphertext as an ArrayBuffer', async () => { + it('returns ciphertext as a Uint8Array', async () => { const ciphertext = await aesCtr.encrypt({ algorithm: { name : 'AES-CTR', - counter : new ArrayBuffer(16), + counter : new Uint8Array(16), length : 128 }, key : secretCryptoKey, data : new Uint8Array([1, 2, 3, 4]) }); - expect(ciphertext).to.be.instanceOf(ArrayBuffer); + expect(ciphertext).to.be.instanceOf(Uint8Array); expect(ciphertext.byteLength).to.equal(4); }); @@ -151,7 +151,7 @@ describe('Default Crypto Algorithm Implementations', () => { secretCryptoKey = new CryptoKey( { name: 'AES-CTR', length: 128 }, false, - Convert.hex(vector.key).toArrayBuffer(), + Convert.hex(vector.key).toUint8Array(), 'secret', ['encrypt', 'decrypt'] ); @@ -164,7 +164,7 @@ describe('Default Crypto Algorithm Implementations', () => { key : secretCryptoKey, data : Convert.hex(vector.data).toUint8Array() }); - expect(Convert.arrayBuffer(ciphertext).toHex()).to.deep.equal(vector.ciphertext); + expect(Convert.uint8Array(ciphertext).toHex()).to.deep.equal(vector.ciphertext); } }); @@ -172,28 +172,28 @@ describe('Default Crypto Algorithm Implementations', () => { const secretCryptoKey: Web5Crypto.CryptoKey = new CryptoKey( { name: 'AES-CTR', length: 128 }, false, - new ArrayBuffer(16), + new Uint8Array(16), 'secret', ['encrypt', 'decrypt'] ); // Invalid (algorithm name, counter, length) result in algorithm name check failing first. await expect(aesCtr.encrypt({ - algorithm : { name: 'foo', counter: new ArrayBuffer(64), length: 512 }, + algorithm : { name: 'foo', counter: new Uint8Array(64), length: 512 }, key : secretCryptoKey, data : new Uint8Array([1, 2, 3, 4]) })).to.eventually.be.rejectedWith(NotSupportedError, 'Algorithm not supported'); // Valid (algorithm name) + Invalid (counter, length) result counter check failing first. await expect(aesCtr.encrypt({ - algorithm : { name: 'AES-CTR', counter: new ArrayBuffer(64), length: 512 }, + algorithm : { name: 'AES-CTR', counter: new Uint8Array(64), length: 512 }, key : secretCryptoKey, data : new Uint8Array([1, 2, 3, 4]) })).to.eventually.be.rejectedWith(OperationError, `'counter' must have length`); // Valid (algorithm name, counter) + Invalid (length) result length check failing first. await expect(aesCtr.encrypt({ - algorithm : { name: 'AES-CTR', counter: new ArrayBuffer(16), length: 512 }, + algorithm : { name: 'AES-CTR', counter: new Uint8Array(16), length: 512 }, key : secretCryptoKey, data : new Uint8Array([1, 2, 3, 4]) })).to.eventually.be.rejectedWith(OperationError, `'length' should be in the range`); @@ -204,7 +204,7 @@ describe('Default Crypto Algorithm Implementations', () => { secretCryptoKey.usages = ['decrypt']; await expect(aesCtr.encrypt({ - algorithm : { name: 'AES-CTR', counter: new ArrayBuffer(16), length: 128 }, + algorithm : { name: 'AES-CTR', counter: new Uint8Array(16), length: 128 }, key : secretCryptoKey, data : new Uint8Array([1, 2, 3, 4]) })).to.eventually.be.rejectedWith(InvalidAccessError, 'is not valid for the provided key'); @@ -221,7 +221,7 @@ describe('Default Crypto Algorithm Implementations', () => { expect(key.algorithm.name).to.equal('AES-CTR'); expect(key.usages).to.deep.equal(['encrypt', 'decrypt', 'wrapKey', 'unwrapKey']); - expect(key.handle.byteLength).to.equal(128 / 8); + expect(key.material.byteLength).to.equal(128 / 8); }); it('secret key is selectively extractable', async () => { @@ -415,7 +415,7 @@ describe('Default Crypto Algorithm Implementations', () => { baseKey : ownPrivateKey, length : null }); - expect(sharedSecret).to.be.instanceOf(ArrayBuffer); + expect(sharedSecret).to.be.instanceOf(Uint8Array); expect(sharedSecret.byteLength).to.equal(32); }); @@ -440,7 +440,7 @@ describe('Default Crypto Algorithm Implementations', () => { length : null }); - expect(sharedSecret).to.be.instanceOf(ArrayBuffer); + expect(sharedSecret).to.be.instanceOf(Uint8Array); expect(sharedSecret.byteLength).to.equal(32); }); @@ -839,7 +839,7 @@ describe('Default Crypto Algorithm Implementations', () => { describe('sign()', () => { let keyPair: Web5Crypto.CryptoKeyPair; - let dataU8A = new Uint8Array([51, 52, 53]); + let data = new Uint8Array([51, 52, 53]); beforeEach(async () => { keyPair = await ecdsa.generateKey({ @@ -853,10 +853,10 @@ describe('Default Crypto Algorithm Implementations', () => { const signature = await ecdsa.sign({ algorithm : { name: 'ECDSA', hash: 'SHA-256' }, key : keyPair.privateKey, - data : dataU8A + data : data }); - expect(signature).to.be.instanceOf(ArrayBuffer); + expect(signature).to.be.instanceOf(Uint8Array); expect(signature.byteLength).to.equal(64); }); @@ -894,7 +894,7 @@ describe('Default Crypto Algorithm Implementations', () => { await expect(ecdsa.sign({ algorithm : { name: 'ECDSA', hash: 'SHA-256' }, key : keyPair.publicKey, - data : dataU8A + data : data })).to.eventually.be.rejectedWith(InvalidAccessError, 'Requested operation is not valid'); }); @@ -905,7 +905,7 @@ describe('Default Crypto Algorithm Implementations', () => { await expect(ecdsa.sign({ algorithm : { name: 'ECDSA', hash: 'SHA-256' }, key : keyPair.privateKey, - data : dataU8A + data : data })).to.eventually.be.rejectedWith(InvalidAccessError, 'is not valid for the provided key'); }); @@ -917,15 +917,15 @@ describe('Default Crypto Algorithm Implementations', () => { await expect(ecdsa.sign({ algorithm : { name: 'ECDSA', hash: 'SHA-256' }, key : keyPair.privateKey, - data : dataU8A + data : data })).to.eventually.be.rejectedWith(TypeError, 'Out of range'); }); }); describe('verify()', () => { let keyPair: Web5Crypto.CryptoKeyPair; - let signature: ArrayBuffer; - let dataU8A = new Uint8Array([51, 52, 53]); + let signature: Uint8Array; + let data = new Uint8Array([51, 52, 53]); beforeEach(async () => { keyPair = await ecdsa.generateKey({ @@ -937,7 +937,7 @@ describe('Default Crypto Algorithm Implementations', () => { signature = await ecdsa.sign({ algorithm : { name: 'ECDSA', hash: 'SHA-256' }, key : keyPair.privateKey, - data : dataU8A + data : data }); }); @@ -946,7 +946,7 @@ describe('Default Crypto Algorithm Implementations', () => { algorithm : { name: 'ECDSA', hash: 'SHA-256' }, key : keyPair.publicKey, signature : signature, - data : dataU8A + data : data }); expect(isValid).to.be.a('boolean'); @@ -994,7 +994,7 @@ describe('Default Crypto Algorithm Implementations', () => { algorithm : { name: 'ECDSA', hash: 'SHA-256' }, key : keyPair.privateKey, signature : signature, - data : dataU8A + data : data })).to.eventually.be.rejectedWith(InvalidAccessError, 'Requested operation is not valid'); }); @@ -1006,7 +1006,7 @@ describe('Default Crypto Algorithm Implementations', () => { algorithm : { name: 'ECDSA', hash: 'SHA-256' }, key : keyPair.publicKey, signature : signature, - data : dataU8A + data : data })).to.eventually.be.rejectedWith(InvalidAccessError, 'is not valid for the provided key'); }); @@ -1019,7 +1019,7 @@ describe('Default Crypto Algorithm Implementations', () => { algorithm : { name: 'ECDSA', hash: 'SHA-256' }, key : keyPair.publicKey, signature : signature, - data : dataU8A + data : data })).to.eventually.be.rejectedWith(TypeError, 'Out of range'); }); }); @@ -1166,7 +1166,7 @@ describe('Default Crypto Algorithm Implementations', () => { describe('sign()', () => { let keyPair: Web5Crypto.CryptoKeyPair; - let dataU8A = new Uint8Array([51, 52, 53]); + let data = new Uint8Array([51, 52, 53]); beforeEach(async () => { keyPair = await eddsa.generateKey({ @@ -1180,10 +1180,10 @@ describe('Default Crypto Algorithm Implementations', () => { const signature = await eddsa.sign({ algorithm : { name: 'EdDSA' }, key : keyPair.privateKey, - data : dataU8A + data : data }); - expect(signature).to.be.instanceOf(ArrayBuffer); + expect(signature).to.be.instanceOf(Uint8Array); expect(signature.byteLength).to.equal(64); }); @@ -1212,7 +1212,7 @@ describe('Default Crypto Algorithm Implementations', () => { await expect(eddsa.sign({ algorithm : { name: 'EdDSA' }, key : keyPair.publicKey, - data : dataU8A + data : data })).to.eventually.be.rejectedWith(InvalidAccessError, 'Requested operation is not valid'); }); @@ -1223,7 +1223,7 @@ describe('Default Crypto Algorithm Implementations', () => { await expect(eddsa.sign({ algorithm : { name: 'EdDSA' }, key : keyPair.privateKey, - data : dataU8A + data : data })).to.eventually.be.rejectedWith(InvalidAccessError, 'is not valid for the provided key'); }); @@ -1235,15 +1235,15 @@ describe('Default Crypto Algorithm Implementations', () => { await expect(eddsa.sign({ algorithm : { name: 'EdDSA' }, key : keyPair.privateKey, - data : dataU8A + data : data })).to.eventually.be.rejectedWith(TypeError, 'Out of range'); }); }); describe('verify()', () => { let keyPair: Web5Crypto.CryptoKeyPair; - let signature: ArrayBuffer; - let dataU8A = new Uint8Array([51, 52, 53]); + let signature: Uint8Array; + let data = new Uint8Array([51, 52, 53]); beforeEach(async () => { keyPair = await eddsa.generateKey({ @@ -1255,7 +1255,7 @@ describe('Default Crypto Algorithm Implementations', () => { signature = await eddsa.sign({ algorithm : { name: 'EdDSA' }, key : keyPair.privateKey, - data : dataU8A + data : data }); }); @@ -1264,7 +1264,7 @@ describe('Default Crypto Algorithm Implementations', () => { algorithm : { name: 'EdDSA' }, key : keyPair.publicKey, signature : signature, - data : dataU8A + data : data }); expect(isValid).to.be.a('boolean'); @@ -1301,7 +1301,7 @@ describe('Default Crypto Algorithm Implementations', () => { algorithm : { name: 'EdDSA' }, key : keyPair.privateKey, signature : signature, - data : dataU8A + data : data })).to.eventually.be.rejectedWith(InvalidAccessError, 'Requested operation is not valid'); }); @@ -1313,7 +1313,7 @@ describe('Default Crypto Algorithm Implementations', () => { algorithm : { name: 'EdDSA' }, key : keyPair.publicKey, signature : signature, - data : dataU8A + data : data })).to.eventually.be.rejectedWith(InvalidAccessError, 'is not valid for the provided key'); }); @@ -1326,7 +1326,7 @@ describe('Default Crypto Algorithm Implementations', () => { algorithm : { name: 'EdDSA' }, key : keyPair.publicKey, signature : signature, - data : dataU8A + data : data })).to.eventually.be.rejectedWith(TypeError, 'Out of range'); }); }); diff --git a/packages/crypto/tests/crypto-primitives.spec.ts b/packages/crypto/tests/crypto-primitives.spec.ts index cf5e52aa2..a5de3877e 100644 --- a/packages/crypto/tests/crypto-primitives.spec.ts +++ b/packages/crypto/tests/crypto-primitives.spec.ts @@ -1,11 +1,25 @@ -import type { BufferKeyPair } from '../src/types/index.js'; +import type { BytesKeyPair } from '../src/types/crypto-key.js'; -import { expect } from 'chai'; -import { Convert } from '@tbd54566975/common'; +import chai, { expect } from 'chai'; +import { Convert } from '@web5/common'; +import chaiAsPromised from 'chai-as-promised'; -import { aesCtrTestVectors, aesGcmTestVectors } from './fixtures/test-vectors/aes.js'; import { NotSupportedError } from '../src/algorithms-api/errors.js'; -import { AesCtr, AesGcm, ConcatKdf, Ed25519, Secp256k1, X25519, XChaCha20 } from '../src/crypto-primitives/index.js'; +import { ed25519TestVectors } from './fixtures/test-vectors/ed25519.js'; +import { secp256k1TestVectors } from './fixtures/test-vectors/secp256k1.js'; +import { aesCtrTestVectors, aesGcmTestVectors } from './fixtures/test-vectors/aes.js'; +import { + AesCtr, + AesGcm, + ConcatKdf, + Ed25519, + Secp256k1, + X25519, + XChaCha20, + XChaCha20Poly1305 +} from '../src/crypto-primitives/index.js'; + +chai.use(chaiAsPromised); // NOTE: @noble/secp256k1 requires globalThis.crypto polyfill for node.js <=18: https://github.com/paulmillr/noble-secp256k1/blob/main/README.md#usage // Remove when we move off of node.js v18 to v20, earliest possible time would be Oct 2023: https://github.com/nodejs/release#release-schedule @@ -22,41 +36,21 @@ describe('Cryptographic Primitive Implementations', () => { const plaintext = await AesCtr.decrypt({ counter : Convert.hex(vector.counter).toUint8Array(), data : Convert.hex(vector.ciphertext).toUint8Array(), - key : Convert.hex(vector.key).toArrayBuffer(), + key : Convert.hex(vector.key).toUint8Array(), length : vector.length }); - expect(Convert.arrayBuffer(plaintext).toHex()).to.deep.equal(vector.data); + expect(Convert.uint8Array(plaintext).toHex()).to.deep.equal(vector.data); }); } - it('accepts ciphertext input as ArrayBuffer, DataView, and TypedArray', async () => { - const dataU8A = new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8]); + it('accepts ciphertext input as Uint8Array', async () => { + const data = new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8]); const secretKey = await AesCtr.generateKey({ length: 256 }); - let ciphertext: ArrayBuffer; - - // ArrayBuffer - const dataArrayBuffer = dataU8A.buffer; - ciphertext = await AesCtr.decrypt({ counter: new ArrayBuffer(16), data: dataArrayBuffer, key: secretKey, length: 128 }); - expect(ciphertext).to.be.instanceOf(ArrayBuffer); - - // DataView - const dataView = new DataView(dataArrayBuffer); - ciphertext = await AesCtr.decrypt({ counter: new ArrayBuffer(16), data: dataView, key: secretKey, length: 128 }); - expect(ciphertext).to.be.instanceOf(ArrayBuffer); + let ciphertext: Uint8Array; // TypedArray - Uint8Array - ciphertext = await AesCtr.decrypt({ counter: new ArrayBuffer(16), data: dataU8A, key: secretKey, length: 128 }); - expect(ciphertext).to.be.instanceOf(ArrayBuffer); - - // TypedArray - Int32Array - const dataI32A = new Int32Array([10, 20, 30, 40]); - ciphertext = await AesCtr.decrypt({ counter: new ArrayBuffer(16), data: dataI32A, key: secretKey, length: 128 }); - expect(ciphertext).to.be.instanceOf(ArrayBuffer); - - // TypedArray - Uint32Array - const dataU32A = new Uint32Array([8, 7, 6, 5, 4, 3, 2, 1]); - ciphertext = await AesCtr.decrypt({ counter: new ArrayBuffer(16), data: dataU32A, key: secretKey, length: 128 }); - expect(ciphertext).to.be.instanceOf(ArrayBuffer); + ciphertext = await AesCtr.decrypt({ counter: new Uint8Array(16), data, key: secretKey, length: 128 }); + expect(ciphertext).to.be.instanceOf(Uint8Array); }); }); @@ -66,52 +60,32 @@ describe('Cryptographic Primitive Implementations', () => { const ciphertext = await AesCtr.encrypt({ counter : Convert.hex(vector.counter).toUint8Array(), data : Convert.hex(vector.data).toUint8Array(), - key : Convert.hex(vector.key).toArrayBuffer(), + key : Convert.hex(vector.key).toUint8Array(), length : vector.length }); - expect(Convert.arrayBuffer(ciphertext).toHex()).to.deep.equal(vector.ciphertext); + expect(Convert.uint8Array(ciphertext).toHex()).to.deep.equal(vector.ciphertext); }); } - it('accepts plaintext input as ArrayBuffer, DataView, and TypedArray', async () => { - const dataU8A = new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8]); + it('accepts plaintext input as Uint8Array', async () => { + const data = new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8]); const secretKey = await AesCtr.generateKey({ length: 256 }); - let ciphertext: ArrayBuffer; - - // ArrayBuffer - const dataArrayBuffer = dataU8A.buffer; - ciphertext = await AesCtr.encrypt({ counter: new ArrayBuffer(16), data: dataArrayBuffer, key: secretKey, length: 128 }); - expect(ciphertext).to.be.instanceOf(ArrayBuffer); + let ciphertext: Uint8Array; - // DataView - const dataView = new DataView(dataArrayBuffer); - ciphertext = await AesCtr.encrypt({ counter: new ArrayBuffer(16), data: dataView, key: secretKey, length: 128 }); - expect(ciphertext).to.be.instanceOf(ArrayBuffer); - - // TypedArray - Uint8Array - ciphertext = await AesCtr.encrypt({ counter: new ArrayBuffer(16), data: dataU8A, key: secretKey, length: 128 }); - expect(ciphertext).to.be.instanceOf(ArrayBuffer); - - // TypedArray - Int32Array - const dataI32A = new Int32Array([10, 20, 30, 40]); - ciphertext = await AesCtr.encrypt({ counter: new ArrayBuffer(16), data: dataI32A, key: secretKey, length: 128 }); - expect(ciphertext).to.be.instanceOf(ArrayBuffer); - - // TypedArray - Uint32Array - const dataU32A = new Uint32Array([8, 7, 6, 5, 4, 3, 2, 1]); - ciphertext = await AesCtr.encrypt({ counter: new ArrayBuffer(16), data: dataU32A, key: secretKey, length: 128 }); - expect(ciphertext).to.be.instanceOf(ArrayBuffer); + // Uint8Array + ciphertext = await AesCtr.encrypt({ counter: new Uint8Array(16), data, key: secretKey, length: 128 }); + expect(ciphertext).to.be.instanceOf(Uint8Array); }); }); describe('generateKey()', () => { - it('returns a secret key of type ArrayBuffer', async () => { + it('returns a secret key of type Uint8Array', async () => { const secretKey = await AesCtr.generateKey({ length: 256 }); - expect(secretKey).to.be.instanceOf(ArrayBuffer); + expect(secretKey).to.be.instanceOf(Uint8Array); }); it('returns a secret key of the specified length', async () => { - let secretKey: ArrayBuffer; + let secretKey: Uint8Array; // 128 bits secretKey= await AesCtr.generateKey({ length: 128 }); @@ -136,26 +110,21 @@ describe('Cryptographic Primitive Implementations', () => { additionalData : Convert.hex(vector.aad).toUint8Array(), iv : Convert.hex(vector.iv).toUint8Array(), data : Convert.hex(vector.ciphertext + vector.tag).toUint8Array(), - key : Convert.hex(vector.key).toArrayBuffer(), + key : Convert.hex(vector.key).toUint8Array(), tagLength : vector.tagLength }); - expect(Convert.arrayBuffer(plaintext).toHex()).to.deep.equal(vector.data); + expect(Convert.uint8Array(plaintext).toHex()).to.deep.equal(vector.data); }); } - it('accepts ciphertext input as ArrayBuffer and TypedArray', async () => { + it('accepts ciphertext input as Uint8Array', async () => { const secretKey = new Uint8Array([222, 78, 162, 222, 38, 146, 151, 191, 191, 75, 227, 71, 220, 221, 70, 49]); - let plaintext: ArrayBuffer; - - // ArrayBuffer - const ciphertextArrayBuffer = (new Uint8Array([242, 126, 129, 170, 99, 195, 21, 165, 205, 3, 226, 171, 203, 198, 42, 86, 101])).buffer; - plaintext = await AesGcm.decrypt({ data: ciphertextArrayBuffer, iv: new ArrayBuffer(12), key: secretKey, tagLength: 128 }); - expect(plaintext).to.be.instanceOf(ArrayBuffer); + let plaintext: Uint8Array; // TypedArray - Uint8Array - const ciphertextU8A = new Uint8Array([242, 126, 129, 170, 99, 195, 21, 165, 205, 3, 226, 171, 203, 198, 42, 86, 101]); - plaintext = await AesGcm.decrypt({ data: ciphertextU8A, iv: new ArrayBuffer(12), key: secretKey, tagLength: 128 }); - expect(plaintext).to.be.instanceOf(ArrayBuffer); + const ciphertext = new Uint8Array([242, 126, 129, 170, 99, 195, 21, 165, 205, 3, 226, 171, 203, 198, 42, 86, 101]); + plaintext = await AesGcm.decrypt({ data: ciphertext, iv: new Uint8Array(12), key: secretKey, tagLength: 128 }); + expect(plaintext).to.be.instanceOf(Uint8Array); }); }); @@ -166,52 +135,32 @@ describe('Cryptographic Primitive Implementations', () => { additionalData : Convert.hex(vector.aad).toUint8Array(), iv : Convert.hex(vector.iv).toUint8Array(), data : Convert.hex(vector.data).toUint8Array(), - key : Convert.hex(vector.key).toArrayBuffer(), + key : Convert.hex(vector.key).toUint8Array(), tagLength : vector.tagLength }); - expect(Convert.arrayBuffer(ciphertext).toHex()).to.deep.equal(vector.ciphertext + vector.tag); + expect(Convert.uint8Array(ciphertext).toHex()).to.deep.equal(vector.ciphertext + vector.tag); }); } - it('accepts plaintext input as ArrayBuffer, DataView, and TypedArray', async () => { - const dataU8A = new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8]); + it('accepts plaintext input as Uint8Array', async () => { + const data = new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8]); const secretKey = await AesGcm.generateKey({ length: 256 }); - let ciphertext: ArrayBuffer; - - // ArrayBuffer - const dataArrayBuffer = dataU8A.buffer; - ciphertext = await AesGcm.encrypt({ data: dataArrayBuffer, iv: new ArrayBuffer(12), key: secretKey, tagLength: 128 }); - expect(ciphertext).to.be.instanceOf(ArrayBuffer); - - // DataView - const dataView = new DataView(dataArrayBuffer); - ciphertext = await AesGcm.encrypt({ data: dataView, iv: new ArrayBuffer(12), key: secretKey, tagLength: 128 }); - expect(ciphertext).to.be.instanceOf(ArrayBuffer); + let ciphertext: Uint8Array; // TypedArray - Uint8Array - ciphertext = await AesGcm.encrypt({ data: dataU8A, iv: new ArrayBuffer(12), key: secretKey, tagLength: 128 }); - expect(ciphertext).to.be.instanceOf(ArrayBuffer); - - // TypedArray - Int32Array - const dataI32A = new Int32Array([10, 20, 30, 40]); - ciphertext = await AesGcm.encrypt({ data: dataI32A, iv: new ArrayBuffer(12), key: secretKey, tagLength: 128 }); - expect(ciphertext).to.be.instanceOf(ArrayBuffer); - - // TypedArray - Uint32Array - const dataU32A = new Uint32Array([8, 7, 6, 5, 4, 3, 2, 1]); - ciphertext = await AesGcm.encrypt({ data: dataU32A, iv: new ArrayBuffer(12), key: secretKey, tagLength: 128 }); - expect(ciphertext).to.be.instanceOf(ArrayBuffer); + ciphertext = await AesGcm.encrypt({ data, iv: new Uint8Array(12), key: secretKey, tagLength: 128 }); + expect(ciphertext).to.be.instanceOf(Uint8Array); }); }); describe('generateKey()', () => { - it('returns a secret key of type ArrayBuffer', async () => { + it('returns a secret key of type Uint8Array', async () => { const secretKey = await AesGcm.generateKey({ length: 256 }); - expect(secretKey).to.be.instanceOf(ArrayBuffer); + expect(secretKey).to.be.instanceOf(Uint8Array); }); it('returns a secret key of the specified length', async () => { - let secretKey: ArrayBuffer; + let secretKey: Uint8Array; // 128 bits secretKey= await AesGcm.generateKey({ length: 128 }); @@ -247,29 +196,18 @@ describe('Cryptographic Primitive Implementations', () => { const derivedKeyingMaterial = await ConcatKdf.deriveKey(input); - const expectedResult = Convert.base64Url(output).toArrayBuffer(); + const expectedResult = Convert.base64Url(output).toUint8Array(); expect(derivedKeyingMaterial).to.deep.equal(expectedResult); expect(derivedKeyingMaterial.byteLength).to.equal(16); }); - it('accepts other info as ArrayBuffer, String, and TypedArray', async () => { + it('accepts other info as String and TypedArray', async () => { const inputBase = { sharedSecret : new Uint8Array([1, 2, 3]), keyDataLen : 256, otherInfo : {} }; - // ArrayBuffer input. - const inputArrayBuffer = { ...inputBase, otherInfo: { - algorithmId : 'A128GCM', - partyUInfo : Convert.string('Alice').toArrayBuffer(), - partyVInfo : Convert.string('Bob').toArrayBuffer(), - suppPubInfo : 128 - }}; - let derivedKeyingMaterial = await ConcatKdf.deriveKey(inputArrayBuffer); - expect(derivedKeyingMaterial).to.be.an('ArrayBuffer'); - expect(derivedKeyingMaterial.byteLength).to.equal(32); - // String input. const inputString = { ...inputBase, otherInfo: { algorithmId : 'A128GCM', @@ -277,8 +215,8 @@ describe('Cryptographic Primitive Implementations', () => { partyVInfo : 'Bob', suppPubInfo : 128 }}; - derivedKeyingMaterial = await ConcatKdf.deriveKey(inputString); - expect(derivedKeyingMaterial).to.be.an('ArrayBuffer'); + let derivedKeyingMaterial = await ConcatKdf.deriveKey(inputString); + expect(derivedKeyingMaterial).to.be.an('Uint8Array'); expect(derivedKeyingMaterial.byteLength).to.equal(32); // TypedArray input. @@ -289,7 +227,7 @@ describe('Cryptographic Primitive Implementations', () => { suppPubInfo : 128 }}; derivedKeyingMaterial = await ConcatKdf.deriveKey(inputTypedArray); - expect(derivedKeyingMaterial).to.be.an('ArrayBuffer'); + expect(derivedKeyingMaterial).to.be.an('Uint8Array'); expect(derivedKeyingMaterial.byteLength).to.equal(32); }); @@ -317,7 +255,7 @@ describe('Cryptographic Primitive Implementations', () => { }); }); - describe('#computeOtherInfo()', () => { + describe('computeOtherInfo()', () => { it('returns concatenated and formatted Uint8Array', () => { const input = { algorithmId : 'A128GCM', @@ -355,13 +293,80 @@ describe('Cryptographic Primitive Implementations', () => { }); describe('Ed25519', () => { + describe('convertPrivateKeyToX25519()', () => { + let validEd25519PrivateKey: Uint8Array; + + // This is a setup step. Before each test, generate a new Ed25519 key pair + // and use the private key in tests. This ensures that we work with fresh keys for every test. + beforeEach(async () => { + const keyPair = await Ed25519.generateKeyPair(); + validEd25519PrivateKey = keyPair.publicKey; + }); + + it('converts a valid Ed25519 private key to X25519 format', async () => { + const x25519PrivateKey = await Ed25519.convertPrivateKeyToX25519({ privateKey: validEd25519PrivateKey }); + + expect(x25519PrivateKey).to.be.instanceOf(Uint8Array); + expect(x25519PrivateKey.length).to.equal(32); + }); + + it('accepts any Uint8Array value as a private key', async () => { + /** For Ed25519 the private key is a random string of bytes that is + * hashed, which means many possible values can serve as a valid + * private key. */ + const key0Bytes = new Uint8Array(0); + const key33Bytes = Convert.hex('02fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f').toUint8Array(); + + let x25519PrivateKey = await Ed25519.convertPrivateKeyToX25519({ privateKey: key0Bytes }); + expect(x25519PrivateKey.length).to.equal(32); + x25519PrivateKey = await Ed25519.convertPrivateKeyToX25519({ privateKey: key33Bytes }); + expect(x25519PrivateKey.length).to.equal(32); + }); + }); + + describe('convertPublicKeyToX25519()', () => { + let validEd25519PublicKey: Uint8Array; + + // This is a setup step. Before each test, generate a new Ed25519 key pair + // and use the public key in tests. This ensures that we work with fresh keys for every test. + beforeEach(async () => { + const keyPair = await Ed25519.generateKeyPair(); + validEd25519PublicKey = keyPair.publicKey; + }); + + it('converts a valid Ed25519 public key to X25519 format', async () => { + const x25519PublicKey = await Ed25519.convertPublicKeyToX25519({ publicKey: validEd25519PublicKey }); + + expect(x25519PublicKey).to.be.instanceOf(Uint8Array); + expect(x25519PublicKey.length).to.equal(32); + }); + + it('throws an error when provided an invalid Ed25519 public key', async () => { + const invalidEd25519PublicKey = Convert.hex('02fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f').toUint8Array(); + + await expect( + Ed25519.convertPublicKeyToX25519({ publicKey: invalidEd25519PublicKey }) + ).to.eventually.be.rejectedWith(Error, 'Invalid public key'); + }); + + it('throws an error when provided an Ed25519 private key', async () => { + for (const vector of ed25519TestVectors) { + const validEd25519PrivateKey = Convert.hex(vector.privateKey.encoded).toUint8Array(); + + await expect( + Ed25519.convertPublicKeyToX25519({ publicKey: validEd25519PrivateKey }) + ).to.eventually.be.rejectedWith(Error, 'Invalid public key'); + } + }); + }); + describe('generateKeyPair()', () => { - it('returns a pair of keys of type ArrayBuffer', async () => { + it('returns a pair of keys of type Uint8Array', async () => { const keyPair = await Ed25519.generateKeyPair(); expect(keyPair).to.have.property('privateKey'); expect(keyPair).to.have.property('publicKey'); - expect(keyPair.privateKey).to.be.instanceOf(ArrayBuffer); - expect(keyPair.publicKey).to.be.instanceOf(ArrayBuffer); + expect(keyPair.privateKey).to.be.instanceOf(Uint8Array); + expect(keyPair.publicKey).to.be.instanceOf(Uint8Array); }); it('returns a 32-byte private key', async () => { @@ -376,7 +381,7 @@ describe('Cryptographic Primitive Implementations', () => { }); describe('getPublicKey()', () => { - let keyPair: BufferKeyPair; + let keyPair: BytesKeyPair; before(async () => { keyPair = await Ed25519.generateKeyPair(); @@ -384,169 +389,213 @@ describe('Cryptographic Primitive Implementations', () => { it('returns a 32-byte compressed public key', async () => { const publicKey = await Ed25519.getPublicKey({ privateKey: keyPair.privateKey }); + expect(publicKey).to.be.instanceOf(Uint8Array); expect(publicKey.byteLength).to.equal(32); }); }); describe('sign()', () => { - let keyPair: BufferKeyPair; + let keyPair: BytesKeyPair; before(async () => { keyPair = await Ed25519.generateKeyPair(); }); - it('returns a 64-byte signature of type ArrayBuffer', async () => { - const dataU8A = new Uint8Array([51, 52, 53]); - const signature = await Ed25519.sign({ key: keyPair.privateKey, data: dataU8A }); - expect(signature).to.be.instanceOf(ArrayBuffer); + it('returns a 64-byte signature of type Uint8Array', async () => { + const data = new Uint8Array([51, 52, 53]); + const signature = await Ed25519.sign({ key: keyPair.privateKey, data }); + expect(signature).to.be.instanceOf(Uint8Array); expect(signature.byteLength).to.equal(64); }); - it('accepts input data as ArrayBuffer, DataView, and TypedArray', async () => { - const dataU8A = new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8]); + it('accepts input data as Uint8Array', async () => { + const data = new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8]); const key = keyPair.privateKey; - let signature: ArrayBuffer; - - // ArrayBuffer - const dataArrayBuffer = dataU8A.buffer; - signature = await Ed25519.sign({ key, data: dataArrayBuffer }); - expect(signature).to.be.instanceOf(ArrayBuffer); - - // DataView - const dataView = new DataView(dataArrayBuffer); - signature = await Ed25519.sign({ key, data: dataView }); - expect(signature).to.be.instanceOf(ArrayBuffer); + let signature: Uint8Array; // TypedArray - Uint8Array - signature = await Ed25519.sign({ key, data: dataU8A }); - expect(signature).to.be.instanceOf(ArrayBuffer); + signature = await Ed25519.sign({ key, data: data }); + expect(signature).to.be.instanceOf(Uint8Array); + }); + }); + + describe('validatePublicKey()', () => { + it('returns true for valid public keys', async () => { + for (const vector of ed25519TestVectors) { + const key = Convert.hex(vector.publicKey.encoded).toUint8Array(); + const isValid = await Ed25519.validatePublicKey({ key }); + expect(isValid).to.be.true; + } + }); - // TypedArray - Int32Array - const dataI32A = new Int32Array([10, 20, 30, 40]); - signature = await Ed25519.sign({ key, data: dataI32A }); - expect(signature).to.be.instanceOf(ArrayBuffer); + it('returns false for invalid public keys', async () => { + const key = Convert.hex('02fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f').toUint8Array(); + const isValid = await Ed25519.validatePublicKey({ key }); + expect(isValid).to.be.false; + }); - // TypedArray - Uint32Array - const dataU32A = new Uint32Array([8, 7, 6, 5, 4, 3, 2, 1]); - signature = await Ed25519.sign({ key, data: dataU32A }); - expect(signature).to.be.instanceOf(ArrayBuffer); + it('returns false if a private key is given', async () => { + for (const vector of ed25519TestVectors) { + const key = Convert.hex(vector.privateKey.encoded).toUint8Array(); + const isValid = await Ed25519.validatePublicKey({ key }); + expect(isValid).to.be.false; + } }); }); describe('verify()', () => { - let keyPair: BufferKeyPair; + let keyPair: BytesKeyPair; before(async () => { keyPair = await Ed25519.generateKeyPair(); }); it('returns a boolean result', async () => { - const dataU8A = new Uint8Array([51, 52, 53]); - const signature = await Ed25519.sign({ key: keyPair.privateKey, data: dataU8A }); + const data = new Uint8Array([51, 52, 53]); + const signature = await Ed25519.sign({ key: keyPair.privateKey, data }); - const isValid = await Ed25519.verify({ key: keyPair.publicKey, signature, data: dataU8A }); + const isValid = await Ed25519.verify({ key: keyPair.publicKey, signature, data }); expect(isValid).to.exist; expect(isValid).to.be.true; }); - it('accepts input data as ArrayBuffer, DataView, and TypedArray', async () => { - const dataU8A = new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8]); + it('accepts input data as Uint8Array', async () => { + const data = new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8]); let isValid: boolean; - let signature: ArrayBuffer; - - // ArrayBuffer - const dataArrayBuffer = dataU8A.buffer; - signature = await Ed25519.sign({ key: keyPair.privateKey, data: dataArrayBuffer }); - isValid = await Ed25519.verify({ key: keyPair.publicKey, signature, data: dataArrayBuffer }); - expect(isValid).to.be.true; - - // DataView - const dataView = new DataView(dataArrayBuffer); - signature = await Ed25519.sign({ key: keyPair.privateKey, data: dataView }); - isValid = await Ed25519.verify({ key: keyPair.publicKey, signature, data: dataView }); - expect(isValid).to.be.true; + let signature: Uint8Array; // TypedArray - Uint8Array - signature = await Ed25519.sign({ key: keyPair.privateKey, data: dataU8A }); - isValid = await Ed25519.verify({ key: keyPair.publicKey, signature, data: dataU8A }); - expect(isValid).to.be.true; - - // TypedArray - Int32Array - const dataI32A = new Int32Array([10, 20, 30, 40]); - signature = await Ed25519.sign({ key: keyPair.privateKey, data: dataI32A }); - isValid = await Ed25519.verify({ key: keyPair.publicKey, signature, data: dataI32A }); - expect(isValid).to.be.true; - - // TypedArray - Uint32Array - const dataU32A = new Uint32Array([8, 7, 6, 5, 4, 3, 2, 1]); - signature = await Ed25519.sign({ key: keyPair.privateKey, data: dataU32A }); - isValid = await Ed25519.verify({ key: keyPair.publicKey, signature, data: dataU32A }); + signature = await Ed25519.sign({ key: keyPair.privateKey, data }); + isValid = await Ed25519.verify({ key: keyPair.publicKey, signature, data }); expect(isValid).to.be.true; }); it('returns false if the signed data was mutated', async () => { - const dataU8A = new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8]); + const data = new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8]); let isValid: boolean; // Generate signature using the private key. - const signature = await Ed25519.sign({ key: keyPair.privateKey, data: dataU8A }); + const signature = await Ed25519.sign({ key: keyPair.privateKey, data }); // Verification should return true with the data used to generate the signature. - isValid = await Ed25519.verify({ key: keyPair.publicKey, signature, data: dataU8A }); + isValid = await Ed25519.verify({ key: keyPair.publicKey, signature, data }); expect(isValid).to.be.true; // Make a copy and flip the least significant bit (the rightmost bit) in the first byte of the array. - const mutatedDataU8A = new Uint8Array(dataU8A); - mutatedDataU8A[0] ^= 1 << 0; + const mutatedData = new Uint8Array(data); + mutatedData[0] ^= 1 << 0; // Verification should return false if the given data does not match the data used to generate the signature. - isValid = await Ed25519.verify({ key: keyPair.publicKey, signature, data: mutatedDataU8A }); + isValid = await Ed25519.verify({ key: keyPair.publicKey, signature, data: mutatedData }); expect(isValid).to.be.false; }); it('returns false if the signature was mutated', async () => { - const dataU8A = new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8]); + const data = new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8]); let isValid: boolean; // Generate a new key pair. keyPair = await Ed25519.generateKeyPair(); // Generate signature using the private key. - const signature = await Ed25519.sign({ key: keyPair.privateKey, data: dataU8A }); + const signature = await Ed25519.sign({ key: keyPair.privateKey, data }); // Make a copy and flip the least significant bit (the rightmost bit) in the first byte of the array. const mutatedSignature = new Uint8Array(signature); mutatedSignature[0] ^= 1 << 0; // Verification should return false if the signature was modified. - isValid = await Ed25519.verify({ key: keyPair.publicKey, signature: signature, data: mutatedSignature.buffer }); + isValid = await Ed25519.verify({ key: keyPair.publicKey, signature: signature, data: mutatedSignature }); expect(isValid).to.be.false; }); it('returns false with a signature generated using a different private key', async () => { - const dataU8A = new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8]); + const data = new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8]); const keyPairA = await Ed25519.generateKeyPair(); const keyPairB = await Ed25519.generateKeyPair(); let isValid: boolean; // Generate a signature using the private key from key pair B. - const signatureB = await Ed25519.sign({ key: keyPairB.privateKey, data: dataU8A }); + const signatureB = await Ed25519.sign({ key: keyPairB.privateKey, data }); // Verification should return false with the public key from key pair A. - isValid = await Ed25519.verify({ key: keyPairA.publicKey, signature: signatureB, data: dataU8A.buffer }); + isValid = await Ed25519.verify({ key: keyPairA.publicKey, signature: signatureB, data }); expect(isValid).to.be.false; }); }); }); describe('Secp256k1', () => { + describe('convertPublicKey method', () => { + it('converts an uncompressed public key to a compressed format', async () => { + // Generate the uncompressed public key. + const keyPair = await Secp256k1.generateKeyPair({ compressedPublicKey: false }); + const uncompressedPublicKey = keyPair.publicKey; + + // Attempt to convert to compressed format. + const compressedKey = await Secp256k1.convertPublicKey({ + publicKey : uncompressedPublicKey, + compressedPublicKey : true + }); + + // Confirm the length of the resulting public key is 33 bytes + expect(compressedKey.byteLength).to.equal(33); + }); + + it('converts a compressed public key to an uncompressed format', async () => { + // Generate the uncompressed public key. + const keyPair = await Secp256k1.generateKeyPair({ compressedPublicKey: true }); + const compressedPublicKey = keyPair.publicKey; + + const uncompressedKey = await Secp256k1.convertPublicKey({ + publicKey : compressedPublicKey, + compressedPublicKey : false + }); + + // Confirm the length of the resulting public key is 65 bytes + expect(uncompressedKey.byteLength).to.equal(65); + }); + + it('throws an error for an invalid compressed public key', async () => { + // Invalid compressed public key. + const invalidPublicKey = Convert.hex('fef0b998921eafb58f49efdeb0adc47123aa28a4042924236f08274d50c72fe7b0').toUint8Array(); + + try { + await Secp256k1.convertPublicKey({ + publicKey : invalidPublicKey, + compressedPublicKey : false + }); + expect.fail('Expected method to throw an error.'); + } catch (error) { + expect(error).to.be.instanceOf(Error); + expect((error as Error).message).to.include('Point of length 33 was invalid'); + } + }); + + it('throws an error for an invalid uncompressed public key', async () => { + // Here, generating a random byte array of size 65. It's unlikely to be a valid public key. + const invalidPublicKey = Convert.hex('dfebc16793a5737ac51f606a43524df8373c063e41d5a99b2f1530afd987284bd1c7cde1658a9a756e71f44a97b4783ea9dee5ccb7f1447eb4836d8de9bd4f81fd').toUint8Array(); + + try { + await Secp256k1.convertPublicKey({ + publicKey : invalidPublicKey, + compressedPublicKey : true + }); + expect.fail('Expected method to throw an error.'); + } catch (error) { + expect(error).to.be.instanceOf(Error); + expect((error as Error).message).to.include('Point of length 65 was invalid'); + } + }); + }); + describe('generateKeyPair()', () => { - it('returns a pair of keys of type ArrayBuffer', async () => { + it('returns a pair of keys of type Uint8Array', async () => { const keyPair = await Secp256k1.generateKeyPair(); expect(keyPair).to.have.property('privateKey'); expect(keyPair).to.have.property('publicKey'); - expect(keyPair.privateKey).to.be.instanceOf(ArrayBuffer); - expect(keyPair.publicKey).to.be.instanceOf(ArrayBuffer); + expect(keyPair.privateKey).to.be.instanceOf(Uint8Array); + expect(keyPair.publicKey).to.be.instanceOf(Uint8Array); }); it('returns a 32-byte private key', async () => { @@ -565,8 +614,54 @@ describe('Cryptographic Primitive Implementations', () => { }); }); + describe('getCurvePoints()', () => { + it('returns public key x and y coordinates given a public key', async () => { + for (const vector of secp256k1TestVectors) { + const key = Convert.hex(vector.publicKey.encoded).toUint8Array(); + const points = await Secp256k1.getCurvePoints({ key }); + expect(points.x).to.deep.equal(Convert.hex(vector.publicKey.x).toUint8Array()); + expect(points.y).to.deep.equal(Convert.hex(vector.publicKey.y).toUint8Array()); + } + }); + + it('returns public key x and y coordinates given a private key', async () => { + for (const vector of secp256k1TestVectors) { + const key = Convert.hex(vector.privateKey.encoded).toUint8Array(); + const points = await Secp256k1.getCurvePoints({ key }); + expect(points.x).to.deep.equal(Convert.hex(vector.publicKey.x).toUint8Array()); + expect(points.y).to.deep.equal(Convert.hex(vector.publicKey.y).toUint8Array()); + } + }); + + it('handles keys that require padded x-coordinate when converting from BigInt to bytes', async () => { + const key = Convert.hex('0206a1f9628c5bcd31f3bbc2f160ec98f99960147e04ea192f56c53a0086c5432d').toUint8Array(); + const points = await Secp256k1.getCurvePoints({ key }); + + const expectedX = Convert.hex('06a1f9628c5bcd31f3bbc2f160ec98f99960147e04ea192f56c53a0086c5432d').toUint8Array(); + const expectedY = Convert.hex('bf2efab7943be51219a283c0979ccba0fbe03f571e75b0eb338cc2ec01e70552').toUint8Array(); + expect(points.x).to.deep.equal(expectedX); + expect(points.y).to.deep.equal(expectedY); + }); + + it('handles keys that require padded y-coordinate when converting from BigInt to bytes', async () => { + const key = Convert.hex('032ff752fb8fc6af85c8682b0ca9d48901b2b9ac130f558bd1a9092240d42c4682').toUint8Array(); + const points = await Secp256k1.getCurvePoints({ key }); + + const expectedX = Convert.hex('2ff752fb8fc6af85c8682b0ca9d48901b2b9ac130f558bd1a9092240d42c4682').toUint8Array(); + const expectedY = Convert.hex('048c39d9ebdc1fd98bda38b7f00b93de1d2af5bb3ba8cb532ad47c1f36e19501').toUint8Array(); + expect(points.x).to.deep.equal(expectedX); + expect(points.y).to.deep.equal(expectedY); + }); + + it('throws error with invalid input key length', async () => { + await expect( + Secp256k1.getCurvePoints({ key: new Uint8Array(16) }) + ).to.eventually.be.rejectedWith(Error, 'Point of length 16 was invalid. Expected 33 compressed bytes or 65 uncompressed bytes'); + }); + }); + describe('getPublicKey()', () => { - let keyPair: BufferKeyPair; + let keyPair: BytesKeyPair; before(async () => { keyPair = await Secp256k1.generateKeyPair(); @@ -574,18 +669,20 @@ describe('Cryptographic Primitive Implementations', () => { it('returns a 33-byte compressed public key, by default', async () => { const publicKey = await Secp256k1.getPublicKey({ privateKey: keyPair.privateKey }); + expect(publicKey).to.be.instanceOf(Uint8Array); expect(publicKey.byteLength).to.equal(33); }); it('returns a 65-byte uncompressed public key, if specified', async () => { const publicKey = await Secp256k1.getPublicKey({ privateKey: keyPair.privateKey, compressedPublicKey: false }); + expect(publicKey).to.be.instanceOf(Uint8Array); expect(publicKey.byteLength).to.equal(65); }); }); describe('sharedSecret()', () => { - let otherPartyKeyPair: BufferKeyPair; - let ownKeyPair: BufferKeyPair; + let otherPartyKeyPair: BytesKeyPair; + let ownKeyPair: BytesKeyPair; beforeEach(async () => { otherPartyKeyPair = await Secp256k1.generateKeyPair(); @@ -597,7 +694,7 @@ describe('Cryptographic Primitive Implementations', () => { privateKey : ownKeyPair.privateKey, publicKey : otherPartyKeyPair.publicKey }); - expect(sharedSecret).to.be.instanceOf(ArrayBuffer); + expect(sharedSecret).to.be.instanceOf(Uint8Array); expect(sharedSecret.byteLength).to.equal(32); }); @@ -617,155 +714,159 @@ describe('Cryptographic Primitive Implementations', () => { }); describe('sign()', () => { - let keyPair: BufferKeyPair; + let keyPair: BytesKeyPair; before(async () => { keyPair = await Secp256k1.generateKeyPair(); }); - it('returns a 64-byte signature of type ArrayBuffer', async () => { + it('returns a 64-byte signature of type Uint8Array', async () => { const hash = 'SHA-256'; - const dataU8A = new Uint8Array([51, 52, 53]); - const signature = await Secp256k1.sign({ hash, key: keyPair.privateKey, data: dataU8A }); - expect(signature).to.be.instanceOf(ArrayBuffer); + const data = new Uint8Array([51, 52, 53]); + const signature = await Secp256k1.sign({ hash, key: keyPair.privateKey, data }); + expect(signature).to.be.instanceOf(Uint8Array); expect(signature.byteLength).to.equal(64); }); - it('accepts input data as ArrayBuffer, DataView, and TypedArray', async () => { - const dataU8A = new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8]); + it('accepts input data as Uint8Array', async () => { + const data = new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8]); const hash = 'SHA-256'; const key = keyPair.privateKey; - let signature: ArrayBuffer; + let signature: Uint8Array; - // ArrayBuffer - const dataArrayBuffer = dataU8A.buffer; - signature = await Secp256k1.sign({ hash, key, data: dataArrayBuffer }); - expect(signature).to.be.instanceOf(ArrayBuffer); + // TypedArray - Uint8Array + signature = await Secp256k1.sign({ hash, key, data }); + expect(signature).to.be.instanceOf(Uint8Array); + }); + }); - // DataView - const dataView = new DataView(dataArrayBuffer); - signature = await Secp256k1.sign({ hash, key, data: dataView }); - expect(signature).to.be.instanceOf(ArrayBuffer); + describe('validatePrivateKey()', () => { + it('returns true for valid private keys', async () => { + for (const vector of secp256k1TestVectors) { + const key = Convert.hex(vector.privateKey.encoded).toUint8Array(); + const isValid = await Secp256k1.validatePrivateKey({ key }); + expect(isValid).to.be.true; + } + }); - // TypedArray - Uint8Array - signature = await Secp256k1.sign({ hash, key, data: dataU8A }); - expect(signature).to.be.instanceOf(ArrayBuffer); + it('returns false for invalid private keys', async () => { + const key = Convert.hex('02fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f').toUint8Array(); + const isValid = await Secp256k1.validatePrivateKey({ key }); + expect(isValid).to.be.false; + }); + + it('returns false if a public key is given', async () => { + for (const vector of secp256k1TestVectors) { + const key = Convert.hex(vector.publicKey.encoded).toUint8Array(); + const isValid = await Secp256k1.validatePrivateKey({ key }); + expect(isValid).to.be.false; + } + }); + }); - // TypedArray - Int32Array - const dataI32A = new Int32Array([10, 20, 30, 40]); - signature = await Secp256k1.sign({ hash, key, data: dataI32A }); - expect(signature).to.be.instanceOf(ArrayBuffer); + describe('validatePublicKey()', () => { + it('returns true for valid public keys', async () => { + for (const vector of secp256k1TestVectors) { + const key = Convert.hex(vector.publicKey.encoded).toUint8Array(); + const isValid = await Secp256k1.validatePublicKey({ key }); + expect(isValid).to.be.true; + } + }); - // TypedArray - Uint32Array - const dataU32A = new Uint32Array([8, 7, 6, 5, 4, 3, 2, 1]); - signature = await Secp256k1.sign({ hash, key, data: dataU32A }); - expect(signature).to.be.instanceOf(ArrayBuffer); + it('returns false for invalid public keys', async () => { + const key = Convert.hex('02fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f').toUint8Array(); + const isValid = await Secp256k1.validatePublicKey({ key }); + expect(isValid).to.be.false; + }); + + it('returns false if a private key is given', async () => { + for (const vector of secp256k1TestVectors) { + const key = Convert.hex(vector.privateKey.encoded).toUint8Array(); + const isValid = await Secp256k1.validatePublicKey({ key }); + expect(isValid).to.be.false; + } }); }); describe('verify()', () => { - let keyPair: BufferKeyPair; + let keyPair: BytesKeyPair; before(async () => { keyPair = await Secp256k1.generateKeyPair(); }); it('returns a boolean result', async () => { - const dataU8A = new Uint8Array([51, 52, 53]); - const signature = await Secp256k1.sign({ hash: 'SHA-256', key: keyPair.privateKey, data: dataU8A }); + const data = new Uint8Array([51, 52, 53]); + const signature = await Secp256k1.sign({ hash: 'SHA-256', key: keyPair.privateKey, data }); - const isValid = await Secp256k1.verify({ hash: 'SHA-256', key: keyPair.publicKey, signature, data: dataU8A }); + const isValid = await Secp256k1.verify({ hash: 'SHA-256', key: keyPair.publicKey, signature, data }); expect(isValid).to.exist; expect(isValid).to.be.true; }); - it('accepts input data as ArrayBuffer, DataView, and TypedArray', async () => { - const dataU8A = new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8]); + it('accepts input data as Uint8Array', async () => { + const data = new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8]); const hash = 'SHA-256'; let isValid: boolean; - let signature: ArrayBuffer; - - // ArrayBuffer - const dataArrayBuffer = dataU8A.buffer; - signature = await Secp256k1.sign({ hash, key: keyPair.privateKey, data: dataArrayBuffer }); - isValid = await Secp256k1.verify({ hash, key: keyPair.publicKey, signature, data: dataArrayBuffer }); - expect(isValid).to.be.true; - - // DataView - const dataView = new DataView(dataArrayBuffer); - signature = await Secp256k1.sign({ hash, key: keyPair.privateKey, data: dataView }); - isValid = await Secp256k1.verify({ hash, key: keyPair.publicKey, signature, data: dataView }); - expect(isValid).to.be.true; + let signature: Uint8Array; // TypedArray - Uint8Array - signature = await Secp256k1.sign({ hash, key: keyPair.privateKey, data: dataU8A }); - isValid = await Secp256k1.verify({ hash, key: keyPair.publicKey, signature, data: dataU8A }); - expect(isValid).to.be.true; - - // TypedArray - Int32Array - const dataI32A = new Int32Array([10, 20, 30, 40]); - signature = await Secp256k1.sign({ hash, key: keyPair.privateKey, data: dataI32A }); - isValid = await Secp256k1.verify({ hash, key: keyPair.publicKey, signature, data: dataI32A }); - expect(isValid).to.be.true; - - // TypedArray - Uint32Array - const dataU32A = new Uint32Array([8, 7, 6, 5, 4, 3, 2, 1]); - signature = await Secp256k1.sign({ hash, key: keyPair.privateKey, data: dataU32A }); - isValid = await Secp256k1.verify({ hash, key: keyPair.publicKey, signature, data: dataU32A }); + signature = await Secp256k1.sign({ hash, key: keyPair.privateKey, data }); + isValid = await Secp256k1.verify({ hash, key: keyPair.publicKey, signature, data }); expect(isValid).to.be.true; }); it('accepts both compressed and uncompressed public keys', async () => { - let signature: ArrayBuffer; + let signature: Uint8Array; let isValid: boolean; const hash = 'SHA-256'; - const dataU8A = new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8]); + const data = new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8]); // Generate signature using the private key. - signature = await Secp256k1.sign({ hash, key: keyPair.privateKey, data: dataU8A }); + signature = await Secp256k1.sign({ hash, key: keyPair.privateKey, data }); // Attempt to verify the signature using a compressed public key. const compressedPublicKey = await Secp256k1.getPublicKey({ privateKey: keyPair.privateKey, compressedPublicKey: true }); - isValid = await Secp256k1.verify({ hash, key: compressedPublicKey, signature, data: dataU8A }); + isValid = await Secp256k1.verify({ hash, key: compressedPublicKey, signature, data }); expect(isValid).to.be.true; // Attempt to verify the signature using an uncompressed public key. const uncompressedPublicKey = await Secp256k1.getPublicKey({ privateKey: keyPair.privateKey, compressedPublicKey: false }); - isValid = await Secp256k1.verify({ hash, key: uncompressedPublicKey, signature, data: dataU8A }); + isValid = await Secp256k1.verify({ hash, key: uncompressedPublicKey, signature, data }); expect(isValid).to.be.true; }); it('returns false if the signed data was mutated', async () => { const hash = 'SHA-256'; - const dataU8A = new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8]); + const data = new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8]); let isValid: boolean; // Generate signature using the private key. - const signature = await Secp256k1.sign({ hash, key: keyPair.privateKey, data: dataU8A }); + const signature = await Secp256k1.sign({ hash, key: keyPair.privateKey, data }); // Verification should return true with the data used to generate the signature. - isValid = await Secp256k1.verify({ hash, key: keyPair.publicKey, signature, data: dataU8A }); + isValid = await Secp256k1.verify({ hash, key: keyPair.publicKey, signature, data }); expect(isValid).to.be.true; // Make a copy and flip the least significant bit (the rightmost bit) in the first byte of the array. - const mutatedDataU8A = new Uint8Array(dataU8A); - mutatedDataU8A[0] ^= 1 << 0; + const mutatedData = new Uint8Array(data); + mutatedData[0] ^= 1 << 0; // Verification should return false if the given data does not match the data used to generate the signature. - isValid = await Secp256k1.verify({ hash, key: keyPair.publicKey, signature, data: mutatedDataU8A }); + isValid = await Secp256k1.verify({ hash, key: keyPair.publicKey, signature, data: mutatedData }); expect(isValid).to.be.false; }); it('returns false if the signature was mutated', async () => { const hash = 'SHA-256'; - const dataU8A = new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8]); + const data = new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8]); let isValid: boolean; // Generate signature using the private key. - const signature = await Secp256k1.sign({ hash, key: keyPair.privateKey, data: dataU8A }); + const signature = await Secp256k1.sign({ hash, key: keyPair.privateKey, data }); // Verification should return true with the data used to generate the signature. - isValid = await Secp256k1.verify({ hash, key: keyPair.publicKey, signature, data: dataU8A }); + isValid = await Secp256k1.verify({ hash, key: keyPair.publicKey, signature, data }); expect(isValid).to.be.true; // Make a copy and flip the least significant bit (the rightmost bit) in the first byte of the array. @@ -773,22 +874,22 @@ describe('Cryptographic Primitive Implementations', () => { mutatedSignature[0] ^= 1 << 0; // Verification should return false if the signature was modified. - isValid = await Secp256k1.verify({ hash, key: keyPair.publicKey, signature: signature, data: mutatedSignature.buffer }); + isValid = await Secp256k1.verify({ hash, key: keyPair.publicKey, signature: signature, data: mutatedSignature }); expect(isValid).to.be.false; }); it('returns false with a signature generated using a different private key', async () => { const hash = 'SHA-256'; - const dataU8A = new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8]); + const data = new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8]); const keyPairA = await Secp256k1.generateKeyPair(); const keyPairB = await Secp256k1.generateKeyPair(); let isValid: boolean; // Generate a signature using the private key from key pair B. - const signatureB = await Secp256k1.sign({ hash, key: keyPairB.privateKey, data: dataU8A }); + const signatureB = await Secp256k1.sign({ hash, key: keyPairB.privateKey, data }); // Verification should return false with the public key from key pair A. - isValid = await Secp256k1.verify({ hash, key: keyPairA.publicKey, signature: signatureB, data: dataU8A.buffer }); + isValid = await Secp256k1.verify({ hash, key: keyPairA.publicKey, signature: signatureB, data }); expect(isValid).to.be.false; }); }); @@ -796,12 +897,12 @@ describe('Cryptographic Primitive Implementations', () => { describe('X25519', () => { describe('generateKeyPair()', () => { - it('returns a pair of keys of type ArrayBuffer', async () => { + it('returns a pair of keys of type Uint8Array', async () => { const keyPair = await X25519.generateKeyPair(); expect(keyPair).to.have.property('privateKey'); expect(keyPair).to.have.property('publicKey'); - expect(keyPair.privateKey).to.be.instanceOf(ArrayBuffer); - expect(keyPair.publicKey).to.be.instanceOf(ArrayBuffer); + expect(keyPair.privateKey).to.be.instanceOf(Uint8Array); + expect(keyPair.publicKey).to.be.instanceOf(Uint8Array); }); it('returns a 32-byte private key', async () => { @@ -816,7 +917,7 @@ describe('Cryptographic Primitive Implementations', () => { }); describe('getPublicKey()', () => { - let keyPair: BufferKeyPair; + let keyPair: BytesKeyPair; before(async () => { keyPair = await X25519.generateKeyPair(); @@ -824,65 +925,72 @@ describe('Cryptographic Primitive Implementations', () => { it('returns a 32-byte compressed public key', async () => { const publicKey = await X25519.getPublicKey({ privateKey: keyPair.privateKey }); + expect(publicKey).to.be.instanceOf(Uint8Array); expect(publicKey.byteLength).to.equal(32); }); }); describe('sharedSecret()', () => { - describe('sharedSecret()', () => { - let otherPartyKeyPair: BufferKeyPair; - let ownKeyPair: BufferKeyPair; + let otherPartyKeyPair: BytesKeyPair; + let ownKeyPair: BytesKeyPair; - beforeEach(async () => { - otherPartyKeyPair = await X25519.generateKeyPair(); - ownKeyPair = await X25519.generateKeyPair(); + beforeEach(async () => { + otherPartyKeyPair = await X25519.generateKeyPair(); + ownKeyPair = await X25519.generateKeyPair(); + }); + + it('generates a 32-byte compressed secret', async () => { + const sharedSecret = await X25519.sharedSecret({ + privateKey : ownKeyPair.privateKey, + publicKey : otherPartyKeyPair.publicKey }); + expect(sharedSecret).to.be.instanceOf(Uint8Array); + expect(sharedSecret.byteLength).to.equal(32); + }); - it('generates a 32-byte compressed secret, by default', async () => { - const sharedSecret = await X25519.sharedSecret({ - privateKey : ownKeyPair.privateKey, - publicKey : otherPartyKeyPair.publicKey - }); - expect(sharedSecret).to.be.instanceOf(ArrayBuffer); - expect(sharedSecret.byteLength).to.equal(32); + it('generates identical output if keypairs are swapped', async () => { + const sharedSecretOwnOther = await X25519.sharedSecret({ + privateKey : ownKeyPair.privateKey, + publicKey : otherPartyKeyPair.publicKey }); - it('generates identical output if keypairs are swapped', async () => { - const sharedSecretOwnOther = await X25519.sharedSecret({ - privateKey : ownKeyPair.privateKey, - publicKey : otherPartyKeyPair.publicKey - }); + const sharedSecretOtherOwn = await X25519.sharedSecret({ + privateKey : otherPartyKeyPair.privateKey, + publicKey : ownKeyPair.publicKey + }); - const sharedSecretOtherOwn = await X25519.sharedSecret({ - privateKey : otherPartyKeyPair.privateKey, - publicKey : ownKeyPair.publicKey - }); + expect(sharedSecretOwnOther).to.deep.equal(sharedSecretOtherOwn); + }); + }); - expect(sharedSecretOwnOther).to.deep.equal(sharedSecretOtherOwn); - }); + describe('validatePublicKey()', () => { + it('throws a not implemented error', async () => { + await expect( + X25519.validatePublicKey({ key: new Uint8Array(32) }) + ).to.eventually.be.rejectedWith(Error, 'Not implemented'); }); }); }); describe('XChaCha20', () => { describe('decrypt()', () => { - it('returns ArrayBuffer plaintext with length matching input', async () => { + it('returns Uint8Array plaintext with length matching input', async () => { const plaintext = await XChaCha20.decrypt({ data : new Uint8Array(10), - key : new ArrayBuffer(32), + key : new Uint8Array(32), nonce : new Uint8Array(24) }); - expect(plaintext).to.be.an('ArrayBuffer'); + expect(plaintext).to.be.an('Uint8Array'); expect(plaintext.byteLength).to.equal(10); }); it('passes test vectors', async () => { const input = { - data : Convert.hex('879b10a139674fe65087f59577ee2c1ab54655d900697fd02d953f53ddcc1ae476e8').toArrayBuffer(), - key : Convert.hex('79c99798ac67300bbb2704c95c341e3245f3dcb21761b98e52ff45b24f304fc4').toArrayBuffer(), - nonce : Convert.hex('b33ffd3096479bcfbc9aee49417688a0a2554f8d95389419').toArrayBuffer() + data : Convert.hex('879b10a139674fe65087f59577ee2c1ab54655d900697fd02d953f53ddcc1ae476e8').toUint8Array(), + key : Convert.hex('79c99798ac67300bbb2704c95c341e3245f3dcb21761b98e52ff45b24f304fc4').toUint8Array(), + nonce : Convert.hex('b33ffd3096479bcfbc9aee49417688a0a2554f8d95389419').toUint8Array() }; - const output = Convert.string(`Are You There Bob? It's Me, Alice.`).toArrayBuffer(); + const output = Convert.string(`Are You There Bob? It's Me, Alice.`).toUint8Array(); const ciphertext = await XChaCha20.decrypt({ data : input.data, @@ -892,63 +1000,26 @@ describe('Cryptographic Primitive Implementations', () => { expect(ciphertext).to.deep.equal(output); }); - - it('accepts plaintext input as ArrayBuffer, DataView, and TypedArray', async () => { - const input = { - data : Convert.hex('879b10a139674fe65087f59577ee2c1ab54655d900697fd02d953f53ddcc1ae476e8').toUint8Array(), - key : Convert.hex('79c99798ac67300bbb2704c95c341e3245f3dcb21761b98e52ff45b24f304fc4').toArrayBuffer(), - nonce : Convert.hex('b33ffd3096479bcfbc9aee49417688a0a2554f8d95389419').toArrayBuffer() - }; - let ciphertext: ArrayBuffer; - let output: ArrayBuffer; - output = Convert.string(`Are You There Bob? It's Me, Alice.`).toArrayBuffer(); - - // ArrayBuffer - const dataArrayBuffer = input.data.buffer; - ciphertext = await XChaCha20.decrypt({ data: dataArrayBuffer, key: input.key, nonce: input.nonce }); - expect(ciphertext).to.deep.equal(output); - - // DataView - const dataView = new DataView(dataArrayBuffer); - ciphertext = await XChaCha20.decrypt({ data: dataView, key: input.key, nonce: input.nonce }); - expect(ciphertext).to.deep.equal(output); - - // TypedArray - Uint8Array - ciphertext = await XChaCha20.decrypt({ data: input.data, key: input.key,nonce: input.nonce }); - expect(ciphertext).to.deep.equal(output); - - // TypedArray - Int32Array - const dataI32A = new Int32Array(Convert.hex('cce9758174083ac61aef90e73ace6e75').toArrayBuffer()); - ciphertext = await XChaCha20.decrypt({ data: dataI32A, key: input.key, nonce: input.nonce }); - output = (new Int32Array([10, 20, 30, 40])).buffer; - expect(ciphertext).to.deep.equal(output); - - // TypedArray - Uint32Array - const dataU32A = new Uint32Array(Convert.hex('cee9758167083ac602ef90e717ce6e75d3797590774e0cf062f013739da07387').toArrayBuffer()); - ciphertext = await XChaCha20.decrypt({ data: dataU32A, key: input.key, nonce: input.nonce }); - output = (new Uint32Array([8, 7, 6, 5, 4, 3, 2, 1])).buffer; - expect(ciphertext).to.deep.equal(output); - }); }); describe('encrypt()', () => { - it('returns ArrayBuffer ciphertext with length matching input', async () => { + it('returns Uint8Array ciphertext with length matching input', async () => { const ciphertext = await XChaCha20.encrypt({ data : new Uint8Array(10), - key : new ArrayBuffer(32), + key : new Uint8Array(32), nonce : new Uint8Array(24) }); - expect(ciphertext).to.be.an('ArrayBuffer'); + expect(ciphertext).to.be.an('Uint8Array'); expect(ciphertext.byteLength).to.equal(10); }); it('passes test vectors', async () => { const input = { - data : Convert.string(`Are You There Bob? It's Me, Alice.`).toArrayBuffer(), - key : Convert.hex('79c99798ac67300bbb2704c95c341e3245f3dcb21761b98e52ff45b24f304fc4').toArrayBuffer(), - nonce : Convert.hex('b33ffd3096479bcfbc9aee49417688a0a2554f8d95389419').toArrayBuffer() + data : Convert.string(`Are You There Bob? It's Me, Alice.`).toUint8Array(), + key : Convert.hex('79c99798ac67300bbb2704c95c341e3245f3dcb21761b98e52ff45b24f304fc4').toUint8Array(), + nonce : Convert.hex('b33ffd3096479bcfbc9aee49417688a0a2554f8d95389419').toUint8Array() }; - const output = Convert.hex('879b10a139674fe65087f59577ee2c1ab54655d900697fd02d953f53ddcc1ae476e8').toArrayBuffer(); + const output = Convert.hex('879b10a139674fe65087f59577ee2c1ab54655d900697fd02d953f53ddcc1ae476e8').toUint8Array(); const ciphertext = await XChaCha20.encrypt({ data : input.data, @@ -958,49 +1029,120 @@ describe('Cryptographic Primitive Implementations', () => { expect(ciphertext).to.deep.equal(output); }); + }); + + describe('generateKey()', () => { + it('returns a 32-byte secret key of type Uint8Array', async () => { + const secretKey = await XChaCha20.generateKey(); + expect(secretKey).to.be.instanceOf(Uint8Array); + expect(secretKey.byteLength).to.equal(32); + }); + }); + }); + + describe('XChaCha20Poly1305', () => { + describe('decrypt()', () => { + it('returns Uint8Array plaintext with length matching input', async () => { + const plaintext = await XChaCha20Poly1305.decrypt({ + data : Convert.hex('789e9689e5208d7fd9e1').toUint8Array(), + key : new Uint8Array(32), + nonce : new Uint8Array(24), + tag : Convert.hex('09701fb9f36ab77a0f136ca539229a34').toUint8Array() + }); + expect(plaintext).to.be.an('Uint8Array'); + expect(plaintext.byteLength).to.equal(10); + }); - it('accepts plaintext input as ArrayBuffer, DataView, and TypedArray', async () => { + it('passes test vectors', async () => { const input = { - data : Convert.string(`Are You There Bob? It's Me, Alice.`).toUint8Array(), - key : Convert.hex('79c99798ac67300bbb2704c95c341e3245f3dcb21761b98e52ff45b24f304fc4').toArrayBuffer(), - nonce : Convert.hex('b33ffd3096479bcfbc9aee49417688a0a2554f8d95389419').toArrayBuffer() + data : Convert.hex('80246ca517c0fb5860c19090a7e7a2b030dde4882520102cbc64fad937916596ca9d').toUint8Array(), + key : Convert.hex('79c99798ac67300bbb2704c95c341e3245f3dcb21761b98e52ff45b24f304fc4').toUint8Array(), + nonce : Convert.hex('b33ffd3096479bcfbc9aee49417688a0a2554f8d95389419').toUint8Array(), + tag : Convert.hex('9e10a121d990e6a290f6b534516aa32f').toUint8Array() }; - let ciphertext: ArrayBuffer; - let output: ArrayBuffer; - output = Convert.hex('879b10a139674fe65087f59577ee2c1ab54655d900697fd02d953f53ddcc1ae476e8').toArrayBuffer(); + const output = Convert.string(`Are You There Bob? It's Me, Alice.`).toUint8Array(); - // ArrayBuffer - const dataArrayBuffer = input.data.buffer; - ciphertext = await XChaCha20.encrypt({ data: dataArrayBuffer, key: input.key, nonce: input.nonce }); - expect(ciphertext).to.deep.equal(output); + const plaintext = await XChaCha20Poly1305.decrypt({ + data : input.data, + key : input.key, + nonce : input.nonce, + tag : input.tag + }); - // DataView - const dataView = new DataView(dataArrayBuffer); - ciphertext = await XChaCha20.encrypt({ data: dataView, key: input.key, nonce: input.nonce }); - expect(ciphertext).to.deep.equal(output); + expect(plaintext).to.deep.equal(output); + }); - // TypedArray - Uint8Array - ciphertext = await XChaCha20.encrypt({ data: input.data, key: input.key,nonce: input.nonce }); - expect(ciphertext).to.deep.equal(output); + it('throws an error if the wrong tag is given', async () => { + await expect( + XChaCha20Poly1305.decrypt({ + data : new Uint8Array(10), + key : new Uint8Array(32), + nonce : new Uint8Array(24), + tag : new Uint8Array(16) + }) + ).to.eventually.be.rejectedWith(Error, 'Wrong tag'); + }); + }); - // TypedArray - Int32Array - const dataI32A = new Int32Array([10, 20, 30, 40]); - ciphertext = await XChaCha20.encrypt({ data: dataI32A, key: input.key, nonce: input.nonce }); - output = Convert.hex('cce9758174083ac61aef90e73ace6e75').toArrayBuffer(); - expect(ciphertext).to.deep.equal(output); + describe('encrypt()', () => { + it('returns Uint8Array ciphertext and tag', async () => { + const { ciphertext, tag } = await XChaCha20Poly1305.encrypt({ + data : new Uint8Array(10), + key : new Uint8Array(32), + nonce : new Uint8Array(24) + }); + expect(ciphertext).to.be.an('Uint8Array'); + expect(ciphertext.byteLength).to.equal(10); + expect(tag).to.be.an('Uint8Array'); + expect(tag.byteLength).to.equal(16); + }); - // TypedArray - Uint32Array - const dataU32A = new Uint32Array([8, 7, 6, 5, 4, 3, 2, 1]); - ciphertext = await XChaCha20.encrypt({ data: dataU32A, key: input.key, nonce: input.nonce }); - output = Convert.hex('cee9758167083ac602ef90e717ce6e75d3797590774e0cf062f013739da07387').toArrayBuffer(); - expect(ciphertext).to.deep.equal(output); + it('accepts additional authenticated data', async () => { + const { ciphertext: ciphertextAad, tag: tagAad } = await XChaCha20Poly1305.encrypt({ + additionalData : new Uint8Array(64), + data : new Uint8Array(10), + key : new Uint8Array(32), + nonce : new Uint8Array(24) + }); + + const { ciphertext, tag } = await XChaCha20Poly1305.encrypt({ + data : new Uint8Array(10), + key : new Uint8Array(32), + nonce : new Uint8Array(24) + }); + + expect(ciphertextAad.byteLength).to.equal(10); + expect(ciphertext.byteLength).to.equal(10); + expect(ciphertextAad).to.deep.equal(ciphertext); + expect(tagAad).to.not.deep.equal(tag); + }); + + it('passes test vectors', async () => { + const input = { + data : Convert.string(`Are You There Bob? It's Me, Alice.`).toUint8Array(), + key : Convert.hex('79c99798ac67300bbb2704c95c341e3245f3dcb21761b98e52ff45b24f304fc4').toUint8Array(), + nonce : Convert.hex('b33ffd3096479bcfbc9aee49417688a0a2554f8d95389419').toUint8Array() + }; + const output = { + ciphertext : Convert.hex('80246ca517c0fb5860c19090a7e7a2b030dde4882520102cbc64fad937916596ca9d').toUint8Array(), + tag : Convert.hex('9e10a121d990e6a290f6b534516aa32f').toUint8Array() + }; + + const { ciphertext, tag } = await XChaCha20Poly1305.encrypt({ + data : input.data, + key : input.key, + nonce : input.nonce + }); + + expect(ciphertext).to.deep.equal(output.ciphertext); + expect(tag).to.deep.equal(output.tag); }); }); describe('generateKey()', () => { - it('returns a 32-byte secret key of type ArrayBuffer', async () => { - const secretKey = await XChaCha20.generateKey(); - expect(secretKey).to.be.instanceOf(ArrayBuffer); + it('returns a 32-byte secret key of type Uint8Array', async () => { + const secretKey = await XChaCha20Poly1305.generateKey(); + expect(secretKey).to.be.instanceOf(Uint8Array); expect(secretKey.byteLength).to.equal(32); }); }); diff --git a/packages/crypto/tests/fixtures/test-vectors/ed25519.ts b/packages/crypto/tests/fixtures/test-vectors/ed25519.ts new file mode 100644 index 000000000..261a00e56 --- /dev/null +++ b/packages/crypto/tests/fixtures/test-vectors/ed25519.ts @@ -0,0 +1,20 @@ +export const ed25519TestVectors = [ + { + id : '1', + privateKey : { + encoded: '4f2db3cf791aaa1a6445490117b1a110435394f4bef8e384c64dce9536053c5b' + }, + publicKey: { + encoded: 'b3cf2b4a6852f156ab1536c204ca6f2eed787bd44f4295104dcb9b6df8329386' + } + }, + { + id : '2', + privateKey : { + encoded: '9b65f2e65734d4f8b338e4a4c81457289564056890d3c539143ab292ad31ee87' + }, + publicKey: { + encoded: '20e95ce23a1b76d8538e8404f9ce22f2d5a0daaa327b07741a07f116e0e87e6e' + } + }, +]; \ No newline at end of file diff --git a/packages/crypto/tests/fixtures/test-vectors/jose.ts b/packages/crypto/tests/fixtures/test-vectors/jose.ts new file mode 100644 index 000000000..0aa31497e --- /dev/null +++ b/packages/crypto/tests/fixtures/test-vectors/jose.ts @@ -0,0 +1,428 @@ +export const cryptoKeyPairToJsonWebKeyTestVectors = [ + { + id : 'ckp.jwk.1', + cryptoKey : { + publicKey: { + algorithm : { name: 'ECDSA', namedCurve: 'secp256k1' }, + extractable : true, + material : '02c6cf53ccfc13fbdfb25d827636839d9874df3148eba88c07f07601645ca5a006', // Hex, compressed + type : 'public', + usages : ['verify'], + }, + privateKey: { + algorithm : { name: 'ECDSA', namedCurve: 'secp256k1' }, + extractable : true, + material : '1d70915381c9bcb940752c3892b6c3b4476a6906b6aee839227f3f38eaf91190', // Hex + type : 'private', + usages : ['sign'], + } + }, + jsonWebKey: { + publicKeyJwk: { + 'alg' : 'ES256K', + 'crv' : 'secp256k1', + 'ext' : 'true', + 'key_ops' : ['verify'], + 'kty' : 'EC', + 'x' : 'xs9TzPwT-9-yXYJ2NoOdmHTfMUjrqIwH8HYBZFyloAY', // Base64url + 'y' : 'tMa4vfJC9rR8S87Sx9yEHACYOWOh7_UWLiFal56lObY', // Base64url + }, + privateKeyJwk: { + 'alg' : 'ES256K', + 'crv' : 'secp256k1', + 'd' : 'HXCRU4HJvLlAdSw4krbDtEdqaQa2rug5In8_OOr5EZA', // Base64url + 'ext' : 'true', + 'key_ops' : ['sign'], + 'kty' : 'EC', + 'x' : 'xs9TzPwT-9-yXYJ2NoOdmHTfMUjrqIwH8HYBZFyloAY', // Base64url + 'y' : 'tMa4vfJC9rR8S87Sx9yEHACYOWOh7_UWLiFal56lObY', // Base64url + }, + } + }, + { + id : 'ckp.jwk.2', + cryptoKey : { + publicKey: { + algorithm : { name: 'ECDSA', namedCurve: 'secp256k1' }, + extractable : true, + material : '045d67b538b1f3dc38326a975b17c4312b7620c39b656b3012dc9205c5804870c7ab53846c0b4c6f6c0267f08b9ac7075fe1f0b617d013630d92a3c760908b71e3', // Hex, uncompressed + type : 'public', + usages : ['verify'], + }, + privateKey: { + algorithm : { name: 'ECDSA', namedCurve: 'secp256k1' }, + extractable : true, + material : 'c1f488e4919027f1da827a3f25c8121f9092f5d940c0da9a52cb36e192fa1610', // Hex + type : 'private', + usages : ['sign'], + } + }, + jsonWebKey: { + publicKeyJwk: { + 'alg' : 'ES256K', + 'crv' : 'secp256k1', + 'ext' : 'true', + 'key_ops' : ['verify'], + 'kty' : 'EC', + 'x' : 'XWe1OLHz3DgyapdbF8QxK3Ygw5tlazAS3JIFxYBIcMc', // Base64url + 'y' : 'q1OEbAtMb2wCZ_CLmscHX-HwthfQE2MNkqPHYJCLceM', // Base64url + }, + privateKeyJwk: { + 'alg' : 'ES256K', + 'crv' : 'secp256k1', + 'd' : 'wfSI5JGQJ_Hagno_JcgSH5CS9dlAwNqaUss24ZL6FhA', // Base64url + 'ext' : 'true', + 'key_ops' : ['sign'], + 'kty' : 'EC', + 'x' : 'XWe1OLHz3DgyapdbF8QxK3Ygw5tlazAS3JIFxYBIcMc', // Base64url + 'y' : 'q1OEbAtMb2wCZ_CLmscHX-HwthfQE2MNkqPHYJCLceM', // Base64url + }, + } + }, + { + id : 'ckp.jwk.3', + cryptoKey : { + publicKey: { + algorithm : { name: 'EdDSA', namedCurve: 'Ed25519' }, + extractable : true, + material : 'ae92a70cff05e3f8f0bd0ef10e492e2b1d7ae4e4b0732ad0be61169767a28085', // Hex + type : 'public', + usages : ['verify'], + }, + privateKey: { + algorithm : { name: 'EdDSA', namedCurve: 'Ed25519' }, + extractable : true, + material : 'f69e3da1db3fc8b7474224e3271099dab537807212477ad034ae52f3e39d8782', // Hex + type : 'private', + usages : ['sign'], + } + }, + jsonWebKey: { + publicKeyJwk: { + 'alg' : 'EdDSA', + 'crv' : 'Ed25519', + 'ext' : 'true', + 'key_ops' : ['verify'], + 'kty' : 'OKP', + 'x' : 'rpKnDP8F4_jwvQ7xDkkuKx165OSwcyrQvmEWl2eigIU', // Base64url + }, + privateKeyJwk: { + 'alg' : 'EdDSA', + 'crv' : 'Ed25519', + 'd' : '9p49ods_yLdHQiTjJxCZ2rU3gHISR3rQNK5S8-Odh4I', // Base64url + 'ext' : 'true', + 'key_ops' : ['sign'], + 'kty' : 'OKP', + 'x' : 'rpKnDP8F4_jwvQ7xDkkuKx165OSwcyrQvmEWl2eigIU', // Base64url + }, + } + }, + { + id : 'ckp.jwk.4', + cryptoKey : { + publicKey: { + algorithm : { name: 'ECDH', namedCurve: 'X25519' }, + extractable : true, + material : '796037a1434a9b79d9374bea882fed0a53c2901ce737947463d3687c99286973', // Hex + type : 'public', + usages : ['deriveBits', 'deriveKey'], + }, + privateKey: { + algorithm : { name: 'ECDH', namedCurve: 'X25519' }, + extractable : true, + material : '20a6d2ab343efc5d8718af1afb3157984b63712edc5f5c1c77bcf8f732f8b545', // Hex + type : 'private', + usages : ['deriveBits', 'deriveKey'], + } + }, + jsonWebKey: { + publicKeyJwk: { + 'crv' : 'X25519', + 'ext' : 'true', + 'key_ops' : ['deriveBits', 'deriveKey'], + 'kty' : 'OKP', + 'x' : 'eWA3oUNKm3nZN0vqiC_tClPCkBznN5R0Y9NofJkoaXM', // Base64url + }, + privateKeyJwk: { + 'crv' : 'X25519', + 'd' : 'IKbSqzQ-_F2HGK8a-zFXmEtjcS7cX1wcd7z49zL4tUU', // Base64url + 'ext' : 'true', + 'key_ops' : ['deriveBits', 'deriveKey'], + 'kty' : 'OKP', + 'x' : 'eWA3oUNKm3nZN0vqiC_tClPCkBznN5R0Y9NofJkoaXM', // Base64url + }, + } + }, +]; + +export const cryptoKeyToJwkTestVectors = [ + { + id : 'csk.jwk.1', + cryptoKey : { + algorithm : { name: 'AES-CTR', length: 256 }, + extractable : true, + material : '510b48012fab99607ebe03601b894fae74d2dad36fc033ca97daecd0bf480a75', // Hex + type : 'secret', + usages : ['encrypt', 'decrypt'], + }, + jsonWebKey: { + 'alg' : 'A256CTR', + 'ext' : 'true', + 'key_ops' : ['encrypt', 'decrypt'], + 'k' : 'UQtIAS-rmWB-vgNgG4lPrnTS2tNvwDPKl9rs0L9ICnU', // Base64url + 'kty' : 'oct', + } + }, + { + id : 'csk.jwk.2', + cryptoKey : { + algorithm : { name: 'AES-GCM', length: 256 }, + extractable : true, + material : 'fa919d00b0edc66c73efcc2325073fff8173bd30956174cd50b3381f438a56ac', // Hex + type : 'secret', + usages : ['encrypt', 'decrypt'], + }, + jsonWebKey: { + 'alg' : 'A256GCM', + 'ext' : 'true', + 'key_ops' : ['encrypt', 'decrypt'], + 'k' : '-pGdALDtxmxz78wjJQc__4FzvTCVYXTNULM4H0OKVqw', // Base64url + 'kty' : 'oct', + } + }, + { + id : 'csk.jwk.3', + cryptoKey : { + algorithm : { name: 'HMAC', hash: { name: 'SHA-256' } }, + extractable : true, + material : 'dc739a7be3ffc152af69bc45dfb02d81cfe313c7cb074c643144a9c15588d87468bafa02da20ab7fc8f7498916b184459b84aff27736be9cc8f60e49ca0d01c7', // Hex + type : 'secret', + usages : ['sign', 'verify'], + }, + jsonWebKey: { + 'alg' : 'HS256', + 'ext' : 'true', + 'key_ops' : ['sign', 'verify'], + 'k' : '3HOae-P_wVKvabxF37Atgc_jE8fLB0xkMUSpwVWI2HRouvoC2iCrf8j3SYkWsYRFm4Sv8nc2vpzI9g5Jyg0Bxw', // Base64url + 'kty' : 'oct', + } + }, +]; + +export const joseToWebCryptoTestVectors = [ + { + id : 'jose.wc.1', + jose : { crv: 'Ed25519', alg: 'EdDSA', kty: 'OKP' }, + webCrypto : { namedCurve: 'Ed25519', name: 'EdDSA' } + }, + { + id : 'jose.wc.2', + jose : { crv: 'Ed448', alg: 'EdDSA', kty: 'OKP' }, + webCrypto : { namedCurve: 'Ed448', name: 'EdDSA' } + }, + { + id : 'jose.wc.3', + jose : { crv: 'X25519', kty: 'OKP' }, + webCrypto : { namedCurve: 'X25519', name: 'ECDH' } + }, + { + id : 'jose.wc.4', + jose : { crv: 'secp256k1', alg: 'ES256K', kty: 'EC' }, + webCrypto : { namedCurve: 'secp256k1', name: 'ECDSA' } + }, + { + id : 'jose.wc.5', + jose : { crv: 'secp256k1', kty: 'EC' }, + webCrypto : { namedCurve: 'secp256k1', name: 'ECDH' } + }, + { + id : 'jose.wc.6', + jose : { crv: 'P-256', alg: 'ES256', kty: 'EC' }, + webCrypto : { namedCurve: 'P-256', name: 'ECDSA' } + }, + { + id : 'jose.wc.7', + jose : { crv: 'P-384', alg: 'ES384', kty: 'EC' }, + webCrypto : { namedCurve: 'P-384', name: 'ECDSA' } + }, + { + id : 'jose.wc.8', + jose : { crv: 'P-521', alg: 'ES512', kty: 'EC' }, + webCrypto : { namedCurve: 'P-521', name: 'ECDSA' } + }, + { + id : 'jose.wc.9', + jose : { alg: 'A128CBC', kty: 'oct' }, + webCrypto : { name: 'AES-CBC', length: 128 } + }, + { + id : 'jose.wc.10', + jose : { alg: 'A192CBC', kty: 'oct' }, + webCrypto : { name: 'AES-CBC', length: 192 } + }, + { + id : 'jose.wc.11', + jose : { alg: 'A256CBC', kty: 'oct' }, + webCrypto : { name: 'AES-CBC', length: 256 } + }, + { + id : 'jose.wc.12', + jose : { alg: 'A128CTR', kty: 'oct' }, + webCrypto : { name: 'AES-CTR', length: 128 } + }, + { + id : 'jose.wc.13', + jose : { alg: 'A192CTR', kty: 'oct' }, + webCrypto : { name: 'AES-CTR', length: 192 } + }, + { + id : 'jose.wc.14', + jose : { alg: 'A256CTR', kty: 'oct' }, + webCrypto : { name: 'AES-CTR', length: 256 } + }, + { + id : 'jose.wc.15', + jose : { alg: 'A128GCM', kty: 'oct' }, + webCrypto : { name: 'AES-GCM', length: 128 } + }, + { + id : 'jose.wc.16', + jose : { alg: 'A192GCM', kty: 'oct' }, + webCrypto : { name: 'AES-GCM', length: 192 } + }, + { + id : 'jose.wc.17', + jose : { alg: 'A256GCM', kty: 'oct' }, + webCrypto : { name: 'AES-GCM', length: 256 } + }, + { + id : 'jose.wc.18', + jose : { alg: 'HS256', kty: 'oct' }, + webCrypto : { name: 'HMAC', hash: { name: 'SHA-256' } } + }, + { + id : 'jose.wc.19', + jose : { alg: 'HS384', kty: 'oct' }, + webCrypto : { name: 'HMAC', hash: { name: 'SHA-384' } } + }, + { + id : 'jose.wc.20', + jose : { alg: 'HS512', kty: 'oct' }, + webCrypto : { name: 'HMAC', hash: { name: 'SHA-512' } } + }, +]; + +export const keyToJwkTestVectorsKeyMaterial = '72e63e7c4bbf575b386fc1db1b3cbff5539a36dc6250fccb9fa28e013773d24b'; +export const keyToJwkMulticodecTestVectors = [ + { + input : 'ed25519-pub', + output : { + alg : 'EdDSA', + crv : 'Ed25519', + kty : 'OKP', + x : 'cuY-fEu_V1s4b8HbGzy_9VOaNtxiUPzLn6KOATdz0ks' + } + }, + { + input : 'ed25519-priv', + output : { + d : '', + alg : 'EdDSA', + crv : 'Ed25519', + kty : 'OKP', + x : 'c5UR1q2r1lOT_ygDhSkU3paf5Bmukg-jX-1t4kIKJvA' + } + }, + { + input : 'secp256k1-pub', + output : { + alg : 'ES256K', + crv : 'secp256k1', + kty : 'EC', + x : '_TihFv5t24hjWsRcdZBeEJa65hQB5aiOYmG6mMu1RZA', + y : 'UfiOGckhJuh9f3-Yi7g-jTILYP6vEWOSF1drwjBHebA' + } + }, + { + input : 'secp256k1-priv', + output : { + d : '', + alg : 'ES256K', + crv : 'secp256k1', + kty : 'EC', + x : '_TihFv5t24hjWsRcdZBeEJa65hQB5aiOYmG6mMu1RZA', + y : 'UfiOGckhJuh9f3-Yi7g-jTILYP6vEWOSF1drwjBHebA' + } + }, + { + input : 'x25519-pub', + output : { + crv : 'X25519', + kty : 'OKP', + x : 'cuY-fEu_V1s4b8HbGzy_9VOaNtxiUPzLn6KOATdz0ks' + } + }, + { + input : 'x25519-priv', + output : { + d : '', + crv : 'X25519', + kty : 'OKP', + x : 'MBZd77wAy5932AEP7MHXOevv_MLzzD9OP_fZAOlnIWM' + } + } +]; +export const keyToJwkWebCryptoTestVectors = [ + { + input : { namedCurve: 'Ed25519', name: 'EdDSA' }, + output : { + alg : 'EdDSA', + crv : 'Ed25519', + kty : 'OKP', + x : 'cuY-fEu_V1s4b8HbGzy_9VOaNtxiUPzLn6KOATdz0ks' + } + }, + { + input : { namedCurve: 'X25519', name: 'ECDH' }, + output : { + crv : 'X25519', + kty : 'OKP', + x : 'cuY-fEu_V1s4b8HbGzy_9VOaNtxiUPzLn6KOATdz0ks' + } + }, + { + input : { namedCurve: 'secp256k1', name: 'ECDSA' }, + output : { + alg : 'ES256K', + crv : 'secp256k1', + kty : 'EC', + x : '_TihFv5t24hjWsRcdZBeEJa65hQB5aiOYmG6mMu1RZA', + y : 'UfiOGckhJuh9f3-Yi7g-jTILYP6vEWOSF1drwjBHebA' + } + }, + { + input : { namedCurve: 'secp256k1', name: 'ECDH' }, + output : { + crv : 'secp256k1', + kty : 'EC', + x : '_TihFv5t24hjWsRcdZBeEJa65hQB5aiOYmG6mMu1RZA', + y : 'UfiOGckhJuh9f3-Yi7g-jTILYP6vEWOSF1drwjBHebA' + } + }, + { + input : { name: 'AES-CBC', length: 128 }, + output : { + alg : 'A128CBC', + kty : 'oct', + k : 'cuY-fEu_V1s4b8HbGzy_9VOaNtxiUPzLn6KOATdz0ks' + } + }, + { + input : { name: 'HMAC', hash: { name: 'SHA-256' } }, + output : { + alg : 'HS256', + kty : 'oct', + k : 'cuY-fEu_V1s4b8HbGzy_9VOaNtxiUPzLn6KOATdz0ks' + } + } +]; \ No newline at end of file diff --git a/packages/crypto/tests/fixtures/test-vectors/secp256k1.ts b/packages/crypto/tests/fixtures/test-vectors/secp256k1.ts new file mode 100644 index 000000000..f6b63dfea --- /dev/null +++ b/packages/crypto/tests/fixtures/test-vectors/secp256k1.ts @@ -0,0 +1,46 @@ +export const secp256k1TestVectors = [ + { + id : '1', + privateKey : { + encoded: '740ec69810de9ad1b8f298f1d2c0e6a52dd1e958dc2afc85764bec169c222e88' + }, + publicKey: { + encoded : '043752951274023296c8a74b0ffe42f82ff4b4d4bba4326477422703f761f59258c26a7465b9a77ac0c3f1cedb139c428b0b1fbb5516867b527636f3286f705553', + x : '3752951274023296c8a74b0ffe42f82ff4b4d4bba4326477422703f761f59258', + y : 'c26a7465b9a77ac0c3f1cedb139c428b0b1fbb5516867b527636f3286f705553' + } + }, + { + id : '2', + privateKey : { + encoded: 'b4713736a3562ff9b7b9e56ad2a533f241610e88217536cf2e620967daf91fd4' + }, + publicKey: { + encoded : '042dc60f3eed21d861fe7ccd353fb87e4dba6d0f453a71d4f8a1d2a17fe5486fd72a1d3bb247c3ca44e2a4d94cc616d6ce991fca220262c51d4edfcd3a1f55c9b4', + x : '2dc60f3eed21d861fe7ccd353fb87e4dba6d0f453a71d4f8a1d2a17fe5486fd7', + y : '2a1d3bb247c3ca44e2a4d94cc616d6ce991fca220262c51d4edfcd3a1f55c9b4' + }, + }, + { + id : '3', + privateKey : { + encoded: 'ea3db478f42cdbca7dbfe521167b03f40a5245370cba07142868c21d0082b391' + }, + publicKey: { + encoded : '037a549a3bdc432592ed40f2549e8668172e8bf1c3985066199472477f767b08f3', + x : '7a549a3bdc432592ed40f2549e8668172e8bf1c3985066199472477f767b08f3', + y : 'a5d03261db10f90f42af658c88f56aaf96fb1561f9c70f61ebe2c5bd2870b571' + } + }, + { + id : '4', + privateKey : { + encoded: '1b23fe831540368c37ec150febdaecc3dc47168585f3171a705f919357f9fff7' + }, + publicKey: { + encoded : '02ea7dd6427cdc1bb1b79584cab8e8109bf98e1cfef6c8dc9d8005d8e49ef1c150', + x : 'ea7dd6427cdc1bb1b79584cab8e8109bf98e1cfef6c8dc9d8005d8e49ef1c150', + y : 'e02763fa1504fa357acbb00c6711b8733c0a5938ebdaf228abd6ccbe7dbc6f80' + } + } +]; \ No newline at end of file diff --git a/packages/crypto/tests/jose.spec.ts b/packages/crypto/tests/jose.spec.ts new file mode 100644 index 000000000..a0d0aa2c4 --- /dev/null +++ b/packages/crypto/tests/jose.spec.ts @@ -0,0 +1,236 @@ +import chai, { expect } from 'chai'; +import { Convert } from '@web5/common'; +import chaiAsPromised from 'chai-as-promised'; + +import type { JsonWebKey } from '../src/jose.js'; +import type { Web5Crypto } from '../src/types/web5-crypto.js'; + +import { CryptoKeyWithJwk, Jose } from '../src/jose.js'; +import { + cryptoKeyToJwkTestVectors, + cryptoKeyPairToJsonWebKeyTestVectors, + joseToWebCryptoTestVectors, + keyToJwkWebCryptoTestVectors, + keyToJwkMulticodecTestVectors, + keyToJwkTestVectorsKeyMaterial +} from './fixtures/test-vectors/jose.js'; + +chai.use(chaiAsPromised); + +describe('CryptoKeyWithJwk()', () => { + it('converts private CryptoKeys to JWK', async () => { + for (const vector of cryptoKeyPairToJsonWebKeyTestVectors) { + const privateKey = { + ...vector.cryptoKey.privateKey, + material: Convert.hex(vector.cryptoKey.privateKey.material).toUint8Array() + } as Web5Crypto.CryptoKey; + + const cryptoKey = new CryptoKeyWithJwk( + privateKey.algorithm, + privateKey.extractable, + privateKey.material, + privateKey.type, + privateKey.usages + ); + + const jsonWebKey = await cryptoKey.toJwk(); + + expect(jsonWebKey).to.deep.equal(vector.jsonWebKey.privateKeyJwk); + } + }); + + it('converts public CryptoKeys to JWK', async () => { + for (const vector of cryptoKeyPairToJsonWebKeyTestVectors) { + const publicKey = { + ...vector.cryptoKey.publicKey, + material: Convert.hex(vector.cryptoKey.publicKey.material).toUint8Array() + } as Web5Crypto.CryptoKey; + + const cryptoKey = new CryptoKeyWithJwk( + publicKey.algorithm, + publicKey.extractable, + publicKey.material, + publicKey.type, + publicKey.usages + ); + + const jsonWebKey = await cryptoKey.toJwk(); + + expect(jsonWebKey).to.deep.equal(vector.jsonWebKey.publicKeyJwk); + } + }); + + it('converts secret CryptoKeys to JWK', async () => { + for (const vector of cryptoKeyToJwkTestVectors) { + const secretKey = { + ...vector.cryptoKey, + material: Convert.hex(vector.cryptoKey.material).toUint8Array() + } as Web5Crypto.CryptoKey; + + const cryptoKey = new CryptoKeyWithJwk( + secretKey.algorithm, + secretKey.extractable, + secretKey.material, + secretKey.type, + secretKey.usages + ); + + const jsonWebKey = await cryptoKey.toJwk(); + + expect(jsonWebKey).to.deep.equal(vector.jsonWebKey); + } + }); + + it('converts public CryptoKeys with extractable=false', async () => { + for (const vector of cryptoKeyPairToJsonWebKeyTestVectors) { + const publicKey = { + ...vector.cryptoKey.publicKey, + material: Convert.hex(vector.cryptoKey.publicKey.material).toUint8Array() + } as Web5Crypto.CryptoKey; + + const cryptoKey = new CryptoKeyWithJwk( + publicKey.algorithm, + false, // override extractable to false + publicKey.material, + publicKey.type, + publicKey.usages + ); + + const jsonWebKey = await cryptoKey.toJwk(); + + expect(jsonWebKey).to.deep.equal({ ...vector.jsonWebKey.publicKeyJwk, ext: 'false' }); + } + }); + + it('throws an error with unsupported algorithms', async () => { + const cryptoKey = new CryptoKeyWithJwk( + { name: 'ECDSA', namedCurve: 'P-256' }, // algorithm identifier + false, // extractable + new Uint8Array(32), // material aka key material + 'private', // key type + ['sign', 'verify'] // key usages + ); + + await expect( + cryptoKey.toJwk() + ).to.eventually.be.rejectedWith(Error, 'Unsupported key to JWK conversion: P-256'); + }); +}); + +describe('Jose', () => { + describe('joseToWebCrypto()', () => { + it('translates algorithm format from JOSE to WebCrypto', () => { + let webCrypto: Web5Crypto.GenerateKeyOptions; + for (const vector of joseToWebCryptoTestVectors) { + webCrypto = Jose.joseToWebCrypto(vector.jose as JsonWebKey); + expect(webCrypto).to.deep.equal(vector.webCrypto); + } + }); + + it('throws an error if required parameters are missing', () => { + expect( + () => Jose.joseToWebCrypto({}) + ).to.throw(TypeError, 'One or more parameters missing'); + }); + + it('throws an error if an unknown JOSE algorithm is specified', () => { + expect( + () => Jose.joseToWebCrypto({ alg: 'non-existent' }) + ).to.throw(Error, `Unsupported JOSE to WebCrypto conversion: 'non-existent'`); + + expect( + // @ts-expect-error because invalid algorithm was intentionally specified to trigger an error. + () => Jose.joseToWebCrypto({ crv: 'non-existent' }) + ).to.throw(Error, `Unsupported JOSE to WebCrypto conversion: 'non-existent'`); + }); + }); + + describe('jwkThumbprint()', () => { + it('passes RFC 7638 test vector', async () => { + // @see {@link https://datatracker.ietf.org/doc/html/rfc7638#section-3.1 | Example JWK Thumbprint Computation} + const jwk: JsonWebKey = { + 'kty' : 'RSA', + 'n' : '0vx7agoebGcQSuuPiLJXZptN9nndrQmbXEps2aiAFbWhM78LhWx4cbbfAAtVT86zwu1RK7aPFFxuhDR1L6tSoc_BJECPebWKRXjBZCiFV4n3oknjhMstn64tZ_2W-5JsGY4Hc5n9yBXArwl93lqt7_RN5w6Cf0h4QyQ5v-65YGjQR0_FDW2QvzqY368QQMicAtaSqzs8KJZgnYb9c7d0zgdAZHzu6qMQvRL5hajrn1n91CbOpbISD08qNLyrdkt-bFTWhAI4vMQFh6WeZu0fM4lFd2NcRwr3XPksINHaQ-G_xBniIqbw0Ls1jF44-csFCur-kEgU8awapJzKnqDKgw', + 'e' : 'AQAB', + 'alg' : 'RS256', + 'kid' : '2011-04-29' + }; + + const jwkThumbprint = await Jose.jwkThumbprint({ key: jwk }); + expect(jwkThumbprint).to.equal('NzbLsXh8uDCcd-6MNwXF4W_7noWXFZAfHkxZsRGC9Xs'); + }); + }); + + describe('keyToJwk()', () => { + it('converts key with Jose parameters (from WebCrypto) into JWK', async () => { + let jwkParams: Partial; + const keyMaterial = Convert.hex(keyToJwkTestVectorsKeyMaterial).toUint8Array(); + + for (const vector of keyToJwkWebCryptoTestVectors) { + jwkParams = Jose.webCryptoToJose(vector.input); + const jwk = await Jose.keyToJwk({ keyMaterial, keyType: 'public', ...jwkParams }); + expect(jwk).to.deep.equal(vector.output); + } + }); + + it('converts key with Jose parameters (from Multicodec) into JWK', async () => { + let jwkParams: Partial; + const keyMaterial = Convert.hex(keyToJwkTestVectorsKeyMaterial).toUint8Array(); + + for (const vector of keyToJwkMulticodecTestVectors) { + jwkParams = await Jose.multicodecToJose({ name: vector.input }); + const keyType = vector.input.includes('priv') ? 'private' : 'public'; + const jwk = await Jose.keyToJwk({ keyMaterial, keyType, ...jwkParams }); + expect(jwk).to.deep.equal(vector.output); + } + }); + + it('throws an error for unsupported conversion', async () => { + let jwkParams: Partial; + const testVectors = [ + { namedCurve: 'Ed448', name: 'EdDSA' }, + { namedCurve: 'P-256', name: 'ECDSA' }, + { namedCurve: 'P-384', name: 'ECDSA' }, + { namedCurve: 'P-521', name: 'ECDSA' } + ]; + const keyMaterial = new Uint8Array(32); + for (const vector of testVectors) { + jwkParams = Jose.webCryptoToJose(vector); + await expect( + Jose.keyToJwk({ keyMaterial, keyType: 'public', ...jwkParams }) + ).to.eventually.be.rejectedWith(Error, 'Unsupported key to JWK conversion'); + } + }); + }); + + describe('webCryptoToJose()', () => { + it('translates algorithm format from WebCrypto to JOSE', () => { + let jose: Partial; + for (const vector of joseToWebCryptoTestVectors) { + jose = Jose.webCryptoToJose(vector.webCrypto); + expect(jose).to.deep.equal(vector.jose); + } + }); + + it('throws an error if required parameters are missing', () => { + expect( + // @ts-expect-error because parameters are intentionally omitted to trigger an error. + () => Jose.webCryptoToJose({}) + ).to.throw(TypeError, 'One or more parameters missing'); + }); + + it('throws an error if an unknown WebCrypto algorithm is specified', () => { + expect( + () => Jose.webCryptoToJose({ name: 'non-existent', namedCurve: 'non-existent' }) + ).to.throw(Error, `Unsupported WebCrypto to JOSE conversion: 'non-existent:non-existent'`); + + expect( + () => Jose.webCryptoToJose({ name: 'non-existent', length: 64 }) + ).to.throw(Error, `Unsupported WebCrypto to JOSE conversion: 'non-existent:64'`); + + expect( + () => Jose.webCryptoToJose({ name: 'non-existent', hash: { name: 'SHA-1' } }) + ).to.throw(Error, `Unsupported WebCrypto to JOSE conversion: 'non-existent:SHA-1'`); + }); + }); +}); \ No newline at end of file diff --git a/packages/crypto/tests/utils-new.spec.ts b/packages/crypto/tests/utils-new.spec.ts deleted file mode 100644 index 7d727c3a6..000000000 --- a/packages/crypto/tests/utils-new.spec.ts +++ /dev/null @@ -1,60 +0,0 @@ -import { expect } from 'chai'; - -import { checkValidProperty, checkRequiredProperty, randomUuid } from '../src/utils-new.js'; - -describe('Crypto Utils', () => { - describe('checkValidProperty()', () => { - it('should not throw for a property in the allowed list', () => { - expect(() => checkValidProperty({ property: 'foo', allowedProperties: ['foo', 'bar']})).to.not.throw(); - expect(() => checkValidProperty({ property: 'foo', allowedProperties: new Set(['foo', 'bar'])})).to.not.throw(); - expect(() => checkValidProperty({ property: 'foo', allowedProperties: new Map([['foo', 1], ['bar', 2]])})).to.not.throw(); - }); - - it('throws an error if required arguments are missing', () => { - expect(() => checkValidProperty({ property: 'foo' } as any)).to.throw(TypeError, 'required arguments missing'); - expect(() => checkValidProperty({ allowedProperties: ['foo', 'bar'] } as any)).to.throw(TypeError, 'required arguments missing'); - // @ts-expect-error because both arguments are intentionally omitted. - expect(() => checkValidProperty()).to.throw(TypeError, 'required arguments missing'); - }); - - it('throws an error if the property does not exist', () => { - expect(() => checkValidProperty({ property: 'baz', allowedProperties: ['foo', 'bar']})).to.throw(TypeError, 'Out of range'); - expect(() => checkValidProperty({ property: 'baz', allowedProperties: new Set(['foo', 'bar'])})).to.throw(TypeError, 'Out of range'); - expect(() => checkValidProperty({ property: 'baz', allowedProperties: new Map([['foo', 1], ['bar', 2]])})).to.throw(TypeError, 'Out of range'); - }); - - }); - - describe('checkRequiredProperty', () => { - it('throws an error if required arguments are missing', () => { - // @ts-expect-error because second argument is intentionally omitted. - expect(() => checkRequiredProperty({ property: 'foo' })).to.throw('required arguments missing'); - // @ts-expect-error because both arguments are intentionally omitted. - expect(() => checkRequiredProperty()).to.throw('required arguments missing'); - }); - - it('throws an error if the property is missing', () => { - const propertiesCollection = { foo: 'bar', baz: 'qux' }; - expect(() => checkRequiredProperty({ property: 'quux', inObject: propertiesCollection })).to.throw('Required parameter was missing'); - }); - - it('does not throw an error if the property is present', () => { - const propertiesCollection = { foo: 'bar', baz: 'qux' }; - expect(() => checkRequiredProperty({ property: 'foo', inObject: propertiesCollection })).to.not.throw(); - }); - }); - - describe('randomUuid()', () => { - it('should generate a valid v4 UUID', () => { - const id = randomUuid(); - expect(id).to.match(/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/); - expect(id).to.have.length(36); - }); - - it('should generate different UUIDs', () => { - const id1 = randomUuid(); - const id2 = randomUuid(); - expect(id1).to.not.equal(id2); - }); - }); -}); \ No newline at end of file diff --git a/packages/crypto/tests/utils.spec.ts b/packages/crypto/tests/utils.spec.ts new file mode 100644 index 000000000..d36bc472d --- /dev/null +++ b/packages/crypto/tests/utils.spec.ts @@ -0,0 +1,187 @@ +import { expect } from 'chai'; + +import { CryptoKey } from '../src/algorithms-api/crypto-key.js'; +import { checkValidProperty, checkRequiredProperty, isCryptoKeyPair, randomUuid, multibaseIdToKey, keyToMultibaseId } from '../src/utils.js'; + +describe('Crypto Utils', () => { + describe('checkValidProperty()', () => { + it('should not throw for a property in the allowed list', () => { + expect(() => checkValidProperty({ property: 'foo', allowedProperties: ['foo', 'bar']})).to.not.throw(); + expect(() => checkValidProperty({ property: 'foo', allowedProperties: new Set(['foo', 'bar'])})).to.not.throw(); + expect(() => checkValidProperty({ property: 'foo', allowedProperties: new Map([['foo', 1], ['bar', 2]])})).to.not.throw(); + }); + + it('throws an error if required parameters are missing', () => { + expect(() => checkValidProperty({ property: 'foo' } as any)).to.throw(TypeError, 'required parameters missing'); + expect(() => checkValidProperty({ allowedProperties: ['foo', 'bar'] } as any)).to.throw(TypeError, 'required parameters missing'); + // @ts-expect-error because both arguments are intentionally omitted. + expect(() => checkValidProperty()).to.throw(TypeError, 'required parameters missing'); + }); + + it('throws an error if the property does not exist', () => { + expect(() => checkValidProperty({ property: 'baz', allowedProperties: ['foo', 'bar']})).to.throw(TypeError, 'Out of range'); + expect(() => checkValidProperty({ property: 'baz', allowedProperties: new Set(['foo', 'bar'])})).to.throw(TypeError, 'Out of range'); + expect(() => checkValidProperty({ property: 'baz', allowedProperties: new Map([['foo', 1], ['bar', 2]])})).to.throw(TypeError, 'Out of range'); + }); + + }); + + describe('checkRequiredProperty', () => { + it('throws an error if required parameters are missing', () => { + // @ts-expect-error because second argument is intentionally omitted. + expect(() => checkRequiredProperty({ property: 'foo' })).to.throw('required parameters missing'); + // @ts-expect-error because both arguments are intentionally omitted. + expect(() => checkRequiredProperty()).to.throw('required parameters missing'); + }); + + it('throws an error if the property is missing', () => { + const propertiesCollection = { foo: 'bar', baz: 'qux' }; + expect(() => checkRequiredProperty({ property: 'quux', inObject: propertiesCollection })).to.throw('Required parameter missing'); + }); + + it('does not throw an error if the property is present', () => { + const propertiesCollection = { foo: 'bar', baz: 'qux' }; + expect(() => checkRequiredProperty({ property: 'foo', inObject: propertiesCollection })).to.not.throw(); + }); + }); + + describe('isCryptoKeyPair()', () => { + it('returns true with a CryptoKeyPair object', () => { + const publicKey = new CryptoKey( + { name: 'EdDSA', namedCurve: 'Ed25519' }, + true, + new Uint8Array(32), + 'public', + ['verify'] + ); + const privateKey = new CryptoKey( + { name: 'EdDSA', namedCurve: 'Ed25519' }, + true, + new Uint8Array(32), + 'private', + ['sign'] + ); + const validCryptoKeyPair = { publicKey, privateKey }; + + const result = isCryptoKeyPair(validCryptoKeyPair); + expect(result).to.be.true; + }); + + it('returns false for a CryptoKey', () => { + const cryptoKey = new CryptoKey( + { name: 'EdDSA', namedCurve: 'Ed25519' }, + true, + new Uint8Array(32), + 'secret', + ['decrypt', 'encrypt'] + ); + + const result = isCryptoKeyPair(cryptoKey); + expect(result).to.be.false; + }); + }); + + describe('keyToMultibaseId()', () => { + it('returns a multibase encoded string', () => { + const input = { + key : new Uint8Array(32), + multicodecName : 'ed25519-pub', + }; + const encoded = keyToMultibaseId({ key: input.key, multicodecName: input.multicodecName }); + expect(encoded).to.be.a.string; + expect(encoded.substring(0, 1)).to.equal('z'); + expect(encoded.substring(1, 4)).to.equal('6Mk'); + }); + + it('passes test vectors', () => { + let input: { key: Uint8Array, multicodecName: string }; + let output: string; + let encoded: string; + + // Test Vector 1. + input = { + key : (new Uint8Array(32)).fill(0), + multicodecName : 'ed25519-pub', + }; + output = 'z6MkeTG3bFFSLYVU7VqhgZxqr6YzpaGrQtFMh1uvqGy1vDnP'; + encoded = keyToMultibaseId({ key: input.key, multicodecName: input.multicodecName }); + expect(encoded).to.equal(output); + + // Test Vector 2. + input = { + key : (new Uint8Array(32)).fill(1), + multicodecName : 'ed25519-pub', + }; + output = 'z6MkeXBLjYiSvqnhFb6D7sHm8yKm4jV45wwBFRaatf1cfZ76'; + encoded = keyToMultibaseId({ key: input.key, multicodecName: input.multicodecName }); + expect(encoded).to.equal(output); + + // Test Vector 3. + input = { + key : (new Uint8Array(32)).fill(9), + multicodecName : 'ed25519-pub', + }; + output = 'z6Mkf4XhsxSXfEAWNK6GcFu7TyVs21AfUTRjiguqMhNQeDgk'; + encoded = keyToMultibaseId({ key: input.key, multicodecName: input.multicodecName }); + expect(encoded).to.equal(output); + }); + }); + + describe('multibaseIdToKey()', () => { + it('Converts secp256k1-pub multibase identifiers', () => { + const multibaseKeyId = 'zQ3shMrXA3Ah8h5asMM69USP8qRDnPaCLRV3nPmitAXVfWhgp'; + + const { key, multicodecCode, multicodecName } = multibaseIdToKey({ multibaseKeyId }); + + expect(key).to.exist; + expect(key).to.be.a('Uint8Array'); + expect(key).to.have.length(33); + expect(multicodecCode).to.exist; + expect(multicodecCode).to.equal(231); + expect(multicodecName).to.exist; + expect(multicodecName).to.equal('secp256k1-pub'); + }); + + it('Converts ed25519-pub multibase identifiers', () => { + const multibaseKeyId = 'z6MkizSHspkM891CAnYZis1TJkB4fWwuyVjt4pV93rWPGYwW'; + + const { key, multicodecCode, multicodecName } = multibaseIdToKey({ multibaseKeyId }); + + expect(key).to.exist; + expect(key).to.be.a('Uint8Array'); + expect(key).to.have.length(32); + expect(multicodecCode).to.exist; + expect(multicodecCode).to.equal(237); + expect(multicodecName).to.exist; + expect(multicodecName).to.equal('ed25519-pub'); + }); + + it('Converts x25519-pub multibase identifiers', () => { + const multibaseKeyId = 'z6LSfsF6tQA7j56WSzNPT4yrzZprzGEK8137DMeAVLgGBJEz'; + + const { key, multicodecCode, multicodecName } = multibaseIdToKey({ multibaseKeyId }); + + expect(key).to.exist; + expect(key).to.be.a('Uint8Array'); + expect(key).to.have.length(32); + expect(multicodecCode).to.exist; + expect(multicodecCode).to.equal(236); + expect(multicodecName).to.exist; + expect(multicodecName).to.equal('x25519-pub'); + }); + }); + + describe('randomUuid()', () => { + it('should generate a valid v4 UUID', () => { + const id = randomUuid(); + expect(id).to.match(/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/); + expect(id).to.have.length(36); + }); + + it('should generate different UUIDs', () => { + const id1 = randomUuid(); + const id2 = randomUuid(); + expect(id1).to.not.equal(id2); + }); + }); +}); \ No newline at end of file diff --git a/packages/crypto/tsconfig.cjs.json b/packages/crypto/tsconfig.cjs.json index 0384273d6..7a6f9c0c0 100644 --- a/packages/crypto/tsconfig.cjs.json +++ b/packages/crypto/tsconfig.cjs.json @@ -10,7 +10,8 @@ "outDir": "dist/cjs", "declaration": false, "declarationMap": false, - "declarationDir": null + "declarationDir": null, + "downlevelIteration": true }, "include": [ "src" diff --git a/packages/crypto/tsconfig.json b/packages/crypto/tsconfig.json index ef53536b2..7fec77c5f 100644 --- a/packages/crypto/tsconfig.json +++ b/packages/crypto/tsconfig.json @@ -19,5 +19,8 @@ }, "include": [ "src" + ], + "exclude": [ + "node_modules" ] } \ No newline at end of file diff --git a/packages/dids/.c8rc.json b/packages/dids/.c8rc.json index 1d1670b70..ab680f663 100644 --- a/packages/dids/.c8rc.json +++ b/packages/dids/.c8rc.json @@ -5,12 +5,12 @@ ".js" ], "include": [ - "tests/compiled/**" + "tests/compiled/src/**" ], "exclude": [ - "tests/compiled/src/main.js", + "tests/compiled/src/index.js", "tests/compiled/src/types.js", - "tests/compiled/types/**" + "tests/compiled/src/types/**" ], "reporter": [ "cobertura", diff --git a/packages/dids/build/esbuild-browser-config.cjs b/packages/dids/build/esbuild-browser-config.cjs index a3ddbd9f8..8fd899321 100644 --- a/packages/dids/build/esbuild-browser-config.cjs +++ b/packages/dids/build/esbuild-browser-config.cjs @@ -1,30 +1,13 @@ -/* eslint-disable @typescript-eslint/no-var-requires */ -const polyfillProviderPlugin = require('node-stdlib-browser/helpers/esbuild/plugin'); -const stdLibBrowser = require('node-stdlib-browser'); - -const requiredPolyfills = new Set(['crypto', 'node:crypto', 'stream']); - -// populate object containing lib -> polyfill path -const polyfills = {}; -for (let lib in stdLibBrowser) { - if (requiredPolyfills.has(lib)) { - const polyfill = stdLibBrowser[lib]; - polyfills[lib] = polyfill; - } -} - /** @type {import('esbuild').BuildOptions} */ module.exports = { - entryPoints : ['./src/main.ts'], + entryPoints : ['./src/index.ts'], bundle : true, format : 'esm', sourcemap : true, minify : true, platform : 'browser', target : ['chrome101', 'firefox108', 'safari16'], - inject : [require.resolve('node-stdlib-browser/helpers/esbuild/shim')], - plugins : [polyfillProviderPlugin(polyfills)], define : { 'global': 'globalThis', }, -}; +}; \ No newline at end of file diff --git a/packages/dids/package.json b/packages/dids/package.json index 7f069ebc9..971d87b55 100644 --- a/packages/dids/package.json +++ b/packages/dids/package.json @@ -1,11 +1,11 @@ { - "name": "@tbd54566975/dids", - "version": "0.8.0", + "name": "@web5/dids", + "version": "0.1.9", "description": "TBD DIDs library", "type": "module", - "main": "./dist/cjs/main.js", - "module": "./dist/esm/main.js", - "types": "./dist/types/main.d.ts", + "main": "./dist/cjs/index.js", + "module": "./dist/esm/index.js", + "types": "./dist/types/index.d.ts", "scripts": { "clean": "rimraf dist coverage tests/compiled", "build:esm": "rimraf dist/esm dist/types && npx tsc -p tsconfig.json", @@ -46,12 +46,12 @@ ], "exports": { ".": { - "import": "./dist/esm/main.js", - "require": "./dist/cjs/main.js", - "types": "./dist/types/main.d.ts" + "import": "./dist/esm/index.js", + "require": "./dist/cjs/index.js", + "types": "./dist/types/index.d.ts" } }, - "react-native": "./dist/esm/main.js", + "react-native": "./dist/esm/index.js", "keywords": [ "decentralized", "decentralized-identity", @@ -69,13 +69,15 @@ "node": ">=18.0.0" }, "dependencies": { - "@decentralized-identity/ion-tools": "1.1.4", - "@tbd54566975/common": "0.8.0", - "@tbd54566975/crypto": "0.8.0", - "@tbd54566975/dwn-sdk-js": "0.2.1", - "cross-fetch": "4.0.0" + "@decentralized-identity/ion-pow-sdk": "1.0.17", + "@decentralized-identity/ion-sdk": "1.0.1", + "@web5/common": "0.1.1", + "@web5/crypto": "0.1.6", + "canonicalize": "2.0.0", + "did-resolver": "4.1.0" }, "devDependencies": { + "@playwright/test": "1.36.2", "@types/chai": "4.3.0", "@types/chai-as-promised": "7.1.5", "@types/eslint": "8.37.0", @@ -98,11 +100,10 @@ "karma-mocha-reporter": "2.2.5", "karma-webkit-launcher": "2.1.0", "mocha": "10.2.0", - "node-stdlib-browser": "1.2.0", "playwright": "1.36.2", "rimraf": "4.4.0", "sinon": "15.0.2", "source-map-loader": "4.0.1", "typescript": "5.1.6" } -} \ No newline at end of file +} diff --git a/packages/dids/src/did-ion.old b/packages/dids/src/did-ion.old new file mode 100644 index 000000000..06352097a --- /dev/null +++ b/packages/dids/src/did-ion.old @@ -0,0 +1,187 @@ +import type { PublicKeyJwk, PrivateKeyJwk } from '@tbd54566975/crypto'; +import type { DidResolutionResult, DidMethodResolver, DidMethodCreator, DidState, DwnServiceEndpoint, DidDocument } from './types.js'; + +import { DID, generateKeyPair } from '@decentralized-identity/ion-tools'; + +export type DidIonCreateOptions = { + keys?: KeyOption[]; + services?: ServiceOption[]; +}; + +export type ServiceOption = { + id: string; + type: string; + serviceEndpoint: string | DwnServiceEndpoint; +} + +export type KeyOption = { + id: string; + type: string; + keyPair: { + publicJwk: PublicKeyJwk; + privateJwk: PrivateKeyJwk; + }, + purposes: string[]; +} + +export class DidIonApi implements DidMethodResolver, DidMethodCreator { + /** + * @param resolutionEndpoint optional custom URL to send DID resolution request to + */ + constructor (private resolutionEndpoint: string = 'https://discover.did.msidentity.com/1.0/identifiers/') {} + + get methodName() { + return 'ion'; + } + + // TODO: discuss. need to normalize what's returned from `create`. DidIon.create and DidKey.create return different things. + async create(options: DidIonCreateOptions = {}): Promise { + options.keys ||= [ + { + id : 'dwn', + type : 'JsonWebKey2020', + keyPair : await generateKeyPair(), + purposes : ['authentication'], + }, + ]; + + const didOptions: any = { publicKeys: [] }; + if (options.services) { + didOptions.services = options.services; + } + + for (let key of options.keys) { + const publicKey: any = { ...key }; + + publicKey.publicKeyJwk = key.keyPair.publicJwk; + delete publicKey.keyPair; + + didOptions.publicKeys.push(publicKey); + } + + const did = new DID({ content: didOptions }); + const didState = { + id : await did.getURI(), + internalId : await did.getURI('short'), + methodData : await did.getAllOperations(), + }; + + // TODO: Migrate this to a utility function that generates a DID document given DidState. + // TODO: Add tests to DID Document generation function to ensure that it produces results identical to DidResolver. + // TODO: Ensure both DID ION and KEY do this consistently. + const didDocument: DidDocument = { + '@context' : 'https://www.w3.org/ns/did/v1', + id : didState.id, + verificationMethod : [], + }; + + for (let key of didState.methodData[0].content.publicKeys) { + const verificationMethod = { + id : `#${key.id}`, + controller : didState.id, + type : key.type, + publicKeyJwk : key.publicKeyJwk + }; + didDocument.verificationMethod.push(verificationMethod); + + for (let purpose of key.purposes) { + if (didDocument[purpose]) { + didDocument[purpose].push(key.id); + } else { + didDocument[purpose] = [`#${key.id}`]; + } + } + } + + for (let service of didState.methodData[0]?.content?.services || []) { + const serviceEntry = { + id : `#${service.id}`, + type : service.type, + serviceEndpoint : { ...service.serviceEndpoint } + }; + if (didDocument.service) { + didDocument.service.push(serviceEntry); + } else { + didDocument.service = [serviceEntry]; + } + } + + const keys = []; + for (let keyOption of options.keys) { + const key = { + id : `${didState.id}#${keyOption.id}`, + type : keyOption.type, + controller : didState.id, + publicKeyJwk : keyOption.keyPair.publicJwk, + privateKeyJwk : keyOption.keyPair.privateJwk + }; + + keys.push(key); + } + + return { + id : didState.id, + internalId : didState.internalId, + didDocument : didDocument, + methodData : didState.methodData, + keys : keys // TODO: Remove keys once KeyManager/KeyStore implemented since everything BUT privateKeyJwk is already in the returned didDocument. + }; + } + + async resolve(did: string): Promise { + // TODO: Support resolutionOptions as defined in https://www.w3.org/TR/did-core/#did-resolution + // using `URL` constructor to handle both existence and absence of trailing slash '/' in resolution endpoint + // appending './' to DID so 'did' in 'did:ion:abc' doesn't get interpreted as a URL scheme (e.g. like 'http') due to the colon + // TODO: Add tests to ensure that the scenarios this contemplated are checked. + const resolutionUrl = new URL('./' + did, this.resolutionEndpoint).toString(); + const response = await fetch(resolutionUrl); + + // TODO: Replace with check of resonse.ok to catch other 2XX codes. + if (response.status !== 200) { + throw new Error(`unable to resolve ${did}, got http status ${response.status}`); + } + + const didResolutionResult = await response.json(); + return didResolutionResult; + } + + /** + * Generates two key pairs used for authorization and encryption purposes + * when interfacing with DWNs. The IDs of these keys are referenced in the + * service object that includes the dwnUrls provided. + */ + async generateDwnConfiguration(dwnUrls: string[]): Promise { + return DidIonApi.generateDwnConfiguration(dwnUrls); + } + + /** + * Generates two key pairs used for authorization and encryption purposes + * when interfacing with DWNs. The IDs of these keys are referenced in the + * service object that includes the dwnUrls provided. + */ + static async generateDwnConfiguration(dwnUrls: string[]): Promise { + const keys = [{ + id : 'authz', + type : 'JsonWebKey2020', + keyPair : await generateKeyPair('secp256k1'), + purposes : ['authentication'], + }, { + id : 'enc', + type : 'JsonWebKey2020', + keyPair : await generateKeyPair('secp256k1'), + purposes : ['keyAgreement'], + }]; + + const services = [{ + 'id' : 'dwn', + 'type' : 'DecentralizedWebNode', + 'serviceEndpoint' : { + 'nodes' : dwnUrls, + 'messageAuthorizationKeys' : ['#authz'], + 'recordEncryptionKeys' : ['#enc'] + } + }]; + + return { keys, services }; + } +} \ No newline at end of file diff --git a/packages/dids/src/did-ion.ts b/packages/dids/src/did-ion.ts index 06352097a..604e3c389 100644 --- a/packages/dids/src/did-ion.ts +++ b/packages/dids/src/did-ion.ts @@ -1,187 +1,548 @@ -import type { PublicKeyJwk, PrivateKeyJwk } from '@tbd54566975/crypto'; -import type { DidResolutionResult, DidMethodResolver, DidMethodCreator, DidState, DwnServiceEndpoint, DidDocument } from './types.js'; +import type { JwkKeyPair, PrivateKeyJwk, PublicKeyJwk, Web5Crypto } from '@web5/crypto'; +import type { IonDocumentModel, IonPublicKeyModel, JwkEd25519, JwkEs256k } from '@decentralized-identity/ion-sdk'; -import { DID, generateKeyPair } from '@decentralized-identity/ion-tools'; +import { Convert, universalTypeOf } from '@web5/common'; +import IonProofOfWork from '@decentralized-identity/ion-pow-sdk'; +// import { IonProofOfWork } from '@decentralized-identity/ion-pow-sdk'; +import { EcdsaAlgorithm, EdDsaAlgorithm, Jose } from '@web5/crypto'; +import { IonDid, IonPublicKeyPurpose, IonRequest } from '@decentralized-identity/ion-sdk'; + +import type { DidDocument, DidKeySetVerificationMethodKey, DidMethod, DidResolutionOptions, DidResolutionResult, DidService, PortableDid } from './types.js'; + +import { getServices, isDwnServiceEndpoint, parseDid } from './utils.js'; + +export type DidIonAnchorOptions = { + challengeEnabled?: boolean; + challengeEndpoint?: string; + operationsEndpoint?: string; + keySet: DidIonKeySet; + services: DidService[]; +} export type DidIonCreateOptions = { - keys?: KeyOption[]; - services?: ServiceOption[]; -}; + anchor?: boolean; + keyAlgorithm?: typeof SupportedCryptoAlgorithms[number]; + keySet?: DidIonKeySet; + services?: DidService[]; +} -export type ServiceOption = { - id: string; - type: string; - serviceEndpoint: string | DwnServiceEndpoint; +export type DidIonKeySet = { + recoveryKey?: JwkKeyPair; + updateKey?: JwkKeyPair; + verificationMethodKeys?: DidKeySetVerificationMethodKey[]; } -export type KeyOption = { - id: string; - type: string; - keyPair: { - publicJwk: PublicKeyJwk; - privateJwk: PrivateKeyJwk; - }, - purposes: string[]; +enum OperationType { + Create = 'create', + Update = 'update', + Deactivate = 'deactivate', + Recover = 'recover' } -export class DidIonApi implements DidMethodResolver, DidMethodCreator { +/** + * Data model representing a public key in the DID Document. + */ +export interface IonCreateRequestModel { + type: OperationType; + suffixData: { + deltaHash: string; + recoveryCommitment: string; + }; + delta: { + updateCommitment: string; + patches: { + action: string; + document: IonDocumentModel; + }[]; + } +} + +const SupportedCryptoAlgorithms = [ + 'Ed25519', + 'secp256k1' +] as const; + +const VerificationRelationshipToIonPublicKeyPurpose = { + assertionMethod : IonPublicKeyPurpose.AssertionMethod, + authentication : IonPublicKeyPurpose.Authentication, + capabilityDelegation : IonPublicKeyPurpose.CapabilityDelegation, + capabilityInvocation : IonPublicKeyPurpose.CapabilityInvocation, + keyAgreement : IonPublicKeyPurpose.KeyAgreement +}; + +export class DidIonMethod implements DidMethod { /** - * @param resolutionEndpoint optional custom URL to send DID resolution request to - */ - constructor (private resolutionEndpoint: string = 'https://discover.did.msidentity.com/1.0/identifiers/') {} + * Name of the DID method + */ + public static methodName = 'ion'; + + public static async anchor(options: { + services: DidService[], + keySet: DidIonKeySet, + challengeEnabled?: boolean, + challengeEndpoint?: string, + operationsEndpoint?: string + }): Promise { + const { + challengeEnabled = true, + challengeEndpoint = 'https://beta.ion.msidentity.com/api/v1.0/proof-of-work-challenge', + keySet, + services, + operationsEndpoint = 'https://beta.ion.msidentity.com/api/v1.0/operations' + } = options; + + // Create ION Document. + const ionDocument = await DidIonMethod.createIonDocument({ + keySet: keySet, + services + }); - get methodName() { - return 'ion'; + const createRequest = await DidIonMethod.getIonCreateRequest({ + ionDocument, + recoveryPublicKeyJwk : keySet.recoveryKey.publicKeyJwk, + updatePublicKeyJwk : keySet.updateKey.publicKeyJwk + }); + + let resolutionResult: DidResolutionResult; + + if (challengeEnabled) { + const response = await IonProofOfWork.submitIonRequest( + challengeEndpoint, + operationsEndpoint, + JSON.stringify(createRequest) + ); + + if (response !== undefined && universalTypeOf(response) === 'String') { + resolutionResult = JSON.parse(response); + } + + } else { + const response = await fetch(operationsEndpoint, { + method : 'POST', + mode : 'cors', + body : JSON.stringify(createRequest), + headers : { + 'Content-Type': 'application/json' + } + }); + + if (response.ok) { + resolutionResult = await response.json(); + } + } + + return resolutionResult; } - // TODO: discuss. need to normalize what's returned from `create`. DidIon.create and DidKey.create return different things. - async create(options: DidIonCreateOptions = {}): Promise { - options.keys ||= [ - { - id : 'dwn', - type : 'JsonWebKey2020', - keyPair : await generateKeyPair(), - purposes : ['authentication'], - }, - ]; + public static async create(options?: DidIonCreateOptions): Promise { + let { anchor, keyAlgorithm, keySet, services } = options ?? { }; + + // Begin constructing a PortableDid. + const did: Partial = {}; + + // If any member of the key set is missing, generate the keys. + did.keySet = await DidIonMethod.generateKeySet({ keyAlgorithm, keySet }); + + // Generate Long Form DID URI. + did.did = await DidIonMethod.getLongFormDid({ + keySet: did.keySet, + services + }); + + // Get short form DID. + did.canonicalId = await DidIonMethod.getShortFormDid({ didUrl: did.did }); + + let didResolutionResult: DidResolutionResult | undefined; + if (anchor) { + // Attempt to anchor the DID. + didResolutionResult = await DidIonMethod.anchor({ + keySet: did.keySet, + services + }); - const didOptions: any = { publicKeys: [] }; - if (options.services) { - didOptions.services = options.services; + } else { + // If anchoring was not requested, then resolve the long form DID. + didResolutionResult = await DidIonMethod.resolve({ didUrl: did.did }); } - for (let key of options.keys) { - const publicKey: any = { ...key }; + // Store the DID Document. + did.document = didResolutionResult.didDocument; - publicKey.publicKeyJwk = key.keyPair.publicJwk; - delete publicKey.keyPair; + return did as PortableDid; + } + + public static async decodeLongFormDid(options: { + didUrl: string + }): Promise { + const { didUrl } = options; - didOptions.publicKeys.push(publicKey); + const parsedDid = parseDid({ didUrl }); + + if (!parsedDid) { + throw new Error(`DidIonMethod: Unable to parse DID: ${didUrl}`); } - const did = new DID({ content: didOptions }); - const didState = { - id : await did.getURI(), - internalId : await did.getURI('short'), - methodData : await did.getAllOperations(), - }; + const decodedLongFormDid = Convert.base64Url( + parsedDid.id.split(':').pop() + ).toObject() as Pick; - // TODO: Migrate this to a utility function that generates a DID document given DidState. - // TODO: Add tests to DID Document generation function to ensure that it produces results identical to DidResolver. - // TODO: Ensure both DID ION and KEY do this consistently. - const didDocument: DidDocument = { - '@context' : 'https://www.w3.org/ns/did/v1', - id : didState.id, - verificationMethod : [], + const createRequest: IonCreateRequestModel = { + ...decodedLongFormDid, + type: OperationType.Create }; - for (let key of didState.methodData[0].content.publicKeys) { - const verificationMethod = { - id : `#${key.id}`, - controller : didState.id, - type : key.type, - publicKeyJwk : key.publicKeyJwk - }; - didDocument.verificationMethod.push(verificationMethod); + return createRequest; + } - for (let purpose of key.purposes) { - if (didDocument[purpose]) { - didDocument[purpose].push(key.id); - } else { - didDocument[purpose] = [`#${key.id}`]; - } - } + public static async generateKeySet(options?: { + keyAlgorithm?: typeof SupportedCryptoAlgorithms[number], + keySet?: DidIonKeySet + }): Promise { + // Generate Ed25519 authentication key pair, by default. + let { keyAlgorithm = 'Ed25519', keySet = {} } = options ?? {}; + + // If keySet lacks verification method keys, generate one. + if (keySet.verificationMethodKeys === undefined) { + const authenticationkeyPair = await DidIonMethod.generateJwkKeyPair({ + keyAlgorithm, + keyId: 'dwn-sig' + }); + keySet.verificationMethodKeys = [{ + ...authenticationkeyPair, + relationships: ['authentication'] + }]; + } + + // If keySet lacks recovery key, generate one. + if (keySet.recoveryKey === undefined) { + // Note: ION/Sidetree only supports secp256k1 recovery keys. + keySet.recoveryKey = await DidIonMethod.generateJwkKeyPair({ + keyAlgorithm : 'secp256k1', + keyId : 'ion-recovery-1' + }); + } + + // If keySet lacks update key, generate one. + if (keySet.updateKey === undefined) { + // Note: ION/Sidetree only supports secp256k1 update keys. + keySet.updateKey = await DidIonMethod.generateJwkKeyPair({ + keyAlgorithm : 'secp256k1', + keyId : 'ion-update-1' + }); + } + + // Generate RFC 7638 JWK thumbprints if `kid` is missing from any key. + for (const key of [...keySet.verificationMethodKeys, keySet.recoveryKey, keySet.updateKey]) { + if ('publicKeyJwk' in key) key.publicKeyJwk.kid ??= await Jose.jwkThumbprint({ key: key.publicKeyJwk }); + if ('privateKeyJwk' in key) key.privateKeyJwk.kid ??= await Jose.jwkThumbprint({ key: key.privateKeyJwk }); + } + + return keySet; + } + + /** + * Given the W3C DID Document of a `did:ion` DID, return the identifier of + * the verification method key that will be used for signing messages and + * credentials, by default. + * + * @param document = DID Document to get the default signing key from. + * @returns Verification method identifier for the default signing key. + */ + public static async getDefaultSigningKey(options: { + didDocument: DidDocument + }): Promise { + const { didDocument } = options; + + if (!didDocument.id) { + throw new Error(`DidIonMethod: DID document is missing 'id' property`); + } + + /** If the DID document contains a DWN service endpoint in the expected + * format, return the first entry in the `signingKeys` array. */ + const [dwnService] = getServices({ didDocument, type: 'DecentralizedWebNode' }); + if (isDwnServiceEndpoint(dwnService?.serviceEndpoint)) { + const [verificationMethodId] = dwnService.serviceEndpoint.signingKeys; + // const did = await DidIonMethod.getShortFormDid({ didUrl: didDocument.id }); + const did = didDocument.id; + const signingKeyId = `${did}${verificationMethodId}`; + return signingKeyId; + } + + /** Otherwise, fallback to a naive approach of returning the first key ID + * in the `authentication` verification relationships array. */ + if (didDocument.authentication + && Array.isArray(didDocument.authentication) + && didDocument.authentication.length > 0 + && typeof didDocument.authentication[0] === 'string') { + const [verificationMethodId] = didDocument.authentication; + // const did = await DidIonMethod.getShortFormDid({ didUrl: didDocument.id }); + const did = didDocument.id; + const signingKeyId = `${did}${verificationMethodId}`; + return signingKeyId; + } + + // if (didDocument.id && + // didDocument.service && + // didDocument.service[0] && + // didDocument.service[0].serviceEndpoint && + // typeof didDocument.service[0].serviceEndpoint !== 'string' && + // !Array.isArray(didDocument.service[0].serviceEndpoint) && + // didDocument.service[0].serviceEndpoint.signingKeys && + // typeof didDocument.service[0].serviceEndpoint.signingKeys[0] === 'string') { + + // const verificationMethodId = didDocument.service[0].serviceEndpoint.signingKeys[0]; + // const did = await DidIonMethod.getShortFormDid({ didUrl: didDocument.id }); + // const signingKeyId = `${did}${verificationMethodId}`; + + // return signingKeyId; + // } + } + + public static async getLongFormDid(options: { + services: DidService[], + keySet: DidIonKeySet + }): Promise { + const { services = [], keySet } = options; + + // Create ION Document. + const ionDocument = await DidIonMethod.createIonDocument({ + keySet: keySet, + services + }); + + // Filter JWK to only those properties expected by ION/Sidetree. + const recoveryKey = DidIonMethod.jwkToIonJwk({ key: keySet.recoveryKey.publicKeyJwk }) as JwkEs256k; + const updateKey = DidIonMethod.jwkToIonJwk({ key: keySet.updateKey.publicKeyJwk }) as JwkEs256k; + + // Create an ION DID create request operation. + const did = await IonDid.createLongFormDid({ + document: ionDocument, + recoveryKey, + updateKey + }); + + return did; + } + + public static async getShortFormDid(options: { + didUrl: string + }): Promise { + const { didUrl } = options; + + const parsedDid = parseDid({ didUrl }); + + if (!parsedDid) { + throw new Error(`DidIonMethod: Unable to parse DID: ${didUrl}`); } - for (let service of didState.methodData[0]?.content?.services || []) { - const serviceEntry = { - id : `#${service.id}`, - type : service.type, - serviceEndpoint : { ...service.serviceEndpoint } + const shortFormDid = parsedDid.did.split(':', 3).join(':'); + + return shortFormDid; + } + + public static async resolve(options: { + didUrl: string, + resolutionOptions?: DidResolutionOptions + }): Promise { + // TODO: Implement resolutionOptions as defined in https://www.w3.org/TR/did-core/#did-resolution + const { didUrl, resolutionOptions = {} } = options; + + const parsedDid = parseDid({ didUrl }); + if (!parsedDid) { + return { + '@context' : 'https://w3id.org/did-resolution/v1', + didDocument : undefined, + didDocumentMetadata : {}, + didResolutionMetadata : { + contentType : 'application/did+ld+json', + error : 'invalidDid', + errorMessage : `Cannot parse DID: ${didUrl}` + } }; - if (didDocument.service) { - didDocument.service.push(serviceEntry); - } else { - didDocument.service = [serviceEntry]; - } } - const keys = []; - for (let keyOption of options.keys) { - const key = { - id : `${didState.id}#${keyOption.id}`, - type : keyOption.type, - controller : didState.id, - publicKeyJwk : keyOption.keyPair.publicJwk, - privateKeyJwk : keyOption.keyPair.privateJwk + if (parsedDid.method !== 'ion') { + return { + '@context' : 'https://w3id.org/did-resolution/v1', + didDocument : undefined, + didDocumentMetadata : {}, + didResolutionMetadata : { + contentType : 'application/did+ld+json', + error : 'methodNotSupported', + errorMessage : `Method not supported: ${parsedDid.method}` + } }; + } + + const { resolutionEndpoint = 'https://discover.did.msidentity.com/1.0/identifiers/' } = resolutionOptions; + const normalizeUrl = (url: string): string => url.endsWith('/') ? url : url + '/'; + const resolutionUrl = `${normalizeUrl(resolutionEndpoint)}${parsedDid.did}`; + + const response = await fetch(resolutionUrl); + + let resolutionResult: DidResolutionResult | object; + try { + resolutionResult = await response.json(); + } catch (error) { + resolutionResult = {}; + } - keys.push(key); + if (response.ok) { + return resolutionResult as DidResolutionResult; + } + + // Return valid DID Resolution Results. + if ('didResolutionMetadata' in resolutionResult) { + return resolutionResult; + } + + // Set default error code and message. + let error = 'internalError'; + let errorMessage = `DID resolver responded with HTTP status code: ${response.status}`; + + /** The Microsoft resolution endpoint does not return a valid DidResolutionResult + * when an ION DID is "not found" so normalization is needed. */ + if ('error' in resolutionResult && + typeof resolutionResult.error === 'object' && + 'code' in resolutionResult.error && + typeof resolutionResult.error.code === 'string' && + 'message' in resolutionResult.error && + typeof resolutionResult.error.message === 'string') { + error = resolutionResult.error.code.includes('not_found') ? 'notFound' : error; + errorMessage = resolutionResult.error.message ?? errorMessage; } return { - id : didState.id, - internalId : didState.internalId, - didDocument : didDocument, - methodData : didState.methodData, - keys : keys // TODO: Remove keys once KeyManager/KeyStore implemented since everything BUT privateKeyJwk is already in the returned didDocument. + '@context' : 'https://w3id.org/did-resolution/v1', + didDocument : undefined, + didDocumentMetadata : {}, + didResolutionMetadata : { + contentType: 'application/did+ld+json', + error, + errorMessage + } }; } - async resolve(did: string): Promise { - // TODO: Support resolutionOptions as defined in https://www.w3.org/TR/did-core/#did-resolution - // using `URL` constructor to handle both existence and absence of trailing slash '/' in resolution endpoint - // appending './' to DID so 'did' in 'did:ion:abc' doesn't get interpreted as a URL scheme (e.g. like 'http') due to the colon - // TODO: Add tests to ensure that the scenarios this contemplated are checked. - const resolutionUrl = new URL('./' + did, this.resolutionEndpoint).toString(); - const response = await fetch(resolutionUrl); + private static async generateJwkKeyPair(options: { + keyAlgorithm: typeof SupportedCryptoAlgorithms[number], + keyId?: string + }): Promise { + const { keyAlgorithm, keyId } = options; - // TODO: Replace with check of resonse.ok to catch other 2XX codes. - if (response.status !== 200) { - throw new Error(`unable to resolve ${did}, got http status ${response.status}`); + let cryptoKeyPair: Web5Crypto.CryptoKeyPair; + + switch (keyAlgorithm) { + case 'Ed25519': { + cryptoKeyPair = await new EdDsaAlgorithm().generateKey({ + algorithm : { name: 'EdDSA', namedCurve: 'Ed25519' }, + extractable : true, + keyUsages : ['sign', 'verify'] + }); + break; + } + + case 'secp256k1': { + cryptoKeyPair = await new EcdsaAlgorithm().generateKey({ + algorithm : { name: 'ECDSA', namedCurve: 'secp256k1' }, + extractable : true, + keyUsages : ['sign', 'verify'] + }); + break; + } + + default: { + throw new Error(`Unsupported crypto algorithm: '${keyAlgorithm}'`); + } } - const didResolutionResult = await response.json(); - return didResolutionResult; + // Convert the CryptoKeyPair to JwkKeyPair. + const jwkKeyPair = await Jose.cryptoKeyToJwkPair({ keyPair: cryptoKeyPair }); + + // Set kid values. + if (keyId) { + jwkKeyPair.privateKeyJwk.kid = keyId; + jwkKeyPair.publicKeyJwk.kid = keyId; + } else { + // If a key ID is not specified, generate RFC 7638 JWK thumbprint. + const jwkThumbprint = await Jose.jwkThumbprint({ key: jwkKeyPair.publicKeyJwk }); + jwkKeyPair.privateKeyJwk.kid = jwkThumbprint; + jwkKeyPair.publicKeyJwk.kid = jwkThumbprint; + } + + return jwkKeyPair; } - /** - * Generates two key pairs used for authorization and encryption purposes - * when interfacing with DWNs. The IDs of these keys are referenced in the - * service object that includes the dwnUrls provided. - */ - async generateDwnConfiguration(dwnUrls: string[]): Promise { - return DidIonApi.generateDwnConfiguration(dwnUrls); + private static async createIonDocument(options: { + keySet: DidIonKeySet, + services?: DidService[] + }): Promise { + const { services = [], keySet } = options; + + const ionPublicKeys: IonPublicKeyModel[] = []; + + for (const key of keySet.verificationMethodKeys) { + // Map W3C DID verification relationship names to ION public key purposes. + const ionPurposes: IonPublicKeyPurpose[] = []; + for (const relationship of key.relationships) { + ionPurposes.push( + VerificationRelationshipToIonPublicKeyPurpose[relationship] + ); + } + + // Convert public key JWK to ION format. + const publicKey: IonPublicKeyModel = { + id : key.publicKeyJwk.kid, + publicKeyJwk : DidIonMethod.jwkToIonJwk({ key: key.publicKeyJwk }), + purposes : ionPurposes, + type : 'JsonWebKey2020' + }; + + ionPublicKeys.push(publicKey); + } + + const ionDocumentModel: IonDocumentModel = { + publicKeys: ionPublicKeys, + services + }; + + return ionDocumentModel; } - /** - * Generates two key pairs used for authorization and encryption purposes - * when interfacing with DWNs. The IDs of these keys are referenced in the - * service object that includes the dwnUrls provided. - */ - static async generateDwnConfiguration(dwnUrls: string[]): Promise { - const keys = [{ - id : 'authz', - type : 'JsonWebKey2020', - keyPair : await generateKeyPair('secp256k1'), - purposes : ['authentication'], - }, { - id : 'enc', - type : 'JsonWebKey2020', - keyPair : await generateKeyPair('secp256k1'), - purposes : ['keyAgreement'], - }]; - - const services = [{ - 'id' : 'dwn', - 'type' : 'DecentralizedWebNode', - 'serviceEndpoint' : { - 'nodes' : dwnUrls, - 'messageAuthorizationKeys' : ['#authz'], - 'recordEncryptionKeys' : ['#enc'] + private static async getIonCreateRequest(options: { + ionDocument: IonDocumentModel, + recoveryPublicKeyJwk: PublicKeyJwk, + updatePublicKeyJwk: PublicKeyJwk + }): Promise { + const { ionDocument, recoveryPublicKeyJwk, updatePublicKeyJwk } = options; + + // Create an ION DID create request operation. + const createRequest = await IonRequest.createCreateRequest({ + document : ionDocument, + recoveryKey : DidIonMethod.jwkToIonJwk({ key: recoveryPublicKeyJwk }) as JwkEs256k, + updateKey : DidIonMethod.jwkToIonJwk({ key: updatePublicKeyJwk }) as JwkEs256k + }); + + return createRequest; + } + + private static jwkToIonJwk({ key }: { key: PrivateKeyJwk | PublicKeyJwk }): JwkEd25519 | JwkEs256k { + let ionJwk: Partial = { }; + + if ('crv' in key) { + ionJwk.crv = key.crv; + ionJwk.kty = key.kty; + ionJwk.x = key.x; + if ('d' in key) ionJwk.d = key.d; + + if ('y' in key && key.y) { + // secp256k1 JWK. + return { ...ionJwk, y: key.y} as JwkEs256k; } - }]; + // Ed25519 JWK. + return { ...ionJwk } as JwkEd25519; + } - return { keys, services }; + throw new Error(`jwkToIonJwk: Unsupported key algorithm.`); } } \ No newline at end of file diff --git a/packages/dids/src/did-key.ts b/packages/dids/src/did-key.ts index 4668704ef..9b8746b16 100644 --- a/packages/dids/src/did-key.ts +++ b/packages/dids/src/did-key.ts @@ -1,50 +1,784 @@ -import type { DidMethodCreator, DidMethodResolver, DidState } from './types.js'; +import type { PrivateKeyJwk, PublicKeyJwk, Web5Crypto } from '@web5/crypto'; -import { ed25519 } from '@tbd54566975/crypto'; -import { DidKeyResolver } from '@tbd54566975/dwn-sdk-js'; -import { createVerificationMethodWithPrivateKeyJwk, keyToMultibaseId } from './utils.js'; +import { universalTypeOf } from '@web5/common'; +import { + Jose, + Ed25519, + Secp256k1, + EcdsaAlgorithm, + EdDsaAlgorithm, + keyToMultibaseId, + multibaseIdToKey, +} from '@web5/crypto'; -const didKeyResolver = new DidKeyResolver(); +import type { DidDocument, DidMethod, DidResolutionOptions, DidResolutionResult, PortableDid, VerificationMethod } from './types.js'; -export type DidKeyOptions = never; +import { DidKeySetVerificationMethodKey } from './types.js'; +import { getVerificationMethodTypes, parseDid } from './utils.js'; -//! i know dwn-sdk-js has a resolver that includes both creation and resolving. but they're slightly different and we really -//! need to settle on what the normalized result of did creation is. +const SupportedCryptoAlgorithms = [ + 'Ed25519', + 'secp256k1' +] as const; -export class DidKeyApi implements DidMethodResolver, DidMethodCreator { - get methodName() { - return 'key'; +const SupportedPublicKeyFormats = [ + 'Ed25519VerificationKey2020', + 'JsonWebKey2020', + 'X25519KeyAgreementKey2020' +]; + +const VERIFICATION_METHOD_TYPES: Record = { + 'Ed25519VerificationKey2020' : 'https://w3id.org/security/suites/ed25519-2020/v1', + 'JsonWebKey2020' : 'https://w3id.org/security/suites/jws-2020/v1', + 'X25519KeyAgreementKey2020' : 'https://w3id.org/security/suites/x25519-2020/v1', +} as const; + +export type DidVerificationMethodType = keyof typeof VERIFICATION_METHOD_TYPES; + +const MULTICODEC_PUBLIC_KEY_LENGTH: Record = { + // secp256k1-pub - Secp256k1 public key (compressed) - 33 bytes + 0xe7: 33, + + // x25519-pub - Curve25519 public key - 32 bytes + 0xec: 32, + + // ed25519-pub - Ed25519 public key - 32 bytes + 0xed: 32 +}; + +export type DidKeyCreateOptions = { + enableEncryptionKeyDerivation?: boolean; + keyAlgorithm?: typeof SupportedCryptoAlgorithms[number]; + keySet?: DidKeyKeySet; + publicKeyFormat?: DidVerificationMethodType; +} + +export type DidKeyCreateDocumentOptions = { + defaultContext?: string; + did: string; + enableEncryptionKeyDerivation?: boolean; + enableExperimentalPublicKeyTypes?: boolean; + publicKeyFormat?: DidVerificationMethodType; +} + +export type DidKeyDeriveEncryptionKeyResult = { + key: Uint8Array; + multicodecCode: number; +} + +export type DidKeyIdentifier = { + fragment: string; + method: string; + multibaseValue: string; + scheme: string; + version: string; +} + +export type DidKeyKeySet = { + verificationMethodKeys?: DidKeySetVerificationMethodKey[]; +} + +export class DidKeyMethod implements DidMethod { + /** + * Name of the DID method + */ + public static methodName = 'key'; + + public static async create(options?: DidKeyCreateOptions): Promise { + let { + enableEncryptionKeyDerivation = false, + keyAlgorithm, + keySet, + publicKeyFormat = 'JsonWebKey2020' + } = options ?? { }; + + // If keySet not given, generate a default key set. + if (keySet === undefined) { + keySet = await DidKeyMethod.generateKeySet({ keyAlgorithm }); + } + + const portableDid: Partial = {}; + let multibaseId = ''; + + if (keySet.verificationMethodKeys?.[0]?.publicKeyJwk) { + // Compute the multibase identifier based on the JSON Web Key. + const publicKeyJwk = keySet.verificationMethodKeys[0].publicKeyJwk; + multibaseId = await Jose.jwkToMultibaseId({ key: publicKeyJwk }); + } + + if (!multibaseId) { + throw new Error('DidKeyMethod: Failed to create DID with given input.'); + } + + // Concatenate the DID identifier. + portableDid.did = `did:key:${multibaseId}`; + + // Expand the DID identifier to a DID document. + portableDid.document = await DidKeyMethod.createDocument({ + did: portableDid.did, + publicKeyFormat, + enableEncryptionKeyDerivation + }); + + // Return the given or generated key set. + portableDid.keySet = keySet; + + return portableDid as PortableDid; } - async create(_options: any = {}): Promise { - // Generate new sign key pair. - const verificationKeyPair = ed25519.generateKeyPair(); - const keyAgreementKeyPair = ed25519.deriveX25519KeyPair(verificationKeyPair); + /** + * Expands a did:key identifier to a DID Document. + * + * Reference: https://w3c-ccg.github.io/did-method-key/#document-creation-algorithm + * + * @param options + * @returns - A DID dodcument. + */ + public static async createDocument(options: DidKeyCreateDocumentOptions): Promise { + const { + defaultContext = 'https://www.w3.org/ns/did/v1', + did, + enableEncryptionKeyDerivation = false, + enableExperimentalPublicKeyTypes = false, + publicKeyFormat = 'JsonWebKey2020' + } = options; - const verificationKeyId = keyToMultibaseId({ key: verificationKeyPair.publicKey, multicodecName: 'ed25519-pub' }); - const keyAgreementKeyId = keyToMultibaseId({ key: keyAgreementKeyPair.publicKey, multicodecName: 'x25519-pub' }); + /** + * 1. Initialize document to an empty object. + */ + const document: Partial = {}; - const id = `did:key:${verificationKeyId}`; + /** + * 2. Using a colon (:) as the delimiter, split the identifier into its + * components: a scheme, a method, a version, and a multibaseValue. + * If there are only three components set the version to the string + * value 1 and use the last value as the multibaseValue. + * + * Note: The W3C DID specification makes no mention of a version value + * being part of the DID syntax. Additionally, there does not + * appear to be any real-world usage of the version number. + * Consequently, this implementation will ignore the version + * related guidance in the did:key specification. + */ + let multibaseValue: string; + try { + ({ id: multibaseValue } = parseDid({ didUrl: did })); + } catch (error: any) { + throw new Error(`invalidDid: Unknown format: ${did}`); + } - const verificationJwkPair = ed25519.keyPairToJwk(verificationKeyPair, verificationKeyId); - const verificationKey = createVerificationMethodWithPrivateKeyJwk(id, verificationJwkPair); + /** + * 3. Check the validity of the input identifier. + * The scheme MUST be the value did. The method MUST be the value key. + * The version MUST be convertible to a positive integer value. The + * multibaseValue MUST be a string and begin with the letter z. If any + * of these requirements fail, an invalidDid error MUST be raised. + */ + if (!DidKeyMethod.validateIdentifier({ did })) { + throw new Error(`invalidDid: Invalid identifier format: ${did}`); + } - const keyAgreementJwkPair = ed25519.keyPairToJwk(keyAgreementKeyPair, keyAgreementKeyId, { crv: 'X25519' }); - const keyAgreementKey = createVerificationMethodWithPrivateKeyJwk(id, keyAgreementJwkPair); + /** + * 4. Initialize the signatureVerificationMethod to the result of passing + * identifier, multibaseValue, and options to a + * {@link https://w3c-ccg.github.io/did-method-key/#signature-method-creation-algorithm | Signature Method Creation Algorithm}. + */ + const signatureVerificationMethod = await DidKeyMethod.createSignatureMethod({ + did, + enableExperimentalPublicKeyTypes, + multibaseValue, + publicKeyFormat + }); - return { - id, - internalId : id, - // didDocument : {}, //! TODO: Add DidDocument to object returned. - keys : [verificationKey, keyAgreementKey], - methodData : {} + /** + * 5. Set document.id to identifier. If document.id is not a valid DID, + * an invalidDid error MUST be raised. + * + * Note: Identifier was already confirmed to be valid in Step 3, so + * skipping the redundant validation. + */ + document.id = did; + + /** + * 6. Initialize the verificationMethod property in document to an array + * where the first value is the signatureVerificationMethod. + */ + document.verificationMethod = [signatureVerificationMethod]; + + /** + * 7. Initialize the authentication, assertionMethod, capabilityInvocation, + * and the capabilityDelegation properties in document to an array where + * the first item is the value of the id property in + * signatureVerificationMethod. + */ + document.authentication = [signatureVerificationMethod.id]; + document.assertionMethod = [signatureVerificationMethod.id]; + document.capabilityInvocation = [signatureVerificationMethod.id]; + document.capabilityDelegation = [signatureVerificationMethod.id]; + + /** + * 8. If options.enableEncryptionKeyDerivation is set to true: + * Add the encryptionVerificationMethod value to the verificationMethod + * array. Initialize the keyAgreement property in document to an array + * where the first item is the value of the id property in + * encryptionVerificationMethod. + */ + if (enableEncryptionKeyDerivation === true) { + /** + * Although not covered by the did:key method specification, a sensible + * default will be taken to use the 'X25519KeyAgreementKey2020' + * verification method type if the given publicKeyFormat is + * 'Ed25519VerificationKey2020' and 'JsonWebKey2020' otherwise. + */ + const encryptionPublicKeyFormat = + (publicKeyFormat === 'Ed25519VerificationKey2020') + ? 'X25519KeyAgreementKey2020' + : 'JsonWebKey2020'; + + /** + * 8.1 Initialize the encryptionVerificationMethod to the result of + * passing identifier, multibaseValue, and options to an + * {@link https://w3c-ccg.github.io/did-method-key/#encryption-method-creation-algorithm | Encryption Method Creation Algorithm}. + */ + const encryptionVerificationMethod = await this.createEncryptionMethod({ + did, + enableExperimentalPublicKeyTypes, + multibaseValue, + publicKeyFormat: encryptionPublicKeyFormat + }); + + /** + * 8.2 Add the encryptionVerificationMethod value to the + * verificationMethod array. + */ + document.verificationMethod.push(encryptionVerificationMethod); + + /** + * 8.3. Initialize the keyAgreement property in document to an array + * where the first item is the value of the id property in + * encryptionVerificationMethod. + */ + document.keyAgreement = [encryptionVerificationMethod.id]; + } + + /** + * 9. Initialize the @context property in document to the result of passing + * document and options to the Context Creation algorithm. + */ + // Set contextArray to an array that is initialized to + // options.defaultContext. + const contextArray = [defaultContext]; + + // For every object in every verification relationship listed in document, + // add a string value to the contextArray based on the object type value, + // if it doesn't already exist, according to the following table: + // {@link https://w3c-ccg.github.io/did-method-key/#context-creation-algorithm | Context Type URL} + const verificationMethodTypes = getVerificationMethodTypes({ didDocument: document }); + verificationMethodTypes.forEach((typeName: string) => { + const typeUrl = VERIFICATION_METHOD_TYPES[typeName]; + contextArray.push(typeUrl); + }); + document['@context'] = contextArray; + + /** + * 10. Return document. + */ + return document as DidDocument; + } + + /** + * Decoding a multibase-encoded multicodec value into a verification method + * that is suitable for verifying that encrypted information will be + * received by the intended recipient. + */ + public static async createEncryptionMethod(options: { + did: string, + enableExperimentalPublicKeyTypes: boolean, + multibaseValue: string, + publicKeyFormat: DidVerificationMethodType + }): Promise { + const { did, enableExperimentalPublicKeyTypes, multibaseValue, publicKeyFormat } = options; + + /** + * 1. Initialize verificationMethod to an empty object. + */ + const verificationMethod: Partial = {}; + + /** + * 2. Set multicodecValue and rawPublicKeyBytes to the result of passing + * multibaseValue and options to a Derive Encryption Key algorithm. + */ + const { + key: rawPublicKeyBytes, + multicodecCode: multicodecValue, + } = await DidKeyMethod.deriveEncryptionKey({ multibaseValue }); + + /** + * 3. Ensure the proper key length of rawPublicKeyBytes based on the + * multicodecValue table provided below: + * + * Multicodec hexadecimal value: 0xec + * + * If the byte length of rawPublicKeyBytes + * does not match the expected public key length for the associated + * multicodecValue, an invalidPublicKeyLength error MUST be raised. + */ + const actualLength = rawPublicKeyBytes.byteLength; + const expectedLength = MULTICODEC_PUBLIC_KEY_LENGTH[multicodecValue]; + if (actualLength !== expectedLength) { + throw new Error(`invalidPublicKeyLength: Expected ${actualLength} bytes. Actual ${expectedLength} bytes.`); + } + + /** + * 4. Create the multibaseValue by concatenating the letter 'z' and the + * base58-btc encoding of the concatenation of the multicodecValue and + * the rawPublicKeyBytes. + */ + const kemMultibaseValue = keyToMultibaseId({ + key : rawPublicKeyBytes, + multicodecCode : multicodecValue + }); + + /** + * 5. Set the verificationMethod.id value by concatenating identifier, + * a hash character (#), and the multibaseValue. If verificationMethod.id + * is not a valid DID URL, an invalidDidUrl error MUST be raised. + */ + verificationMethod.id = `${did}#${kemMultibaseValue}`; + try { + new URL(verificationMethod.id); + } catch (error: any) { + throw new Error('invalidDidUrl: Verification Method ID is not a valid DID URL.'); + } + + /** + * 6. Set the publicKeyFormat value to the options.publicKeyFormat value. + * 7. If publicKeyFormat is not known to the implementation, an + * unsupportedPublicKeyType error MUST be raised. + */ + if (!(SupportedPublicKeyFormats.includes(publicKeyFormat))) { + throw new Error(`unsupportedPublicKeyType: Unsupported format: ${publicKeyFormat}`); + } + + /** + * 8. If options.enableExperimentalPublicKeyTypes is set to false and + * publicKeyFormat is not Multikey, JsonWebKey2020, or + * X25519KeyAgreementKey2020, an invalidPublicKeyType error MUST be + * raised. + */ + const StandardPublicKeyTypes = ['Multikey', 'JsonWebKey2020', 'X25519KeyAgreementKey2020']; + if (enableExperimentalPublicKeyTypes === false + && !(StandardPublicKeyTypes.includes(publicKeyFormat))) { + throw new Error(`invalidPublicKeyType: Specified '${publicKeyFormat}' without setting enableExperimentalPublicKeyTypes to true.`); + } + + /** + * 9. Set verificationMethod.type to the publicKeyFormat value. + */ + verificationMethod.type = publicKeyFormat; + + /** + * 10. Set verificationMethod.controller to the identifier value. + * If verificationMethod.controller is not a valid DID, an invalidDid + * error MUST be raised. + */ + verificationMethod.controller = did; + if (!DidKeyMethod.validateIdentifier({ did })) { + throw new Error(`invalidDid: Invalid identifier format: ${did}`); + } + + /** + * 11. If publicKeyFormat is Multikey or X25519KeyAgreementKey2020, + * set the verificationMethod.publicKeyMultibase value to multibaseValue. + * + * Note: This implementation does not currently support the Multikey + * format. + */ + if (publicKeyFormat === 'X25519KeyAgreementKey2020') { + verificationMethod.publicKeyMultibase = kemMultibaseValue; + } + + /** + * 12. If publicKeyFormat is JsonWebKey2020, set the + * verificationMethod.publicKeyJwk value to the result of passing + * multicodecValue and rawPublicKeyBytes to a JWK encoding algorithm. + */ + if (publicKeyFormat === 'JsonWebKey2020') { + const jwkParams = await Jose.multicodecToJose({ code: multicodecValue }); + const jsonWebKey = await Jose.keyToJwk({ + keyMaterial : rawPublicKeyBytes, + keyType : 'public', + ...jwkParams + }); + // Ensure that "d" is NOT present. + if ('x' in jsonWebKey && !('d' in jsonWebKey)) { + verificationMethod.publicKeyJwk = jsonWebKey; + } + } + + /** + * 13. Return verificationMethod. + */ + return verificationMethod as VerificationMethod; + } + + /** + * Transform a multibase-encoded multicodec value to public encryption key + * components that are suitable for encrypting messages to a receiver. A + * mathematical proof elaborating on the safety of performing this operation + * is available in: + * {@link https://eprint.iacr.org/2021/509.pdf | On using the same key pair for Ed25519 and an X25519 based KEM} + */ + public static async deriveEncryptionKey(options: { + multibaseValue: string + }): Promise { + const { multibaseValue } = options; + + /** + * 1. Set publicEncryptionKey to an empty object. + */ + let publicEncryptionKey: Partial = {}; + + /** + * 2. Decode multibaseValue using the base58-btc multibase alphabet and + * set multicodecValue to the multicodec header for the decoded value. + * Implementers are cautioned to ensure that the multicodecValue is set + * to the result after performing varint decoding. + * + * 3. Set the rawPublicKeyBytes to the bytes remaining after the multicodec + * header. + */ + const { + key: rawPublicKeyBytes, + multicodecCode: multicodecValue + } = multibaseIdToKey({ multibaseKeyId: multibaseValue }); + + /** + * 4. If the multicodecValue is 0xed, derive a public X25519 encryption key + * by using the rawPublicKeyBytes and the algorithm defined in + * {@link https://datatracker.ietf.org/doc/html/draft-ietf-core-oscore-groupcomm | Group OSCORE - Secure Group Communication for CoAP} + * for Curve25519 in Section 2.4.2: ECDH with Montgomery Coordinates and + * set generatedPublicEncryptionKeyBytes to the result. + */ + if (multicodecValue === 0xed) { + const generatedPublicEncryptionKeyBytes = await Ed25519.convertPublicKeyToX25519({ + publicKey: rawPublicKeyBytes + }); + + /** + * 5. Set multicodecValue in publicEncryptionKey to 0xec. + * + * 6. Set rawPublicKeyBytes in publicEncryptionKey to + * generatedPublicEncryptionKeyBytes. + */ + publicEncryptionKey = { + key : generatedPublicEncryptionKeyBytes, + multicodecCode : 0xec + }; + } + + /** + * 7. Return publicEncryptionKey. + */ + return publicEncryptionKey as DidKeyDeriveEncryptionKeyResult; + } + + /** + * Decodes a multibase-encoded multicodec value into a verification method + * that is suitable for verifying digital signatures. + * @param options - Signature method creation algorithm inputs. + * @returns - A verification method. + */ + public static async createSignatureMethod(options: { + did: string, + enableExperimentalPublicKeyTypes: boolean, + multibaseValue: string, + publicKeyFormat: DidVerificationMethodType + }): Promise { + const { did, enableExperimentalPublicKeyTypes, multibaseValue, publicKeyFormat } = options; + + /** + * 1. Initialize verificationMethod to an empty object. + */ + const verificationMethod: Partial = {}; + + /** + * 2. Set multicodecValue and rawPublicKeyBytes to the result of passing + * multibaseValue and options to a Decode Public Key algorithm. + */ + const { + key: rawPublicKeyBytes, + multicodecCode: multicodecValue, + multicodecName + } = multibaseIdToKey({ multibaseKeyId: multibaseValue }); + + /** + * 3. Ensure the proper key length of rawPublicKeyBytes based on the + * multicodecValue {@link https://w3c-ccg.github.io/did-method-key/#signature-method-creation-algorithm | table provided}. + * If the byte length of rawPublicKeyBytes does not match the expected + * public key length for the associated multicodecValue, an + * invalidPublicKeyLength error MUST be raised. + */ + const actualLength = rawPublicKeyBytes.byteLength; + const expectedLength = MULTICODEC_PUBLIC_KEY_LENGTH[multicodecValue]; + if (actualLength !== expectedLength) { + throw new Error(`invalidPublicKeyLength: Expected ${actualLength} bytes. Actual ${expectedLength} bytes.`); + } + + /** + * 4. Ensure the rawPublicKeyBytes are a proper encoding of the public + * key type as specified by the multicodecValue. This validation is often + * done by a cryptographic library when importing the public key by, + * for example, ensuring that an Elliptic Curve public key is a specific + * coordinate that exists on the elliptic curve. If an invalid public key + * value is detected, an invalidPublicKey error MUST be raised. + */ + let isValid = false; + switch (multicodecName) { + case 'secp256k1-pub': + isValid = await Secp256k1.validatePublicKey({ key: rawPublicKeyBytes }); + break; + case 'ed25519-pub': + isValid = await Ed25519.validatePublicKey({ key: rawPublicKeyBytes }); + break; + case 'x25519-pub': + // TODO: Validate key once/if X25519.validatePublicKey() is implemented. + // isValid = X25519.validatePublicKey({ key: rawPublicKeyBytes}) + isValid = true; + break; + } + if (!isValid) { + throw new Error('invalidPublicKey: Invalid public key detected.'); + } + + /** + * 5. Set the verificationMethod.id value by concatenating identifier, + * a hash character (#), and the multibaseValue. If verificationMethod.id + * is not a valid DID URL, an invalidDidUrl error MUST be raised. + */ + verificationMethod.id = `${did}#${multibaseValue}`; + try { + new URL(verificationMethod.id); + } catch (error: any) { + throw new Error('invalidDidUrl: Verification Method ID is not a valid DID URL.'); + } + + /** + * 6. Set the publicKeyFormat value to the options.publicKeyFormat value. + * 7. If publicKeyFormat is not known to the implementation, an + * unsupportedPublicKeyType error MUST be raised. + */ + if (!(SupportedPublicKeyFormats.includes(publicKeyFormat))) { + throw new Error(`unsupportedPublicKeyType: Unsupported format: ${publicKeyFormat}`); + } + + /** + * 8. If options.enableExperimentalPublicKeyTypes is set to false and + * publicKeyFormat is not Multikey, JsonWebKey2020, or + * Ed25519VerificationKey2020, an invalidPublicKeyType error MUST be + * raised. + */ + const StandardPublicKeyTypes = ['Multikey', 'JsonWebKey2020', 'Ed25519VerificationKey2020']; + if (enableExperimentalPublicKeyTypes === false + && !(StandardPublicKeyTypes.includes(publicKeyFormat))) { + throw new Error(`invalidPublicKeyType: Specified '${publicKeyFormat}' without setting enableExperimentalPublicKeyTypes to true.`); + } + + /** + * 9. Set verificationMethod.type to the publicKeyFormat value. + */ + verificationMethod.type = publicKeyFormat; + + /** + * 10. Set verificationMethod.controller to the identifier value. + * If verificationMethod.controller is not a valid DID, an invalidDid + * error MUST be raised. + */ + verificationMethod.controller = did; + if (!DidKeyMethod.validateIdentifier({ did })) { + throw new Error(`invalidDid: Invalid identifier format: ${did}`); + } + + /** + * 11. If publicKeyFormat is Multikey or Ed25519VerificationKey2020, + * set the verificationMethod.publicKeyMultibase value to multibaseValue. + * + * Note: This implementation does not currently support the Multikey + * format. + */ + if (publicKeyFormat === 'Ed25519VerificationKey2020') { + verificationMethod.publicKeyMultibase = multibaseValue; + } + + /** + * 12. If publicKeyFormat is JsonWebKey2020, set the + * verificationMethod.publicKeyJwk value to the result of passing + * multicodecValue and rawPublicKeyBytes to a JWK encoding algorithm. + */ + if (publicKeyFormat === 'JsonWebKey2020') { + const jwkParams = await Jose.multicodecToJose({ code: multicodecValue }); + const jsonWebKey = await Jose.keyToJwk({ + keyMaterial : rawPublicKeyBytes, + keyType : 'public', + ...jwkParams + }); + // Ensure that "d" is NOT present. + if ('x' in jsonWebKey && !('d' in jsonWebKey)) { + verificationMethod.publicKeyJwk = jsonWebKey; + } + } + + /** + * 13. Return verificationMethod. + */ + return verificationMethod as VerificationMethod; + } + + public static async generateKeySet(options?: { + keyAlgorithm?: typeof SupportedCryptoAlgorithms[number] + }): Promise { + // Generate Ed25519 keys, by default. + const { keyAlgorithm = 'Ed25519' } = options ?? {}; + + let keyPair: Web5Crypto.CryptoKeyPair; + + switch (keyAlgorithm) { + case 'Ed25519': { + keyPair = await new EdDsaAlgorithm().generateKey({ + algorithm : { name: 'EdDSA', namedCurve: 'Ed25519' }, + extractable : true, + keyUsages : ['sign', 'verify'] + }); + break; + } + + case 'secp256k1': { + keyPair = await new EcdsaAlgorithm().generateKey({ + algorithm : { name: 'ECDSA', namedCurve: 'secp256k1' }, + extractable : true, + keyUsages : ['sign', 'verify'] + }); + break; + } + + default: { + throw new Error(`Unsupported crypto algorithm: '${keyAlgorithm}'`); + } + } + + const publicKeyJwk = await Jose.cryptoKeyToJwk({ key: keyPair.publicKey }) as PublicKeyJwk; + const privateKeyJwk = await Jose.cryptoKeyToJwk({ key: keyPair.privateKey }) as PrivateKeyJwk; + + const keySet: DidKeyKeySet = { + verificationMethodKeys: [{ + publicKeyJwk, + privateKeyJwk, + relationships: ['authentication'] + }] }; + + return keySet; } - resolve(did: string) { - // TODO: Support resolutionOptions as defined in https://www.w3.org/TR/did-core/#did-resolution - // TODO: move did:key resolving logic to this package. resolved Did Doc does **not** include keyAgreement - return didKeyResolver.resolve(did); + /** + * Given the W3C DID Document of a `did:key` DID, return the identifier of + * the verification method key that will be used for signing messages and + * credentials, by default. + * + * @param document = DID Document to get the default signing key from. + * @returns Verification method identifier for the default signing key. + */ + public static async getDefaultSigningKey(options: { + didDocument: DidDocument + }): Promise { + const { didDocument } = options; + + if (didDocument.authentication + && Array.isArray(didDocument.authentication) + && didDocument.authentication.length > 0 + && typeof didDocument.authentication[0] === 'string') { + + const [verificationMethodId] = didDocument.authentication; + const signingKeyId = verificationMethodId; + + return signingKeyId; + } } -} + public static async resolve(options: { + didUrl: string, + resolutionOptions?: DidResolutionOptions + }): Promise { + const { didUrl, resolutionOptions: _ } = options; + // TODO: Implement resolutionOptions as defined in https://www.w3.org/TR/did-core/#did-resolution + + const parsedDid = parseDid({ didUrl }); + if (!parsedDid) { + return { + '@context' : 'https://w3id.org/did-resolution/v1', + didDocument : undefined, + didDocumentMetadata : {}, + didResolutionMetadata : { + contentType : 'application/did+ld+json', + error : 'invalidDid', + errorMessage : `Cannot parse DID: ${didUrl}` + } + }; + } + + if (parsedDid.method !== 'key') { + return { + '@context' : 'https://w3id.org/did-resolution/v1', + didDocument : undefined, + didDocumentMetadata : {}, + didResolutionMetadata : { + contentType : 'application/did+ld+json', + error : 'methodNotSupported', + errorMessage : `Method not supported: ${parsedDid.method}` + } + }; + } + + const didDocument = await DidKeyMethod.createDocument({ did: parsedDid.did }); + + return { + '@context' : 'https://w3id.org/did-resolution/v1', + didDocument, + didDocumentMetadata : {}, + didResolutionMetadata : { + contentType : 'application/did+ld+json', + did : { + didString : parsedDid.did, + methodSpecificId : parsedDid.id, + method : parsedDid.method + } + } + }; + } + + public static validateIdentifier(options: { + did: string + }): boolean { + const { did } = options; + + const { method, id: multibaseValue } = parseDid({ didUrl: did }); + const [scheme] = did.split(':', 1); + + /** + * Note: The W3C DID specification makes no mention of a version value + * being part of the DID syntax. Additionally, there does not + * appear to be any real-world usage of the version number. + * Consequently, this implementation will ignore the version + * related guidance in the did:key specification. + */ + const version = '1'; + + return ( + scheme !== 'did' || + method !== 'key' || + parseInt(version) > 0 || + universalTypeOf(multibaseValue) !== 'String' || + !multibaseValue.startsWith('z') + ); + } +} \ No newline at end of file diff --git a/packages/dids/src/did-resolver.ts b/packages/dids/src/did-resolver.ts index 10d3d9d4f..c7c4b81a8 100644 --- a/packages/dids/src/did-resolver.ts +++ b/packages/dids/src/did-resolver.ts @@ -1,43 +1,101 @@ -import type { DidResolutionResult, DidMethodResolver, DidResolverCache } from './types.js'; +import type { DidResolutionOptions, DidResolutionResult, DidMethodResolver, DidResolverCache } from './types.js'; import { parseDid } from './utils.js'; -import { nopCache } from './nop-cache.js'; +import { noopCache } from './noop-cache.js'; export type DidResolverOptions = { - methodResolvers: DidMethodResolver[]; + didResolvers: DidMethodResolver[]; cache?: DidResolverCache; } +/** + * The `DidResolver` class is responsible for resolving DIDs to DID documents. + * It uses method resolvers to resolve DIDs of different methods and a cache + * to store resolved DID documents. + */ export class DidResolver { - cache: DidResolverCache; - methodResolverMap: Map = new Map(); + /** + * A cache for storing resolved DID documents. + */ + private cache: DidResolverCache; + /** + * A map to store method resolvers against method names. + */ + private didResolvers: Map = new Map(); + + /** + * Constructs a new `DidResolver`. + * + * @param options - The options for constructing the `DidResolver`. + * @param options.didResolvers - An array of `DidMethodResolver` instances. + * @param options.cache - Optional. A cache for storing resolved DID documents. If not provided, a no-operation cache is used. + */ constructor(options: DidResolverOptions) { - this.cache = options.cache || nopCache; + this.cache = options.cache || noopCache; - for (let methodResolver of options.methodResolvers) { - this.methodResolverMap.set(methodResolver.methodName, methodResolver); + for (const resolver of options.didResolvers) { + this.didResolvers.set(resolver.methodName, resolver); } } - async resolve(did: string): Promise { - // TODO: Support resolutionOptions as defined in https://www.w3.org/TR/did-core/#did-resolution - const { method } = parseDid(did); - const resolver = this.methodResolverMap.get(method); + /** + * Resolves a DID to a DID Resolution Result. + * If the DID Resolution Result is present in the cache, it returns the cached + * result. Otherwise, it uses the appropriate method resolver to resolve + * the DID, stores the resolution result in the cache, and returns the + * resolultion result. + * + * Note: The method signature for resolve() in this implementation must match + * the `DidResolver` implementation in + * {@link https://github.com/TBD54566975/dwn-sdk-js | dwn-sdk-js} so that + * Web5 apps and the underlying DWN instance can share the same DID + * resolution cache. + * + * @param didUrl - The DID or DID URL to resolve. + * @returns A promise that resolves to the DID Resolution Result. + */ + async resolve(didUrl: string, options?: DidResolutionOptions): Promise { + const parsedDid = parseDid({ didUrl }); + if (!parsedDid) { + return { + '@context' : 'https://w3id.org/did-resolution/v1', + didDocument : undefined, + didDocumentMetadata : {}, + didResolutionMetadata : { + contentType : 'application/did+ld+json', + error : 'invalidDid', + errorMessage : `Cannot parse DID: ${didUrl}` + } + }; + } + const resolver = this.didResolvers.get(parsedDid.method); if (!resolver) { - throw new Error(`no resolver for ${method}`); + return { + '@context' : 'https://w3id.org/did-resolution/v1', + didDocument : undefined, + didDocumentMetadata : {}, + didResolutionMetadata : { + contentType : 'application/did+ld+json', + error : 'methodNotSupported', + errorMessage : `Method not supported: ${parsedDid.method}` + } + }; } - const cachedResolution = await this.cache.get(did); + const cachedResolutionResult = await this.cache.get(parsedDid.did); - if (cachedResolution) { - return cachedResolution; + if (cachedResolutionResult) { + return cachedResolutionResult; } else { - const didResolutionResult = await resolver.resolve(did); - await this.cache.set(did, didResolutionResult); + const resolutionResult = await resolver.resolve({ + didUrl : parsedDid.did, + resolutionOptions : options ?? {} + }); + await this.cache.set(parsedDid.did, resolutionResult); - return didResolutionResult; + return resolutionResult; } } } \ No newline at end of file diff --git a/packages/dids/src/main.ts b/packages/dids/src/index.ts similarity index 82% rename from packages/dids/src/main.ts rename to packages/dids/src/index.ts index 247729026..2d9dbda80 100644 --- a/packages/dids/src/main.ts +++ b/packages/dids/src/index.ts @@ -3,5 +3,4 @@ export * from './did-ion.js'; export * from './did-key.js'; export * from './did-resolver.js'; -// TODO: change name to didUtils? export * as utils from './utils.js'; \ No newline at end of file diff --git a/packages/dids/src/nop-cache.ts b/packages/dids/src/noop-cache.ts similarity index 78% rename from packages/dids/src/nop-cache.ts rename to packages/dids/src/noop-cache.ts index 51f2ca1b1..efd3eaa5e 100644 --- a/packages/dids/src/nop-cache.ts +++ b/packages/dids/src/noop-cache.ts @@ -6,20 +6,20 @@ import type { DidResolutionResult, DidResolverCache } from './types.js'; * the desire to maximize the potential for this library to be used * in as many JS runtimes as possible */ -export const nopCache: DidResolverCache = { +export const noopCache: DidResolverCache = { get: function (_key: string): Promise { - return; + return null as any; }, set: function (_key: string, _value: DidResolutionResult): Promise { - return; + return null as any; }, delete: function (_key: string): Promise { - return; + return null as any; }, clear: function (): Promise { - return; + return null as any; }, close: function (): Promise { - return; + return null as any; } }; \ No newline at end of file diff --git a/packages/dids/src/types.ts b/packages/dids/src/types.ts index 361b5fe8d..d02e7ca56 100644 --- a/packages/dids/src/types.ts +++ b/packages/dids/src/types.ts @@ -1,18 +1,22 @@ -import type { KeyValueStore } from '@tbd54566975/common'; -import type { PublicKeyJwk, PrivateKeyJwk } from '@tbd54566975/crypto'; +import type { KeyValueStore } from '@web5/common'; +import type { PrivateKeyJwk, PublicKeyJwk } from '@web5/crypto'; -export type DidResolutionResult = { - '@context'?: 'https://w3id.org/did-resolution/v1' | string | string[] - didResolutionMetadata: DidResolutionMetadata - didDocument?: DidDocument - didDocumentMetadata: DidDocumentMetadata -}; +import { DidKeyKeySet } from './did-key.js'; +import { DidIonKeySet } from './did-ion.js'; -export type DidResolutionMetadata = { - contentType?: string - error?: 'invalidDid' | 'notFound' | 'representationNotSupported' | - 'unsupportedDidMethod' | string -}; +export type DidDocument = { + '@context'?: 'https://www.w3.org/ns/did/v1' | string | string[]; + id: string; + alsoKnownAs?: string[]; + controller?: string | string[]; + verificationMethod?: VerificationMethod[]; + service?: DidService[]; + assertionMethod?: VerificationMethod[] | string[]; + authentication?: VerificationMethod[] | string[]; + keyAgreement?: VerificationMethod[] | string[]; + capabilityDelegation?: VerificationMethod[] | string[]; + capabilityInvocation?: VerificationMethod[] | string[]; +} export type DidDocumentMetadata = { // indicates the timestamp of the Create operation. ISO8601 timestamp @@ -35,83 +39,239 @@ export type DidDocumentMetadata = { equivalentId?: string // @see https://www.w3.org/TR/did-core/#dfn-canonicalid canonicalId?: string + // Additional output metadata generated during DID Resolution. + [key: string]: any }; -export type DidDocument = { - '@context'?: 'https://www.w3.org/ns/did/v1' | string | string[] - id: string - alsoKnownAs?: string[] - controller?: string | string[] - verificationMethod?: VerificationMethod[] - service?: ServiceEndpoint[] - authentication?: VerificationMethod[] | string[] - assertionMethod?: VerificationMethod[] | string[] - keyAgreement?: VerificationMethod[] | string[] - capabilityInvocation?: VerificationMethod[] | string[] - capabilityDelegation?: VerificationMethod[] | string[] -}; +export type DidKeySet = DidKeyKeySet | DidIonKeySet; -export type ServiceEndpoint = { - id: string - type: string - serviceEndpoint: string | DwnServiceEndpoint - description?: string -}; +export type DidKeySetVerificationMethodKey = { + /** Unique identifier for the key in the KeyManager store. */ + keyManagerId?: string; + publicKeyJwk?: PublicKeyJwk; + privateKeyJwk?: PrivateKeyJwk; + relationships: VerificationRelationship[]; +} -export type DwnServiceEndpoint = { - messageAttestationKeys?: string[] - messageAuthorizationKeys?: string[] //! TODO: This property should be required by TS throws an error if it is. - nodes: string[] - recordEncryptionKeys?: string[] //! TODO: This property should be required by TS throws an error if it is. -}; +export type DidMetadata = { + /** + * Additional properties of any type. + */ + [key: string]: any; +} +// eslint-disable-next-line @typescript-eslint/no-empty-interface +export interface DidMethod {} -export type VerificationMethod = { - id: string - // one of the valid verification method types as per - // https://www.w3.org/TR/did-spec-registries/#verification-method-types - type: string - // DID of the key's controller - controller: string - // a JSON Web Key that conforms to https://datatracker.ietf.org/doc/html/rfc7517 - publicKeyJwk?: PublicKeyJwk -}; +export interface DidMethodApi extends DidMethodOperator, DidMethodResolver { + new (): DidMethod; + methodName: string; +} + +export interface DidMethodResolver { + new (): DidMethod; + methodName: string; + + resolve(options: { + didUrl: string, + resolutionOptions?: DidResolutionOptions + }): Promise; +} + +export interface DidMethodOperator { + new (): DidMethod; + methodName: string; + + create(options: any): Promise; + + generateKeySet(): Promise; + + getDefaultSigningKey(options: { didDocument: DidDocument }): Promise; +} /** - * implement this interface to provide your own cache for did resolution results. can be plugged in through Web5 API + * Services are used in DID documents to express ways of communicating with the DID subject or associated entities. + * A service can be any type of service the DID subject wants to advertise. + * + * @see {@link https://www.w3.org/TR/did-core/#services} */ -export type DidResolverCache = KeyValueStore; +export type DidService = { + id: string; + type: string; + serviceEndpoint: string | DidServiceEndpoint | DidServiceEndpoint[]; + description?: string; +}; /** - * implement this interface to include support for different did methods. can be plugged in through Web5 API + * A service endpoint is a URI (Uniform Resource Identifier) that can be used to interact with the service. + * + * @see {@link https://www.w3.org/TR/did-core/#dfn-serviceendpoint} */ -export interface DidMethodApi extends DidMethodCreator, DidMethodResolver {} +export interface DidServiceEndpoint { + [key: string]: any; +} + +export interface DwnServiceEndpoint extends DidServiceEndpoint { + encryptionKeys: string[]; + nodes: string[]; + signingKeys: string[]; +} + +export type DidResolutionMetadata = { + contentType?: string + + error?: + /** + * When an unexpected error occurs during DID Resolution or DID URL dereferencing, the value of the DID Resolution or DID URL Dereferencing Metadata error property MUST be internalError. + */ + | 'internalError' + + /** + * If an invalid DID is detected during DID Resolution, the value of the + * DID Resolution Metadata error property MUST be invalidDid. + */ + | 'invalidDid' + + /** + * If a DID method is not supported during DID Resolution or DID URL + * dereferencing, the value of the DID Resolution or DID URL Dereferencing + * Metadata error property MUST be methodNotSupported. + */ + | 'methodNotSupported' + + /** + * If during DID Resolution or DID URL dereferencing a DID or DID URL + * doesn't exist, the value of the DID Resolution or DID URL dereferencing + * Metadata error property MUST be notFound. + */ + | 'notFound' + + /** + * If a DID document representation is not supported during DID Resolution + * or DID URL dereferencing, the value of the DID Resolution Metadata error + * property MUST be representationNotSupported. + */ + | 'representationNotSupported' + | string + + // Additional output metadata generated during DID Resolution. + [key: string]: any +}; /** - * implement this interface to include support for resolving different dids. can be plugged in through Web5 API + * DID Resolution input metadata. + * + * @see {@link https://www.w3.org/TR/did-core/#did-resolution-options} */ -export interface DidMethodResolver { - get methodName(): string; - resolve(did: string): Promise +export interface DidResolutionOptions { + accept?: string + + // Additional properties used during DID Resolution. + [key: string]: any } +export type DidResolutionResult = { + '@context'?: 'https://w3id.org/did-resolution/v1' | string | string[] + didResolutionMetadata: DidResolutionMetadata + didDocument?: DidDocument + didDocumentMetadata: DidDocumentMetadata +}; + /** - * implement this interface to include support for creating different dids. can be plugged in through Web5 API + * implement this interface to provide your own cache for did resolution results. can be plugged in through Web5 API + */ +export type DidResolverCache = KeyValueStore; + +/** + * Format to document a DID identifier, along with its associated data, + * which can be exported, saved to a file, or imported. The intent is + * bundle all of the necessary metadata to enable usage of the DID in + * different contexts. */ -export interface DidMethodCreator { - get methodName(): string; - create(options: any): Promise +export interface PortableDid { + did: string; + + /** + * A DID method can define different forms of a DID that are logically + * equivalent. An example is when a DID takes one form prior to registration + * in a verifiable data registry and another form after such registration. + * This is the purpose of the canonicalId property. + * + * The `canonicalId` must be used as the primary ID for the DID subject, + * with all other equivalent values treated as secondary aliases. + * + * @see {@link https://www.w3.org/TR/did-core/#dfn-canonicalid | W3C DID Document Metadata} + */ + canonicalId?: string; + + /** + * A set of data describing the DID subject, including mechanisms, such as + * cryptographic public keys, that the DID subject or a DID delegate can use + * to authenticate itself and prove its association with the DID. + */ + document: DidDocument; + + /** + * A collection of cryptographic keys associated with the DID subject. The + * `keySet` encompasses various forms, such as recovery keys, update keys, + * and verification method keys, to enable authentication and verification + * of the DID subject's association with the DID. + */ + keySet: DidKeySet; + + /** + * This property can be used to store method specific data about + * each managed DID and additional properties of any type. + */ + metadata?: DidMetadata; } -export type DidState = { +export type VerificationMethod = { id: string; - internalId: string; - didDocument?: DidDocument; - keys: VerificationMethodWithPrivateKeyJwk[]; - methodData: { [prop: string]: any }; -} + // one of the valid verification method types as per + // https://www.w3.org/TR/did-spec-registries/#verification-method-types + type: string; + // DID of the key's controller + controller: string; + // a JSON Web Key that conforms to https://datatracker.ietf.org/doc/html/rfc7517 + publicKeyJwk?: PublicKeyJwk; + // an encoded (e.g, base58) key with a Multibase-prefix that conforms to + // https://datatracker.ietf.org/doc/draft-multiformats-multibase/ + publicKeyMultibase?: string; +}; + +export type VerificationRelationship = + /** + * Used to specify how the DID subject is expected to express claims, such + * as for the purposes of issuing a Verifiable Credential + */ + | 'assertionMethod' + + /** + * Used to specify how the DID subject is expected to be authenticated, for + * purposes such as logging into a website or engaging in any sort of + * challenge-response protocol. + */ + | 'authentication' + + /** + * Used to specify how an entity can generate encryption material in order to + * transmit confidential information intended for the DID subject, such as + * for the purposes of establishing a secure communication channel with the + * recipient. + */ + | 'keyAgreement' + + /** + * Used to specify a mechanism that might be used by the DID subject to + * delegate a cryptographic capability to another party, such as delegating + * the authority to access a specific HTTP API to a subordinate. + */ + | 'capabilityDelegation' -// TODO: remove this once we've figured out keystore stuff -export type VerificationMethodWithPrivateKeyJwk = VerificationMethod & { - privateKeyJwk: PrivateKeyJwk -}; \ No newline at end of file + /** + * Used to specify a verification method that might be used by the DID + * subject to invoke a cryptographic capability, such as the authorization + * to update the DID Document. + */ + | 'capabilityInvocation'; \ No newline at end of file diff --git a/packages/dids/src/utils.ts b/packages/dids/src/utils.ts index ba8359c9d..30f0cd23a 100644 --- a/packages/dids/src/utils.ts +++ b/packages/dids/src/utils.ts @@ -1,65 +1,125 @@ -import type { KeyPairJwk } from '@tbd54566975/crypto'; -import type { DidDocument, VerificationMethodWithPrivateKeyJwk, ServiceEndpoint } from './types.js'; +import type { PublicKeyJwk } from '@web5/crypto'; +import { parse, type ParsedDID } from 'did-resolver'; -import { Convert, Multicodec } from '@tbd54566975/common'; +import type { DidDocument, DidService, DidServiceEndpoint, DwnServiceEndpoint } from './types.js'; -export type ParsedDid = { - method: string; - id: string; +export interface ParsedDid { + did: string + didUrl: string + method: string + id: string + path?: string + fragment?: string + query?: string + params?: ParsedDID['params'] } export const DID_REGEX = /^did:([a-z0-9]+):((?:(?:[a-zA-Z0-9._-]|(?:%[0-9a-fA-F]{2}))*:)*((?:[a-zA-Z0-9._-]|(?:%[0-9a-fA-F]{2}))+))((;[a-zA-Z0-9_.:%-]+=[a-zA-Z0-9_.:%-]*)*)(\/[^#?]*)?([?][^#]*)?(#.*)?$/; -export function parseDid(did: string): ParsedDid { - if (!DID_REGEX.test(did)) { - throw new Error('Invalid DID'); - } - - const [didString,] = did.split('#'); - const [, method, id] = didString.split(':', 3); +/** + * Retrieves services from a given DID document based on provided options. + * If no `id` or `type` filters are provided, all defined services are returned. + * + * Note: The DID document must adhere to the W3C DID specification. + * + * @param options - An object containing input parameters for retrieving services. + * @param options.didDocument - The DID document from which services are retrieved. + * @param options.id - Optional. A string representing the specific service ID to match. If provided, only the service with this ID will be returned. + * @param options.type - Optional. A string representing the specific service type to match. If provided, only the service(s) of this type will be returned. + * + * @returns An array of services. If no matching service is found, an empty array is returned. + * + * @example + * + * const didDoc = { ... }; // W3C DID document + * const services = getServices({ didDocument: didDoc, type: 'DecentralizedWebNode' }); + */ +export function getServices(options: { + didDocument: DidDocument, + id?: string, + type?: string +}): DidService[] { + const { didDocument, id, type } = options ?? {}; - return { method, id }; + return didDocument?.service?.filter(service => { + if (id && service.id !== id) return false; + if (type && service.type !== type) return false; + return true; + }) ?? [ ]; } -export function createVerificationMethodWithPrivateKeyJwk(id: string, keyPairJwk: KeyPairJwk): VerificationMethodWithPrivateKeyJwk { - const { publicKeyJwk, privateKeyJwk } = keyPairJwk; +export function getVerificationMethodIds(options: { + didDocument: DidDocument, + publicKeyJwk?: PublicKeyJwk, + publicKeyMultibase?: string +}): string | undefined { + const { didDocument, publicKeyJwk, publicKeyMultibase } = options; + if (!didDocument) throw new Error(`Required parameter missing: 'didDocument'`); + if (!didDocument.verificationMethod) throw new Error('Given `didDocument` is missing `verificationMethod` entries.'); - return { - id : `${id}#${keyPairJwk.publicKeyJwk.kid}`, - type : 'JsonWebKey2020', - controller : id, - publicKeyJwk, - privateKeyJwk - }; + for (let method of didDocument.verificationMethod) { + if (publicKeyMultibase && 'publicKeyMultibase' in method) { + if (publicKeyMultibase === method.publicKeyMultibase) { + return method.id; + } + } else if (publicKeyJwk && 'crv' in publicKeyJwk && + 'publicKeyJwk' in method && 'crv' in method.publicKeyJwk) { + if (publicKeyJwk.crv === method.publicKeyJwk.crv && + publicKeyJwk.x === method.publicKeyJwk.x) { + return method.id; + } + } + } } -export type GetServicesOptions = { - id?: string; - type?: string; -}; +/** + * Retrieves DID verification method types from a given DID document. + * + * Note: The DID document must adhere to the W3C DID specification. + * + * @param options - An object containing input parameters for retrieving types. + * @param options.didDocument - The DID document from which types are retrieved. + * + * @returns An array of types. If no types were found, an empty array is returned. + */ +export function getVerificationMethodTypes(options: { + didDocument: Record +}): string[] { + const { didDocument } = options; + + let types: string[] = []; + + for (let key in didDocument) { + if (typeof didDocument[key] === 'object') { + types = types.concat(getVerificationMethodTypes({ + didDocument: didDocument[key] + })); + + } else if (key === 'type') { + types.push(didDocument[key]); + } + } + + return [...new Set(types)]; // return only unique types +} /** - * returns services from the provided DID Document based on the filter. will return all services if no filter is provided - * @param didDocument the did document to search - * @param options search filter - * @returns matched services + * Type guard function to check if the given endpoint is a DwnServiceEndpoint. + * + * @param key The endpoint to check. + * @returns True if the endpoint is a DwnServiceEndpoint, false otherwise. */ -export function getServices(didDocument: DidDocument, options: GetServicesOptions = {}): ServiceEndpoint[] { - return didDocument?.service?.filter(service => { - if (options?.id && service.id !== options.id) return false; - if (options?.type && service.type !== options.type) return false; - return true; - }) ?? [ ]; +export function isDwnServiceEndpoint(endpoint: string | DidServiceEndpoint | DidServiceEndpoint[]): endpoint is DwnServiceEndpoint { + return endpoint !== undefined && + typeof endpoint !== 'string' && + !Array.isArray(endpoint) && + 'encryptionKeys' in endpoint && + 'nodes' in endpoint && + 'signingKeys' in endpoint; } -export function keyToMultibaseId(options: { - key: Uint8Array, - multicodecName: string -}): string { - const { key, multicodecName } = options; - const prefixedKey = Multicodec.addPrefix({ name: multicodecName, data: key }); - const prefixedKeyB58 = Convert.uint8Array(prefixedKey).toBase58Btc(); - const multibaseKeyId = Convert.base58Btc(prefixedKeyB58).toMultibase(); +export function parseDid({ didUrl }: { didUrl: string }): ParsedDid | undefined { + const parsedDid: ParsedDid = parse(didUrl); - return multibaseKeyId; + return parsedDid; } \ No newline at end of file diff --git a/packages/dids/tests/did-ion.spec.ts b/packages/dids/tests/did-ion.spec.ts index c7cfa6eb1..bd815c561 100644 --- a/packages/dids/tests/did-ion.spec.ts +++ b/packages/dids/tests/did-ion.spec.ts @@ -1,31 +1,661 @@ -import { expect } from 'chai'; +import type { JwkKeyPair } from '@web5/crypto'; -import { DidIonApi } from '../src/did-ion.js'; +import * as sinon from 'sinon'; +import chai, { expect } from 'chai'; +import chaiAsPromised from 'chai-as-promised'; -// NOTE: @noble/secp256k1 requires globalThis.crypto polyfill for node.js <=18: https://github.com/paulmillr/noble-secp256k1/blob/main/README.md#usage -// Remove when we move off of node.js v18 to v20, earliest possible time would be Oct 2023: https://github.com/nodejs/release#release-schedule -import { webcrypto } from 'node:crypto'; -// @ts-ignore -if (!globalThis.crypto) globalThis.crypto = webcrypto; +chai.use(chaiAsPromised); -const DidIon = new DidIonApi(); +import { DidDocument, DidKeySetVerificationMethodKey, DidService } from '../src/types.js'; +import { didIonCreateTestVectors } from './fixtures/test-vectors/did-ion.js'; +import { DidIonCreateOptions, DidIonKeySet, DidIonMethod } from '../src/did-ion.js'; + +describe('DidIonMethod', () => { + let testRecoveryKey: JwkKeyPair; + let testUpdateKey: JwkKeyPair; + let testVerificationMethodKeys: DidKeySetVerificationMethodKey[]; + let testKeySet: DidIonKeySet; + + beforeEach(() => { + testRecoveryKey = structuredClone(didIonCreateTestVectors[0].input.keySet.recoveryKey) as JwkKeyPair; + testRecoveryKey.privateKeyJwk.kid = 'test-recovery-1'; + testRecoveryKey.publicKeyJwk.kid = 'test-recovery-1'; + + testUpdateKey = structuredClone(didIonCreateTestVectors[0].input.keySet.updateKey) as JwkKeyPair; + testUpdateKey.privateKeyJwk.kid = 'test-update-1'; + testUpdateKey.publicKeyJwk.kid = 'test-update-1'; + + testVerificationMethodKeys = structuredClone(didIonCreateTestVectors[0].input.keySet.verificationMethodKeys) as DidKeySetVerificationMethodKey[]; + testVerificationMethodKeys[0].publicKeyJwk!.kid = 'test-kid'; + + testKeySet = { + recoveryKey : testRecoveryKey, + updateKey : testUpdateKey, + verificationMethodKeys : testVerificationMethodKeys + }; + }); + + describe('anchor()', () => { + it('accepts a custom operations endpoint', async () => { + // Setup stub so that a mocked response is returned rather than calling over the network. + const mockResult = { mock: 'data' }; + const fetchStub = sinon.stub(global, 'fetch'); + // @ts-expect-error because we're only mocking ok and json() from global.fetch(). + fetchStub.returns(Promise.resolve({ + ok : true, + json : () => Promise.resolve(mockResult) + })); + + const resolutionResult = await DidIonMethod.anchor({ + challengeEnabled : false, + keySet : testKeySet, + operationsEndpoint : 'https://ion-service.com/operations', + services : [] + }); + fetchStub.restore(); + + expect(resolutionResult).to.deep.equal(mockResult); + expect(fetchStub.calledOnceWith( + 'https://ion-service.com/operations', + sinon.match({ + method : 'POST', + mode : 'cors', + body : sinon.match.string, + headers : { + 'Content-Type': 'application/json' + } + }) + )).to.be.true; + }); + + it('supports disabling POW/challenge', async () => { + // Setup stub so that a mocked response is returned rather than calling over the network. + const mockResult = { mock: 'data' }; + const fetchStub = sinon.stub(global, 'fetch'); + // @ts-expect-error because we're only mocking ok and json() from global.fetch(). + fetchStub.returns(Promise.resolve({ + ok : true, + json : () => Promise.resolve(mockResult) + })); + + const resolutionResult = await DidIonMethod.anchor({ + challengeEnabled : false, + keySet : testKeySet, + operationsEndpoint : 'https://ion-service.com/operations', + services : [] + }); + fetchStub.restore(); + + expect(resolutionResult).to.deep.equal(mockResult); + }); + }); -describe('DidIonApi', () => { describe('create()', () => { - it('returns a valid didState', async () => { - const didState = await DidIon.create(); - - expect(didState.id).to.exist; - expect(didState.internalId).to.exist; - expect(didState.keys).to.exist; - - for (let key of didState.keys) { - expect(key.id).to.exist; - expect(key.controller).to.exist; - expect(key.publicKeyJwk).to.exist; - expect(key.privateKeyJwk).to.exist; - expect(key.type).to.exist; - } + it('creates a DID with Ed25519 keys, by default', async () => { + const portableDid = await DidIonMethod.create(); + + // Verify expected result. + expect(portableDid).to.have.property('did'); + expect(portableDid).to.have.property('canonicalId'); + expect(portableDid).to.have.property('document'); + expect(portableDid).to.have.property('keySet'); + + const keySet = portableDid.keySet as DidIonKeySet; + + expect(keySet).to.have.property('verificationMethodKeys'); + expect(keySet.verificationMethodKeys).to.have.length(1); + expect(keySet.verificationMethodKeys?.[0]).to.have.property('publicKeyJwk'); + expect(keySet.verificationMethodKeys?.[0]).to.have.property('privateKeyJwk'); + expect(keySet.verificationMethodKeys?.[0].publicKeyJwk).to.have.property('alg', 'EdDSA'); + expect(keySet.verificationMethodKeys?.[0].publicKeyJwk).to.have.property('crv', 'Ed25519'); + + expect(keySet).to.have.property('recoveryKey'); + expect(keySet.recoveryKey).to.have.property('publicKeyJwk'); + expect(keySet.recoveryKey).to.have.property('privateKeyJwk'); + + expect(keySet).to.have.property('updateKey'); + expect(keySet.recoveryKey).to.have.property('publicKeyJwk'); + expect(keySet.recoveryKey).to.have.property('privateKeyJwk'); + }); + + it('creates a DID with secp256k1 keys, if specified', async () => { + const portableDid = await DidIonMethod.create({ keyAlgorithm: 'secp256k1' }); + + // Verify expected result. + expect(portableDid).to.have.property('did'); + expect(portableDid).to.have.property('canonicalId'); + expect(portableDid).to.have.property('document'); + expect(portableDid).to.have.property('keySet'); + + const keySet = portableDid.keySet as DidIonKeySet; + + expect(keySet).to.have.property('verificationMethodKeys'); + expect(keySet.verificationMethodKeys).to.have.length(1); + expect(keySet.verificationMethodKeys?.[0]).to.have.property('publicKeyJwk'); + expect(keySet.verificationMethodKeys?.[0]).to.have.property('privateKeyJwk'); + expect(keySet.verificationMethodKeys?.[0].publicKeyJwk).to.have.property('alg', 'ES256K'); + expect(keySet.verificationMethodKeys?.[0].publicKeyJwk).to.have.property('crv', 'secp256k1'); + + expect(keySet).to.have.property('recoveryKey'); + expect(keySet.recoveryKey).to.have.property('publicKeyJwk'); + expect(keySet.recoveryKey).to.have.property('privateKeyJwk'); + + expect(keySet).to.have.property('updateKey'); + expect(keySet.recoveryKey).to.have.property('publicKeyJwk'); + expect(keySet.recoveryKey).to.have.property('privateKeyJwk'); + }); + + it('uses specified key ID values for key set, if given', async () => { + const portableDid = await DidIonMethod.create({ + keyAlgorithm : 'Ed25519', + keySet : testKeySet + }); + + const keySet = portableDid.keySet as DidIonKeySet; + expect(keySet.recoveryKey?.privateKeyJwk.kid).to.equal('test-recovery-1'); + expect(keySet.recoveryKey?.publicKeyJwk.kid).to.equal('test-recovery-1'); + expect(keySet.updateKey?.privateKeyJwk.kid).to.equal('test-update-1'); + expect(keySet.updateKey?.publicKeyJwk.kid).to.equal('test-update-1'); + expect(keySet.verificationMethodKeys?.[0].publicKeyJwk?.kid).to.equal('test-kid'); + expect(keySet.verificationMethodKeys?.[0].publicKeyJwk?.kid).to.equal('test-kid'); + }); + + it('generates key ID values for key set, if missing', async () => { + delete testRecoveryKey.privateKeyJwk.kid; + delete testRecoveryKey.publicKeyJwk.kid; + delete testUpdateKey.privateKeyJwk.kid; + delete testUpdateKey.publicKeyJwk.kid; + delete testVerificationMethodKeys[0].publicKeyJwk!.kid; + + const portableDid = await DidIonMethod.create({ + keyAlgorithm : 'Ed25519', + keySet : testKeySet + }); + + const keySet = portableDid.keySet as DidIonKeySet; + expect(keySet.recoveryKey?.privateKeyJwk.kid).to.equal('AEOG_sxXHhCA1Fel8fpheyLxAcW89D7V86lMcJXc500'); + expect(keySet.recoveryKey?.publicKeyJwk.kid).to.equal('AEOG_sxXHhCA1Fel8fpheyLxAcW89D7V86lMcJXc500'); + expect(keySet.updateKey?.privateKeyJwk.kid).to.equal('_1CySHVtk6tNXke3t_7NLI2nvaVlH5GFyuO9HjQCRKs'); + expect(keySet.updateKey?.publicKeyJwk.kid).to.equal('_1CySHVtk6tNXke3t_7NLI2nvaVlH5GFyuO9HjQCRKs'); + expect(keySet.verificationMethodKeys?.[0].publicKeyJwk?.kid).to.equal('OAPj7ObrEJFgVNA2rrkPM5A-vYVsH_lyz4LgOUdJBa8'); + expect(keySet.verificationMethodKeys?.[0].publicKeyJwk?.kid).to.equal('OAPj7ObrEJFgVNA2rrkPM5A-vYVsH_lyz4LgOUdJBa8'); + }); + + it('specified key IDs are prefixed with hash symbol (#) in DID Document', async () => { + const portableDid = await DidIonMethod.create({ + keyAlgorithm : 'Ed25519', + keySet : testKeySet + }); + + expect(portableDid.document.authentication).includes(`#test-kid`); + expect(portableDid.document.verificationMethod![0].id).to.equal(`#test-kid`); + }); + + it('accepts recovery and update key IDs that include a hash symbol (#)', async () => { + testRecoveryKey.privateKeyJwk.kid = '#test-recovery-1'; + testRecoveryKey.publicKeyJwk.kid = '#test-recovery-1'; + + await expect( + DidIonMethod.create({ keySet: { recoveryKey: testRecoveryKey } }) + ).to.eventually.be.fulfilled; + + testUpdateKey.privateKeyJwk.kid = '#test-update-1'; + testUpdateKey.publicKeyJwk.kid = '#test-update-1'; + + await expect( + DidIonMethod.create({ keySet: { updateKey: testUpdateKey } }) + ).to.eventually.eventually.be.fulfilled; + }); + + it('throws an error if verification method key IDs include a hash symbol (#)', async () => { + testVerificationMethodKeys[0].publicKeyJwk!.kid = '#test-kid'; + + await expect( + DidIonMethod.create({ keySet: { verificationMethodKeys: testVerificationMethodKeys } }) + ).to.eventually.eventually.be.rejectedWith(Error, 'IdNotUsingBase64UrlCharacterSet'); + }); + + it('creates a DID with service entries, if specified', async () => { + const dwnEndpoints = [ + 'https://dwn.tbddev.test/dwn0', + 'https://dwn.tbddev.test/dwn1' + ]; + + const services: DidService[] = [{ + 'id' : 'dwn', + 'type' : 'DecentralizedWebNode', + 'serviceEndpoint' : { + 'nodes' : dwnEndpoints, + 'signingKeys' : ['#dwn-sig'], + 'encryptionKeys' : ['#dwn-enc'] + } + }]; + + const portableDid = await DidIonMethod.create({ services }); + + const dwnService = portableDid.document.service?.[0]; + expect(dwnService).to.have.property('type', 'DecentralizedWebNode'); + expect(dwnService?.serviceEndpoint).to.have.property('nodes'); + expect(dwnService?.serviceEndpoint).to.have.property('signingKeys'); + expect(dwnService?.serviceEndpoint).to.have.property('encryptionKeys'); + }); + + it('specified service IDs are prefixed with hash symbol (#) in DID Document', async () => { + const dwnEndpoints = ['https://dwn.tbddev.test/dwn0']; + + const services: DidService[] = [{ + 'id' : 'dwn', + 'type' : 'DecentralizedWebNode', + 'serviceEndpoint' : { + 'nodes': dwnEndpoints + } + }]; + + const portableDid = await DidIonMethod.create({ services }); + + const dwnService = portableDid.document.service?.[0]; + expect(dwnService).to.have.property('id', '#dwn'); + }); + + it('throws an error if service IDs include a hash symbol (#)', async () => { + let services: DidService[] = [{ + 'id' : '#dwn', + 'type' : 'DecentralizedWebNode', + 'serviceEndpoint' : { } + }]; + + await expect( + DidIonMethod.create({ services }) + ).to.eventually.eventually.be.rejectedWith(Error, 'IdNotUsingBase64UrlCharacterSet'); + + services = [{ + 'id' : 'foo#bar', + 'type' : 'DecentralizedWebNode', + 'serviceEndpoint' : { } + }]; + + await expect( + DidIonMethod.create({ services }) + ).to.eventually.eventually.be.rejectedWith(Error, 'IdNotUsingBase64UrlCharacterSet'); + }); + + for (const vector of didIonCreateTestVectors ) { + it(`passes test vector ${vector.id}`, async () => { + const portableDid = await DidIonMethod.create(vector.input as DidIonCreateOptions); + + expect(portableDid).to.deep.equal(vector.output); + }); + } + }); + + describe('decodeLongFormDid()', () => { + it('returns ION create request with services', async () => { + const longFormDid = 'did:ion:EiC94n5yoQEpRfmT6Co7Q4GCUWmuAK4UzDFpk5W4_BzP4A:eyJkZWx0YSI6eyJwYXRjaGVzIjpbeyJhY3Rpb24iOiJyZXBsYWNlIiwiZG9jdW1lbnQiOnsicHVibGljS2V5cyI6W3siaWQiOiJkd24tc2lnIiwicHVibGljS2V5SndrIjp7ImNydiI6IkVkMjU1MTkiLCJrdHkiOiJPS1AiLCJ4IjoiSl9lek5jT2pqNTIxbWEtN18tanFWdC1JODRzendSRTJzMGFCN3h2R1ljYyJ9LCJwdXJwb3NlcyI6WyJhdXRoZW50aWNhdGlvbiJdLCJ0eXBlIjoiSnNvbldlYktleTIwMjAifV0sInNlcnZpY2VzIjpbeyJpZCI6ImR3biIsInNlcnZpY2VFbmRwb2ludCI6eyJub2RlcyI6WyJodHRwczovL2R3bi50YmRkZXYudGVzdC9kd24wIl19LCJ0eXBlIjoiRGVjZW50cmFsaXplZFdlYk5vZGUifV19fV0sInVwZGF0ZUNvbW1pdG1lbnQiOiJFaUR2WHJNV3V0LTNIUWpsTm5JbHlKR2F0WVBsNWo2MFp3SnB4cG9wOHk2RGxBIn0sInN1ZmZpeERhdGEiOnsiZGVsdGFIYXNoIjoiRWlEd2VVOG82clVZY1lCNHQzaHBoaXdtZFpxZWRVdm5zQ251a2xMdWVfOVFOUSIsInJlY292ZXJ5Q29tbWl0bWVudCI6IkVpREhIb3E0bFhoMndjWUZNSnNuNkZBN3otVk9sTHBiU20xcnRNOXlJMUQzd3cifX0'; + + const createRequest = await DidIonMethod.decodeLongFormDid({ didUrl: longFormDid}); + + expect(createRequest).to.have.property('delta'); + expect(createRequest).to.have.property('suffixData'); + expect(createRequest).to.have.property('type', 'create'); + + expect(createRequest.delta).to.have.property('updateCommitment', 'EiDvXrMWut-3HQjlNnIlyJGatYPl5j60ZwJpxpop8y6DlA'); + expect(createRequest.suffixData).to.have.property('recoveryCommitment', 'EiDHHoq4lXh2wcYFMJsn6FA7z-VOlLpbSm1rtM9yI1D3ww'); + + expect(createRequest.delta.patches[0].document.services).to.deep.equal([{ + 'id' : 'dwn', + 'type' : 'DecentralizedWebNode', + 'serviceEndpoint' : { + 'nodes': ['https://dwn.tbddev.test/dwn0'] + } + }]); + }); + + it('returns output that matches ION create request', async () => { + const services: DidService[] = [{ + 'id' : 'dwn', + 'type' : 'DecentralizedWebNode', + 'serviceEndpoint' : { + 'nodes': ['https://dwn.tbddev.test/dwn0'] + } + }]; + + const { did } = await DidIonMethod.create({ + keySet: testKeySet, + services + }); + + // @ts-expect-error because we're intentionally accessing a private method. + const ionDocument = await DidIonMethod.createIonDocument({ + keySet: testKeySet, + services + }); + + if (!testKeySet.recoveryKey) throw new Error('Type guard'); + if (!testKeySet.updateKey) throw new Error('Type guard'); + // @ts-expect-error because we're intentionally accessing a private method. + const createRequest = await DidIonMethod.getIonCreateRequest({ + ionDocument, + recoveryPublicKeyJwk : testKeySet.recoveryKey.publicKeyJwk, + updatePublicKeyJwk : testKeySet.updateKey.publicKeyJwk + }); + + if (!did) throw Error('Type guard'); + const decodedLongFormDid = await DidIonMethod.decodeLongFormDid({ didUrl: did }); + + expect(decodedLongFormDid).to.deep.equal(createRequest); + }); + }); + + describe('getDefaultSigningKey()', () => { + it('returns the did:ion default signing key from long form DID, when present', async () => { + const partialDidDocument: Partial = { + id : 'did:ion:EiAO3IAedMSHaGOZIuIVwLEBHd0SEuWwt2h00dbiGD7Hww:eyJkZWx0YSI6eyJwYXRjaGVzIjpbeyJhY3Rpb24iOiJyZXBsYWNlIiwiZG9jdW1lbnQiOnsicHVibGljS2V5cyI6W3siaWQiOiJkd24tc2lnIiwicHVibGljS2V5SndrIjp7ImNydiI6IkVkMjU1MTkiLCJrdHkiOiJPS1AiLCJ4IjoicnBLbkRQOEY0X2p3dlE3eERra3VLeDE2NU9Td2N5clF2bUVXbDJlaWdJVSJ9LCJwdXJwb3NlcyI6WyJhdXRoZW50aWNhdGlvbiJdLCJ0eXBlIjoiSnNvbldlYktleTIwMjAifV0sInNlcnZpY2VzIjpbXX19XSwidXBkYXRlQ29tbWl0bWVudCI6IkVpRFZyOHUzVWxvOGtNVUx3WEh6VUdSMFdGdy1ROU14el8zRGQyQXEwVF9KR3cifSwic3VmZml4RGF0YSI6eyJkZWx0YUhhc2giOiJFaUJOX1JaeXZka1lmb2tkRlV5MTNiWnFwR2gzdmhZU3IxVnh3MmVieE5uQzZRIiwicmVjb3ZlcnlDb21taXRtZW50IjoiRWlEOEQtdjlsVjdqTzZ3ajVjSXVsRXRwZEFqaHE5NEFnTm54SlozWThVUnlrZyJ9fQ', + service : [ + { + id : '#dwn', + type : 'DecentralizedWebNode', + serviceEndpoint : { + encryptionKeys: [ + '#dwn-enc' + ], + nodes: [ + 'https://dwn.tbddev.test/dwn0', + 'https://dwn.tbddev.test/dwn1' + ], + signingKeys: [ + '#dwn-sig' + ] + } + } + ], + }; + + const defaultSigningKeyId = await DidIonMethod.getDefaultSigningKey({ + didDocument: partialDidDocument as DidDocument + }); + + expect(defaultSigningKeyId).to.equal('did:ion:EiAO3IAedMSHaGOZIuIVwLEBHd0SEuWwt2h00dbiGD7Hww:eyJkZWx0YSI6eyJwYXRjaGVzIjpbeyJhY3Rpb24iOiJyZXBsYWNlIiwiZG9jdW1lbnQiOnsicHVibGljS2V5cyI6W3siaWQiOiJkd24tc2lnIiwicHVibGljS2V5SndrIjp7ImNydiI6IkVkMjU1MTkiLCJrdHkiOiJPS1AiLCJ4IjoicnBLbkRQOEY0X2p3dlE3eERra3VLeDE2NU9Td2N5clF2bUVXbDJlaWdJVSJ9LCJwdXJwb3NlcyI6WyJhdXRoZW50aWNhdGlvbiJdLCJ0eXBlIjoiSnNvbldlYktleTIwMjAifV0sInNlcnZpY2VzIjpbXX19XSwidXBkYXRlQ29tbWl0bWVudCI6IkVpRFZyOHUzVWxvOGtNVUx3WEh6VUdSMFdGdy1ROU14el8zRGQyQXEwVF9KR3cifSwic3VmZml4RGF0YSI6eyJkZWx0YUhhc2giOiJFaUJOX1JaeXZka1lmb2tkRlV5MTNiWnFwR2gzdmhZU3IxVnh3MmVieE5uQzZRIiwicmVjb3ZlcnlDb21taXRtZW50IjoiRWlEOEQtdjlsVjdqTzZ3ajVjSXVsRXRwZEFqaHE5NEFnTm54SlozWThVUnlrZyJ9fQ#dwn-sig'); + }); + + it('returns the did:ion default signing key from short form DID, when present', async () => { + const partialDidDocument: Partial = { + id : 'did:ion:EiAO3IAedMSHaGOZIuIVwLEBHd0SEuWwt2h00dbiGD7Hww:eyJkZWx0YSI6eyJwYXRjaGVzIjpbeyJhY3Rpb24iOiJyZXBsYWNlIiwiZG9jdW1lbnQiOnsicHVibGljS2V5cyI6W3siaWQiOiJkd24tc2lnIiwicHVibGljS2V5SndrIjp7ImNydiI6IkVkMjU1MTkiLCJrdHkiOiJPS1AiLCJ4IjoicnBLbkRQOEY0X2p3dlE3eERra3VLeDE2NU9Td2N5clF2bUVXbDJlaWdJVSJ9LCJwdXJwb3NlcyI6WyJhdXRoZW50aWNhdGlvbiJdLCJ0eXBlIjoiSnNvbldlYktleTIwMjAifV0sInNlcnZpY2VzIjpbXX19XSwidXBkYXRlQ29tbWl0bWVudCI6IkVpRFZyOHUzVWxvOGtNVUx3WEh6VUdSMFdGdy1ROU14el8zRGQyQXEwVF9KR3cifSwic3VmZml4RGF0YSI6eyJkZWx0YUhhc2giOiJFaUJOX1JaeXZka1lmb2tkRlV5MTNiWnFwR2gzdmhZU3IxVnh3MmVieE5uQzZRIiwicmVjb3ZlcnlDb21taXRtZW50IjoiRWlEOEQtdjlsVjdqTzZ3ajVjSXVsRXRwZEFqaHE5NEFnTm54SlozWThVUnlrZyJ9fQ', + service : [ + { + id : '#dwn', + type : 'DecentralizedWebNode', + serviceEndpoint : { + encryptionKeys: [ + '#dwn-enc' + ], + nodes: [ + 'https://dwn.tbddev.test/dwn0', + 'https://dwn.tbddev.test/dwn1' + ], + signingKeys: [ + '#dwn-sig' + ] + } + } + ], + }; + + const defaultSigningKeyId = await DidIonMethod.getDefaultSigningKey({ + didDocument: partialDidDocument as DidDocument + }); + + expect(defaultSigningKeyId).to.equal('did:ion:EiAO3IAedMSHaGOZIuIVwLEBHd0SEuWwt2h00dbiGD7Hww:eyJkZWx0YSI6eyJwYXRjaGVzIjpbeyJhY3Rpb24iOiJyZXBsYWNlIiwiZG9jdW1lbnQiOnsicHVibGljS2V5cyI6W3siaWQiOiJkd24tc2lnIiwicHVibGljS2V5SndrIjp7ImNydiI6IkVkMjU1MTkiLCJrdHkiOiJPS1AiLCJ4IjoicnBLbkRQOEY0X2p3dlE3eERra3VLeDE2NU9Td2N5clF2bUVXbDJlaWdJVSJ9LCJwdXJwb3NlcyI6WyJhdXRoZW50aWNhdGlvbiJdLCJ0eXBlIjoiSnNvbldlYktleTIwMjAifV0sInNlcnZpY2VzIjpbXX19XSwidXBkYXRlQ29tbWl0bWVudCI6IkVpRFZyOHUzVWxvOGtNVUx3WEh6VUdSMFdGdy1ROU14el8zRGQyQXEwVF9KR3cifSwic3VmZml4RGF0YSI6eyJkZWx0YUhhc2giOiJFaUJOX1JaeXZka1lmb2tkRlV5MTNiWnFwR2gzdmhZU3IxVnh3MmVieE5uQzZRIiwicmVjb3ZlcnlDb21taXRtZW50IjoiRWlEOEQtdjlsVjdqTzZ3ajVjSXVsRXRwZEFqaHE5NEFnTm54SlozWThVUnlrZyJ9fQ#dwn-sig'); + }); + + it(`returns first 'authentication' key if DID document is missing 'signingKeys'`, async () => { + const partialDidDocument: Partial = { + id : 'did:ion:EiAO3IAedMSHaGOZIuIVwLEBHd0SEuWwt2h00dbiGD7Hww:eyJkZWx0YSI6eyJwYXRjaGVzIjpbeyJhY3Rpb24iOiJyZXBsYWNlIiwiZG9jdW1lbnQiOnsicHVibGljS2V5cyI6W3siaWQiOiJkd24tc2lnIiwicHVibGljS2V5SndrIjp7ImNydiI6IkVkMjU1MTkiLCJrdHkiOiJPS1AiLCJ4IjoicnBLbkRQOEY0X2p3dlE3eERra3VLeDE2NU9Td2N5clF2bUVXbDJlaWdJVSJ9LCJwdXJwb3NlcyI6WyJhdXRoZW50aWNhdGlvbiJdLCJ0eXBlIjoiSnNvbldlYktleTIwMjAifV0sInNlcnZpY2VzIjpbXX19XSwidXBkYXRlQ29tbWl0bWVudCI6IkVpRFZyOHUzVWxvOGtNVUx3WEh6VUdSMFdGdy1ROU14el8zRGQyQXEwVF9KR3cifSwic3VmZml4RGF0YSI6eyJkZWx0YUhhc2giOiJFaUJOX1JaeXZka1lmb2tkRlV5MTNiWnFwR2gzdmhZU3IxVnh3MmVieE5uQzZRIiwicmVjb3ZlcnlDb21taXRtZW50IjoiRWlEOEQtdjlsVjdqTzZ3ajVjSXVsRXRwZEFqaHE5NEFnTm54SlozWThVUnlrZyJ9fQ', + 'verificationMethod' : [ + { + id : '#OAPj7ObrEJFgVNA2rrkPM5A-vYVsH_lyz4LgOUdJBa8', + type : 'JsonWebKey2020', + controller : 'did:ion:EiBP6JaGhwYye4zz-wdeXR2JWl1JclaVDPA7FDgpzM8-ig:eyJkZWx0YSI6eyJwYXRjaGVzIjpbeyJhY3Rpb24iOiJyZXBsYWNlIiwiZG9jdW1lbnQiOnsicHVibGljS2V5cyI6W3siaWQiOiJPQVBqN09ickVKRmdWTkEycnJrUE01QS12WVZzSF9seXo0TGdPVWRKQmE4IiwicHVibGljS2V5SndrIjp7ImNydiI6IkVkMjU1MTkiLCJrdHkiOiJPS1AiLCJ4IjoicnBLbkRQOEY0X2p3dlE3eERra3VLeDE2NU9Td2N5clF2bUVXbDJlaWdJVSJ9LCJwdXJwb3NlcyI6WyJhdXRoZW50aWNhdGlvbiJdLCJ0eXBlIjoiSnNvbldlYktleTIwMjAifV0sInNlcnZpY2VzIjpbXX19XSwidXBkYXRlQ29tbWl0bWVudCI6IkVpRFZyOHUzVWxvOGtNVUx3WEh6VUdSMFdGdy1ROU14el8zRGQyQXEwVF9KR3cifSwic3VmZml4RGF0YSI6eyJkZWx0YUhhc2giOiJFaUNUMEh4ZGNTRHkwQ0t5eHV4VkZ3d3A3N3YteEJkSkVRLUVtSXhZUGR4VnV3IiwicmVjb3ZlcnlDb21taXRtZW50IjoiRWlEOEQtdjlsVjdqTzZ3ajVjSXVsRXRwZEFqaHE5NEFnTm54SlozWThVUnlrZyJ9fQ', + publicKeyJwk : { + crv : 'Ed25519', + kty : 'OKP', + x : 'rpKnDP8F4_jwvQ7xDkkuKx165OSwcyrQvmEWl2eigIU' + } + } + ], + service: [ + { + id : '#dwn', + type : 'DecentralizedWebNode', + serviceEndpoint : { + encryptionKeys: [ + '#dwn-enc' + ], + nodes: [ + 'https://dwn.tbddev.test/dwn0', + 'https://dwn.tbddev.test/dwn1' + ] + } + } + ], + authentication: [ + '#OAPj7ObrEJFgVNA2rrkPM5A-vYVsH_lyz4LgOUdJBa8' + ], + }; + + const defaultSigningKeyId = await DidIonMethod.getDefaultSigningKey({ + didDocument: partialDidDocument as DidDocument + }); + + expect(defaultSigningKeyId).to.equal('did:ion:EiAO3IAedMSHaGOZIuIVwLEBHd0SEuWwt2h00dbiGD7Hww:eyJkZWx0YSI6eyJwYXRjaGVzIjpbeyJhY3Rpb24iOiJyZXBsYWNlIiwiZG9jdW1lbnQiOnsicHVibGljS2V5cyI6W3siaWQiOiJkd24tc2lnIiwicHVibGljS2V5SndrIjp7ImNydiI6IkVkMjU1MTkiLCJrdHkiOiJPS1AiLCJ4IjoicnBLbkRQOEY0X2p3dlE3eERra3VLeDE2NU9Td2N5clF2bUVXbDJlaWdJVSJ9LCJwdXJwb3NlcyI6WyJhdXRoZW50aWNhdGlvbiJdLCJ0eXBlIjoiSnNvbldlYktleTIwMjAifV0sInNlcnZpY2VzIjpbXX19XSwidXBkYXRlQ29tbWl0bWVudCI6IkVpRFZyOHUzVWxvOGtNVUx3WEh6VUdSMFdGdy1ROU14el8zRGQyQXEwVF9KR3cifSwic3VmZml4RGF0YSI6eyJkZWx0YUhhc2giOiJFaUJOX1JaeXZka1lmb2tkRlV5MTNiWnFwR2gzdmhZU3IxVnh3MmVieE5uQzZRIiwicmVjb3ZlcnlDb21taXRtZW50IjoiRWlEOEQtdjlsVjdqTzZ3ajVjSXVsRXRwZEFqaHE5NEFnTm54SlozWThVUnlrZyJ9fQ#OAPj7ObrEJFgVNA2rrkPM5A-vYVsH_lyz4LgOUdJBa8'); + }); + + it(`returns short form DID when DID has been anchored/published`, async () => { + let partialDidDocument: Partial = { + id : 'did:ion:EiAO3IAedMSHaGOZIuIVwLEBHd0SEuWwt2h00dbiGD7Hww', + service : [ + { + id : '#dwn', + type : 'DecentralizedWebNode', + serviceEndpoint : { + encryptionKeys: [ + '#dwn-enc' + ], + nodes: [ + 'https://dwn.tbddev.test/dwn0', + 'https://dwn.tbddev.test/dwn1' + ], + signingKeys: [ + '#dwn-sig' + ] + } + } + ], + }; + + let defaultSigningKeyId = await DidIonMethod.getDefaultSigningKey({ + didDocument: partialDidDocument as DidDocument + }); + + expect(defaultSigningKeyId).to.equal('did:ion:EiAO3IAedMSHaGOZIuIVwLEBHd0SEuWwt2h00dbiGD7Hww#dwn-sig'); + + partialDidDocument = { + 'id' : 'did:ion:EiCab9QRUcUTKKIM-W2SMCwnOPxa4y0q7emoWJDSOSz3HQ', + 'service' : [], + 'verificationMethod' : [ + { + 'id' : '#dwn-sig', + 'controller' : 'did:ion:EiCab9QRUcUTKKIM-W2SMCwnOPxa4y0q7emoWJDSOSz3HQ', + 'type' : 'JsonWebKey2020', + 'publicKeyJwk' : { + 'crv' : 'Ed25519', + 'kty' : 'OKP', + 'x' : 'Sy0lk6pMXC10WyIh4g8sLz1loL8ImzLcqmFW2267IXc' + } + } + ], + 'authentication': [ + '#dwn-sig' + ] + }; + + defaultSigningKeyId = await DidIonMethod.getDefaultSigningKey({ + didDocument: partialDidDocument as DidDocument + }); + + expect(defaultSigningKeyId).to.equal('did:ion:EiCab9QRUcUTKKIM-W2SMCwnOPxa4y0q7emoWJDSOSz3HQ#dwn-sig'); + }); + + it(`returns undefined if DID document is missing 'signingKeys' and 'authentication'`, async () => { + const partialDidDocument: Partial = { + id : 'did:ion:EiAO3IAedMSHaGOZIuIVwLEBHd0SEuWwt2h00dbiGD7Hww:eyJkZWx0YSI6eyJwYXRjaGVzIjpbeyJhY3Rpb24iOiJyZXBsYWNlIiwiZG9jdW1lbnQiOnsicHVibGljS2V5cyI6W3siaWQiOiJkd24tc2lnIiwicHVibGljS2V5SndrIjp7ImNydiI6IkVkMjU1MTkiLCJrdHkiOiJPS1AiLCJ4IjoicnBLbkRQOEY0X2p3dlE3eERra3VLeDE2NU9Td2N5clF2bUVXbDJlaWdJVSJ9LCJwdXJwb3NlcyI6WyJhdXRoZW50aWNhdGlvbiJdLCJ0eXBlIjoiSnNvbldlYktleTIwMjAifV0sInNlcnZpY2VzIjpbXX19XSwidXBkYXRlQ29tbWl0bWVudCI6IkVpRFZyOHUzVWxvOGtNVUx3WEh6VUdSMFdGdy1ROU14el8zRGQyQXEwVF9KR3cifSwic3VmZml4RGF0YSI6eyJkZWx0YUhhc2giOiJFaUJOX1JaeXZka1lmb2tkRlV5MTNiWnFwR2gzdmhZU3IxVnh3MmVieE5uQzZRIiwicmVjb3ZlcnlDb21taXRtZW50IjoiRWlEOEQtdjlsVjdqTzZ3ajVjSXVsRXRwZEFqaHE5NEFnTm54SlozWThVUnlrZyJ9fQ', + service : [ + { + id : '#dwn', + type : 'DecentralizedWebNode', + serviceEndpoint : { + encryptionKeys: [ + '#dwn-enc' + ], + nodes: [ + 'https://dwn.tbddev.test/dwn0', + 'https://dwn.tbddev.test/dwn1' + ] + } + } + ], + }; + + const defaultSigningKeyId = await DidIonMethod.getDefaultSigningKey({ + didDocument: partialDidDocument as DidDocument + }); + + expect(defaultSigningKeyId).to.be.undefined; + }); + + it(`throws error if DID document is missing 'id' property`, async () => { + const partialDidDocument: Partial = {}; + + await expect( + DidIonMethod.getDefaultSigningKey({ + didDocument: partialDidDocument as DidDocument + }) + ).to.eventually.be.rejectedWith(Error, `DID document is missing 'id' property`); + }); + }); + + describe('resolve()', () => { + it('resolves published short form ION DIDs', async() => { + const did = 'did:ion:EiCab9QRUcUTKKIM-W2SMCwnOPxa4y0q7emoWJDSOSz3HQ'; + const resolutionResult = await DidIonMethod.resolve({ didUrl: did }); + + expect(resolutionResult).to.have.property('@context'); + expect(resolutionResult).to.have.property('didDocument'); + expect(resolutionResult).to.have.property('didDocumentMetadata'); + + expect(resolutionResult.didDocument).to.have.property('id', did); + expect(resolutionResult.didDocumentMetadata.method).to.have.property('published', true); + }); + + it('returns notFound error with unpublished short form ION DIDs', async() => { + const did = 'did:ion:EiBCi7lnGtotBsFkbI_lQskQZLk_GPelU0C5-nRB4_nMfA'; + const resolutionResult = await DidIonMethod.resolve({ didUrl: did }); + + expect(resolutionResult).to.have.property('@context'); + expect(resolutionResult).to.have.property('didDocument'); + expect(resolutionResult).to.have.property('didDocumentMetadata'); + + expect(resolutionResult.didResolutionMetadata).to.have.property('error', 'notFound'); + }); + + it('resolves published long form ION DIDs', async() => { + const did = 'did:ion:EiAi68p2irCNQIzaui8gTjGDeOqSUusZS8jWVHfseSWZ5g:eyJkZWx0YSI6eyJwYXRjaGVzIjpbeyJhY3Rpb24iOiJyZXBsYWNlIiwiZG9jdW1lbnQiOnsicHVibGljS2V5cyI6W3siaWQiOiJrZXktMSIsInB1YmxpY0tleUp3ayI6eyJjcnYiOiJzZWNwMjU2azEiLCJrdHkiOiJFQyIsIngiOiI2MWlQWXVHZWZ4b3R6QmRRWnREdnY2Y1dIWm1YclRUc2NZLXU3WTJwRlpjIiwieSI6Ijg4blBDVkxmckFZOWktd2c1T1Jjd1ZiSFdDX3RiZUFkMUpFMmUwY28wbFUifSwicHVycG9zZXMiOlsiYXV0aGVudGljYXRpb24iXSwidHlwZSI6IkVjZHNhU2VjcDI1NmsxVmVyaWZpY2F0aW9uS2V5MjAxOSJ9XSwic2VydmljZXMiOlt7ImlkIjoiZHduIiwic2VydmljZUVuZHBvaW50Ijp7Im5vZGVzIjpbImh0dHA6Ly9sb2NhbGhvc3Q6ODA4NSJdfSwidHlwZSI6IkRlY2VudHJhbGl6ZWRXZWJOb2RlIn1dfX1dLCJ1cGRhdGVDb21taXRtZW50IjoiRWlCb1c2dGs4WlZRTWs3YjFubkF2R3F3QTQ2amlaaUc2dWNYemxyNTZDWWFiUSJ9LCJzdWZmaXhEYXRhIjp7ImRlbHRhSGFzaCI6IkVpQ3Y2cUhEMFV4TTBadmZlTHU4dDR4eU5DVjNscFBSaTl6a3paU3h1LW8wWUEiLCJyZWNvdmVyeUNvbW1pdG1lbnQiOiJFaUN0STM0ckdGNU9USkJETXRUYm14a1lQeC0ydFd3MldZLTU2UTVPNHR0WWJBIn19'; + const resolutionResult = await DidIonMethod.resolve({ didUrl: did }); + + expect(resolutionResult).to.have.property('@context'); + expect(resolutionResult).to.have.property('didDocument'); + expect(resolutionResult).to.have.property('didDocumentMetadata'); + + expect(resolutionResult.didDocument).to.have.property('id', did); + expect(resolutionResult.didDocumentMetadata.method).to.have.property('published', true); + }); + + + it('resolves unpublished long form ION DIDs', async() => { + const did = 'did:ion:EiBCi7lnGtotBsFkbI_lQskQZLk_GPelU0C5-nRB4_nMfA:eyJkZWx0YSI6eyJwYXRjaGVzIjpbeyJhY3Rpb24iOiJyZXBsYWNlIiwiZG9jdW1lbnQiOnsicHVibGljS2V5cyI6W3siaWQiOiJkd24tc2lnIiwicHVibGljS2V5SndrIjp7ImNydiI6IkVkMjU1MTkiLCJrdHkiOiJPS1AiLCJ4Ijoib0xVQmdKUnA1dlVfSTdfOXB3UTFkb2IwSWg2VjUwT2FrenNOY2R6Uk1CbyJ9LCJwdXJwb3NlcyI6WyJhdXRoZW50aWNhdGlvbiJdLCJ0eXBlIjoiSnNvbldlYktleTIwMjAifV0sInNlcnZpY2VzIjpbXX19XSwidXBkYXRlQ29tbWl0bWVudCI6IkVpQlRRYlV6cmlTU3FEVVpPb0JvUTZWek5wWFRvQWNtSjNHMlBIZzJ3ZXpFcHcifSwic3VmZml4RGF0YSI6eyJkZWx0YUhhc2giOiJFaURLSFlkRFRpT3lCTWRORWtBcGJtUklHU1ExOFctUHFUeGlrZ0IzX1RpSlVBIiwicmVjb3ZlcnlDb21taXRtZW50IjoiRWlBb2pZYzV6eTR2RFZFdElnS1lzWHgtdnBnZzNEeXBUOW0tRmtfMXZ0WHBkQSJ9fQ'; + const resolutionResult = await DidIonMethod.resolve({ didUrl: did }); + + expect(resolutionResult).to.have.property('@context'); + expect(resolutionResult).to.have.property('didDocument'); + expect(resolutionResult).to.have.property('didDocumentMetadata'); + + expect(resolutionResult.didDocument).to.have.property('id', did); + expect(resolutionResult.didDocumentMetadata.method).to.have.property('published', false); + }); + + it('returns internalError if custom DID resolver returns invalid response', async () => { + // Setup stub so that a mocked response is returned rather than calling over the network. + const mockResult = ` + 404 Not Found + +

404 Not Found

+
nginx/1.25.1
+ + `; + const fetchStub = sinon.stub(global, 'fetch'); + // @ts-expect-error because we're only mocking ok and json() from global.fetch(). + fetchStub.returns(Promise.resolve({ + ok : false, + json : () => Promise.reject(JSON.parse(mockResult)) + })); + + const did = 'did:ion:EiCab9QRUcUTKKIM-W2SMCwnOPxa4y0q7emoWJDSOSz3HQ'; + const resolutionResult = await DidIonMethod.resolve({ + didUrl : did, + resolutionOptions : { resolutionEndpoint: 'https://dev.uniresolver.io/7.5/identifiers' } + }); + fetchStub.restore(); + + expect(resolutionResult).to.have.property('@context'); + expect(resolutionResult).to.have.property('didDocument'); + expect(resolutionResult).to.have.property('didDocumentMetadata'); + + expect(resolutionResult.didResolutionMetadata).to.have.property('error', 'internalError'); + }); + + it(`returns methodNotSupported if DID method is not 'ion'`, async () => { + const did = 'did:key:z6MkvEvogvhMEv9bXLyDXdqSSvvh5goAMtUruYwCbFpuhDjx'; + const resolutionResult = await DidIonMethod.resolve({ didUrl: did }); + expect(resolutionResult).to.have.property('@context'); + expect(resolutionResult).to.have.property('didDocument'); + expect(resolutionResult).to.have.property('didDocumentMetadata'); + + expect(resolutionResult.didResolutionMetadata).to.have.property('error', 'methodNotSupported'); + }); + + it('accepts custom DID resolver with trailing slash', async () => { + const did = 'did:ion:EiCab9QRUcUTKKIM-W2SMCwnOPxa4y0q7emoWJDSOSz3HQ'; + const resolutionResult = await DidIonMethod.resolve({ + didUrl : did, + resolutionOptions : { resolutionEndpoint: 'https://dev.uniresolver.io/1.0/identifiers/' } + }); + expect(resolutionResult).to.have.property('@context'); + expect(resolutionResult).to.have.property('didDocument'); + expect(resolutionResult).to.have.property('didDocumentMetadata'); + + expect(resolutionResult.didDocument).to.have.property('id', did); + }); + + it('accepts custom DID resolver without trailing slash', async () => { + const did = 'did:ion:EiCab9QRUcUTKKIM-W2SMCwnOPxa4y0q7emoWJDSOSz3HQ'; + const resolutionResult = await DidIonMethod.resolve({ + didUrl : did, + resolutionOptions : { resolutionEndpoint: 'https://dev.uniresolver.io/1.0/identifiers' } + }); + expect(resolutionResult).to.have.property('@context'); + expect(resolutionResult).to.have.property('didDocument'); + expect(resolutionResult).to.have.property('didDocumentMetadata'); + + expect(resolutionResult.didDocument).to.have.property('id', did); }); }); }); \ No newline at end of file diff --git a/packages/dids/tests/did-key.spec.ts b/packages/dids/tests/did-key.spec.ts index ea5775ee3..682d2dbe4 100644 --- a/packages/dids/tests/did-key.spec.ts +++ b/packages/dids/tests/did-key.spec.ts @@ -1,27 +1,108 @@ import { expect } from 'chai'; -import { DidKeyApi } from '../src/did-key.js'; -const DidKey = new DidKeyApi(); +import type { DidKeyCreateOptions, DidKeyCreateDocumentOptions } from '../src/did-key.js'; -describe('DidKeyApi', () => { - it('works', async () => { - const didState = await DidKey.create(); +import { DidKeyMethod } from '../src/did-key.js'; +import { didKeyCreateTestVectors, didKeyCreateDocumentTestVectors, } from './fixtures/test-vectors/did-key.js'; +import { DidDocument } from '../src/types.js'; - expect(didState.id).to.exist; - expect(didState.internalId).to.exist; - expect(didState.keys).to.exist; +describe('DidKeyMethod', () => { + describe('create()', () => { + it('creates a DID with Ed25519 keys, by default', async () => { + const portableDid = await DidKeyMethod.create(); - expect(didState.methodData).to.exist; - expect(Object.keys(didState.methodData).length).to.equal(0); + // Verify expected result. + expect(portableDid).to.have.property('did'); + expect(portableDid).to.have.property('document'); + expect(portableDid).to.have.property('keySet'); + expect(portableDid.keySet).to.have.property('verificationMethodKeys'); + expect(portableDid.keySet.verificationMethodKeys).to.have.length(1); + expect(portableDid.keySet.verificationMethodKeys?.[0]).to.have.property('publicKeyJwk'); + expect(portableDid.keySet.verificationMethodKeys?.[0]).to.have.property('privateKeyJwk'); + expect(portableDid.keySet.verificationMethodKeys?.[0].publicKeyJwk).to.have.property('alg', 'EdDSA'); + expect(portableDid.keySet.verificationMethodKeys?.[0].publicKeyJwk).to.have.property('crv', 'Ed25519'); + }); - expect((didState as any).services).to.not.exist; + it('creates a DID with secp256k1 keys, if specified', async () => { + const portableDid = await DidKeyMethod.create({ keyAlgorithm: 'secp256k1' }); - for (let key of didState.keys) { - expect(key.id).to.exist; - expect(key.controller).to.exist; - expect(key.publicKeyJwk).to.exist; - expect(key.privateKeyJwk).to.exist; - expect(key.type).to.exist; + // Verify expected result. + expect(portableDid).to.have.property('did'); + expect(portableDid).to.have.property('document'); + expect(portableDid).to.have.property('keySet'); + expect(portableDid.keySet).to.have.property('verificationMethodKeys'); + expect(portableDid.keySet.verificationMethodKeys).to.have.length(1); + expect(portableDid.keySet.verificationMethodKeys?.[0]).to.have.property('publicKeyJwk'); + expect(portableDid.keySet.verificationMethodKeys?.[0]).to.have.property('privateKeyJwk'); + expect(portableDid.keySet.verificationMethodKeys?.[0].publicKeyJwk).to.have.property('alg', 'ES256K'); + expect(portableDid.keySet.verificationMethodKeys?.[0].publicKeyJwk).to.have.property('crv', 'secp256k1'); + }); + + for (const vector of didKeyCreateTestVectors ) { + it(`passes test vector ${vector.id}`, async () => { + const portableDid = await DidKeyMethod.create(vector.input as DidKeyCreateOptions); + + expect(portableDid).to.deep.equal(vector.output); + }); + } + }); + + describe('createDocument()', () => { + it('accepts an alternate default context', async () => { + const didDocument = await DidKeyMethod.createDocument({ + did : 'did:key:z6MkjVM3rLLh9KCFBfKPNA5oEBq6KXXsPu72FDX7cZzYJN3y', + defaultContext : 'https://www.w3.org/ns/did/v99', + publicKeyFormat : 'JsonWebKey2020' + }); + + expect(didDocument['@context']).to.include('https://www.w3.org/ns/did/v99'); + }); + + for (const vector of didKeyCreateDocumentTestVectors ) { + it(`passes test vector ${vector.id}`, async () => { + const didDocument = await DidKeyMethod.createDocument(vector.input as DidKeyCreateDocumentOptions); + expect(didDocument).to.deep.equal(vector.output); + }); } }); + + describe('getDefaultSigningKey()', () => { + it('returns the did:key default signing key, when present', async () => { + const partialDidDocument = { + authentication: [ + 'did:key:z6MkrCigh4zugDVEieqt4WbtWParigHeH5TEYEuKcSyCykUk#z6MkrCigh4zugDVEieqt4WbtWParigHeH5TEYEuKcSyCykUk' + ] + } as unknown as DidDocument; + + const defaultSigningKeyId = await DidKeyMethod.getDefaultSigningKey({ + didDocument: partialDidDocument + }); + + expect(defaultSigningKeyId).to.equal('did:key:z6MkrCigh4zugDVEieqt4WbtWParigHeH5TEYEuKcSyCykUk#z6MkrCigh4zugDVEieqt4WbtWParigHeH5TEYEuKcSyCykUk'); + }); + + it('returns undefined if the did:key default signing key is not present', async () => { + const partialDidDocument = { + authentication: [{ + id : 'did:key:z6LSgmjjYTAffdKWLmBbYxe5d5fgLzuZxi6PEbHZNt3Cifvg#z6LSgmjjYTAffdKWLmBbYxe5d5fgLzuZxi6PEbHZNt3Cifvg', + type : 'JsonWebKey2020', + controller : 'did:key:z6LSgmjjYTAffdKWLmBbYxe5d5fgLzuZxi6PEbHZNt3Cifvg', + publicKeyJwk : { + kty : 'OKP', + crv : 'X25519', + x : 'S7cqN2_-PIPK6fVjR6PrQ1YZyyw61ajVnAJClFcXVhk' + } + }], + keyAgreement: [ + 'did:key:z6LSqCkip7X19obTwRpWc8ZLLCiXLzVQBFpcBAsTW38m6Rzs#z6LSqCkip7X19obTwRpWc8ZLLCiXLzVQBFpcBAsTW38m6Rzs' + ] + } as unknown as DidDocument; + + const defaultSigningKeyId = await DidKeyMethod.getDefaultSigningKey({ + didDocument: partialDidDocument + }); + + expect(defaultSigningKeyId).to.be.undefined; + }); + }); }); \ No newline at end of file diff --git a/packages/dids/tests/did-resolver.spec.ts b/packages/dids/tests/did-resolver.spec.ts new file mode 100644 index 000000000..1d5a67c2f --- /dev/null +++ b/packages/dids/tests/did-resolver.spec.ts @@ -0,0 +1,23 @@ +import { expect } from 'chai'; + +import { DidKeyMethod } from '../src/did-key.js'; +import { DidResolver } from '../src/did-resolver.js'; +import { didResolverTestVectors } from './fixtures/test-vectors/did-resolver.js'; + +describe('DidResolver', () => { + describe('resolve()', () => { + let didResolver: DidResolver; + + beforeEach(() => { + const didMethodApis = [DidKeyMethod]; + didResolver = new DidResolver({ didResolvers: didMethodApis }); + }); + + it('passes test vectors', async () => { + for (const vector of didResolverTestVectors) { + const didResolutionResult = await didResolver.resolve(vector.input); + expect(didResolutionResult.didDocument).to.deep.equal(vector.output); + } + }); + }); +}); \ No newline at end of file diff --git a/packages/dids/tests/fixtures/test-vectors/did-ion.ts b/packages/dids/tests/fixtures/test-vectors/did-ion.ts new file mode 100644 index 000000000..872e6047d --- /dev/null +++ b/packages/dids/tests/fixtures/test-vectors/did-ion.ts @@ -0,0 +1,448 @@ +export const didIonCreateTestVectors = [ + { + id : 'did.create.1', + input : { + keySet: { + recoveryKey: { + privateKeyJwk: { + alg : 'ES256K', + crv : 'secp256k1', + d : 'cy4EzFT9K0sdCcz0gnctkcPq0szOP-d8smA9Hvp5ejo', + ext : 'true', + key_ops : ['sign'], + kid : 'ion-recovery-1', + kty : 'EC', + x : 'vLvKcPjYVnJi6dZpq15PrJqdxBERuvL8EqvTh1_0ikg', + y : 'AsQstiUIt5tGyAyM7LzytsbdVbsb-5oWVNYIUEjOePs' + }, + publicKeyJwk: { + alg : 'ES256K', + crv : 'secp256k1', + ext : 'true', + key_ops : ['verify'], + kid : 'ion-recovery-1', + kty : 'EC', + x : 'vLvKcPjYVnJi6dZpq15PrJqdxBERuvL8EqvTh1_0ikg', + y : 'AsQstiUIt5tGyAyM7LzytsbdVbsb-5oWVNYIUEjOePs' + }, + }, + updateKey: { + privateKeyJwk: { + alg : 'ES256K', + crv : 'secp256k1', + d : 'j4VcN2I5uw5kwPC5e8rxDaA7OxkmrJ-2BgdyKwayO9E', + ext : 'true', + key_ops : ['sign'], + kid : 'ion-update-1', + kty : 'EC', + x : '4Z9Tt1tuFlI3YwJfT3eS72r0sa9UxdtalgW14gep2DQ', + y : 'FIAtUW8B54L0Y-0e9n_rc1GJhxMIkZ3iYGnMQufgT_s' + }, + publicKeyJwk: { + alg : 'ES256K', + crv : 'secp256k1', + ext : 'true', + key_ops : ['verify'], + kid : 'ion-update-1', + kty : 'EC', + x : '4Z9Tt1tuFlI3YwJfT3eS72r0sa9UxdtalgW14gep2DQ', + y : 'FIAtUW8B54L0Y-0e9n_rc1GJhxMIkZ3iYGnMQufgT_s' + } + }, + verificationMethodKeys: [{ + publicKeyJwk: { + alg : 'EdDSA', + crv : 'Ed25519', + kid : 'dwn-sig', + kty : 'OKP', + x : 'rpKnDP8F4_jwvQ7xDkkuKx165OSwcyrQvmEWl2eigIU' + }, + relationships: ['authentication'] + }], + }, + keyAlgorithm: 'Ed25519' + }, + output: { + canonicalId : 'did:ion:EiAO3IAedMSHaGOZIuIVwLEBHd0SEuWwt2h00dbiGD7Hww', + did : 'did:ion:EiAO3IAedMSHaGOZIuIVwLEBHd0SEuWwt2h00dbiGD7Hww:eyJkZWx0YSI6eyJwYXRjaGVzIjpbeyJhY3Rpb24iOiJyZXBsYWNlIiwiZG9jdW1lbnQiOnsicHVibGljS2V5cyI6W3siaWQiOiJkd24tc2lnIiwicHVibGljS2V5SndrIjp7ImNydiI6IkVkMjU1MTkiLCJrdHkiOiJPS1AiLCJ4IjoicnBLbkRQOEY0X2p3dlE3eERra3VLeDE2NU9Td2N5clF2bUVXbDJlaWdJVSJ9LCJwdXJwb3NlcyI6WyJhdXRoZW50aWNhdGlvbiJdLCJ0eXBlIjoiSnNvbldlYktleTIwMjAifV0sInNlcnZpY2VzIjpbXX19XSwidXBkYXRlQ29tbWl0bWVudCI6IkVpRFZyOHUzVWxvOGtNVUx3WEh6VUdSMFdGdy1ROU14el8zRGQyQXEwVF9KR3cifSwic3VmZml4RGF0YSI6eyJkZWx0YUhhc2giOiJFaUJOX1JaeXZka1lmb2tkRlV5MTNiWnFwR2gzdmhZU3IxVnh3MmVieE5uQzZRIiwicmVjb3ZlcnlDb21taXRtZW50IjoiRWlEOEQtdjlsVjdqTzZ3ajVjSXVsRXRwZEFqaHE5NEFnTm54SlozWThVUnlrZyJ9fQ', + document : { + '@context': [ + 'https://www.w3.org/ns/did/v1', + { + '@base': 'did:ion:EiAO3IAedMSHaGOZIuIVwLEBHd0SEuWwt2h00dbiGD7Hww:eyJkZWx0YSI6eyJwYXRjaGVzIjpbeyJhY3Rpb24iOiJyZXBsYWNlIiwiZG9jdW1lbnQiOnsicHVibGljS2V5cyI6W3siaWQiOiJkd24tc2lnIiwicHVibGljS2V5SndrIjp7ImNydiI6IkVkMjU1MTkiLCJrdHkiOiJPS1AiLCJ4IjoicnBLbkRQOEY0X2p3dlE3eERra3VLeDE2NU9Td2N5clF2bUVXbDJlaWdJVSJ9LCJwdXJwb3NlcyI6WyJhdXRoZW50aWNhdGlvbiJdLCJ0eXBlIjoiSnNvbldlYktleTIwMjAifV0sInNlcnZpY2VzIjpbXX19XSwidXBkYXRlQ29tbWl0bWVudCI6IkVpRFZyOHUzVWxvOGtNVUx3WEh6VUdSMFdGdy1ROU14el8zRGQyQXEwVF9KR3cifSwic3VmZml4RGF0YSI6eyJkZWx0YUhhc2giOiJFaUJOX1JaeXZka1lmb2tkRlV5MTNiWnFwR2gzdmhZU3IxVnh3MmVieE5uQzZRIiwicmVjb3ZlcnlDb21taXRtZW50IjoiRWlEOEQtdjlsVjdqTzZ3ajVjSXVsRXRwZEFqaHE5NEFnTm54SlozWThVUnlrZyJ9fQ' + }, + ], + id : 'did:ion:EiAO3IAedMSHaGOZIuIVwLEBHd0SEuWwt2h00dbiGD7Hww:eyJkZWx0YSI6eyJwYXRjaGVzIjpbeyJhY3Rpb24iOiJyZXBsYWNlIiwiZG9jdW1lbnQiOnsicHVibGljS2V5cyI6W3siaWQiOiJkd24tc2lnIiwicHVibGljS2V5SndrIjp7ImNydiI6IkVkMjU1MTkiLCJrdHkiOiJPS1AiLCJ4IjoicnBLbkRQOEY0X2p3dlE3eERra3VLeDE2NU9Td2N5clF2bUVXbDJlaWdJVSJ9LCJwdXJwb3NlcyI6WyJhdXRoZW50aWNhdGlvbiJdLCJ0eXBlIjoiSnNvbldlYktleTIwMjAifV0sInNlcnZpY2VzIjpbXX19XSwidXBkYXRlQ29tbWl0bWVudCI6IkVpRFZyOHUzVWxvOGtNVUx3WEh6VUdSMFdGdy1ROU14el8zRGQyQXEwVF9KR3cifSwic3VmZml4RGF0YSI6eyJkZWx0YUhhc2giOiJFaUJOX1JaeXZka1lmb2tkRlV5MTNiWnFwR2gzdmhZU3IxVnh3MmVieE5uQzZRIiwicmVjb3ZlcnlDb21taXRtZW50IjoiRWlEOEQtdjlsVjdqTzZ3ajVjSXVsRXRwZEFqaHE5NEFnTm54SlozWThVUnlrZyJ9fQ', + 'verificationMethod' : [ + { + id : '#dwn-sig', + type : 'JsonWebKey2020', + controller : 'did:ion:EiAO3IAedMSHaGOZIuIVwLEBHd0SEuWwt2h00dbiGD7Hww:eyJkZWx0YSI6eyJwYXRjaGVzIjpbeyJhY3Rpb24iOiJyZXBsYWNlIiwiZG9jdW1lbnQiOnsicHVibGljS2V5cyI6W3siaWQiOiJkd24tc2lnIiwicHVibGljS2V5SndrIjp7ImNydiI6IkVkMjU1MTkiLCJrdHkiOiJPS1AiLCJ4IjoicnBLbkRQOEY0X2p3dlE3eERra3VLeDE2NU9Td2N5clF2bUVXbDJlaWdJVSJ9LCJwdXJwb3NlcyI6WyJhdXRoZW50aWNhdGlvbiJdLCJ0eXBlIjoiSnNvbldlYktleTIwMjAifV0sInNlcnZpY2VzIjpbXX19XSwidXBkYXRlQ29tbWl0bWVudCI6IkVpRFZyOHUzVWxvOGtNVUx3WEh6VUdSMFdGdy1ROU14el8zRGQyQXEwVF9KR3cifSwic3VmZml4RGF0YSI6eyJkZWx0YUhhc2giOiJFaUJOX1JaeXZka1lmb2tkRlV5MTNiWnFwR2gzdmhZU3IxVnh3MmVieE5uQzZRIiwicmVjb3ZlcnlDb21taXRtZW50IjoiRWlEOEQtdjlsVjdqTzZ3ajVjSXVsRXRwZEFqaHE5NEFnTm54SlozWThVUnlrZyJ9fQ', + publicKeyJwk : { + crv : 'Ed25519', + kty : 'OKP', + x : 'rpKnDP8F4_jwvQ7xDkkuKx165OSwcyrQvmEWl2eigIU' + } + } + ], + authentication: [ + '#dwn-sig' + ], + service: [] + }, + keySet: { + recoveryKey: { + privateKeyJwk: { + alg : 'ES256K', + crv : 'secp256k1', + d : 'cy4EzFT9K0sdCcz0gnctkcPq0szOP-d8smA9Hvp5ejo', + ext : 'true', + key_ops : ['sign'], + kid : 'ion-recovery-1', + kty : 'EC', + x : 'vLvKcPjYVnJi6dZpq15PrJqdxBERuvL8EqvTh1_0ikg', + y : 'AsQstiUIt5tGyAyM7LzytsbdVbsb-5oWVNYIUEjOePs' + }, + publicKeyJwk: { + alg : 'ES256K', + crv : 'secp256k1', + ext : 'true', + key_ops : ['verify'], + kid : 'ion-recovery-1', + kty : 'EC', + x : 'vLvKcPjYVnJi6dZpq15PrJqdxBERuvL8EqvTh1_0ikg', + y : 'AsQstiUIt5tGyAyM7LzytsbdVbsb-5oWVNYIUEjOePs' + }, + }, + updateKey: { + privateKeyJwk: { + alg : 'ES256K', + crv : 'secp256k1', + d : 'j4VcN2I5uw5kwPC5e8rxDaA7OxkmrJ-2BgdyKwayO9E', + ext : 'true', + key_ops : ['sign'], + kid : 'ion-update-1', + kty : 'EC', + x : '4Z9Tt1tuFlI3YwJfT3eS72r0sa9UxdtalgW14gep2DQ', + y : 'FIAtUW8B54L0Y-0e9n_rc1GJhxMIkZ3iYGnMQufgT_s' + }, + publicKeyJwk: { + alg : 'ES256K', + crv : 'secp256k1', + ext : 'true', + key_ops : ['verify'], + kid : 'ion-update-1', + kty : 'EC', + x : '4Z9Tt1tuFlI3YwJfT3eS72r0sa9UxdtalgW14gep2DQ', + y : 'FIAtUW8B54L0Y-0e9n_rc1GJhxMIkZ3iYGnMQufgT_s' + } + }, + verificationMethodKeys: [{ + publicKeyJwk: { + alg : 'EdDSA', + crv : 'Ed25519', + kid : 'dwn-sig', + kty : 'OKP', + x : 'rpKnDP8F4_jwvQ7xDkkuKx165OSwcyrQvmEWl2eigIU' + }, + relationships: ['authentication'] + }], + }, + } + }, + { + id : 'did.create.2', + input : { + keySet: { + recoveryKey: { + privateKeyJwk: { + alg : 'ES256K', + crv : 'secp256k1', + d : 'cy4EzFT9K0sdCcz0gnctkcPq0szOP-d8smA9Hvp5ejo', + ext : 'true', + key_ops : ['sign'], + kty : 'EC', + x : 'vLvKcPjYVnJi6dZpq15PrJqdxBERuvL8EqvTh1_0ikg', + y : 'AsQstiUIt5tGyAyM7LzytsbdVbsb-5oWVNYIUEjOePs' + }, + publicKeyJwk: { + alg : 'ES256K', + crv : 'secp256k1', + ext : 'true', + key_ops : ['verify'], + kty : 'EC', + x : 'vLvKcPjYVnJi6dZpq15PrJqdxBERuvL8EqvTh1_0ikg', + y : 'AsQstiUIt5tGyAyM7LzytsbdVbsb-5oWVNYIUEjOePs' + }, + }, + updateKey: { + privateKeyJwk: { + alg : 'ES256K', + crv : 'secp256k1', + d : 'j4VcN2I5uw5kwPC5e8rxDaA7OxkmrJ-2BgdyKwayO9E', + ext : 'true', + key_ops : ['sign'], + kty : 'EC', + x : '4Z9Tt1tuFlI3YwJfT3eS72r0sa9UxdtalgW14gep2DQ', + y : 'FIAtUW8B54L0Y-0e9n_rc1GJhxMIkZ3iYGnMQufgT_s' + }, + publicKeyJwk: { + alg : 'ES256K', + crv : 'secp256k1', + ext : 'true', + key_ops : ['verify'], + kty : 'EC', + x : '4Z9Tt1tuFlI3YwJfT3eS72r0sa9UxdtalgW14gep2DQ', + y : 'FIAtUW8B54L0Y-0e9n_rc1GJhxMIkZ3iYGnMQufgT_s' + } + }, + verificationMethodKeys: [{ + publicKeyJwk: { + alg : 'EdDSA', + crv : 'Ed25519', + kty : 'OKP', + x : 'rpKnDP8F4_jwvQ7xDkkuKx165OSwcyrQvmEWl2eigIU' + }, + relationships: ['authentication'] + }], + }, + keyAlgorithm: 'Ed25519' + }, + output: { + canonicalId : 'did:ion:EiBP6JaGhwYye4zz-wdeXR2JWl1JclaVDPA7FDgpzM8-ig', + did : 'did:ion:EiBP6JaGhwYye4zz-wdeXR2JWl1JclaVDPA7FDgpzM8-ig:eyJkZWx0YSI6eyJwYXRjaGVzIjpbeyJhY3Rpb24iOiJyZXBsYWNlIiwiZG9jdW1lbnQiOnsicHVibGljS2V5cyI6W3siaWQiOiJPQVBqN09ickVKRmdWTkEycnJrUE01QS12WVZzSF9seXo0TGdPVWRKQmE4IiwicHVibGljS2V5SndrIjp7ImNydiI6IkVkMjU1MTkiLCJrdHkiOiJPS1AiLCJ4IjoicnBLbkRQOEY0X2p3dlE3eERra3VLeDE2NU9Td2N5clF2bUVXbDJlaWdJVSJ9LCJwdXJwb3NlcyI6WyJhdXRoZW50aWNhdGlvbiJdLCJ0eXBlIjoiSnNvbldlYktleTIwMjAifV0sInNlcnZpY2VzIjpbXX19XSwidXBkYXRlQ29tbWl0bWVudCI6IkVpRFZyOHUzVWxvOGtNVUx3WEh6VUdSMFdGdy1ROU14el8zRGQyQXEwVF9KR3cifSwic3VmZml4RGF0YSI6eyJkZWx0YUhhc2giOiJFaUNUMEh4ZGNTRHkwQ0t5eHV4VkZ3d3A3N3YteEJkSkVRLUVtSXhZUGR4VnV3IiwicmVjb3ZlcnlDb21taXRtZW50IjoiRWlEOEQtdjlsVjdqTzZ3ajVjSXVsRXRwZEFqaHE5NEFnTm54SlozWThVUnlrZyJ9fQ', + document : { + '@context': [ + 'https://www.w3.org/ns/did/v1', + { + '@base': 'did:ion:EiBP6JaGhwYye4zz-wdeXR2JWl1JclaVDPA7FDgpzM8-ig:eyJkZWx0YSI6eyJwYXRjaGVzIjpbeyJhY3Rpb24iOiJyZXBsYWNlIiwiZG9jdW1lbnQiOnsicHVibGljS2V5cyI6W3siaWQiOiJPQVBqN09ickVKRmdWTkEycnJrUE01QS12WVZzSF9seXo0TGdPVWRKQmE4IiwicHVibGljS2V5SndrIjp7ImNydiI6IkVkMjU1MTkiLCJrdHkiOiJPS1AiLCJ4IjoicnBLbkRQOEY0X2p3dlE3eERra3VLeDE2NU9Td2N5clF2bUVXbDJlaWdJVSJ9LCJwdXJwb3NlcyI6WyJhdXRoZW50aWNhdGlvbiJdLCJ0eXBlIjoiSnNvbldlYktleTIwMjAifV0sInNlcnZpY2VzIjpbXX19XSwidXBkYXRlQ29tbWl0bWVudCI6IkVpRFZyOHUzVWxvOGtNVUx3WEh6VUdSMFdGdy1ROU14el8zRGQyQXEwVF9KR3cifSwic3VmZml4RGF0YSI6eyJkZWx0YUhhc2giOiJFaUNUMEh4ZGNTRHkwQ0t5eHV4VkZ3d3A3N3YteEJkSkVRLUVtSXhZUGR4VnV3IiwicmVjb3ZlcnlDb21taXRtZW50IjoiRWlEOEQtdjlsVjdqTzZ3ajVjSXVsRXRwZEFqaHE5NEFnTm54SlozWThVUnlrZyJ9fQ' + }, + ], + id : 'did:ion:EiBP6JaGhwYye4zz-wdeXR2JWl1JclaVDPA7FDgpzM8-ig:eyJkZWx0YSI6eyJwYXRjaGVzIjpbeyJhY3Rpb24iOiJyZXBsYWNlIiwiZG9jdW1lbnQiOnsicHVibGljS2V5cyI6W3siaWQiOiJPQVBqN09ickVKRmdWTkEycnJrUE01QS12WVZzSF9seXo0TGdPVWRKQmE4IiwicHVibGljS2V5SndrIjp7ImNydiI6IkVkMjU1MTkiLCJrdHkiOiJPS1AiLCJ4IjoicnBLbkRQOEY0X2p3dlE3eERra3VLeDE2NU9Td2N5clF2bUVXbDJlaWdJVSJ9LCJwdXJwb3NlcyI6WyJhdXRoZW50aWNhdGlvbiJdLCJ0eXBlIjoiSnNvbldlYktleTIwMjAifV0sInNlcnZpY2VzIjpbXX19XSwidXBkYXRlQ29tbWl0bWVudCI6IkVpRFZyOHUzVWxvOGtNVUx3WEh6VUdSMFdGdy1ROU14el8zRGQyQXEwVF9KR3cifSwic3VmZml4RGF0YSI6eyJkZWx0YUhhc2giOiJFaUNUMEh4ZGNTRHkwQ0t5eHV4VkZ3d3A3N3YteEJkSkVRLUVtSXhZUGR4VnV3IiwicmVjb3ZlcnlDb21taXRtZW50IjoiRWlEOEQtdjlsVjdqTzZ3ajVjSXVsRXRwZEFqaHE5NEFnTm54SlozWThVUnlrZyJ9fQ', + 'verificationMethod' : [ + { + id : '#OAPj7ObrEJFgVNA2rrkPM5A-vYVsH_lyz4LgOUdJBa8', + type : 'JsonWebKey2020', + controller : 'did:ion:EiBP6JaGhwYye4zz-wdeXR2JWl1JclaVDPA7FDgpzM8-ig:eyJkZWx0YSI6eyJwYXRjaGVzIjpbeyJhY3Rpb24iOiJyZXBsYWNlIiwiZG9jdW1lbnQiOnsicHVibGljS2V5cyI6W3siaWQiOiJPQVBqN09ickVKRmdWTkEycnJrUE01QS12WVZzSF9seXo0TGdPVWRKQmE4IiwicHVibGljS2V5SndrIjp7ImNydiI6IkVkMjU1MTkiLCJrdHkiOiJPS1AiLCJ4IjoicnBLbkRQOEY0X2p3dlE3eERra3VLeDE2NU9Td2N5clF2bUVXbDJlaWdJVSJ9LCJwdXJwb3NlcyI6WyJhdXRoZW50aWNhdGlvbiJdLCJ0eXBlIjoiSnNvbldlYktleTIwMjAifV0sInNlcnZpY2VzIjpbXX19XSwidXBkYXRlQ29tbWl0bWVudCI6IkVpRFZyOHUzVWxvOGtNVUx3WEh6VUdSMFdGdy1ROU14el8zRGQyQXEwVF9KR3cifSwic3VmZml4RGF0YSI6eyJkZWx0YUhhc2giOiJFaUNUMEh4ZGNTRHkwQ0t5eHV4VkZ3d3A3N3YteEJkSkVRLUVtSXhZUGR4VnV3IiwicmVjb3ZlcnlDb21taXRtZW50IjoiRWlEOEQtdjlsVjdqTzZ3ajVjSXVsRXRwZEFqaHE5NEFnTm54SlozWThVUnlrZyJ9fQ', + publicKeyJwk : { + crv : 'Ed25519', + kty : 'OKP', + x : 'rpKnDP8F4_jwvQ7xDkkuKx165OSwcyrQvmEWl2eigIU' + } + } + ], + authentication: [ + '#OAPj7ObrEJFgVNA2rrkPM5A-vYVsH_lyz4LgOUdJBa8' + ], + service: [] + }, + keySet: { + recoveryKey: { + privateKeyJwk: { + alg : 'ES256K', + crv : 'secp256k1', + d : 'cy4EzFT9K0sdCcz0gnctkcPq0szOP-d8smA9Hvp5ejo', + ext : 'true', + key_ops : ['sign'], + kid : 'AEOG_sxXHhCA1Fel8fpheyLxAcW89D7V86lMcJXc500', + kty : 'EC', + x : 'vLvKcPjYVnJi6dZpq15PrJqdxBERuvL8EqvTh1_0ikg', + y : 'AsQstiUIt5tGyAyM7LzytsbdVbsb-5oWVNYIUEjOePs' + }, + publicKeyJwk: { + alg : 'ES256K', + crv : 'secp256k1', + ext : 'true', + key_ops : ['verify'], + kid : 'AEOG_sxXHhCA1Fel8fpheyLxAcW89D7V86lMcJXc500', + kty : 'EC', + x : 'vLvKcPjYVnJi6dZpq15PrJqdxBERuvL8EqvTh1_0ikg', + y : 'AsQstiUIt5tGyAyM7LzytsbdVbsb-5oWVNYIUEjOePs' + }, + }, + updateKey: { + privateKeyJwk: { + alg : 'ES256K', + crv : 'secp256k1', + d : 'j4VcN2I5uw5kwPC5e8rxDaA7OxkmrJ-2BgdyKwayO9E', + ext : 'true', + key_ops : ['sign'], + kid : '_1CySHVtk6tNXke3t_7NLI2nvaVlH5GFyuO9HjQCRKs', + kty : 'EC', + x : '4Z9Tt1tuFlI3YwJfT3eS72r0sa9UxdtalgW14gep2DQ', + y : 'FIAtUW8B54L0Y-0e9n_rc1GJhxMIkZ3iYGnMQufgT_s' + }, + publicKeyJwk: { + alg : 'ES256K', + crv : 'secp256k1', + ext : 'true', + key_ops : ['verify'], + kid : '_1CySHVtk6tNXke3t_7NLI2nvaVlH5GFyuO9HjQCRKs', + kty : 'EC', + x : '4Z9Tt1tuFlI3YwJfT3eS72r0sa9UxdtalgW14gep2DQ', + y : 'FIAtUW8B54L0Y-0e9n_rc1GJhxMIkZ3iYGnMQufgT_s' + } + }, + verificationMethodKeys: [{ + publicKeyJwk: { + alg : 'EdDSA', + crv : 'Ed25519', + kid : 'OAPj7ObrEJFgVNA2rrkPM5A-vYVsH_lyz4LgOUdJBa8', + kty : 'OKP', + x : 'rpKnDP8F4_jwvQ7xDkkuKx165OSwcyrQvmEWl2eigIU' + }, + relationships: ['authentication'] + }], + }, + } + }, + { + id : 'did.create.3', + input : { + keySet: { + recoveryKey: { + privateKeyJwk: { + alg : 'ES256K', + crv : 'secp256k1', + d : 'cy4EzFT9K0sdCcz0gnctkcPq0szOP-d8smA9Hvp5ejo', + ext : 'true', + key_ops : ['sign'], + kty : 'EC', + x : 'vLvKcPjYVnJi6dZpq15PrJqdxBERuvL8EqvTh1_0ikg', + y : 'AsQstiUIt5tGyAyM7LzytsbdVbsb-5oWVNYIUEjOePs' + }, + publicKeyJwk: { + alg : 'ES256K', + crv : 'secp256k1', + ext : 'true', + key_ops : ['verify'], + kty : 'EC', + x : 'vLvKcPjYVnJi6dZpq15PrJqdxBERuvL8EqvTh1_0ikg', + y : 'AsQstiUIt5tGyAyM7LzytsbdVbsb-5oWVNYIUEjOePs' + }, + }, + updateKey: { + privateKeyJwk: { + alg : 'ES256K', + crv : 'secp256k1', + d : 'j4VcN2I5uw5kwPC5e8rxDaA7OxkmrJ-2BgdyKwayO9E', + ext : 'true', + key_ops : ['sign'], + kty : 'EC', + x : '4Z9Tt1tuFlI3YwJfT3eS72r0sa9UxdtalgW14gep2DQ', + y : 'FIAtUW8B54L0Y-0e9n_rc1GJhxMIkZ3iYGnMQufgT_s' + }, + publicKeyJwk: { + alg : 'ES256K', + crv : 'secp256k1', + ext : 'true', + key_ops : ['verify'], + kty : 'EC', + x : '4Z9Tt1tuFlI3YwJfT3eS72r0sa9UxdtalgW14gep2DQ', + y : 'FIAtUW8B54L0Y-0e9n_rc1GJhxMIkZ3iYGnMQufgT_s' + } + }, + verificationMethodKeys: [{ + publicKeyJwk: { + alg : 'ES256K', + crv : 'secp256k1', + kty : 'EC', + x : 'gdpQnpSlWSJXQEJJjVnNEi6-5H1L-jwNCDchM_JHDZQ', + y : 'SnEwisOamyUA7HYh8NKwYwgAR6_0CvHXWG26tXJa4RU' + }, + relationships: ['authentication'] + }], + }, + keyAlgorithm: 'secp256k1' + }, + output: { + canonicalId : 'did:ion:EiAeqx3f9VMGhk35znqsCEZuELryh8mXUyhnou1Zf6YpDw', + did : 'did:ion:EiAeqx3f9VMGhk35znqsCEZuELryh8mXUyhnou1Zf6YpDw:eyJkZWx0YSI6eyJwYXRjaGVzIjpbeyJhY3Rpb24iOiJyZXBsYWNlIiwiZG9jdW1lbnQiOnsicHVibGljS2V5cyI6W3siaWQiOiJLNkJwQW9xSU1La2d1Z2xGV0hNT01Xam5VM1BzeUhfQzBSZVJzUDZqRW0wIiwicHVibGljS2V5SndrIjp7ImNydiI6InNlY3AyNTZrMSIsImt0eSI6IkVDIiwieCI6ImdkcFFucFNsV1NKWFFFSkpqVm5ORWk2LTVIMUwtandOQ0RjaE1fSkhEWlEiLCJ5IjoiU25Fd2lzT2FteVVBN0hZaDhOS3dZd2dBUjZfMEN2SFhXRzI2dFhKYTRSVSJ9LCJwdXJwb3NlcyI6WyJhdXRoZW50aWNhdGlvbiJdLCJ0eXBlIjoiSnNvbldlYktleTIwMjAifV0sInNlcnZpY2VzIjpbXX19XSwidXBkYXRlQ29tbWl0bWVudCI6IkVpRFZyOHUzVWxvOGtNVUx3WEh6VUdSMFdGdy1ROU14el8zRGQyQXEwVF9KR3cifSwic3VmZml4RGF0YSI6eyJkZWx0YUhhc2giOiJFaUI0SzdZNHJtVEZTUjFWVUVlOEE1blQ0UnRpa1B6R3NTYjluOWl0SzJQWlhnIiwicmVjb3ZlcnlDb21taXRtZW50IjoiRWlEOEQtdjlsVjdqTzZ3ajVjSXVsRXRwZEFqaHE5NEFnTm54SlozWThVUnlrZyJ9fQ', + document : { + '@context': [ + 'https://www.w3.org/ns/did/v1', + { + '@base': 'did:ion:EiAeqx3f9VMGhk35znqsCEZuELryh8mXUyhnou1Zf6YpDw:eyJkZWx0YSI6eyJwYXRjaGVzIjpbeyJhY3Rpb24iOiJyZXBsYWNlIiwiZG9jdW1lbnQiOnsicHVibGljS2V5cyI6W3siaWQiOiJLNkJwQW9xSU1La2d1Z2xGV0hNT01Xam5VM1BzeUhfQzBSZVJzUDZqRW0wIiwicHVibGljS2V5SndrIjp7ImNydiI6InNlY3AyNTZrMSIsImt0eSI6IkVDIiwieCI6ImdkcFFucFNsV1NKWFFFSkpqVm5ORWk2LTVIMUwtandOQ0RjaE1fSkhEWlEiLCJ5IjoiU25Fd2lzT2FteVVBN0hZaDhOS3dZd2dBUjZfMEN2SFhXRzI2dFhKYTRSVSJ9LCJwdXJwb3NlcyI6WyJhdXRoZW50aWNhdGlvbiJdLCJ0eXBlIjoiSnNvbldlYktleTIwMjAifV0sInNlcnZpY2VzIjpbXX19XSwidXBkYXRlQ29tbWl0bWVudCI6IkVpRFZyOHUzVWxvOGtNVUx3WEh6VUdSMFdGdy1ROU14el8zRGQyQXEwVF9KR3cifSwic3VmZml4RGF0YSI6eyJkZWx0YUhhc2giOiJFaUI0SzdZNHJtVEZTUjFWVUVlOEE1blQ0UnRpa1B6R3NTYjluOWl0SzJQWlhnIiwicmVjb3ZlcnlDb21taXRtZW50IjoiRWlEOEQtdjlsVjdqTzZ3ajVjSXVsRXRwZEFqaHE5NEFnTm54SlozWThVUnlrZyJ9fQ' + }, + ], + id : 'did:ion:EiAeqx3f9VMGhk35znqsCEZuELryh8mXUyhnou1Zf6YpDw:eyJkZWx0YSI6eyJwYXRjaGVzIjpbeyJhY3Rpb24iOiJyZXBsYWNlIiwiZG9jdW1lbnQiOnsicHVibGljS2V5cyI6W3siaWQiOiJLNkJwQW9xSU1La2d1Z2xGV0hNT01Xam5VM1BzeUhfQzBSZVJzUDZqRW0wIiwicHVibGljS2V5SndrIjp7ImNydiI6InNlY3AyNTZrMSIsImt0eSI6IkVDIiwieCI6ImdkcFFucFNsV1NKWFFFSkpqVm5ORWk2LTVIMUwtandOQ0RjaE1fSkhEWlEiLCJ5IjoiU25Fd2lzT2FteVVBN0hZaDhOS3dZd2dBUjZfMEN2SFhXRzI2dFhKYTRSVSJ9LCJwdXJwb3NlcyI6WyJhdXRoZW50aWNhdGlvbiJdLCJ0eXBlIjoiSnNvbldlYktleTIwMjAifV0sInNlcnZpY2VzIjpbXX19XSwidXBkYXRlQ29tbWl0bWVudCI6IkVpRFZyOHUzVWxvOGtNVUx3WEh6VUdSMFdGdy1ROU14el8zRGQyQXEwVF9KR3cifSwic3VmZml4RGF0YSI6eyJkZWx0YUhhc2giOiJFaUI0SzdZNHJtVEZTUjFWVUVlOEE1blQ0UnRpa1B6R3NTYjluOWl0SzJQWlhnIiwicmVjb3ZlcnlDb21taXRtZW50IjoiRWlEOEQtdjlsVjdqTzZ3ajVjSXVsRXRwZEFqaHE5NEFnTm54SlozWThVUnlrZyJ9fQ', + 'verificationMethod' : [ + { + id : '#K6BpAoqIMKkguglFWHMOMWjnU3PsyH_C0ReRsP6jEm0', + type : 'JsonWebKey2020', + controller : 'did:ion:EiAeqx3f9VMGhk35znqsCEZuELryh8mXUyhnou1Zf6YpDw:eyJkZWx0YSI6eyJwYXRjaGVzIjpbeyJhY3Rpb24iOiJyZXBsYWNlIiwiZG9jdW1lbnQiOnsicHVibGljS2V5cyI6W3siaWQiOiJLNkJwQW9xSU1La2d1Z2xGV0hNT01Xam5VM1BzeUhfQzBSZVJzUDZqRW0wIiwicHVibGljS2V5SndrIjp7ImNydiI6InNlY3AyNTZrMSIsImt0eSI6IkVDIiwieCI6ImdkcFFucFNsV1NKWFFFSkpqVm5ORWk2LTVIMUwtandOQ0RjaE1fSkhEWlEiLCJ5IjoiU25Fd2lzT2FteVVBN0hZaDhOS3dZd2dBUjZfMEN2SFhXRzI2dFhKYTRSVSJ9LCJwdXJwb3NlcyI6WyJhdXRoZW50aWNhdGlvbiJdLCJ0eXBlIjoiSnNvbldlYktleTIwMjAifV0sInNlcnZpY2VzIjpbXX19XSwidXBkYXRlQ29tbWl0bWVudCI6IkVpRFZyOHUzVWxvOGtNVUx3WEh6VUdSMFdGdy1ROU14el8zRGQyQXEwVF9KR3cifSwic3VmZml4RGF0YSI6eyJkZWx0YUhhc2giOiJFaUI0SzdZNHJtVEZTUjFWVUVlOEE1blQ0UnRpa1B6R3NTYjluOWl0SzJQWlhnIiwicmVjb3ZlcnlDb21taXRtZW50IjoiRWlEOEQtdjlsVjdqTzZ3ajVjSXVsRXRwZEFqaHE5NEFnTm54SlozWThVUnlrZyJ9fQ', + publicKeyJwk : { + crv : 'secp256k1', + kty : 'EC', + x : 'gdpQnpSlWSJXQEJJjVnNEi6-5H1L-jwNCDchM_JHDZQ', + y : 'SnEwisOamyUA7HYh8NKwYwgAR6_0CvHXWG26tXJa4RU' + } + } + ], + authentication: [ + '#K6BpAoqIMKkguglFWHMOMWjnU3PsyH_C0ReRsP6jEm0' + ], + service: [] + }, + keySet: { + recoveryKey: { + privateKeyJwk: { + alg : 'ES256K', + crv : 'secp256k1', + d : 'cy4EzFT9K0sdCcz0gnctkcPq0szOP-d8smA9Hvp5ejo', + ext : 'true', + key_ops : ['sign'], + kid : 'AEOG_sxXHhCA1Fel8fpheyLxAcW89D7V86lMcJXc500', + kty : 'EC', + x : 'vLvKcPjYVnJi6dZpq15PrJqdxBERuvL8EqvTh1_0ikg', + y : 'AsQstiUIt5tGyAyM7LzytsbdVbsb-5oWVNYIUEjOePs' + }, + publicKeyJwk: { + alg : 'ES256K', + crv : 'secp256k1', + ext : 'true', + key_ops : ['verify'], + kid : 'AEOG_sxXHhCA1Fel8fpheyLxAcW89D7V86lMcJXc500', + kty : 'EC', + x : 'vLvKcPjYVnJi6dZpq15PrJqdxBERuvL8EqvTh1_0ikg', + y : 'AsQstiUIt5tGyAyM7LzytsbdVbsb-5oWVNYIUEjOePs' + }, + }, + updateKey: { + privateKeyJwk: { + alg : 'ES256K', + crv : 'secp256k1', + d : 'j4VcN2I5uw5kwPC5e8rxDaA7OxkmrJ-2BgdyKwayO9E', + ext : 'true', + key_ops : ['sign'], + kid : '_1CySHVtk6tNXke3t_7NLI2nvaVlH5GFyuO9HjQCRKs', + kty : 'EC', + x : '4Z9Tt1tuFlI3YwJfT3eS72r0sa9UxdtalgW14gep2DQ', + y : 'FIAtUW8B54L0Y-0e9n_rc1GJhxMIkZ3iYGnMQufgT_s' + }, + publicKeyJwk: { + alg : 'ES256K', + crv : 'secp256k1', + ext : 'true', + key_ops : ['verify'], + kid : '_1CySHVtk6tNXke3t_7NLI2nvaVlH5GFyuO9HjQCRKs', + kty : 'EC', + x : '4Z9Tt1tuFlI3YwJfT3eS72r0sa9UxdtalgW14gep2DQ', + y : 'FIAtUW8B54L0Y-0e9n_rc1GJhxMIkZ3iYGnMQufgT_s' + } + }, + verificationMethodKeys: [{ + publicKeyJwk: { + alg : 'ES256K', + crv : 'secp256k1', + kid : 'K6BpAoqIMKkguglFWHMOMWjnU3PsyH_C0ReRsP6jEm0', + kty : 'EC', + x : 'gdpQnpSlWSJXQEJJjVnNEi6-5H1L-jwNCDchM_JHDZQ', + y : 'SnEwisOamyUA7HYh8NKwYwgAR6_0CvHXWG26tXJa4RU' + }, + relationships: ['authentication'] + }], + }, + } + } +]; \ No newline at end of file diff --git a/packages/dids/tests/fixtures/test-vectors/did-key.ts b/packages/dids/tests/fixtures/test-vectors/did-key.ts new file mode 100644 index 000000000..580ac1162 --- /dev/null +++ b/packages/dids/tests/fixtures/test-vectors/did-key.ts @@ -0,0 +1,318 @@ +export const didKeyCreateDocumentTestVectors = [ + { + id : 'did.createDocument.1', + input : { + did : 'did:key:z6MkmNvXGmVuux5W63nXKEM8zoxFmDLNfe7siCKG2GM7Kd8D', + publicKeyFormat : 'JsonWebKey2020' + }, + output: { + '@context': [ + 'https://www.w3.org/ns/did/v1', + 'https://w3id.org/security/suites/jws-2020/v1' + ], + 'id' : 'did:key:z6MkmNvXGmVuux5W63nXKEM8zoxFmDLNfe7siCKG2GM7Kd8D', + 'verificationMethod' : [ + { + 'id' : 'did:key:z6MkmNvXGmVuux5W63nXKEM8zoxFmDLNfe7siCKG2GM7Kd8D#z6MkmNvXGmVuux5W63nXKEM8zoxFmDLNfe7siCKG2GM7Kd8D', + 'type' : 'JsonWebKey2020', + 'controller' : 'did:key:z6MkmNvXGmVuux5W63nXKEM8zoxFmDLNfe7siCKG2GM7Kd8D', + 'publicKeyJwk' : { + 'alg' : 'EdDSA', + 'crv' : 'Ed25519', + 'kty' : 'OKP', + 'x' : 'ZuVpK6HnahBtV1Y_jhnYK-fqHAz3dXmWXT_h-J7SL6I' + } + } + ], + 'assertionMethod': [ + 'did:key:z6MkmNvXGmVuux5W63nXKEM8zoxFmDLNfe7siCKG2GM7Kd8D#z6MkmNvXGmVuux5W63nXKEM8zoxFmDLNfe7siCKG2GM7Kd8D' + ], + 'authentication': [ + 'did:key:z6MkmNvXGmVuux5W63nXKEM8zoxFmDLNfe7siCKG2GM7Kd8D#z6MkmNvXGmVuux5W63nXKEM8zoxFmDLNfe7siCKG2GM7Kd8D' + ], + 'capabilityDelegation': [ + 'did:key:z6MkmNvXGmVuux5W63nXKEM8zoxFmDLNfe7siCKG2GM7Kd8D#z6MkmNvXGmVuux5W63nXKEM8zoxFmDLNfe7siCKG2GM7Kd8D' + ], + 'capabilityInvocation': [ + 'did:key:z6MkmNvXGmVuux5W63nXKEM8zoxFmDLNfe7siCKG2GM7Kd8D#z6MkmNvXGmVuux5W63nXKEM8zoxFmDLNfe7siCKG2GM7Kd8D' + ] + } + }, + { + id : 'did.createDocument.2', + input : { + did : 'did:key:z6MkmNvXGmVuux5W63nXKEM8zoxFmDLNfe7siCKG2GM7Kd8D', + publicKeyFormat : 'Ed25519VerificationKey2020' + }, + output: { + '@context': [ + 'https://www.w3.org/ns/did/v1', + 'https://w3id.org/security/suites/ed25519-2020/v1' + ], + 'id' : 'did:key:z6MkmNvXGmVuux5W63nXKEM8zoxFmDLNfe7siCKG2GM7Kd8D', + 'verificationMethod' : [ + { + 'id' : 'did:key:z6MkmNvXGmVuux5W63nXKEM8zoxFmDLNfe7siCKG2GM7Kd8D#z6MkmNvXGmVuux5W63nXKEM8zoxFmDLNfe7siCKG2GM7Kd8D', + 'type' : 'Ed25519VerificationKey2020', + 'controller' : 'did:key:z6MkmNvXGmVuux5W63nXKEM8zoxFmDLNfe7siCKG2GM7Kd8D', + 'publicKeyMultibase' : 'z6MkmNvXGmVuux5W63nXKEM8zoxFmDLNfe7siCKG2GM7Kd8D' + } + ], + 'assertionMethod': [ + 'did:key:z6MkmNvXGmVuux5W63nXKEM8zoxFmDLNfe7siCKG2GM7Kd8D#z6MkmNvXGmVuux5W63nXKEM8zoxFmDLNfe7siCKG2GM7Kd8D' + ], + 'authentication': [ + 'did:key:z6MkmNvXGmVuux5W63nXKEM8zoxFmDLNfe7siCKG2GM7Kd8D#z6MkmNvXGmVuux5W63nXKEM8zoxFmDLNfe7siCKG2GM7Kd8D' + ], + 'capabilityDelegation': [ + 'did:key:z6MkmNvXGmVuux5W63nXKEM8zoxFmDLNfe7siCKG2GM7Kd8D#z6MkmNvXGmVuux5W63nXKEM8zoxFmDLNfe7siCKG2GM7Kd8D' + ], + 'capabilityInvocation': [ + 'did:key:z6MkmNvXGmVuux5W63nXKEM8zoxFmDLNfe7siCKG2GM7Kd8D#z6MkmNvXGmVuux5W63nXKEM8zoxFmDLNfe7siCKG2GM7Kd8D' + ] + } + }, + { + id : 'did.createDocument.3', + input : { + did : 'did:key:z6MkmNvXGmVuux5W63nXKEM8zoxFmDLNfe7siCKG2GM7Kd8D', + enableEncryptionKeyDerivation : true, + publicKeyFormat : 'JsonWebKey2020' + }, + output: { + '@context': [ + 'https://www.w3.org/ns/did/v1', + 'https://w3id.org/security/suites/jws-2020/v1' + ], + 'id' : 'did:key:z6MkmNvXGmVuux5W63nXKEM8zoxFmDLNfe7siCKG2GM7Kd8D', + 'verificationMethod' : [ + { + 'id' : 'did:key:z6MkmNvXGmVuux5W63nXKEM8zoxFmDLNfe7siCKG2GM7Kd8D#z6MkmNvXGmVuux5W63nXKEM8zoxFmDLNfe7siCKG2GM7Kd8D', + 'type' : 'JsonWebKey2020', + 'controller' : 'did:key:z6MkmNvXGmVuux5W63nXKEM8zoxFmDLNfe7siCKG2GM7Kd8D', + 'publicKeyJwk' : { + 'alg' : 'EdDSA', + 'crv' : 'Ed25519', + 'kty' : 'OKP', + 'x' : 'ZuVpK6HnahBtV1Y_jhnYK-fqHAz3dXmWXT_h-J7SL6I' + } + }, + { + 'id' : 'did:key:z6MkmNvXGmVuux5W63nXKEM8zoxFmDLNfe7siCKG2GM7Kd8D#z6LSdCnN59MPkRCaVvXczoipz5tMcPpjrCnvqBcHHjCDohYd', + 'type' : 'JsonWebKey2020', + 'controller' : 'did:key:z6MkmNvXGmVuux5W63nXKEM8zoxFmDLNfe7siCKG2GM7Kd8D', + 'publicKeyJwk' : { + 'crv' : 'X25519', + 'kty' : 'OKP', + 'x' : 'FrLpNU0FVX4oAByhAbU71h4yb-WMr6penULFCzbMtxo', + }, + } + ], + 'assertionMethod': [ + 'did:key:z6MkmNvXGmVuux5W63nXKEM8zoxFmDLNfe7siCKG2GM7Kd8D#z6MkmNvXGmVuux5W63nXKEM8zoxFmDLNfe7siCKG2GM7Kd8D' + ], + 'authentication': [ + 'did:key:z6MkmNvXGmVuux5W63nXKEM8zoxFmDLNfe7siCKG2GM7Kd8D#z6MkmNvXGmVuux5W63nXKEM8zoxFmDLNfe7siCKG2GM7Kd8D' + ], + 'capabilityDelegation': [ + 'did:key:z6MkmNvXGmVuux5W63nXKEM8zoxFmDLNfe7siCKG2GM7Kd8D#z6MkmNvXGmVuux5W63nXKEM8zoxFmDLNfe7siCKG2GM7Kd8D' + ], + 'capabilityInvocation': [ + 'did:key:z6MkmNvXGmVuux5W63nXKEM8zoxFmDLNfe7siCKG2GM7Kd8D#z6MkmNvXGmVuux5W63nXKEM8zoxFmDLNfe7siCKG2GM7Kd8D' + ], + 'keyAgreement': [ + 'did:key:z6MkmNvXGmVuux5W63nXKEM8zoxFmDLNfe7siCKG2GM7Kd8D#z6LSdCnN59MPkRCaVvXczoipz5tMcPpjrCnvqBcHHjCDohYd' + ] + } + }, + { + id : 'did.createDocument.4', + input : { + did : 'did:key:z6MkmNvXGmVuux5W63nXKEM8zoxFmDLNfe7siCKG2GM7Kd8D', + enableEncryptionKeyDerivation : true, + publicKeyFormat : 'Ed25519VerificationKey2020' + }, + output: { + '@context': [ + 'https://www.w3.org/ns/did/v1', + 'https://w3id.org/security/suites/ed25519-2020/v1', + 'https://w3id.org/security/suites/x25519-2020/v1' + ], + 'id' : 'did:key:z6MkmNvXGmVuux5W63nXKEM8zoxFmDLNfe7siCKG2GM7Kd8D', + 'verificationMethod' : [ + { + 'id' : 'did:key:z6MkmNvXGmVuux5W63nXKEM8zoxFmDLNfe7siCKG2GM7Kd8D#z6MkmNvXGmVuux5W63nXKEM8zoxFmDLNfe7siCKG2GM7Kd8D', + 'type' : 'Ed25519VerificationKey2020', + 'controller' : 'did:key:z6MkmNvXGmVuux5W63nXKEM8zoxFmDLNfe7siCKG2GM7Kd8D', + 'publicKeyMultibase' : 'z6MkmNvXGmVuux5W63nXKEM8zoxFmDLNfe7siCKG2GM7Kd8D' + }, + { + 'id' : 'did:key:z6MkmNvXGmVuux5W63nXKEM8zoxFmDLNfe7siCKG2GM7Kd8D#z6LSdCnN59MPkRCaVvXczoipz5tMcPpjrCnvqBcHHjCDohYd', + 'type' : 'X25519KeyAgreementKey2020', + 'controller' : 'did:key:z6MkmNvXGmVuux5W63nXKEM8zoxFmDLNfe7siCKG2GM7Kd8D', + 'publicKeyMultibase' : 'z6LSdCnN59MPkRCaVvXczoipz5tMcPpjrCnvqBcHHjCDohYd' + } + ], + 'assertionMethod': [ + 'did:key:z6MkmNvXGmVuux5W63nXKEM8zoxFmDLNfe7siCKG2GM7Kd8D#z6MkmNvXGmVuux5W63nXKEM8zoxFmDLNfe7siCKG2GM7Kd8D' + ], + 'authentication': [ + 'did:key:z6MkmNvXGmVuux5W63nXKEM8zoxFmDLNfe7siCKG2GM7Kd8D#z6MkmNvXGmVuux5W63nXKEM8zoxFmDLNfe7siCKG2GM7Kd8D' + ], + 'capabilityDelegation': [ + 'did:key:z6MkmNvXGmVuux5W63nXKEM8zoxFmDLNfe7siCKG2GM7Kd8D#z6MkmNvXGmVuux5W63nXKEM8zoxFmDLNfe7siCKG2GM7Kd8D' + ], + 'capabilityInvocation': [ + 'did:key:z6MkmNvXGmVuux5W63nXKEM8zoxFmDLNfe7siCKG2GM7Kd8D#z6MkmNvXGmVuux5W63nXKEM8zoxFmDLNfe7siCKG2GM7Kd8D' + ], + 'keyAgreement': [ + 'did:key:z6MkmNvXGmVuux5W63nXKEM8zoxFmDLNfe7siCKG2GM7Kd8D#z6LSdCnN59MPkRCaVvXczoipz5tMcPpjrCnvqBcHHjCDohYd' + ] + } + } +]; + +export const didKeyCreateTestVectors = [ + { + id : 'did.create.1', + input : { + keySet: { + verificationMethodKeys: [{ + 'publicKeyJwk': { + 'alg' : 'EdDSA', + 'crv' : 'Ed25519', + 'kty' : 'OKP', + 'x' : 'rpKnDP8F4_jwvQ7xDkkuKx165OSwcyrQvmEWl2eigIU' + }, + relationships: ['authentication'] + }], + }, + publicKeyAlgorithm : 'Ed25519', + publicKeyFormat : 'JsonWebKey2020' + }, + output: { + did : 'did:key:z6MkrCigh4zugDVEieqt4WbtWParigHeH5TEYEuKcSyCykUk', + document : { + '@context': [ + 'https://www.w3.org/ns/did/v1', + 'https://w3id.org/security/suites/jws-2020/v1' + ], + 'id' : 'did:key:z6MkrCigh4zugDVEieqt4WbtWParigHeH5TEYEuKcSyCykUk', + 'verificationMethod' : [ + { + 'id' : 'did:key:z6MkrCigh4zugDVEieqt4WbtWParigHeH5TEYEuKcSyCykUk#z6MkrCigh4zugDVEieqt4WbtWParigHeH5TEYEuKcSyCykUk', + 'type' : 'JsonWebKey2020', + 'controller' : 'did:key:z6MkrCigh4zugDVEieqt4WbtWParigHeH5TEYEuKcSyCykUk', + 'publicKeyJwk' : { + 'alg' : 'EdDSA', + 'crv' : 'Ed25519', + 'kty' : 'OKP', + 'x' : 'rpKnDP8F4_jwvQ7xDkkuKx165OSwcyrQvmEWl2eigIU' + } + } + ], + 'assertionMethod': [ + 'did:key:z6MkrCigh4zugDVEieqt4WbtWParigHeH5TEYEuKcSyCykUk#z6MkrCigh4zugDVEieqt4WbtWParigHeH5TEYEuKcSyCykUk' + ], + 'authentication': [ + 'did:key:z6MkrCigh4zugDVEieqt4WbtWParigHeH5TEYEuKcSyCykUk#z6MkrCigh4zugDVEieqt4WbtWParigHeH5TEYEuKcSyCykUk' + ], + 'capabilityDelegation': [ + 'did:key:z6MkrCigh4zugDVEieqt4WbtWParigHeH5TEYEuKcSyCykUk#z6MkrCigh4zugDVEieqt4WbtWParigHeH5TEYEuKcSyCykUk' + ], + 'capabilityInvocation': [ + 'did:key:z6MkrCigh4zugDVEieqt4WbtWParigHeH5TEYEuKcSyCykUk#z6MkrCigh4zugDVEieqt4WbtWParigHeH5TEYEuKcSyCykUk' + ], + }, + keySet: { + verificationMethodKeys: [{ + 'publicKeyJwk': { + 'alg' : 'EdDSA', + 'crv' : 'Ed25519', + 'kty' : 'OKP', + 'x' : 'rpKnDP8F4_jwvQ7xDkkuKx165OSwcyrQvmEWl2eigIU' + }, + relationships: ['authentication'] + }], + }, + } + }, + { + id : 'did.create.2', + input : { + enableEncryptionKeyDerivation : true, + keySet : { + verificationMethodKeys: [{ + publicKeyJwk: { + alg : 'EdDSA', + crv : 'Ed25519', + kty : 'OKP', + x : 'rpKnDP8F4_jwvQ7xDkkuKx165OSwcyrQvmEWl2eigIU' + }, + relationships: ['authentication'] + }], + }, + publicKeyAlgorithm : 'Ed25519', + publicKeyFormat : 'JsonWebKey2020' + }, + output: { + did : 'did:key:z6MkrCigh4zugDVEieqt4WbtWParigHeH5TEYEuKcSyCykUk', + document : { + '@context': [ + 'https://www.w3.org/ns/did/v1', + 'https://w3id.org/security/suites/jws-2020/v1' + ], + 'id' : 'did:key:z6MkrCigh4zugDVEieqt4WbtWParigHeH5TEYEuKcSyCykUk', + 'verificationMethod' : [ + { + 'id' : 'did:key:z6MkrCigh4zugDVEieqt4WbtWParigHeH5TEYEuKcSyCykUk#z6MkrCigh4zugDVEieqt4WbtWParigHeH5TEYEuKcSyCykUk', + 'type' : 'JsonWebKey2020', + 'controller' : 'did:key:z6MkrCigh4zugDVEieqt4WbtWParigHeH5TEYEuKcSyCykUk', + 'publicKeyJwk' : { + 'alg' : 'EdDSA', + 'crv' : 'Ed25519', + 'kty' : 'OKP', + 'x' : 'rpKnDP8F4_jwvQ7xDkkuKx165OSwcyrQvmEWl2eigIU' + } + }, + { + 'controller' : 'did:key:z6MkrCigh4zugDVEieqt4WbtWParigHeH5TEYEuKcSyCykUk', + 'type' : 'JsonWebKey2020', + 'id' : 'did:key:z6MkrCigh4zugDVEieqt4WbtWParigHeH5TEYEuKcSyCykUk#z6LSjqybG4FgDYHxo4v9tWzgTpCm9a3b9K3QYqicCabqWeHQ', + 'publicKeyJwk' : { + 'crv' : 'X25519', + 'kty' : 'OKP', + 'x' : 'eWA3oUNKm3nZN0vqiC_tClPCkBznN5R0Y9NofJkoaXM' + } + } + ], + 'assertionMethod': [ + 'did:key:z6MkrCigh4zugDVEieqt4WbtWParigHeH5TEYEuKcSyCykUk#z6MkrCigh4zugDVEieqt4WbtWParigHeH5TEYEuKcSyCykUk' + ], + 'authentication': [ + 'did:key:z6MkrCigh4zugDVEieqt4WbtWParigHeH5TEYEuKcSyCykUk#z6MkrCigh4zugDVEieqt4WbtWParigHeH5TEYEuKcSyCykUk' + ], + 'capabilityDelegation': [ + 'did:key:z6MkrCigh4zugDVEieqt4WbtWParigHeH5TEYEuKcSyCykUk#z6MkrCigh4zugDVEieqt4WbtWParigHeH5TEYEuKcSyCykUk' + ], + 'capabilityInvocation': [ + 'did:key:z6MkrCigh4zugDVEieqt4WbtWParigHeH5TEYEuKcSyCykUk#z6MkrCigh4zugDVEieqt4WbtWParigHeH5TEYEuKcSyCykUk' + ], + 'keyAgreement': [ + 'did:key:z6MkrCigh4zugDVEieqt4WbtWParigHeH5TEYEuKcSyCykUk#z6LSjqybG4FgDYHxo4v9tWzgTpCm9a3b9K3QYqicCabqWeHQ' + ] + }, + keySet: { + verificationMethodKeys: [{ + publicKeyJwk: { + alg : 'EdDSA', + crv : 'Ed25519', + kty : 'OKP', + x : 'rpKnDP8F4_jwvQ7xDkkuKx165OSwcyrQvmEWl2eigIU' + }, + relationships: ['authentication'] + }], + }, + } + } +]; \ No newline at end of file diff --git a/packages/dids/tests/fixtures/test-vectors/did-resolver.ts b/packages/dids/tests/fixtures/test-vectors/did-resolver.ts new file mode 100644 index 000000000..e215406e9 --- /dev/null +++ b/packages/dids/tests/fixtures/test-vectors/did-resolver.ts @@ -0,0 +1,38 @@ +export const didResolverTestVectors = [ + { + id : 'did.resolve.1', + input : 'did:key:z6MkmNvXGmVuux5W63nXKEM8zoxFmDLNfe7siCKG2GM7Kd8D', + output : { + '@context': [ + 'https://www.w3.org/ns/did/v1', + 'https://w3id.org/security/suites/jws-2020/v1' + ], + 'id' : 'did:key:z6MkmNvXGmVuux5W63nXKEM8zoxFmDLNfe7siCKG2GM7Kd8D', + 'verificationMethod' : [ + { + 'id' : 'did:key:z6MkmNvXGmVuux5W63nXKEM8zoxFmDLNfe7siCKG2GM7Kd8D#z6MkmNvXGmVuux5W63nXKEM8zoxFmDLNfe7siCKG2GM7Kd8D', + 'type' : 'JsonWebKey2020', + 'controller' : 'did:key:z6MkmNvXGmVuux5W63nXKEM8zoxFmDLNfe7siCKG2GM7Kd8D', + 'publicKeyJwk' : { + 'alg' : 'EdDSA', + 'crv' : 'Ed25519', + 'kty' : 'OKP', + 'x' : 'ZuVpK6HnahBtV1Y_jhnYK-fqHAz3dXmWXT_h-J7SL6I' + } + } + ], + 'assertionMethod': [ + 'did:key:z6MkmNvXGmVuux5W63nXKEM8zoxFmDLNfe7siCKG2GM7Kd8D#z6MkmNvXGmVuux5W63nXKEM8zoxFmDLNfe7siCKG2GM7Kd8D' + ], + 'authentication': [ + 'did:key:z6MkmNvXGmVuux5W63nXKEM8zoxFmDLNfe7siCKG2GM7Kd8D#z6MkmNvXGmVuux5W63nXKEM8zoxFmDLNfe7siCKG2GM7Kd8D' + ], + 'capabilityDelegation': [ + 'did:key:z6MkmNvXGmVuux5W63nXKEM8zoxFmDLNfe7siCKG2GM7Kd8D#z6MkmNvXGmVuux5W63nXKEM8zoxFmDLNfe7siCKG2GM7Kd8D' + ], + 'capabilityInvocation': [ + 'did:key:z6MkmNvXGmVuux5W63nXKEM8zoxFmDLNfe7siCKG2GM7Kd8D#z6MkmNvXGmVuux5W63nXKEM8zoxFmDLNfe7siCKG2GM7Kd8D' + ] + } + }, +]; \ No newline at end of file diff --git a/packages/dids/tests/fixtures/test-vectors/did-utils.ts b/packages/dids/tests/fixtures/test-vectors/did-utils.ts new file mode 100644 index 000000000..0aed2d3de --- /dev/null +++ b/packages/dids/tests/fixtures/test-vectors/did-utils.ts @@ -0,0 +1,253 @@ +const didDocumentForIdTestVectors = { + '@context': [ + 'https://www.w3.org/ns/did/v1', + 'https://w3id.org/security/suites/jws-2020/v1', + 'https://w3id.org/security/suites/ed25519-2020/v1', + ], + id : 'did:method:alice', + verificationMethod : [ + { + id : 'did:method:alice#key-1', + type : 'JsonWebKey2020', + controller : 'did:method:alice', + publicKeyJwk : { + alg : 'EdDSA', + kty : 'OKP', + crv : 'Ed25519', + x : 'GM_NcTChsLlfdODKG573OSWGO7wNwzhkHRPHPxdAYfc' + } + }, + { + id : 'did:method:alice#key-2', + type : 'Ed25519VerificationKey2020', + controller : 'did:method:alice', + publicKeyMultibase : 'z6LSdCnN59MPkRCaVvXczoipz5tMcPpjrCnvqBcHHjCDohYd' + }, + ], + 'authentication': [ + 'did:method:alice#key-1', + { + id : 'did:method:alice#key-3', + type : 'JsonWebKey2020', + controller : 'did:method:alice', + publicKeyJwk : { + alg : 'EdDSA', + kty : 'OKP', + crv : 'Ed25519', + x : 'k1GKchkMMp9nbYsShY1R2UVzPsQill6zv2De38ERkfI' + }, + }, + ], + 'keyAgreement': [ + { + id : 'did:method:alice#key-5', + type : 'JsonWebKey2020', + controller : 'did:method:alice', + publicKeyJwk : { + alg : 'EdDSA', + kty : 'OKP', + crv : 'X25519', + x : 'SOKzporeWqJMJxf1NgPtup3whiBLPLZxgLDORNzbXwA' + }, + }, + { + id : 'did:method:alice#key-6', + type : 'X25519KeyAgreementKey2020', + controller : 'did:method:alice', + publicKeyMultibase : 'z6LSgah1r8rDCT2brDg7Vhh2LYmTkcEVgUHng1Ji68XBy4d' + }, + ] +}; + +export const didDocumentIdTestVectors = [ + { + id : 'did.getIdByKey.1', + input : { + didDocument : didDocumentForIdTestVectors, + publicKeyJwk : { + kty : 'OKP', + crv : 'Ed25519', + x : 'GM_NcTChsLlfdODKG573OSWGO7wNwzhkHRPHPxdAYfc' + }, + }, + output: 'did:method:alice#key-1' + }, + { + id : 'did.getIdByKey.2', + input : { + didDocument : didDocumentForIdTestVectors, + publicKeyMultibase : 'z6LSdCnN59MPkRCaVvXczoipz5tMcPpjrCnvqBcHHjCDohYd', + }, + output: 'did:method:alice#key-2' + }, + { + id : 'did.getIdByKey.3', + input : { + didDocument : didDocumentForIdTestVectors, + publicKeyJwk : { + kty : 'OKP', + crv : 'Ed25519', + x : 'k1GKchkMMp9nbYsShY1R2UVzPsQill6zv2De38ERkfI' + }, + publicKeyMultibase: 'z6LSdCnN59MPkRCaVvXczoipz5tMcPpjrCnvqBcHHjCDohYd', + }, + output: 'did:method:alice#key-2' + }, + { + id : 'did.getIdByKey.4', + input : { + didDocument : didDocumentForIdTestVectors, + publicKeyJwk : { + kty : 'OKP', + crv : 'Ed25519', + x : 'k1GKchkMMp9nbYsShY1R2UVzPsQill6zv2De38ERkfI' + }, + }, + output: undefined + } +]; + +export const didDocumentTypeTestVectors = [ + { + id : 'did.getTypes.1', + input : { + didDocument: { + '@context': [ + 'https://www.w3.org/ns/did/v1', + 'https://w3id.org/security/suites/ed25519-2020/v1', + 'https://w3id.org/security/suites/x25519-2020/v1' + ], + 'id' : 'did:key:z6MkiTBz1ymuepAQ4HEHYSF1H8quG5GLVVQR3djdX3mDooWp', + 'verificationMethod' : [ + { + 'id' : 'did:key:z6MkiTBz1ymuepAQ4HEHYSF1H8quG5GLVVQR3djdX3mDooWp#z6MkiTBz1ymuepAQ4HEHYSF1H8quG5GLVVQR3djdX3mDooWp', + 'type' : 'Ed25519VerificationKey2020', + 'controller' : 'did:key:z6MkiTBz1ymuepAQ4HEHYSF1H8quG5GLVVQR3djdX3mDooWp', + 'publicKeyMultibase' : 'z6MkiTBz1ymuepAQ4HEHYSF1H8quG5GLVVQR3djdX3mDooWp' + }, + { + 'id' : 'did:key:z6MkiTBz1ymuepAQ4HEHYSF1H8quG5GLVVQR3djdX3mDooWp#z6LShs9GGnqk85isEBzzshkuVWrVKsRp24GnDuHk8QWkARMW', + 'type' : 'X25519KeyAgreementKey2020', + 'controller' : 'did:key:z6MkiTBz1ymuepAQ4HEHYSF1H8quG5GLVVQR3djdX3mDooWp', + 'publicKeyMultibase' : 'z6LShs9GGnqk85isEBzzshkuVWrVKsRp24GnDuHk8QWkARMW' + } + ], + 'authentication': [ + 'did:key:z6MkiTBz1ymuepAQ4HEHYSF1H8quG5GLVVQR3djdX3mDooWp#z6MkiTBz1ymuepAQ4HEHYSF1H8quG5GLVVQR3djdX3mDooWp', + { + 'id' : 'did:key:z6MkiTBz1ymuepAQ4HEHYSF1H8quG5GLVVQR3djdX3mDooWp#zH3C2AVvLMv6gmMNam3uVAjZpfkcJCwDwnZn6z3wXmqPV', + 'type' : 'Ed25519VerificationKey2020', + 'controller' : 'did:key:z6MkiTBz1ymuepAQ4HEHYSF1H8quG5GLVVQR3djdX3mDooWp', + 'publicKeyMultibase' : 'zH3C2AVvLMv6gmMNam3uVAjZpfkcJCwDwnZn6z3wXmqPV' + } + ], + 'assertionMethod': [ + 'did:key:z6MkiTBz1ymuepAQ4HEHYSF1H8quG5GLVVQR3djdX3mDooWp#z6MkiTBz1ymuepAQ4HEHYSF1H8quG5GLVVQR3djdX3mDooWp', + { + 'id' : 'did:key:z6MkiTBz1ymuepAQ4HEHYSF1H8quG5GLVVQR3djdX3mDooWp#z6MknGc3ocHs3zdPiJbnaaqDi58NGb4pk1Sp9WxWufuXSdxf', + 'type' : 'Ed25519VerificationKey2020', + 'controller' : 'did:key:z6MkiTBz1ymuepAQ4HEHYSF1H8quG5GLVVQR3djdX3mDooWp', + 'publicKeyMultibase' : 'z6MknGc3ocHs3zdPiJbnaaqDi58NGb4pk1Sp9WxWufuXSdxf' + } + ], + 'capabilityDelegation': [ + 'did:key:z6MkiTBz1ymuepAQ4HEHYSF1H8quG5GLVVQR3djdX3mDooWp#z6MkiTBz1ymuepAQ4HEHYSF1H8quG5GLVVQR3djdX3mDooWp' + ], + 'capabilityInvocation': [ + 'did:key:z6MkiTBz1ymuepAQ4HEHYSF1H8quG5GLVVQR3djdX3mDooWp#z6MkiTBz1ymuepAQ4HEHYSF1H8quG5GLVVQR3djdX3mDooWp' + ], + 'keyAgreement': [ + 'did:key:z6MkiTBz1ymuepAQ4HEHYSF1H8quG5GLVVQR3djdX3mDooWp#z6LShs9GGnqk85isEBzzshkuVWrVKsRp24GnDuHk8QWkARMW' + ] + }, + }, + output: ['Ed25519VerificationKey2020', 'X25519KeyAgreementKey2020'] + }, + + { + id : 'did.getTypes.2', + input : { + didDocument: { + '@context': [ + 'https://www.w3.org/ns/did/v1', + 'https://w3id.org/security/suites/jws-2020/v1' + ], + 'id' : 'did:key:z6MkmNvXGmVuux5W63nXKEM8zoxFmDLNfe7siCKG2GM7Kd8D', + 'verificationMethod' : [ + { + 'id' : 'did:key:z6MkmNvXGmVuux5W63nXKEM8zoxFmDLNfe7siCKG2GM7Kd8D#z6MkmNvXGmVuux5W63nXKEM8zoxFmDLNfe7siCKG2GM7Kd8D', + 'type' : 'JsonWebKey2020', + 'controller' : 'did:key:z6MkmNvXGmVuux5W63nXKEM8zoxFmDLNfe7siCKG2GM7Kd8D', + 'publicKeyJwk' : { + 'alg' : 'EdDSA', + 'crv' : 'Ed25519', + 'kty' : 'OKP', + 'x' : 'ZuVpK6HnahBtV1Y_jhnYK-fqHAz3dXmWXT_h-J7SL6I' + } + } + ], + 'assertionMethod': [ + 'did:key:z6MkmNvXGmVuux5W63nXKEM8zoxFmDLNfe7siCKG2GM7Kd8D#z6MkmNvXGmVuux5W63nXKEM8zoxFmDLNfe7siCKG2GM7Kd8D' + ], + 'authentication': [ + 'did:key:z6MkmNvXGmVuux5W63nXKEM8zoxFmDLNfe7siCKG2GM7Kd8D#z6MkmNvXGmVuux5W63nXKEM8zoxFmDLNfe7siCKG2GM7Kd8D' + ], + 'capabilityDelegation': [ + 'did:key:z6MkmNvXGmVuux5W63nXKEM8zoxFmDLNfe7siCKG2GM7Kd8D#z6MkmNvXGmVuux5W63nXKEM8zoxFmDLNfe7siCKG2GM7Kd8D' + ], + 'capabilityInvocation': [ + 'did:key:z6MkmNvXGmVuux5W63nXKEM8zoxFmDLNfe7siCKG2GM7Kd8D#z6MkmNvXGmVuux5W63nXKEM8zoxFmDLNfe7siCKG2GM7Kd8D' + ] + }, + }, + output: ['JsonWebKey2020'] + }, + + // Source: https://w3c.github.io/did-core/#example-did-document-with-different-verification-method-types + { + id : 'did.type.w3c.32', + input : { + didDocument: { + '@context': [ + 'https://www.w3.org/ns/did/v1', + 'https://w3id.org/security/suites/ed25519-2018/v1', + 'https://w3id.org/security/suites/x25519-2019/v1', + 'https://w3id.org/security/suites/secp256k1-2019/v1', + 'https://w3id.org/security/suites/jws-2020/v1' + ], + 'verificationMethod': [ + { + 'id' : 'did:example:123#key-0', + 'type' : 'Ed25519VerificationKey2018', + 'controller' : 'did:example:123', + 'publicKeyBase58' : '3M5RCDjPTWPkKSN3sxUmmMqHbmRPegYP1tjcKyrDbt9J' // external (property name) + }, + { + 'id' : 'did:example:123#key-1', + 'type' : 'X25519KeyAgreementKey2019', + 'controller' : 'did:example:123', + 'publicKeyBase58' : 'FbQWLPRhTH95MCkQUeFYdiSoQt8zMwetqfWoxqPgaq7x' // external (property name) + }, + { + 'id' : 'did:example:123#key-2', + 'type' : 'EcdsaSecp256k1VerificationKey2019', + 'controller' : 'did:example:123', + 'publicKeyBase58' : 'ns2aFDq25fEV1NUd3wZ65sgj5QjFW8JCAHdUJfLwfodt' // external (property name) + }, + { + 'id' : 'did:example:123#key-3', + 'type' : 'JsonWebKey2020', + 'controller' : 'did:example:123', + 'publicKeyJwk' : { + 'kty' : 'EC', // external (property name) + 'crv' : 'P-256', // external (property name) + 'x' : 'Er6KSSnAjI70ObRWhlaMgqyIOQYrDJTE94ej5hybQ2M', // external (property name) + 'y' : 'pPVzCOTJwgikPjuUE6UebfZySqEJ0ZtsWFpj7YSPGEk' // external (property name) + } + } + ] + }, + }, + output: ['Ed25519VerificationKey2018', 'X25519KeyAgreementKey2019', 'EcdsaSecp256k1VerificationKey2019', 'JsonWebKey2020'] + } +]; \ No newline at end of file diff --git a/packages/dids/tests/tech-preview.spec.ts b/packages/dids/tests/tech-preview.old similarity index 90% rename from packages/dids/tests/tech-preview.spec.ts rename to packages/dids/tests/tech-preview.old index 74c45022b..0c8661343 100644 --- a/packages/dids/tests/tech-preview.spec.ts +++ b/packages/dids/tests/tech-preview.old @@ -1,13 +1,13 @@ -import type { DwnServiceEndpoint } from '../src/types.js'; - import { expect } from 'chai'; -import { DidIonApi } from '../src/did-ion.js'; +import type { DwnServiceEndpoint } from '../src/types.js'; + +import { DidIonMethod } from '../src/did-ion.js'; describe('Tech Preview', function () { describe('generateDwnConfiguration()', () => { it('returns keys and services with two DWN URLs', async () => { - const ionCreateOptions = await DidIonApi.generateDwnConfiguration([ + const ionCreateOptions = await DidIonMethod.generateDwnConfiguration([ 'https://dwn.tbddev.test/dwn0', 'https://dwn.tbddev.test/dwn1' ]); @@ -36,7 +36,7 @@ describe('Tech Preview', function () { }); it('returns keys and services with one DWN URLs', async () => { - const ionCreateOptions = await DidIonApi.generateDwnConfiguration([ + const ionCreateOptions = await DidIonMethod.generateDwnConfiguration([ 'https://dwn.tbddev.test/dwn0' ]); @@ -52,7 +52,7 @@ describe('Tech Preview', function () { }); it('returns keys and services with 0 DWN URLs', async () => { - const ionCreateOptions = await DidIonApi.generateDwnConfiguration([]); + const ionCreateOptions = await DidIonMethod.generateDwnConfiguration([]); const [ service ] = ionCreateOptions.services!; expect(service.id).to.equal('dwn'); diff --git a/packages/dids/tests/tsconfig.json b/packages/dids/tests/tsconfig.json index 7c6d2c8e7..ee97c2267 100644 --- a/packages/dids/tests/tsconfig.json +++ b/packages/dids/tests/tsconfig.json @@ -7,6 +7,7 @@ }, "include": [ "../src", + "../typings", ".", ], "exclude": [ diff --git a/packages/dids/tests/utils.spec.ts b/packages/dids/tests/utils.spec.ts index 07eefdb27..59160fa12 100644 --- a/packages/dids/tests/utils.spec.ts +++ b/packages/dids/tests/utils.spec.ts @@ -1,51 +1,55 @@ import { expect } from 'chai'; -import { keyToMultibaseId } from '../src/utils.js'; +import { + getVerificationMethodIds, + getVerificationMethodTypes, + parseDid, +} from '../src/utils.js'; +import { didDocumentIdTestVectors, didDocumentTypeTestVectors } from './fixtures/test-vectors/did-utils.js'; describe('DID Utils', () => { - describe('keyToMultibaseId()', () => { - it('returns a multibase encoded string', () => { - const input = { - key : new Uint8Array(32), - multicodecName : 'ed25519-pub', - }; - const encoded = keyToMultibaseId({ key: input.key, multicodecName: input.multicodecName }); - expect(encoded).to.be.a.string; - expect(encoded.substring(0, 1)).to.equal('z'); - expect(encoded.substring(1, 4)).to.equal('6Mk'); + describe('getVerificationMethodIds()', () => { + for (const vector of didDocumentIdTestVectors) { + it(`passes test vector ${vector.id}`, () => { + const methodIds = getVerificationMethodIds(vector.input as any); + expect(methodIds).to.deep.equal(vector.output); + }); + } + }); + + describe('getTypesFromDocument()', () => { + for (const vector of didDocumentTypeTestVectors) { + it(`passes test vector ${vector.id}`, () => { + const types = getVerificationMethodTypes(vector.input); + expect(types).to.deep.equal(vector.output); + }); + } + }); + + describe('parseDid()', () => { + it('extracts ION DID long form identifier from DID URL', async () => { + const { did } = parseDid({ + didUrl: 'did:ion:EiAi68p2irCNQIzaui8gTjGDeOqSUusZS8jWVHfseSWZ5g:eyJkZWx0YSI6eyJwYXRjaGVzIjpbeyJhY3Rpb24iOiJyZXBsYWNlIiwiZG9jdW1lbnQiOnsicHVibGljS2V5cyI6W3siaWQiOiJrZXktMSIsInB1YmxpY0tleUp3ayI6eyJjcnYiOiJzZWNwMjU2azEiLCJrdHkiOiJFQyIsIngiOiI2MWlQWXVHZWZ4b3R6QmRRWnREdnY2Y1dIWm1YclRUc2NZLXU3WTJwRlpjIiwieSI6Ijg4blBDVkxmckFZOWktd2c1T1Jjd1ZiSFdDX3RiZUFkMUpFMmUwY28wbFUifSwicHVycG9zZXMiOlsiYXV0aGVudGljYXRpb24iXSwidHlwZSI6IkVjZHNhU2VjcDI1NmsxVmVyaWZpY2F0aW9uS2V5MjAxOSJ9XSwic2VydmljZXMiOlt7ImlkIjoiZHduIiwic2VydmljZUVuZHBvaW50Ijp7Im5vZGVzIjpbImh0dHA6Ly9sb2NhbGhvc3Q6ODA4NSJdfSwidHlwZSI6IkRlY2VudHJhbGl6ZWRXZWJOb2RlIn1dfX1dLCJ1cGRhdGVDb21taXRtZW50IjoiRWlCb1c2dGs4WlZRTWs3YjFubkF2R3F3QTQ2amlaaUc2dWNYemxyNTZDWWFiUSJ9LCJzdWZmaXhEYXRhIjp7ImRlbHRhSGFzaCI6IkVpQ3Y2cUhEMFV4TTBadmZlTHU4dDR4eU5DVjNscFBSaTl6a3paU3h1LW8wWUEiLCJyZWNvdmVyeUNvbW1pdG1lbnQiOiJFaUN0STM0ckdGNU9USkJETXRUYm14a1lQeC0ydFd3MldZLTU2UTVPNHR0WWJBIn19' + }) ?? {}; + + expect(did).to.equal('did:ion:EiAi68p2irCNQIzaui8gTjGDeOqSUusZS8jWVHfseSWZ5g:eyJkZWx0YSI6eyJwYXRjaGVzIjpbeyJhY3Rpb24iOiJyZXBsYWNlIiwiZG9jdW1lbnQiOnsicHVibGljS2V5cyI6W3siaWQiOiJrZXktMSIsInB1YmxpY0tleUp3ayI6eyJjcnYiOiJzZWNwMjU2azEiLCJrdHkiOiJFQyIsIngiOiI2MWlQWXVHZWZ4b3R6QmRRWnREdnY2Y1dIWm1YclRUc2NZLXU3WTJwRlpjIiwieSI6Ijg4blBDVkxmckFZOWktd2c1T1Jjd1ZiSFdDX3RiZUFkMUpFMmUwY28wbFUifSwicHVycG9zZXMiOlsiYXV0aGVudGljYXRpb24iXSwidHlwZSI6IkVjZHNhU2VjcDI1NmsxVmVyaWZpY2F0aW9uS2V5MjAxOSJ9XSwic2VydmljZXMiOlt7ImlkIjoiZHduIiwic2VydmljZUVuZHBvaW50Ijp7Im5vZGVzIjpbImh0dHA6Ly9sb2NhbGhvc3Q6ODA4NSJdfSwidHlwZSI6IkRlY2VudHJhbGl6ZWRXZWJOb2RlIn1dfX1dLCJ1cGRhdGVDb21taXRtZW50IjoiRWlCb1c2dGs4WlZRTWs3YjFubkF2R3F3QTQ2amlaaUc2dWNYemxyNTZDWWFiUSJ9LCJzdWZmaXhEYXRhIjp7ImRlbHRhSGFzaCI6IkVpQ3Y2cUhEMFV4TTBadmZlTHU4dDR4eU5DVjNscFBSaTl6a3paU3h1LW8wWUEiLCJyZWNvdmVyeUNvbW1pdG1lbnQiOiJFaUN0STM0ckdGNU9USkJETXRUYm14a1lQeC0ydFd3MldZLTU2UTVPNHR0WWJBIn19'); }); - it('passes test vectors', () => { - let input: { key: Uint8Array, multicodecName: string }; - let output: string; - let encoded: string; - - // Test Vector 1. - input = { - key : (new Uint8Array(32)).fill(0), - multicodecName : 'ed25519-pub', - }; - output = 'z6MkeTG3bFFSLYVU7VqhgZxqr6YzpaGrQtFMh1uvqGy1vDnP'; - encoded = keyToMultibaseId({ key: input.key, multicodecName: input.multicodecName }); - expect(encoded).to.equal(output); - - // Test Vector 2. - input = { - key : (new Uint8Array(32)).fill(1), - multicodecName : 'ed25519-pub', - }; - output = 'z6MkeXBLjYiSvqnhFb6D7sHm8yKm4jV45wwBFRaatf1cfZ76'; - encoded = keyToMultibaseId({ key: input.key, multicodecName: input.multicodecName }); - expect(encoded).to.equal(output); - - // Test Vector 3. - input = { - key : (new Uint8Array(32)).fill(9), - multicodecName : 'ed25519-pub', - }; - output = 'z6Mkf4XhsxSXfEAWNK6GcFu7TyVs21AfUTRjiguqMhNQeDgk'; - encoded = keyToMultibaseId({ key: input.key, multicodecName: input.multicodecName }); - expect(encoded).to.equal(output); + it('extracts ION DID long form identifier from DID URL with query and fragment', async () => { + const { did } = parseDid({ + didUrl: 'did:ion:EiAi68p2irCNQIzaui8gTjGDeOqSUusZS8jWVHfseSWZ5g:eyJkZWx0YSI6eyJwYXRjaGVzIjpbeyJhY3Rpb24iOiJyZXBsYWNlIiwiZG9jdW1lbnQiOnsicHVibGljS2V5cyI6W3siaWQiOiJrZXktMSIsInB1YmxpY0tleUp3ayI6eyJjcnYiOiJzZWNwMjU2azEiLCJrdHkiOiJFQyIsIngiOiI2MWlQWXVHZWZ4b3R6QmRRWnREdnY2Y1dIWm1YclRUc2NZLXU3WTJwRlpjIiwieSI6Ijg4blBDVkxmckFZOWktd2c1T1Jjd1ZiSFdDX3RiZUFkMUpFMmUwY28wbFUifSwicHVycG9zZXMiOlsiYXV0aGVudGljYXRpb24iXSwidHlwZSI6IkVjZHNhU2VjcDI1NmsxVmVyaWZpY2F0aW9uS2V5MjAxOSJ9XSwic2VydmljZXMiOlt7ImlkIjoiZHduIiwic2VydmljZUVuZHBvaW50Ijp7Im5vZGVzIjpbImh0dHA6Ly9sb2NhbGhvc3Q6ODA4NSJdfSwidHlwZSI6IkRlY2VudHJhbGl6ZWRXZWJOb2RlIn1dfX1dLCJ1cGRhdGVDb21taXRtZW50IjoiRWlCb1c2dGs4WlZRTWs3YjFubkF2R3F3QTQ2amlaaUc2dWNYemxyNTZDWWFiUSJ9LCJzdWZmaXhEYXRhIjp7ImRlbHRhSGFzaCI6IkVpQ3Y2cUhEMFV4TTBadmZlTHU4dDR4eU5DVjNscFBSaTl6a3paU3h1LW8wWUEiLCJyZWNvdmVyeUNvbW1pdG1lbnQiOiJFaUN0STM0ckdGNU9USkJETXRUYm14a1lQeC0ydFd3MldZLTU2UTVPNHR0WWJBIn19?service=agent&relativeRef=/credentials#degree' + }) ?? {}; + + expect(did).to.equal('did:ion:EiAi68p2irCNQIzaui8gTjGDeOqSUusZS8jWVHfseSWZ5g:eyJkZWx0YSI6eyJwYXRjaGVzIjpbeyJhY3Rpb24iOiJyZXBsYWNlIiwiZG9jdW1lbnQiOnsicHVibGljS2V5cyI6W3siaWQiOiJrZXktMSIsInB1YmxpY0tleUp3ayI6eyJjcnYiOiJzZWNwMjU2azEiLCJrdHkiOiJFQyIsIngiOiI2MWlQWXVHZWZ4b3R6QmRRWnREdnY2Y1dIWm1YclRUc2NZLXU3WTJwRlpjIiwieSI6Ijg4blBDVkxmckFZOWktd2c1T1Jjd1ZiSFdDX3RiZUFkMUpFMmUwY28wbFUifSwicHVycG9zZXMiOlsiYXV0aGVudGljYXRpb24iXSwidHlwZSI6IkVjZHNhU2VjcDI1NmsxVmVyaWZpY2F0aW9uS2V5MjAxOSJ9XSwic2VydmljZXMiOlt7ImlkIjoiZHduIiwic2VydmljZUVuZHBvaW50Ijp7Im5vZGVzIjpbImh0dHA6Ly9sb2NhbGhvc3Q6ODA4NSJdfSwidHlwZSI6IkRlY2VudHJhbGl6ZWRXZWJOb2RlIn1dfX1dLCJ1cGRhdGVDb21taXRtZW50IjoiRWlCb1c2dGs4WlZRTWs3YjFubkF2R3F3QTQ2amlaaUc2dWNYemxyNTZDWWFiUSJ9LCJzdWZmaXhEYXRhIjp7ImRlbHRhSGFzaCI6IkVpQ3Y2cUhEMFV4TTBadmZlTHU4dDR4eU5DVjNscFBSaTl6a3paU3h1LW8wWUEiLCJyZWNvdmVyeUNvbW1pdG1lbnQiOiJFaUN0STM0ckdGNU9USkJETXRUYm14a1lQeC0ydFd3MldZLTU2UTVPNHR0WWJBIn19'); + }); + + it('extracts query and fragment from DID URL', () => { + const { fragment, query } = parseDid({ + didUrl: 'did:example:123?service=agent&relativeRef=/credentials#degree' + }) ?? {}; + + expect(fragment).to.equal('degree'); + expect(query).to.equal('service=agent&relativeRef=/credentials'); }); }); }); \ No newline at end of file diff --git a/packages/dids/tsconfig.cjs.json b/packages/dids/tsconfig.cjs.json index 0384273d6..e34b3674f 100644 --- a/packages/dids/tsconfig.cjs.json +++ b/packages/dids/tsconfig.cjs.json @@ -10,9 +10,11 @@ "outDir": "dist/cjs", "declaration": false, "declarationMap": false, - "declarationDir": null + "declarationDir": null, + "downlevelIteration": true }, "include": [ - "src" + "src", + "typings" ] } \ No newline at end of file diff --git a/packages/dids/tsconfig.json b/packages/dids/tsconfig.json index c8ce1cc3d..efd20a630 100644 --- a/packages/dids/tsconfig.json +++ b/packages/dids/tsconfig.json @@ -20,7 +20,6 @@ "typings" ], "exclude": [ - "node_modules", - "dist" + "node_modules" ] } \ No newline at end of file diff --git a/packages/dids/typings/decentralized-identity__ion-pow-sdk.d.ts b/packages/dids/typings/decentralized-identity__ion-pow-sdk.d.ts new file mode 100644 index 000000000..03fc84a06 --- /dev/null +++ b/packages/dids/typings/decentralized-identity__ion-pow-sdk.d.ts @@ -0,0 +1,7 @@ +declare module '@decentralized-identity/ion-pow-sdk' { + export default class IonProofOfWork { + static randomHexString(): string; + static submitIonRequestUntilSuccess(getChallengeUri: string, solveChallengeUri: string, requestBody: string): Promise; + static submitIonRequest(getChallengeUri: string, solveChallengeUri: string, requestBody: string): Promise; + } +} \ No newline at end of file diff --git a/packages/dids/typings/decentralized-identity__ion-tools.d.ts b/packages/dids/typings/decentralized-identity__ion-tools.d.ts deleted file mode 100644 index 9a54c0d30..000000000 --- a/packages/dids/typings/decentralized-identity__ion-tools.d.ts +++ /dev/null @@ -1 +0,0 @@ -declare module '@decentralized-identity/ion-tools'; \ No newline at end of file diff --git a/packages/web5/.c8rc.json b/packages/identity-agent/.c8rc.json similarity index 66% rename from packages/web5/.c8rc.json rename to packages/identity-agent/.c8rc.json index 1d1670b70..ab680f663 100644 --- a/packages/web5/.c8rc.json +++ b/packages/identity-agent/.c8rc.json @@ -5,12 +5,12 @@ ".js" ], "include": [ - "tests/compiled/**" + "tests/compiled/src/**" ], "exclude": [ - "tests/compiled/src/main.js", + "tests/compiled/src/index.js", "tests/compiled/src/types.js", - "tests/compiled/types/**" + "tests/compiled/src/types/**" ], "reporter": [ "cobertura", diff --git a/packages/web5/.mocharc.json b/packages/identity-agent/.mocharc.json similarity index 100% rename from packages/web5/.mocharc.json rename to packages/identity-agent/.mocharc.json diff --git a/packages/web5-user-agent/.vscode/launch.json b/packages/identity-agent/.vscode/launch.json similarity index 100% rename from packages/web5-user-agent/.vscode/launch.json rename to packages/identity-agent/.vscode/launch.json diff --git a/packages/identity-agent/.vscode/tasks.json b/packages/identity-agent/.vscode/tasks.json new file mode 100644 index 000000000..c5b8d931a --- /dev/null +++ b/packages/identity-agent/.vscode/tasks.json @@ -0,0 +1,37 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "label": "npm run build", + "type": "npm", + "script": "build", + "group": { + "kind": "build", + "isDefault": true + } + }, + { + "label": "tsc: build - tsconfig.json", + "type": "typescript", + "tsconfig": "tsconfig.json", + "problemMatcher": [ + "$tsc" + ], + "group": { + "kind": "build", + "isDefault": false + }, + "options": { + "cwd": "${workspaceFolder:root}" + } + }, + { + "label": "build tests", + "type": "npm", + "script": "build:tests:node", + "options": { + "cwd": "${workspaceFolder:identity-agent}" + } + } + ] +} \ No newline at end of file diff --git a/packages/identity-agent/build/bundles.js b/packages/identity-agent/build/bundles.js new file mode 100644 index 000000000..752ac09fc --- /dev/null +++ b/packages/identity-agent/build/bundles.js @@ -0,0 +1,16 @@ +import esbuild from 'esbuild'; +import browserConfig from './esbuild-browser-config.cjs'; + +// esm polyfilled bundle for browser +esbuild.build({ + ...browserConfig, + outfile: 'dist/browser.mjs', +}); + +// iife polyfilled bundle for browser +esbuild.build({ + ...browserConfig, + format : 'iife', + globalName : 'Web5IdentityAgent', + outfile : 'dist/browser.js', +}); \ No newline at end of file diff --git a/packages/identity-agent/build/esbuild-browser-config.cjs b/packages/identity-agent/build/esbuild-browser-config.cjs new file mode 100644 index 000000000..bd8bd99b1 --- /dev/null +++ b/packages/identity-agent/build/esbuild-browser-config.cjs @@ -0,0 +1,30 @@ +/* eslint-disable @typescript-eslint/no-var-requires */ +const polyfillProviderPlugin = require('node-stdlib-browser/helpers/esbuild/plugin'); +const stdLibBrowser = require('node-stdlib-browser'); + +const requiredPolyfills = new Set(['crypto', 'node:crypto', 'stream']); + +// populate object containing lib -> polyfill path +const polyfills = {}; +for (let lib in stdLibBrowser) { + if (requiredPolyfills.has(lib)) { + const polyfill = stdLibBrowser[lib]; + polyfills[lib] = polyfill; + } +} + +/** @type {import('esbuild').BuildOptions} */ +module.exports = { + entryPoints : ['./src/index.ts'], + bundle : true, + format : 'esm', + sourcemap : true, + minify : true, + platform : 'browser', + target : ['chrome101', 'firefox108', 'safari16'], + inject : [require.resolve('node-stdlib-browser/helpers/esbuild/shim')], + plugins : [polyfillProviderPlugin(polyfills)], + define : { + 'global': 'globalThis', + }, +}; \ No newline at end of file diff --git a/packages/identity-agent/karma.conf.cjs b/packages/identity-agent/karma.conf.cjs new file mode 100644 index 000000000..aa89cbca8 --- /dev/null +++ b/packages/identity-agent/karma.conf.cjs @@ -0,0 +1,86 @@ +/* eslint-disable @typescript-eslint/no-var-requires */ +// Karma is what we're using to run our tests in browser environments +// Karma does not support .mjs + +// playwright acts as a safari executable on windows and mac +const playwright = require('@playwright/test'); +const esbuildBrowserConfig = require('./build/esbuild-browser-config.cjs'); + +// use playwright chrome exec path as run target for chromium tests +process.env.CHROME_BIN = playwright.chromium.executablePath(); + +// use playwright webkit exec path as run target for safari tests +process.env.WEBKIT_HEADLESS_BIN = playwright.webkit.executablePath(); + +// use playwright firefox exec path as run target for firefox tests +process.env.FIREFOX_BIN = playwright.firefox.executablePath(); + +module.exports = function (config) { + config.set({ + plugins: [ + 'karma-chrome-launcher', + 'karma-firefox-launcher', + 'karma-webkit-launcher', + 'karma-esbuild', + 'karma-mocha', + 'karma-mocha-reporter', + ], + + // frameworks to use + // available frameworks: https://www.npmjs.com/search?q=keywords:karma-adapter + frameworks: ['mocha'], + + // Increase Mocha's default timeout of 2 seconds to prevent timeouts during GitHub CI runs. + client: { + mocha: { + timeout: 10000 // 10 seconds + } + }, + + + // list of files / patterns to load in the browser + files: [ + { pattern: 'tests/**/*.spec.ts', watched: false }, + ], + + // preprocess matching files before serving them to the browser + // available preprocessors: https://www.npmjs.com/search?q=keywords:karma-preprocessor + preprocessors: { + 'tests/**/*.spec.ts': ['esbuild'], + }, + + esbuild: esbuildBrowserConfig, + + // list of files / patterns to exclude + exclude: [], + + // test results reporter to use + // available reporters: https://www.npmjs.com/search?q=keywords:karma-reporter + reporters: ['mocha'], + + // web server port + port: 9876, + + // enable / disable colors in the output (reporters and logs) + colors: true, + + // level of logging + // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || + // config.LOG_INFO || config.LOG_DEBUG + logLevel: config.LOG_INFO, + + concurrency: 1, + + // start these browsers + // available browser launchers: https://www.npmjs.com/search?q=keywords:karma-launcher + browsers: ['ChromeHeadless', 'FirefoxHeadless', 'WebkitHeadless'], + + // Continuous Integration mode + // if true, Karma captures browsers, runs the tests and exits + singleRun: true, + + // Increase browser timeouts to avoid DISCONNECTED messages during GitHub CI runs. + browserDisconnectTimeout : 10000, // default 2000 + browserDisconnectTolerance : 1, // default 0 + }); +}; diff --git a/packages/web5-agent/package.json b/packages/identity-agent/package.json similarity index 77% rename from packages/web5-agent/package.json rename to packages/identity-agent/package.json index ed090fdd0..0c96d7fe4 100644 --- a/packages/web5-agent/package.json +++ b/packages/identity-agent/package.json @@ -1,13 +1,12 @@ { - "name": "@tbd54566975/web5-agent", - "version": "0.8.0", - "description": "Web5 Agent", + "name": "@web5/identity-agent", + "version": "0.1.0", "type": "module", - "main": "./dist/cjs/main.js", - "module": "./dist/esm/main.js", - "types": "./dist/types/main.d.ts", + "main": "./dist/cjs/index.js", + "module": "./dist/esm/index.js", + "types": "./dist/types/index.d.ts", "scripts": { - "clean": "rimraf dist coverage tests/compiled", + "clean": "rimraf dist tests/compiled", "build:esm": "rimraf dist/esm dist/types && npx tsc -p tsconfig.json", "build:cjs": "rimraf dist/cjs && tsc -p tsconfig.cjs.json && echo '{\"type\": \"commonjs\"}' > ./dist/cjs/package.json", "build:browser": "rimraf dist/browser.mjs dist/browser.js && node build/bundles.js", @@ -18,12 +17,12 @@ "test:node": "npm run build:tests:node && c8 mocha", "test:browser": "karma start karma.conf.cjs" }, - "homepage": "https://github.com/TBD54566975/web5-js/tree/main/packages/web5-agent#readme", + "homepage": "https://github.com/TBD54566975/web5-js/tree/main/packages/identity-agent#readme", "bugs": "https://github.com/TBD54566975/web5-js/issues", "repository": { "type": "git", "url": "git+https://github.com/TBD54566975/web5-js", - "directory": "packages/web5-agent" + "directory": "packages/identity-agent" }, "license": "Apache-2.0", "contributors": [ @@ -46,17 +45,19 @@ ], "exports": { ".": { - "import": "./dist/esm/main.js", - "require": "./dist/cjs/main.js", - "types": "./dist/types/main.d.ts" + "import": "./dist/esm/index.js", + "require": "./dist/cjs/index.js", + "types": "./dist/types/index.d.ts" } }, - "react-native": "./dist/esm/main.mjs", + "react-native": "./dist/esm/index.js", "keywords": [ "decentralized", "decentralized-applications", "decentralized-identity", "decentralized-web", + "vcs", + "verifiable credentials", "web5" ], "publishConfig": { @@ -66,16 +67,14 @@ "node": ">=18.0.0" }, "dependencies": { - "readable-stream": "4.4.0", - "@tbd54566975/dwn-sdk-js": "0.2.1" + "@web5/agent": "0.1.7" }, "devDependencies": { + "@playwright/test": "1.36.2", "@types/chai": "4.3.0", "@types/chai-as-promised": "7.1.5", "@types/eslint": "8.37.0", "@types/mocha": "10.0.1", - "@types/readable-stream": "2.3.15", - "@types/sinon": "10.0.15", "@typescript-eslint/eslint-plugin": "5.59.0", "@typescript-eslint/parser": "5.59.0", "c8": "8.0.0", @@ -93,10 +92,9 @@ "karma-mocha-reporter": "2.2.5", "karma-webkit-launcher": "2.1.0", "mocha": "10.2.0", + "node-stdlib-browser": "1.2.0", "playwright": "1.36.2", "rimraf": "4.4.0", - "sinon": "15.0.2", - "source-map-loader": "4.0.1", "typescript": "5.1.6" } -} \ No newline at end of file +} diff --git a/packages/identity-agent/src/identity-agent.ts b/packages/identity-agent/src/identity-agent.ts new file mode 100644 index 000000000..3f669cded --- /dev/null +++ b/packages/identity-agent/src/identity-agent.ts @@ -0,0 +1,238 @@ +import type { + DwnRpc, + VcResponse, + DidResponse, + DwnResponse, + AppDataStore, + SendVcRequest, + SendDidRequest, + SendDwnRequest, + ProcessVcRequest, + Web5ManagedAgent, + ProcessDwnRequest, + ProcessDidRequest, +} from '@web5/agent'; + +import { LevelStore } from '@web5/common'; +import { EdDsaAlgorithm } from '@web5/crypto'; +import { DidResolver, DidKeyMethod } from '@web5/dids'; +import { + LocalKms, + DidManager, + DwnManager, + KeyManager, + DidStoreDwn, + KeyStoreDwn, + AppDataVault, + Web5RpcClient, + IdentityManager, + IdentityStoreDwn, + PrivateKeyStoreDwn, + cryptoToPortableKeyPair, +} from '@web5/agent'; + +export type IdentityAgentOptions = { + agentDid: string; + appData: AppDataStore; + didManager: DidManager; + didResolver: DidResolver; + dwnManager: DwnManager; + identityManager: IdentityManager; + keyManager: KeyManager; + rpcClient: DwnRpc; +} + +export class IdentityAgent implements Web5ManagedAgent { + agentDid: string; + appData: AppDataStore; + didManager: DidManager; + didResolver: DidResolver; + dwnManager: DwnManager; + identityManager: IdentityManager; + keyManager: KeyManager; + rpcClient: DwnRpc; + + constructor(options: IdentityAgentOptions) { + this.agentDid = options.agentDid; + this.appData = options.appData; + this.didManager = options.didManager; + this.didResolver = options.didResolver; + this.dwnManager = options.dwnManager; + this.identityManager = options.identityManager; + this.keyManager = options.keyManager; + this.rpcClient = options.rpcClient; + + // Set this agent to be the default agent. + this.didManager.agent = this; + this.dwnManager.agent = this; + this.identityManager.agent = this; + this.keyManager.agent = this; + } + + static async create(options: Partial = {}): Promise { + let { agentDid, appData, didManager, didResolver, dwnManager, identityManager, keyManager, rpcClient } = options; + + if (agentDid === undefined) { + // An Agent DID was not specified, so set to empty string. + agentDid = ''; + } + + if (appData === undefined) { + // A custom AppDataStore implementation was not specified, so + // instantiate a LevelDB backed secure AppDataVault. + appData = new AppDataVault({ + store: new LevelStore('data/agent/vault') + }); + } + + if (didManager === undefined) { + // A custom DidManager implementation was not specified, so + // instantiate a default with in-memory store. + didManager = new DidManager({ + didMethods : [DidKeyMethod], + store : new DidStoreDwn() + }); + } + + if (didResolver === undefined) { + // A custom DidManager implementation was not specified, so + // instantiate a default with in-memory store. + didResolver = new DidResolver({ didResolvers: [DidKeyMethod] }); + } + + if (dwnManager === undefined) { + // A custom DwnManager implementation was not specified, so + // instantiate a default. + dwnManager = await DwnManager.create({ didResolver }); + } + + if (identityManager === undefined) { + // A custom IdentityManager implementation was not specified, so + // instantiate a default that uses a DWN store. + identityManager = new IdentityManager({ + store: new IdentityStoreDwn() + }); + } + + if (keyManager === undefined) { + // A custom KeyManager implementation was not specified, so + // instantiate a default with KMSs. + const localKmsDwn = new LocalKms({ + kmsName : 'local', + keyStore : new KeyStoreDwn({ schema: 'https://identity.foundation/schemas/web5/kms-key' }), + privateKeyStore : new PrivateKeyStoreDwn() + }); + const localKmsMemory = new LocalKms({ + kmsName: 'memory' + }); + keyManager = new KeyManager({ + kms: { + local : localKmsDwn, + memory : localKmsMemory + }, + store: new KeyStoreDwn({ schema: 'https://identity.foundation/schemas/web5/managed-key' }) + }); + } + + if (rpcClient === undefined) { + // A custom RPC Client implementation was not specified, so + // instantiate a default. + rpcClient = new Web5RpcClient(); + } + + // Instantiate the Identity Agent. + const agent = new IdentityAgent({ + agentDid, + appData, + didManager, + didResolver, + dwnManager, + identityManager, + keyManager, + rpcClient + }); + + return agent; + } + + async firstLaunch(): Promise { + // Check whether data vault is already initialized. + const { initialized } = await this.appData.getStatus(); + return initialized === false; + } + + /** + * Executed once the first time the Identity Agent is launched. + * The passphrase should be input by the end-user. + * */ + async initialize(options: { passphrase: string }) { + const { passphrase } = options; + + // Generate an Ed25519 key pair for the Identity Agent. + const agentKeyPair = await new EdDsaAlgorithm().generateKey({ + algorithm : { name: 'EdDSA', namedCurve: 'Ed25519' }, + extractable : true, + keyUsages : ['sign', 'verify'] + }); + + /** Initialize the AppDataStore with the Identity Agent's + * private key and passphrase, which also unlocks the data vault. */ + await this.appData.initialize({ + passphrase : passphrase, + keyPair : agentKeyPair, + }); + } + + async processDidRequest(_request: ProcessDidRequest): Promise { + throw new Error('Not implemented'); + } + + async processDwnRequest(request: ProcessDwnRequest): Promise { + return this.dwnManager.processRequest(request); + } + + async processVcRequest(_request: ProcessVcRequest): Promise { + throw new Error('Not implemented'); + } + + async sendDidRequest(_request: SendDidRequest): Promise { + throw new Error('Not implemented'); + } + + async sendDwnRequest(request: SendDwnRequest): Promise { + return this.dwnManager.sendRequest(request); + } + + async sendVcRequest(_request: SendVcRequest): Promise { + throw new Error('Not implemented'); + } + + async start(options: { passphrase: string }) { + const { passphrase } = options; + + if (await this.firstLaunch()) { + // 1A. Agent's first launch so initialize. + await this.initialize({ passphrase }); + } else { + // 1B. Agent was previously initialized. + // Unlock the data vault and cache the vault unlock key (VUK) in memory. + await this.appData.unlock({ passphrase }); + } + + // 2. Set the Identity Agent's root did:key identifier. + this.agentDid = await this.appData.getDid(); + + // 3. Import the Identity Agent's signing key pair to KeyManager. + const defaultSigningKey = cryptoToPortableKeyPair({ + cryptoKeyPair: { + privateKey : await this.appData.getPrivateKey(), + publicKey : await this.appData.getPublicKey() + }, + keyData: { + alias : await this.didManager.getDefaultSigningKey({ did: this.agentDid }), + kms : 'memory' + } + }); + await this.keyManager.setDefaultSigningKey({ key: defaultSigningKey }); + } +} \ No newline at end of file diff --git a/packages/identity-agent/src/index.ts b/packages/identity-agent/src/index.ts new file mode 100644 index 000000000..976d400e2 --- /dev/null +++ b/packages/identity-agent/src/index.ts @@ -0,0 +1 @@ +export * from './identity-agent.js'; \ No newline at end of file diff --git a/packages/identity-agent/tests/identity-agent.spec.ts b/packages/identity-agent/tests/identity-agent.spec.ts new file mode 100644 index 000000000..4debdcf52 --- /dev/null +++ b/packages/identity-agent/tests/identity-agent.spec.ts @@ -0,0 +1,177 @@ +import * as sinon from 'sinon'; +import chai, { expect } from 'chai'; +import chaiAsPromised from 'chai-as-promised'; +import { TestManagedAgent } from '@web5/agent'; + +import { IdentityAgent } from '../src/identity-agent.js'; + +chai.use(chaiAsPromised); + +// NOTE: @noble/secp256k1 requires globalThis.crypto polyfill for node.js <=18: https://github.com/paulmillr/noble-secp256k1/blob/main/README.md#usage +// Remove when we move off of node.js v18 to v20, earliest possible time would be Oct 2023: https://github.com/nodejs/release#release-schedule +// NOTE: @noble/secp256k1 requires globalThis.crypto polyfill for node.js <=18: https://github.com/paulmillr/noble-secp256k1/blob/main/README.md#usage +// Remove when we move off of node.js v18 to v20, earliest possible time would be Oct 2023: https://github.com/nodejs/release#release-schedule +import { webcrypto } from 'node:crypto'; +// @ts-ignore +if (!globalThis.crypto) globalThis.crypto = webcrypto; + +describe('IdentityAgent', () => { + + const agentStoreTypes = ['dwn', 'memory'] as const; + agentStoreTypes.forEach((agentStoreType) => { + + describe(`with ${agentStoreType} data stores`, () => { + let testAgent: TestManagedAgent; + + before(async () => { + testAgent = await TestManagedAgent.create({ + agentClass : IdentityAgent, + agentStores : agentStoreType + }); + }); + + beforeEach(async () => { + await testAgent.clearStorage(); + }); + + after(async () => { + await testAgent.clearStorage(); + await testAgent.closeStorage(); + }); + + describe('firstLaunch()', () => { + it('returns true the first time the Identity Agent runs', async () => { + await expect(testAgent.agent.firstLaunch()).to.eventually.be.true; + }); + + it('returns false after Identity Agent initialization', async () => { + await expect(testAgent.agent.firstLaunch()).to.eventually.be.true; + + await testAgent.agent.start({ passphrase: 'test' }); + await expect(testAgent.agent.firstLaunch()).to.eventually.be.false; + }); + }); + + describe('initialize()', () => { + it('initializes the AppData store and stores the vault key set', async () => { + await testAgent.agent.initialize({ passphrase: 'test' }); + + // Confirm the AppData store was initialized. + const { initialized } = await testAgent.agent.appData.getStatus(); + expect(initialized).to.be.true; + + // Confirm the vault key set was stored. + const storedVaultKeySet = await testAgent.appDataStore.get('vaultKeySet'); + expect(storedVaultKeySet).to.exist; + expect(storedVaultKeySet).to.be.a.string; + }); + }); + + describe('start()', () => { + it('initializes the AppData store the first time the Identity Agent runs', async () => { + // const agent = await IdentityAgent.create({ appData, dwnManager }); + let initializeSpy = sinon.spy(testAgent.agent, 'initialize'); + let unlockSpy = sinon.spy(testAgent.agent.appData, 'unlock'); + + // Execute agent.start() for the first time. + await testAgent.agent.start({ passphrase: 'test' }); + + // Confirm agent.initialize() was called. + expect(initializeSpy.called).to.be.true; + + // Confirm agent.appData.unlock() was not called. + expect(unlockSpy.called).to.be.false; + + // Confirm the AppData store was initialized. + const { initialized } = await testAgent.agent.appData.getStatus(); + expect(initialized).to.be.true; + + // Confirm the vault key set was stored. + const storedVaultKeySet = await testAgent.appDataStore.get('vaultKeySet'); + expect(storedVaultKeySet).to.exist; + expect(storedVaultKeySet).to.be.a.string; + + // Confirm the AppData store was unlocked. + // @ts-expect-error because a private variable is being intentionally accessed. + const vaultUnlockKey = testAgent.agent.appData._vaultUnlockKey; + expect(vaultUnlockKey).to.exist; + expect(vaultUnlockKey).to.be.a('Uint8Array'); + + // Confirm the Agent's key pair was stored in KeyManager. + if (testAgent.agent.agentDid === undefined) throw new Error(); // Type guard. + const signingKeyId = await testAgent.agent.didManager.getDefaultSigningKey({ did: testAgent.agent.agentDid }); + if (!signingKeyId) throw new Error('Type guard'); + const agentKeyPair = await testAgent.agent.keyManager.getKey({ keyRef: signingKeyId }); + expect(agentKeyPair).to.exist; + expect(agentKeyPair).to.have.property('privateKey'); + expect(agentKeyPair).to.have.property('publicKey'); + + initializeSpy.restore(); + unlockSpy.restore(); + }); + + it('unlocks the AppData store on subsequent Identity Agent runs', async () => { + // Execute agent.start() for the first time. + await testAgent.agent.start({ passphrase: 'test' }); + + let initializeSpy = sinon.spy(testAgent.agent, 'initialize'); + let unlockSpy = sinon.spy(testAgent.agent.appData, 'unlock'); + + // Execute agent.start() for the second time. + await testAgent.agent.start({ passphrase: 'test' }); + + // Confirm agent.initialize() was not called. + expect(initializeSpy.called).to.be.false; + + // Confirm agent.appData.unlock() was called. + expect(unlockSpy.called).to.be.true; + + // Confirm the vault key set was stored. + const storedVaultKeySet = await testAgent.appDataStore.get('vaultKeySet'); + expect(storedVaultKeySet).to.exist; + expect(storedVaultKeySet).to.be.a.string; + + // Confirm the AppData store was unlocked. + // @ts-expect-error because a private variable is being intentionally accessed. + const vaultUnlockKey = testAgent.agent.appData._vaultUnlockKey; + expect(vaultUnlockKey).to.exist; + expect(vaultUnlockKey).to.be.a('Uint8Array'); + + // Confirm the Agent's key pair was stored in KeyManager. + if (testAgent.agent.agentDid === undefined) throw new Error(); // Type guard. + const signingKeyId = await testAgent.agent.didManager.getDefaultSigningKey({ did: testAgent.agent.agentDid }); + if (!signingKeyId) throw new Error('Type guard'); + const agentKeyPair = await testAgent.agent.keyManager.getKey({ keyRef: signingKeyId }); + expect(agentKeyPair).to.exist; + expect(agentKeyPair).to.have.property('privateKey'); + expect(agentKeyPair).to.have.property('publicKey'); + + initializeSpy.restore(); + unlockSpy.restore(); + }); + + it('unlocks the AppData store', async () => { + // const agent = await IdentityAgent.create({ appData, dwnManager }); + let initializeSpy = sinon.spy(testAgent.agent, 'initialize'); + let unlockSpy = sinon.spy(testAgent.agent.appData, 'unlock'); + + await testAgent.agent.start({ passphrase: 'test' }); + + // Confirm agent.initialize() was called. + expect(initializeSpy.called).to.be.true; + + // Confirm agent.appData.unlock() was not called. + expect(unlockSpy.called).to.be.false; + + // @ts-expect-error because a private variable is being intentionally accessed. + const vaultUnlockKey = testAgent.agent.appData._vaultUnlockKey; + expect(vaultUnlockKey).to.exist; + expect(vaultUnlockKey).to.be.a('Uint8Array'); + + initializeSpy.restore(); + unlockSpy.restore(); + }); + }); + }); + }); +}); \ No newline at end of file diff --git a/packages/identity-agent/tests/managing-identities.spec.ts b/packages/identity-agent/tests/managing-identities.spec.ts new file mode 100644 index 000000000..f608e1102 --- /dev/null +++ b/packages/identity-agent/tests/managing-identities.spec.ts @@ -0,0 +1,222 @@ +import { Web5 } from '@web5/api'; +import chai, { expect } from 'chai'; +import chaiAsPromised from 'chai-as-promised'; +import { TestManagedAgent } from '@web5/agent'; + +import { IdentityAgent } from '../src/identity-agent.js'; + +chai.use(chaiAsPromised); + +// NOTE: @noble/secp256k1 requires globalThis.crypto polyfill for node.js <=18: https://github.com/paulmillr/noble-secp256k1/blob/main/README.md#usage +// Remove when we move off of node.js v18 to v20, earliest possible time would be Oct 2023: https://github.com/nodejs/release#release-schedule +// NOTE: @noble/secp256k1 requires globalThis.crypto polyfill for node.js <=18: https://github.com/paulmillr/noble-secp256k1/blob/main/README.md#usage +// Remove when we move off of node.js v18 to v20, earliest possible time would be Oct 2023: https://github.com/nodejs/release#release-schedule +import { webcrypto } from 'node:crypto'; +// @ts-ignore +if (!globalThis.crypto) globalThis.crypto = webcrypto; + +describe('Managing Identities', () => { + + const agentStoreTypes = ['dwn', 'memory'] as const; + agentStoreTypes.forEach((agentStoreType) => { + + describe(`with ${agentStoreType} data stores`, () => { + let testAgent: TestManagedAgent; + + before(async () => { + testAgent = await TestManagedAgent.create({ + agentClass : IdentityAgent, + agentStores : agentStoreType + }); + }); + + beforeEach(async () => { + await testAgent.clearStorage(); + }); + + after(async () => { + await testAgent.clearStorage(); + await testAgent.closeStorage(); + }); + + describe('initial identity creation', () => { + it('can create three identities', async () => { + // Start agent for the first time. + await testAgent.agent.start({ passphrase: 'test' }); + + // Create three identities, each of which is stored in a new tenant. + const careerIdentity = await testAgent.agent.identityManager.create({ + name : 'Social', + didMethod : 'key', + kms : 'local' + }); + + const familyIdentity = await testAgent.agent.identityManager.create({ + name : 'Social', + didMethod : 'key', + kms : 'local' + }); + + const socialIdentity = await testAgent.agent.identityManager.create({ + name : 'Social', + didMethod : 'key', + kms : 'local' + }); + + // Verify the Identities were stored in each new Identity's tenant. + const storedCareerIdentity = await testAgent.agent.identityManager.get({ did: careerIdentity.did, context: careerIdentity.did }); + const storedFamilyIdentity = await testAgent.agent.identityManager.get({ did: familyIdentity.did, context: familyIdentity.did }); + const storedSocialIdentity = await testAgent.agent.identityManager.get({ did: socialIdentity.did, context: socialIdentity.did }); + expect(storedCareerIdentity).to.have.property('did', careerIdentity.did); + expect(storedFamilyIdentity).to.have.property('did', familyIdentity.did); + expect(storedSocialIdentity).to.have.property('did', socialIdentity.did); + }); + + // Tests that should only run for DWN-backed stores that provide multi-tenancy. + if (agentStoreType === 'dwn') { + it('supports tenant isolation between Identity Agent and Identities under management', async () => { + // Start agent for the first time. + await testAgent.agent.start({ passphrase: 'test' }); + + // Create three identities, each of which is stored in a new tenant. + const careerIdentity = await testAgent.agent.identityManager.create({ + name : 'Career', + didMethod : 'key', + kms : 'local' + }); + + const familyIdentity = await testAgent.agent.identityManager.create({ + name : 'Family', + didMethod : 'key', + kms : 'local' + }); + + const socialIdentity = await testAgent.agent.identityManager.create({ + name : 'Social', + didMethod : 'key', + kms : 'local' + }); + + // Import just the Identity metadata for the new identities to the Identity Agent's tenant. + await testAgent.agent.identityManager.import({ identity: careerIdentity, context: testAgent.agent.agentDid }); + await testAgent.agent.identityManager.import({ identity: familyIdentity, context: testAgent.agent.agentDid }); + await testAgent.agent.identityManager.import({ identity: socialIdentity, context: testAgent.agent.agentDid }); + + // Verify the Identities were stored in each new Identity's tenant. + const storedCareerIdentity = await testAgent.agent.identityManager.get({ did: careerIdentity.did, context: careerIdentity.did }); + const storedFamilyIdentity = await testAgent.agent.identityManager.get({ did: familyIdentity.did, context: familyIdentity.did }); + const storedSocialIdentity = await testAgent.agent.identityManager.get({ did: socialIdentity.did, context: socialIdentity.did }); + expect(storedCareerIdentity).to.have.property('did', careerIdentity.did); + expect(storedFamilyIdentity).to.have.property('did', familyIdentity.did); + expect(storedSocialIdentity).to.have.property('did', socialIdentity.did); + + // Verify the Identities were ALSO stored in the Identity Agent's tenant. + const storedIdentities = await testAgent.agent.identityManager.list(); + expect(storedIdentities).to.have.length(3); + + // Verify the DIDs were only stored in the new Identity's tenant. + let storedCareerDid = await testAgent.agent.didManager.get({ didRef: careerIdentity.did, context: careerIdentity.did }); + expect(storedCareerDid).to.exist; + storedCareerDid = await testAgent.agent.didManager.get({ didRef: careerIdentity.did }); + expect(storedCareerDid).to.not.exist; + let storedFamilyDid = await testAgent.agent.didManager.get({ didRef: familyIdentity.did, context: familyIdentity.did }); + expect(storedFamilyDid).to.exist; + storedFamilyDid = await testAgent.agent.didManager.get({ didRef: familyIdentity.did }); + expect(storedFamilyDid).to.not.exist; + let storedSocialDid = await testAgent.agent.didManager.get({ didRef: socialIdentity.did, context: socialIdentity.did }); + expect(storedSocialDid).to.exist; + storedSocialDid = await testAgent.agent.didManager.get({ didRef: socialIdentity.did }); + expect(storedSocialDid).to.not.exist; + + // Verify keys were stored in Identity Agent's DWN. + const careerKey = await testAgent.agent.keyManager.getKey({ + keyRef: await testAgent.agent.didManager.getDefaultSigningKey({ did: careerIdentity.did }) ?? '' // Type guard. + }); + expect(careerKey).to.exist; + const familyKey = await testAgent.agent.keyManager.getKey({ + keyRef: await testAgent.agent.didManager.getDefaultSigningKey({ did: familyIdentity.did }) ?? '' // Type guard. + }); + expect(familyKey).to.exist; + const socialKey = await testAgent.agent.keyManager.getKey({ + keyRef: await testAgent.agent.didManager.getDefaultSigningKey({ did: socialIdentity.did }) ?? '' // Type guard. + }); + expect(socialKey).to.exist; + }); + } + }); + + describe('Using Web5 API', async () => { + it('should instantiate Web5 API with provided Web5Agent and DID', async () => { + // Start agent for the first time. + await testAgent.agent.start({ passphrase: 'test' }); + + // Create two identities, each of which is stored in a new tenant. + const careerIdentity = await testAgent.agent.identityManager.create({ + name : 'Career', + didMethod : 'key', + kms : 'local' + }); + const socialIdentity = await testAgent.agent.identityManager.create({ + name : 'Social', + didMethod : 'key', + kms : 'local' + }); + + // Instantiate a Web5 instance with the "Career" Identity, write a record, and verify the result. + const web5Career = new Web5({ agent: testAgent.agent, connectedDid: careerIdentity.did }); + expect(web5Career).to.exist; + + // Instantiate a Web5 instance with the "Social" Identity, write a record, and verify the result. + const web5Social = new Web5({ agent: testAgent.agent, connectedDid: socialIdentity.did }); + expect(web5Social).to.exist; + }); + + it('Can write records using an Identity under management', async () => { + // Start agent for the first time. + await testAgent.agent.start({ passphrase: 'test' }); + + // Create two identities, each of which is stored in a new tenant. + const careerIdentity = await testAgent.agent.identityManager.create({ + name : 'Social', + didMethod : 'key', + kms : 'local' + }); + const socialIdentity = await testAgent.agent.identityManager.create({ + name : 'Social', + didMethod : 'key', + kms : 'local' + }); + + // Instantiate a Web5 instance with the "Career" Identity, write a record, and verify the result. + const web5Career = new Web5({ agent: testAgent.agent, connectedDid: careerIdentity.did }); + const careerResult = await web5Career.dwn.records.write({ + data : 'Hello, world!', + message : { + schema : 'foo/bar', + dataFormat : 'text/plain' + } + }); + expect(careerResult.status.code).to.equal(202); + expect(careerResult.record).to.exist; + expect(careerResult.record?.author).to.equal(careerIdentity.did); + expect(await careerResult.record?.data.text()).to.equal('Hello, world!'); + + // Instantiate a Web5 instance with the "Social" Identity, write a record, and verify the result. + const web5Social = new Web5({ agent: testAgent.agent, connectedDid: socialIdentity.did }); + const socialResult = await web5Social.dwn.records.write({ + data : 'Hello, everyone!', + message : { + schema : 'foo/bar', + dataFormat : 'text/plain' + } + }); + expect(socialResult.status.code).to.equal(202); + expect(socialResult.record).to.exist; + expect(socialResult.record?.author).to.equal(socialIdentity.did); + expect(await socialResult.record?.data.text()).to.equal('Hello, everyone!'); + }); + + }); + }); + }); +}); \ No newline at end of file diff --git a/packages/web5-user-agent/tests/tsconfig.json b/packages/identity-agent/tests/tsconfig.json similarity index 100% rename from packages/web5-user-agent/tests/tsconfig.json rename to packages/identity-agent/tests/tsconfig.json diff --git a/packages/web5-agent/tsconfig.cjs.json b/packages/identity-agent/tsconfig.cjs.json similarity index 100% rename from packages/web5-agent/tsconfig.cjs.json rename to packages/identity-agent/tsconfig.cjs.json diff --git a/packages/web5-user-agent/tsconfig.json b/packages/identity-agent/tsconfig.json similarity index 87% rename from packages/web5-user-agent/tsconfig.json rename to packages/identity-agent/tsconfig.json index 9627f3d42..b29e4e7e1 100644 --- a/packages/web5-user-agent/tsconfig.json +++ b/packages/identity-agent/tsconfig.json @@ -1,5 +1,6 @@ { "compilerOptions": { + "strict": true, "lib": [ "DOM", "ES6" @@ -11,12 +12,16 @@ "declarationMap": true, "declarationDir": "dist/types", "outDir": "dist/esm", + "sourceMap": true, // `NodeNext` will throw compilation errors if relative import paths are missing file extension // reference: https://devblogs.microsoft.com/typescript/announcing-typescript-4-7/#ecmascript-module-support-in-node-js "moduleResolution": "NodeNext", "esModuleInterop": true }, "include": [ - "src" + "src", + ], + "exclude": [ + "node_modules" ] } \ No newline at end of file diff --git a/packages/old/build/bundles.cjs b/packages/old/pre-polyrepo/build/bundles.cjs similarity index 100% rename from packages/old/build/bundles.cjs rename to packages/old/pre-polyrepo/build/bundles.cjs diff --git a/packages/old/build/esbuild-browser-config.cjs b/packages/old/pre-polyrepo/build/esbuild-browser-config.cjs similarity index 100% rename from packages/old/build/esbuild-browser-config.cjs rename to packages/old/pre-polyrepo/build/esbuild-browser-config.cjs diff --git a/packages/old/build/publish-unstable.sh b/packages/old/pre-polyrepo/build/publish-unstable.sh similarity index 100% rename from packages/old/build/publish-unstable.sh rename to packages/old/pre-polyrepo/build/publish-unstable.sh diff --git a/packages/old/examples/README.md b/packages/old/pre-polyrepo/examples/README.md similarity index 100% rename from packages/old/examples/README.md rename to packages/old/pre-polyrepo/examples/README.md diff --git a/packages/old/examples/simple-agent/.gitignore b/packages/old/pre-polyrepo/examples/simple-agent/.gitignore similarity index 100% rename from packages/old/examples/simple-agent/.gitignore rename to packages/old/pre-polyrepo/examples/simple-agent/.gitignore diff --git a/packages/old/examples/simple-agent/README.md b/packages/old/pre-polyrepo/examples/simple-agent/README.md similarity index 100% rename from packages/old/examples/simple-agent/README.md rename to packages/old/pre-polyrepo/examples/simple-agent/README.md diff --git a/packages/old/examples/simple-agent/etc/did.json b/packages/old/pre-polyrepo/examples/simple-agent/etc/did.json similarity index 100% rename from packages/old/examples/simple-agent/etc/did.json rename to packages/old/pre-polyrepo/examples/simple-agent/etc/did.json diff --git a/packages/old/examples/simple-agent/package-lock.json b/packages/old/pre-polyrepo/examples/simple-agent/package-lock.json similarity index 100% rename from packages/old/examples/simple-agent/package-lock.json rename to packages/old/pre-polyrepo/examples/simple-agent/package-lock.json diff --git a/packages/old/examples/simple-agent/package.json b/packages/old/pre-polyrepo/examples/simple-agent/package.json similarity index 100% rename from packages/old/examples/simple-agent/package.json rename to packages/old/pre-polyrepo/examples/simple-agent/package.json diff --git a/packages/old/examples/simple-agent/resources/test-protocol.json b/packages/old/pre-polyrepo/examples/simple-agent/resources/test-protocol.json similarity index 100% rename from packages/old/examples/simple-agent/resources/test-protocol.json rename to packages/old/pre-polyrepo/examples/simple-agent/resources/test-protocol.json diff --git a/packages/old/examples/simple-agent/src/index.js b/packages/old/pre-polyrepo/examples/simple-agent/src/index.js similarity index 100% rename from packages/old/examples/simple-agent/src/index.js rename to packages/old/pre-polyrepo/examples/simple-agent/src/index.js diff --git a/packages/old/examples/simple-agent/src/utils.js b/packages/old/pre-polyrepo/examples/simple-agent/src/utils.js similarity index 100% rename from packages/old/examples/simple-agent/src/utils.js rename to packages/old/pre-polyrepo/examples/simple-agent/src/utils.js diff --git a/packages/old/examples/test-dashboard/desktop-agent-original.html b/packages/old/pre-polyrepo/examples/test-dashboard/desktop-agent-original.html similarity index 100% rename from packages/old/examples/test-dashboard/desktop-agent-original.html rename to packages/old/pre-polyrepo/examples/test-dashboard/desktop-agent-original.html diff --git a/packages/old/examples/test-dashboard/desktop-agent.html b/packages/old/pre-polyrepo/examples/test-dashboard/desktop-agent.html similarity index 100% rename from packages/old/examples/test-dashboard/desktop-agent.html rename to packages/old/pre-polyrepo/examples/test-dashboard/desktop-agent.html diff --git a/packages/old/examples/test-dashboard/index.js b/packages/old/pre-polyrepo/examples/test-dashboard/index.js similarity index 100% rename from packages/old/examples/test-dashboard/index.js rename to packages/old/pre-polyrepo/examples/test-dashboard/index.js diff --git a/packages/old/examples/test-dashboard/package-lock.json b/packages/old/pre-polyrepo/examples/test-dashboard/package-lock.json similarity index 100% rename from packages/old/examples/test-dashboard/package-lock.json rename to packages/old/pre-polyrepo/examples/test-dashboard/package-lock.json diff --git a/packages/old/examples/test-dashboard/package.json b/packages/old/pre-polyrepo/examples/test-dashboard/package.json similarity index 100% rename from packages/old/examples/test-dashboard/package.json rename to packages/old/pre-polyrepo/examples/test-dashboard/package.json diff --git a/packages/old/examples/test-dashboard/simple-agent.html b/packages/old/pre-polyrepo/examples/test-dashboard/simple-agent.html similarity index 100% rename from packages/old/examples/test-dashboard/simple-agent.html rename to packages/old/pre-polyrepo/examples/test-dashboard/simple-agent.html diff --git a/packages/old/jsdoc.json b/packages/old/pre-polyrepo/jsdoc.json similarity index 100% rename from packages/old/jsdoc.json rename to packages/old/pre-polyrepo/jsdoc.json diff --git a/packages/old/karma.conf.cjs b/packages/old/pre-polyrepo/karma.conf.cjs similarity index 100% rename from packages/old/karma.conf.cjs rename to packages/old/pre-polyrepo/karma.conf.cjs diff --git a/packages/old/package-lock.json b/packages/old/pre-polyrepo/package-lock.json similarity index 100% rename from packages/old/package-lock.json rename to packages/old/pre-polyrepo/package-lock.json diff --git a/packages/old/package.json b/packages/old/pre-polyrepo/package.json similarity index 100% rename from packages/old/package.json rename to packages/old/pre-polyrepo/package.json diff --git a/packages/old/src/did/connect/connect.js b/packages/old/pre-polyrepo/src/did/connect/connect.js similarity index 100% rename from packages/old/src/did/connect/connect.js rename to packages/old/pre-polyrepo/src/did/connect/connect.js diff --git a/packages/old/src/did/connect/utils.js b/packages/old/pre-polyrepo/src/did/connect/utils.js similarity index 100% rename from packages/old/src/did/connect/utils.js rename to packages/old/pre-polyrepo/src/did/connect/utils.js diff --git a/packages/old/src/did/connect/ws-client.js b/packages/old/pre-polyrepo/src/did/connect/ws-client.js similarity index 100% rename from packages/old/src/did/connect/ws-client.js rename to packages/old/pre-polyrepo/src/did/connect/ws-client.js diff --git a/packages/old/src/did/crypto/ciphers.js b/packages/old/pre-polyrepo/src/did/crypto/ciphers.js similarity index 100% rename from packages/old/src/did/crypto/ciphers.js rename to packages/old/pre-polyrepo/src/did/crypto/ciphers.js diff --git a/packages/old/src/did/crypto/x25519-xsalsa20-poly1305.js b/packages/old/pre-polyrepo/src/did/crypto/x25519-xsalsa20-poly1305.js similarity index 100% rename from packages/old/src/did/crypto/x25519-xsalsa20-poly1305.js rename to packages/old/pre-polyrepo/src/did/crypto/x25519-xsalsa20-poly1305.js diff --git a/packages/old/src/did/manager.js b/packages/old/pre-polyrepo/src/did/manager.js similarity index 100% rename from packages/old/src/did/manager.js rename to packages/old/pre-polyrepo/src/did/manager.js diff --git a/packages/old/src/did/methods/ion.js b/packages/old/pre-polyrepo/src/did/methods/ion.js similarity index 100% rename from packages/old/src/did/methods/ion.js rename to packages/old/pre-polyrepo/src/did/methods/ion.js diff --git a/packages/old/src/did/methods/key.js b/packages/old/pre-polyrepo/src/did/methods/key.js similarity index 100% rename from packages/old/src/did/methods/key.js rename to packages/old/pre-polyrepo/src/did/methods/key.js diff --git a/packages/old/src/did/methods/methods.js b/packages/old/pre-polyrepo/src/did/methods/methods.js similarity index 100% rename from packages/old/src/did/methods/methods.js rename to packages/old/pre-polyrepo/src/did/methods/methods.js diff --git a/packages/old/src/did/utils.js b/packages/old/pre-polyrepo/src/did/utils.js similarity index 100% rename from packages/old/src/did/utils.js rename to packages/old/pre-polyrepo/src/did/utils.js diff --git a/packages/old/src/did/web5-did.js b/packages/old/pre-polyrepo/src/did/web5-did.js similarity index 100% rename from packages/old/src/did/web5-did.js rename to packages/old/pre-polyrepo/src/did/web5-did.js diff --git a/packages/old/src/dwn/dwn-utils.js b/packages/old/pre-polyrepo/src/dwn/dwn-utils.js similarity index 100% rename from packages/old/src/dwn/dwn-utils.js rename to packages/old/pre-polyrepo/src/dwn/dwn-utils.js diff --git a/packages/old/src/dwn/interfaces/interface.js b/packages/old/pre-polyrepo/src/dwn/interfaces/interface.js similarity index 100% rename from packages/old/src/dwn/interfaces/interface.js rename to packages/old/pre-polyrepo/src/dwn/interfaces/interface.js diff --git a/packages/old/src/dwn/interfaces/permissions.js b/packages/old/pre-polyrepo/src/dwn/interfaces/permissions.js similarity index 100% rename from packages/old/src/dwn/interfaces/permissions.js rename to packages/old/pre-polyrepo/src/dwn/interfaces/permissions.js diff --git a/packages/old/src/dwn/interfaces/protocols.js b/packages/old/pre-polyrepo/src/dwn/interfaces/protocols.js similarity index 100% rename from packages/old/src/dwn/interfaces/protocols.js rename to packages/old/pre-polyrepo/src/dwn/interfaces/protocols.js diff --git a/packages/old/src/dwn/interfaces/records.js b/packages/old/pre-polyrepo/src/dwn/interfaces/records.js similarity index 100% rename from packages/old/src/dwn/interfaces/records.js rename to packages/old/pre-polyrepo/src/dwn/interfaces/records.js diff --git a/packages/old/src/dwn/models/record.js b/packages/old/pre-polyrepo/src/dwn/models/record.js similarity index 100% rename from packages/old/src/dwn/models/record.js rename to packages/old/pre-polyrepo/src/dwn/models/record.js diff --git a/packages/old/src/dwn/web5-dwn.js b/packages/old/pre-polyrepo/src/dwn/web5-dwn.js similarity index 100% rename from packages/old/src/dwn/web5-dwn.js rename to packages/old/pre-polyrepo/src/dwn/web5-dwn.js diff --git a/packages/old/src/main.js b/packages/old/pre-polyrepo/src/main.js similarity index 100% rename from packages/old/src/main.js rename to packages/old/pre-polyrepo/src/main.js diff --git a/packages/old/src/storage/local-storage.js b/packages/old/pre-polyrepo/src/storage/local-storage.js similarity index 100% rename from packages/old/src/storage/local-storage.js rename to packages/old/pre-polyrepo/src/storage/local-storage.js diff --git a/packages/old/src/storage/memory-storage.js b/packages/old/pre-polyrepo/src/storage/memory-storage.js similarity index 100% rename from packages/old/src/storage/memory-storage.js rename to packages/old/pre-polyrepo/src/storage/memory-storage.js diff --git a/packages/old/src/storage/storage.js b/packages/old/pre-polyrepo/src/storage/storage.js similarity index 100% rename from packages/old/src/storage/storage.js rename to packages/old/pre-polyrepo/src/storage/storage.js diff --git a/packages/old/src/transport/app-transport.js b/packages/old/pre-polyrepo/src/transport/app-transport.js similarity index 100% rename from packages/old/src/transport/app-transport.js rename to packages/old/pre-polyrepo/src/transport/app-transport.js diff --git a/packages/old/src/transport/http-transport.js b/packages/old/pre-polyrepo/src/transport/http-transport.js similarity index 100% rename from packages/old/src/transport/http-transport.js rename to packages/old/pre-polyrepo/src/transport/http-transport.js diff --git a/packages/old/src/transport/transport.js b/packages/old/pre-polyrepo/src/transport/transport.js similarity index 100% rename from packages/old/src/transport/transport.js rename to packages/old/pre-polyrepo/src/transport/transport.js diff --git a/packages/old/src/types.js b/packages/old/pre-polyrepo/src/types.js similarity index 100% rename from packages/old/src/types.js rename to packages/old/pre-polyrepo/src/types.js diff --git a/packages/old/src/utils.js b/packages/old/pre-polyrepo/src/utils.js similarity index 100% rename from packages/old/src/utils.js rename to packages/old/pre-polyrepo/src/utils.js diff --git a/packages/old/src/web5.js b/packages/old/pre-polyrepo/src/web5.js similarity index 100% rename from packages/old/src/web5.js rename to packages/old/pre-polyrepo/src/web5.js diff --git a/packages/old/tests/did/manager.spec.js b/packages/old/pre-polyrepo/tests/did/manager.spec.js similarity index 100% rename from packages/old/tests/did/manager.spec.js rename to packages/old/pre-polyrepo/tests/did/manager.spec.js diff --git a/packages/old/tests/did/methods/ion.spec.js b/packages/old/pre-polyrepo/tests/did/methods/ion.spec.js similarity index 100% rename from packages/old/tests/did/methods/ion.spec.js rename to packages/old/pre-polyrepo/tests/did/methods/ion.spec.js diff --git a/packages/old/tests/did/methods/key.spec.js b/packages/old/pre-polyrepo/tests/did/methods/key.spec.js similarity index 100% rename from packages/old/tests/did/methods/key.spec.js rename to packages/old/pre-polyrepo/tests/did/methods/key.spec.js diff --git a/packages/old/tests/did/utils.spec.js b/packages/old/pre-polyrepo/tests/did/utils.spec.js similarity index 100% rename from packages/old/tests/did/utils.spec.js rename to packages/old/pre-polyrepo/tests/did/utils.spec.js diff --git a/packages/old/tests/did/web5-did.spec.js b/packages/old/pre-polyrepo/tests/did/web5-did.spec.js similarity index 100% rename from packages/old/tests/did/web5-did.spec.js rename to packages/old/pre-polyrepo/tests/did/web5-did.spec.js diff --git a/packages/old/tests/dwn/interfaces/records.spec.js b/packages/old/pre-polyrepo/tests/dwn/interfaces/records.spec.js similarity index 100% rename from packages/old/tests/dwn/interfaces/records.spec.js rename to packages/old/pre-polyrepo/tests/dwn/interfaces/records.spec.js diff --git a/packages/old/tests/dwn/models/record.spec.js b/packages/old/pre-polyrepo/tests/dwn/models/record.spec.js similarity index 100% rename from packages/old/tests/dwn/models/record.spec.js rename to packages/old/pre-polyrepo/tests/dwn/models/record.spec.js diff --git a/packages/old/tests/fixtures/did-documents.js b/packages/old/pre-polyrepo/tests/fixtures/did-documents.js similarity index 100% rename from packages/old/tests/fixtures/did-documents.js rename to packages/old/pre-polyrepo/tests/fixtures/did-documents.js diff --git a/packages/old/tests/storage/memory-storage.spec.js b/packages/old/pre-polyrepo/tests/storage/memory-storage.spec.js similarity index 100% rename from packages/old/tests/storage/memory-storage.spec.js rename to packages/old/pre-polyrepo/tests/storage/memory-storage.spec.js diff --git a/packages/old/tests/test-utils/promises.js b/packages/old/pre-polyrepo/tests/test-utils/promises.js similarity index 100% rename from packages/old/tests/test-utils/promises.js rename to packages/old/pre-polyrepo/tests/test-utils/promises.js diff --git a/packages/old/tests/test-utils/test-data-generator.js b/packages/old/pre-polyrepo/tests/test-utils/test-data-generator.js similarity index 100% rename from packages/old/tests/test-utils/test-data-generator.js rename to packages/old/pre-polyrepo/tests/test-utils/test-data-generator.js diff --git a/packages/old/tests/test-utils/test-dwn.js b/packages/old/pre-polyrepo/tests/test-utils/test-dwn.js similarity index 100% rename from packages/old/tests/test-utils/test-dwn.js rename to packages/old/pre-polyrepo/tests/test-utils/test-dwn.js diff --git a/packages/old/tests/utils.spec.js b/packages/old/pre-polyrepo/tests/utils.spec.js similarity index 100% rename from packages/old/tests/utils.spec.js rename to packages/old/pre-polyrepo/tests/utils.spec.js diff --git a/packages/web5-user-agent/.c8rc.json b/packages/old/web5-agent/.c8rc.json similarity index 50% rename from packages/web5-user-agent/.c8rc.json rename to packages/old/web5-agent/.c8rc.json index 9763ced85..0d483c700 100644 --- a/packages/web5-user-agent/.c8rc.json +++ b/packages/old/web5-agent/.c8rc.json @@ -5,13 +5,11 @@ ".js" ], "include": [ - "tests/compiled/**" + "__tests__/src/**" ], "exclude": [ - "tests/compiled/tests/**", - "tests/compiled/src/main.js", - "tests/compiled/src/types/**", - "tests/compiled/types/**" + "__tests__/src/main.js", + "__tests__/types/**" ], "reporter": [ "cobertura", diff --git a/packages/old/web5-agent/.mocharc.json b/packages/old/web5-agent/.mocharc.json new file mode 100644 index 000000000..c223f40c7 --- /dev/null +++ b/packages/old/web5-agent/.mocharc.json @@ -0,0 +1,5 @@ +{ + "enable-source-maps": true, + "exit": true, + "spec": ["__tests__/**/*.spec.js"] + } \ No newline at end of file diff --git a/packages/old/web5-agent/.vscode/launch.json b/packages/old/web5-agent/.vscode/launch.json new file mode 100644 index 000000000..427bc2237 --- /dev/null +++ b/packages/old/web5-agent/.vscode/launch.json @@ -0,0 +1,22 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "type": "node", + "request": "launch", + "name": "test:node", + "runtimeExecutable": "${workspaceFolder:root}/node_modules/.bin/mocha", + "runtimeArgs": [ + "${workspaceFolder:web5-agent}/__tests__/**/*.spec.js" + ], + "console": "internalConsole", + "preLaunchTask": "tsc: build - tsconfig.test.json", + "outFiles": [ + "${workspaceFolder:web5-agent}/__tests__/**/*.js" + ], + } + ] +} \ No newline at end of file diff --git a/packages/old/web5-agent/.vscode/tasks.json b/packages/old/web5-agent/.vscode/tasks.json new file mode 100644 index 000000000..a6d959d04 --- /dev/null +++ b/packages/old/web5-agent/.vscode/tasks.json @@ -0,0 +1,45 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "label": "npm run build", + "type": "npm", + "script": "build", + "problemMatcher": [], + "group": { + "kind": "build", + "isDefault": true + } + }, + { + "label": "tsc: build - tsconfig.json", + "type": "typescript", + "tsconfig": "tsconfig.json", + "problemMatcher": [ + "$tsc" + ], + "group": { + "kind": "build", + "isDefault": false + }, + "options": { + "cwd": "${workspaceFolder:root}" + } + }, + { + "label": "tsc: build - tsconfig.test.json", + "type": "typescript", + "tsconfig": "tsconfig.test.json", + "problemMatcher": [ + "$tsc" + ], + "group": { + "kind": "build", + "isDefault": false + }, + "options": { + "cwd": "${workspaceFolder:root}" + } + }, + ] +} \ No newline at end of file diff --git a/packages/web5-proxy-agent/LICENSE b/packages/old/web5-agent/LICENSE similarity index 100% rename from packages/web5-proxy-agent/LICENSE rename to packages/old/web5-agent/LICENSE diff --git a/packages/web5-agent/README.md b/packages/old/web5-agent/README.md similarity index 100% rename from packages/web5-agent/README.md rename to packages/old/web5-agent/README.md diff --git a/packages/old/web5-agent/build/bundles.js b/packages/old/web5-agent/build/bundles.js new file mode 100644 index 000000000..b1041cc61 --- /dev/null +++ b/packages/old/web5-agent/build/bundles.js @@ -0,0 +1,55 @@ +import esbuild from 'esbuild'; +import browserConfig from './esbuild-browser-config.cjs'; + +// cjs bundle for Electron apps. external dependencies bundled except LevelDB +// Remove if/when the following PR is merged and this bundle is no longer needed by Electron apps +// https://github.com/electron/electron/pull/37535 +esbuild.buildSync({ + platform : 'node', + bundle : true, + format : 'cjs', + // packages: 'external', + external : ['level'], + sourcemap : true, + entryPoints : ['./src/main.ts'], + outfile : './dist/electron/main.cjs', + allowOverwrite : true, +}); + +// cjs bundle. external dependencies **not** bundled +esbuild.buildSync({ + platform : 'node', + bundle : true, + format : 'cjs', + packages : 'external', + sourcemap : true, + entryPoints : ['./src/main.ts'], + outfile : './dist/cjs/main.cjs', + allowOverwrite : true, +}); + +// esm bundle. external dependencies **not** bundled +esbuild.buildSync({ + platform : 'node', + bundle : true, + format : 'esm', + packages : 'external', + sourcemap : true, + entryPoints : ['./src/main.ts'], + outfile : './dist/esm/main.mjs', + allowOverwrite : true, +}); + +// esm polyfilled bundle for browser +esbuild.build({ + ...browserConfig, + outfile: 'dist/browser.mjs', +}); + +// iife polyfilled bundle for browser +esbuild.build({ + ...browserConfig, + format : 'iife', + globalName : 'Web5', + outfile : 'dist/browser.js', +}); \ No newline at end of file diff --git a/packages/old/web5-agent/build/esbuild-browser-config.cjs b/packages/old/web5-agent/build/esbuild-browser-config.cjs new file mode 100644 index 000000000..4ef8cf1d8 --- /dev/null +++ b/packages/old/web5-agent/build/esbuild-browser-config.cjs @@ -0,0 +1,19 @@ +/* eslint-disable @typescript-eslint/no-var-requires */ +const polyfillProviderPlugin = require('node-stdlib-browser/helpers/esbuild/plugin'); +const stdLibBrowser = require('node-stdlib-browser'); + +/** @type {import('esbuild').BuildOptions} */ +module.exports = { + entryPoints : ['./src/main.ts'], + bundle : true, + format : 'esm', + sourcemap : true, + minify : true, + platform : 'browser', + target : ['chrome101', 'firefox108', 'safari16'], + inject : [require.resolve('node-stdlib-browser/helpers/esbuild/shim')], + plugins : [polyfillProviderPlugin(stdLibBrowser)], + define : { + 'global': 'globalThis', + }, +}; diff --git a/packages/web5-agent/karma.conf.cjs b/packages/old/web5-agent/karma.conf.cjs similarity index 100% rename from packages/web5-agent/karma.conf.cjs rename to packages/old/web5-agent/karma.conf.cjs diff --git a/packages/old/web5-agent/package.json b/packages/old/web5-agent/package.json new file mode 100644 index 000000000..2c4fda446 --- /dev/null +++ b/packages/old/web5-agent/package.json @@ -0,0 +1,117 @@ +{ + "name": "@tbd54566975/web5-agent", + "version": "0.1.7", + "description": "Web5 Agent", + "type": "module", + "main": "./dist/cjs/main.cjs", + "module": "./dist/esm/main.mjs", + "types": "./dist/types/main.d.ts", + "scripts": { + "build": "rimraf dist && node build/bundles.js && echo '{\"type\": \"commonjs\"}' > ./dist/cjs/package.json && tsc", + "lint": "eslint . --ext .ts --max-warnings 0", + "lint:fix": "eslint . --ext .ts --fix", + "test:node": "rimraf __tests__ && tsc -p tsconfig.test.json && c8 mocha", + "test:browser": "karma start karma.conf.cjs" + }, + "homepage": "https://github.com/TBD54566975/web5-js/tree/main/packages/web5-agent#readme", + "bugs": "https://github.com/TBD54566975/web5-js/issues", + "repository": { + "type": "git", + "url": "git+https://github.com/TBD54566975/web5-js", + "directory": "packages/web5-agent" + }, + "license": "Apache-2.0", + "contributors": [ + { + "name": "Daniel Buchner", + "url": "https://github.com/csuwildcat" + }, + { + "name": "Frank Hinek", + "url": "https://github.com/frankhinek" + }, + { + "name": "Moe Jangda", + "url": "https://github.com/mistermoe" + } + ], + "files": [ + "dist", + "src" + ], + "exports": { + ".": { + "import": "./dist/esm/main.mjs", + "require": "./dist/cjs/main.cjs", + "types": "./dist/types/main.d.ts" + }, + "./browser": { + "import": "./dist/browser.mjs", + "require": "./dist/browser.js", + "types": "./dist/types/main.d.ts" + }, + "./electron": { + "import": "./dist/esm/main.mjs", + "require": "./dist/electron/main.cjs", + "types": "./dist/types/main.d.ts" + } + }, + "browser": { + "./dist/esm/main.mjs": "./dist/browser.mjs", + "./dist/cjs/main.cjs": "./dist/browser.js", + "types": "./dist/types/main.d.ts" + }, + "react-native": { + "./dist/esm/main.mjs": "./dist/esm/main.mjs", + "./dist/cjs/main.cjs": "./dist/esm/main.mjs", + "types": "./dist/types/main.d.ts" + }, + "keywords": [ + "decentralized", + "decentralized-applications", + "decentralized-identity", + "decentralized-web", + "web5" + ], + "publishConfig": { + "access": "public" + }, + "engines": { + "node": ">=18.0.0" + }, + "dependencies": { + "readable-stream": "4.4.0", + "@tbd54566975/dwn-sdk-js": "0.0.37" + }, + "devDependencies": { + "@playwright/test": "1.34.3", + "@types/chai": "4.3.0", + "@types/chai-as-promised": "7.1.5", + "@types/eslint": "8.37.0", + "@types/mocha": "10.0.1", + "@types/readable-stream": "2.3.15", + "@types/sinon": "10.0.15", + "@typescript-eslint/eslint-plugin": "5.59.0", + "@typescript-eslint/parser": "5.59.0", + "c8": "8.0.0", + "chai": "4.3.7", + "chai-as-promised": "7.1.1", + "esbuild": "0.16.17", + "eslint": "8.39.0", + "eslint-plugin-mocha": "10.1.0", + "karma": "6.4.1", + "karma-chai": "0.1.0", + "karma-chrome-launcher": "3.1.1", + "karma-esbuild": "2.2.5", + "karma-firefox-launcher": "2.1.2", + "karma-mocha": "2.0.1", + "karma-mocha-reporter": "2.2.5", + "karma-webkit-launcher": "2.1.0", + "mocha": "10.2.0", + "node-stdlib-browser": "1.2.0", + "rimraf": "4.4.0", + "sinon": "15.0.2", + "source-map-loader": "4.0.1", + "typescript": "5.0.4" + } +} \ No newline at end of file diff --git a/packages/old/web5-agent/src/json-rpc.ts b/packages/old/web5-agent/src/json-rpc.ts new file mode 100644 index 000000000..14623fa57 --- /dev/null +++ b/packages/old/web5-agent/src/json-rpc.ts @@ -0,0 +1,107 @@ +export type JsonRpcId = string | number | null; +export type JsonRpcParams = any; +export type JsonRpcVersion = '2.0'; + +export interface JsonRpcRequest { + jsonrpc: JsonRpcVersion; + id?: JsonRpcId; + method: string; + params?: any; +} + +export interface JsonRpcError { + code: JsonRpcErrorCodes; + message: string; + data?: any; +} + +export enum JsonRpcErrorCodes { + // JSON-RPC 2.0 pre-defined errors + InvalidRequest = -32600, + MethodNotFound = -32601, + InvalidParams = -32602, + InternalError = -32603, + ParseError = -32700, + TransportError = -32300, + + // App defined errors + BadRequest = -50400, // equivalent to HTTP Status 400 + Unauthorized = -50401, // equivalent to HTTP Status 401 + Forbidden = -50403, // equivalent to HTTP Status 403 +} + +export type JsonRpcResponse = JsonRpcSuccessResponse | JsonRpcErrorResponse; + +export interface JsonRpcSuccessResponse { + jsonrpc: JsonRpcVersion; + id: JsonRpcId; + result: any; + error?: never; +} + +export interface JsonRpcErrorResponse { + jsonrpc: JsonRpcVersion; + id: JsonRpcId; + result?: never; + error: JsonRpcError; +} + +export const createJsonRpcErrorResponse = ( + id: JsonRpcId, + code: JsonRpcErrorCodes, + message: string, + data?: any, +): JsonRpcErrorResponse => { + const error: JsonRpcError = { code, message }; + if (data != undefined) { + error.data = data; + } + return { + jsonrpc: '2.0', + id, + error, + }; +}; + +export const createJsonRpcNotification = ( + method: string, + params?: JsonRpcParams, +): JsonRpcRequest => { + return { + jsonrpc: '2.0', + method, + params, + }; +}; + +export const createJsonRpcRequest = ( + id: JsonRpcId, + method: string, + params?: JsonRpcParams, +): JsonRpcRequest => { + return { + jsonrpc: '2.0', + id, + method, + params, + }; +}; + +export const createJsonRpcSuccessResponse = ( + id: JsonRpcId, + result?: any, +): JsonRpcSuccessResponse => { + return { + jsonrpc : '2.0', + id, + result : result ?? null, + }; +}; + +export function parseJson(text: string): object | null { + try { + return JSON.parse(text); + } catch { + return null; + } +} \ No newline at end of file diff --git a/packages/web5-agent/src/main.ts b/packages/old/web5-agent/src/main.ts similarity index 100% rename from packages/web5-agent/src/main.ts rename to packages/old/web5-agent/src/main.ts diff --git a/packages/web5-agent/src/query-store.ts b/packages/old/web5-agent/src/query-store.ts similarity index 100% rename from packages/web5-agent/src/query-store.ts rename to packages/old/web5-agent/src/query-store.ts diff --git a/packages/web5-agent/src/web5-agent.ts b/packages/old/web5-agent/src/web5-agent.ts similarity index 99% rename from packages/web5-agent/src/web5-agent.ts rename to packages/old/web5-agent/src/web5-agent.ts index 6297e376b..b1d1f8f6b 100644 --- a/packages/web5-agent/src/web5-agent.ts +++ b/packages/old/web5-agent/src/web5-agent.ts @@ -1,6 +1,6 @@ import type { Readable } from 'readable-stream'; -import type { +import { EventsGetMessage, UnionMessageReply, MessagesGetMessage, diff --git a/packages/web5-agent/tests/needed.spec.ts b/packages/old/web5-agent/tests/needed.spec.ts similarity index 100% rename from packages/web5-agent/tests/needed.spec.ts rename to packages/old/web5-agent/tests/needed.spec.ts diff --git a/packages/old/web5-agent/tsconfig.json b/packages/old/web5-agent/tsconfig.json new file mode 100644 index 000000000..b63536fdd --- /dev/null +++ b/packages/old/web5-agent/tsconfig.json @@ -0,0 +1,28 @@ +{ + "compilerOptions": { + "strict": true, + "lib": [ + "DOM", + "ES6" + ], + "allowJs": true, + "target": "es6", + "module": "ESNext", // Required for enabling JavaScript import assertion support + "declaration": true, + "declarationMap": true, + "emitDeclarationOnly": true, + "declarationDir": "dist/types", + "sourceMap": true, + // `NodeNext` will throw compilation errors if relative import paths are missing file extension + // reference: https://devblogs.microsoft.com/typescript/announcing-typescript-4-7/#ecmascript-module-support-in-node-js + "moduleResolution": "NodeNext", + "esModuleInterop": true + }, + "include": [ + "src", + ], + "exclude": [ + "node_modules", + "dist" + ] +} \ No newline at end of file diff --git a/packages/old/web5-agent/tsconfig.test.json b/packages/old/web5-agent/tsconfig.test.json new file mode 100644 index 000000000..7e40c3908 --- /dev/null +++ b/packages/old/web5-agent/tsconfig.test.json @@ -0,0 +1,29 @@ +{ + "compilerOptions": { + "lib": [ + "DOM", + "ES6" + ], + "target": "es6", + "module": "ESNext", // Required for enabling JavaScript import assertion support + "declaration": true, + "declarationMap": true, + "outDir": "__tests__", + "declarationDir": "__tests__/types", + "sourceMap": true, + // `NodeNext` will throw compilation errors if relative import paths are missing file extension + // reference: https://devblogs.microsoft.com/typescript/announcing-typescript-4-7/#ecmascript-module-support-in-node-js + "moduleResolution": "NodeNext", + // allows us to import json files + "resolveJsonModule": true, + "esModuleInterop": true + }, + "include": [ + "src", + "tests", + ], + "exclude": [ + "node_modules", + "dist" + ] +} \ No newline at end of file diff --git a/packages/old/web5-proxy-agent/.c8rc.json b/packages/old/web5-proxy-agent/.c8rc.json new file mode 100644 index 000000000..0d483c700 --- /dev/null +++ b/packages/old/web5-proxy-agent/.c8rc.json @@ -0,0 +1,18 @@ +{ + "all": true, + "cache": false, + "extension": [ + ".js" + ], + "include": [ + "__tests__/src/**" + ], + "exclude": [ + "__tests__/src/main.js", + "__tests__/types/**" + ], + "reporter": [ + "cobertura", + "text" + ] +} \ No newline at end of file diff --git a/packages/old/web5-proxy-agent/.mocharc.json b/packages/old/web5-proxy-agent/.mocharc.json new file mode 100644 index 000000000..c223f40c7 --- /dev/null +++ b/packages/old/web5-proxy-agent/.mocharc.json @@ -0,0 +1,5 @@ +{ + "enable-source-maps": true, + "exit": true, + "spec": ["__tests__/**/*.spec.js"] + } \ No newline at end of file diff --git a/packages/old/web5-proxy-agent/.vscode/launch.json b/packages/old/web5-proxy-agent/.vscode/launch.json new file mode 100644 index 000000000..4f8f247d2 --- /dev/null +++ b/packages/old/web5-proxy-agent/.vscode/launch.json @@ -0,0 +1,22 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "type": "node", + "request": "launch", + "name": "test:node", + "runtimeExecutable": "${workspaceFolder:root}/node_modules/.bin/mocha", + "runtimeArgs": [ + "${workspaceFolder:web5-proxy-agent}/__tests__/**/*.spec.js" + ], + "console": "internalConsole", + "preLaunchTask": "tsc: build - tsconfig.test.json", + "outFiles": [ + "${workspaceFolder:web5-proxy-agent}/__tests__/**/*.js" + ], + } + ] +} \ No newline at end of file diff --git a/packages/old/web5-proxy-agent/.vscode/tasks.json b/packages/old/web5-proxy-agent/.vscode/tasks.json new file mode 100644 index 000000000..a6d959d04 --- /dev/null +++ b/packages/old/web5-proxy-agent/.vscode/tasks.json @@ -0,0 +1,45 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "label": "npm run build", + "type": "npm", + "script": "build", + "problemMatcher": [], + "group": { + "kind": "build", + "isDefault": true + } + }, + { + "label": "tsc: build - tsconfig.json", + "type": "typescript", + "tsconfig": "tsconfig.json", + "problemMatcher": [ + "$tsc" + ], + "group": { + "kind": "build", + "isDefault": false + }, + "options": { + "cwd": "${workspaceFolder:root}" + } + }, + { + "label": "tsc: build - tsconfig.test.json", + "type": "typescript", + "tsconfig": "tsconfig.test.json", + "problemMatcher": [ + "$tsc" + ], + "group": { + "kind": "build", + "isDefault": false + }, + "options": { + "cwd": "${workspaceFolder:root}" + } + }, + ] +} \ No newline at end of file diff --git a/packages/web5-user-agent/LICENSE b/packages/old/web5-proxy-agent/LICENSE similarity index 100% rename from packages/web5-user-agent/LICENSE rename to packages/old/web5-proxy-agent/LICENSE diff --git a/packages/web5-proxy-agent/README.md b/packages/old/web5-proxy-agent/README.md similarity index 100% rename from packages/web5-proxy-agent/README.md rename to packages/old/web5-proxy-agent/README.md diff --git a/packages/old/web5-proxy-agent/build/bundles.js b/packages/old/web5-proxy-agent/build/bundles.js new file mode 100644 index 000000000..b1041cc61 --- /dev/null +++ b/packages/old/web5-proxy-agent/build/bundles.js @@ -0,0 +1,55 @@ +import esbuild from 'esbuild'; +import browserConfig from './esbuild-browser-config.cjs'; + +// cjs bundle for Electron apps. external dependencies bundled except LevelDB +// Remove if/when the following PR is merged and this bundle is no longer needed by Electron apps +// https://github.com/electron/electron/pull/37535 +esbuild.buildSync({ + platform : 'node', + bundle : true, + format : 'cjs', + // packages: 'external', + external : ['level'], + sourcemap : true, + entryPoints : ['./src/main.ts'], + outfile : './dist/electron/main.cjs', + allowOverwrite : true, +}); + +// cjs bundle. external dependencies **not** bundled +esbuild.buildSync({ + platform : 'node', + bundle : true, + format : 'cjs', + packages : 'external', + sourcemap : true, + entryPoints : ['./src/main.ts'], + outfile : './dist/cjs/main.cjs', + allowOverwrite : true, +}); + +// esm bundle. external dependencies **not** bundled +esbuild.buildSync({ + platform : 'node', + bundle : true, + format : 'esm', + packages : 'external', + sourcemap : true, + entryPoints : ['./src/main.ts'], + outfile : './dist/esm/main.mjs', + allowOverwrite : true, +}); + +// esm polyfilled bundle for browser +esbuild.build({ + ...browserConfig, + outfile: 'dist/browser.mjs', +}); + +// iife polyfilled bundle for browser +esbuild.build({ + ...browserConfig, + format : 'iife', + globalName : 'Web5', + outfile : 'dist/browser.js', +}); \ No newline at end of file diff --git a/packages/web5-proxy-agent/build/esbuild-browser-config.cjs b/packages/old/web5-proxy-agent/build/esbuild-browser-config.cjs similarity index 54% rename from packages/web5-proxy-agent/build/esbuild-browser-config.cjs rename to packages/old/web5-proxy-agent/build/esbuild-browser-config.cjs index ae37c97f0..52f2f347c 100644 --- a/packages/web5-proxy-agent/build/esbuild-browser-config.cjs +++ b/packages/old/web5-proxy-agent/build/esbuild-browser-config.cjs @@ -1,3 +1,6 @@ +const polyfillProviderPlugin = require('node-stdlib-browser/helpers/esbuild/plugin'); +const stdLibBrowser = require('node-stdlib-browser'); + /** @type {import('esbuild').BuildOptions} */ module.exports = { entryPoints : ['./src/main.ts'], @@ -7,6 +10,8 @@ module.exports = { minify : true, platform : 'browser', target : ['chrome101', 'firefox108', 'safari16'], + inject : [require.resolve('node-stdlib-browser/helpers/esbuild/shim')], + plugins : [polyfillProviderPlugin(stdLibBrowser)], define : { 'global': 'globalThis', }, diff --git a/packages/web5-proxy-agent/karma.conf.cjs b/packages/old/web5-proxy-agent/karma.conf.cjs similarity index 100% rename from packages/web5-proxy-agent/karma.conf.cjs rename to packages/old/web5-proxy-agent/karma.conf.cjs diff --git a/packages/old/web5-proxy-agent/package.json b/packages/old/web5-proxy-agent/package.json new file mode 100644 index 000000000..768c75034 --- /dev/null +++ b/packages/old/web5-proxy-agent/package.json @@ -0,0 +1,116 @@ +{ + "name": "@tbd54566975/web5-proxy-agent", + "version": "0.1.7", + "description": "Web5 Proxy Agent", + "type": "module", + "main": "./dist/cjs/main.cjs", + "module": "./dist/esm/main.mjs", + "types": "./dist/types/main.d.ts", + "scripts": { + "build": "rimraf dist && node build/bundles.js && echo '{\"type\": \"commonjs\"}' > ./dist/cjs/package.json && tsc", + "lint": "eslint . --ext .ts --max-warnings 0", + "lint:fix": "eslint . --ext .ts --fix", + "test:node": "rimraf __tests__ && tsc -p tsconfig.test.json && c8 mocha", + "test:browser": "karma start karma.conf.cjs" + }, + "homepage": "https://github.com/TBD54566975/web5-js/tree/main/packages/web5-proxy-agent#readme", + "bugs": "https://github.com/TBD54566975/web5-js/issues", + "repository": { + "type": "git", + "url": "git+https://github.com/TBD54566975/web5-js", + "directory": "packages/web5-proxy-agent" + }, + "license": "Apache-2.0", + "contributors": [ + { + "name": "Daniel Buchner", + "url": "https://github.com/csuwildcat" + }, + { + "name": "Frank Hinek", + "url": "https://github.com/frankhinek" + }, + { + "name": "Moe Jangda", + "url": "https://github.com/mistermoe" + } + ], + "files": [ + "dist", + "src" + ], + "exports": { + ".": { + "import": "./dist/esm/main.mjs", + "require": "./dist/cjs/main.cjs", + "types": "./dist/types/main.d.ts" + }, + "./browser": { + "import": "./dist/browser.mjs", + "require": "./dist/browser.js", + "types": "./dist/types/main.d.ts" + }, + "./electron": { + "import": "./dist/esm/main.mjs", + "require": "./dist/electron/main.cjs", + "types": "./dist/types/main.d.ts" + } + }, + "browser": { + "./dist/esm/main.mjs": "./dist/browser.mjs", + "./dist/cjs/main.cjs": "./dist/browser.js", + "types": "./dist/types/main.d.ts" + }, + "react-native": { + "./dist/esm/main.mjs": "./dist/esm/main.mjs", + "./dist/cjs/main.cjs": "./dist/esm/main.mjs", + "types": "./dist/types/main.d.ts" + }, + "keywords": [ + "decentralized", + "decentralized-applications", + "decentralized-identity", + "decentralized-web", + "web5" + ], + "publishConfig": { + "access": "public" + }, + "engines": { + "node": ">=18.0.0" + }, + "dependencies": { + "@tbd54566975/web5-agent": "0.1.7" + }, + "devDependencies": { + "@playwright/test": "1.34.3", + "@types/chai": "4.3.0", + "@types/chai-as-promised": "7.1.5", + "@types/ed2curve": "0.2.2", + "@types/eslint": "8.37.0", + "@types/mocha": "10.0.1", + "@types/sinon": "10.0.15", + "@typescript-eslint/eslint-plugin": "5.59.0", + "@typescript-eslint/parser": "5.59.0", + "c8": "8.0.0", + "chai": "4.3.7", + "chai-as-promised": "7.1.1", + "esbuild": "0.16.17", + "eslint": "8.39.0", + "eslint-plugin-mocha": "10.1.0", + "karma": "6.4.1", + "karma-chai": "0.1.0", + "karma-chrome-launcher": "3.1.1", + "karma-esbuild": "2.2.5", + "karma-firefox-launcher": "2.1.2", + "karma-mocha": "2.0.1", + "karma-mocha-reporter": "2.2.5", + "karma-webkit-launcher": "2.1.0", + "mocha": "10.2.0", + "node-stdlib-browser": "1.2.0", + "rimraf": "4.4.0", + "sinon": "15.0.2", + "source-map-loader": "4.0.1", + "typescript": "5.0.4" + } +} diff --git a/packages/web5-proxy-agent/src/main.ts b/packages/old/web5-proxy-agent/src/main.ts similarity index 100% rename from packages/web5-proxy-agent/src/main.ts rename to packages/old/web5-proxy-agent/src/main.ts diff --git a/packages/web5-proxy-agent/src/web5-proxy-agent.ts b/packages/old/web5-proxy-agent/src/web5-proxy-agent.ts similarity index 100% rename from packages/web5-proxy-agent/src/web5-proxy-agent.ts rename to packages/old/web5-proxy-agent/src/web5-proxy-agent.ts diff --git a/packages/web5-proxy-agent/tests/needed.spec.ts b/packages/old/web5-proxy-agent/tests/needed.spec.ts similarity index 100% rename from packages/web5-proxy-agent/tests/needed.spec.ts rename to packages/old/web5-proxy-agent/tests/needed.spec.ts diff --git a/packages/old/web5-proxy-agent/tsconfig.json b/packages/old/web5-proxy-agent/tsconfig.json new file mode 100644 index 000000000..c4cc3b877 --- /dev/null +++ b/packages/old/web5-proxy-agent/tsconfig.json @@ -0,0 +1,28 @@ +{ + "compilerOptions": { + "strict": true, + "lib": [ + "DOM", + "ES6" + ], + "allowJs": true, + "target": "es6", + "module": "ESNext", // Required for enabling JavaScript import assertion support + "declaration": true, + "declarationMap": true, + "emitDeclarationOnly": true, + "declarationDir": "dist/types", + "sourceMap": true, + // `NodeNext` will throw compilation errors if relative import paths are missing file extension + // reference: https://devblogs.microsoft.com/typescript/announcing-typescript-4-7/#ecmascript-module-support-in-node-js + "moduleResolution": "NodeNext", + "esModuleInterop": true + }, + "include": [ + "src" + ], + "exclude": [ + "node_modules", + "dist" + ] +} \ No newline at end of file diff --git a/packages/old/web5-proxy-agent/tsconfig.test.json b/packages/old/web5-proxy-agent/tsconfig.test.json new file mode 100644 index 000000000..7e40c3908 --- /dev/null +++ b/packages/old/web5-proxy-agent/tsconfig.test.json @@ -0,0 +1,29 @@ +{ + "compilerOptions": { + "lib": [ + "DOM", + "ES6" + ], + "target": "es6", + "module": "ESNext", // Required for enabling JavaScript import assertion support + "declaration": true, + "declarationMap": true, + "outDir": "__tests__", + "declarationDir": "__tests__/types", + "sourceMap": true, + // `NodeNext` will throw compilation errors if relative import paths are missing file extension + // reference: https://devblogs.microsoft.com/typescript/announcing-typescript-4-7/#ecmascript-module-support-in-node-js + "moduleResolution": "NodeNext", + // allows us to import json files + "resolveJsonModule": true, + "esModuleInterop": true + }, + "include": [ + "src", + "tests", + ], + "exclude": [ + "node_modules", + "dist" + ] +} \ No newline at end of file diff --git a/packages/old/web5-user-agent/.c8rc.json b/packages/old/web5-user-agent/.c8rc.json new file mode 100644 index 000000000..0d483c700 --- /dev/null +++ b/packages/old/web5-user-agent/.c8rc.json @@ -0,0 +1,18 @@ +{ + "all": true, + "cache": false, + "extension": [ + ".js" + ], + "include": [ + "__tests__/src/**" + ], + "exclude": [ + "__tests__/src/main.js", + "__tests__/types/**" + ], + "reporter": [ + "cobertura", + "text" + ] +} \ No newline at end of file diff --git a/packages/web5-user-agent/.gitignore b/packages/old/web5-user-agent/.gitignore similarity index 100% rename from packages/web5-user-agent/.gitignore rename to packages/old/web5-user-agent/.gitignore diff --git a/packages/old/web5-user-agent/.mocharc.json b/packages/old/web5-user-agent/.mocharc.json new file mode 100644 index 000000000..c223f40c7 --- /dev/null +++ b/packages/old/web5-user-agent/.mocharc.json @@ -0,0 +1,5 @@ +{ + "enable-source-maps": true, + "exit": true, + "spec": ["__tests__/**/*.spec.js"] + } \ No newline at end of file diff --git a/packages/old/web5-user-agent/.vscode/launch.json b/packages/old/web5-user-agent/.vscode/launch.json new file mode 100644 index 000000000..506daa8a9 --- /dev/null +++ b/packages/old/web5-user-agent/.vscode/launch.json @@ -0,0 +1,22 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "type": "node", + "request": "launch", + "name": "test:node", + "runtimeExecutable": "${workspaceFolder:root}/node_modules/.bin/mocha", + "runtimeArgs": [ + "${workspaceFolder:web5-user-agent}/__tests__/**/*.spec.js" + ], + "console": "internalConsole", + "preLaunchTask": "tsc: build - tsconfig.test.json", + "outFiles": [ + "${workspaceFolder:web5-user-agent}/__tests__/**/*.js" + ], + } + ] +} \ No newline at end of file diff --git a/packages/old/web5-user-agent/.vscode/tasks.json b/packages/old/web5-user-agent/.vscode/tasks.json new file mode 100644 index 000000000..a6d959d04 --- /dev/null +++ b/packages/old/web5-user-agent/.vscode/tasks.json @@ -0,0 +1,45 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "label": "npm run build", + "type": "npm", + "script": "build", + "problemMatcher": [], + "group": { + "kind": "build", + "isDefault": true + } + }, + { + "label": "tsc: build - tsconfig.json", + "type": "typescript", + "tsconfig": "tsconfig.json", + "problemMatcher": [ + "$tsc" + ], + "group": { + "kind": "build", + "isDefault": false + }, + "options": { + "cwd": "${workspaceFolder:root}" + } + }, + { + "label": "tsc: build - tsconfig.test.json", + "type": "typescript", + "tsconfig": "tsconfig.test.json", + "problemMatcher": [ + "$tsc" + ], + "group": { + "kind": "build", + "isDefault": false + }, + "options": { + "cwd": "${workspaceFolder:root}" + } + }, + ] +} \ No newline at end of file diff --git a/packages/web5/LICENSE b/packages/old/web5-user-agent/LICENSE similarity index 100% rename from packages/web5/LICENSE rename to packages/old/web5-user-agent/LICENSE diff --git a/packages/web5-user-agent/README.md b/packages/old/web5-user-agent/README.md similarity index 100% rename from packages/web5-user-agent/README.md rename to packages/old/web5-user-agent/README.md diff --git a/packages/old/web5-user-agent/build/bundles.js b/packages/old/web5-user-agent/build/bundles.js new file mode 100644 index 000000000..9da2522b3 --- /dev/null +++ b/packages/old/web5-user-agent/build/bundles.js @@ -0,0 +1,62 @@ +import esbuild from 'esbuild'; +import browserConfig from './esbuild-browser-config.cjs'; + +// cjs bundle for Electron apps. external dependencies bundled except LevelDB +// Remove if/when the following PR is merged and this bundle is no longer needed by Electron apps +// https://github.com/electron/electron/pull/37535 +esbuild.buildSync({ + platform : 'node', + bundle : true, + format : 'cjs', + // packages: 'external', + external : ['level'], + sourcemap : true, + entryPoints : ['./src/main.ts'], + outfile : './dist/electron/main.cjs', + allowOverwrite : true, +}); + +// cjs bundle. external dependencies **not** bundled +esbuild.buildSync({ + platform : 'node', + bundle : true, + format : 'cjs', + packages : 'external', + sourcemap : true, + entryPoints : ['./src/main.ts'], + outfile : './dist/cjs/main.cjs', + allowOverwrite : true, +}); + +// esm bundle. external dependencies **not** bundled +esbuild.buildSync({ + platform : 'node', + bundle : true, + format : 'esm', + packages : 'external', + sourcemap : true, + entryPoints : ['./src/main.ts'], + outfile : './dist/esm/main.mjs', + allowOverwrite : true, +}); + +// esm polyfilled bundle for browser +esbuild.build({ + ...browserConfig, + outfile: 'dist/browser.mjs', +}); + +// esm polyfilled test-user-agent bundle for playwright browser tests +esbuild.build({ + ...browserConfig, + entryPoints : ['tests/common/utils/test-user-agent.ts'], + outfile : '__tests__/browser/test-user-agent.mjs', +}); + +// iife polyfilled bundle for browser +esbuild.build({ + ...browserConfig, + format : 'iife', + globalName : 'Web5', + outfile : 'dist/browser.js', +}); \ No newline at end of file diff --git a/packages/old/web5-user-agent/build/esbuild-browser-config.cjs b/packages/old/web5-user-agent/build/esbuild-browser-config.cjs new file mode 100644 index 000000000..4ef8cf1d8 --- /dev/null +++ b/packages/old/web5-user-agent/build/esbuild-browser-config.cjs @@ -0,0 +1,19 @@ +/* eslint-disable @typescript-eslint/no-var-requires */ +const polyfillProviderPlugin = require('node-stdlib-browser/helpers/esbuild/plugin'); +const stdLibBrowser = require('node-stdlib-browser'); + +/** @type {import('esbuild').BuildOptions} */ +module.exports = { + entryPoints : ['./src/main.ts'], + bundle : true, + format : 'esm', + sourcemap : true, + minify : true, + platform : 'browser', + target : ['chrome101', 'firefox108', 'safari16'], + inject : [require.resolve('node-stdlib-browser/helpers/esbuild/shim')], + plugins : [polyfillProviderPlugin(stdLibBrowser)], + define : { + 'global': 'globalThis', + }, +}; diff --git a/packages/web5-user-agent/karma.conf.cjs b/packages/old/web5-user-agent/karma.conf.cjs similarity index 100% rename from packages/web5-user-agent/karma.conf.cjs rename to packages/old/web5-user-agent/karma.conf.cjs diff --git a/packages/old/web5-user-agent/package.json b/packages/old/web5-user-agent/package.json new file mode 100644 index 000000000..19a429674 --- /dev/null +++ b/packages/old/web5-user-agent/package.json @@ -0,0 +1,126 @@ +{ + "name": "@tbd54566975/web5-user-agent", + "version": "0.1.10", + "description": "Web5 User Agent", + "type": "module", + "main": "./dist/cjs/main.cjs", + "module": "./dist/esm/main.mjs", + "types": "./dist/types/main.d.ts", + "scripts": { + "build": "rimraf dist && node build/bundles.js && echo '{\"type\": \"commonjs\"}' > ./dist/cjs/package.json && tsc", + "lint": "eslint . --ext .ts --max-warnings 0", + "lint:fix": "eslint . --ext .ts --fix", + "test:node": "rimraf __tests__ && tsc -p tsconfig.test.json && c8 mocha", + "test:browser": "karma start karma.conf.cjs" + }, + "homepage": "https://github.com/TBD54566975/web5-js/tree/main/packages/web5-user-agent#readme", + "bugs": "https://github.com/TBD54566975/web5-js/issues", + "repository": { + "type": "git", + "url": "git+https://github.com/TBD54566975/web5-js", + "directory": "packages/web5-user-agent" + }, + "license": "Apache-2.0", + "contributors": [ + { + "name": "Daniel Buchner", + "url": "https://github.com/csuwildcat" + }, + { + "name": "Frank Hinek", + "url": "https://github.com/frankhinek" + }, + { + "name": "Moe Jangda", + "url": "https://github.com/mistermoe" + } + ], + "files": [ + "dist", + "src" + ], + "exports": { + ".": { + "import": "./dist/esm/main.mjs", + "require": "./dist/cjs/main.cjs", + "types": "./dist/types/main.d.ts" + }, + "./browser": { + "import": "./dist/browser.mjs", + "require": "./dist/browser.js", + "types": "./dist/types/main.d.ts" + }, + "./electron": { + "import": "./dist/esm/main.mjs", + "require": "./dist/electron/main.cjs", + "types": "./dist/types/main.d.ts" + } + }, + "browser": { + "./dist/esm/main.mjs": "./dist/browser.mjs", + "./dist/cjs/main.cjs": "./dist/browser.js", + "types": "./dist/types/main.d.ts" + }, + "react-native": { + "./dist/esm/main.mjs": "./dist/esm/main.mjs", + "./dist/cjs/main.cjs": "./dist/esm/main.mjs", + "types": "./dist/types/main.d.ts" + }, + "keywords": [ + "decentralized", + "decentralized-applications", + "decentralized-identity", + "decentralized-web", + "web5" + ], + "publishConfig": { + "access": "public" + }, + "engines": { + "node": ">=18.0.0" + }, + "dependencies": { + "@decentralized-identity/ion-tools": "1.1.1", + "@tbd54566975/dids": "0.1.9", + "@tbd54566975/dwn-sdk-js": "0.0.37", + "@tbd54566975/web5-agent": "0.1.7", + "abstract-level": "1.0.3", + "cross-fetch": "3.1.5", + "flat": "5.0.2", + "level": "8.0.0", + "readable-web-to-node-stream": "3.0.2", + "uuid": "9.0.0" + }, + "devDependencies": { + "@faker-js/faker": "8.0.1", + "@playwright/test": "1.34.3", + "@types/chai": "4.3.0", + "@types/chai-as-promised": "7.1.5", + "@types/flat": "5.0.2", + "@types/mocha": "10.0.1", + "@types/readable-stream": "2.3.15", + "@types/sinon": "10.0.15", + "@typescript-eslint/eslint-plugin": "5.59.0", + "@typescript-eslint/parser": "5.59.0", + "c8": "8.0.0", + "chai": "4.3.7", + "chai-as-promised": "7.1.1", + "esbuild": "0.16.17", + "eslint": "8.39.0", + "eslint-plugin-mocha": "10.1.0", + "karma": "6.4.1", + "karma-chai": "0.1.0", + "karma-chrome-launcher": "3.1.1", + "karma-esbuild": "2.2.5", + "karma-firefox-launcher": "2.1.2", + "karma-mocha": "2.0.1", + "karma-mocha-reporter": "2.2.5", + "karma-webkit-launcher": "2.1.0", + "mocha": "10.2.0", + "node-stdlib-browser": "1.2.0", + "rimraf": "4.4.0", + "sinon": "15.0.2", + "source-map-loader": "4.0.1", + "typescript": "5.0.4" + } +} \ No newline at end of file diff --git a/packages/old/web5-user-agent/playwright.config.ts b/packages/old/web5-user-agent/playwright.config.ts new file mode 100644 index 000000000..f45c01c0b --- /dev/null +++ b/packages/old/web5-user-agent/playwright.config.ts @@ -0,0 +1,77 @@ +import { defineConfig, devices } from '@playwright/test'; + +/** + * Read environment variables from file. + * https://github.com/motdotla/dotenv + */ +// require('dotenv').config(); + +/** + * See https://playwright.dev/docs/test-configuration. + */ +export default defineConfig({ + testDir : './tests/browser', + /* Run tests in files in parallel */ + fullyParallel : true, + /* Fail the build on CI if you accidentally left test.only in the source code. */ + forbidOnly : !!process.env.CI, + /* Retry on CI only */ + retries : process.env.CI ? 2 : 0, + /* Opt out of parallel tests on CI. */ + workers : process.env.CI ? 1 : undefined, + /* Reporter to use. See https://playwright.dev/docs/test-reporters */ + reporter : 'html', + /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ + use : { + /* Base URL to use in actions like `await page.goto('/')`. */ + // baseURL: 'http://127.0.0.1:3000', + + /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ + trace: 'on-first-retry', + }, + + /* Configure projects for major browsers */ + projects: [ + { + name : 'chromium', + use : { ...devices['Desktop Chrome'] }, + }, + + { + name : 'firefox', + use : { ...devices['Desktop Firefox'] }, + }, + + { + name : 'webkit', + use : { ...devices['Desktop Safari'] }, + }, + + /* Test against mobile viewports. */ + // { + // name: 'Mobile Chrome', + // use: { ...devices['Pixel 5'] }, + // }, + // { + // name: 'Mobile Safari', + // use: { ...devices['iPhone 12'] }, + // }, + + /* Test against branded browsers. */ + // { + // name: 'Microsoft Edge', + // use: { ...devices['Desktop Edge'], channel: 'msedge' }, + // }, + // { + // name: 'Google Chrome', + // use: { ..devices['Desktop Chrome'], channel: 'chrome' }, + // }, + ], + + /* Run your local dev server before starting the tests */ + webServer: { + command : 'npx http-server .', + url : 'http://127.0.0.1:8080/tests/browser/index.html', + reuseExistingServer : !process.env.CI, + }, +}); diff --git a/packages/web5-user-agent/src/dwn-rpc-client.ts b/packages/old/web5-user-agent/src/dwn-rpc-client.ts similarity index 91% rename from packages/web5-user-agent/src/dwn-rpc-client.ts rename to packages/old/web5-user-agent/src/dwn-rpc-client.ts index 1984e1059..e8569821c 100644 --- a/packages/web5-user-agent/src/dwn-rpc-client.ts +++ b/packages/old/web5-user-agent/src/dwn-rpc-client.ts @@ -9,10 +9,10 @@ import { createJsonRpcRequest, parseJson } from '@tbd54566975/web5-agent'; * Client used to communicate with Dwn Servers */ export class DwnRpcClient implements DwnRpc { - private transportClients: Map; + #transportClients: Map; constructor(clients: DwnRpc[] = []) { - this.transportClients = new Map(); + this.#transportClients = new Map(); // include http client as default. can be overwritten for 'http:' or 'https:' if instantiator provides // their own. @@ -20,20 +20,20 @@ export class DwnRpcClient implements DwnRpc { for (let client of clients) { for (let transportScheme of client.transportProtocols) { - this.transportClients.set(transportScheme, client); + this.#transportClients.set(transportScheme, client); } } } get transportProtocols(): string[] { - return Array.from(this.transportClients.keys()); + return Array.from(this.#transportClients.keys()); } sendDwnRequest(request: DwnRpcRequest): Promise { // will throw if url is invalid const url = new URL(request.dwnUrl); - const transportClient = this.transportClients.get(url.protocol); + const transportClient = this.#transportClients.get(url.protocol); if (!transportClient) { const error = new Error(`no ${url.protocol} transport client available`); error.name = 'NO_TRANSPORT_CLIENT'; diff --git a/packages/web5-user-agent/src/main.ts b/packages/old/web5-user-agent/src/main.ts similarity index 75% rename from packages/web5-user-agent/src/main.ts rename to packages/old/web5-user-agent/src/main.ts index 14333cd26..56768246f 100644 --- a/packages/web5-user-agent/src/main.ts +++ b/packages/old/web5-user-agent/src/main.ts @@ -3,4 +3,5 @@ export * from './profile-manager.js'; export * from './profile-api.js'; export * from './profile-store.js'; export * from './sync-manager.js'; -export * from './sync-api.js'; \ No newline at end of file +export * from './sync-api.js'; +export * from './utils.js'; \ No newline at end of file diff --git a/packages/web5-user-agent/src/profile-api.ts b/packages/old/web5-user-agent/src/profile-api.ts similarity index 100% rename from packages/web5-user-agent/src/profile-api.ts rename to packages/old/web5-user-agent/src/profile-api.ts diff --git a/packages/web5-user-agent/src/profile-index.ts b/packages/old/web5-user-agent/src/profile-index.ts similarity index 100% rename from packages/web5-user-agent/src/profile-index.ts rename to packages/old/web5-user-agent/src/profile-index.ts diff --git a/packages/web5-user-agent/src/profile-manager.ts b/packages/old/web5-user-agent/src/profile-manager.ts similarity index 100% rename from packages/web5-user-agent/src/profile-manager.ts rename to packages/old/web5-user-agent/src/profile-manager.ts diff --git a/packages/web5-user-agent/src/profile-store.ts b/packages/old/web5-user-agent/src/profile-store.ts similarity index 100% rename from packages/web5-user-agent/src/profile-store.ts rename to packages/old/web5-user-agent/src/profile-store.ts diff --git a/packages/web5-user-agent/src/sync-api.ts b/packages/old/web5-user-agent/src/sync-api.ts similarity index 76% rename from packages/web5-user-agent/src/sync-api.ts rename to packages/old/web5-user-agent/src/sync-api.ts index 2316f651f..c64c68444 100644 --- a/packages/web5-user-agent/src/sync-api.ts +++ b/packages/old/web5-user-agent/src/sync-api.ts @@ -46,43 +46,43 @@ type DwnMessage = { type DbBatchOperation = BatchOperation; export class SyncApi implements SyncManager { - private db: Level; - private dwn: Dwn; - private didResolver: DidResolver; - private profileManager: ProfileManager; - private dwnRpcClient: DwnRpc; + #db: Level; + #dwn: Dwn; + #didResolver: DidResolver; + #profileManager: ProfileManager; + #dwnRpcClient: DwnRpc; - static defaultOptions = { + static #defaultOptions = { storeLocation: 'data/agent/sync-store', }; constructor(options: SyncApiOptions) { - options = { ...SyncApi.defaultOptions, ...options }; - this.dwn = options.dwn; - this.didResolver = options.didResolver; - this.profileManager = options.profileManager; + options = { ...SyncApi.#defaultOptions, ...options }; + this.#dwn = options.dwn; + this.#didResolver = options.didResolver; + this.#profileManager = options.profileManager; - this.db = new Level(options.storeLocation); - this.dwnRpcClient = new DwnRpcClient(); + this.#db = new Level(options.storeLocation); + this.#dwnRpcClient = new DwnRpcClient(); } async clear() { - return this.db.clear(); + return this.#db.clear(); } async registerProfile(did: string): Promise { - const registeredProfiles = this.db.sublevel('registeredProfiles'); + const registeredProfiles = this.#db.sublevel('registeredProfiles'); await registeredProfiles.put(did, ''); } async enqueuePush() { - const profileDids = await this.db.sublevel('registeredProfiles').keys().all(); + const profileDids = await this.#db.sublevel('registeredProfiles').keys().all(); const syncStates: SyncState[] = []; for (let did of profileDids) { // TODO: try/catch - const { didDocument } = await this.didResolver.resolve(did); + const { didDocument } = await this.#didResolver.resolve(did); const [ service ] = didUtils.getServices(didDocument, { id: '#dwn', type: 'DecentralizedWebNode' }); // did has no dwn service endpoints listed in DID Doc. ignore @@ -99,13 +99,13 @@ export class SyncApi implements SyncManager { } for (let syncState of syncStates) { - const signatureInput = await this.getAuthorSignatureInput(syncState.did); + const signatureInput = await this.#getAuthorSignatureInput(syncState.did); const eventsGet = await EventsGet.create({ watermark : syncState.watermark, authorizationSignatureInput : signatureInput }); - const eventsReply = await this.dwn.processMessage(syncState.did, eventsGet.toJSON()) as EventsGetReply; + const eventsReply = await this.#dwn.processMessage(syncState.did, eventsGet.toJSON()) as EventsGetReply; const putOps: DbBatchOperation[] = []; for (let event of eventsReply.events) { @@ -115,13 +115,13 @@ export class SyncApi implements SyncManager { putOps.push(putOp); } - const pushQueue = this.getPushQueue(); + const pushQueue = this.#getPushQueue(); await pushQueue.batch(putOps as any); } } async getEvents(did: string, watermark: string | undefined, dwnUrl: string) { - const signatureInput = await this.getAuthorSignatureInput(did); + const signatureInput = await this.#getAuthorSignatureInput(did); const eventsGet = await EventsGet.create({ watermark : watermark, authorizationSignatureInput : signatureInput @@ -129,10 +129,10 @@ export class SyncApi implements SyncManager { let events: Event[]; if (dwnUrl === 'local') { - const reply = await this.dwn.processMessage(did, eventsGet.toJSON()) as EventsGetReply; + const reply = await this.#dwn.processMessage(did, eventsGet.toJSON()) as EventsGetReply; ({ events } = reply); } else { - const reply = await this.dwnRpcClient.sendDwnRequest({ + const reply = await this.#dwnRpcClient.sendDwnRequest({ dwnUrl, targetDid : did, message : eventsGet @@ -147,7 +147,7 @@ export class SyncApi implements SyncManager { async push() { await this.enqueuePush(); - const pushQueue = this.getPushQueue(); + const pushQueue = this.#getPushQueue(); const pushJobs = await pushQueue.iterator().all(); const errored: Set = new Set(); @@ -161,17 +161,17 @@ export class SyncApi implements SyncManager { continue; } - const dwnMessage = await this.getDwnMessage(did, messageCid); + const dwnMessage = await this.#getDwnMessage(did, messageCid); if (!dwnMessage) { delOps.push({ type: 'del', key: key }); await this.setWatermark(did, dwnUrl, 'push', watermark); - await this.addMessage(did, messageCid); + await this.#addMessage(did, messageCid); continue; } try { - const reply = await this.dwnRpcClient.sendDwnRequest({ + const reply = await this.#dwnRpcClient.sendDwnRequest({ dwnUrl, targetDid : did, data : dwnMessage.data, @@ -181,7 +181,7 @@ export class SyncApi implements SyncManager { if (reply.status.code === 202 || reply.status.code === 409) { delOps.push({ type: 'del', key: key }); await this.setWatermark(did, dwnUrl, 'push', watermark); - await this.addMessage(did, messageCid); + await this.#addMessage(did, messageCid); } } catch(e) { errored.add(dwnUrl); @@ -192,12 +192,12 @@ export class SyncApi implements SyncManager { } async enqueuePull() { - const profileDids = await this.db.sublevel('registeredProfiles').keys().all(); + const profileDids = await this.#db.sublevel('registeredProfiles').keys().all(); const syncStates: SyncState[] = []; for (let did of profileDids) { // TODO: try/catch - const { didDocument } = await this.didResolver.resolve(did); + const { didDocument } = await this.#didResolver.resolve(did); const [ service ] = didUtils.getServices(didDocument, { id: '#dwn', type: 'DecentralizedWebNode' }); // did has no dwn service endpoints listed in DID Doc. ignore @@ -215,7 +215,7 @@ export class SyncApi implements SyncManager { const pullOps: DbBatchOperation[] = []; for (let syncState of syncStates) { - const signatureInput = await this.getAuthorSignatureInput(syncState.did); + const signatureInput = await this.#getAuthorSignatureInput(syncState.did); const eventsGet = await EventsGet.create({ watermark : syncState.watermark, authorizationSignatureInput : signatureInput @@ -224,7 +224,7 @@ export class SyncApi implements SyncManager { let reply: EventsGetReply; try { - reply = await this.dwnRpcClient.sendDwnRequest({ + reply = await this.#dwnRpcClient.sendDwnRequest({ dwnUrl : syncState.dwnUrl, targetDid : syncState.did, message : eventsGet @@ -241,7 +241,7 @@ export class SyncApi implements SyncManager { } if (pullOps.length > 0) { - const pullQueue = this.getPullQueue(); + const pullQueue = this.#getPullQueue(); pullQueue.batch(pullOps as any); } } @@ -250,7 +250,7 @@ export class SyncApi implements SyncManager { async pull() { await this.enqueuePull(); - const pullQueue = this.getPullQueue(); + const pullQueue = this.#getPullQueue(); const pullJobs = await pullQueue.iterator().all(); const delOps: DbBatchOperation[] = []; const errored: Set = new Set(); @@ -263,7 +263,7 @@ export class SyncApi implements SyncManager { continue; } - const messageExists = await this.messageExists(did, messageCid); + const messageExists = await this.#messageExists(did, messageCid); if (messageExists) { await this.setWatermark(did, dwnUrl, 'pull', watermark); delOps.push({ type: 'del', key }); @@ -271,7 +271,7 @@ export class SyncApi implements SyncManager { continue; } - const signatureInput = await this.getAuthorSignatureInput(did); + const signatureInput = await this.#getAuthorSignatureInput(did); const messagesGet = await MessagesGet.create({ messageCids : [messageCid], authorizationSignatureInput : signatureInput @@ -280,7 +280,7 @@ export class SyncApi implements SyncManager { let reply: MessagesGetReply; try { - reply = await this.dwnRpcClient.sendDwnRequest({ + reply = await this.#dwnRpcClient.sendDwnRequest({ dwnUrl, targetDid : did, message : messagesGet @@ -295,13 +295,13 @@ export class SyncApi implements SyncManager { console.warn(`message ${messageCid} not found. entry: ${JSON.stringify(entry, null, 2)} ignoring..`); await this.setWatermark(did, dwnUrl, 'pull', watermark); - await this.addMessage(did, messageCid); + await this.#addMessage(did, messageCid); delOps.push({ type: 'del', key }); continue; } - const messageType = this.getDwnMessageType(entry.message); + const messageType = this.#getDwnMessageType(entry.message); let dataStream; if (messageType === 'RecordsWrite') { @@ -317,18 +317,18 @@ export class SyncApi implements SyncManager { recordId : message['recordId'] }); - const recordsReadReply = await this.dwnRpcClient.sendDwnRequest({ + const recordsReadReply = await this.#dwnRpcClient.sendDwnRequest({ targetDid : did, dwnUrl, message : recordsRead }) as RecordsReadReply; if (recordsReadReply.status.code >= 400) { - const pruneReply = await this.dwn.synchronizePrunedInitialRecordsWrite(did, message); + const pruneReply = await this.#dwn.synchronizePrunedInitialRecordsWrite(did, message); if (pruneReply.status.code === 202 || pruneReply.status.code === 409) { await this.setWatermark(did, dwnUrl, 'pull', watermark); - await this.addMessage(did, messageCid); + await this.#addMessage(did, messageCid); delOps.push({ type: 'del', key }); continue; @@ -341,11 +341,11 @@ export class SyncApi implements SyncManager { } } - const pullReply = await this.dwn.processMessage(did, entry.message, dataStream); + const pullReply = await this.#dwn.processMessage(did, entry.message, dataStream); if (pullReply.status.code === 202 || pullReply.status.code === 409) { await this.setWatermark(did, dwnUrl, 'pull', watermark); - await this.addMessage(did, messageCid); + await this.#addMessage(did, messageCid); delOps.push({ type: 'del', key }); } } @@ -354,14 +354,14 @@ export class SyncApi implements SyncManager { await pullQueue.batch(delOps as any); } - async getDwnMessage(author: string, messageCid: string): Promise { - const dwnSignatureInput = await this.getAuthorSignatureInput(author); + async #getDwnMessage(author: string, messageCid: string): Promise { + const dwnSignatureInput = await this.#getAuthorSignatureInput(author); const messagesGet = await MessagesGet.create({ authorizationSignatureInput : dwnSignatureInput, messageCids : [messageCid] }); - const result: MessagesGetReply = await this.dwn.processMessage(author, messagesGet.toJSON()); + const result: MessagesGetReply = await this.#dwn.processMessage(author, messagesGet.toJSON()); const [ messageEntry ] = result.messages; // absence of a messageEntry or message within messageEntry can happen because updating a Record actually creates another @@ -393,7 +393,7 @@ export class SyncApi implements SyncManager { recordId : message['recordId'] }); - const reply = await this.dwn.processMessage(author, recordsRead.toJSON()) as RecordsReadReply; + const reply = await this.#dwn.processMessage(author, recordsRead.toJSON()) as RecordsReadReply; // if the data no longer exists (aka 404), it's likely that a `RecordsDelete` took place. // `RecordsDelete` keeps a `RecordsWrite` and just deletes the associated data, effectively acting as a "tombstone". @@ -417,8 +417,8 @@ export class SyncApi implements SyncManager { * @param authorDid * @returns {SignatureInput} */ - async getAuthorSignatureInput(authorDid: string): Promise { - const profile = await this.profileManager.getProfile(authorDid); + async #getAuthorSignatureInput(authorDid: string): Promise { + const profile = await this.#profileManager.getProfile(authorDid); if (!profile) { throw new Error('profile not found for author.'); @@ -442,7 +442,7 @@ export class SyncApi implements SyncManager { async getWatermark(did: string, dwnUrl: string, direction: Direction) { const wmKey = `${did}~${dwnUrl}~${direction}`; - const watermarkStore = this.getWatermarkStore(); + const watermarkStore = this.#getWatermarkStore(); try { return await watermarkStore.get(wmKey); @@ -455,13 +455,13 @@ export class SyncApi implements SyncManager { async setWatermark(did: string, dwnUrl: string, direction: Direction, watermark: string) { const wmKey = `${did}~${dwnUrl}~${direction}`; - const watermarkStore = this.getWatermarkStore(); + const watermarkStore = this.#getWatermarkStore(); return watermarkStore.put(wmKey, watermark); } - async messageExists(did: string, messageCid: string) { - const messageStore = this.getMessageStore(did); + async #messageExists(did: string, messageCid: string) { + const messageStore = this.#getMessageStore(did); const hashedKey = new Set([messageCid]); const itr = messageStore.keys({ lte: messageCid, limit: 1 }); @@ -474,30 +474,30 @@ export class SyncApi implements SyncManager { } } - async addMessage(did: string, messageCid: string) { - const messageStore = this.getMessageStore(did); + async #addMessage(did: string, messageCid: string) { + const messageStore = this.#getMessageStore(did); return messageStore.put(messageCid, ''); } - getMessageStore(did: string) { - return this.db.sublevel('history').sublevel(did).sublevel('messages'); + #getMessageStore(did: string) { + return this.#db.sublevel('history').sublevel(did).sublevel('messages'); } - getWatermarkStore() { - return this.db.sublevel('watermarks'); + #getWatermarkStore() { + return this.#db.sublevel('watermarks'); } - getPushQueue() { - return this.db.sublevel('pushQueue'); + #getPushQueue() { + return this.#db.sublevel('pushQueue'); } - getPullQueue() { - return this.db.sublevel('pullQueue'); + #getPullQueue() { + return this.#db.sublevel('pullQueue'); } // TODO: export BaseMessage from dwn-sdk. - getDwnMessageType(message: any) { + #getDwnMessageType(message: any) { return `${message.descriptor.interface}${message.descriptor.method}`; } } \ No newline at end of file diff --git a/packages/web5-user-agent/src/sync-manager.ts b/packages/old/web5-user-agent/src/sync-manager.ts similarity index 100% rename from packages/web5-user-agent/src/sync-manager.ts rename to packages/old/web5-user-agent/src/sync-manager.ts diff --git a/packages/web5-user-agent/src/utils.ts b/packages/old/web5-user-agent/src/utils.ts similarity index 100% rename from packages/web5-user-agent/src/utils.ts rename to packages/old/web5-user-agent/src/utils.ts diff --git a/packages/web5-user-agent/src/web5-user-agent.ts b/packages/old/web5-user-agent/src/web5-user-agent.ts similarity index 90% rename from packages/web5-user-agent/src/web5-user-agent.ts rename to packages/old/web5-user-agent/src/web5-user-agent.ts index a3ac042e3..2b7698762 100644 --- a/packages/web5-user-agent/src/web5-user-agent.ts +++ b/packages/old/web5-user-agent/src/web5-user-agent.ts @@ -1,7 +1,4 @@ import type { DwnServiceEndpoint } from '@tbd54566975/dids'; - -import { Readable } from 'readable-stream'; -import { MessageStoreLevel, DataStoreLevel, EventLogLevel } from '@tbd54566975/dwn-sdk-js/stores'; import { DataStream, SignatureInput, @@ -10,12 +7,10 @@ import { UnionMessageReply, RecordsWriteMessage, RecordsWriteOptions, - Cid, - Encoder, - Message, PrivateJwk as DwnPrivateKeyJwk, } from '@tbd54566975/dwn-sdk-js'; +import { Readable } from 'readable-stream'; import { DwnRpc, Web5Agent, @@ -25,6 +20,12 @@ import { ProcessDwnRequest, } from '@tbd54566975/web5-agent'; +import { + Cid, + Encoder, + Message, +} from '@tbd54566975/dwn-sdk-js'; + import type { SyncManager } from './sync-manager.js'; import type { ProfileManager } from './profile-manager.js'; @@ -95,14 +96,7 @@ export class Web5UserAgent implements Web5Agent { * @returns */ static async create(options: Partial) { - if (!options.dwn) { - const messageStore = new MessageStoreLevel(); - const dataStore = new DataStoreLevel(); - const eventLog = new EventLogLevel(); - - options.dwn = await Dwn.create({ messageStore, dataStore, eventLog }); - } - + options.dwn ||= await Dwn.create(); options.profileManager ||= new ProfileApi(); options.didResolver ||= new DidResolver({ methodResolvers: [new DidIonApi(), new DidKeyApi()] }); @@ -115,7 +109,7 @@ export class Web5UserAgent implements Web5Agent { * @returns */ async processDwnRequest(request: ProcessDwnRequest): Promise { - const { message, dataStream }= await this.constructDwnMessage(request); + const { message, dataStream }= await this.#constructDwnMessage(request); let reply: UnionMessageReply; if (request.store !== false) { @@ -136,12 +130,12 @@ export class Web5UserAgent implements Web5Agent { let messageData; if ('messageCid' in request) { - const { message, data } = await this.getDwnMessage(request.author, request.messageType, request.messageCid); + const { message, data } = await this.#getDwnMessage(request.author, request.messageType, request.messageCid); dwnRpcRequest.message = message; messageData = data; } else { - const { message } = await this.constructDwnMessage(request); + const { message } = await this.#constructDwnMessage(request); dwnRpcRequest.message = message; messageData = request.dataStream; } @@ -196,8 +190,8 @@ export class Web5UserAgent implements Web5Agent { }; } - async getDwnMessage(author: string, messageType: string, messageCid: string): Promise { - const dwnSignatureInput = await this.getAuthorSignatureInput(author); + async #getDwnMessage(author: string, messageType: string, messageCid: string): Promise { + const dwnSignatureInput = await this.#getAuthorSignatureInput(author); const messagesGet = await MessagesGet.create({ authorizationSignatureInput : dwnSignatureInput, messageCids : [messageCid] @@ -246,8 +240,8 @@ export class Web5UserAgent implements Web5Agent { return dwnMessage; } - async constructDwnMessage(request: ProcessDwnRequest) { - const dwnSignatureInput = await this.getAuthorSignatureInput(request.author); + async #constructDwnMessage(request: ProcessDwnRequest) { + const dwnSignatureInput = await this.#getAuthorSignatureInput(request.author); let readableStream: Readable; // TODO: Consider refactoring to move data transformations imposed by fetch() limitations to the HTTP transport-related methods. @@ -291,7 +285,7 @@ export class Web5UserAgent implements Web5Agent { * @param authorDid * @returns {SignatureInput} */ - async getAuthorSignatureInput(authorDid: string): Promise { + async #getAuthorSignatureInput(authorDid: string): Promise { const profile = await this.profileManager.getProfile(authorDid); if (!profile) { diff --git a/packages/old/web5-user-agent/tests/browser/index.html b/packages/old/web5-user-agent/tests/browser/index.html new file mode 100644 index 000000000..821cd1201 --- /dev/null +++ b/packages/old/web5-user-agent/tests/browser/index.html @@ -0,0 +1,55 @@ + + + + + + + + Web5 User Agent Test + + + +
+ + +
+ + + + + \ No newline at end of file diff --git a/packages/old/web5-user-agent/tests/browser/test.jpeg b/packages/old/web5-user-agent/tests/browser/test.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..295f11c91d070506f5b906f2bd7cc4893f45eabb GIT binary patch literal 234053 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 X?9s`|o%p(FKt@B^eS#9)Z_bwEC})`8$P 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 z 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 { + await page.goto('http://127.0.0.1:8080/tests/browser/index.html'); + + page.on('console', msg => console.log(msg.text())); + + const inputChonkerHandler = await page.$('#chonker'); + await expect(inputChonkerHandler).toBeTruthy; + + await inputChonkerHandler?.setInputFiles('./tests/browser/test.jpeg'); + await page.click('#chonker_btn'); + expect(true).toBe(false); +}); diff --git a/packages/web5-user-agent/tests/common/utils/test-user-agent.ts b/packages/old/web5-user-agent/tests/common/utils/test-user-agent.ts similarity index 96% rename from packages/web5-user-agent/tests/common/utils/test-user-agent.ts rename to packages/old/web5-user-agent/tests/common/utils/test-user-agent.ts index 094106e65..d0120e7f9 100644 --- a/packages/web5-user-agent/tests/common/utils/test-user-agent.ts +++ b/packages/old/web5-user-agent/tests/common/utils/test-user-agent.ts @@ -1,11 +1,8 @@ import type { DidIonCreateOptions } from '@tbd54566975/dids'; -import { Dwn } from '@tbd54566975/dwn-sdk-js'; -import { DataStoreLevel, EventLogLevel, MessageStoreLevel } from '@tbd54566975/dwn-sdk-js/stores'; import { DidIonApi, DidKeyApi, DidResolver } from '@tbd54566975/dids'; - - import { Web5UserAgent, ProfileApi, ProfileStore } from '../../../src/main.js'; +import { Dwn, DataStoreLevel, EventLogLevel, MessageStoreLevel } from '@tbd54566975/dwn-sdk-js'; type CreateMethodOptions = { testDataLocation?: string; diff --git a/packages/web5-user-agent/tests/common/web5-user-agent.spec.ts b/packages/old/web5-user-agent/tests/common/web5-user-agent.spec.ts similarity index 100% rename from packages/web5-user-agent/tests/common/web5-user-agent.spec.ts rename to packages/old/web5-user-agent/tests/common/web5-user-agent.spec.ts diff --git a/packages/web5-user-agent/tests/fixtures/protocol-definitions/email.json b/packages/old/web5-user-agent/tests/fixtures/protocol-definitions/email.json similarity index 100% rename from packages/web5-user-agent/tests/fixtures/protocol-definitions/email.json rename to packages/old/web5-user-agent/tests/fixtures/protocol-definitions/email.json diff --git a/packages/web5-user-agent/tests/fixtures/protocol-definitions/message.json b/packages/old/web5-user-agent/tests/fixtures/protocol-definitions/message.json similarity index 100% rename from packages/web5-user-agent/tests/fixtures/protocol-definitions/message.json rename to packages/old/web5-user-agent/tests/fixtures/protocol-definitions/message.json diff --git a/packages/web5-user-agent/tests/fixtures/test-profiles.ts b/packages/old/web5-user-agent/tests/fixtures/test-profiles.ts similarity index 100% rename from packages/web5-user-agent/tests/fixtures/test-profiles.ts rename to packages/old/web5-user-agent/tests/fixtures/test-profiles.ts diff --git a/packages/web5-user-agent/tests/node/web5-user-agent.spec.ts b/packages/old/web5-user-agent/tests/node/web5-user-agent.spec.ts similarity index 100% rename from packages/web5-user-agent/tests/node/web5-user-agent.spec.ts rename to packages/old/web5-user-agent/tests/node/web5-user-agent.spec.ts diff --git a/packages/old/web5-user-agent/tsconfig.json b/packages/old/web5-user-agent/tsconfig.json new file mode 100644 index 000000000..30cb484fa --- /dev/null +++ b/packages/old/web5-user-agent/tsconfig.json @@ -0,0 +1,27 @@ +{ + "compilerOptions": { + "lib": [ + "DOM", + "ES6" + ], + "allowJs": true, + "target": "es6", + "module": "ESNext", // Required for enabling JavaScript import assertion support + "declaration": true, + "declarationMap": true, + "emitDeclarationOnly": true, + "declarationDir": "dist/types", + "sourceMap": true, + // `NodeNext` will throw compilation errors if relative import paths are missing file extension + // reference: https://devblogs.microsoft.com/typescript/announcing-typescript-4-7/#ecmascript-module-support-in-node-js + "moduleResolution": "NodeNext", + "esModuleInterop": true + }, + "include": [ + "src" + ], + "exclude": [ + "node_modules", + "dist" + ] +} \ No newline at end of file diff --git a/packages/old/web5-user-agent/tsconfig.test.json b/packages/old/web5-user-agent/tsconfig.test.json new file mode 100644 index 000000000..4c4902bfa --- /dev/null +++ b/packages/old/web5-user-agent/tsconfig.test.json @@ -0,0 +1,32 @@ +{ + "compilerOptions": { + "lib": [ + "DOM", + "ES6" + ], + "target": "ESNext", + "module": "ESNext", // Required for enabling JavaScript import assertion support + "declaration": true, + "declarationMap": true, + "rootDir": "./", + "outDir": "__tests__", + "declarationDir": "__tests__/types", + "sourceMap": true, + // `NodeNext` will throw compilation errors if relative import paths are missing file extension + // reference: https://devblogs.microsoft.com/typescript/announcing-typescript-4-7/#ecmascript-module-support-in-node-js + "moduleResolution": "NodeNext", + // allows us to import json files + "resolveJsonModule": true, + "esModuleInterop": true + }, + "include": [ + "src", + "tests/common", + "tests/node" + ], + "exclude": [ + "node_modules", + "dist", + "__tests__" + ] +} \ No newline at end of file diff --git a/packages/old/web5/.c8rc.json b/packages/old/web5/.c8rc.json new file mode 100644 index 000000000..0d483c700 --- /dev/null +++ b/packages/old/web5/.c8rc.json @@ -0,0 +1,18 @@ +{ + "all": true, + "cache": false, + "extension": [ + ".js" + ], + "include": [ + "__tests__/src/**" + ], + "exclude": [ + "__tests__/src/main.js", + "__tests__/types/**" + ], + "reporter": [ + "cobertura", + "text" + ] +} \ No newline at end of file diff --git a/packages/old/web5/.mocharc.json b/packages/old/web5/.mocharc.json new file mode 100644 index 000000000..c223f40c7 --- /dev/null +++ b/packages/old/web5/.mocharc.json @@ -0,0 +1,5 @@ +{ + "enable-source-maps": true, + "exit": true, + "spec": ["__tests__/**/*.spec.js"] + } \ No newline at end of file diff --git a/packages/old/web5/.vscode/launch.json b/packages/old/web5/.vscode/launch.json new file mode 100644 index 000000000..d00daba36 --- /dev/null +++ b/packages/old/web5/.vscode/launch.json @@ -0,0 +1,29 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "name": "Browser in-page.html", + "request": "launch", + "type": "chrome", + "url": "http://localhost:8000/packages/web5/examples/in-page.html", + "webRoot": "${workspaceFolder:web5}" + }, + { + "type": "node", + "request": "launch", + "name": "test:node", + "runtimeExecutable": "${workspaceFolder:root}/node_modules/.bin/mocha", + "runtimeArgs": [ + "${workspaceFolder:web5}/__tests__/**/*.spec.js" + ], + "console": "internalConsole", + "preLaunchTask": "tsc: build - tsconfig.test.json", + "outFiles": [ + "${workspaceFolder:web5}/__tests__/**/*.js" + ], + } + ] +} \ No newline at end of file diff --git a/packages/old/web5/.vscode/tasks.json b/packages/old/web5/.vscode/tasks.json new file mode 100644 index 000000000..2529e7e5d --- /dev/null +++ b/packages/old/web5/.vscode/tasks.json @@ -0,0 +1,45 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "label": "npm run build", + "type": "npm", + "script": "build", + "problemMatcher": [], + "group": { + "kind": "build", + "isDefault": true + } + }, + { + "label": "tsc: build - tsconfig.json", + "type": "typescript", + "tsconfig": "tsconfig.json", + "problemMatcher": [ + "$tsc" + ], + "group": { + "kind": "build", + "isDefault": false + }, + "options": { + "cwd": "${workspaceFolder:root}" + } + }, + { + "label": "tsc: build - tsconfig.test.json", + "type": "typescript", + "tsconfig": "tsconfig.test.json", + "problemMatcher": [ + "$tsc" + ], + "group": { + "kind": "build", + "isDefault": false + }, + "options": { + "cwd": "${workspaceFolder:root}" + } + }, + ] +} \ No newline at end of file diff --git a/packages/old/web5/LICENSE b/packages/old/web5/LICENSE new file mode 100644 index 000000000..f49a4e16e --- /dev/null +++ b/packages/old/web5/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. \ No newline at end of file diff --git a/packages/web5/README.md b/packages/old/web5/README.md similarity index 100% rename from packages/web5/README.md rename to packages/old/web5/README.md diff --git a/packages/old/web5/build/bundles.js b/packages/old/web5/build/bundles.js new file mode 100644 index 000000000..b1041cc61 --- /dev/null +++ b/packages/old/web5/build/bundles.js @@ -0,0 +1,55 @@ +import esbuild from 'esbuild'; +import browserConfig from './esbuild-browser-config.cjs'; + +// cjs bundle for Electron apps. external dependencies bundled except LevelDB +// Remove if/when the following PR is merged and this bundle is no longer needed by Electron apps +// https://github.com/electron/electron/pull/37535 +esbuild.buildSync({ + platform : 'node', + bundle : true, + format : 'cjs', + // packages: 'external', + external : ['level'], + sourcemap : true, + entryPoints : ['./src/main.ts'], + outfile : './dist/electron/main.cjs', + allowOverwrite : true, +}); + +// cjs bundle. external dependencies **not** bundled +esbuild.buildSync({ + platform : 'node', + bundle : true, + format : 'cjs', + packages : 'external', + sourcemap : true, + entryPoints : ['./src/main.ts'], + outfile : './dist/cjs/main.cjs', + allowOverwrite : true, +}); + +// esm bundle. external dependencies **not** bundled +esbuild.buildSync({ + platform : 'node', + bundle : true, + format : 'esm', + packages : 'external', + sourcemap : true, + entryPoints : ['./src/main.ts'], + outfile : './dist/esm/main.mjs', + allowOverwrite : true, +}); + +// esm polyfilled bundle for browser +esbuild.build({ + ...browserConfig, + outfile: 'dist/browser.mjs', +}); + +// iife polyfilled bundle for browser +esbuild.build({ + ...browserConfig, + format : 'iife', + globalName : 'Web5', + outfile : 'dist/browser.js', +}); \ No newline at end of file diff --git a/packages/old/web5/build/esbuild-browser-config.cjs b/packages/old/web5/build/esbuild-browser-config.cjs new file mode 100644 index 000000000..4ef8cf1d8 --- /dev/null +++ b/packages/old/web5/build/esbuild-browser-config.cjs @@ -0,0 +1,19 @@ +/* eslint-disable @typescript-eslint/no-var-requires */ +const polyfillProviderPlugin = require('node-stdlib-browser/helpers/esbuild/plugin'); +const stdLibBrowser = require('node-stdlib-browser'); + +/** @type {import('esbuild').BuildOptions} */ +module.exports = { + entryPoints : ['./src/main.ts'], + bundle : true, + format : 'esm', + sourcemap : true, + minify : true, + platform : 'browser', + target : ['chrome101', 'firefox108', 'safari16'], + inject : [require.resolve('node-stdlib-browser/helpers/esbuild/shim')], + plugins : [polyfillProviderPlugin(stdLibBrowser)], + define : { + 'global': 'globalThis', + }, +}; diff --git a/packages/web5/examples/in-page.html b/packages/old/web5/examples/in-page.html similarity index 100% rename from packages/web5/examples/in-page.html rename to packages/old/web5/examples/in-page.html diff --git a/packages/old/web5/karma.conf.cjs b/packages/old/web5/karma.conf.cjs new file mode 100644 index 000000000..e114c588c --- /dev/null +++ b/packages/old/web5/karma.conf.cjs @@ -0,0 +1,87 @@ +/* eslint-disable @typescript-eslint/no-var-requires */ +// Karma is what we're using to run our tests in browser environments +// Karma does not support .mjs + +// playwright acts as a safari executable on windows and mac +const playwright = require('@playwright/test'); +const esbuildBrowserConfig = require('./build/esbuild-browser-config.cjs'); + +// use playwright chrome exec path as run target for chromium tests +process.env.CHROME_BIN = playwright.chromium.executablePath(); + +// use playwright webkit exec path as run target for safari tests +process.env.WEBKIT_HEADLESS_BIN = playwright.webkit.executablePath(); + +// use playwright firefox exec path as run target for firefox tests +process.env.FIREFOX_BIN = playwright.firefox.executablePath(); + +module.exports = function (config) { + config.set({ + plugins: [ + 'karma-chrome-launcher', + 'karma-firefox-launcher', + 'karma-webkit-launcher', + 'karma-esbuild', + 'karma-mocha', + 'karma-mocha-reporter', + ], + + // frameworks to use + // available frameworks: https://www.npmjs.com/search?q=keywords:karma-adapter + frameworks: ['mocha'], + + // Increase Mocha's default timeout of 2 seconds to prevent timeouts during GitHub CI runs. + client: { + mocha: { + timeout: 10000 // 10 seconds + } + }, + + // list of files / patterns to load in the browser + files: [ + // { pattern: 'tests/test-user-agent.ts', watched: false }, + { pattern: 'tests/**/*.spec.ts', watched: false }, + ], + + // preprocess matching files before serving them to the browser + // available preprocessors: https://www.npmjs.com/search?q=keywords:karma-preprocessor + preprocessors: { + // 'tests/test-user-agent.ts' : ['esbuild'], + 'tests/**/*.spec.ts': ['esbuild'], + }, + + esbuild: esbuildBrowserConfig, + + // list of files / patterns to exclude + exclude: [], + + // test results reporter to use + // available reporters: https://www.npmjs.com/search?q=keywords:karma-reporter + reporters: ['mocha'], + + // web server port + port: 9876, + + // enable / disable colors in the output (reporters and logs) + colors: true, + + // level of logging + // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || + // config.LOG_INFO || config.LOG_DEBUG + logLevel: config.INFO, + + concurrency: 1, + + // start these browsers + // available browser launchers: https://www.npmjs.com/search?q=keywords:karma-launcher + browsers: ['ChromeHeadless', 'FirefoxHeadless', 'WebkitHeadless'], + + // Continuous Integration mode + // if true, Karma captures browsers, runs the tests and exits + singleRun: true, + + // Increase browser timeouts to avoid DISCONNECTED messages during GitHub CI runs. + browserDisconnectTimeout : 10000, // default 2000 + browserDisconnectTolerance : 1, // default 0 + }); +}; diff --git a/packages/old/web5/package.json b/packages/old/web5/package.json new file mode 100644 index 000000000..6ac833053 --- /dev/null +++ b/packages/old/web5/package.json @@ -0,0 +1,130 @@ +{ + "name": "@tbd54566975/web5", + "version": "0.7.11", + "description": "SDK for accessing the features and capabilities of Web5", + "type": "module", + "main": "./dist/cjs/main.cjs", + "module": "./dist/esm/main.mjs", + "types": "./dist/types/main.d.ts", + "scripts": { + "build": "rimraf dist && node build/bundles.js && echo '{\"type\": \"commonjs\"}' > ./dist/cjs/package.json && tsc", + "lint": "eslint . --ext .ts --max-warnings 0", + "lint:fix": "eslint . --ext .ts --fix", + "test:node": "rimraf __tests__ && tsc -p tsconfig.test.json && c8 mocha", + "test:browser": "karma start karma.conf.cjs" + }, + "homepage": "https://github.com/TBD54566975/web5-js/tree/main/packages/web5#readme", + "bugs": "https://github.com/TBD54566975/web5-js/issues", + "repository": { + "type": "git", + "url": "git+https://github.com/TBD54566975/web5-js", + "directory": "packages/web5" + }, + "license": "Apache-2.0", + "contributors": [ + { + "name": "Daniel Buchner", + "url": "https://github.com/csuwildcat" + }, + { + "name": "Frank Hinek", + "url": "https://github.com/frankhinek" + }, + { + "name": "Moe Jangda", + "url": "https://github.com/mistermoe" + } + ], + "files": [ + "dist", + "src" + ], + "exports": { + ".": { + "import": "./dist/esm/main.mjs", + "require": "./dist/cjs/main.cjs", + "types": "./dist/types/main.d.ts" + }, + "./browser": { + "import": "./dist/browser.mjs", + "require": "./dist/browser.js", + "types": "./dist/types/main.d.ts" + }, + "./electron": { + "import": "./dist/esm/main.mjs", + "require": "./dist/electron/main.cjs", + "types": "./dist/types/main.d.ts" + } + }, + "browser": { + "./dist/esm/main.mjs": "./dist/browser.mjs", + "./dist/cjs/main.cjs": "./dist/browser.js", + "types": "./dist/types/main.d.ts" + }, + "react-native": { + "./dist/esm/main.mjs": "./dist/esm/main.mjs", + "./dist/cjs/main.cjs": "./dist/esm/main.mjs", + "types": "./dist/types/main.d.ts" + }, + "keywords": [ + "decentralized", + "decentralized-applications", + "decentralized-identity", + "decentralized-web", + "DID", + "sdk", + "verifiable-credentials", + "web5", + "web5-sdk" + ], + "publishConfig": { + "access": "public" + }, + "engines": { + "node": ">=18.0.0" + }, + "dependencies": { + "@decentralized-identity/ion-tools": "1.1.1", + "@tbd54566975/crypto": "0.1.6", + "@tbd54566975/dids": "0.1.9", + "@tbd54566975/dwn-sdk-js": "0.0.37", + "@tbd54566975/web5-agent": "0.1.7", + "@tbd54566975/web5-proxy-agent": "0.1.7", + "@tbd54566975/web5-user-agent": "0.1.10", + "level": "8.0.0", + "ms": "2.1.3", + "readable-web-to-node-stream": "3.0.2" + }, + "devDependencies": { + "@playwright/test": "1.34.3", + "@types/chai": "4.3.0", + "@types/chai-as-promised": "7.1.5", + "@types/eslint": "8.37.0", + "@types/mocha": "10.0.1", + "@types/ms": "0.7.31", + "@types/readable-stream": "2.3.15", + "@types/sinon": "10.0.15", + "@typescript-eslint/eslint-plugin": "5.59.0", + "@typescript-eslint/parser": "5.59.0", + "c8": "8.0.0", + "chai": "4.3.7", + "chai-as-promised": "7.1.1", + "esbuild": "0.16.17", + "eslint": "8.39.0", + "eslint-plugin-mocha": "10.1.0", + "karma": "6.4.1", + "karma-chai": "0.1.0", + "karma-chrome-launcher": "3.1.1", + "karma-esbuild": "2.2.5", + "karma-firefox-launcher": "2.1.2", + "karma-mocha": "2.0.1", + "karma-mocha-reporter": "2.2.5", + "karma-webkit-launcher": "2.1.0", + "mocha": "10.2.0", + "node-stdlib-browser": "1.2.0", + "rimraf": "4.4.0", + "sinon": "15.0.2", + "source-map-loader": "4.0.1", + "typescript": "5.0.4" + } +} \ No newline at end of file diff --git a/packages/web5/src/app-storage.ts b/packages/old/web5/src/app-storage.ts similarity index 100% rename from packages/web5/src/app-storage.ts rename to packages/old/web5/src/app-storage.ts diff --git a/packages/web5/src/did-api.ts b/packages/old/web5/src/did-api.ts similarity index 100% rename from packages/web5/src/did-api.ts rename to packages/old/web5/src/did-api.ts diff --git a/packages/old/web5/src/did-resolution-cache.ts b/packages/old/web5/src/did-resolution-cache.ts new file mode 100644 index 000000000..8969da7bb --- /dev/null +++ b/packages/old/web5/src/did-resolution-cache.ts @@ -0,0 +1,77 @@ +import type { DidResolutionResult, DidResolverCache } from '@tbd54566975/dids'; + +import ms from 'ms'; +import { Level } from 'level'; + +export type DidResolutionCacheOptions = { + location?: string; + ttl?: string; +} + +type CacheWrapper = { + ttlMillis: number; + value: DidResolutionResult; +} + +/** + * naive level-based cache for did resolution results. It just so happens that level aggressively keeps as much as it + * can in memory when possible while also writing to the filesystem (in node runtime) and indexedDB (in browser runtime). + * the persistent aspect is especially useful across page refreshes. + */ +export class DidResolutionCache implements DidResolverCache { + private cache: Level; + private ttl: number; + + static #defaultOptions = { + location : 'data/did-res-cache', + ttl : '15m' + }; + + constructor(options: DidResolutionCacheOptions = {}) { + options = { ...DidResolutionCache.#defaultOptions, ...options }; + + this.cache = new Level(options.location!); + this.ttl = ms(options.ttl!); + } + + async get(did: string): Promise { + try { + const str = await this.cache.get(did); + const cacheWrapper: CacheWrapper = JSON.parse(str); + + if (Date.now() >= cacheWrapper.ttlMillis) { + // defer deletion to be called in the next tick of the js event loop + this.cache.nextTick(() => this.cache.del(did)); + + return; + } else { + return cacheWrapper.value; + } + + + } catch(e: any) { + // thrown when a key wasn't found + if (e.code === 'LEVEL_NOT_FOUND') { + return; + } + + throw e; + } + } + set(did: string, value: DidResolutionResult): Promise { + const cacheWrapper: CacheWrapper = { ttlMillis: Date.now() + this.ttl, value }; + const str = JSON.stringify(cacheWrapper); + + return this.cache.put(did, str); + } + delete(did: string): Promise { + return this.cache.del(did); + } + clear(): Promise { + return this.cache.clear(); + } + close(): Promise { + return this.cache.close(); + } + +} diff --git a/packages/web5/src/dwn-api.ts b/packages/old/web5/src/dwn-api.ts similarity index 100% rename from packages/web5/src/dwn-api.ts rename to packages/old/web5/src/dwn-api.ts diff --git a/packages/old/web5/src/main.ts b/packages/old/web5/src/main.ts new file mode 100644 index 000000000..8bf80c7d6 --- /dev/null +++ b/packages/old/web5/src/main.ts @@ -0,0 +1 @@ +export * from './web5.js'; \ No newline at end of file diff --git a/packages/web5/src/protocol.ts b/packages/old/web5/src/protocol.ts similarity index 56% rename from packages/web5/src/protocol.ts rename to packages/old/web5/src/protocol.ts index f70d86b73..291230e17 100644 --- a/packages/web5/src/protocol.ts +++ b/packages/old/web5/src/protocol.ts @@ -9,30 +9,30 @@ type ProtocolMetadata = { }; export class Protocol { - private metadata: ProtocolMetadata; - private web5Agent: Web5Agent; - private protocolsConfigureMessage: ProtocolsConfigureMessage; + #metadata: ProtocolMetadata; + #web5Agent: Web5Agent; + #protocolsConfigureMessage: ProtocolsConfigureMessage; get definition() { - return this.protocolsConfigureMessage.descriptor.definition; + return this.#protocolsConfigureMessage.descriptor.definition; } constructor(web5Agent: Web5Agent, protocolsConfigureMessage: ProtocolsConfigureMessage, metadata: ProtocolMetadata) { - this.metadata = metadata; - this.web5Agent = web5Agent; - this.protocolsConfigureMessage = protocolsConfigureMessage; + this.#metadata = metadata; + this.#web5Agent = web5Agent; + this.#protocolsConfigureMessage = protocolsConfigureMessage; } toJSON() { - return this.protocolsConfigureMessage; + return this.#protocolsConfigureMessage; } async send(target: string) { - const { reply } = await this.web5Agent.sendDwnRequest({ + const { reply } = await this.#web5Agent.sendDwnRequest({ messageType : 'ProtocolsConfigure', - author : this.metadata.author, + author : this.#metadata.author, target : target, - messageCid : this.metadata.messageCid + messageCid : this.#metadata.messageCid }); return { status: reply.status }; diff --git a/packages/old/web5/src/record.ts b/packages/old/web5/src/record.ts new file mode 100644 index 000000000..4e4f920ee --- /dev/null +++ b/packages/old/web5/src/record.ts @@ -0,0 +1,350 @@ +import type { Readable } from 'readable-stream'; +import type { Web5Agent } from '@tbd54566975/web5-agent'; +import type { RecordsReadReply, RecordsWriteDescriptor, RecordsWriteMessage, RecordsWriteOptions } from '@tbd54566975/dwn-sdk-js'; + +import { ReadableWebToNodeStream } from 'readable-web-to-node-stream'; +import { DataStream, DwnInterfaceName, DwnMethodName, Encoder } from '@tbd54566975/dwn-sdk-js'; + +import { dataToBlob } from './utils.js'; +import type { RecordsDeleteResponse } from './dwn-api.js'; + +export type RecordOptions = RecordsWriteMessage & { + author: string; + target: string; + encodedData?: string | Blob; + data?: Readable | ReadableStream; +}; + +export type RecordModel = RecordsWriteDescriptor & Omit & { + author: string; + recordId?: string; + target: string; +} + +export type RecordUpdateOptions = { + data?: unknown; + dataCid?: RecordsWriteDescriptor['dataCid']; + dataSize?: RecordsWriteDescriptor['dataSize']; + dateModified?: RecordsWriteDescriptor['dateModified']; + datePublished?: RecordsWriteDescriptor['datePublished']; + published?: RecordsWriteDescriptor['published']; +} + +/** + * TODO: Document class. + */ +export class Record implements RecordModel { + // mutable properties + author: string; + target: string; + isDeleted = false; + + #attestation?: RecordsWriteMessage['attestation']; + #contextId?: string; + #descriptor: RecordsWriteDescriptor; + #encodedData?: string | Blob | null; + #encryption?: RecordsWriteMessage['encryption']; + #readableStream?: Readable | Promise; + #recordId: string; + #web5Agent: Web5Agent; + + // Immutable DWN Record properties. + get attestation(): RecordsWriteMessage['attestation'] { return this.#attestation; } + get contextId() { return this.#contextId; } + get dataFormat() { return this.#descriptor.dataFormat; } + get dateCreated() { return this.#descriptor.dateCreated; } + get encryption(): RecordsWriteMessage['encryption'] { return this.#encryption; } + get id() { return this.#recordId; } + get interface() { return this.#descriptor.interface; } + get method() { return this.#descriptor.method; } + get parentId() { return this.#descriptor.parentId; } + get protocol() { return this.#descriptor.protocol; } + get protocolPath() { return this.#descriptor.protocolPath; } + get recipient() { return this.#descriptor.recipient; } + get schema() { return this.#descriptor.schema; } + + // Mutable DWN Record properties. + get dataCid() { return this.#descriptor.dataCid; } + get dataSize() { return this.#descriptor.dataSize; } + get dateModified() { return this.#descriptor.dateModified; } + get datePublished() { return this.#descriptor.datePublished; } + get published() { return this.#descriptor.published; } + + constructor(web5Agent: Web5Agent, options: RecordOptions) { + this.#web5Agent = web5Agent; + + // Store the target and author DIDs that were used to create the message to use for subsequent reads, etc. + this.author = options.author; + this.target = options.target; + + // RecordsWriteMessage properties. + this.#attestation = options.attestation; + this.#contextId = options.contextId; + this.#descriptor = options.descriptor; + this.#encryption = options.encryption; + this.#recordId = options.recordId; + + + // options.encodedData will either be a base64url encoded string (in the case of RecordsQuery) + // OR a Blob in the case of a RecordsWrite. + this.#encodedData = options.encodedData ?? null; + + // If the record was created from a RecordsRead reply then it will have a `data` property. + if (options.data) { + this.#readableStream = Record.isReadableWebStream(options.data) ? + new ReadableWebToNodeStream(options.data) as Readable : options.data as Readable; + } + } + + /** + * TODO: Document method. + */ + get data() { + if (this.isDeleted) throw new Error('Operation failed: Attempted to access `data` of a record that has already been deleted.'); + + if (!this.#encodedData && !this.#readableStream) { + // `encodedData` will be set if the Record was instantiated by dwn.records.create()/write(). + // `readableStream` will be set if Record was instantiated by dwn.records.read(). + // If neither of the above are true, then the record must be fetched from the DWN. + this.#readableStream = this.#web5Agent.processDwnRequest({ + author : this.author, + messageOptions : { recordId: this.id }, + messageType : DwnInterfaceName.Records + DwnMethodName.Read, + target : this.target, + }) + .then(response => response.reply as RecordsReadReply) + .then(reply => reply.record.data as Readable) + .catch(error => { throw new Error(`Error encountered while attempting to read data: ${error.message}`); }); + } + + if (typeof this.#encodedData === 'string') { + // If `encodedData` is set, then it is expected that: + // type is Blob if the Record object was instantiated by dwn.records.create()/write(). + // type is Base64 URL encoded string if the Record object was instantiated by dwn.records.query(). + // If it is a string, we need to Base64 URL decode to bytes and instantiate a Blob. + const dataBytes = Encoder.base64UrlToBytes(this.#encodedData); + this.#encodedData = new Blob([dataBytes], { type: this.dataFormat }); + } + + // Explicitly cast #encodedData as a Blob since if non-null, it has been converted from string to Blob. + const dataBlob = this.#encodedData as Blob; + + // eslint-disable-next-line @typescript-eslint/no-this-alias + const self = this; // Capture the context of the `Record` instance. + const dataObj = { + async blob(): Promise { + if (dataBlob) return dataBlob; + if (self.#readableStream) return new Blob([await this.stream().then(DataStream.toBytes)], { type: self.dataFormat }); + }, + async json() { + if (dataBlob) return this.text().then(JSON.parse); + if (self.#readableStream) return this.text().then(JSON.parse); + return null; + }, + async text() { + if (dataBlob) return dataBlob.text(); + if (self.#readableStream) return this.stream().then(DataStream.toBytes).then(Encoder.bytesToString); + return null; + }, + async stream() { + if (dataBlob) return new ReadableWebToNodeStream(dataBlob.stream()); + if (self.#readableStream) return self.#readableStream; + return null; + }, + then(...callbacks) { + return this.stream().then(...callbacks); + }, + catch(callback) { + return dataObj.then().catch(callback); + }, + }; + return dataObj; + } + + /** + * TODO: Document method. + */ + async delete(): Promise { + if (this.isDeleted) throw new Error('Operation failed: Attempted to call `delete()` on a record that has already been deleted.'); + + // Attempt to delete the record from the DWN. + const agentResponse = await this.#web5Agent.processDwnRequest({ + author : this.author, + messageOptions : { recordId: this.id }, + messageType : DwnInterfaceName.Records + DwnMethodName.Delete, + target : this.target, + }); + + const { reply: { status } } = agentResponse; + + if (status.code === 202) { + // If the record was successfully deleted, mark the instance as deleted to prevent further modifications. + this.#setDeletedStatus(true); + } + + return { status }; + } + + /** + * TODO: Document method. + */ + async send(target: string): Promise { + if (this.isDeleted) throw new Error('Operation failed: Attempted to call `send()` on a record that has already been deleted.'); + + const { reply: { status } } = await this.#web5Agent.sendDwnRequest({ + messageType : DwnInterfaceName.Records + DwnMethodName.Write, + author : this.author, + dataStream : await this.data.blob(), + target : target, + messageOptions : this.toJSON(), + }); + + return { status }; + } + + /** + * TODO: Document method. + * + * Called by `JSON.stringify(...)` automatically. + */ + toJSON(): RecordModel { + return { + attestation : this.attestation, + author : this.author, + contextId : this.contextId, + dataCid : this.dataCid, + dataFormat : this.dataFormat, + dataSize : this.dataSize, + dateCreated : this.dateCreated, + dateModified : this.dateModified, + datePublished : this.datePublished, + encryption : this.encryption, + interface : this.interface, + method : this.method, + parentId : this.parentId, + protocol : this.protocol, + protocolPath : this.protocolPath, + published : this.published, + recipient : this.recipient, + recordId : this.id, + schema : this.schema, + target : this.target, + }; + } + + /** + * TODO: Document method. + * + * Called automatically in string concatenation, String() type conversion, and template literals. + */ + toString() { + let str = `Record: {\n`; + str += ` ID: ${this.id}\n`; + str += this.contextId ? ` Context ID: ${this.contextId}\n` : ''; + str += this.protocol ? ` Protocol: ${this.protocol}\n` : ''; + str += this.schema ? ` Schema: ${this.schema}\n` : ''; + str += ` Data CID: ${this.dataCid}\n`; + str += ` Data Format: ${this.dataFormat}\n`; + str += ` Data Size: ${this.dataSize}\n`; + str += ` Created: ${this.dateCreated}\n`; + str += ` Modified: ${this.dateModified}\n`; + str += `}`; + return str; + } + + /** + * TODO: Document method. + */ + async update(options: RecordUpdateOptions = {}) { + if (this.isDeleted) throw new Error('Operation failed: Attempted to call `update()` on a record that has already been deleted.'); + + // Begin assembling update message. + let updateMessage = { ...this.#descriptor, ...options } as Partial; + + let dataBlob: Blob; + if (options.data !== undefined) { + // If `data` is being updated then `dataCid` and `dataSize` must be undefined and the `data` property is passed as + // a top-level property to `web5Agent.processDwnRequest()`. + delete updateMessage.dataCid; + delete updateMessage.dataSize; + delete updateMessage.data; + + ({ dataBlob } = dataToBlob(options.data, updateMessage.dataFormat)); + } + + // Throw an error if an attempt is made to modify immutable properties. `data` has already been handled. + const mutableDescriptorProperties = new Set(['data', 'dataCid', 'dataSize', 'dateModified', 'datePublished', 'published']); + Record.#verifyPermittedMutation(Object.keys(options), mutableDescriptorProperties); + + // If a new `dateModified` was not provided, remove it from the updateMessage to let the DWN SDK auto-fill. + // This is necessary because otherwise DWN SDK throws an Error 409 Conflict due to attempting to overwrite a record + // when the `dateModified` timestamps are identical. + if (options.dateModified === undefined) { + delete updateMessage.dateModified; + } + + // If `published` is set to false, ensure that `datePublished` is undefined. Otherwise, DWN SDK's schema validation + // will throw an error if `published` is false but `datePublished` is set. + if (options.published === false && updateMessage.datePublished !== undefined) { + delete updateMessage.datePublished; + } + + // Set the record ID and context ID, if any. + updateMessage.recordId = this.#recordId; + updateMessage.contextId = this.#contextId; + + const messageOptions: Partial = { + ...updateMessage + }; + + const agentResponse = await this.#web5Agent.processDwnRequest({ + author : this.author, + dataStream : dataBlob, + messageOptions, + messageType : DwnInterfaceName.Records + DwnMethodName.Write, + target : this.target, + }); + + const { message, reply: { status } } = agentResponse; + const responseMessage = message as RecordsWriteMessage; + + if (200 <= status.code && status.code <= 299) { + // Only update the local Record instance mutable properties if the record was successfully (over)written. + mutableDescriptorProperties.forEach(property => { + this.#descriptor[property] = responseMessage.descriptor[property]; + }); + // Only cache data if `dataSize` is less than DWN 'max data size allowed to be encoded'. + if (options.data !== undefined) { + this.#encodedData = dataBlob; // Clear `encodedData` in case it was previously set. + } + } + + return { status }; + } + + /** + * TODO: Document method. + */ + #setDeletedStatus(status: boolean): void { + this.isDeleted = status; + } + + /** + * TODO: Document method. + */ + static isReadableWebStream(stream) { + // TODO: Improve robustness of the check modeled after node:stream. + return typeof stream._read !== 'function'; + } + + /** + * TODO: Document method. + */ + static #verifyPermittedMutation(propertiesToMutate: Iterable, mutableDescriptorProperties: Set) { + for (const property of propertiesToMutate) { + if (!mutableDescriptorProperties.has(property)) { + throw new Error(`${property} is an immutable property. Its value cannot be changed.`); + } + } + } +} \ No newline at end of file diff --git a/packages/web5/src/utils.ts b/packages/old/web5/src/utils.ts similarity index 100% rename from packages/web5/src/utils.ts rename to packages/old/web5/src/utils.ts diff --git a/packages/web5/src/vc-api.ts b/packages/old/web5/src/vc-api.ts similarity index 65% rename from packages/web5/src/vc-api.ts rename to packages/old/web5/src/vc-api.ts index acbe71a28..b741f7076 100644 --- a/packages/web5/src/vc-api.ts +++ b/packages/old/web5/src/vc-api.ts @@ -1,12 +1,12 @@ import type { Web5Agent } from '@tbd54566975/web5-agent'; export class VcApi { - private agent: Web5Agent; - private connectedDid: string; + #agent: Web5Agent; + #connectedDid: string; constructor(agent: Web5Agent, connectedDid: string) { - this.agent = agent; - this.connectedDid = connectedDid; + this.#agent = agent; + this.#connectedDid = connectedDid; } async create() { diff --git a/packages/web5/src/web5.ts b/packages/old/web5/src/web5.ts similarity index 88% rename from packages/web5/src/web5.ts rename to packages/old/web5/src/web5.ts index 653a4f20e..0d22e0a30 100644 --- a/packages/web5/src/web5.ts +++ b/packages/old/web5/src/web5.ts @@ -4,8 +4,8 @@ import type { DidState, DidMethodApi, DidResolverCache, DwnServiceEndpoint } fro import ms from 'ms'; +// import { Web5ProxyAgent } from '@tbd54566975/web5-proxy-agent'; import { Dwn } from '@tbd54566975/dwn-sdk-js'; -import { MessageStoreLevel, DataStoreLevel, EventLogLevel } from '@tbd54566975/dwn-sdk-js/stores'; import { Web5UserAgent, ProfileApi, SyncApi } from '@tbd54566975/web5-user-agent'; import { DidIonApi, DidKeyApi, utils as didUtils } from '@tbd54566975/dids'; @@ -51,7 +51,7 @@ export class Web5 { appStorage: AppStorage; dwn: DwnApi; vc: VcApi; - private connectedDid: string; + #connectedDid: string; /** * Statically available DID functionality. can be used to create and resolve DIDs without calling {@link connect}. @@ -70,9 +70,9 @@ export class Web5 { private static APP_DID_KEY = 'WEB5_APP_DID'; private constructor(options: Web5Options) { - this.connectedDid = options.connectedDid; - this.dwn = new DwnApi(options.web5Agent, this.connectedDid); - this.vc = new VcApi(options.web5Agent, this.connectedDid); + this.#connectedDid = options.connectedDid; + this.dwn = new DwnApi(options.web5Agent, this.#connectedDid); + this.vc = new VcApi(options.web5Agent, this.#connectedDid); this.appStorage ||= new AppStorage(); } @@ -109,12 +109,7 @@ export class Web5 { cache : options.didResolutionCache || new DidResolutionCache() }); - const messageStore = new MessageStoreLevel({ blockstoreLocation: 'data/dwn/message-store', indexLocation: 'data/dwn/message-index' }); - const dataStore = new DataStoreLevel({ blockstoreLocation: 'data/dwn/data-store' }); - const eventLog = new EventLogLevel({ location: 'data/dwn/event-log' }); - - const dwn = await Dwn.create({ messageStore, dataStore, eventLog }); - + const dwn = await Dwn.create(); const syncManager = new SyncApi({ profileManager : profileApi, didResolver : Web5.did.resolver, // share the same resolver to share the same underlying cache @@ -146,7 +141,7 @@ export class Web5 { const connectedDid = profile.did.id; const web5 = new Web5({ appStorage: appStorage, web5Agent: agent, connectedDid }); - Web5.enqueueNextSync(syncManager, ms('2m')); + Web5.#enqueueNextSync(syncManager, ms('2m')); return { web5, did: connectedDid }; } @@ -192,13 +187,13 @@ export class Web5 { return Array.from(dwnUrls); } - private static enqueueNextSync(syncManager: SyncManager, delay = 1_000) { + static #enqueueNextSync(syncManager: SyncManager, delay = 1_000) { setTimeout(async () => { try { await syncManager.push(); await syncManager.pull(); - return this.enqueueNextSync(syncManager, delay); + return this.#enqueueNextSync(syncManager, delay); } catch(e) { console.error('Sync failed due to error: ', e); } diff --git a/packages/old/web5/tests/chai-plugins.d.ts b/packages/old/web5/tests/chai-plugins.d.ts new file mode 100644 index 000000000..8dbcb2354 --- /dev/null +++ b/packages/old/web5/tests/chai-plugins.d.ts @@ -0,0 +1,14 @@ +/// + +declare namespace Chai { + + // For BDD API + interface Assertion extends LanguageChains, NumericComparison, TypeComparison { + url: Assertion; + } + + // For Assert API + interface AssertStatic { + isUrl: (actual: any) => Assertion; + } +} \ No newline at end of file diff --git a/packages/old/web5/tests/did-resolution-cache.spec.ts b/packages/old/web5/tests/did-resolution-cache.spec.ts new file mode 100644 index 000000000..d2fc8f0c7 --- /dev/null +++ b/packages/old/web5/tests/did-resolution-cache.spec.ts @@ -0,0 +1,99 @@ +import sinon from 'sinon'; +import { expect } from 'chai'; + +import { DidResolutionCache } from '../src/did-resolution-cache.js'; + +describe('DidResolutionCache', () => { + before(function () { + this.clock = sinon.useFakeTimers(); + }); + + after(function () { + this.clock.restore(); + }); + + it('uses a custom TTL when specified', async function () { + // Instantiate DID resolution cache with custom TTL of 60 seconds. + const cache = new DidResolutionCache({ ttl: '1m', location: '__TESTDATA__/DID-RES-CACHE' }); + + const testDid = 'did:example:alice'; + + const testDidResolutionResult = { + didResolutionMetadata : {}, + didDocument : { id: 'abc123' }, + didDocumentMetadata : {} + }; + + // Write an entry into the cache. + await cache.set(testDid, testDidResolutionResult); + + // Confirm a cache hit. + let valueInCache = await cache.get(testDid); + expect(valueInCache).to.deep.equal(testDidResolutionResult); + + // Time travel 61 seconds. + this.clock.tick(1000 * 61); + + // Confirm a cache miss. + valueInCache = await cache.get(testDid); + expect(valueInCache).to.be.undefined; + + await cache.close(); + }); + + it('deletes specified entry', async function () { + // Instantiate DID resolution cache with default TTL of 15 minutes. + const cache = new DidResolutionCache({ location: '__TESTDATA__/DID-RES-CACHE' }); + + const testDid1 = 'did:example:alice'; + const testDid2 = 'did:example:bob'; + + const testDidResolutionResult = { + didResolutionMetadata : {}, + didDocument : { id: 'abc123' }, + didDocumentMetadata : {} + }; + + await cache.set(testDid1, testDidResolutionResult); + await cache.set(testDid2, testDidResolutionResult); + + await cache.delete(testDid1); + + // Confirm cache miss for deleted entry. + let valueInCache = await cache.get(testDid1); + expect(valueInCache).to.be.undefined; + + // Time travel 14 minutes. + this.clock.tick(1000 * 60 * 14); + + // Confirm cache hit for entry that hasn't yet expired. + valueInCache = await cache.get(testDid2); + expect(valueInCache).to.deep.equal(testDidResolutionResult); + + await cache.close(); + }); + + it('deletes all entries after clear()', async () => { + // Instantiate DID resolution cache with default TTL of 15 minutes. + const cache = new DidResolutionCache({ location: '__TESTDATA__/DID-RES-CACHE' }); + + const testDid1 = 'did:example:alice'; + const testDid2 = 'did:example:bob'; + + const testDidResolutionResult = { + didResolutionMetadata : {}, + didDocument : { id: 'abc123' }, + didDocumentMetadata : {} + }; + + await cache.set(testDid1, testDidResolutionResult); + await cache.set(testDid2, testDidResolutionResult); + + await cache.clear(); + + let valueInCache = await cache.get(testDid1); + expect(valueInCache).to.be.undefined; + valueInCache = await cache.get(testDid2); + expect(valueInCache).to.be.undefined; + }); +}); \ No newline at end of file diff --git a/packages/web5/tests/fixtures/did-documents.js b/packages/old/web5/tests/fixtures/did-documents.js similarity index 100% rename from packages/web5/tests/fixtures/did-documents.js rename to packages/old/web5/tests/fixtures/did-documents.js diff --git a/packages/web5/tests/fixtures/protocol-definitions/email.json b/packages/old/web5/tests/fixtures/protocol-definitions/email.json similarity index 97% rename from packages/web5/tests/fixtures/protocol-definitions/email.json rename to packages/old/web5/tests/fixtures/protocol-definitions/email.json index 9155bc22c..67e56f3c6 100644 --- a/packages/web5/tests/fixtures/protocol-definitions/email.json +++ b/packages/old/web5/tests/fixtures/protocol-definitions/email.json @@ -1,6 +1,5 @@ { "protocol": "http://email-protocol.xyz", - "published": false, "types": { "email": { "schema": "email", diff --git a/packages/web5/tests/fixtures/protocol-definitions/message.json b/packages/old/web5/tests/fixtures/protocol-definitions/message.json similarity index 100% rename from packages/web5/tests/fixtures/protocol-definitions/message.json rename to packages/old/web5/tests/fixtures/protocol-definitions/message.json diff --git a/packages/web5/tests/fixtures/test-profiles.ts b/packages/old/web5/tests/fixtures/test-profiles.ts similarity index 100% rename from packages/web5/tests/fixtures/test-profiles.ts rename to packages/old/web5/tests/fixtures/test-profiles.ts diff --git a/packages/web5/tests/record.spec.ts b/packages/old/web5/tests/record.spec.ts similarity index 99% rename from packages/web5/tests/record.spec.ts rename to packages/old/web5/tests/record.spec.ts index 3acdb66ab..3dfa13dcd 100644 --- a/packages/web5/tests/record.spec.ts +++ b/packages/old/web5/tests/record.spec.ts @@ -107,7 +107,7 @@ describe('Record', () => { key : TestDataGenerator.randomBytes(32), keyEncryptionInputs : [ { - derivationScheme : KeyDerivationScheme.ProtocolPath, + derivationScheme : KeyDerivationScheme.Protocols, publicKey : encryptionPublicKeyJwk as DwnPublicKeyJwk, publicKeyId : recordEncryptionKeyId }, @@ -168,7 +168,7 @@ describe('Record', () => { expect(record.id).to.equal(recordsWrite.message.recordId); expect(record.encryption).to.not.be.undefined; expect(record.encryption).to.deep.equal(recordsWrite.message.encryption); - expect(record.encryption!.keyEncryption.find(key => key.derivationScheme === KeyDerivationScheme.ProtocolPath)); + expect(record.encryption!.keyEncryption.find(key => key.derivationScheme === KeyDerivationScheme.Protocols)); expect(record.encryption!.keyEncryption.find(key => key.derivationScheme === KeyDerivationScheme.Schemas)); expect(record.attestation).to.not.be.undefined; expect(record.attestation).to.have.property('signatures'); @@ -182,7 +182,7 @@ describe('Record', () => { expect(record.dataCid).to.equal(recordsWrite.message.descriptor.dataCid); expect(record.dataSize).to.equal(recordsWrite.message.descriptor.dataSize); expect(record.dateCreated).to.equal(recordsWrite.message.descriptor.dateCreated); - expect(record.dateModified).to.equal(recordsWrite.message.descriptor.messageTimestamp); + expect(record.dateModified).to.equal(recordsWrite.message.descriptor.dateModified); expect(record.published).to.equal(published); expect(record.datePublished).to.equal(recordsWrite.message.descriptor.datePublished); expect(record.dataFormat).to.equal(dataFormat); @@ -918,7 +918,7 @@ describe('Record', () => { key : TestDataGenerator.randomBytes(32), keyEncryptionInputs : [ { - derivationScheme : KeyDerivationScheme.ProtocolPath, + derivationScheme : KeyDerivationScheme.Protocols, publicKey : encryptionPublicKeyJwk as DwnPublicKeyJwk, publicKeyId : recordEncryptionKeyId }, @@ -982,7 +982,7 @@ describe('Record', () => { expect(record.id).to.equal(recordsWrite.message.recordId); expect(record.encryption).to.not.be.undefined; expect(record.encryption).to.deep.equal(recordsWrite.message.encryption); - expect(record.encryption!.keyEncryption.find(key => key.derivationScheme === KeyDerivationScheme.ProtocolPath)); + expect(record.encryption!.keyEncryption.find(key => key.derivationScheme === KeyDerivationScheme.Protocols)); expect(record.encryption!.keyEncryption.find(key => key.derivationScheme === KeyDerivationScheme.Schemas)); expect(record.attestation).to.not.be.undefined; expect(record.attestation).to.have.property('signatures'); @@ -998,7 +998,7 @@ describe('Record', () => { expect(recordJson.dataCid).to.equal(recordsWrite.message.descriptor.dataCid); expect(recordJson.dataSize).to.equal(recordsWrite.message.descriptor.dataSize); expect(recordJson.dateCreated).to.equal(recordsWrite.message.descriptor.dateCreated); - expect(recordJson.messageTimestamp).to.equal(recordsWrite.message.descriptor.messageTimestamp); + expect(recordJson.dateModified).to.equal(recordsWrite.message.descriptor.dateModified); expect(recordJson.published).to.equal(published); expect(recordJson.datePublished).to.equal(recordsWrite.message.descriptor.datePublished); expect(recordJson.dataFormat).to.equal(dataFormat); diff --git a/packages/web5/tests/tech-preview.spec.ts b/packages/old/web5/tests/tech-preview.spec.ts similarity index 99% rename from packages/web5/tests/tech-preview.spec.ts rename to packages/old/web5/tests/tech-preview.spec.ts index ef9ef9ad8..404fd1ce3 100644 --- a/packages/web5/tests/tech-preview.spec.ts +++ b/packages/old/web5/tests/tech-preview.spec.ts @@ -7,8 +7,11 @@ import { Web5 } from '../src/web5.js'; chai.use(chaiUrl); describe('Tech Preview', () => { + describe('web5.getTechPreviewDwnEndpoints()', () => { + let fetchStub: sinon.SinonStub; + let mockDwnEndpoints: Array; let tbdWellKnownOkResponse = { diff --git a/packages/old/web5/tests/test-utils/chai-plugins.ts b/packages/old/web5/tests/test-utils/chai-plugins.ts new file mode 100644 index 000000000..5f0c9fc21 --- /dev/null +++ b/packages/old/web5/tests/test-utils/chai-plugins.ts @@ -0,0 +1,59 @@ +/** + * Chai plugin for validating URLs. + * + * This function adds two types of URL validation methods to Chai: + * 1. For the BDD "expect" API: `expect(string).to.be.a.url;` + * 2. For the Assert API: `assert.isUrl(string);` + * + * @param {Chai.ChaiStatic} chai - The Chai library object. + * @param {Chai.ChaiUtils} utils - The Chai Utilities object. + * + * @example + * // BDD API example: + * import chai, { expect } from 'chai'; + * import chaiUrl from './chai-plugins.js'; + * chai.use(chaiUrl); + * + * describe('My Test Suite', () => { + * it('should validate the URL', () => { + * const url = 'https://example.org'; + * expect(url).to.be.a.url; + * }); + * }); + * + * @example + * // Assert API example: + * import chai, { assert } from 'chai'; + * import chaiUrl from './chai-plugins.js'; + * chai.use(chaiUrl); + * + * describe('My Test Suite', () => { + * it('should validate the URL', () => { + * const url = 'https://example.org'; + * assert.isUrl(url); + * }); + * }); + */ +export const chaiUrl: Chai.ChaiPlugin = function(chai: Chai.ChaiStatic, utils: Chai.ChaiUtils) { + const assert = chai.assert; + function isValidUrl() { + const obj = utils.flag(this, 'object') as string; + let isUrl = true; + try { + new URL(obj); + } catch (err) { + isUrl = false; + } + this.assert( + isUrl, + 'expected #{this} to be a valid URL', + 'expected #{this} not to be a valid URL' + ); + } + + // Add the property to the BDD "expect" API. + utils.addProperty(chai.Assertion.prototype, 'url', isValidUrl); + + // Add the method to the Assert API. + assert.isUrl = (actual) => (new chai.Assertion(actual)).to.be.a.url; +}; \ No newline at end of file diff --git a/packages/web5/tests/test-utils/promises.ts b/packages/old/web5/tests/test-utils/promises.ts similarity index 100% rename from packages/web5/tests/test-utils/promises.ts rename to packages/old/web5/tests/test-utils/promises.ts diff --git a/packages/web5/tests/test-utils/test-data-generator.ts b/packages/old/web5/tests/test-utils/test-data-generator.ts similarity index 100% rename from packages/web5/tests/test-utils/test-data-generator.ts rename to packages/old/web5/tests/test-utils/test-data-generator.ts diff --git a/packages/web5/tests/test-utils/test-user-agent.ts b/packages/old/web5/tests/test-utils/test-user-agent.ts similarity index 96% rename from packages/web5/tests/test-utils/test-user-agent.ts rename to packages/old/web5/tests/test-utils/test-user-agent.ts index 4ded88897..5eb8d4b16 100644 --- a/packages/web5/tests/test-utils/test-user-agent.ts +++ b/packages/old/web5/tests/test-utils/test-user-agent.ts @@ -2,8 +2,7 @@ import type { DidIonCreateOptions } from '@tbd54566975/dids'; import { DidIonApi, DidKeyApi, DidResolver } from '@tbd54566975/dids'; import { Web5UserAgent, ProfileApi, ProfileStore } from '@tbd54566975/web5-user-agent'; -import { Dwn } from '@tbd54566975/dwn-sdk-js'; -import { DataStoreLevel, EventLogLevel, MessageStoreLevel } from '@tbd54566975/dwn-sdk-js/stores'; +import { Dwn, DataStoreLevel, EventLogLevel, MessageStoreLevel } from '@tbd54566975/dwn-sdk-js'; import { AppStorage } from '../../src/app-storage.js'; diff --git a/packages/web5/tests/web5-did.spec.ts b/packages/old/web5/tests/web5-did.spec.ts similarity index 100% rename from packages/web5/tests/web5-did.spec.ts rename to packages/old/web5/tests/web5-did.spec.ts diff --git a/packages/web5/tests/web5-dwn.spec.ts b/packages/old/web5/tests/web5-dwn.spec.ts similarity index 99% rename from packages/web5/tests/web5-dwn.spec.ts rename to packages/old/web5/tests/web5-dwn.spec.ts index bff8aee6b..6795c6718 100644 --- a/packages/web5/tests/web5-dwn.spec.ts +++ b/packages/old/web5/tests/web5-dwn.spec.ts @@ -30,7 +30,6 @@ describe('web5.dwn', () => { describe('protocols', () => { describe('configure', () => { - // eslint-disable-next-line mocha/no-exclusive-tests describe('agent', () => { it('writes a protocol definition', async () => { const response = await dwn.protocols.configure({ diff --git a/packages/web5/tests/web5-vc.spec.ts b/packages/old/web5/tests/web5-vc.spec.ts similarity index 100% rename from packages/web5/tests/web5-vc.spec.ts rename to packages/old/web5/tests/web5-vc.spec.ts diff --git a/packages/old/web5/tsconfig.json b/packages/old/web5/tsconfig.json new file mode 100644 index 000000000..5e66e05d4 --- /dev/null +++ b/packages/old/web5/tsconfig.json @@ -0,0 +1,28 @@ +{ + "compilerOptions": { + "strict": false, + "lib": [ + "DOM", + "ES6" + ], + "allowJs": true, + "target": "es6", + "module": "ESNext", // Required for enabling JavaScript import assertion support + "declaration": true, + "declarationMap": true, + "emitDeclarationOnly": true, + "declarationDir": "dist/types", + // `NodeNext` will throw compilation errors if relative import paths are missing file extension + // reference: https://devblogs.microsoft.com/typescript/announcing-typescript-4-7/#ecmascript-module-support-in-node-js + "moduleResolution": "NodeNext", + "esModuleInterop": true + }, + "include": [ + "./src/main.ts", + ], + "exclude": [ + "node_modules", + "dist", + "examples", + ] +} \ No newline at end of file diff --git a/packages/old/web5/tsconfig.test.json b/packages/old/web5/tsconfig.test.json new file mode 100644 index 000000000..7e40c3908 --- /dev/null +++ b/packages/old/web5/tsconfig.test.json @@ -0,0 +1,29 @@ +{ + "compilerOptions": { + "lib": [ + "DOM", + "ES6" + ], + "target": "es6", + "module": "ESNext", // Required for enabling JavaScript import assertion support + "declaration": true, + "declarationMap": true, + "outDir": "__tests__", + "declarationDir": "__tests__/types", + "sourceMap": true, + // `NodeNext` will throw compilation errors if relative import paths are missing file extension + // reference: https://devblogs.microsoft.com/typescript/announcing-typescript-4-7/#ecmascript-module-support-in-node-js + "moduleResolution": "NodeNext", + // allows us to import json files + "resolveJsonModule": true, + "esModuleInterop": true + }, + "include": [ + "src", + "tests", + ], + "exclude": [ + "node_modules", + "dist" + ] +} \ No newline at end of file diff --git a/packages/proxy-agent/.c8rc.json b/packages/proxy-agent/.c8rc.json new file mode 100644 index 000000000..ab680f663 --- /dev/null +++ b/packages/proxy-agent/.c8rc.json @@ -0,0 +1,19 @@ +{ + "all": true, + "cache": false, + "extension": [ + ".js" + ], + "include": [ + "tests/compiled/src/**" + ], + "exclude": [ + "tests/compiled/src/index.js", + "tests/compiled/src/types.js", + "tests/compiled/src/types/**" + ], + "reporter": [ + "cobertura", + "text" + ] +} \ No newline at end of file diff --git a/packages/web5-user-agent/.mocharc.json b/packages/proxy-agent/.mocharc.json similarity index 50% rename from packages/web5-user-agent/.mocharc.json rename to packages/proxy-agent/.mocharc.json index e999f4ae8..5aa8c5bfe 100644 --- a/packages/web5-user-agent/.mocharc.json +++ b/packages/proxy-agent/.mocharc.json @@ -1,5 +1,5 @@ { "enable-source-maps": true, "exit": true, - "spec": ["tests/compiled/tests/**/*.spec.js"] + "spec": ["tests/compiled/**/*.spec.js"] } \ No newline at end of file diff --git a/packages/web5-proxy-agent/.vscode/launch.json b/packages/proxy-agent/.vscode/launch.json similarity index 89% rename from packages/web5-proxy-agent/.vscode/launch.json rename to packages/proxy-agent/.vscode/launch.json index e2927b1de..bc398db79 100644 --- a/packages/web5-proxy-agent/.vscode/launch.json +++ b/packages/proxy-agent/.vscode/launch.json @@ -11,7 +11,9 @@ "runtimeExecutable": "${workspaceFolder:root}/node_modules/.bin/mocha", "console": "internalConsole", "preLaunchTask": "build tests", - "skipFiles": ["/**"] + "skipFiles": [ + "/**" + ] } ] } \ No newline at end of file diff --git a/packages/web5-user-agent/.vscode/tasks.json b/packages/proxy-agent/.vscode/tasks.json similarity index 92% rename from packages/web5-user-agent/.vscode/tasks.json rename to packages/proxy-agent/.vscode/tasks.json index 363a6c5c7..a11b32446 100644 --- a/packages/web5-user-agent/.vscode/tasks.json +++ b/packages/proxy-agent/.vscode/tasks.json @@ -31,7 +31,7 @@ "type": "npm", "script": "build:tests:node", "options": { - "cwd": "${workspaceFolder:web5-user-agent}" + "cwd": "${workspaceFolder:proxy-agent}" } } ] diff --git a/packages/web5-agent/build/bundles.js b/packages/proxy-agent/build/bundles.js similarity index 90% rename from packages/web5-agent/build/bundles.js rename to packages/proxy-agent/build/bundles.js index c9119e67b..3f09ffed6 100644 --- a/packages/web5-agent/build/bundles.js +++ b/packages/proxy-agent/build/bundles.js @@ -11,6 +11,6 @@ esbuild.build({ esbuild.build({ ...browserConfig, format : 'iife', - globalName : 'Web5', + globalName : 'Web5ProxyAgent', outfile : 'dist/browser.js', }); \ No newline at end of file diff --git a/packages/proxy-agent/build/esbuild-browser-config.cjs b/packages/proxy-agent/build/esbuild-browser-config.cjs new file mode 100644 index 000000000..bd8bd99b1 --- /dev/null +++ b/packages/proxy-agent/build/esbuild-browser-config.cjs @@ -0,0 +1,30 @@ +/* eslint-disable @typescript-eslint/no-var-requires */ +const polyfillProviderPlugin = require('node-stdlib-browser/helpers/esbuild/plugin'); +const stdLibBrowser = require('node-stdlib-browser'); + +const requiredPolyfills = new Set(['crypto', 'node:crypto', 'stream']); + +// populate object containing lib -> polyfill path +const polyfills = {}; +for (let lib in stdLibBrowser) { + if (requiredPolyfills.has(lib)) { + const polyfill = stdLibBrowser[lib]; + polyfills[lib] = polyfill; + } +} + +/** @type {import('esbuild').BuildOptions} */ +module.exports = { + entryPoints : ['./src/index.ts'], + bundle : true, + format : 'esm', + sourcemap : true, + minify : true, + platform : 'browser', + target : ['chrome101', 'firefox108', 'safari16'], + inject : [require.resolve('node-stdlib-browser/helpers/esbuild/shim')], + plugins : [polyfillProviderPlugin(polyfills)], + define : { + 'global': 'globalThis', + }, +}; \ No newline at end of file diff --git a/packages/proxy-agent/karma.conf.cjs b/packages/proxy-agent/karma.conf.cjs new file mode 100644 index 000000000..aa89cbca8 --- /dev/null +++ b/packages/proxy-agent/karma.conf.cjs @@ -0,0 +1,86 @@ +/* eslint-disable @typescript-eslint/no-var-requires */ +// Karma is what we're using to run our tests in browser environments +// Karma does not support .mjs + +// playwright acts as a safari executable on windows and mac +const playwright = require('@playwright/test'); +const esbuildBrowserConfig = require('./build/esbuild-browser-config.cjs'); + +// use playwright chrome exec path as run target for chromium tests +process.env.CHROME_BIN = playwright.chromium.executablePath(); + +// use playwright webkit exec path as run target for safari tests +process.env.WEBKIT_HEADLESS_BIN = playwright.webkit.executablePath(); + +// use playwright firefox exec path as run target for firefox tests +process.env.FIREFOX_BIN = playwright.firefox.executablePath(); + +module.exports = function (config) { + config.set({ + plugins: [ + 'karma-chrome-launcher', + 'karma-firefox-launcher', + 'karma-webkit-launcher', + 'karma-esbuild', + 'karma-mocha', + 'karma-mocha-reporter', + ], + + // frameworks to use + // available frameworks: https://www.npmjs.com/search?q=keywords:karma-adapter + frameworks: ['mocha'], + + // Increase Mocha's default timeout of 2 seconds to prevent timeouts during GitHub CI runs. + client: { + mocha: { + timeout: 10000 // 10 seconds + } + }, + + + // list of files / patterns to load in the browser + files: [ + { pattern: 'tests/**/*.spec.ts', watched: false }, + ], + + // preprocess matching files before serving them to the browser + // available preprocessors: https://www.npmjs.com/search?q=keywords:karma-preprocessor + preprocessors: { + 'tests/**/*.spec.ts': ['esbuild'], + }, + + esbuild: esbuildBrowserConfig, + + // list of files / patterns to exclude + exclude: [], + + // test results reporter to use + // available reporters: https://www.npmjs.com/search?q=keywords:karma-reporter + reporters: ['mocha'], + + // web server port + port: 9876, + + // enable / disable colors in the output (reporters and logs) + colors: true, + + // level of logging + // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || + // config.LOG_INFO || config.LOG_DEBUG + logLevel: config.LOG_INFO, + + concurrency: 1, + + // start these browsers + // available browser launchers: https://www.npmjs.com/search?q=keywords:karma-launcher + browsers: ['ChromeHeadless', 'FirefoxHeadless', 'WebkitHeadless'], + + // Continuous Integration mode + // if true, Karma captures browsers, runs the tests and exits + singleRun: true, + + // Increase browser timeouts to avoid DISCONNECTED messages during GitHub CI runs. + browserDisconnectTimeout : 10000, // default 2000 + browserDisconnectTolerance : 1, // default 0 + }); +}; diff --git a/packages/web5-proxy-agent/package.json b/packages/proxy-agent/package.json similarity index 79% rename from packages/web5-proxy-agent/package.json rename to packages/proxy-agent/package.json index 771483a41..afebc29e1 100644 --- a/packages/web5-proxy-agent/package.json +++ b/packages/proxy-agent/package.json @@ -1,11 +1,10 @@ { - "name": "@tbd54566975/web5-proxy-agent", - "version": "0.8.0", - "description": "Web5 Proxy Agent", + "name": "@web5/proxy-agent", + "version": "0.1.10", "type": "module", - "main": "./dist/cjs/main.js", - "module": "./dist/esm/main.js", - "types": "./dist/types/main.d.ts", + "main": "./dist/cjs/index.js", + "module": "./dist/esm/index.js", + "types": "./dist/types/index.d.ts", "scripts": { "clean": "rimraf dist coverage tests/compiled", "build:esm": "rimraf dist/esm dist/types && npx tsc -p tsconfig.json", @@ -18,12 +17,12 @@ "test:node": "npm run build:tests:node && c8 mocha", "test:browser": "karma start karma.conf.cjs" }, - "homepage": "https://github.com/TBD54566975/web5-js/tree/main/packages/web5-proxy-agent#readme", + "homepage": "https://github.com/TBD54566975/web5-js/tree/main/packages/proxy-agent#readme", "bugs": "https://github.com/TBD54566975/web5-js/issues", "repository": { "type": "git", "url": "git+https://github.com/TBD54566975/web5-js", - "directory": "packages/web5-proxy-agent" + "directory": "packages/proxy-agent" }, "license": "Apache-2.0", "contributors": [ @@ -46,17 +45,19 @@ ], "exports": { ".": { - "import": "./dist/esm/main.js", - "require": "./dist/cjs/main.js", - "types": "./dist/types/main.d.ts" + "import": "./dist/esm/index.js", + "require": "./dist/cjs/index.js", + "types": "./dist/types/index.d.ts" } }, - "react-native": "./dist/esm/main.js", + "react-native": "./dist/esm/index.js", "keywords": [ "decentralized", "decentralized-applications", "decentralized-identity", "decentralized-web", + "vcs", + "verifiable credentials", "web5" ], "publishConfig": { @@ -66,15 +67,17 @@ "node": ">=18.0.0" }, "dependencies": { - "@tbd54566975/web5-agent": "0.8.0" + "@web5/agent": "0.1.7", + "@web5/common": "0.1.1", + "@web5/crypto": "0.1.6", + "@web5/dids": "0.1.9" }, "devDependencies": { + "@playwright/test": "1.36.2", "@types/chai": "4.3.0", "@types/chai-as-promised": "7.1.5", - "@types/ed2curve": "0.2.2", "@types/eslint": "8.37.0", "@types/mocha": "10.0.1", - "@types/sinon": "10.0.15", "@typescript-eslint/eslint-plugin": "5.59.0", "@typescript-eslint/parser": "5.59.0", "c8": "8.0.0", @@ -92,10 +95,9 @@ "karma-mocha-reporter": "2.2.5", "karma-webkit-launcher": "2.1.0", "mocha": "10.2.0", + "node-stdlib-browser": "1.2.0", "playwright": "1.36.2", "rimraf": "4.4.0", - "sinon": "15.0.2", - "source-map-loader": "4.0.1", "typescript": "5.1.6" } } diff --git a/packages/proxy-agent/src/index.ts b/packages/proxy-agent/src/index.ts new file mode 100644 index 000000000..93ba1a294 --- /dev/null +++ b/packages/proxy-agent/src/index.ts @@ -0,0 +1 @@ +export * from './proxy-agent.js'; \ No newline at end of file diff --git a/packages/proxy-agent/src/proxy-agent.ts b/packages/proxy-agent/src/proxy-agent.ts new file mode 100644 index 000000000..823c2af48 --- /dev/null +++ b/packages/proxy-agent/src/proxy-agent.ts @@ -0,0 +1,240 @@ +import type { + DwnRpc, + VcResponse, + DidResponse, + DwnResponse, + AppDataStore, + SendVcRequest, + SendDidRequest, + SendDwnRequest, + ProcessVcRequest, + Web5ManagedAgent, + ProcessDwnRequest, + ProcessDidRequest, +} from '@web5/agent'; + +import { LevelStore } from '@web5/common'; +import { EdDsaAlgorithm } from '@web5/crypto'; +import { DidKeyMethod, DidResolver } from '@web5/dids'; +import { + LocalKms, + DidManager, + DwnManager, + KeyManager, + DidStoreDwn, + KeyStoreDwn, + AppDataVault, + Web5RpcClient, + IdentityManager, + IdentityStoreDwn, + PrivateKeyStoreDwn, + cryptoToPortableKeyPair, +} from '@web5/agent'; + +export type Web5ProxyAgentOptions = { + agentDid: string; + appData: AppDataStore; + didManager: DidManager; + didResolver: DidResolver; + dwnManager: DwnManager; + identityManager: IdentityManager; + keyManager: KeyManager; + rpcClient: DwnRpc; +} + +export class Web5ProxyAgent implements Web5ManagedAgent { + agentDid: string; + appData: AppDataStore; + didManager: DidManager; + didResolver: DidResolver; + dwnManager: DwnManager; + identityManager: IdentityManager; + keyManager: KeyManager; + rpcClient: DwnRpc; + + constructor(options: Web5ProxyAgentOptions) { + this.agentDid = options.agentDid; + this.appData = options.appData; + this.keyManager = options.keyManager; + this.didManager = options.didManager; + this.didResolver = options.didResolver; + this.dwnManager = options.dwnManager; + this.identityManager = options.identityManager; + this.rpcClient = options.rpcClient; + + // Set this agent to be the default agent. + this.didManager.agent = this; + this.dwnManager.agent = this; + this.identityManager.agent = this; + this.keyManager.agent = this; + } + + static async create(options: Partial = {}): Promise { + let { agentDid, appData, didManager, didResolver, dwnManager, identityManager, keyManager, rpcClient } = options; + + if (agentDid === undefined) { + // An Agent DID was not specified, so set to empty string. + agentDid = ''; + } + + if (appData === undefined) { + // A custom AppDataStore implementation was not specified, so + // instantiate a LevelDB backed secure AppDataVault. + appData = new AppDataVault({ + store: new LevelStore('data/agent/vault') + }); + } + + if (didManager === undefined) { + // A custom DidManager implementation was not specified, so + // instantiate a default with in-memory store. + didManager = new DidManager({ + didMethods : [DidKeyMethod], + store : new DidStoreDwn() + }); + } + + if (didResolver === undefined) { + // A custom DidManager implementation was not specified, so + // instantiate a default with in-memory store. + didResolver = new DidResolver({ didResolvers: [DidKeyMethod] }); + } + + if (dwnManager === undefined) { + // A custom DwnManager implementation was not specified, so + // instantiate a default. + dwnManager = await DwnManager.create({ didResolver }); + } + + if (identityManager === undefined) { + // A custom IdentityManager implementation was not specified, so + // instantiate a default that uses a DWN store. + identityManager = new IdentityManager({ + store: new IdentityStoreDwn() + }); + } + + if (keyManager === undefined) { + // A custom KeyManager implementation was not specified, so + // instantiate a default with KMSs. + const localKmsDwn = new LocalKms({ + kmsName : 'local', + keyStore : new KeyStoreDwn({ schema: 'https://identity.foundation/schemas/web5/kms-key' }), + privateKeyStore : new PrivateKeyStoreDwn() + }); + const localKmsMemory = new LocalKms({ + kmsName: 'memory' + }); + keyManager = new KeyManager({ + kms: { + local : localKmsDwn, + memory : localKmsMemory + }, + store: new KeyStoreDwn({ schema: 'https://identity.foundation/schemas/web5/managed-key' }) + }); + } + + if (rpcClient === undefined) { + // A custom RPC Client implementation was not specified, so + // instantiate a default. + rpcClient = new Web5RpcClient(); + } + + // Instantiate the Identity Agent. + const agent = new Web5ProxyAgent({ + agentDid, + appData, + didManager, + didResolver, + dwnManager, + keyManager, + identityManager, + rpcClient + }); + + return agent; + } + + async firstLaunch(): Promise { + // Check whether data vault is already initialized. + const { initialized } = await this.appData.getStatus(); + return initialized === false; + } + + /** + * Executed once the first time the Identity Agent is launched. + * The passphrase should be input by the end-user. + * */ + async initialize(options: { passphrase: string }) { + const { passphrase } = options; + + // Generate an Ed25519 key pair for the Identity Agent. + const agentKeyPair = await new EdDsaAlgorithm().generateKey({ + algorithm : { name: 'EdDSA', namedCurve: 'Ed25519' }, + extractable : true, + keyUsages : ['sign', 'verify'] + }); + + /** Initialize the AppDataStore with the Identity Agent's + * private key and passphrase, which also unlocks the data vault. */ + await this.appData.initialize({ + passphrase : passphrase, + keyPair : agentKeyPair, + }); + } + + async processDidRequest(_request: ProcessDidRequest): Promise { + throw new Error('Not implemented'); + } + + async processDwnRequest(request: ProcessDwnRequest): Promise { + return this.dwnManager.processRequest(request); + } + + async processVcRequest(_request: ProcessVcRequest): Promise { + throw new Error('Not implemented'); + } + + async sendDidRequest(_request: SendDidRequest): Promise { + throw new Error('Not implemented'); + } + + async sendDwnRequest(request: SendDwnRequest): Promise { + return this.dwnManager.sendRequest(request); + } + + async sendVcRequest(_request: SendVcRequest): Promise { + throw new Error('Not implemented'); + } + + async start(options: { passphrase: string }) { + const { passphrase } = options; + + if (await this.firstLaunch()) { + // 1A. Agent's first launch so initialize. + await this.initialize({ passphrase }); + } else { + // 1B. Agent was previously initialized. + // Unlock the data vault and cache the vault unlock key (VUK) in memory. + await this.appData.unlock({ passphrase }); + } + + // 2. Set the Identity Agent's root did:key identifier. + this.agentDid = await this.appData.getDid(); + + // 3. Import the Identity Agent's private key into the KeyManager. + const defaultSigningKey = cryptoToPortableKeyPair({ + cryptoKeyPair: { + privateKey : await this.appData.getPrivateKey(), + publicKey : await this.appData.getPublicKey() + }, + keyData: { + alias : await this.didManager.getDefaultSigningKey({ did: this.agentDid }), + kms : 'memory' + } + }); + + // Import the Agent's signing key pair to the in-memory KMS key stores. + await this.keyManager.setDefaultSigningKey({ key: defaultSigningKey }); + } +} \ No newline at end of file diff --git a/packages/proxy-agent/tests/proxy-agent.spec.ts b/packages/proxy-agent/tests/proxy-agent.spec.ts new file mode 100644 index 000000000..01cdc2810 --- /dev/null +++ b/packages/proxy-agent/tests/proxy-agent.spec.ts @@ -0,0 +1,87 @@ +import chai, { expect } from 'chai'; +import chaiAsPromised from 'chai-as-promised'; +import { TestManagedAgent } from '@web5/agent'; + +import { Web5ProxyAgent } from '../src/proxy-agent.js'; + +chai.use(chaiAsPromised); + +// NOTE: @noble/secp256k1 requires globalThis.crypto polyfill for node.js <=18: https://github.com/paulmillr/noble-secp256k1/blob/main/README.md#usage +// Remove when we move off of node.js v18 to v20, earliest possible time would be Oct 2023: https://github.com/nodejs/release#release-schedule +// NOTE: @noble/secp256k1 requires globalThis.crypto polyfill for node.js <=18: https://github.com/paulmillr/noble-secp256k1/blob/main/README.md#usage +// Remove when we move off of node.js v18 to v20, earliest possible time would be Oct 2023: https://github.com/nodejs/release#release-schedule +import { webcrypto } from 'node:crypto'; +// @ts-ignore +if (!globalThis.crypto) globalThis.crypto = webcrypto; + +describe('Web5ProxyAgent', () => { + + const agentStoreTypes = ['dwn', 'memory'] as const; + agentStoreTypes.forEach((agentStoreType) => { + + describe(`with ${agentStoreType} data stores`, () => { + let testAgent: TestManagedAgent; + + before(async () => { + testAgent = await TestManagedAgent.create({ + agentClass : Web5ProxyAgent, + agentStores : agentStoreType + }); + }); + + beforeEach(async () => { + await testAgent.clearStorage(); + }); + + after(async () => { + await testAgent.clearStorage(); + await testAgent.closeStorage(); + }); + + describe('firstLaunch()', () => { + it('returns true the first time the Identity Agent runs', async () => { + await expect(testAgent.agent.firstLaunch()).to.eventually.be.true; + }); + + it('returns false after Identity Agent initialization', async () => { + await expect(testAgent.agent.firstLaunch()).to.eventually.be.true; + + await testAgent.agent.initialize({ passphrase: 'test' }); + await expect(testAgent.agent.firstLaunch()).to.eventually.be.false; + }); + }); + + if (agentStoreType === 'dwn') { + describe('subsequent launches', () => { + it('can access stored identifiers after second launch', async () => { + // First launch and initialization. + await testAgent.agent.start({ passphrase: 'test' }); + + // Create and persist a new Identity (with DID and Keys). + const socialIdentity = await testAgent.agent.identityManager.create({ + name : 'Social', + didMethod : 'key', + kms : 'local' + }); + + // Simulate terminating and restarting an app. + testAgent.closeStorage(); + testAgent = await TestManagedAgent.create({ + agentClass : Web5ProxyAgent, + agentStores : 'dwn' + }); + await testAgent.agent.start({ passphrase: 'test' }); + + // Try to get the identity and verify it exists. + const storedIdentity = await testAgent.agent.identityManager.get({ + did : socialIdentity.did, + context : socialIdentity.did + }); + expect(storedIdentity).to.have.property('did', socialIdentity.did); + }); + }); + } + }); + }); + +}); diff --git a/packages/web5/tests/tsconfig.json b/packages/proxy-agent/tests/tsconfig.json similarity index 100% rename from packages/web5/tests/tsconfig.json rename to packages/proxy-agent/tests/tsconfig.json diff --git a/packages/web5-proxy-agent/tsconfig.cjs.json b/packages/proxy-agent/tsconfig.cjs.json similarity index 100% rename from packages/web5-proxy-agent/tsconfig.cjs.json rename to packages/proxy-agent/tsconfig.cjs.json diff --git a/packages/web5-proxy-agent/tsconfig.json b/packages/proxy-agent/tsconfig.json similarity index 91% rename from packages/web5-proxy-agent/tsconfig.json rename to packages/proxy-agent/tsconfig.json index c8ce1cc3d..fea2cf4f1 100644 --- a/packages/web5-proxy-agent/tsconfig.json +++ b/packages/proxy-agent/tsconfig.json @@ -1,5 +1,6 @@ { "compilerOptions": { + "strict": true, "lib": [ "DOM", "ES6" @@ -10,6 +11,7 @@ "declarationMap": true, "declarationDir": "dist/types", "outDir": "dist/esm", + "sourceMap": true, // `NodeNext` will throw compilation errors if relative import paths are missing file extension // reference: https://devblogs.microsoft.com/typescript/announcing-typescript-4-7/#ecmascript-module-support-in-node-js "moduleResolution": "NodeNext", @@ -17,10 +19,8 @@ }, "include": [ "src", - "typings" ], "exclude": [ - "node_modules", - "dist" + "node_modules" ] } \ No newline at end of file diff --git a/packages/user-agent/.c8rc.json b/packages/user-agent/.c8rc.json new file mode 100644 index 000000000..ab680f663 --- /dev/null +++ b/packages/user-agent/.c8rc.json @@ -0,0 +1,19 @@ +{ + "all": true, + "cache": false, + "extension": [ + ".js" + ], + "include": [ + "tests/compiled/src/**" + ], + "exclude": [ + "tests/compiled/src/index.js", + "tests/compiled/src/types.js", + "tests/compiled/src/types/**" + ], + "reporter": [ + "cobertura", + "text" + ] +} \ No newline at end of file diff --git a/packages/user-agent/.mocharc.json b/packages/user-agent/.mocharc.json new file mode 100644 index 000000000..5aa8c5bfe --- /dev/null +++ b/packages/user-agent/.mocharc.json @@ -0,0 +1,5 @@ +{ + "enable-source-maps": true, + "exit": true, + "spec": ["tests/compiled/**/*.spec.js"] +} \ No newline at end of file diff --git a/packages/user-agent/.vscode/launch.json b/packages/user-agent/.vscode/launch.json new file mode 100644 index 000000000..bc398db79 --- /dev/null +++ b/packages/user-agent/.vscode/launch.json @@ -0,0 +1,19 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "type": "node", + "request": "launch", + "name": "test:node", + "runtimeExecutable": "${workspaceFolder:root}/node_modules/.bin/mocha", + "console": "internalConsole", + "preLaunchTask": "build tests", + "skipFiles": [ + "/**" + ] + } + ] +} \ No newline at end of file diff --git a/packages/web5-proxy-agent/.vscode/tasks.json b/packages/user-agent/.vscode/tasks.json similarity index 92% rename from packages/web5-proxy-agent/.vscode/tasks.json rename to packages/user-agent/.vscode/tasks.json index 4acdcfd8d..8c5147274 100644 --- a/packages/web5-proxy-agent/.vscode/tasks.json +++ b/packages/user-agent/.vscode/tasks.json @@ -31,7 +31,7 @@ "type": "npm", "script": "build:tests:node", "options": { - "cwd": "${workspaceFolder:web5-proxy-agent}" + "cwd": "${workspaceFolder:user-agent}" } } ] diff --git a/packages/web5-proxy-agent/build/bundles.js b/packages/user-agent/build/bundles.js similarity index 91% rename from packages/web5-proxy-agent/build/bundles.js rename to packages/user-agent/build/bundles.js index c9119e67b..c77302f82 100644 --- a/packages/web5-proxy-agent/build/bundles.js +++ b/packages/user-agent/build/bundles.js @@ -11,6 +11,6 @@ esbuild.build({ esbuild.build({ ...browserConfig, format : 'iife', - globalName : 'Web5', + globalName : 'Web5UserAgent', outfile : 'dist/browser.js', }); \ No newline at end of file diff --git a/packages/user-agent/build/esbuild-browser-config.cjs b/packages/user-agent/build/esbuild-browser-config.cjs new file mode 100644 index 000000000..bd8bd99b1 --- /dev/null +++ b/packages/user-agent/build/esbuild-browser-config.cjs @@ -0,0 +1,30 @@ +/* eslint-disable @typescript-eslint/no-var-requires */ +const polyfillProviderPlugin = require('node-stdlib-browser/helpers/esbuild/plugin'); +const stdLibBrowser = require('node-stdlib-browser'); + +const requiredPolyfills = new Set(['crypto', 'node:crypto', 'stream']); + +// populate object containing lib -> polyfill path +const polyfills = {}; +for (let lib in stdLibBrowser) { + if (requiredPolyfills.has(lib)) { + const polyfill = stdLibBrowser[lib]; + polyfills[lib] = polyfill; + } +} + +/** @type {import('esbuild').BuildOptions} */ +module.exports = { + entryPoints : ['./src/index.ts'], + bundle : true, + format : 'esm', + sourcemap : true, + minify : true, + platform : 'browser', + target : ['chrome101', 'firefox108', 'safari16'], + inject : [require.resolve('node-stdlib-browser/helpers/esbuild/shim')], + plugins : [polyfillProviderPlugin(polyfills)], + define : { + 'global': 'globalThis', + }, +}; \ No newline at end of file diff --git a/packages/user-agent/karma.conf.cjs b/packages/user-agent/karma.conf.cjs new file mode 100644 index 000000000..aa89cbca8 --- /dev/null +++ b/packages/user-agent/karma.conf.cjs @@ -0,0 +1,86 @@ +/* eslint-disable @typescript-eslint/no-var-requires */ +// Karma is what we're using to run our tests in browser environments +// Karma does not support .mjs + +// playwright acts as a safari executable on windows and mac +const playwright = require('@playwright/test'); +const esbuildBrowserConfig = require('./build/esbuild-browser-config.cjs'); + +// use playwright chrome exec path as run target for chromium tests +process.env.CHROME_BIN = playwright.chromium.executablePath(); + +// use playwright webkit exec path as run target for safari tests +process.env.WEBKIT_HEADLESS_BIN = playwright.webkit.executablePath(); + +// use playwright firefox exec path as run target for firefox tests +process.env.FIREFOX_BIN = playwright.firefox.executablePath(); + +module.exports = function (config) { + config.set({ + plugins: [ + 'karma-chrome-launcher', + 'karma-firefox-launcher', + 'karma-webkit-launcher', + 'karma-esbuild', + 'karma-mocha', + 'karma-mocha-reporter', + ], + + // frameworks to use + // available frameworks: https://www.npmjs.com/search?q=keywords:karma-adapter + frameworks: ['mocha'], + + // Increase Mocha's default timeout of 2 seconds to prevent timeouts during GitHub CI runs. + client: { + mocha: { + timeout: 10000 // 10 seconds + } + }, + + + // list of files / patterns to load in the browser + files: [ + { pattern: 'tests/**/*.spec.ts', watched: false }, + ], + + // preprocess matching files before serving them to the browser + // available preprocessors: https://www.npmjs.com/search?q=keywords:karma-preprocessor + preprocessors: { + 'tests/**/*.spec.ts': ['esbuild'], + }, + + esbuild: esbuildBrowserConfig, + + // list of files / patterns to exclude + exclude: [], + + // test results reporter to use + // available reporters: https://www.npmjs.com/search?q=keywords:karma-reporter + reporters: ['mocha'], + + // web server port + port: 9876, + + // enable / disable colors in the output (reporters and logs) + colors: true, + + // level of logging + // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || + // config.LOG_INFO || config.LOG_DEBUG + logLevel: config.LOG_INFO, + + concurrency: 1, + + // start these browsers + // available browser launchers: https://www.npmjs.com/search?q=keywords:karma-launcher + browsers: ['ChromeHeadless', 'FirefoxHeadless', 'WebkitHeadless'], + + // Continuous Integration mode + // if true, Karma captures browsers, runs the tests and exits + singleRun: true, + + // Increase browser timeouts to avoid DISCONNECTED messages during GitHub CI runs. + browserDisconnectTimeout : 10000, // default 2000 + browserDisconnectTolerance : 1, // default 0 + }); +}; diff --git a/packages/user-agent/package.json b/packages/user-agent/package.json new file mode 100644 index 000000000..29d6c197b --- /dev/null +++ b/packages/user-agent/package.json @@ -0,0 +1,103 @@ +{ + "name": "@web5/user-agent", + "version": "0.1.10", + "type": "module", + "main": "./dist/cjs/index.js", + "module": "./dist/esm/index.js", + "types": "./dist/types/index.d.ts", + "scripts": { + "clean": "rimraf dist tests/compiled", + "build:esm": "rimraf dist/esm dist/types && npx tsc -p tsconfig.json", + "build:cjs": "rimraf dist/cjs && tsc -p tsconfig.cjs.json && echo '{\"type\": \"commonjs\"}' > ./dist/cjs/package.json", + "build:browser": "rimraf dist/browser.mjs dist/browser.js && node build/bundles.js", + "build:tests:node": "rimraf tests/compiled && tsc -p tests/tsconfig.json", + "build": "npm run clean && npm run build:esm && npm run build:cjs && npm run build:browser", + "lint": "eslint . --ext .ts --max-warnings 0", + "lint:fix": "eslint . --ext .ts --fix", + "test:node": "npm run build:tests:node && c8 mocha", + "test:browser": "karma start karma.conf.cjs" + }, + "homepage": "https://github.com/TBD54566975/web5-js/tree/main/packages/user-agent#readme", + "bugs": "https://github.com/TBD54566975/web5-js/issues", + "repository": { + "type": "git", + "url": "git+https://github.com/TBD54566975/web5-js", + "directory": "packages/user-agent" + }, + "license": "Apache-2.0", + "contributors": [ + { + "name": "Daniel Buchner", + "url": "https://github.com/csuwildcat" + }, + { + "name": "Frank Hinek", + "url": "https://github.com/frankhinek" + }, + { + "name": "Moe Jangda", + "url": "https://github.com/mistermoe" + } + ], + "files": [ + "dist", + "src" + ], + "exports": { + ".": { + "import": "./dist/esm/index.js", + "require": "./dist/cjs/index.js", + "types": "./dist/types/index.d.ts" + } + }, + "react-native": "./dist/esm/index.js", + "keywords": [ + "decentralized", + "decentralized-applications", + "decentralized-identity", + "decentralized-web", + "vcs", + "verifiable credentials", + "web5" + ], + "publishConfig": { + "access": "public" + }, + "engines": { + "node": ">=18.0.0" + }, + "dependencies": { + "@web5/agent": "0.1.7", + "@web5/common": "0.1.1", + "@web5/crypto": "0.1.6", + "@web5/dids": "0.1.9" + }, + "devDependencies": { + "@playwright/test": "1.36.2", + "@types/chai": "4.3.0", + "@types/chai-as-promised": "7.1.5", + "@types/eslint": "8.37.0", + "@types/mocha": "10.0.1", + "@typescript-eslint/eslint-plugin": "5.59.0", + "@typescript-eslint/parser": "5.59.0", + "c8": "8.0.0", + "chai": "4.3.7", + "chai-as-promised": "7.1.1", + "esbuild": "0.16.17", + "eslint": "8.39.0", + "eslint-plugin-mocha": "10.1.0", + "karma": "6.4.1", + "karma-chai": "0.1.0", + "karma-chrome-launcher": "3.1.1", + "karma-esbuild": "2.2.5", + "karma-firefox-launcher": "2.1.2", + "karma-mocha": "2.0.1", + "karma-mocha-reporter": "2.2.5", + "karma-webkit-launcher": "2.1.0", + "mocha": "10.2.0", + "node-stdlib-browser": "1.2.0", + "playwright": "1.36.2", + "rimraf": "4.4.0", + "typescript": "5.1.6" + } +} diff --git a/packages/user-agent/src/index.ts b/packages/user-agent/src/index.ts new file mode 100644 index 000000000..278c66aaf --- /dev/null +++ b/packages/user-agent/src/index.ts @@ -0,0 +1 @@ +export * from './user-agent.js'; \ No newline at end of file diff --git a/packages/user-agent/src/user-agent.ts b/packages/user-agent/src/user-agent.ts new file mode 100644 index 000000000..4503d9243 --- /dev/null +++ b/packages/user-agent/src/user-agent.ts @@ -0,0 +1,240 @@ +import type { + DwnRpc, + VcResponse, + DidResponse, + DwnResponse, + AppDataStore, + SendVcRequest, + SendDidRequest, + SendDwnRequest, + ProcessVcRequest, + Web5ManagedAgent, + ProcessDwnRequest, + ProcessDidRequest, +} from '@web5/agent'; + +import { LevelStore } from '@web5/common'; +import { EdDsaAlgorithm } from '@web5/crypto'; +import { DidKeyMethod, DidResolver } from '@web5/dids'; +import { + LocalKms, + DidManager, + DwnManager, + KeyManager, + DidStoreDwn, + KeyStoreDwn, + AppDataVault, + Web5RpcClient, + IdentityManager, + IdentityStoreDwn, + PrivateKeyStoreDwn, + cryptoToPortableKeyPair, +} from '@web5/agent'; + +export type Web5UserAgentOptions = { + agentDid: string; + appData: AppDataStore; + didManager: DidManager; + didResolver: DidResolver; + dwnManager: DwnManager; + identityManager: IdentityManager; + keyManager: KeyManager; + rpcClient: DwnRpc; +} + +export class Web5UserAgent implements Web5ManagedAgent { + agentDid: string; + appData: AppDataStore; + didManager: DidManager; + didResolver: DidResolver; + dwnManager: DwnManager; + identityManager: IdentityManager; + keyManager: KeyManager; + rpcClient: DwnRpc; + + constructor(options: Web5UserAgentOptions) { + this.agentDid = options.agentDid; + this.appData = options.appData; + this.keyManager = options.keyManager; + this.didManager = options.didManager; + this.didResolver = options.didResolver; + this.dwnManager = options.dwnManager; + this.identityManager = options.identityManager; + this.rpcClient = options.rpcClient; + + // Set this agent to be the default agent. + this.didManager.agent = this; + this.dwnManager.agent = this; + this.identityManager.agent = this; + this.keyManager.agent = this; + } + + static async create(options: Partial = {}): Promise { + let { agentDid, appData, didManager, didResolver, dwnManager, identityManager, keyManager, rpcClient } = options; + + if (agentDid === undefined) { + // An Agent DID was not specified, so set to empty string. + agentDid = ''; + } + + if (appData === undefined) { + // A custom AppDataStore implementation was not specified, so + // instantiate a LevelDB backed secure AppDataVault. + appData = new AppDataVault({ + store: new LevelStore('data/agent/vault') + }); + } + + if (didManager === undefined) { + // A custom DidManager implementation was not specified, so + // instantiate a default with in-memory store. + didManager = new DidManager({ + didMethods : [DidKeyMethod], + store : new DidStoreDwn() + }); + } + + if (didResolver === undefined) { + // A custom DidManager implementation was not specified, so + // instantiate a default with in-memory store. + didResolver = new DidResolver({ didResolvers: [DidKeyMethod] }); + } + + if (dwnManager === undefined) { + // A custom DwnManager implementation was not specified, so + // instantiate a default. + dwnManager = await DwnManager.create({ didResolver }); + } + + if (identityManager === undefined) { + // A custom IdentityManager implementation was not specified, so + // instantiate a default that uses a DWN store. + identityManager = new IdentityManager({ + store: new IdentityStoreDwn() + }); + } + + if (keyManager === undefined) { + // A custom KeyManager implementation was not specified, so + // instantiate a default with KMSs. + const localKmsDwn = new LocalKms({ + kmsName : 'local', + keyStore : new KeyStoreDwn({ schema: 'https://identity.foundation/schemas/web5/kms-key' }), + privateKeyStore : new PrivateKeyStoreDwn() + }); + const localKmsMemory = new LocalKms({ + kmsName: 'memory' + }); + keyManager = new KeyManager({ + kms: { + local : localKmsDwn, + memory : localKmsMemory + }, + store: new KeyStoreDwn({ schema: 'https://identity.foundation/schemas/web5/managed-key' }) + }); + } + + if (rpcClient === undefined) { + // A custom RPC Client implementation was not specified, so + // instantiate a default. + rpcClient = new Web5RpcClient(); + } + + // Instantiate the Identity Agent. + const agent = new Web5UserAgent({ + agentDid, + appData, + didManager, + didResolver, + dwnManager, + keyManager, + identityManager, + rpcClient + }); + + return agent; + } + + async firstLaunch(): Promise { + // Check whether data vault is already initialized. + const { initialized } = await this.appData.getStatus(); + return initialized === false; + } + + /** + * Executed once the first time the Identity Agent is launched. + * The passphrase should be input by the end-user. + * */ + async initialize(options: { passphrase: string }) { + const { passphrase } = options; + + // Generate an Ed25519 key pair for the Identity Agent. + const agentKeyPair = await new EdDsaAlgorithm().generateKey({ + algorithm : { name: 'EdDSA', namedCurve: 'Ed25519' }, + extractable : true, + keyUsages : ['sign', 'verify'] + }); + + /** Initialize the AppDataStore with the Identity Agent's + * private key and passphrase, which also unlocks the data vault. */ + await this.appData.initialize({ + passphrase : passphrase, + keyPair : agentKeyPair, + }); + } + + async processDidRequest(_request: ProcessDidRequest): Promise { + throw new Error('Not implemented'); + } + + async processDwnRequest(request: ProcessDwnRequest): Promise { + return this.dwnManager.processRequest(request); + } + + async processVcRequest(_request: ProcessVcRequest): Promise { + throw new Error('Not implemented'); + } + + async sendDidRequest(_request: SendDidRequest): Promise { + throw new Error('Not implemented'); + } + + async sendDwnRequest(request: SendDwnRequest): Promise { + return this.dwnManager.sendRequest(request); + } + + async sendVcRequest(_request: SendVcRequest): Promise { + throw new Error('Not implemented'); + } + + async start(options: { passphrase: string }) { + const { passphrase } = options; + + if (await this.firstLaunch()) { + // 1A. Agent's first launch so initialize. + await this.initialize({ passphrase }); + } else { + // 1B. Agent was previously initialized. + // Unlock the data vault and cache the vault unlock key (VUK) in memory. + await this.appData.unlock({ passphrase }); + } + + // 2. Set the Identity Agent's root did:key identifier. + this.agentDid = await this.appData.getDid(); + + // 3. Import the Identity Agent's private key into the KeyManager. + const defaultSigningKey = cryptoToPortableKeyPair({ + cryptoKeyPair: { + privateKey : await this.appData.getPrivateKey(), + publicKey : await this.appData.getPublicKey() + }, + keyData: { + alias : await this.didManager.getDefaultSigningKey({ did: this.agentDid }), + kms : 'memory' + } + }); + + // Import the Agent's signing key pair to the in-memory KMS key stores. + await this.keyManager.setDefaultSigningKey({ key: defaultSigningKey }); + } +} \ No newline at end of file diff --git a/packages/user-agent/tests/tsconfig.json b/packages/user-agent/tests/tsconfig.json new file mode 100644 index 000000000..7c6d2c8e7 --- /dev/null +++ b/packages/user-agent/tests/tsconfig.json @@ -0,0 +1,15 @@ +{ + "extends": "../tsconfig.json", + "compilerOptions": { + "outDir": "compiled", + "declarationDir": "compiled/types", + "sourceMap": true, + }, + "include": [ + "../src", + ".", + ], + "exclude": [ + "./compiled" + ] +} \ No newline at end of file diff --git a/packages/user-agent/tests/user-agent.spec.ts b/packages/user-agent/tests/user-agent.spec.ts new file mode 100644 index 000000000..320f10d85 --- /dev/null +++ b/packages/user-agent/tests/user-agent.spec.ts @@ -0,0 +1,87 @@ +import chai, { expect } from 'chai'; +import chaiAsPromised from 'chai-as-promised'; +import { TestManagedAgent } from '@web5/agent'; + +import { Web5UserAgent } from '../src/user-agent.js'; + +chai.use(chaiAsPromised); + +// NOTE: @noble/secp256k1 requires globalThis.crypto polyfill for node.js <=18: https://github.com/paulmillr/noble-secp256k1/blob/main/README.md#usage +// Remove when we move off of node.js v18 to v20, earliest possible time would be Oct 2023: https://github.com/nodejs/release#release-schedule +// NOTE: @noble/secp256k1 requires globalThis.crypto polyfill for node.js <=18: https://github.com/paulmillr/noble-secp256k1/blob/main/README.md#usage +// Remove when we move off of node.js v18 to v20, earliest possible time would be Oct 2023: https://github.com/nodejs/release#release-schedule +import { webcrypto } from 'node:crypto'; +// @ts-ignore +if (!globalThis.crypto) globalThis.crypto = webcrypto; + +describe('Web5UserAgent', () => { + + const agentStoreTypes = ['dwn', 'memory'] as const; + agentStoreTypes.forEach((agentStoreType) => { + + describe(`with ${agentStoreType} data stores`, () => { + let testAgent: TestManagedAgent; + + before(async () => { + testAgent = await TestManagedAgent.create({ + agentClass : Web5UserAgent, + agentStores : agentStoreType + }); + }); + + beforeEach(async () => { + await testAgent.clearStorage(); + }); + + after(async () => { + await testAgent.clearStorage(); + await testAgent.closeStorage(); + }); + + describe('firstLaunch()', () => { + it('returns true the first time the Identity Agent runs', async () => { + await expect(testAgent.agent.firstLaunch()).to.eventually.be.true; + }); + + it('returns false after Identity Agent initialization', async () => { + await expect(testAgent.agent.firstLaunch()).to.eventually.be.true; + + await testAgent.agent.initialize({ passphrase: 'test' }); + await expect(testAgent.agent.firstLaunch()).to.eventually.be.false; + }); + }); + + if (agentStoreType === 'dwn') { + describe('subsequent launches', () => { + it('can access stored identifiers after second launch', async () => { + // First launch and initialization. + await testAgent.agent.start({ passphrase: 'test' }); + + // Create and persist a new Identity (with DID and Keys). + const socialIdentity = await testAgent.agent.identityManager.create({ + name : 'Social', + didMethod : 'key', + kms : 'local' + }); + + // Simulate terminating and restarting an app. + testAgent.closeStorage(); + testAgent = await TestManagedAgent.create({ + agentClass : Web5UserAgent, + agentStores : 'dwn' + }); + await testAgent.agent.start({ passphrase: 'test' }); + + // Try to get the identity and verify it exists. + const storedIdentity = await testAgent.agent.identityManager.get({ + did : socialIdentity.did, + context : socialIdentity.did + }); + expect(storedIdentity).to.have.property('did', socialIdentity.did); + }); + }); + } + }); + }); + +}); diff --git a/packages/web5-user-agent/tsconfig.cjs.json b/packages/user-agent/tsconfig.cjs.json similarity index 100% rename from packages/web5-user-agent/tsconfig.cjs.json rename to packages/user-agent/tsconfig.cjs.json diff --git a/packages/user-agent/tsconfig.json b/packages/user-agent/tsconfig.json new file mode 100644 index 000000000..b29e4e7e1 --- /dev/null +++ b/packages/user-agent/tsconfig.json @@ -0,0 +1,27 @@ +{ + "compilerOptions": { + "strict": true, + "lib": [ + "DOM", + "ES6" + ], + "allowJs": true, + "target": "es6", + "module": "ESNext", // Required for enabling JavaScript import assertion support + "declaration": true, + "declarationMap": true, + "declarationDir": "dist/types", + "outDir": "dist/esm", + "sourceMap": true, + // `NodeNext` will throw compilation errors if relative import paths are missing file extension + // reference: https://devblogs.microsoft.com/typescript/announcing-typescript-4-7/#ecmascript-module-support-in-node-js + "moduleResolution": "NodeNext", + "esModuleInterop": true + }, + "include": [ + "src", + ], + "exclude": [ + "node_modules" + ] +} \ No newline at end of file diff --git a/packages/web5-agent/build/esbuild-browser-config.cjs b/packages/web5-agent/build/esbuild-browser-config.cjs deleted file mode 100644 index ae37c97f0..000000000 --- a/packages/web5-agent/build/esbuild-browser-config.cjs +++ /dev/null @@ -1,13 +0,0 @@ -/** @type {import('esbuild').BuildOptions} */ -module.exports = { - entryPoints : ['./src/main.ts'], - bundle : true, - format : 'esm', - sourcemap : true, - minify : true, - platform : 'browser', - target : ['chrome101', 'firefox108', 'safari16'], - define : { - 'global': 'globalThis', - }, -}; diff --git a/packages/web5/src/main.ts b/packages/web5/src/main.ts deleted file mode 100644 index a3c2ca4ff..000000000 --- a/packages/web5/src/main.ts +++ /dev/null @@ -1,4 +0,0 @@ -export type { Record } from './record.js'; - -export * from './web5.js'; -export { DateSort } from '@tbd54566975/dwn-sdk-js'; \ No newline at end of file diff --git a/web5-js.code-workspace b/web5-js.code-workspace index 850885daf..7a2018698 100644 --- a/web5-js.code-workspace +++ b/web5-js.code-workspace @@ -1,104 +1,110 @@ { - "folders": [ - { - // Source root - "name": "root", - "path": "." - }, - { - // @tbd54566975/common - "name": "common", - "path": "packages/common", - }, - { - // @tbd54566975/credentials - "name": "credentials", - "path": "packages/credentials", - }, - { - // @tbd54566975/crypto - "name": "crypto", - "path": "packages/crypto", - }, - { - // @tbd54566975/dids - "name": "dids", - "path": "packages/dids", - }, - { - // @tbd54566975/web5 - "name": "web5", - "path": "packages/web5", - }, - { - // @tbd54566975/web5-agent - "name": "web5-agent", - "path": "packages/web5-agent", - }, - { - // @tbd54566975/web5-proxy-agent - "name": "web5-proxy-agent", - "path": "packages/web5-proxy-agent", - }, - { - // @tbd54566975/web5-user-agent - "name": "web5-user-agent", - "path": "packages/web5-user-agent", - }, - ], - "settings": { - "eslint.workingDirectories": [ - { - "mode": "auto" - } - ], - }, - "launch": { - "version": "0.2.0", - "configurations": [ - { - "type": "node", - "request": "launch", - "name": "Test All - Node", - "runtimeExecutable": "${workspaceFolder:root}/node_modules/.bin/mocha", - "runtimeArgs": [ - "${workspaceFolder:root}/packages/**/tests/compiled/**/*.spec.js" - ], - "preLaunchTask": "build tests", - "console": "internalConsole", - "internalConsoleOptions": "openOnSessionStart", - } - ] - }, - "tasks": { - "version": "2.0.0", - "tasks": [ - { - "label": "Build All", - "type": "shell", - "command": "npm run build", - "problemMatcher": [], - "group": { - "kind": "build", - "isDefault": true - } - }, - { - "label": "build tests", - "type": "shell", - "command": "npm", - "args": [ - "run", - "build:tests:node", - "--ws" - ], - "problemMatcher": [ - "$tsc" - ], - "options": { - "cwd": "${workspaceFolder:root}" - } - }, - ] - } + "folders": [ + { + // Source root + "name": "root", + "path": "." + }, + { + // @web5/agent + "name": "agent", + "path": "packages/agent", + }, + { + // @web5/api + "name": "api", + "path": "packages/api", + }, + { + // @web5/common + "name": "common", + "path": "packages/common", + }, + { + // @web5/credentials + "name": "credentials", + "path": "packages/credentials", + }, + { + // @web5/crypto + "name": "crypto", + "path": "packages/crypto", + }, + { + // @web5/dids + "name": "dids", + "path": "packages/dids", + }, + { + // @web5/identity-agent + "name": "identity-agent", + "path": "packages/identity-agent", + }, + { + // @web5/proxy-agent + "name": "proxy-agent", + "path": "packages/proxy-agent", + }, + { + // @web5/user-agent + "name": "user-agent", + "path": "packages/user-agent", + }, + ], + "settings": { + "eslint.workingDirectories": [ + { + "mode": "auto" + } + ], + "npm.packageManager": "npm" + }, + "launch": { + "version": "0.2.0", + "configurations": [ + { + "type": "node", + "request": "launch", + "name": "Test All - Node", + "runtimeExecutable": "${workspaceFolder:root}/node_modules/.bin/mocha", + "runtimeArgs": [ + "${workspaceFolder:root}/packages/**/tests/compiled/**/*.spec.js" + ], + "preLaunchTask": "build tests", + "console": "internalConsole", + "internalConsoleOptions": "openOnSessionStart", + } + ] + }, + "tasks": { + "version": "2.0.0", + "tasks": [ + { + "label": "Build All", + "type": "shell", + "command": "npm run build", + "problemMatcher": [], + "group": { + "kind": "build", + "isDefault": true + } + }, + { + "label": "build tests", + "type": "shell", + "command": "npm", + "args": [ + "run", + "build:tests:node", + "--ws" + ], + "problemMatcher": [ + "$tsc" + ], + "options": { + "cwd": "${workspaceFolder:root}" + } + }, + ] + } } \ No newline at end of file