From e8d6ac31a8e18847d99d7998bd7658439e48875b Mon Sep 17 00:00:00 2001 From: Victor Anene <62852943+Vickysomtee@users.noreply.github.com> Date: Thu, 26 Jan 2023 20:51:18 +0100 Subject: [PATCH] feat(indy-vdr): add indy-vdr package and indy vdr pool (#1160) work funded by the Government of Ontario Signed-off-by: Victor Anene --- packages/core/tests/helpers.ts | 3 + packages/indy-vdr/README.md | 35 +++ packages/indy-vdr/jest.config.ts | 14 ++ packages/indy-vdr/package.json | 36 +++ packages/indy-vdr/src/error/IndyVdrError.ts | 7 + .../src/error/IndyVdrNotConfiguredError.ts | 7 + .../indy-vdr/src/error/IndyVdrNotFound.ts | 7 + packages/indy-vdr/src/error/index.ts | 3 + packages/indy-vdr/src/index.ts | 6 + packages/indy-vdr/src/pool/IndyVdrPool.ts | 184 +++++++++++++++ .../indy-vdr/src/pool/IndyVdrPoolService.ts | 209 ++++++++++++++++ packages/indy-vdr/src/pool/index.ts | 1 + packages/indy-vdr/src/utils/did.ts | 61 +++++ packages/indy-vdr/src/utils/promises.ts | 44 ++++ .../indy-vdr/tests/indy-vdr-pool.e2e.test.ts | 223 ++++++++++++++++++ packages/indy-vdr/tests/setup.ts | 4 + packages/indy-vdr/tsconfig.build.json | 7 + packages/indy-vdr/tsconfig.json | 6 + yarn.lock | 96 +++++++- 19 files changed, 952 insertions(+), 1 deletion(-) create mode 100644 packages/indy-vdr/README.md create mode 100644 packages/indy-vdr/jest.config.ts create mode 100644 packages/indy-vdr/package.json create mode 100644 packages/indy-vdr/src/error/IndyVdrError.ts create mode 100644 packages/indy-vdr/src/error/IndyVdrNotConfiguredError.ts create mode 100644 packages/indy-vdr/src/error/IndyVdrNotFound.ts create mode 100644 packages/indy-vdr/src/error/index.ts create mode 100644 packages/indy-vdr/src/index.ts create mode 100644 packages/indy-vdr/src/pool/IndyVdrPool.ts create mode 100644 packages/indy-vdr/src/pool/IndyVdrPoolService.ts create mode 100644 packages/indy-vdr/src/pool/index.ts create mode 100644 packages/indy-vdr/src/utils/did.ts create mode 100644 packages/indy-vdr/src/utils/promises.ts create mode 100644 packages/indy-vdr/tests/indy-vdr-pool.e2e.test.ts create mode 100644 packages/indy-vdr/tests/setup.ts create mode 100644 packages/indy-vdr/tsconfig.build.json create mode 100644 packages/indy-vdr/tsconfig.json diff --git a/packages/core/tests/helpers.ts b/packages/core/tests/helpers.ts index 9e57c21943..9718a60975 100644 --- a/packages/core/tests/helpers.ts +++ b/packages/core/tests/helpers.ts @@ -23,6 +23,7 @@ import type { Awaited } from '../src/types' import type { CredDef, Schema } from 'indy-sdk' import type { Observable } from 'rxjs' +import { readFileSync } from 'fs' import path from 'path' import { firstValueFrom, ReplaySubject, Subject } from 'rxjs' import { catchError, filter, map, timeout } from 'rxjs/operators' @@ -83,6 +84,8 @@ export const genesisPath = process.env.GENESIS_TXN_PATH ? path.resolve(process.env.GENESIS_TXN_PATH) : path.join(__dirname, '../../../network/genesis/local-genesis.txn') +export const genesisTransactions = readFileSync(genesisPath).toString('utf-8') + export const publicDidSeed = process.env.TEST_AGENT_PUBLIC_DID_SEED ?? '000000000000000000000000Trustee9' const taaVersion = (process.env.TEST_AGENT_TAA_VERSION ?? '1') as `${number}.${number}` | `${number}` const taaAcceptanceMechanism = process.env.TEST_AGENT_TAA_ACCEPTANCE_MECHANISM ?? 'accept' diff --git a/packages/indy-vdr/README.md b/packages/indy-vdr/README.md new file mode 100644 index 0000000000..310b38a4f9 --- /dev/null +++ b/packages/indy-vdr/README.md @@ -0,0 +1,35 @@ +

+
+ Hyperledger Aries logo +

+

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

+

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

+
+ +### Installation + +### Quick start + +### Example of usage diff --git a/packages/indy-vdr/jest.config.ts b/packages/indy-vdr/jest.config.ts new file mode 100644 index 0000000000..55c67d70a6 --- /dev/null +++ b/packages/indy-vdr/jest.config.ts @@ -0,0 +1,14 @@ +import type { Config } from '@jest/types' + +import base from '../../jest.config.base' + +import packageJson from './package.json' + +const config: Config.InitialOptions = { + ...base, + name: packageJson.name, + displayName: packageJson.name, + setupFilesAfterEnv: ['./tests/setup.ts'], +} + +export default config diff --git a/packages/indy-vdr/package.json b/packages/indy-vdr/package.json new file mode 100644 index 0000000000..32c8689d5d --- /dev/null +++ b/packages/indy-vdr/package.json @@ -0,0 +1,36 @@ +{ + "name": "@aries-framework/indy-vdr", + "main": "build/index", + "types": "build/index", + "version": "0.3.3", + "private": true, + "files": [ + "build" + ], + "license": "Apache-2.0", + "publishConfig": { + "access": "public" + }, + "homepage": "https://github.com/hyperledger/aries-framework-javascript/tree/main/packages/indy-vdr", + "repository": { + "type": "git", + "url": "https://github.com/hyperledger/aries-framework-javascript", + "directory": "packages/indy-vdr" + }, + "scripts": { + "build": "yarn run clean && yarn run compile", + "clean": "rimraf ./build", + "compile": "tsc -p tsconfig.build.json", + "prepublishOnly": "yarn run build", + "test": "jest" + }, + "dependencies": { + "@aries-framework/core": "0.3.3", + "indy-vdr-test-shared": "^0.1.3" + }, + "devDependencies": { + "indy-vdr-test-nodejs": "^0.1.3", + "rimraf": "~4.0.7", + "typescript": "~4.9.4" + } +} diff --git a/packages/indy-vdr/src/error/IndyVdrError.ts b/packages/indy-vdr/src/error/IndyVdrError.ts new file mode 100644 index 0000000000..501f428640 --- /dev/null +++ b/packages/indy-vdr/src/error/IndyVdrError.ts @@ -0,0 +1,7 @@ +import { AriesFrameworkError } from '@aries-framework/core' + +export class IndyVdrError extends AriesFrameworkError { + public constructor(message: string, { cause }: { cause?: Error } = {}) { + super(message, { cause }) + } +} diff --git a/packages/indy-vdr/src/error/IndyVdrNotConfiguredError.ts b/packages/indy-vdr/src/error/IndyVdrNotConfiguredError.ts new file mode 100644 index 0000000000..75cf40c9f6 --- /dev/null +++ b/packages/indy-vdr/src/error/IndyVdrNotConfiguredError.ts @@ -0,0 +1,7 @@ +import { IndyVdrError } from './IndyVdrError' + +export class IndyVdrNotConfiguredError extends IndyVdrError { + public constructor(message: string, { cause }: { cause?: Error } = {}) { + super(message, { cause }) + } +} diff --git a/packages/indy-vdr/src/error/IndyVdrNotFound.ts b/packages/indy-vdr/src/error/IndyVdrNotFound.ts new file mode 100644 index 0000000000..00b1b94c47 --- /dev/null +++ b/packages/indy-vdr/src/error/IndyVdrNotFound.ts @@ -0,0 +1,7 @@ +import { IndyVdrError } from './IndyVdrError' + +export class IndyVdrNotFoundError extends IndyVdrError { + public constructor(message: string, { cause }: { cause?: Error } = {}) { + super(message, { cause }) + } +} diff --git a/packages/indy-vdr/src/error/index.ts b/packages/indy-vdr/src/error/index.ts new file mode 100644 index 0000000000..f062bfbed0 --- /dev/null +++ b/packages/indy-vdr/src/error/index.ts @@ -0,0 +1,3 @@ +export * from './IndyVdrError' +export * from './IndyVdrNotFound' +export * from './IndyVdrNotConfiguredError' diff --git a/packages/indy-vdr/src/index.ts b/packages/indy-vdr/src/index.ts new file mode 100644 index 0000000000..8a5ca6c21a --- /dev/null +++ b/packages/indy-vdr/src/index.ts @@ -0,0 +1,6 @@ +try { + // eslint-disable-next-line import/no-extraneous-dependencies + require('indy-vdr-test-nodejs') +} catch (error) { + throw new Error('Error registering nodejs bindings for Indy VDR') +} diff --git a/packages/indy-vdr/src/pool/IndyVdrPool.ts b/packages/indy-vdr/src/pool/IndyVdrPool.ts new file mode 100644 index 0000000000..6958d882ab --- /dev/null +++ b/packages/indy-vdr/src/pool/IndyVdrPool.ts @@ -0,0 +1,184 @@ +import type { Logger, AgentContext, Key } from '@aries-framework/core' +import type { IndyVdrRequest, IndyVdrPool as indyVdrPool } from 'indy-vdr-test-shared' + +import { TypedArrayEncoder } from '@aries-framework/core' +import { + GetTransactionAuthorAgreementRequest, + GetAcceptanceMechanismsRequest, + PoolCreate, + indyVdr, +} from 'indy-vdr-test-shared' + +import { IndyVdrError } from '../error' + +export interface TransactionAuthorAgreement { + version?: `${number}.${number}` | `${number}` + acceptanceMechanism: string +} + +export interface AuthorAgreement { + digest: string + version: string + text: string + ratification_ts: number + acceptanceMechanisms: AcceptanceMechanisms +} + +export interface AcceptanceMechanisms { + aml: Record + amlContext: string + version: string +} + +export interface IndyVdrPoolConfig { + genesisTransactions: string + isProduction: boolean + indyNamespace: string + transactionAuthorAgreement?: TransactionAuthorAgreement +} + +export class IndyVdrPool { + private _pool?: indyVdrPool + private logger: Logger + private poolConfig: IndyVdrPoolConfig + public authorAgreement?: AuthorAgreement | null + + public constructor(poolConfig: IndyVdrPoolConfig, logger: Logger) { + this.logger = logger + this.poolConfig = poolConfig + } + + public get indyNamespace(): string { + return this.poolConfig.indyNamespace + } + + public get config() { + return this.poolConfig + } + + public async connect() { + this._pool = new PoolCreate({ + parameters: { + transactions: this.config.genesisTransactions, + }, + }) + + return this.pool.handle + } + + private get pool(): indyVdrPool { + if (!this._pool) { + throw new IndyVdrError('Pool is not connected. Make sure to call .connect() first') + } + + return this._pool + } + + public close() { + if (!this.pool) { + throw new IndyVdrError("Can't close pool. Pool is not connected") + } + + // FIXME: this method doesn't work?? + // this.pool.close() + } + + public async submitWriteRequest( + agentContext: AgentContext, + request: Request, + signingKey: Key + ) { + await this.appendTaa(request) + + const signature = await agentContext.wallet.sign({ + data: TypedArrayEncoder.fromString(request.signatureInput), + key: signingKey, + }) + + request.setSignature({ + signature, + }) + + return await this.pool.submitRequest(request) + } + + public async submitReadRequest(request: Request) { + return await this.pool.submitRequest(request) + } + + private async appendTaa(request: IndyVdrRequest) { + const authorAgreement = await this.getTransactionAuthorAgreement() + const poolTaa = this.config.transactionAuthorAgreement + + // If ledger does not have TAA, we can just send request + if (authorAgreement == null) { + return request + } + + // Ledger has taa but user has not specified which one to use + if (!poolTaa) { + throw new IndyVdrError( + `Please, specify a transaction author agreement with version and acceptance mechanism. ${JSON.stringify( + authorAgreement + )}` + ) + } + + // Throw an error if the pool doesn't have the specified version and acceptance mechanism + if ( + authorAgreement.version !== poolTaa.version || + !authorAgreement.acceptanceMechanisms.aml[poolTaa.acceptanceMechanism] + ) { + // Throw an error with a helpful message + const errMessage = `Unable to satisfy matching TAA with mechanism ${JSON.stringify( + poolTaa.acceptanceMechanism + )} and version ${poolTaa.version} in pool.\n Found ${JSON.stringify( + authorAgreement.acceptanceMechanisms.aml + )} and version ${authorAgreement.version} in pool.` + throw new IndyVdrError(errMessage) + } + + const acceptance = indyVdr.prepareTxnAuthorAgreementAcceptance({ + text: authorAgreement.text, + version: authorAgreement.version, + taaDigest: authorAgreement.digest, + time: Math.floor(new Date().getTime() / 1000), + acceptanceMechanismType: poolTaa.acceptanceMechanism, + }) + + request.setTransactionAuthorAgreementAcceptance({ acceptance }) + } + + private async getTransactionAuthorAgreement(): Promise { + // TODO Replace this condition with memoization + if (this.authorAgreement !== undefined) { + return this.authorAgreement + } + + const taaRequest = new GetTransactionAuthorAgreementRequest({}) + const taaResponse = await this.submitReadRequest(taaRequest) + + const acceptanceMechanismRequest = new GetAcceptanceMechanismsRequest({}) + const acceptanceMechanismResponse = await this.submitReadRequest(acceptanceMechanismRequest) + + const taaData = taaResponse.result.data + + // TAA can be null + if (taaData == null) { + this.authorAgreement = null + return null + } + + // If TAA is not null, we can be sure AcceptanceMechanisms is also not null + const authorAgreement = taaData as Omit + + // FIME: remove cast when https://github.com/hyperledger/indy-vdr/pull/142 is released + const acceptanceMechanisms = acceptanceMechanismResponse.result.data as unknown as AcceptanceMechanisms + this.authorAgreement = { + ...authorAgreement, + acceptanceMechanisms, + } + + return this.authorAgreement + } +} diff --git a/packages/indy-vdr/src/pool/IndyVdrPoolService.ts b/packages/indy-vdr/src/pool/IndyVdrPoolService.ts new file mode 100644 index 0000000000..2cc1b5a206 --- /dev/null +++ b/packages/indy-vdr/src/pool/IndyVdrPoolService.ts @@ -0,0 +1,209 @@ +import type { IndyVdrPoolConfig } from './IndyVdrPool' +import type { AgentContext } from '@aries-framework/core' +import type { GetNymResponse } from 'indy-vdr-test-shared' + +import { Logger, InjectionSymbols, injectable, inject, CacheModuleConfig } from '@aries-framework/core' +import { GetNymRequest } from 'indy-vdr-test-shared' + +import { IndyVdrError, IndyVdrNotFoundError, IndyVdrNotConfiguredError } from '../error' +import { isSelfCertifiedDid, DID_INDY_REGEX } from '../utils/did' +import { allSettled, onlyFulfilled, onlyRejected } from '../utils/promises' + +import { IndyVdrPool } from './IndyVdrPool' + +export interface CachedDidResponse { + nymResponse: { + did: string + verkey: string + } + indyNamespace: string +} +@injectable() +export class IndyVdrPoolService { + public pools: IndyVdrPool[] = [] + private logger: Logger + + public constructor(@inject(InjectionSymbols.Logger) logger: Logger) { + this.logger = logger + } + + public setPools(poolConfigs: IndyVdrPoolConfig[]) { + this.pools = poolConfigs.map((poolConfig) => new IndyVdrPool(poolConfig, this.logger)) + } + + /** + * Create connections to all ledger pools + */ + public async connectToPools() { + const handleArray: number[] = [] + // Sequentially connect to pools so we don't use up too many resources connecting in parallel + for (const pool of this.pools) { + this.logger.debug(`Connecting to pool: ${pool.indyNamespace}`) + const poolHandle = await pool.connect() + this.logger.debug(`Finished connection to pool: ${pool.indyNamespace}`) + handleArray.push(poolHandle) + } + return handleArray + } + + /** + * Get the most appropriate pool for the given did. + * If the did is a qualified indy did, the pool will be determined based on the namespace. + * If it is a legacy unqualified indy did, the pool will be determined based on the algorithm as described in this document: + * https://docs.google.com/document/d/109C_eMsuZnTnYe2OAd02jAts1vC4axwEKIq7_4dnNVA/edit + */ + public async getPoolForDid(agentContext: AgentContext, did: string): Promise { + // Check if the did starts with did:indy + const match = did.match(DID_INDY_REGEX) + + if (match) { + const [, namespace] = match + + const pool = this.getPoolForNamespace(namespace) + + if (pool) return pool + + throw new IndyVdrError(`Pool for indy namespace '${namespace}' not found`) + } else { + return await this.getPoolForLegacyDid(agentContext, did) + } + } + + private async getPoolForLegacyDid(agentContext: AgentContext, did: string): Promise { + const pools = this.pools + + if (pools.length === 0) { + throw new IndyVdrNotConfiguredError( + "No indy ledgers configured. Provide at least one pool configuration in the 'indyLedgers' agent configuration" + ) + } + + const didCache = agentContext.dependencyManager.resolve(CacheModuleConfig).cache + + const cachedNymResponse = await didCache.get(agentContext, `IndyVdrPoolService:${did}`) + const pool = this.pools.find((pool) => pool.indyNamespace === cachedNymResponse?.indyNamespace) + + // If we have the nym response with associated pool in the cache, we'll use that + if (cachedNymResponse && pool) { + this.logger.trace(`Found ledger id '${pool.indyNamespace}' for did '${did}' in cache`) + return pool + } + + const { successful, rejected } = await this.getSettledDidResponsesFromPools(did, pools) + + if (successful.length === 0) { + const allNotFound = rejected.every((e) => e.reason instanceof IndyVdrNotFoundError) + const rejectedOtherThanNotFound = rejected.filter((e) => !(e.reason instanceof IndyVdrNotFoundError)) + + // All ledgers returned response that the did was not found + if (allNotFound) { + throw new IndyVdrNotFoundError(`Did '${did}' not found on any of the ledgers (total ${this.pools.length}).`) + } + + // one or more of the ledgers returned an unknown error + throw new IndyVdrError( + `Unknown error retrieving did '${did}' from '${rejectedOtherThanNotFound.length}' of '${pools.length}' ledgers`, + { cause: rejectedOtherThanNotFound[0].reason } + ) + } + + // If there are self certified DIDs we always prefer it over non self certified DIDs + // We take the first self certifying DID as we take the order in the + // indyLedgers config as the order of preference of ledgers + let value = successful.find((response) => + isSelfCertifiedDid(response.value.did.nymResponse.did, response.value.did.nymResponse.verkey) + )?.value + + if (!value) { + // Split between production and nonProduction ledgers. If there is at least one + // successful response from a production ledger, only keep production ledgers + // otherwise we only keep the non production ledgers. + const production = successful.filter((s) => s.value.pool.config.isProduction) + const nonProduction = successful.filter((s) => !s.value.pool.config.isProduction) + const productionOrNonProduction = production.length >= 1 ? production : nonProduction + + // We take the first value as we take the order in the indyLedgers config as + // the order of preference of ledgers + value = productionOrNonProduction[0].value + } + + await didCache.set(agentContext, did, { + nymResponse: { + did: value.did.nymResponse.did, + verkey: value.did.nymResponse.verkey, + }, + indyNamespace: value.did.indyNamespace, + }) + return value.pool + } + + private async getSettledDidResponsesFromPools(did: string, pools: IndyVdrPool[]) { + this.logger.trace(`Retrieving did '${did}' from ${pools.length} ledgers`) + const didResponses = await allSettled(pools.map((pool) => this.getDidFromPool(did, pool))) + + const successful = onlyFulfilled(didResponses) + this.logger.trace(`Retrieved ${successful.length} responses from ledgers for did '${did}'`) + + const rejected = onlyRejected(didResponses) + + return { + rejected, + successful, + } + } + + /** + * Get the most appropriate pool for the given indyNamespace + */ + public getPoolForNamespace(indyNamespace: string) { + if (this.pools.length === 0) { + throw new IndyVdrNotConfiguredError( + "No indy ledgers configured. Provide at least one pool configuration in the 'indyLedgers' agent configuration" + ) + } + + const pool = this.pools.find((pool) => pool.indyNamespace === indyNamespace) + + if (!pool) { + throw new IndyVdrError(`No ledgers found for IndyNamespace '${indyNamespace}'.`) + } + + return pool + } + + private async getDidFromPool(did: string, pool: IndyVdrPool): Promise { + try { + this.logger.trace(`Get public did '${did}' from ledger '${pool.indyNamespace}'`) + const request = await new GetNymRequest({ dest: did }) + + this.logger.trace(`Submitting get did request for did '${did}' to ledger '${pool.indyNamespace}'`) + const response = await pool.submitReadRequest(request) + + if (!response.result.data) { + throw new IndyVdrNotFoundError(`Did ${did} not found on indy pool with namespace ${pool.indyNamespace}`) + } + + const result = JSON.parse(response.result.data) + + this.logger.trace(`Retrieved did '${did}' from ledger '${pool.indyNamespace}'`, result) + + return { + did: result, + pool, + response, + } + } catch (error) { + this.logger.trace(`Error retrieving did '${did}' from ledger '${pool.indyNamespace}'`, { + error, + did, + }) + throw error + } + } +} + +export interface PublicDidRequest { + did: CachedDidResponse + pool: IndyVdrPool + response: GetNymResponse +} diff --git a/packages/indy-vdr/src/pool/index.ts b/packages/indy-vdr/src/pool/index.ts new file mode 100644 index 0000000000..1e1f1b52f8 --- /dev/null +++ b/packages/indy-vdr/src/pool/index.ts @@ -0,0 +1 @@ +export * from './IndyVdrPool' diff --git a/packages/indy-vdr/src/utils/did.ts b/packages/indy-vdr/src/utils/did.ts new file mode 100644 index 0000000000..9cda8ee95d --- /dev/null +++ b/packages/indy-vdr/src/utils/did.ts @@ -0,0 +1,61 @@ +/** + * Based on DidUtils implementation in Aries Framework .NET + * @see: https://github.com/hyperledger/aries-framework-dotnet/blob/f90eaf9db8548f6fc831abea917e906201755763/src/Hyperledger.Aries/Utils/DidUtils.cs + * + * Some context about full verkeys versus abbreviated verkeys: + * A standard verkey is 32 bytes, and by default in Indy the DID is chosen as the first 16 bytes of that key, before base58 encoding. + * An abbreviated verkey replaces the first 16 bytes of the verkey with ~ when it matches the DID. + * + * When a full verkey is used to register on the ledger, this is stored as a full verkey on the ledger and also returned from the ledger as a full verkey. + * The same applies to an abbreviated verkey. If an abbreviated verkey is used to register on the ledger, this is stored as an abbreviated verkey on the ledger and also returned from the ledger as an abbreviated verkey. + * + * For this reason we need some methods to check whether verkeys are full or abbreviated, so we can align this with `indy.abbreviateVerkey` + * + * Aries Framework .NET also abbreviates verkey before sending to ledger: + * https://github.com/hyperledger/aries-framework-dotnet/blob/f90eaf9db8548f6fc831abea917e906201755763/src/Hyperledger.Aries/Ledger/DefaultLedgerService.cs#L139-L147 + */ + +import { TypedArrayEncoder } from '@aries-framework/core' + +export const DID_INDY_REGEX = /^did:indy:((?:[a-z][_a-z0-9-]*)(?::[a-z][_a-z0-9-]*)):([1-9A-HJ-NP-Za-km-z]{21,22})$/ +export const ABBREVIATED_VERKEY_REGEX = /^~[1-9A-HJ-NP-Za-km-z]{21,22}$/ + +/** + * Check whether the did is a self certifying did. If the verkey is abbreviated this method + * will always return true. Make sure that the verkey you pass in this method belongs to the + * did passed in + * + * @return Boolean indicating whether the did is self certifying + */ +export function isSelfCertifiedDid(did: string, verkey: string): boolean { + // If the verkey is Abbreviated, it means the full verkey + // is the did + the verkey + if (isAbbreviatedVerkey(verkey)) { + return true + } + + const didFromVerkey = indyDidFromPublicKeyBase58(verkey) + + if (didFromVerkey === did) { + return true + } + + return false +} + +export function indyDidFromPublicKeyBase58(publicKeyBase58: string): string { + const buffer = TypedArrayEncoder.fromBase58(publicKeyBase58) + + const did = TypedArrayEncoder.toBase58(buffer.slice(0, 16)) + + return did +} + +/** + * Check a base58 encoded string against a regex expression to determine if it is a valid abbreviated verkey + * @param verkey Base58 encoded string representation of an abbreviated verkey + * @returns Boolean indicating if the string is a valid abbreviated verkey + */ +export function isAbbreviatedVerkey(verkey: string): boolean { + return ABBREVIATED_VERKEY_REGEX.test(verkey) +} diff --git a/packages/indy-vdr/src/utils/promises.ts b/packages/indy-vdr/src/utils/promises.ts new file mode 100644 index 0000000000..0e843d73b5 --- /dev/null +++ b/packages/indy-vdr/src/utils/promises.ts @@ -0,0 +1,44 @@ +// This file polyfills the allSettled method introduced in ESNext + +export type AllSettledFulfilled = { + status: 'fulfilled' + value: T +} + +export type AllSettledRejected = { + status: 'rejected' + // eslint-disable-next-line @typescript-eslint/no-explicit-any + reason: any +} + +export function allSettled(promises: Promise[]) { + return Promise.all( + promises.map((p) => + p + .then( + (value) => + ({ + status: 'fulfilled', + value, + } as AllSettledFulfilled) + ) + .catch( + (reason) => + ({ + status: 'rejected', + reason, + } as AllSettledRejected) + ) + ) + ) +} + +export function onlyFulfilled(entries: Array | AllSettledRejected>) { + // We filter for only the rejected values, so we can safely cast the type + return entries.filter((e) => e.status === 'fulfilled') as AllSettledFulfilled[] +} + +export function onlyRejected(entries: Array | AllSettledRejected>) { + // We filter for only the rejected values, so we can safely cast the type + return entries.filter((e) => e.status === 'rejected') as AllSettledRejected[] +} diff --git a/packages/indy-vdr/tests/indy-vdr-pool.e2e.test.ts b/packages/indy-vdr/tests/indy-vdr-pool.e2e.test.ts new file mode 100644 index 0000000000..5920344527 --- /dev/null +++ b/packages/indy-vdr/tests/indy-vdr-pool.e2e.test.ts @@ -0,0 +1,223 @@ +import type { Key } from '@aries-framework/core' + +import { IndyWallet, KeyType, SigningProviderRegistry, TypedArrayEncoder } from '@aries-framework/core' +import { GetNymRequest, NymRequest, SchemaRequest, CredentialDefinitionRequest } from 'indy-vdr-test-shared' + +import { agentDependencies, genesisTransactions, getAgentConfig, getAgentContext } from '../../core/tests/helpers' +import testLogger from '../../core/tests/logger' +import { IndyVdrPool } from '../src/pool' +import { IndyVdrPoolService } from '../src/pool/IndyVdrPoolService' + +const indyVdrPoolService = new IndyVdrPoolService(testLogger) +const wallet = new IndyWallet(agentDependencies, testLogger, new SigningProviderRegistry([])) +const agentConfig = getAgentConfig('IndyVdrPoolService') +const agentContext = getAgentContext({ wallet, agentConfig }) + +const config = { + isProduction: false, + genesisTransactions, + indyNamespace: `pool:localtest`, + transactionAuthorAgreement: { version: '1', acceptanceMechanism: 'accept' }, +} as const + +let signerKey: Key + +indyVdrPoolService.setPools([config]) + +describe('IndyVdrPoolService', () => { + beforeAll(async () => { + await indyVdrPoolService.connectToPools() + + if (agentConfig.walletConfig) { + await wallet.createAndOpen(agentConfig.walletConfig) + } + + signerKey = await wallet.createKey({ seed: '000000000000000000000000Trustee9', keyType: KeyType.Ed25519 }) + }) + + afterAll(async () => { + for (const pool of indyVdrPoolService.pools) { + pool.close() + } + + await wallet.delete() + }) + + describe('DIDs', () => { + test('can get a pool based on the namespace', async () => { + const pool = indyVdrPoolService.getPoolForNamespace('pool:localtest') + expect(pool).toBeInstanceOf(IndyVdrPool) + expect(pool.config).toEqual(config) + }) + + test('can resolve a did using the pool', async () => { + const pool = indyVdrPoolService.getPoolForNamespace('pool:localtest') + + const request = new GetNymRequest({ + dest: 'TL1EaPFCZ8Si5aUrqScBDt', + }) + + const response = await pool.submitReadRequest(request) + + expect(response).toMatchObject({ + op: 'REPLY', + result: { + dest: 'TL1EaPFCZ8Si5aUrqScBDt', + type: '105', + data: expect.any(String), + identifier: 'LibindyDid111111111111', + reqId: expect.any(Number), + seqNo: expect.any(Number), + txnTime: expect.any(Number), + state_proof: expect.any(Object), + }, + }) + + expect(JSON.parse(response.result.data as string)).toMatchObject({ + dest: 'TL1EaPFCZ8Si5aUrqScBDt', + identifier: 'V4SGRU86Z58d6TV7PBUe6f', + role: '0', + seqNo: expect.any(Number), + txnTime: expect.any(Number), + verkey: '~43X4NhAFqREffK7eWdKgFH', + }) + }) + + test('can write a did using the pool', async () => { + const pool = indyVdrPoolService.getPoolForNamespace('pool:localtest') + + // prepare the DID we are going to write to the ledger + const key = await wallet.createKey({ keyType: KeyType.Ed25519 }) + const buffer = TypedArrayEncoder.fromBase58(key.publicKeyBase58) + const did = TypedArrayEncoder.toBase58(buffer.slice(0, 16)) + + const request = new NymRequest({ + dest: did, + submitterDid: 'TL1EaPFCZ8Si5aUrqScBDt', + verkey: key.publicKeyBase58, + }) + + const response = await pool.submitWriteRequest(agentContext, request, signerKey) + + expect(response).toMatchObject({ + op: 'REPLY', + result: { + txn: { + protocolVersion: 2, + metadata: expect.any(Object), + data: expect.any(Object), + type: '1', + }, + ver: '1', + rootHash: expect.any(String), + txnMetadata: expect.any(Object), + }, + }) + }) + }) + + describe('Schemas & credential Definition', () => { + test('can write a schema using the pool', async () => { + const pool = indyVdrPoolService.getPoolForNamespace('pool:localtest') + + const dynamicVersion = `1.${Math.random() * 100}` + + const schemaRequest = new SchemaRequest({ + submitterDid: 'TL1EaPFCZ8Si5aUrqScBDt', + schema: { + id: 'test-schema-id', + name: 'test-schema', + ver: '1.0', + version: dynamicVersion, + attrNames: ['first_name', 'last_name', 'age'], + }, + }) + + const schemaResponse = await pool.submitWriteRequest(agentContext, schemaRequest, signerKey) + + expect(schemaResponse).toMatchObject({ + op: 'REPLY', + result: { + ver: '1', + txn: { + metadata: expect.any(Object), + type: '101', + data: { + data: { + attr_names: expect.arrayContaining(['age', 'last_name', 'first_name']), + name: 'test-schema', + version: dynamicVersion, + }, + }, + }, + }, + }) + + const credentialDefinitionRequest = new CredentialDefinitionRequest({ + submitterDid: 'TL1EaPFCZ8Si5aUrqScBDt', + credentialDefinition: { + ver: '1.0', + id: `TL1EaPFCZ8Si5aUrqScBDt:3:CL:${schemaResponse.result.txnMetadata.seqNo}:TAG`, + // must be string version of the schema seqNo + schemaId: `${schemaResponse.result.txnMetadata.seqNo}`, + type: 'CL', + tag: 'TAG', + value: { + primary: { + n: '95671911213029889766246243339609567053285242961853979532076192834533577534909796042025401129640348836502648821408485216223269830089771714177855160978214805993386076928594836829216646288195127289421136294309746871614765411402917891972999085287429566166932354413679994469616357622976775651506242447852304853465380257226445481515631782793575184420720296120464167257703633829902427169144462981949944348928086406211627174233811365419264314148304536534528344413738913277713548403058098093453580992173145127632199215550027527631259565822872315784889212327945030315062879193999012349220118290071491899498795367403447663354833', + s: '1573939820553851804028472930351082111827449763317396231059458630252708273163050576299697385049087601314071156646675105028237105229428440185022593174121924731226634356276616495327358864865629675802738680754755949997611920669823449540027707876555408118172529688443208301403297680159171306000341239398135896274940688268460793682007115152428685521865921925309154307574955324973580144009271977076586453011938089159885164705002797196738438392179082905738155386545935208094240038135576042886730802817809757582039362798495805441520744154270346780731494125065136433163757697326955962282840631850597919384092584727207908978907', + r: { + master_secret: + '51468326064458249697956272807708948542001661888325200180968238787091473418947480867518174106588127385097619219536294589148765074804124925845579871788369264160902401097166484002617399484700234182426993061977152961670486891123188739266793651668791365808983166555735631354925174224786218771453042042304773095663181121735652667614424198057134974727791329623974680096491276337756445057223988781749506082654194307092164895251308088903000573135447235553684949564809677864522417041639512933806794232354223826262154508950271949764583849083972967642587778197779127063591201123312548182885603427440981731822883101260509710567731', + last_name: + '35864556460959997092903171610228165251001245539613587319116151716453114432309327039517115215674024166920383445379522674504803469517283236033110568676156285676664363558333716898161685255450536856645604857714925836474250821415182026707218622134953915013803750771185050002646661004119778318524426368842019753903741998256374803456282688037624993010626333853831264356355867746685055670790915539230702546586615988121383960277550317876816983602795121749533628953449405383896799464872758725899520173321672584180060465965090049734285011738428381648013150818429882628144544132356242262467090140003979917439514443707537952643217', + first_name: + '26405366527417391838431479783966663952336302347775179063968690502492620867161212873635806190080000833725932174641667734138216137047349915190546601368424742647800764149890590518336588437317392528514313749533980651547425554257026971104775208127915118918084350210726664749850578299247705298976657301433446491575776774836993110356033664644761593799921221474617858131678955318702706530853801195330271860527250931569815553226145458665481867408279941785848264018364216087471931232367137301987457054918438087686484522112532447779498424748261678616461026788516567300969886029412198319909977473167405879110243445062391837349387', + age: '19865805272519696320755573045337531955436490760876870776207490804137339344112305203631892390827288264857621916650098902064979838987400911652887344763586495880167030031364467726355103327059673023946234460960685398768709062405377107912774045508870580108596597470880834205563197111550140867466625683117333370595295321833757429488192170551320637065066368716366317421169802474954914904380304190861641082310805418122837214965865969459724848071006870574514215255412289237027267424055400593307112849859757094597401668252862525566316402695830217450073667487951799749275437192883439584518905943435472478496028380016245355151988', + }, + rctxt: + '17146114573198643698878017247599007910707723139165264508694101989891626297408755744139587708989465136799243292477223763665064840330721616213638280284119891715514951989022398510785960099708705561761504012512387129498731093386014964896897751536856287377064154297370092339714578039195258061017640952790913108285519632654466006255438773382930416822756630391947263044087385305540191237328903426888518439803354213792647775798033294505898635058814132665832000734168261793545453678083703704122695006541391598116359796491845268631009298069826949515604008666680160398698425061157356267086946953480945396595351944425658076127674', + z: '57056568014385132434061065334124327103768023932445648883765905576432733866307137325457775876741578717650388638737098805750938053855430851133826479968450532729423746605371536096355616166421996729493639634413002114547787617999178137950004782677177313856876420539744625174205603354705595789330008560775613287118432593300023801651460885523314713996258581986238928077688246511704050386525431448517516821261983193275502089060128363906909778842476516981025598807378338053788433033754999771876361716562378445777250912525673660842724168260417083076824975992327559199634032439358787956784395443246565622469187082767614421691234', + }, + }, + }, + }) + + const response = await pool.submitWriteRequest(agentContext, credentialDefinitionRequest, signerKey) + + expect(response).toMatchObject({ + op: 'REPLY', + result: { + ver: '1', + txn: { + metadata: expect.any(Object), + type: '102', + data: { + data: { + primary: { + r: { + last_name: + '35864556460959997092903171610228165251001245539613587319116151716453114432309327039517115215674024166920383445379522674504803469517283236033110568676156285676664363558333716898161685255450536856645604857714925836474250821415182026707218622134953915013803750771185050002646661004119778318524426368842019753903741998256374803456282688037624993010626333853831264356355867746685055670790915539230702546586615988121383960277550317876816983602795121749533628953449405383896799464872758725899520173321672584180060465965090049734285011738428381648013150818429882628144544132356242262467090140003979917439514443707537952643217', + first_name: + '26405366527417391838431479783966663952336302347775179063968690502492620867161212873635806190080000833725932174641667734138216137047349915190546601368424742647800764149890590518336588437317392528514313749533980651547425554257026971104775208127915118918084350210726664749850578299247705298976657301433446491575776774836993110356033664644761593799921221474617858131678955318702706530853801195330271860527250931569815553226145458665481867408279941785848264018364216087471931232367137301987457054918438087686484522112532447779498424748261678616461026788516567300969886029412198319909977473167405879110243445062391837349387', + age: '19865805272519696320755573045337531955436490760876870776207490804137339344112305203631892390827288264857621916650098902064979838987400911652887344763586495880167030031364467726355103327059673023946234460960685398768709062405377107912774045508870580108596597470880834205563197111550140867466625683117333370595295321833757429488192170551320637065066368716366317421169802474954914904380304190861641082310805418122837214965865969459724848071006870574514215255412289237027267424055400593307112849859757094597401668252862525566316402695830217450073667487951799749275437192883439584518905943435472478496028380016245355151988', + master_secret: + '51468326064458249697956272807708948542001661888325200180968238787091473418947480867518174106588127385097619219536294589148765074804124925845579871788369264160902401097166484002617399484700234182426993061977152961670486891123188739266793651668791365808983166555735631354925174224786218771453042042304773095663181121735652667614424198057134974727791329623974680096491276337756445057223988781749506082654194307092164895251308088903000573135447235553684949564809677864522417041639512933806794232354223826262154508950271949764583849083972967642587778197779127063591201123312548182885603427440981731822883101260509710567731', + }, + z: '57056568014385132434061065334124327103768023932445648883765905576432733866307137325457775876741578717650388638737098805750938053855430851133826479968450532729423746605371536096355616166421996729493639634413002114547787617999178137950004782677177313856876420539744625174205603354705595789330008560775613287118432593300023801651460885523314713996258581986238928077688246511704050386525431448517516821261983193275502089060128363906909778842476516981025598807378338053788433033754999771876361716562378445777250912525673660842724168260417083076824975992327559199634032439358787956784395443246565622469187082767614421691234', + rctxt: + '17146114573198643698878017247599007910707723139165264508694101989891626297408755744139587708989465136799243292477223763665064840330721616213638280284119891715514951989022398510785960099708705561761504012512387129498731093386014964896897751536856287377064154297370092339714578039195258061017640952790913108285519632654466006255438773382930416822756630391947263044087385305540191237328903426888518439803354213792647775798033294505898635058814132665832000734168261793545453678083703704122695006541391598116359796491845268631009298069826949515604008666680160398698425061157356267086946953480945396595351944425658076127674', + n: '95671911213029889766246243339609567053285242961853979532076192834533577534909796042025401129640348836502648821408485216223269830089771714177855160978214805993386076928594836829216646288195127289421136294309746871614765411402917891972999085287429566166932354413679994469616357622976775651506242447852304853465380257226445481515631782793575184420720296120464167257703633829902427169144462981949944348928086406211627174233811365419264314148304536534528344413738913277713548403058098093453580992173145127632199215550027527631259565822872315784889212327945030315062879193999012349220118290071491899498795367403447663354833', + s: '1573939820553851804028472930351082111827449763317396231059458630252708273163050576299697385049087601314071156646675105028237105229428440185022593174121924731226634356276616495327358864865629675802738680754755949997611920669823449540027707876555408118172529688443208301403297680159171306000341239398135896274940688268460793682007115152428685521865921925309154307574955324973580144009271977076586453011938089159885164705002797196738438392179082905738155386545935208094240038135576042886730802817809757582039362798495805441520744154270346780731494125065136433163757697326955962282840631850597919384092584727207908978907', + }, + }, + signature_type: 'CL', + ref: schemaResponse.result.txnMetadata.seqNo, + tag: 'TAG', + }, + }, + }, + }) + }) + }) +}) diff --git a/packages/indy-vdr/tests/setup.ts b/packages/indy-vdr/tests/setup.ts new file mode 100644 index 0000000000..ce7749d25e --- /dev/null +++ b/packages/indy-vdr/tests/setup.ts @@ -0,0 +1,4 @@ +// Needed to register indy-vdr node bindings +import '../src/index' + +jest.setTimeout(20000) diff --git a/packages/indy-vdr/tsconfig.build.json b/packages/indy-vdr/tsconfig.build.json new file mode 100644 index 0000000000..2b75d0adab --- /dev/null +++ b/packages/indy-vdr/tsconfig.build.json @@ -0,0 +1,7 @@ +{ + "extends": "../../tsconfig.build.json", + "compilerOptions": { + "outDir": "./build" + }, + "include": ["src/**/*"] +} diff --git a/packages/indy-vdr/tsconfig.json b/packages/indy-vdr/tsconfig.json new file mode 100644 index 0000000000..46efe6f721 --- /dev/null +++ b/packages/indy-vdr/tsconfig.json @@ -0,0 +1,6 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "types": ["jest"] + } +} diff --git a/yarn.lock b/yarn.lock index c79dff1e1c..30954778a4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1795,6 +1795,21 @@ semver "^7.3.5" tar "^6.1.11" +"@mapbox/node-pre-gyp@^1.0.10": + version "1.0.10" + resolved "https://registry.yarnpkg.com/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.10.tgz#8e6735ccebbb1581e5a7e652244cadc8a844d03c" + integrity sha512-4ySo4CjzStuprMwk35H5pPbkymjv1SF3jGLj6rAHp/xT/RF7TL7bd9CTm1xDY49K2qF7jmR/g7k+SkLETP6opA== + dependencies: + detect-libc "^2.0.0" + https-proxy-agent "^5.0.0" + make-dir "^3.1.0" + node-fetch "^2.6.7" + nopt "^5.0.0" + npmlog "^5.0.1" + rimraf "^3.0.2" + semver "^7.3.5" + tar "^6.1.11" + "@mattrglobal/bbs-signatures@1.0.0": version "1.0.0" resolved "https://registry.yarnpkg.com/@mattrglobal/bbs-signatures/-/bbs-signatures-1.0.0.tgz#8ff272c6d201aadab7e08bd84dbfd6e0d48ba12d" @@ -3087,6 +3102,14 @@ array-includes@^3.1.6: get-intrinsic "^1.1.3" is-string "^1.0.7" +array-index@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/array-index/-/array-index-1.0.0.tgz#ec56a749ee103e4e08c790b9c353df16055b97f9" + integrity sha512-jesyNbBkLQgGZMSwA1FanaFjalb1mZUGxGeUEkSDidzgrbjBGhvizJkaItdhkt8eIHFOJC7nDsrXk+BaehTdRw== + dependencies: + debug "^2.2.0" + es6-symbol "^3.0.2" + array-map@~0.0.0: version "0.0.1" resolved "https://registry.yarnpkg.com/array-map/-/array-map-0.0.1.tgz#d1bf3cc8813a7daaa335e5c8eb21d9d06230c1a7" @@ -4327,6 +4350,14 @@ csstype@^3.0.2: resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.1.1.tgz#841b532c45c758ee546a11d5bd7b7b473c8c30b9" integrity sha512-DJR/VvkAvSZW9bTouZue2sSxDwdTN92uHjqeKVm+0dAqdfNykRzQ95tay8aXMBAAPpUiq4Qcug2L7neoRh2Egw== +d@1, d@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/d/-/d-1.0.1.tgz#8698095372d58dbee346ffd0c7093f99f8f9eb5a" + integrity sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA== + dependencies: + es5-ext "^0.10.50" + type "^1.0.1" + dargs@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/dargs/-/dargs-7.0.0.tgz#04015c41de0bcb69ec84050f3d9be0caf8d6d5cc" @@ -4779,6 +4810,32 @@ es-to-primitive@^1.2.1: is-date-object "^1.0.1" is-symbol "^1.0.2" +es5-ext@^0.10.35, es5-ext@^0.10.50: + version "0.10.62" + resolved "https://registry.yarnpkg.com/es5-ext/-/es5-ext-0.10.62.tgz#5e6adc19a6da524bf3d1e02bbc8960e5eb49a9a5" + integrity sha512-BHLqn0klhEpnOKSrzn/Xsz2UIW8j+cGmo9JLzr8BiUapV8hPL9+FliFqjwr9ngW7jWdnxv6eO+/LqyhJVqgrjA== + dependencies: + es6-iterator "^2.0.3" + es6-symbol "^3.1.3" + next-tick "^1.1.0" + +es6-iterator@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/es6-iterator/-/es6-iterator-2.0.3.tgz#a7de889141a05a94b0854403b2d0a0fbfa98f3b7" + integrity sha512-zw4SRzoUkd+cl+ZoE15A9o1oQd920Bb0iOJMQkQhl3jNc03YqVjAhG7scf9C5KWRU/R13Orf588uCC6525o02g== + dependencies: + d "1" + es5-ext "^0.10.35" + es6-symbol "^3.1.1" + +es6-symbol@^3.0.2, es6-symbol@^3.1.1, es6-symbol@^3.1.3: + version "3.1.3" + resolved "https://registry.yarnpkg.com/es6-symbol/-/es6-symbol-3.1.3.tgz#bad5d3c1bcdac28269f4cb331e431c78ac705d18" + integrity sha512-NJ6Yn3FuDinBaBRWl/q5X/s4koRHBrgKAu+yGI6JCBeiu3qrcbJhwT2GeR/EXVfylRk8dpQVJoLEFhK+Mu31NA== + dependencies: + d "^1.0.1" + ext "^1.1.2" + escalade@^3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40" @@ -5138,6 +5195,13 @@ express@^4.17.1: utils-merge "1.0.1" vary "~1.1.2" +ext@^1.1.2: + version "1.7.0" + resolved "https://registry.yarnpkg.com/ext/-/ext-1.7.0.tgz#0ea4383c0103d60e70be99e9a7f11027a33c4f5f" + integrity sha512-6hxeJYaL110a9b5TEJSj0gojyHQAmA2ch5Os+ySCiA1QGdS697XWY1pzsrSjqA9LDEEgdB/KypIlR59RcLuHYw== + dependencies: + type "^2.7.2" + extend-shallow@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/extend-shallow/-/extend-shallow-2.0.1.tgz#51af7d614ad9a9f610ea1bafbb989d6b1c56890f" @@ -6075,6 +6139,23 @@ indy-sdk@^1.16.0-dev-1636: nan "^2.11.1" node-gyp "^8.0.0" +indy-vdr-test-nodejs@^0.1.3: + version "0.1.3" + resolved "https://registry.yarnpkg.com/indy-vdr-test-nodejs/-/indy-vdr-test-nodejs-0.1.3.tgz#97eaf38b1035bfabcd772a8399f23d766dfd493e" + integrity sha512-E6r86QGbswa+hBgMJKVWJycqvvmOgepFMDaAvuZQtxQK1Z2gghco6m/9EOAPYaJRs0MMEEhzUGhvtSpCzeZ6sg== + dependencies: + "@mapbox/node-pre-gyp" "^1.0.10" + ffi-napi "^4.0.3" + indy-vdr-test-shared "0.1.3" + ref-array-di "^1.2.2" + ref-napi "^3.0.3" + ref-struct-di "^1.1.1" + +indy-vdr-test-shared@0.1.3, indy-vdr-test-shared@^0.1.3: + version "0.1.3" + resolved "https://registry.yarnpkg.com/indy-vdr-test-shared/-/indy-vdr-test-shared-0.1.3.tgz#3b5ee9492ebc3367a027670aa9686c493de5929c" + integrity sha512-fdgV388zi3dglu49kqrV+i40w+18uJkv96Tk4nziLdP280SLnZKKnIRAiq11Hj8aHpnZmwMloyQCsIyQZDZk2g== + infer-owner@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/infer-owner/-/infer-owner-1.0.4.tgz#c4cefcaa8e51051c2a40ba2ce8a3d27295af9467" @@ -8281,6 +8362,11 @@ neon-cli@0.8.2: validate-npm-package-license "^3.0.4" validate-npm-package-name "^3.0.0" +next-tick@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/next-tick/-/next-tick-1.1.0.tgz#1836ee30ad56d67ef281b22bd199f709449b35eb" + integrity sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ== + nice-try@^1.0.4: version "1.0.5" resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366" @@ -9590,6 +9676,14 @@ reduce-flatten@^2.0.0: resolved "https://registry.yarnpkg.com/reduce-flatten/-/reduce-flatten-2.0.0.tgz#734fd84e65f375d7ca4465c69798c25c9d10ae27" integrity sha512-EJ4UNY/U1t2P/2k6oqotuX2Cc3T6nxJwsM0N0asT7dhrtH1ltUxDn4NalSYmPE2rCkVpcf/X6R0wDwcFpzhd4w== +ref-array-di@^1.2.2: + version "1.2.2" + resolved "https://registry.yarnpkg.com/ref-array-di/-/ref-array-di-1.2.2.tgz#ceee9d667d9c424b5a91bb813457cc916fb1f64d" + integrity sha512-jhCmhqWa7kvCVrWhR/d7RemkppqPUdxEil1CtTtm7FkZV8LcHHCK3Or9GinUiFP5WY3k0djUkMvhBhx49Jb2iA== + dependencies: + array-index "^1.0.0" + debug "^3.1.0" + "ref-napi@^2.0.1 || ^3.0.2", ref-napi@^3.0.3: version "3.0.3" resolved "https://registry.yarnpkg.com/ref-napi/-/ref-napi-3.0.3.tgz#e259bfc2bbafb3e169e8cd9ba49037dd00396b22" @@ -9600,7 +9694,7 @@ reduce-flatten@^2.0.0: node-addon-api "^3.0.0" node-gyp-build "^4.2.1" -ref-struct-di@^1.1.0: +ref-struct-di@^1.1.0, ref-struct-di@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/ref-struct-di/-/ref-struct-di-1.1.1.tgz#5827b1d3b32372058f177547093db1fe1602dc10" integrity sha512-2Xyn/0Qgz89VT+++WP0sTosdm9oeowLP23wRJYhG4BFdMUrLj3jhwHZNEytYNYgtPKLNTP3KJX4HEgBvM1/Y2g==