Skip to content

Commit

Permalink
feat(indy-vdr): add indy-vdr package and indy vdr pool (#1160)
Browse files Browse the repository at this point in the history
work funded by the Government of Ontario

Signed-off-by: Victor Anene <victor@animo.id>
  • Loading branch information
Vickysomtee authored Jan 26, 2023
1 parent b6ae948 commit e8d6ac3
Show file tree
Hide file tree
Showing 19 changed files with 952 additions and 1 deletion.
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
36 changes: 36 additions & 0 deletions packages/indy-vdr/package.json
Original file line number Diff line number Diff line change
@@ -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"
}
}
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'
6 changes: 6 additions & 0 deletions packages/indy-vdr/src/index.ts
Original file line number Diff line number Diff line change
@@ -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')
}
184 changes: 184 additions & 0 deletions packages/indy-vdr/src/pool/IndyVdrPool.ts
Original file line number Diff line number Diff line change
@@ -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<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()
}

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'>

// 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
}
}
Loading

0 comments on commit e8d6ac3

Please sign in to comment.