From 02730cc0d71fcfdad03798ec909c9d675cb936a1 Mon Sep 17 00:00:00 2001 From: Eric Richardson Date: Thu, 3 Oct 2024 17:40:44 -0400 Subject: [PATCH] feat(azure-signing-manager): add azure signing manager add azure signing manager that allows for keys to be stored in an Azure Key Vault and to be used with the Polymesh SDK DA-1322 --- .vscode/settings.json | 1 + README.md | 3 +- commitlint.config.js | 1 + package.json | 3 + packages/azure-signing-manager/.babelrc | 3 + packages/azure-signing-manager/.eslintrc.json | 18 + packages/azure-signing-manager/README.md | 53 +++ packages/azure-signing-manager/jest.config.js | 12 + packages/azure-signing-manager/package.json | 25 + packages/azure-signing-manager/project.json | 51 +++ packages/azure-signing-manager/src/index.ts | 2 + .../src/lib/azure-hsm/azure-hsm.spec.ts | 154 +++++++ .../src/lib/azure-hsm/azure-hsm.ts | 230 ++++++++++ .../src/lib/azure-hsm/index.ts | 3 + .../src/lib/azure-hsm/types.ts | 50 ++ .../src/lib/azure-hsm/util.ts | 48 ++ .../src/lib/azure-signing-manager.spec.ts | 191 ++++++++ .../src/lib/azure-signing-manager.ts | 161 +++++++ .../azure-signing-manager/src/types/index.ts | 24 + packages/azure-signing-manager/tsconfig.json | 19 + .../azure-signing-manager/tsconfig.lib.json | 11 + .../azure-signing-manager/tsconfig.spec.json | 22 + tsconfig.base.json | 1 + workspace.json | 5 +- yarn.lock | 428 +++++++++++++++++- 25 files changed, 1513 insertions(+), 6 deletions(-) create mode 100644 packages/azure-signing-manager/.babelrc create mode 100644 packages/azure-signing-manager/.eslintrc.json create mode 100644 packages/azure-signing-manager/README.md create mode 100644 packages/azure-signing-manager/jest.config.js create mode 100644 packages/azure-signing-manager/package.json create mode 100644 packages/azure-signing-manager/project.json create mode 100644 packages/azure-signing-manager/src/index.ts create mode 100644 packages/azure-signing-manager/src/lib/azure-hsm/azure-hsm.spec.ts create mode 100644 packages/azure-signing-manager/src/lib/azure-hsm/azure-hsm.ts create mode 100644 packages/azure-signing-manager/src/lib/azure-hsm/index.ts create mode 100644 packages/azure-signing-manager/src/lib/azure-hsm/types.ts create mode 100644 packages/azure-signing-manager/src/lib/azure-hsm/util.ts create mode 100644 packages/azure-signing-manager/src/lib/azure-signing-manager.spec.ts create mode 100644 packages/azure-signing-manager/src/lib/azure-signing-manager.ts create mode 100644 packages/azure-signing-manager/src/types/index.ts create mode 100644 packages/azure-signing-manager/tsconfig.json create mode 100644 packages/azure-signing-manager/tsconfig.lib.json create mode 100644 packages/azure-signing-manager/tsconfig.spec.json diff --git a/.vscode/settings.json b/.vscode/settings.json index 93a0c6a3..46b1987d 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -21,6 +21,7 @@ "polywallet", "Resonse", "satoshi", + "secp", "sonarcloud", "tscpaths", "Unsub" diff --git a/README.md b/README.md index 870e68ae..cab04772 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # SigningManagers -Monorepo for Polymesh SDK compatible Signing Managers. +Monorepo for Polymesh SDK compatible Signing Managers. A signing manager abstracts key signing operations to provide a uniform interface for a variety of key storage solutions ## Projects @@ -10,6 +10,7 @@ Monorepo for Polymesh SDK compatible Signing Managers. | **Browser Extension Signing Manager** | [`@polymeshassociation/browser-extension-signing-manager`](https://npmjs.com/package/@polymeshassociation/browser-extension-signing-manager) | [![npm latest version](https://img.shields.io/npm/v/@polymeshassociation/browser-extension-signing-manager/latest.svg)](https://www.npmjs.com/package/@polymeshassociation/browser-extension-signing-manager) | [![README](https://img.shields.io/badge/README--green.svg)](/packages/browser-extension-signing-manager/README.md) [![CHANGELOG](https://img.shields.io/badge/CHANGELOG--orange.svg)](/packages/browser-extension-signing-manager/CHANGELOG.md) | | **Hashicorp Vault Signing Manager** | [`@polymeshassociation/hashicorp-vault-signing-manager`](https://npmjs.com/package/@polymeshassociation/hashicorp-vault-signing-manager) | [![npm latest version](https://img.shields.io/npm/v/@polymeshassociation/hashicorp-vault-signing-manager/latest.svg)](https://www.npmjs.com/package/@polymeshassociation/hashicorp-vault-signing-manager) | [![README](https://img.shields.io/badge/README--green.svg)](/packages/hashicorp-vault-signing-manager/README.md) [![CHANGELOG](https://img.shields.io/badge/CHANGELOG--orange.svg)](/packages/hashicorp-vault-signing-manager/CHANGELOG.md) | | **Fireblocks Signing Manager** | [`@polymeshassociation/fireblocks-signing-manager`](https://npmjs.com/package/@polymeshassociation/fireblocks-signing-manager) | [![npm latest version](https://img.shields.io/npm/v/@polymeshassociation/fireblocks-signing-manager/latest.svg)](https://www.npmjs.com/package/@polymeshassociation/fireblocks-signing-manager) | [![README](https://img.shields.io/badge/README--green.svg)](/packages/fireblocks-signing-manager/README.md) [![CHANGELOG](https://img.shields.io/badge/CHANGELOG--orange.svg)](/packages/fireblocks-signing-manager/CHANGELOG.md) | +| **Azure Signing Manager** | [`@polymeshassociation/azure-signing-manager`](https://npmjs.com/package/@polymeshassociation/azure-signing-manager) | [![npm latest version](https://img.shields.io/npm/v/@polymeshassociation/azure-signing-manager/latest.svg)](https://www.npmjs.com/package/@polymeshassociation/azure-signing-manager) | [![README](https://img.shields.io/badge/README--green.svg)](/packages/azure-signing-manager/README.md) [![CHANGELOG](https://img.shields.io/badge/CHANGELOG--orange.svg)](/packages/azure-signing-manager/CHANGELOG.md) | ## Scripts diff --git a/commitlint.config.js b/commitlint.config.js index 67fbadc3..37bebdc7 100644 --- a/commitlint.config.js +++ b/commitlint.config.js @@ -15,6 +15,7 @@ module.exports = { 'approval-signing-manager', 'fireblocks-signing-manager', 'walletconnect-signing-manager', + 'azure-signing-manager', ], ], }, diff --git a/package.json b/package.json index 9ebc409c..3735c468 100644 --- a/package.json +++ b/package.json @@ -33,6 +33,7 @@ "@babel/preset-env": "^7.16.11", "@commitlint/cli": "^16.2.1", "@commitlint/config-conventional": "^16.2.1", + "@golevelup/ts-jest": "^0.5.6", "@ng-easy/builders": "^5.2.0", "@nrwl/cli": "13.8.2", "@nrwl/devkit": "13.8.2", @@ -48,6 +49,8 @@ "@types/node": "16.11.7", "@types/require-from-string": "^1.2.1", "@typescript-eslint/eslint-plugin": "~5.10.0", + "@types/jest-when": "^3.5.2", + "jest-when": "^3.6.0", "@typescript-eslint/parser": "~5.10.0", "babel-loader": "^8.2.3", "commitiquette": "^1.2.1", diff --git a/packages/azure-signing-manager/.babelrc b/packages/azure-signing-manager/.babelrc new file mode 100644 index 00000000..cf7ddd99 --- /dev/null +++ b/packages/azure-signing-manager/.babelrc @@ -0,0 +1,3 @@ +{ + "presets": [["@nrwl/web/babel", { "useBuiltIns": "usage" }]] +} diff --git a/packages/azure-signing-manager/.eslintrc.json b/packages/azure-signing-manager/.eslintrc.json new file mode 100644 index 00000000..9d9c0db5 --- /dev/null +++ b/packages/azure-signing-manager/.eslintrc.json @@ -0,0 +1,18 @@ +{ + "extends": ["../../.eslintrc.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + } + ] +} diff --git a/packages/azure-signing-manager/README.md b/packages/azure-signing-manager/README.md new file mode 100644 index 00000000..9c96c85d --- /dev/null +++ b/packages/azure-signing-manager/README.md @@ -0,0 +1,53 @@ +# azure-signing-manager + +Polymesh SDK compatible signing manager. This allows Polymesh transactions to be signed with keys in an Microsoft Azure [key vault](https://azure.microsoft.com/en-us/products/key-vault). The keys must be "EC" +type and use curve "P-256K". The signing manager will ignore any other type. + +## Usage + +```typescript +import { AzureSigningManager } from '@polymeshassociation/azure-signing-manager'; +import { Polymesh } from '@polymeshassociation/polymesh-sdk'; + +// defaults to constructing `new DefaultAzureCredential()` for credential +const signingManager = new AzureSigningManager({ + keyVaultUrl: 'https://somekeyvault.vault.azure.net/', +}); + +const polymesh = await Polymesh.connect({ + nodeUrl, + signingManager, +}); + +const newKey = await signingManager.createKey('myKey') // keys can be created in the Azure UI or CLI as well +console.log('created key with address: ', newKey.address) // address is the primary way of specifying public keys on Polymesh +``` + +Details about the default credential behavior can be found [here](https://learn.microsoft.com/en-us/javascript/api/@azure/identity/defaultazurecredential?view=azure-node-latest#@azure-identity-defaultazurecredential-constructor). You can optionally pass in your own credential object and it will be used instead. + + +## Performance Note (for 1000+ keys) + +The current implementation enumerates all possible keys and their versions to construct an index of public key to key name lookup. As an integrator you will likely have this data already indexed. If N+1 style performance issues are a concern the constructor can be extended where a lookup you provide can be called. e.g. + +```ts +interface { + getKeyName(address: string): Promise<{ name: string; version: string }> +} +``` + +For now it is recommended to have a key vault dedicated to Polymesh keys. Please open an issue if you performing the additional work is worth having thousands of keys stored. + +For Reference - Azure key vault pricing ($USD), October 2024: +``` +First 250 keys $5 per key per month +From 251 – 1,500 keys $2.50 per key per month +From 1,501 – 4,000 keys $0.90 per key per month +4,001+ keys $0.40 per key per month +``` + +## Running unit tests + +Run `nx test azure-signing-manager` to execute the unit tests via [Jest](https://jestjs.io). + +This library was generated with [Nx](https://nx.dev). \ No newline at end of file diff --git a/packages/azure-signing-manager/jest.config.js b/packages/azure-signing-manager/jest.config.js new file mode 100644 index 00000000..2778ee8e --- /dev/null +++ b/packages/azure-signing-manager/jest.config.js @@ -0,0 +1,12 @@ +module.exports = { + displayName: 'azure-signing-manager', + preset: '../../jest.preset.js', + globals: { + 'ts-jest': { + tsconfig: '/tsconfig.spec.json', + }, + }, + testEnvironment: 'node', + moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx'], + coverageDirectory: '../../coverage/packages/azure-signing-manager', +}; diff --git a/packages/azure-signing-manager/package.json b/packages/azure-signing-manager/package.json new file mode 100644 index 00000000..4763e05b --- /dev/null +++ b/packages/azure-signing-manager/package.json @@ -0,0 +1,25 @@ +{ + "name": "@polymeshassociation/azure-signing-manager", + "version": "0.0.1", + "publishConfig": { + "access": "public" + }, + "dependencies": { + "@polymeshassociation/signing-manager-types": "^3.3.0", + "@azure/keyvault-keys": "4.8.0", + "@azure/identity": "4.4.1", + "secp256k1": "5.0.0" + }, + "devDependencies": { + "@types/secp256k1": "4.0.6" + }, + "peerDependencies": { + "@polymeshassociation/polymesh-sdk": ">=15.0.0" + }, + "main": "./index.js", + "typings": "./index.d.ts", + "repository": { + "type": "git", + "url": "https://github.com/PolymeshAssociation/signing-managers.git" + } +} diff --git a/packages/azure-signing-manager/project.json b/packages/azure-signing-manager/project.json new file mode 100644 index 00000000..9b20f2f1 --- /dev/null +++ b/packages/azure-signing-manager/project.json @@ -0,0 +1,51 @@ +{ + "root": "packages/azure-signing-manager", + "sourceRoot": "packages/azure-signing-manager/src", + "projectType": "library", + "targets": { + "build": { + "executor": "@nrwl/node:package", + "outputs": ["{options.outputPath}"], + "options": { + "outputPath": "dist/packages/azure-signing-manager", + "tsConfig": "packages/azure-signing-manager/tsconfig.lib.json", + "packageJson": "packages/azure-signing-manager/package.json", + "main": "packages/azure-signing-manager/src/index.ts", + "assets": ["packages/azure-signing-manager/*.md"], + "srcRootForCompilationRoot": "packages/azure-signing-manager/src" + } + }, + "lint": { + "executor": "@nrwl/linter:eslint", + "outputs": ["{options.outputFile}"], + "options": { + "lintFilePatterns": ["packages/azure-signing-manager/**/*.ts"] + } + }, + "test": { + "executor": "@nrwl/jest:jest", + "outputs": ["coverage/packages/azure-signing-manager"], + "options": { + "jestConfig": "packages/azure-signing-manager/jest.config.js", + "passWithNoTests": true + } + }, + "release": { + "executor": "@ng-easy/builders:semantic-release", + "configurations": { + "local": { + "force": true + } + } + }, + "run-local": { + "executor": "./tools/executors/run-local:run-local", + "options": { + "runInBrowser": false, + "path": "packages/azure-signing-manager/sandbox/index.ts", + "port": 9000 + } + } + }, + "tags": [] +} diff --git a/packages/azure-signing-manager/src/index.ts b/packages/azure-signing-manager/src/index.ts new file mode 100644 index 00000000..67543e87 --- /dev/null +++ b/packages/azure-signing-manager/src/index.ts @@ -0,0 +1,2 @@ +export * from './lib/azure-signing-manager'; +export * from './types'; diff --git a/packages/azure-signing-manager/src/lib/azure-hsm/azure-hsm.spec.ts b/packages/azure-signing-manager/src/lib/azure-hsm/azure-hsm.spec.ts new file mode 100644 index 00000000..d9783ea9 --- /dev/null +++ b/packages/azure-signing-manager/src/lib/azure-hsm/azure-hsm.spec.ts @@ -0,0 +1,154 @@ +import { + CryptographyClient, + KeyClient, + KeyProperties, + KeyVaultKey, + KnownSignatureAlgorithms, +} from '@azure/keyvault-keys'; +import { createMock, DeepMocked } from '@golevelup/ts-jest'; +import { when } from 'jest-when'; + +import { AzureHsm } from './azure-hsm'; +import { AzureSignerError } from './types'; +import { createPagedAsyncIterableIterator } from './util'; + +const mockKeyClient = createMock(); +const mockCryptographyClient = createMock(); +const mockKeyVaultKey = createMock(); + +jest.mock('@azure/keyvault-keys', () => ({ + ...jest.requireActual('@azure/keyvault-keys'), + KeyClient: jest.fn().mockImplementation(() => mockKeyClient), + CryptographyClient: jest.fn().mockImplementation(() => mockCryptographyClient), + KeyVaultKey: jest.fn().mockImplementation(() => mockKeyVaultKey), +})); + +describe('AzureHsm', () => { + let azure: AzureHsm; + let mockKey: DeepMocked; + let mockNewKey: DeepMocked; + let mockPartialKey: DeepMocked; + + const x = Buffer.from('d6eb9ae6b44331da8c92a0bd88850b94c7f32981aaf42d1c1345e05af810470a', 'hex'); + const y = Buffer.from('7b4603950822dd8806494a3aff5d6bc2e437b71f7c7de64e9d84a60411dc1293', 'hex'); + const publicKey = '0x63eb973f33ad5737a1ea2e4100c04e806c403b5994d986f0c71a2fd80dbbd179'; + + beforeEach(() => { + azure = new AzureHsm({ keyVaultUrl: 'https://example.com' }); + + mockKey = createMock({ + key: { x, y, crv: 'P-256K' }, + properties: { version: 'v1' }, + }); + mockNewKey = createMock({ + key: { x, y, crv: 'P-256K' }, + properties: { version: 'v1' }, + }); + mockPartialKey = createMock({ + key: { x: undefined, y: undefined, crv: 'P-256K' }, + properties: { version: 'v1' }, + }); + + const mockKeyProperties: KeyProperties = { + name: 'someKey', + vaultUrl: 'http://example.com/keyOne', + version: 'v1', + }; + + const mockIterator = createPagedAsyncIterableIterator([mockKeyProperties]); + + mockKeyClient.listPropertiesOfKeys.mockReturnValue(mockIterator); + mockKeyClient.listPropertiesOfKeyVersions.mockReturnValue( + createPagedAsyncIterableIterator([mockKey.properties]) + ); + + mockKeyClient.getKey.mockRejectedValue({ code: 'KeyNotFound' }); + + mockKeyClient.createKey.mockImplementation(async name => { + when(mockKeyClient.getKey).calledWith(name, { version: 'v1' }).mockResolvedValue(mockKey); + when(mockKeyClient.listPropertiesOfKeyVersions) + .calledWith(name) + .mockReturnValue(createPagedAsyncIterableIterator([{ ...mockKeyProperties, name }])); + + return mockKey; + }); + when(mockKeyClient.getKey).calledWith('someKey', { version: 'v1' }).mockResolvedValue(mockKey); + + when(mockKeyClient.getKey) + .calledWith('partialKey', { version: 'v1' }) + .mockResolvedValue(mockPartialKey); + + mockCryptographyClient.sign.mockResolvedValue({ + result: Buffer.from( + 'b4b24959854a6afdba21e1007955c6c728725009feab9886408d6dc4fb9712e3ab5f30be4f06d2f12f01a10ccd374278d44ab133ed8ee5998341034f9347f156', + 'hex' + ), + algorithm: KnownSignatureAlgorithms.ES256K, + }); + + mockKeyClient.getCryptographyClient.mockReturnValue(mockCryptographyClient); + }); + + describe('createKey', () => { + it('should create a key', async () => { + const result = await azure.createKey('newKey'); + + expect(result).toEqual( + expect.objectContaining({ + name: 'newKey', + keyVaultKey: mockNewKey, + }) + ); + }); + }); + + describe('fetchAllKeys', () => { + it('should return all of the keys', async () => { + const result = await azure.fetchAllKeys(); + + expect(result).toEqual( + expect.arrayContaining([expect.objectContaining({ name: 'someKey', keyVaultKey: mockKey })]) + ); + }); + }); + + describe('getAzureKeyByPubKey', () => { + it('should return the key', async () => { + const key = await azure.getAzureKeyByPubKey(publicKey); + + expect(key).toEqual(expect.objectContaining({ name: 'someKey', keyVaultKey: mockKey })); + }); + }); + + describe('getAzureKey', () => { + it('should return the azure key', async () => { + const result = await azure.getAzureKeyVersions('someKey'); + + expect(result).toEqual( + expect.arrayContaining([expect.objectContaining({ name: 'someKey', keyVaultKey: mockKey })]) + ); + }); + + it('should return empty array if key is not found', () => { + return expect(azure.getAzureKeyVersions('unknownKey')).resolves.toEqual([]); + }); + + it('should throw an error if the key is missing details', async () => { + const expectedError = new AzureSignerError({ message: 'essential key details missing' }); + + return expect(azure.getAzureKeyVersions('partialKey')).rejects.toThrow(expectedError); + }); + }); + + describe('signData', () => { + it('should sign the data', async () => { + const bytes = Buffer.from('00', 'hex'); + + const result = await azure.signData(publicKey, bytes); + + expect(result).toEqual( + '0x02b4b24959854a6afdba21e1007955c6c728725009feab9886408d6dc4fb9712e3ab5f30be4f06d2f12f01a10ccd374278d44ab133ed8ee5998341034f9347f15601' + ); + }); + }); +}); diff --git a/packages/azure-signing-manager/src/lib/azure-hsm/azure-hsm.ts b/packages/azure-signing-manager/src/lib/azure-hsm/azure-hsm.ts new file mode 100644 index 00000000..613a75b0 --- /dev/null +++ b/packages/azure-signing-manager/src/lib/azure-hsm/azure-hsm.ts @@ -0,0 +1,230 @@ +import { DefaultAzureCredential } from '@azure/identity'; +import { + CreateKeyOptions, + CryptographyClient, + KeyClient, + KeyVaultKey, + KnownSignatureAlgorithms, +} from '@azure/keyvault-keys'; +import { u8aToHex } from '@polkadot/util'; +import { HexString } from '@polkadot/util/types'; +import { blake2AsU8a } from '@polkadot/util-crypto'; +import { ecdsaRecover } from 'secp256k1'; + +import { AzureSigningManagerArgs } from '../../types'; +import { AzureKey, AzureSignerError } from './types'; +import { bufferToCompressedKey } from './util'; + +export class AzureHsm { + private readonly credential: DefaultAzureCredential; + private readonly keyClient: KeyClient; + + private pubKeyCache: Record = {}; + + /** + * @hidden + */ + constructor(args: AzureSigningManagerArgs) { + const { keyVaultUrl, credential } = args; + + this.credential = credential ?? new DefaultAzureCredential(); + this.keyClient = new KeyClient(keyVaultUrl, this.credential); + } + + public async createKey(name: string, options?: CreateKeyOptions): Promise { + const existingKeys = await this.getAzureKeyVersions(name); + if (existingKeys.length !== 0) { + throw new AzureSignerError({ + message: 'key already exists with the given name', + data: { name }, + }); + } + + await this.keyClient.createKey(name, 'EC', { ...options, curve: 'P-256K' }); + + const key = await this.getAzureKeyVersions(name); + if (key.length === 0) { + throw new AzureSignerError({ message: 'created key was not found by name', data: { name } }); + } + + return key[0]; + } + + /** + * Fetches a list of all keys, then fetches all versions of each key and filters for EC P-256K keys + * + * @note performance may suffer with key vaults containing thousands of keys. Please open a Github issue if this is a concern + * + * @returns all available signing keys + */ + public async fetchAllKeys(): Promise { + const { keyClient } = this; + + for await (const { name } of keyClient.listPropertiesOfKeys()) { + await this.getAzureKeyVersions(name); // use the side effect of cache population + } + + return Object.values(this.pubKeyCache); + } + + public async getAzureKeyByPubKey(publicKey: string): Promise { + const cachedKey = this.pubKeyCache[publicKey]; + + if (cachedKey) { + return cachedKey; + } + + // Try refetching in case the key was newly added + await this.fetchAllKeys(); + + const newKey = this.pubKeyCache[publicKey]; + + if (!newKey) { + throw new AzureSignerError({ + message: 'signing key was not found', + data: { publicKey }, + }); + } + + return newKey; + } + + /** + * @note this method will filter non EC P-256K keys since their signatures are not supported on Polymesh + * + * @returns all versions for the given key name + */ + public async getAzureKeyVersions(name: string): Promise { + const azureKeys: AzureKey[] = []; + + for await (const keyProperties of this.keyClient.listPropertiesOfKeyVersions(name)) { + const keyVaultKey = await this.getKeyVaultKey(name, keyProperties.version); + + if (!keyVaultKey) { + continue; + } + + const { key, id, properties } = keyVaultKey; + + // Non P-256K keys are not supported so we will just ignore them + if (key?.crv !== 'P-256K') { + continue; + } + + if (!key) { + throw new AzureSignerError({ message: 'key details were not found', data: { name } }); + } + + const { x, y } = key; + + if (!x || !y || !id) { + throw new AzureSignerError({ + message: 'essential key details missing', + data: { name, id, x: u8aToHex(x), y: u8aToHex(y) }, + }); + } + + const keyBuffer = Buffer.concat([Buffer.from([0x04]), x, y]); + const publicKey = bufferToCompressedKey(keyBuffer); + + const azureKey = { + name, + id, + publicKey, + properties, + keyVaultKey, + }; + + this.setPubkeyCache(azureKey); + + azureKeys.push(azureKey); + } + + return azureKeys; + } + + /** + * Sign data on behalf of a key and return the hex signature + * + * @param publicKey the hex formatted public key to sign with e.g. `0x63eb973f33ad5737a1ea2e4100c04e806c403b5994d986f0c71a2fd80dbbd179` + * @param data - data that will be signed + */ + public async signData(pubKey: string, signingPayload: Uint8Array): Promise { + const standardPayload = + signingPayload.length > 256 ? blake2AsU8a(signingPayload) : signingPayload; + + const encoded = blake2AsU8a(standardPayload); + + const { + name, + publicKey, + properties: { version }, + } = await this.getAzureKeyByPubKey(pubKey); + + const cryptoClient = this.getCryptoClient(name, version); + const { result: signatureRs } = await cryptoClient.sign( + KnownSignatureAlgorithms.ES256K, + encoded + ); + + // Compute the recovery ID + let recoveryId = null; + let lastError: Error | null = null; + for (let recId = 0; recId < 2; recId++) { + try { + const recoveredPubKey = ecdsaRecover(signatureRs, recId, encoded, false); // Uncompressed + + const recoveredKey = bufferToCompressedKey(recoveredPubKey); + + if (Buffer.compare(recoveredKey, publicKey) === 0) { + recoveryId = recId; + break; + } + } catch (error) { + lastError = error instanceof Error ? error : new Error(JSON.stringify(error)); + continue; + } + } + + if (recoveryId === null) { + throw new AzureSignerError({ + message: 'Failed to compute recovery ID', + data: { name, publicKey: publicKey.toString('hex'), lastError }, + }); + } + + // Construct the signature: [r (32 bytes), s (32 bytes), recoveryId (1 byte)] + const signatureWithRecovery = Buffer.concat([signatureRs, Buffer.from([recoveryId])]); + + // Prefix with 0x02 to indicate ECDSA signature + const ecdsaSignature = Buffer.concat([Buffer.from([2]), signatureWithRecovery]); + + return `0x${ecdsaSignature.toString('hex')}`; + } + + /** + * @hidden + */ + private async getKeyVaultKey(name: string, version?: string): Promise { + return this.keyClient.getKey(name, { version }).catch(error => { + if (error.code === 'KeyNotFound') { + return undefined; + } + + throw error; + }); + } + + private setPubkeyCache(key: AzureKey): void { + const cacheKey = `0x${key.publicKey.toString('hex')}`; + + this.pubKeyCache[cacheKey] = key; + } + + /** + * @hidden + */ + private getCryptoClient(name: string, keyVersion?: string): CryptographyClient { + return this.keyClient.getCryptographyClient(name, { keyVersion }); + } +} diff --git a/packages/azure-signing-manager/src/lib/azure-hsm/index.ts b/packages/azure-signing-manager/src/lib/azure-hsm/index.ts new file mode 100644 index 00000000..ef84a4b0 --- /dev/null +++ b/packages/azure-signing-manager/src/lib/azure-hsm/index.ts @@ -0,0 +1,3 @@ +export * from './azure-hsm'; +export * from './types'; +export * from './util'; diff --git a/packages/azure-signing-manager/src/lib/azure-hsm/types.ts b/packages/azure-signing-manager/src/lib/azure-hsm/types.ts new file mode 100644 index 00000000..b9839fa6 --- /dev/null +++ b/packages/azure-signing-manager/src/lib/azure-hsm/types.ts @@ -0,0 +1,50 @@ +import { KeyProperties, KeyVaultKey } from '@azure/keyvault-keys'; + +export interface AzureKey { + /** + * Internal key ID. + */ + id: string; + /** + * Internal Azure name. This is used to identify the key within the Key Vault + */ + name: string; + + /** + * The public key in raw format + */ + publicKey: Buffer; + + /** + * Additional details about the key + */ + properties: KeyProperties; + + /** + * The Azure key SDK object. Used for interacting with the key + */ + keyVaultKey: KeyVaultKey; +} + +/** + * Signable data as expected by the Azure Key Vault API + */ +export interface SignRequestPayload { + /** + * base64 encoded data to sign + */ + input: string; +} + +export class AzureSignerError extends Error { + public data?: Record; + + /** + * @hidden + */ + constructor({ message, data }: { message?: string; data?: Record }) { + super(message); + + this.data = data; + } +} diff --git a/packages/azure-signing-manager/src/lib/azure-hsm/util.ts b/packages/azure-signing-manager/src/lib/azure-hsm/util.ts new file mode 100644 index 00000000..12a43aba --- /dev/null +++ b/packages/azure-signing-manager/src/lib/azure-hsm/util.ts @@ -0,0 +1,48 @@ +import { PagedAsyncIterableIterator, PageSettings } from '@azure/keyvault-keys'; +import { blake2AsU8a } from '@polkadot/util-crypto'; + +const compressPublicKey = (publicKeyBuffer: Uint8Array): Uint8Array => { + // Ensure the input is a 65-byte uncompressed key starting with 0x04 + if (publicKeyBuffer.length !== 65 || publicKeyBuffer[0] !== 0x04) { + throw new Error('Invalid uncompressed public key format'); + } + + // Extract x and y coordinates + const x = publicKeyBuffer.slice(1, 33); // First 32 bytes after 0x04 + const y = publicKeyBuffer.slice(33, 65); // Next 32 bytes after x + + // Determine whether y is even or odd + const isEvenY = (y[31] & 1) === 0; // Check the least significant bit of y + + // Compressed public key starts with 0x02 if y is even, 0x03 if odd + const prefix = isEvenY ? 0x02 : 0x03; + + // Create the compressed public key by concatenating the prefix with the x coordinate + const compressedPublicKey = new Uint8Array(33); + compressedPublicKey[0] = prefix; + compressedPublicKey.set(x, 1); // Copy the x coordinate starting at position 1 + + return compressedPublicKey; +}; + +export const createPagedAsyncIterableIterator = ( + items: T[] +): PagedAsyncIterableIterator => { + const pagedResults: T[] = items; + return { + async next() { + const value = pagedResults.shift(); + return { value, done: value === undefined }; + }, + [Symbol.asyncIterator]() { + return this; + }, + } as PagedAsyncIterableIterator; +}; + +export const bufferToCompressedKey = (key: Uint8Array): Buffer => { + const publicCompressedKey = compressPublicKey(key); + const publicAccountId = blake2AsU8a(publicCompressedKey); + + return Buffer.from(publicAccountId); +}; diff --git a/packages/azure-signing-manager/src/lib/azure-signing-manager.spec.ts b/packages/azure-signing-manager/src/lib/azure-signing-manager.spec.ts new file mode 100644 index 00000000..7161acf9 --- /dev/null +++ b/packages/azure-signing-manager/src/lib/azure-signing-manager.spec.ts @@ -0,0 +1,191 @@ +import { CryptographyClient, KeyClient, KeyProperties, KeyVaultKey } from '@azure/keyvault-keys'; +import { createMock, DeepMocked } from '@golevelup/ts-jest'; +import { TypeRegistry } from '@polkadot/types'; +import { SignerPayloadJSON } from '@polkadot/types/types'; +import { when } from 'jest-when'; + +import { AzureHsm, AzureSignerError, createPagedAsyncIterableIterator } from './azure-hsm'; +import { AzureSigner, AzureSigningManager } from './azure-signing-manager'; + +const mockKeyClient = createMock(); +const mockCryptographyClient = createMock(); +const mockKeyVaultKey = createMock(); + +jest.mock('@azure/keyvault-keys', () => ({ + ...jest.requireActual('@azure/keyvault-keys'), + KeyClient: jest.fn().mockImplementation(() => mockKeyClient), + CryptographyClient: jest.fn().mockImplementation(() => mockCryptographyClient), + KeyVaultKey: jest.fn().mockImplementation(() => mockKeyVaultKey), +})); + +const address = '5EKiakuf6cT9RZ3oJKto4EKhprtx6afUu5LJozeidGGBGkqy'; + +describe('AzureSigner', () => { + const payload: SignerPayloadJSON = { + address, + specVersion: '0x00000bba', + transactionVersion: '0x00000002', + blockHash: '0xdf06dca982acacbd5f0bcd7a8a062465b8441d569813561ed13ab81883bc08e7', + blockNumber: '0x00000280', + era: '0x0500', + genesisHash: '0x44748824f9798715435c421b5db9af2beae537974d192fab5fb6fc12e1523765', + method: '0x1a005041594c4f41445f54455354', + nonce: '0x00000004', + signedExtensions: [ + 'CheckSpecVersion', + 'CheckTxVersion', + 'CheckGenesis', + 'CheckMortality', + 'CheckNonce', + 'CheckWeight', + 'ChargeTransactionPayment', + ], + tip: '0x00000000000000000000000000000002', + version: 4, + }; + const signature = '0x123'; + + let signer: AzureSigner; + let mockHsm: DeepMocked; + + beforeEach(() => { + const registry = new TypeRegistry(); + mockHsm = createMock(); + mockHsm.signData.mockResolvedValue(signature); + signer = new AzureSigner(mockHsm, registry); + }); + + it('should sign a payload', async () => { + const result = await signer.signPayload(payload); + + expect(result).toEqual({ id: 0, signature }); + }); + + it('should sign a raw payload', async () => { + const raw = { + address, + data: '0x00', + type: 'bytes' as const, + }; + + const result = await signer.signRaw(raw); + + expect(result).toEqual({ id: 0, signature }); + }); +}); + +describe('AzureSigningManager', () => { + let manager: AzureSigningManager; + const x = Buffer.from('d6eb9ae6b44331da8c92a0bd88850b94c7f32981aaf42d1c1345e05af810470a', 'hex'); + const y = Buffer.from('7b4603950822dd8806494a3aff5d6bc2e437b71f7c7de64e9d84a60411dc1293', 'hex'); + + const x2 = Buffer.from('0931923491ba255a837c47785b4092ff5c47ea6f9fcc2a40f772b3c0d88844c4', 'hex'); + const y2 = Buffer.from('ec1e690e2bf86850421417853fd60526695303c299ecc4f3c9a6a75b11a2e405', 'hex'); + const address2 = '5Fhw9c3rCD1KWSqHpLgnRHwjQJNcYVNLCjJXuHVuzw9VL1t7'; + + beforeEach(() => { + const mockKeyProperties: KeyProperties = { name: 'someKey', version: 'v1', vaultUrl: '' }; + const mockKey2Properties: KeyProperties = { name: 'someKey', version: 'v2', vaultUrl: '' }; + + const mockKeyDetails = [ + { name: 'someKey', vaultUrl: 'http://example.com', properties: mockKeyProperties }, + ]; + + mockKeyClient.listPropertiesOfKeys.mockReturnValue( + createPagedAsyncIterableIterator(mockKeyDetails) + ); + + mockKeyClient.listPropertiesOfKeyVersions.mockReturnValue( + createPagedAsyncIterableIterator([mockKeyProperties, mockKey2Properties]) + ); + + mockKeyClient.getKey.mockRejectedValue({ code: 'KeyNotFound' }); + + when(mockKeyClient.getKey) + .calledWith('someKey', { version: 'v1' }) + .mockResolvedValue(createMock({ key: { x, y, crv: 'P-256K' } })); + + when(mockKeyClient.getKey) + .calledWith('someKey', { version: 'v2' }) + .mockResolvedValue(createMock({ key: { x: x2, y: y2, crv: 'P-256K' } })); + + mockKeyClient.createKey.mockImplementation(async name => { + const mockKey = createMock({ + key: { x, y, crv: 'P-256K' }, + properties: { version: 'v1' }, + }); + + mockKeyDetails.push({ name, vaultUrl: 'http://example.com', properties: mockKey.properties }); + + mockKeyClient.listPropertiesOfKeys.mockReturnValue( + createPagedAsyncIterableIterator(mockKeyDetails) + ); + + when(mockKeyClient.listPropertiesOfKeyVersions) + .calledWith(name) + .mockReturnValue(createPagedAsyncIterableIterator([mockKey.properties])); + + when(mockKeyClient.getKey).calledWith(name, { version: 'v1' }).mockResolvedValue(mockKey); + + return mockKey; + }); + + manager = new AzureSigningManager({ keyVaultUrl: 'https://example.com' }); + }); + + describe('createKey', () => { + it('should call create key', async () => { + manager.setSs58Format(42); + const result = await manager.createKey('someNewKey'); + + expect(result).toEqual(expect.objectContaining({ name: 'someNewKey' })); + }); + + it('should throw an error if creating a key that already exists', () => { + manager.setSs58Format(42); + const expectedError = new AzureSignerError({ + message: 'key already exists with the given name', + }); + + return expect(manager.createKey('someKey')).rejects.toThrow(expectedError); + }); + + it('should throw an error if ss58 format is not set', async () => { + const expectedError = new AzureSignerError({ + message: + "Cannot call 'createKey' before calling 'setSs58Format'. Did you forget to use this Signing Manager to connect with the Polymesh SDK?", + }); + + return expect(manager.createKey('someNewKey')).rejects.toThrow(expectedError); + }); + }); + + describe('getAccounts', () => { + it('should return all of the accounts', async () => { + manager.setSs58Format(42); + const result = await manager.getAccounts(); + + expect(result).toEqual([address, address2]); + }); + + it('should throw an error if ss58 format was not set', async () => { + return expect(manager.getAccounts()).rejects.toThrow( + "Cannot call 'getAccounts' before calling 'setSs58Format'. Did you forget to use this Signing Manager to connect with the Polymesh SDK?" + ); + }); + }); + + describe('getExternalSigner', () => { + it('should return the external signer', () => { + const result = manager.getExternalSigner(); + + expect(result).toBeInstanceOf(AzureSigner); + }); + }); + + describe('setSs58Format', () => { + it('should set the ss58Format', () => { + expect(() => manager.setSs58Format(42)).not.toThrow(); + }); + }); +}); diff --git a/packages/azure-signing-manager/src/lib/azure-signing-manager.ts b/packages/azure-signing-manager/src/lib/azure-signing-manager.ts new file mode 100644 index 00000000..6c3e45ac --- /dev/null +++ b/packages/azure-signing-manager/src/lib/azure-signing-manager.ts @@ -0,0 +1,161 @@ +import { CreateKeyOptions } from '@azure/keyvault-keys'; +import { TypeRegistry } from '@polkadot/types'; +import { SignerPayloadJSON, SignerPayloadRaw, SignerResult } from '@polkadot/types/types'; +import { hexToU8a, u8aToHex } from '@polkadot/util'; +import { blake2AsU8a, decodeAddress, encodeAddress } from '@polkadot/util-crypto'; +import { + PolkadotSigner, + signedExtensions, + SigningManager, +} from '@polymeshassociation/signing-manager-types'; + +import { AddressedAzureKey, AzureSigningManagerArgs } from '../types'; +import { AzureHsm, AzureSignerError } from './azure-hsm'; + +export class AzureSigner implements PolkadotSigner { + private currentId = -1; + + constructor(private readonly azure: AzureHsm, private readonly registry: TypeRegistry) {} + + public async signPayload(payload: SignerPayloadJSON): Promise { + const { address, version } = payload; + const { registry } = this; + + const signablePayload = registry.createType('ExtrinsicPayload', payload, { + version, + }); + + return this.signData(address, signablePayload.toU8a(true)); + } + + public async signRaw(raw: SignerPayloadRaw): Promise { + const { address, data } = raw; + + return this.signData(address, hexToU8a(data)); + } + + /** + * @hidden + * + * Use the Azure HSM to sign raw data and return the signature + update ID + */ + private async signData(address: string, data: Uint8Array): Promise { + const pubKey = u8aToHex(decodeAddress(address)); + + const fixedData = data.length > 256 ? blake2AsU8a(data) : data; + + const signature = await this.azure.signData(pubKey, fixedData); + + const id = (this.currentId += 1); + + return { + signature, + id, + }; + } +} + +/** + * Enables signing Polymesh SDK transactions using a Microsoft Azure key vault + * + * @note only elliptic curve keys on P-256K are valid Polymesh. Any other key type will be ignored + */ +export class AzureSigningManager implements SigningManager { + private readonly azureHsm: AzureHsm; + private readonly externalSigner: PolkadotSigner; + private _ss58Format?: number; + + constructor(args: AzureSigningManagerArgs) { + const registry = new TypeRegistry(); + registry.setSignedExtensions(signedExtensions); + + this.azureHsm = new AzureHsm(args); + this.externalSigner = new AzureSigner(this.azureHsm, registry); + } + + /** + * Creates a key with the given name + * + * @note keys must use the P-256K curve (aka secp256k1) + * + * @throws AzureSignerError if the name is already used + * + * @returns key details + */ + public async createKey(name: string, options?: CreateKeyOptions): Promise { + if (options?.curve && options.curve !== 'P-256K') { + throw new AzureSignerError({ + message: 'Only curve P-256K is supported on Polymesh', + data: { curve: options.curve }, + }); + } + + const ss58Format = this.getSs58Format('createKey'); + + const key = await this.azureHsm.createKey(name, options); + + return { + ...key, + address: encodeAddress(key.publicKey, ss58Format), + }; + } + + /** + * @note `getAzureKeys` returns additional details + * + * @returns all addresses stored in the key vault + */ + public async getAccounts(): Promise { + const ss58Format = this.getSs58Format('getAccounts'); + const keys = await this.azureHsm.fetchAllKeys(); + + return keys.map(({ publicKey }) => encodeAddress(publicKey, ss58Format)); + } + + /** + * + * @returns signer compatible implementing polkadot-js signer interface + */ + public getExternalSigner(): PolkadotSigner { + return this.externalSigner; + } + + /** + * Sets the ss58 format so addresses can be properly formatted. This must be called before other methods are usable + * + * @note The Polymesh SDK will call this method internally. This only needs to be called if the SDK is not being used + */ + public setSs58Format(ss58Format: number): void { + this._ss58Format = ss58Format; + } + + /** + * @returns Details about all available signing keys within the key vault + */ + public async getAzureKeys(): Promise { + const ss58Format = this.getSs58Format('getVaultKeys'); + const keys = await this.azureHsm.fetchAllKeys(); + + return keys.map(key => ({ + ...key, + address: encodeAddress(key.publicKey, ss58Format), + })); + } + + /** + * @hidden + * + * @throws if the SS58 format hasn't been set yet + */ + private getSs58Format(methodName: string): number { + const { _ss58Format: format } = this; + + if (format === undefined) { + throw new AzureSignerError({ + message: `Cannot call '${methodName}' before calling 'setSs58Format'. Did you forget to use this Signing Manager to connect with the Polymesh SDK?`, + }); + } + + return format; + } +} diff --git a/packages/azure-signing-manager/src/types/index.ts b/packages/azure-signing-manager/src/types/index.ts new file mode 100644 index 00000000..cb186977 --- /dev/null +++ b/packages/azure-signing-manager/src/types/index.ts @@ -0,0 +1,24 @@ +import { DefaultAzureCredential } from '@azure/identity'; + +import { AzureKey } from '../lib/azure-hsm'; + +export interface AddressedAzureKey extends AzureKey { + /** + * ss58 encoded version of the publicKey + */ + address: string; +} + +/** + * Arguments required to construct an Azure Signing Manager + */ +export interface AzureSigningManagerArgs { + /** + * The key vault URL where the keys are stored + */ + keyVaultUrl: string; + /** + * @optional The Azure credential object. If not provided `new DefaultAzureCredential()` will be used. More information can be found in Microsoft's documentation: https://learn.microsoft.com/en-us/javascript/api/@azure/identity/defaultazurecredential?view=azure-node-latest + */ + credential?: DefaultAzureCredential; +} diff --git a/packages/azure-signing-manager/tsconfig.json b/packages/azure-signing-manager/tsconfig.json new file mode 100644 index 00000000..e258886f --- /dev/null +++ b/packages/azure-signing-manager/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../tsconfig.base.json", + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + }, + { + "path": "./tsconfig.spec.json" + } + ], + "compilerOptions": { + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + } +} diff --git a/packages/azure-signing-manager/tsconfig.lib.json b/packages/azure-signing-manager/tsconfig.lib.json new file mode 100644 index 00000000..2ad6b016 --- /dev/null +++ b/packages/azure-signing-manager/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["**/*.spec.ts", "**/*.test.ts", "**/mocks.ts", "**/*.example.ts", "sandbox"], + "include": ["**/*.ts"] +} diff --git a/packages/azure-signing-manager/tsconfig.spec.json b/packages/azure-signing-manager/tsconfig.spec.json new file mode 100644 index 00000000..60a91956 --- /dev/null +++ b/packages/azure-signing-manager/tsconfig.spec.json @@ -0,0 +1,22 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "../../dist/out-tsc", + "module": "commonjs", + "types": ["jest", "node"], + "allowJs": true, + "esModuleInterop": true + }, + "include": [ + "**/*.test.ts", + "**/*.spec.ts", + "**/*.test.tsx", + "**/*.spec.tsx", + "**/*.test.js", + "**/*.spec.js", + "**/*.test.jsx", + "**/*.spec.jsx", + "**/*.d.ts", + "**/mocks.ts" +, "src/lib/azure-signing-manager.spec.ts" ] +} diff --git a/tsconfig.base.json b/tsconfig.base.json index bcfd0e8b..a6302754 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -16,6 +16,7 @@ "baseUrl": ".", "paths": { "@polymeshassociation/approval-signing-manager": ["packages/approval-signing-manager/src"], + "@polymeshassociation/azure-signing-manager": ["packages/azure-signing-manager/src"], "@polymeshassociation/browser-extension-signing-manager": [ "packages/browser-extension-signing-manager/src" ], diff --git a/workspace.json b/workspace.json index cafa59ec..7ae8237d 100644 --- a/workspace.json +++ b/workspace.json @@ -2,11 +2,12 @@ "version": 2, "projects": { "approval-signing-manager": "packages/approval-signing-manager", + "azure-signing-manager": "packages/azure-signing-manager", "browser-extension-signing-manager": "packages/browser-extension-signing-manager", "fireblocks-signing-manager": "packages/fireblocks-signing-manager", "hashicorp-vault-signing-manager": "packages/hashicorp-vault-signing-manager", "local-signing-manager": "packages/local-signing-manager", - "walletconnect-signing-manager": "packages/walletconnect-signing-manager", - "types": "packages/types" + "types": "packages/types", + "walletconnect-signing-manager": "packages/walletconnect-signing-manager" } } diff --git a/yarn.lock b/yarn.lock index a0c16bb6..455760cc 100644 --- a/yarn.lock +++ b/yarn.lock @@ -157,6 +157,162 @@ resolved "https://registry.yarnpkg.com/@assemblyscript/loader/-/loader-0.10.1.tgz#70e45678f06c72fa2e350e8553ec4a4d72b92e06" integrity sha512-H71nDOOL8Y7kWRLqf6Sums+01Q5msqBW2KhDUTemh1tvY04eSkSXrK0uj/4mmY0Xr16/3zyZmsrxN7CKuRbNRg== +"@azure/abort-controller@^1.0.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@azure/abort-controller/-/abort-controller-1.1.0.tgz#788ee78457a55af8a1ad342acb182383d2119249" + integrity sha512-TrRLIoSQVzfAJX9H1JeFjzAoDGcoK1IYX1UImfceTZpsyYfWr09Ss1aHW1y5TrrR3iq6RZLBwJ3E24uwPhwahw== + dependencies: + tslib "^2.2.0" + +"@azure/abort-controller@^2.0.0": + version "2.1.2" + resolved "https://registry.yarnpkg.com/@azure/abort-controller/-/abort-controller-2.1.2.tgz#42fe0ccab23841d9905812c58f1082d27784566d" + integrity sha512-nBrLsEWm4J2u5LpAPjxADTlq3trDgVZZXHNKabeXZtpq3d3AbN/KGO82R87rdDz5/lYB024rtEf10/q0urNgsA== + dependencies: + tslib "^2.6.2" + +"@azure/core-auth@^1.3.0", "@azure/core-auth@^1.4.0", "@azure/core-auth@^1.5.0", "@azure/core-auth@^1.8.0": + version "1.8.0" + resolved "https://registry.yarnpkg.com/@azure/core-auth/-/core-auth-1.8.0.tgz#281b4a6d3309c3e7b15bcd967f01d4c79ae4a1d6" + integrity sha512-YvFMowkXzLbXNM11yZtVLhUCmuG0ex7JKOH366ipjmHBhL3vpDcPAeWF+jf0X+jVXwFqo3UhsWUq4kH0ZPdu/g== + dependencies: + "@azure/abort-controller" "^2.0.0" + "@azure/core-util" "^1.1.0" + tslib "^2.6.2" + +"@azure/core-client@^1.3.0", "@azure/core-client@^1.5.0", "@azure/core-client@^1.9.2": + version "1.9.2" + resolved "https://registry.yarnpkg.com/@azure/core-client/-/core-client-1.9.2.tgz#6fc69cee2816883ab6c5cdd653ee4f2ff9774f74" + integrity sha512-kRdry/rav3fUKHl/aDLd/pDLcB+4pOFwPPTVEExuMyaI5r+JBbMWqRbCY1pn5BniDaU3lRxO9eaQ1AmSMehl/w== + dependencies: + "@azure/abort-controller" "^2.0.0" + "@azure/core-auth" "^1.4.0" + "@azure/core-rest-pipeline" "^1.9.1" + "@azure/core-tracing" "^1.0.0" + "@azure/core-util" "^1.6.1" + "@azure/logger" "^1.0.0" + tslib "^2.6.2" + +"@azure/core-http-compat@^2.0.1": + version "2.1.2" + resolved "https://registry.yarnpkg.com/@azure/core-http-compat/-/core-http-compat-2.1.2.tgz#d1585ada24ba750dc161d816169b33b35f762f0d" + integrity sha512-5MnV1yqzZwgNLLjlizsU3QqOeQChkIXw781Fwh1xdAqJR5AA32IUaq6xv1BICJvfbHoa+JYcaij2HFkhLbNTJQ== + dependencies: + "@azure/abort-controller" "^2.0.0" + "@azure/core-client" "^1.3.0" + "@azure/core-rest-pipeline" "^1.3.0" + +"@azure/core-lro@^2.2.0": + version "2.7.2" + resolved "https://registry.yarnpkg.com/@azure/core-lro/-/core-lro-2.7.2.tgz#787105027a20e45c77651a98b01a4d3b01b75a08" + integrity sha512-0YIpccoX8m/k00O7mDDMdJpbr6mf1yWo2dfmxt5A8XVZVVMz2SSKaEbMCeJRvgQ0IaSlqhjT47p4hVIRRy90xw== + dependencies: + "@azure/abort-controller" "^2.0.0" + "@azure/core-util" "^1.2.0" + "@azure/logger" "^1.0.0" + tslib "^2.6.2" + +"@azure/core-paging@^1.1.1": + version "1.6.2" + resolved "https://registry.yarnpkg.com/@azure/core-paging/-/core-paging-1.6.2.tgz#40d3860dc2df7f291d66350b2cfd9171526433e7" + integrity sha512-YKWi9YuCU04B55h25cnOYZHxXYtEvQEbKST5vqRga7hWY9ydd3FZHdeQF8pyh+acWZvppw13M/LMGx0LABUVMA== + dependencies: + tslib "^2.6.2" + +"@azure/core-rest-pipeline@^1.1.0", "@azure/core-rest-pipeline@^1.3.0", "@azure/core-rest-pipeline@^1.8.1", "@azure/core-rest-pipeline@^1.9.1": + version "1.17.0" + resolved "https://registry.yarnpkg.com/@azure/core-rest-pipeline/-/core-rest-pipeline-1.17.0.tgz#55dafa1093553c549ed6d8dbca69aa505c7b3aa3" + integrity sha512-62Vv8nC+uPId3j86XJ0WI+sBf0jlqTqPUFCBNrGtlaUeQUIXWV/D8GE5A1d+Qx8H7OQojn2WguC8kChD6v0shA== + dependencies: + "@azure/abort-controller" "^2.0.0" + "@azure/core-auth" "^1.8.0" + "@azure/core-tracing" "^1.0.1" + "@azure/core-util" "^1.9.0" + "@azure/logger" "^1.0.0" + http-proxy-agent "^7.0.0" + https-proxy-agent "^7.0.0" + tslib "^2.6.2" + +"@azure/core-tracing@^1.0.0", "@azure/core-tracing@^1.0.1": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@azure/core-tracing/-/core-tracing-1.1.2.tgz#065dab4e093fb61899988a1cdbc827d9ad90b4ee" + integrity sha512-dawW9ifvWAWmUm9/h+/UQ2jrdvjCJ7VJEuCJ6XVNudzcOwm53BFZH4Q845vjfgoUAM8ZxokvVNxNxAITc502YA== + dependencies: + tslib "^2.6.2" + +"@azure/core-util@^1.0.0", "@azure/core-util@^1.1.0", "@azure/core-util@^1.2.0", "@azure/core-util@^1.3.0", "@azure/core-util@^1.6.1", "@azure/core-util@^1.9.0": + version "1.10.0" + resolved "https://registry.yarnpkg.com/@azure/core-util/-/core-util-1.10.0.tgz#cf3163382d40343972848c914869864df5d44bdb" + integrity sha512-dqLWQsh9Nro1YQU+405POVtXnwrIVqPyfUzc4zXCbThTg7+vNNaiMkwbX9AMXKyoFYFClxmB3s25ZFr3+jZkww== + dependencies: + "@azure/abort-controller" "^2.0.0" + tslib "^2.6.2" + +"@azure/identity@4.4.1": + version "4.4.1" + resolved "https://registry.yarnpkg.com/@azure/identity/-/identity-4.4.1.tgz#490fa2ad26786229afa36411892bb53dfa3478d3" + integrity sha512-DwnG4cKFEM7S3T+9u05NstXU/HN0dk45kPOinUyNKsn5VWwpXd9sbPKEg6kgJzGbm1lMuhx9o31PVbCtM5sfBA== + dependencies: + "@azure/abort-controller" "^1.0.0" + "@azure/core-auth" "^1.5.0" + "@azure/core-client" "^1.9.2" + "@azure/core-rest-pipeline" "^1.1.0" + "@azure/core-tracing" "^1.0.0" + "@azure/core-util" "^1.3.0" + "@azure/logger" "^1.0.0" + "@azure/msal-browser" "^3.14.0" + "@azure/msal-node" "^2.9.2" + events "^3.0.0" + jws "^4.0.0" + open "^8.0.0" + stoppable "^1.1.0" + tslib "^2.2.0" + +"@azure/keyvault-keys@4.8.0": + version "4.8.0" + resolved "https://registry.yarnpkg.com/@azure/keyvault-keys/-/keyvault-keys-4.8.0.tgz#1513b3a187bb3a9a372b5980c593962fb793b2ad" + integrity sha512-jkuYxgkw0aaRfk40OQhFqDIupqblIOIlYESWB6DKCVDxQet1pyv86Tfk9M+5uFM0+mCs6+MUHU+Hxh3joiUn4Q== + dependencies: + "@azure/abort-controller" "^1.0.0" + "@azure/core-auth" "^1.3.0" + "@azure/core-client" "^1.5.0" + "@azure/core-http-compat" "^2.0.1" + "@azure/core-lro" "^2.2.0" + "@azure/core-paging" "^1.1.1" + "@azure/core-rest-pipeline" "^1.8.1" + "@azure/core-tracing" "^1.0.0" + "@azure/core-util" "^1.0.0" + "@azure/logger" "^1.0.0" + tslib "^2.2.0" + +"@azure/logger@^1.0.0": + version "1.1.4" + resolved "https://registry.yarnpkg.com/@azure/logger/-/logger-1.1.4.tgz#223cbf2b424dfa66478ce9a4f575f59c6f379768" + integrity sha512-4IXXzcCdLdlXuCG+8UKEwLA1T1NHqUfanhXYHiQTn+6sfWCZXduqbtXDGceg3Ce5QxTGo7EqmbV6Bi+aqKuClQ== + dependencies: + tslib "^2.6.2" + +"@azure/msal-browser@^3.14.0": + version "3.24.0" + resolved "https://registry.yarnpkg.com/@azure/msal-browser/-/msal-browser-3.24.0.tgz#3208047672d0b0c943b0bef5f995d510d6582ae4" + integrity sha512-JGNV9hTYAa7lsum9IMIibn2kKczAojNihGo1hi7pG0kNrcKej530Fl6jxwM05A44/6I079CSn6WxYxbVhKUmWg== + dependencies: + "@azure/msal-common" "14.15.0" + +"@azure/msal-common@14.15.0": + version "14.15.0" + resolved "https://registry.yarnpkg.com/@azure/msal-common/-/msal-common-14.15.0.tgz#0e27ac0bb88fe100f4f8d1605b64d5c268636a55" + integrity sha512-ImAQHxmpMneJ/4S8BRFhjt1MZ3bppmpRPYYNyzeQPeFN288YKbb8TmmISQEbtfkQ1BPASvYZU5doIZOPBAqENQ== + +"@azure/msal-node@^2.9.2": + version "2.14.0" + resolved "https://registry.yarnpkg.com/@azure/msal-node/-/msal-node-2.14.0.tgz#7881895d41b03d8b9b38a29550ba3bbb15f73b3c" + integrity sha512-rrfzIpG3Q1rHjVYZmHAEDidWAZZ2cgkxlIcMQ8dHebRISaZ2KCV33Q8Vs+uaV6lxweROabNxKFlR2lIKagZqYg== + dependencies: + "@azure/msal-common" "14.15.0" + jsonwebtoken "^9.0.0" + uuid "^8.3.0" + "@babel/code-frame@^7.0.0", "@babel/code-frame@^7.12.13", "@babel/code-frame@^7.16.7", "@babel/code-frame@^7.8.3": version "7.16.7" resolved "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.16.7.tgz" @@ -1355,6 +1511,11 @@ resolved "https://registry.yarnpkg.com/@gar/promisify/-/promisify-1.1.3.tgz#555193ab2e3bb3b6adc3d551c9c030d9e860daf6" integrity sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw== +"@golevelup/ts-jest@^0.5.6": + version "0.5.6" + resolved "https://registry.yarnpkg.com/@golevelup/ts-jest/-/ts-jest-0.5.6.tgz#e63e3d746417de07cbd5d45208de380cd185346a" + integrity sha512-QnxP42Qu9M2UogdrF2kxpZcgWeW9R3WoUr+LdpcsbkvX3LjEoDYgrJ/PnV/QUCbxB1gaKbhR0ZxlDxE1cPkpDg== + "@humanwhocodes/config-array@^0.9.2": version "0.9.3" resolved "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.9.3.tgz" @@ -1446,6 +1607,13 @@ "@types/node" "*" jest-mock "^27.5.1" +"@jest/expect-utils@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/expect-utils/-/expect-utils-29.7.0.tgz#023efe5d26a8a70f21677d0a1afc0f0a44e3a1c6" + integrity sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA== + dependencies: + jest-get-type "^29.6.3" + "@jest/fake-timers@^27.5.1": version "27.5.1" resolved "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-27.5.1.tgz" @@ -1528,6 +1696,13 @@ terminal-link "^2.0.0" v8-to-istanbul "^8.1.0" +"@jest/schemas@^29.6.3": + version "29.6.3" + resolved "https://registry.yarnpkg.com/@jest/schemas/-/schemas-29.6.3.tgz#430b5ce8a4e0044a7e3819663305a7b3091c8e03" + integrity sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA== + dependencies: + "@sinclair/typebox" "^0.27.8" + "@jest/source-map@^27.5.1": version "27.5.1" resolved "https://registry.npmjs.org/@jest/source-map/-/source-map-27.5.1.tgz" @@ -1599,6 +1774,18 @@ "@types/yargs" "^16.0.0" chalk "^4.0.0" +"@jest/types@^29.6.3": + version "29.6.3" + resolved "https://registry.yarnpkg.com/@jest/types/-/types-29.6.3.tgz#1131f8cf634e7e84c5e77bab12f052af585fba59" + integrity sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw== + dependencies: + "@jest/schemas" "^29.6.3" + "@types/istanbul-lib-coverage" "^2.0.0" + "@types/istanbul-reports" "^3.0.0" + "@types/node" "*" + "@types/yargs" "^17.0.8" + chalk "^4.0.0" + "@jimp/bmp@^0.16.1": version "0.16.1" resolved "https://registry.yarnpkg.com/@jimp/bmp/-/bmp-0.16.1.tgz#6e2da655b2ba22e721df0795423f34e92ef13768" @@ -3119,6 +3306,11 @@ lodash "^4.17.4" read-pkg-up "^7.0.0" +"@sinclair/typebox@^0.27.8": + version "0.27.8" + resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.27.8.tgz#6667fac16c436b5434a387a34dedb013198f6e6e" + integrity sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA== + "@sindresorhus/is@^0.7.0": version "0.7.0" resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-0.7.0.tgz#9a06f4f137ee84d7df0460c1fdb1135ffa6c50fd" @@ -3614,6 +3806,21 @@ dependencies: "@types/istanbul-lib-report" "*" +"@types/jest-when@^3.5.2": + version "3.5.5" + resolved "https://registry.yarnpkg.com/@types/jest-when/-/jest-when-3.5.5.tgz#c23e97945959277946c15eff2a2fe51d18743045" + integrity sha512-H9MDPIrz7NOu6IXP9OHExNN9LnJbGYAzRsGIDKxWr7Fth9vovemNV8yFbkUWLSEmuA8PREvAEvt9yK0PPLmFHA== + dependencies: + "@types/jest" "*" + +"@types/jest@*": + version "29.5.13" + resolved "https://registry.yarnpkg.com/@types/jest/-/jest-29.5.13.tgz#8bc571659f401e6a719a7bf0dbcb8b78c71a8adc" + integrity sha512-wd+MVEZCHt23V0/L642O5APvspWply/rGY5BcW4SUETo2UzPU3Z26qr8jC2qxpimI2jjx9h7+2cj2FwIr01bXg== + dependencies: + expect "^29.0.0" + pretty-format "^29.0.0" + "@types/jest@27.0.2": version "27.0.2" resolved "https://registry.npmjs.org/@types/jest/-/jest-27.0.2.tgz" @@ -3687,6 +3894,13 @@ resolved "https://registry.yarnpkg.com/@types/retry/-/retry-0.12.1.tgz#d8f1c0d0dc23afad6dc16a9e993a0865774b4065" integrity sha512-xoDlM2S4ortawSWORYqsdU+2rxdh4LRW9ytc3zmT37RIKQh6IHyKwwtKhKis9ah8ol07DCkZxPt8BBvPjC6v4g== +"@types/secp256k1@4.0.6": + version "4.0.6" + resolved "https://registry.yarnpkg.com/@types/secp256k1/-/secp256k1-4.0.6.tgz#d60ba2349a51c2cbc5e816dcd831a42029d376bf" + integrity sha512-hHxJU6PAEUn0TP4S/ZOzuTUvJWuZ6eIKeNKb5RBpODvSl6hp1Wrw4s7ATY50rklRCScUDpHzVA/DQdSjJ3UoYQ== + dependencies: + "@types/node" "*" + "@types/serve-index@^1.9.1": version "1.9.1" resolved "https://registry.yarnpkg.com/@types/serve-index/-/serve-index-1.9.1.tgz#1b5e85370a192c01ec6cec4735cf2917337a6278" @@ -3738,6 +3952,13 @@ dependencies: "@types/yargs-parser" "*" +"@types/yargs@^17.0.8": + version "17.0.33" + resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-17.0.33.tgz#8c32303da83eec050a84b3c7ae7b9f922d13e32d" + integrity sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA== + dependencies: + "@types/yargs-parser" "*" + "@typescript-eslint/eslint-plugin@~5.10.0": version "5.10.2" resolved "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.10.2.tgz" @@ -4260,6 +4481,13 @@ agent-base@6, agent-base@^6.0.2: dependencies: debug "4" +agent-base@^7.0.2, agent-base@^7.1.0: + version "7.1.1" + resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-7.1.1.tgz#bdbded7dfb096b751a2a087eeeb9664725b2e317" + integrity sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA== + dependencies: + debug "^4.3.4" + agentkeepalive@^4.1.3, agentkeepalive@^4.2.0: version "4.2.0" resolved "https://registry.yarnpkg.com/agentkeepalive/-/agentkeepalive-4.2.0.tgz#616ce94ccb41d1a39a45d203d8076fe98713062d" @@ -6249,6 +6477,13 @@ debug@^3.1.1, debug@^3.2.6: dependencies: ms "^2.1.1" +debug@^4.3.4: + version "4.3.7" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.7.tgz#87945b4151a011d76d95a198d7111c865c360a52" + integrity sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ== + dependencies: + ms "^2.1.3" + debuglog@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/debuglog/-/debuglog-1.0.1.tgz#aa24ffb9ac3df9a2351837cfb2d279360cd78492" @@ -6517,6 +6752,11 @@ diff-sequences@^27.5.1: resolved "https://registry.npmjs.org/diff-sequences/-/diff-sequences-27.5.1.tgz" integrity sha512-k1gCAXAsNgLwEL+Y8Wvl+M6oEFj5bgazfZULpS5CneoPPXRaCCW7dm+q21Ky2VEE5X+VeRDBVg1Pcvvsr4TtNQ== +diff-sequences@^29.6.3: + version "29.6.3" + resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-29.6.3.tgz#4deaf894d11407c51efc8418012f9e70b84ea921" + integrity sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q== + diff@^4.0.1: version "4.0.2" resolved "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz" @@ -6756,6 +6996,19 @@ elliptic@^6.5.3: minimalistic-assert "^1.0.1" minimalistic-crypto-utils "^1.0.1" +elliptic@^6.5.4: + version "6.5.7" + resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.5.7.tgz#8ec4da2cb2939926a1b9a73619d768207e647c8b" + integrity sha512-ESVCtTwiA+XhY3wyh24QqRGBoP3rEdDUl3EDUUo9tft074fi19IrdpH7hLCMMP3CIj7jb3W96rn8lt/BqIlt5Q== + dependencies: + bn.js "^4.11.9" + brorand "^1.1.0" + hash.js "^1.0.0" + hmac-drbg "^1.0.1" + inherits "^2.0.4" + minimalistic-assert "^1.0.1" + minimalistic-crypto-utils "^1.0.1" + emittery@^0.8.1: version "0.8.1" resolved "https://registry.npmjs.org/emittery/-/emittery-0.8.1.tgz" @@ -7206,7 +7459,7 @@ eventemitter3@^5.0.1: resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-5.0.1.tgz#53f5ffd0a492ac800721bb42c66b841de96423c4" integrity sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA== -events@3.3.0, events@^3.2.0, events@^3.3.0: +events@3.3.0, events@^3.0.0, events@^3.2.0, events@^3.3.0: version "3.3.0" resolved "https://registry.npmjs.org/events/-/events-3.3.0.tgz" integrity sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q== @@ -7329,6 +7582,17 @@ expect@^27.5.1: jest-matcher-utils "^27.5.1" jest-message-util "^27.5.1" +expect@^29.0.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/expect/-/expect-29.7.0.tgz#578874590dcb3214514084c08115d8aee61e11bc" + integrity sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw== + dependencies: + "@jest/expect-utils" "^29.7.0" + jest-get-type "^29.6.3" + jest-matcher-utils "^29.7.0" + jest-message-util "^29.7.0" + jest-util "^29.7.0" + express@^4.17.1: version "4.17.3" resolved "https://registry.yarnpkg.com/express/-/express-4.17.3.tgz#f6c7302194a4fb54271b73a1fe7a06478c8f85a1" @@ -8476,6 +8740,14 @@ http-proxy-agent@^5.0.0: agent-base "6" debug "4" +http-proxy-agent@^7.0.0: + version "7.0.2" + resolved "https://registry.yarnpkg.com/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz#9a8b1f246866c028509486585f62b8f2c18c270e" + integrity sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig== + dependencies: + agent-base "^7.1.0" + debug "^4.3.4" + http-proxy-middleware@^2.0.0: version "2.0.3" resolved "https://registry.yarnpkg.com/http-proxy-middleware/-/http-proxy-middleware-2.0.3.tgz#5df04f69a89f530c2284cd71eeaa51ba52243289" @@ -8523,6 +8795,14 @@ https-proxy-agent@5.0.0, https-proxy-agent@^5.0.0: agent-base "6" debug "4" +https-proxy-agent@^7.0.0: + version "7.0.5" + resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-7.0.5.tgz#9e8b5013873299e11fab6fd548405da2d6c602b2" + integrity sha512-1e4Wqeblerz+tMKPIq2EMGiiWW1dIjZOksyHWSUm1rmuvw/how9hBHZ38lAGj5ID4Ik6EdkOw7NmWPy6LAwalw== + dependencies: + agent-base "^7.0.2" + debug "4" + human-signals@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-1.1.1.tgz#c5b1cd14f50aeae09ab6c59fe63ba3395fe4dfa3" @@ -9402,6 +9682,16 @@ jest-diff@^27.0.0, jest-diff@^27.5.1: jest-get-type "^27.5.1" pretty-format "^27.5.1" +jest-diff@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-29.7.0.tgz#017934a66ebb7ecf6f205e84699be10afd70458a" + integrity sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw== + dependencies: + chalk "^4.0.0" + diff-sequences "^29.6.3" + jest-get-type "^29.6.3" + pretty-format "^29.7.0" + jest-docblock@^27.5.1: version "27.5.1" resolved "https://registry.npmjs.org/jest-docblock/-/jest-docblock-27.5.1.tgz" @@ -9450,6 +9740,11 @@ jest-get-type@^27.0.6, jest-get-type@^27.5.1: resolved "https://registry.npmjs.org/jest-get-type/-/jest-get-type-27.5.1.tgz" integrity sha512-2KY95ksYSaK7DMBWQn6dQz3kqAf3BB64y2udeG+hv4KfSOb9qwcYQstTJc1KCbsix+wLZWZYN8t7nwX3GOBLRw== +jest-get-type@^29.6.3: + version "29.6.3" + resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-29.6.3.tgz#36f499fdcea197c1045a127319c0481723908fd1" + integrity sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw== + jest-haste-map@^27.2.2, jest-haste-map@^27.5.1: version "27.5.1" resolved "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-27.5.1.tgz" @@ -9511,6 +9806,16 @@ jest-matcher-utils@^27.5.1: jest-get-type "^27.5.1" pretty-format "^27.5.1" +jest-matcher-utils@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-29.7.0.tgz#ae8fec79ff249fd592ce80e3ee474e83a6c44f12" + integrity sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g== + dependencies: + chalk "^4.0.0" + jest-diff "^29.7.0" + jest-get-type "^29.6.3" + pretty-format "^29.7.0" + jest-message-util@^27.5.1: version "27.5.1" resolved "https://registry.npmjs.org/jest-message-util/-/jest-message-util-27.5.1.tgz" @@ -9526,6 +9831,21 @@ jest-message-util@^27.5.1: slash "^3.0.0" stack-utils "^2.0.3" +jest-message-util@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-29.7.0.tgz#8bc392e204e95dfe7564abbe72a404e28e51f7f3" + integrity sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w== + dependencies: + "@babel/code-frame" "^7.12.13" + "@jest/types" "^29.6.3" + "@types/stack-utils" "^2.0.0" + chalk "^4.0.0" + graceful-fs "^4.2.9" + micromatch "^4.0.4" + pretty-format "^29.7.0" + slash "^3.0.0" + stack-utils "^2.0.3" + jest-mock@^27.5.1: version "27.5.1" resolved "https://registry.npmjs.org/jest-mock/-/jest-mock-27.5.1.tgz" @@ -9700,6 +10020,18 @@ jest-util@^27.0.0, jest-util@^27.2.0, jest-util@^27.5.1: graceful-fs "^4.2.9" picomatch "^2.2.3" +jest-util@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-29.7.0.tgz#23c2b62bfb22be82b44de98055802ff3710fc0bc" + integrity sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA== + dependencies: + "@jest/types" "^29.6.3" + "@types/node" "*" + chalk "^4.0.0" + ci-info "^3.2.0" + graceful-fs "^4.2.9" + picomatch "^2.2.3" + jest-validate@^27.2.2, jest-validate@^27.5.1: version "27.5.1" resolved "https://registry.npmjs.org/jest-validate/-/jest-validate-27.5.1.tgz" @@ -9725,6 +10057,11 @@ jest-watcher@^27.5.1: jest-util "^27.5.1" string-length "^4.0.1" +jest-when@^3.6.0: + version "3.6.0" + resolved "https://registry.yarnpkg.com/jest-when/-/jest-when-3.6.0.tgz#b46ee408d68f671447b218f2ae6bd93fb5028acf" + integrity sha512-+cZWTy0ekAJo7M9Om0Scdor1jm3wDiYJWmXE8U22UVnkH54YCXAuaqz3P+up/FdtOg8g4wHOxV7Thd7nKhT6Dg== + jest-worker@^27.2.2, jest-worker@^27.4.5, jest-worker@^27.5.1: version "27.5.1" resolved "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz" @@ -9965,6 +10302,22 @@ jsonwebtoken@8.5.1: ms "^2.1.1" semver "^5.6.0" +jsonwebtoken@^9.0.0: + version "9.0.2" + resolved "https://registry.yarnpkg.com/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz#65ff91f4abef1784697d40952bb1998c504caaf3" + integrity sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ== + dependencies: + jws "^3.2.2" + lodash.includes "^4.3.0" + lodash.isboolean "^3.0.3" + lodash.isinteger "^4.0.4" + lodash.isnumber "^3.0.3" + lodash.isplainobject "^4.0.6" + lodash.isstring "^4.0.1" + lodash.once "^4.0.0" + ms "^2.1.1" + semver "^7.5.4" + jsprim@^1.2.2: version "1.4.2" resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.2.tgz#712c65533a15c878ba59e9ed5f0e26d5b77c5feb" @@ -9999,6 +10352,15 @@ jwa@^1.4.1: ecdsa-sig-formatter "1.0.11" safe-buffer "^5.0.1" +jwa@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/jwa/-/jwa-2.0.0.tgz#a7e9c3f29dae94027ebcaf49975c9345593410fc" + integrity sha512-jrZ2Qx916EA+fq9cEAeCROWPTfCwi1IVHqT2tapuqLEVVDKFDENFw1oL+MwrTvH6msKxsd1YTDVw6uKEcsrLEA== + dependencies: + buffer-equal-constant-time "1.0.1" + ecdsa-sig-formatter "1.0.11" + safe-buffer "^5.0.1" + jws@^3.2.2: version "3.2.2" resolved "https://registry.yarnpkg.com/jws/-/jws-3.2.2.tgz#001099f3639468c9414000e99995fa52fb478304" @@ -10007,6 +10369,14 @@ jws@^3.2.2: jwa "^1.4.1" safe-buffer "^5.0.1" +jws@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/jws/-/jws-4.0.0.tgz#2d4e8cf6a318ffaa12615e9dec7e86e6c97310f4" + integrity sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg== + dependencies: + jwa "^2.0.0" + safe-buffer "^5.0.1" + karma-source-map-support@1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/karma-source-map-support/-/karma-source-map-support-1.4.0.tgz#58526ceccf7e8730e56effd97a4de8d712ac0d6b" @@ -10983,7 +11353,7 @@ ms@2.1.2: resolved "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz" integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== -ms@2.1.3, ms@^2.0.0, ms@^2.1.1, ms@^2.1.2: +ms@2.1.3, ms@^2.0.0, ms@^2.1.1, ms@^2.1.2, ms@^2.1.3: version "2.1.3" resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== @@ -11107,6 +11477,11 @@ node-addon-api@^4.3.0: resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-4.3.0.tgz#52a1a0b475193e0928e98e0426a0d1254782b77f" integrity sha512-73sE9+3UaLYYFmDsFZnqCInzPyh3MqIwZO9cw58yIqAZhONrrabrYyYe3TuIqtIiOuTXVhsGau8hcrhhwSsDIQ== +node-addon-api@^5.0.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-5.1.0.tgz#49da1ca055e109a23d537e9de43c09cca21eb762" + integrity sha512-eh0GgfEkpnoWDq+VY8OyvYhFEzBk6jIYbRKdIlyTiAXIVJ8PyBaKb0rp7oDtoddbdoHWhq8wwr+XZ81F1rpNdA== + node-addon-api@^7.0.0: version "7.1.0" resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-7.1.0.tgz#71f609369379c08e251c558527a107107b5e0fdb" @@ -11162,6 +11537,11 @@ node-forge@^1.3.1: resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-1.3.1.tgz#be8da2af243b2417d5f646a770663a92b7e9ded3" integrity sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA== +node-gyp-build@^4.2.0: + version "4.8.2" + resolved "https://registry.yarnpkg.com/node-gyp-build/-/node-gyp-build-4.8.2.tgz#4f802b71c1ab2ca16af830e6c1ea7dd1ad9496fa" + integrity sha512-IRUxE4BVsHWXkV/SFOut4qTlagw2aM8T5/vnTsmrHJvVoKueJHRc/JaFND7QDDc61kLYUJ6qlZM3sqTSyx2dTw== + node-gyp-build@^4.2.2, node-gyp-build@^4.3.0: version "4.3.0" resolved "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.3.0.tgz" @@ -11642,6 +12022,15 @@ open@8.4.0, open@^8.0.9, open@^8.4.0: is-docker "^2.1.1" is-wsl "^2.2.0" +open@^8.0.0: + version "8.4.2" + resolved "https://registry.yarnpkg.com/open/-/open-8.4.2.tgz#5b5ffe2a8f793dcd2aad73e550cb87b59cb084f9" + integrity sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ== + dependencies: + define-lazy-prop "^2.0.0" + is-docker "^2.1.1" + is-wsl "^2.2.0" + opener@^1.5.2: version "1.5.2" resolved "https://registry.yarnpkg.com/opener/-/opener-1.5.2.tgz#5d37e1f35077b9dcac4301372271afdeb2a13598" @@ -12616,6 +13005,15 @@ pretty-format@^27.0.0, pretty-format@^27.2.2, pretty-format@^27.5.1: ansi-styles "^5.0.0" react-is "^17.0.1" +pretty-format@^29.0.0, pretty-format@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-29.7.0.tgz#ca42c758310f365bfa71a0bda0a807160b776812" + integrity sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ== + dependencies: + "@jest/schemas" "^29.6.3" + ansi-styles "^5.0.0" + react-is "^18.0.0" + proc-log@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/proc-log/-/proc-log-1.0.0.tgz#0d927307401f69ed79341e83a0b2c9a13395eb77" @@ -12871,6 +13269,11 @@ react-is@^17.0.1: resolved "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz" integrity sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w== +react-is@^18.0.0: + version "18.3.1" + resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.3.1.tgz#e83557dc12eae63a99e003a46388b1dcbb44db7e" + integrity sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg== + read-cache@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/read-cache/-/read-cache-1.0.0.tgz#e664ef31161166c9751cdbe8dbcf86b5fb58f774" @@ -13399,6 +13802,15 @@ schema-utils@^4.0.0: ajv-formats "^2.1.1" ajv-keywords "^5.0.0" +secp256k1@5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/secp256k1/-/secp256k1-5.0.0.tgz#be6f0c8c7722e2481e9773336d351de8cddd12f7" + integrity sha512-TKWX8xvoGHrxVdqbYeZM9w+izTF4b9z3NhSaDkdn81btvuh+ivbIMGT/zQvDtTFWhRlThpoz6LEYTr7n8A5GcA== + dependencies: + elliptic "^6.5.4" + node-addon-api "^5.0.0" + node-gyp-build "^4.2.0" + seek-bzip@^1.0.5: version "1.0.6" resolved "https://registry.yarnpkg.com/seek-bzip/-/seek-bzip-1.0.6.tgz#35c4171f55a680916b52a07859ecf3b5857f21c4" @@ -13505,6 +13917,11 @@ semver@^6.0.0, semver@^6.1.1, semver@^6.1.2, semver@^6.3.0: resolved "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz" integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== +semver@^7.5.4: + version "7.6.3" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.6.3.tgz#980f7b5550bc175fb4dc09403085627f9eb33143" + integrity sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A== + send@0.17.2: version "0.17.2" resolved "https://registry.yarnpkg.com/send/-/send-0.17.2.tgz#926622f76601c41808012c8bf1688fe3906f7820" @@ -13992,6 +14409,11 @@ std-env@^3.7.0: resolved "https://registry.yarnpkg.com/std-env/-/std-env-3.7.0.tgz#c9f7386ced6ecf13360b6c6c55b8aaa4ef7481d2" integrity sha512-JPbdCEQLj1w5GilpiHAx3qJvFndqybBysA3qUOnznweH4QbNYUsW/ea8QzSrnh0vNsezMMw5bcVool8lM0gwzg== +stoppable@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/stoppable/-/stoppable-1.1.0.tgz#32da568e83ea488b08e4d7ea2c3bcc9d75015d5b" + integrity sha512-KXDYZ9dszj6bzvnEMRYvxgeTHU74QBFL54XKtP3nyMuJ81CFYtABZ3bAzL2EdFUaEwJOBOgENyFj3R7oTzDyyw== + stream-browserify@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/stream-browserify/-/stream-browserify-3.0.0.tgz#22b0a2850cdf6503e73085da1fc7b7d0c2122f2f" @@ -15100,7 +15522,7 @@ uuid@^3.0.1, uuid@^3.3.2: resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee" integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A== -uuid@^8.3.2: +uuid@^8.3.0, uuid@^8.3.2: version "8.3.2" resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2" integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==