Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(indy-vdr): add indy-vdr package and indy vdr pool #1160

Merged
merged 25 commits into from
Jan 26, 2023
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
78 changes: 78 additions & 0 deletions packages/indy-vdr/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
<p align="center">
<br />
<img
alt="Hyperledger Aries logo"
src="https://raw.githubusercontent.com/hyperledger/aries-framework-javascript/aa31131825e3331dc93694bc58414d955dcb1129/images/aries-logo.png"
height="250px"
/>
</p>
<h1 align="center"><b>Aries Framework JavaScript Action Menu Plugin</b></h1>
<p align="center">
<a
href="https://raw.githubusercontent.com/hyperledger/aries-framework-javascript/main/LICENSE"
><img
alt="License"
src="https://img.shields.io/badge/License-Apache%202.0-blue.svg"
/></a>
<a href="https://www.typescriptlang.org/"
><img
alt="typescript"
src="https://img.shields.io/badge/%3C%2F%3E-TypeScript-%230074c1.svg"
/></a>
<a href="https://www.npmjs.com/package/@aries-framework/action-menu"
><img
alt="@aries-framework/action-menu version"
src="https://img.shields.io/npm/v/@aries-framework/action-menu"
/></a>

</p>
<br />

Action Menu plugin for [Aries Framework JavaScript](https://github.com/hyperledger/aries-framework-javascript.git). Implements [Aries RFC 0509](https://github.com/hyperledger/aries-rfcs/blob/1795d5c2d36f664f88f5e8045042ace8e573808c/features/0509-action-menu/README.md).

### Installation

Make sure you have set up the correct version of Aries Framework JavaScript according to the AFJ repository. To find out which version of AFJ you need to have installed you can run the following command. This will list the required peer dependency for `@aries-framework/core`.

```sh
npm info "@aries-framework/action-menu" peerDependencies
```

Then add the action-menu plugin to your project.

```sh
yarn add @aries-framework/action-menu
```

### Quick start

In order for this plugin to work, we have to inject it into the agent to access agent functionality. See the example for more information.

### Example of usage

```ts
import { ActionMenuModule } from '@aries-framework/action-menu'

const agent = new Agent({
config: {
/* config */
},
dependencies: agentDependencies,
modules: {
actionMenu: new ActionMenuModule(),
/* other custom modules */
},
})

await agent.initialize()

// To request root menu to a given connection (menu will be received
// asynchronously in a ActionMenuStateChangedEvent)
await agent.modules.actionMenu.requestMenu({ connectionId })

// To select an option from the action menu
await agent.modules.actionMenu.performAction({
connectionId,
performedAction: { name: 'option-1' },
})
```
14 changes: 14 additions & 0 deletions packages/indy-vdr/jest.config.ts
Original file line number Diff line number Diff line change
@@ -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
47 changes: 47 additions & 0 deletions packages/indy-vdr/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
{
"name": "@aries-framework/indy-vdr",
"main": "build/index",
"types": "build/index",
"version": "0.2.5",
"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 -rf ./build",
"compile": "tsc -p tsconfig.build.json",
"prepublishOnly": "yarn run build",
"test": "jest"
},
"dependencies": {
"class-transformer": "0.5.1",
"class-validator": "0.13.1",
"rxjs": "^7.2.0"
},
"peerDependencies": {
"@aries-framework/core": "0.2.5",
"indy-vdr-test-shared": "^0.1.2"
},
"devDependencies": {
"@aries-framework/node": "0.2.5",
"indy-vdr-test-shared": "^0.1.13",
"reflect-metadata": "^0.1.13",
"rimraf": "~3.0.2",
"typescript": "~4.3.0"
},
"peerDependenciesMeta": {
"indy-vdr-test-shared": {
"optional": true
}
}
}
7 changes: 7 additions & 0 deletions packages/indy-vdr/src/error/IndyVdrPoolError.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { AriesFrameworkError } from '@aries-framework/core'

export class IndyVdrPoolError extends AriesFrameworkError {
public constructor(message: string, { cause }: { cause?: Error } = {}) {
super(message, { cause })
}
}
5 changes: 5 additions & 0 deletions packages/indy-vdr/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
try {
require('indy-vdr-test-nodejs')
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How will this work when we want to use this package in React Native? Should we add the require('indy-vdr-test-react-native') to the catch block?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was thinking we could either do it with the catch as you say, or we detect the platform. Or would you prefer the manual approach? (so you provide it to the module). That's also fine for me.

} catch (error) {
throw new Error('Error registering nodejs bindings for Indy VDR')
}
174 changes: 174 additions & 0 deletions packages/indy-vdr/src/pool/IndyVdrPool.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
import { Logger, TypedArrayEncoder } from '@aries-framework/core'

import * as IndyVdr from 'indy-vdr-test-shared'
import { AgentDependencies, AgentContext, LedgerError, AriesFrameworkError, Key } from '@aries-framework/core'

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: string[]
amlContext: string
version: string
}

export interface IndyVdrPoolConfig {
genesisTransactions: string
isProduction: boolean
indyNamespace: string
transactionAuthorAgreement?: TransactionAuthorAgreement
}

export class IndyVdrPool {
// indyVdr?: typeof IndyVdr
private _pool?: IndyVdr.IndyVdrPool
private logger: Logger
private poolConfig: IndyVdrPoolConfig
public authorAgreement?: AuthorAgreement | null

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 get id() {
return this.pool.handle
}

public async connect() {
this._pool = new IndyVdr.PoolCreate({
parameters: {
transactions: this.config.genesisTransactions,
},
})

return this.pool.handle
}

private get pool(): IndyVdr.IndyVdrPool {
if (!this._pool) {
// TODO: create custom IndyVdrError
throw new AriesFrameworkError('Pool is not connected. Make sure to call .connect() first')
}

return this._pool
}

public async close() {
this.pool?.close()
}

public async submitWriteRequest<Request extends IndyVdr.IndyVdrRequest>(agentContext: AgentContext, request: IndyVdr.IndyVdrRequest, signingKey: Key) {
await this.appendTaa(request)

const signature = await agentContext.wallet.sign({
data: TypedArrayEncoder.fromString(request.signatureInput),
key: signingKey
})

request.setSignature({
signature
})

await this.pool.submitRequest(request)
}

public async submitReadRequest<Request extends IndyVdr.IndyVdrRequest>(request: Request) {
return await this.pool.submitRequest(request)
}

private async appendTaa(request: IndyVdr.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 LedgerError(
`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.includes(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 LedgerError(errMessage)
}

const acceptance = IndyVdr.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<AuthorAgreement | null> {
// TODO Replace this condition with memoization
if (this.authorAgreement !== undefined) {
return this.authorAgreement
}

const taaRequest =new IndyVdr.GetTransactionAuthorAgreementRequest({})
const taaResponse = await this.submitReadRequest(taaRequest)

const acceptanceMechanismRequest = new IndyVdr.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<AuthorAgreement, 'acceptanceMechanisms'>
const acceptanceMechanisms = acceptanceMechanismResponse.result.data as AcceptanceMechanisms
this.authorAgreement = {
...authorAgreement,
acceptanceMechanisms,
}

return this.authorAgreement
}
}
Loading