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 21 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
1 change: 1 addition & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ module.exports = {
alwaysTryTypes: true,
},
},
'import/core-modules': ['indy-vdr-test-nodejs'],
Vickysomtee marked this conversation as resolved.
Show resolved Hide resolved
},
rules: {
'@typescript-eslint/explicit-function-return-type': 'off',
Expand Down
3 changes: 3 additions & 0 deletions packages/core/tests/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down Expand Up @@ -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'
Expand Down
35 changes: 35 additions & 0 deletions packages/indy-vdr/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<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 - Indy Verifiable Data Registry (Indy-Vdr)</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/indy-vdr"
><img
alt="@aries-framework/anoncreds version"
src="https://img.shields.io/npm/v/@aries-framework/indy-vdr"
/></a>

</p>
<br />

### Installation

### Quick start

### Example of usage
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
44 changes: 44 additions & 0 deletions packages/indy-vdr/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
{
"name": "@aries-framework/indy-vdr",
"main": "build/index",
"types": "build/index",
"version": "0.3.3",
Vickysomtee marked this conversation as resolved.
Show resolved Hide resolved
"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",
"@types/express": "^4.17.13",
"@typescript-eslint/eslint-plugin": "^5.48.1",
"@typescript-eslint/parser": "^5.48.1",
"class-transformer": "0.5.1",
"class-validator": "0.13.1",
"eslint-plugin-import": "^2.23.4",
Vickysomtee marked this conversation as resolved.
Show resolved Hide resolved
"indy-vdr-test-shared": "^0.1.3",
"rxjs": "^7.2.0"
Vickysomtee marked this conversation as resolved.
Show resolved Hide resolved
},
"devDependencies": {
"@aries-framework/node": "0.3.3",
Vickysomtee marked this conversation as resolved.
Show resolved Hide resolved
"indy-vdr-test-nodejs": "^0.1.3",
"reflect-metadata": "^0.1.13",
Vickysomtee marked this conversation as resolved.
Show resolved Hide resolved
"rimraf": "~3.0.2",
Vickysomtee marked this conversation as resolved.
Show resolved Hide resolved
"typescript": "~4.9.4"
}
}
7 changes: 7 additions & 0 deletions packages/indy-vdr/src/error/IndyVdrError.ts
Original file line number Diff line number Diff line change
@@ -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 })
}
}
7 changes: 7 additions & 0 deletions packages/indy-vdr/src/error/IndyVdrNotConfiguredError.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { IndyVdrError } from './IndyVdrError'

export class IndyVdrNotConfiguredError extends IndyVdrError {
public constructor(message: string, { cause }: { cause?: Error } = {}) {
super(message, { cause })
}
}
7 changes: 7 additions & 0 deletions packages/indy-vdr/src/error/IndyVdrNotFound.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { IndyVdrError } from './IndyVdrError'

export class IndyVdrNotFoundError extends IndyVdrError {
public constructor(message: string, { cause }: { cause?: Error } = {}) {
super(message, { cause })
}
}
3 changes: 3 additions & 0 deletions packages/indy-vdr/src/error/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export * from './IndyVdrError'
export * from './IndyVdrNotFound'
export * from './IndyVdrNotConfiguredError'
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')
}
182 changes: 182 additions & 0 deletions packages/indy-vdr/src/pool/IndyVdrPool.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
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<string, string>
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()
Copy link
Contributor

Choose a reason for hiding this comment

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

Is it throwing an error or something like that?

Copy link
Contributor

Choose a reason for hiding this comment

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

Yes. it hangs indefinitely. should create an issue for it in the indy-vdr repo. @blu3beri do you still know the details?

Copy link
Contributor

Choose a reason for hiding this comment

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

Ah yeah, apparently, almost a year ago now, I create an issue for this.
hyperledger/indy-vdr#92

Copy link
Contributor

Choose a reason for hiding this comment

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

I can investigate this further if it's required. The reasoning (no write-access to lock) might be completely incorrect.

}

public async submitWriteRequest<Request extends IndyVdrRequest>(
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 extends IndyVdrRequest>(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<AuthorAgreement | null> {
// 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<AuthorAgreement, 'acceptanceMechanisms'>
const acceptanceMechanisms = acceptanceMechanismResponse.result.data as AcceptanceMechanisms
Vickysomtee marked this conversation as resolved.
Show resolved Hide resolved
this.authorAgreement = {
...authorAgreement,
acceptanceMechanisms,
}

return this.authorAgreement
}
}
Loading