From ee34fe71780a0787db96e28575eeedce3b4704bd Mon Sep 17 00:00:00 2001 From: Berend Sliedrecht <61358536+berendsliedrecht@users.noreply.github.com> Date: Thu, 18 Jan 2024 13:03:19 +0100 Subject: [PATCH] feat(indy-vdr): register revocation registry definitions and status list (#1693) --- demo/package.json | 2 +- .../anoncreds-rs/tests/anoncreds-flow.test.ts | 2 +- packages/anoncreds/src/index.ts | 1 + packages/anoncreds/src/models/registry.ts | 2 +- .../RevocationRegistryDefinitionOptions.ts | 19 +- .../registry/RevocationStatusListOptions.ts | 31 +- .../utils/__tests__/indyIdentifiers.test.ts | 4 +- .../anoncreds/src/utils/indyIdentifiers.ts | 16 +- .../tests/InMemoryAnonCredsRegistry.ts | 26 +- packages/core/src/index.ts | 1 + .../services/IndySdkAnonCredsRegistry.ts | 6 +- .../utils/__tests__/identifiers.test.ts | 4 +- .../src/anoncreds/utils/identifiers.ts | 10 +- packages/indy-vdr/package.json | 6 +- .../src/anoncreds/IndyVdrAnonCredsRegistry.ts | 554 +++++++++++++++--- .../utils/__tests__/identifiers.test.ts | 4 +- .../utils/__tests__/transform.test.ts | 164 ++++++ .../src/anoncreds/utils/identifiers.ts | 20 +- .../indy-vdr/src/anoncreds/utils/transform.ts | 116 +++- .../indy-vdr/tests/__fixtures__/anoncreds.ts | 11 + .../indy-vdr-anoncreds-registry.e2e.test.ts | 489 +++++++++------- yarn.lock | 18 +- 22 files changed, 1127 insertions(+), 379 deletions(-) create mode 100644 packages/indy-vdr/src/anoncreds/utils/__tests__/transform.test.ts diff --git a/demo/package.json b/demo/package.json index 9a686b3e75..4cb9fe150f 100644 --- a/demo/package.json +++ b/demo/package.json @@ -14,7 +14,7 @@ "refresh": "rm -rf ./node_modules ./yarn.lock && yarn" }, "dependencies": { - "@hyperledger/indy-vdr-nodejs": "^0.2.0-dev.5", + "@hyperledger/indy-vdr-nodejs": "^0.2.0-dev.6", "@hyperledger/anoncreds-nodejs": "^0.2.0-dev.5", "@hyperledger/aries-askar-nodejs": "^0.2.0-dev.5", "inquirer": "^8.2.5" diff --git a/packages/anoncreds-rs/tests/anoncreds-flow.test.ts b/packages/anoncreds-rs/tests/anoncreds-flow.test.ts index 06481055f4..01a71f1ae3 100644 --- a/packages/anoncreds-rs/tests/anoncreds-flow.test.ts +++ b/packages/anoncreds-rs/tests/anoncreds-flow.test.ts @@ -230,7 +230,7 @@ async function anonCredsFlowTest(options: { issuerId: string; revocable: boolean options: {}, }) - if (!revocationStatusListState.revocationStatusList || !revocationStatusListState.timestamp) { + if (!revocationStatusListState.revocationStatusList) { throw new Error('Failed to create revocation status list') } } diff --git a/packages/anoncreds/src/index.ts b/packages/anoncreds/src/index.ts index fad5355d54..4b8fcd8133 100644 --- a/packages/anoncreds/src/index.ts +++ b/packages/anoncreds/src/index.ts @@ -15,3 +15,4 @@ export { generateLegacyProverDidLikeString } from './utils/proverDid' export * from './utils/indyIdentifiers' export { assertBestPracticeRevocationInterval } from './utils/revocationInterval' export { storeLinkSecret } from './utils/linkSecret' +export { dateToTimestamp } from './utils' diff --git a/packages/anoncreds/src/models/registry.ts b/packages/anoncreds/src/models/registry.ts index bc4ad8a152..883fd859fe 100644 --- a/packages/anoncreds/src/models/registry.ts +++ b/packages/anoncreds/src/models/registry.ts @@ -37,7 +37,7 @@ export interface AnonCredsRevocationRegistryDefinition { export interface AnonCredsRevocationStatusList { issuerId: string revRegDefId: string - revocationList: number[] + revocationList: Array currentAccumulator: string timestamp: number } diff --git a/packages/anoncreds/src/services/registry/RevocationRegistryDefinitionOptions.ts b/packages/anoncreds/src/services/registry/RevocationRegistryDefinitionOptions.ts index 3f7a07ed77..fee5653c1e 100644 --- a/packages/anoncreds/src/services/registry/RevocationRegistryDefinitionOptions.ts +++ b/packages/anoncreds/src/services/registry/RevocationRegistryDefinitionOptions.ts @@ -4,6 +4,7 @@ import type { AnonCredsOperationStateFinished, AnonCredsResolutionMetadata, Extensible, + AnonCredsOperationStateAction, } from './base' import type { AnonCredsRevocationRegistryDefinition } from '../../models/registry' @@ -19,27 +20,33 @@ export interface RegisterRevocationRegistryDefinitionOptions { options: Extensible } +export interface RegisterRevocationRegistryDefinitionReturnStateAction extends AnonCredsOperationStateAction { + revocationRegistryDefinition: AnonCredsRevocationRegistryDefinition + revocationRegistryDefinitionId: string +} + export interface RegisterRevocationRegistryDefinitionReturnStateFailed extends AnonCredsOperationStateFailed { revocationRegistryDefinition?: AnonCredsRevocationRegistryDefinition revocationRegistryDefinitionId?: string } +export interface RegisterRevocationRegistryDefinitionReturnStateWait extends AnonCredsOperationStateWait { + revocationRegistryDefinition?: AnonCredsRevocationRegistryDefinition + revocationRegistryDefinitionId?: string +} + export interface RegisterRevocationRegistryDefinitionReturnStateFinished extends AnonCredsOperationStateFinished { revocationRegistryDefinition: AnonCredsRevocationRegistryDefinition revocationRegistryDefinitionId: string } -export interface RegisterRevocationRegistryDefinitionReturnState extends AnonCredsOperationStateWait { - revocationRegistryDefinition?: AnonCredsRevocationRegistryDefinition - revocationRegistryDefinitionId?: string -} - export interface RegisterRevocationRegistryDefinitionReturn { jobId?: string revocationRegistryDefinitionState: + | RegisterRevocationRegistryDefinitionReturnStateWait + | RegisterRevocationRegistryDefinitionReturnStateAction | RegisterRevocationRegistryDefinitionReturnStateFailed | RegisterRevocationRegistryDefinitionReturnStateFinished - | RegisterRevocationRegistryDefinitionReturnState revocationRegistryDefinitionMetadata: Extensible registrationMetadata: Extensible } diff --git a/packages/anoncreds/src/services/registry/RevocationStatusListOptions.ts b/packages/anoncreds/src/services/registry/RevocationStatusListOptions.ts index 05b1353801..21ea1bca95 100644 --- a/packages/anoncreds/src/services/registry/RevocationStatusListOptions.ts +++ b/packages/anoncreds/src/services/registry/RevocationStatusListOptions.ts @@ -4,8 +4,10 @@ import type { AnonCredsOperationStateFinished, AnonCredsResolutionMetadata, Extensible, + AnonCredsOperationStateAction, } from './base' import type { AnonCredsRevocationStatusList } from '../../models/registry' +import type { Optional } from '@aries-framework/core' export interface GetRevocationStatusListReturn { revocationStatusList?: AnonCredsRevocationStatusList @@ -13,34 +15,39 @@ export interface GetRevocationStatusListReturn { revocationStatusListMetadata: Extensible } +// Timestamp is often calculated by the ledger, otherwise method should just take current time +// Return type does include the timestamp. +export type AnonCredsRevocationStatusListWithoutTimestamp = Omit +export type AnonCredsRevocationStatusListWithOptionalTimestamp = Optional + export interface RegisterRevocationStatusListOptions { - // Timestamp is often calculated by the ledger, otherwise method should just take current time - // Return type does include the timestamp. - revocationStatusList: Omit + revocationStatusList: AnonCredsRevocationStatusListWithoutTimestamp options: Extensible } +export interface RegisterRevocationStatusListReturnStateAction extends AnonCredsOperationStateAction { + revocationStatusList: AnonCredsRevocationStatusListWithOptionalTimestamp +} + export interface RegisterRevocationStatusListReturnStateFailed extends AnonCredsOperationStateFailed { - revocationStatusList?: AnonCredsRevocationStatusList - timestamp?: string + revocationStatusList?: AnonCredsRevocationStatusListWithOptionalTimestamp } -export interface RegisterRevocationStatusListReturnStateFinished extends AnonCredsOperationStateFinished { - revocationStatusList: AnonCredsRevocationStatusList - timestamp: string +export interface RegisterRevocationStatusListReturnStateWait extends AnonCredsOperationStateWait { + revocationStatusList?: AnonCredsRevocationStatusListWithOptionalTimestamp } -export interface RegisterRevocationStatusListReturnState extends AnonCredsOperationStateWait { - revocationStatusList?: AnonCredsRevocationStatusList - timestamp?: string +export interface RegisterRevocationStatusListReturnStateFinished extends AnonCredsOperationStateFinished { + revocationStatusList: AnonCredsRevocationStatusList } export interface RegisterRevocationStatusListReturn { jobId?: string revocationStatusListState: + | RegisterRevocationStatusListReturnStateWait + | RegisterRevocationStatusListReturnStateAction | RegisterRevocationStatusListReturnStateFailed | RegisterRevocationStatusListReturnStateFinished - | RegisterRevocationStatusListReturnState revocationStatusListMetadata: Extensible registrationMetadata: Extensible } diff --git a/packages/anoncreds/src/utils/__tests__/indyIdentifiers.test.ts b/packages/anoncreds/src/utils/__tests__/indyIdentifiers.test.ts index 7fba68562d..7f94486586 100644 --- a/packages/anoncreds/src/utils/__tests__/indyIdentifiers.test.ts +++ b/packages/anoncreds/src/utils/__tests__/indyIdentifiers.test.ts @@ -1,6 +1,6 @@ import { getUnqualifiedCredentialDefinitionId, - getUnqualifiedRevocationRegistryId, + getUnqualifiedRevocationRegistryDefinitionId, getUnqualifiedSchemaId, parseIndyCredentialDefinitionId, parseIndyRevocationRegistryId, @@ -67,7 +67,7 @@ describe('Legacy Indy Identifier Regex', () => { const credentialDefinitionTag = 'someTag' const tag = 'anotherTag' - expect(getUnqualifiedRevocationRegistryId(did, seqNo, credentialDefinitionTag, tag)).toEqual( + expect(getUnqualifiedRevocationRegistryDefinitionId(did, seqNo, credentialDefinitionTag, tag)).toEqual( '12345:4:12345:3:CL:420:someTag:CL_ACCUM:anotherTag' ) }) diff --git a/packages/anoncreds/src/utils/indyIdentifiers.ts b/packages/anoncreds/src/utils/indyIdentifiers.ts index 1e20f75c55..3cd050e7c8 100644 --- a/packages/anoncreds/src/utils/indyIdentifiers.ts +++ b/packages/anoncreds/src/utils/indyIdentifiers.ts @@ -18,7 +18,7 @@ export const didIndyCredentialDefinitionIdRegex = new RegExp( `^${didIndyAnonCredsBase.source}/CLAIM_DEF/([1-9][0-9]*)/(.+)$` ) -// :4::3:CL::CL_ACCUM: +// :4::3:CL:::CL_ACCUM: export const unqualifiedRevocationRegistryIdRegex = /^([a-zA-Z0-9]{21,22}):4:[a-zA-Z0-9]{21,22}:3:CL:([1-9][0-9]*):(.+):CL_ACCUM:(.+)$/ // did:indy::/anoncreds/v0/REV_REG_DEF/// @@ -32,18 +32,22 @@ export function getUnqualifiedSchemaId(unqualifiedDid: string, name: string, ver return `${unqualifiedDid}:2:${name}:${version}` } -export function getUnqualifiedCredentialDefinitionId(unqualifiedDid: string, seqNo: string | number, tag: string) { - return `${unqualifiedDid}:3:CL:${seqNo}:${tag}` +export function getUnqualifiedCredentialDefinitionId( + unqualifiedDid: string, + schemaSeqNo: string | number, + tag: string +) { + return `${unqualifiedDid}:3:CL:${schemaSeqNo}:${tag}` } // TZQuLp43UcYTdtc3HewcDz:4:TZQuLp43UcYTdtc3HewcDz:3:CL:98158:BaustellenzertifikateNU1:CL_ACCUM:1-100 -export function getUnqualifiedRevocationRegistryId( +export function getUnqualifiedRevocationRegistryDefinitionId( unqualifiedDid: string, - seqNo: string | number, + schemaSeqNo: string | number, credentialDefinitionTag: string, revocationRegistryTag: string ) { - return `${unqualifiedDid}:4:${unqualifiedDid}:3:CL:${seqNo}:${credentialDefinitionTag}:CL_ACCUM:${revocationRegistryTag}` + return `${unqualifiedDid}:4:${unqualifiedDid}:3:CL:${schemaSeqNo}:${credentialDefinitionTag}:CL_ACCUM:${revocationRegistryTag}` } export function isUnqualifiedCredentialDefinitionId(credentialDefinitionId: string) { diff --git a/packages/anoncreds/tests/InMemoryAnonCredsRegistry.ts b/packages/anoncreds/tests/InMemoryAnonCredsRegistry.ts index da51aa12ec..9c2e8bc42b 100644 --- a/packages/anoncreds/tests/InMemoryAnonCredsRegistry.ts +++ b/packages/anoncreds/tests/InMemoryAnonCredsRegistry.ts @@ -1,4 +1,3 @@ -/* eslint-disable @typescript-eslint/no-unused-vars */ import type { AnonCredsRegistry, GetSchemaReturn, @@ -25,12 +24,12 @@ import BigNumber from 'bn.js' import { getDidIndyCredentialDefinitionId, - getDidIndyRevocationRegistryId, + getDidIndyRevocationRegistryDefinitionId, getDidIndySchemaId, } from '../../indy-sdk/src/anoncreds/utils/identifiers' import { parseIndyCredentialDefinitionId, - getUnqualifiedRevocationRegistryId, + getUnqualifiedRevocationRegistryDefinitionId, getUnqualifiedCredentialDefinitionId, getUnqualifiedSchemaId, parseIndyDid, @@ -71,7 +70,7 @@ export class InMemoryAnonCredsRegistry implements AnonCredsRegistry { this.revocationStatusLists = existingRevocationStatusLists } - public async getSchema(agentContext: AgentContext, schemaId: string): Promise { + public async getSchema(_agentContext: AgentContext, schemaId: string): Promise { const schema = this.schemas[schemaId] const parsed = parseIndySchemaId(schemaId) @@ -103,7 +102,7 @@ export class InMemoryAnonCredsRegistry implements AnonCredsRegistry { } public async registerSchema( - agentContext: AgentContext, + _agentContext: AgentContext, options: RegisterSchemaOptions ): Promise { const { namespace, namespaceIdentifier } = parseIndyDid(options.schema.issuerId) @@ -140,7 +139,7 @@ export class InMemoryAnonCredsRegistry implements AnonCredsRegistry { } public async getCredentialDefinition( - agentContext: AgentContext, + _agentContext: AgentContext, credentialDefinitionId: string ): Promise { const credentialDefinition = this.credentialDefinitions[credentialDefinitionId] @@ -165,7 +164,7 @@ export class InMemoryAnonCredsRegistry implements AnonCredsRegistry { } public async registerCredentialDefinition( - agentContext: AgentContext, + _agentContext: AgentContext, options: RegisterCredentialDefinitionOptions ): Promise { const parsedSchema = parseIndySchemaId(options.credentialDefinition.schemaId) @@ -211,7 +210,7 @@ export class InMemoryAnonCredsRegistry implements AnonCredsRegistry { } public async getRevocationRegistryDefinition( - agentContext: AgentContext, + _agentContext: AgentContext, revocationRegistryDefinitionId: string ): Promise { const revocationRegistryDefinition = this.revocationRegistryDefinitions[revocationRegistryDefinitionId] @@ -236,7 +235,7 @@ export class InMemoryAnonCredsRegistry implements AnonCredsRegistry { } public async registerRevocationRegistryDefinition( - agentContext: AgentContext, + _agentContext: AgentContext, options: RegisterRevocationRegistryDefinitionOptions ): Promise { const parsedCredentialDefinition = parseIndyCredentialDefinitionId(options.revocationRegistryDefinition.credDefId) @@ -249,7 +248,7 @@ export class InMemoryAnonCredsRegistry implements AnonCredsRegistry { const { namespace, namespaceIdentifier } = parseIndyDid(options.revocationRegistryDefinition.issuerId) const legacyIssuerId = namespaceIdentifier - const didIndyRevocationRegistryDefinitionId = getDidIndyRevocationRegistryId( + const didIndyRevocationRegistryDefinitionId = getDidIndyRevocationRegistryDefinitionId( namespace, namespaceIdentifier, indyLedgerSeqNo, @@ -259,7 +258,7 @@ export class InMemoryAnonCredsRegistry implements AnonCredsRegistry { this.revocationRegistryDefinitions[didIndyRevocationRegistryDefinitionId] = options.revocationRegistryDefinition - const legacyRevocationRegistryDefinitionId = getUnqualifiedRevocationRegistryId( + const legacyRevocationRegistryDefinitionId = getUnqualifiedRevocationRegistryDefinitionId( legacyIssuerId, indyLedgerSeqNo, parsedCredentialDefinition.tag, @@ -284,7 +283,7 @@ export class InMemoryAnonCredsRegistry implements AnonCredsRegistry { } public async getRevocationStatusList( - agentContext: AgentContext, + _agentContext: AgentContext, revocationRegistryId: string, timestamp: number ): Promise { @@ -322,7 +321,7 @@ export class InMemoryAnonCredsRegistry implements AnonCredsRegistry { } public async registerRevocationStatusList( - agentContext: AgentContext, + _agentContext: AgentContext, options: RegisterRevocationStatusListOptions ): Promise { const timestamp = (options.options.timestamp as number) ?? dateToTimestamp(new Date()) @@ -341,7 +340,6 @@ export class InMemoryAnonCredsRegistry implements AnonCredsRegistry { revocationStatusListState: { state: 'finished', revocationStatusList, - timestamp: timestamp.toString(), }, } } diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 46b4dbddc1..9ec5248ac4 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -80,6 +80,7 @@ export * from './crypto/' // TODO: clean up util exports export { encodeAttachment, isLinkedAttachment } from './utils/attachment' +export type { Optional } from './utils' export { Hasher, HashName } from './utils/Hasher' export { MessageValidator } from './utils/MessageValidator' export { LinkedAttachment, LinkedAttachmentOptions } from './utils/LinkedAttachment' diff --git a/packages/indy-sdk/src/anoncreds/services/IndySdkAnonCredsRegistry.ts b/packages/indy-sdk/src/anoncreds/services/IndySdkAnonCredsRegistry.ts index a386675b1e..0b9fdb0718 100644 --- a/packages/indy-sdk/src/anoncreds/services/IndySdkAnonCredsRegistry.ts +++ b/packages/indy-sdk/src/anoncreds/services/IndySdkAnonCredsRegistry.ts @@ -18,7 +18,7 @@ import type { Schema as IndySdkSchema } from 'indy-sdk' import { getUnqualifiedCredentialDefinitionId, - getUnqualifiedRevocationRegistryId, + getUnqualifiedRevocationRegistryDefinitionId, getUnqualifiedSchemaId, parseIndyCredentialDefinitionId, parseIndyDid, @@ -394,7 +394,7 @@ export class IndySdkAnonCredsRegistry implements AnonCredsRegistry { `Using ledger '${pool.didIndyNamespace}' to retrieve revocation registry definition '${revocationRegistryDefinitionId}'` ) - const legacyRevocationRegistryId = getUnqualifiedRevocationRegistryId( + const legacyRevocationRegistryId = getUnqualifiedRevocationRegistryDefinitionId( namespaceIdentifier, schemaSeqNo, credentialDefinitionTag, @@ -492,7 +492,7 @@ export class IndySdkAnonCredsRegistry implements AnonCredsRegistry { `Using ledger '${pool.didIndyNamespace}' to retrieve revocation registry deltas with revocation registry definition id '${revocationRegistryId}' until ${timestamp}` ) - const legacyRevocationRegistryId = getUnqualifiedRevocationRegistryId( + const legacyRevocationRegistryId = getUnqualifiedRevocationRegistryDefinitionId( namespaceIdentifier, schemaSeqNo, credentialDefinitionTag, diff --git a/packages/indy-sdk/src/anoncreds/utils/__tests__/identifiers.test.ts b/packages/indy-sdk/src/anoncreds/utils/__tests__/identifiers.test.ts index 9b9a54ba83..546579330b 100644 --- a/packages/indy-sdk/src/anoncreds/utils/__tests__/identifiers.test.ts +++ b/packages/indy-sdk/src/anoncreds/utils/__tests__/identifiers.test.ts @@ -1,6 +1,6 @@ import { getDidIndyCredentialDefinitionId, - getDidIndyRevocationRegistryId, + getDidIndyRevocationRegistryDefinitionId, getDidIndySchemaId, indySdkAnonCredsRegistryIdentifierRegex, } from '../identifiers' @@ -72,7 +72,7 @@ describe('identifiers', () => { const credentialDefinitionTag = 'someTag' const tag = 'anotherTag' - expect(getDidIndyRevocationRegistryId(namespace, did, seqNo, credentialDefinitionTag, tag)).toEqual( + expect(getDidIndyRevocationRegistryDefinitionId(namespace, did, seqNo, credentialDefinitionTag, tag)).toEqual( 'did:indy:sovrin:test:12345/anoncreds/v0/REV_REG_DEF/420/someTag/anotherTag' ) }) diff --git a/packages/indy-sdk/src/anoncreds/utils/identifiers.ts b/packages/indy-sdk/src/anoncreds/utils/identifiers.ts index 4cedb11ff4..ccc7433e34 100644 --- a/packages/indy-sdk/src/anoncreds/utils/identifiers.ts +++ b/packages/indy-sdk/src/anoncreds/utils/identifiers.ts @@ -46,18 +46,18 @@ export function getDidIndySchemaId(namespace: string, unqualifiedDid: string, na export function getDidIndyCredentialDefinitionId( namespace: string, unqualifiedDid: string, - seqNo: string | number, + schemaSeqNo: string | number, tag: string ) { - return `did:indy:${namespace}:${unqualifiedDid}/anoncreds/v0/CLAIM_DEF/${seqNo}/${tag}` + return `did:indy:${namespace}:${unqualifiedDid}/anoncreds/v0/CLAIM_DEF/${schemaSeqNo}/${tag}` } -export function getDidIndyRevocationRegistryId( +export function getDidIndyRevocationRegistryDefinitionId( namespace: string, unqualifiedDid: string, - seqNo: string | number, + schemaSeqNo: string | number, credentialDefinitionTag: string, revocationRegistryTag: string ) { - return `did:indy:${namespace}:${unqualifiedDid}/anoncreds/v0/REV_REG_DEF/${seqNo}/${credentialDefinitionTag}/${revocationRegistryTag}` + return `did:indy:${namespace}:${unqualifiedDid}/anoncreds/v0/REV_REG_DEF/${schemaSeqNo}/${credentialDefinitionTag}/${revocationRegistryTag}` } diff --git a/packages/indy-vdr/package.json b/packages/indy-vdr/package.json index e49286f040..d558c83dd0 100644 --- a/packages/indy-vdr/package.json +++ b/packages/indy-vdr/package.json @@ -28,8 +28,8 @@ "@aries-framework/core": "0.4.2" }, "devDependencies": { - "@hyperledger/indy-vdr-nodejs": "^0.2.0-dev.5", - "@hyperledger/indy-vdr-shared": "^0.2.0-dev.5", + "@hyperledger/indy-vdr-nodejs": "^0.2.0-dev.6", + "@hyperledger/indy-vdr-shared": "^0.2.0-dev.6", "@stablelib/ed25519": "^1.0.2", "@types/ref-array-di": "^1.2.6", "@types/ref-struct-di": "^1.1.10", @@ -38,6 +38,6 @@ "typescript": "~4.9.5" }, "peerDependencies": { - "@hyperledger/indy-vdr-shared": "^0.2.0-dev.5" + "@hyperledger/indy-vdr-shared": "^0.2.0-dev.6" } } diff --git a/packages/indy-vdr/src/anoncreds/IndyVdrAnonCredsRegistry.ts b/packages/indy-vdr/src/anoncreds/IndyVdrAnonCredsRegistry.ts index 5251362983..15db83beee 100644 --- a/packages/indy-vdr/src/anoncreds/IndyVdrAnonCredsRegistry.ts +++ b/packages/indy-vdr/src/anoncreds/IndyVdrAnonCredsRegistry.ts @@ -1,3 +1,4 @@ +import type { RevocationRegistryDelta } from './utils/transform' import type { AnonCredsRegistry, GetCredentialDefinitionReturn, @@ -19,21 +20,33 @@ import type { RegisterCredentialDefinitionReturnStateWait, RegisterCredentialDefinitionReturnStateFinished, RegisterCredentialDefinitionReturnStateFailed, + RegisterRevocationRegistryDefinitionReturnStateFinished, + RegisterRevocationRegistryDefinitionReturnStateFailed, + RegisterRevocationRegistryDefinitionReturnStateWait, + RegisterRevocationRegistryDefinitionReturnStateAction, + RegisterRevocationStatusListReturnStateFinished, + RegisterRevocationStatusListReturnStateFailed, + RegisterRevocationStatusListReturnStateWait, + RegisterRevocationStatusListReturnStateAction, + RegisterRevocationStatusListOptions, } from '@aries-framework/anoncreds' import type { AgentContext } from '@aries-framework/core' import type { SchemaResponse } from '@hyperledger/indy-vdr-shared' import { getUnqualifiedCredentialDefinitionId, - getUnqualifiedRevocationRegistryId, + getUnqualifiedRevocationRegistryDefinitionId, getUnqualifiedSchemaId, parseIndyCredentialDefinitionId, parseIndyDid, parseIndyRevocationRegistryId, parseIndySchemaId, + dateToTimestamp, } from '@aries-framework/anoncreds' import { AriesFrameworkError } from '@aries-framework/core' import { + RevocationRegistryEntryRequest, + RevocationRegistryDefinitionRequest, GetSchemaRequest, SchemaRequest, GetCredentialDefinitionRequest, @@ -52,8 +65,10 @@ import { indyVdrAnonCredsRegistryIdentifierRegex, getDidIndySchemaId, getDidIndyCredentialDefinitionId, + getDidIndyRevocationRegistryDefinitionId, + getDidIndyRevocationRegistryEntryId, } from './utils/identifiers' -import { anonCredsRevocationStatusListFromIndyVdr } from './utils/transform' +import { indyVdrCreateLatestRevocationDelta, anonCredsRevocationStatusListFromIndyVdr } from './utils/transform' export class IndyVdrAnonCredsRegistry implements AnonCredsRegistry { public readonly methodName = 'indy' @@ -277,7 +292,7 @@ export class IndyVdrAnonCredsRegistry implements AnonCredsRegistry { : schema.schema.schemaId return { - credentialDefinitionId: credentialDefinitionId, + credentialDefinitionId, credentialDefinition: { issuerId: did, schemaId, @@ -329,7 +344,7 @@ export class IndyVdrAnonCredsRegistry implements AnonCredsRegistry { let writeRequest: CustomRequest let didIndyCredentialDefinitionId: string - let seqNo: number + let schemaSeqNo: number const endorsedTransaction = options.options.endorsedTransaction if (endorsedTransaction) { @@ -340,8 +355,13 @@ export class IndyVdrAnonCredsRegistry implements AnonCredsRegistry { writeRequest = new CustomRequest({ customRequest: endorsedTransaction }) const operation = JSON.parse(endorsedTransaction)?.operation // extract the seqNo from the endorsed transaction, which is contained in the ref field of the operation - seqNo = Number(operation?.ref) - didIndyCredentialDefinitionId = getDidIndyCredentialDefinitionId(namespace, namespaceIdentifier, seqNo, tag) + schemaSeqNo = Number(operation?.ref) + didIndyCredentialDefinitionId = getDidIndyCredentialDefinitionId( + namespace, + namespaceIdentifier, + schemaSeqNo, + tag + ) } else { // TODO: this will bypass caching if done on a higher level. const { schemaMetadata, resolutionMetadata } = await this.getSchema(agentContext, schemaId) @@ -359,20 +379,31 @@ export class IndyVdrAnonCredsRegistry implements AnonCredsRegistry { }, } } - seqNo = schemaMetadata.indyLedgerSeqNo - - const legacyCredentialDefinitionId = getUnqualifiedCredentialDefinitionId(issuerId, seqNo, tag) - didIndyCredentialDefinitionId = getDidIndyCredentialDefinitionId(namespace, namespaceIdentifier, seqNo, tag) + schemaSeqNo = schemaMetadata.indyLedgerSeqNo + + // FIXME: we need to check if schemaId has same namespace as issuerId and it should not be a legacy identifier + // FIXME: in the other methods we also need to add checks. E.g. when creating a revocation + // status list, you can only create a revocation status list for a credential definition registry that is created + // under the same namespace and by the same issuer id (you can create a cred def for a schema created by another issuer + // but you can't create a revocation registry based on a cred def created by another issuer. We need to add these checks + // to all register methods in this file) + const legacyCredentialDefinitionId = getUnqualifiedCredentialDefinitionId(issuerId, schemaSeqNo, tag) + didIndyCredentialDefinitionId = getDidIndyCredentialDefinitionId( + namespace, + namespaceIdentifier, + schemaSeqNo, + tag + ) const credentialDefinitionRequest = new CredentialDefinitionRequest({ submitterDid: namespaceIdentifier, credentialDefinition: { ver: '1.0', id: legacyCredentialDefinitionId, - schemaId: `${seqNo}`, + schemaId: schemaSeqNo.toString(), type: 'CL', - tag: tag, - value: value, + tag, + value, }, }) @@ -457,7 +488,7 @@ export class IndyVdrAnonCredsRegistry implements AnonCredsRegistry { `Using ledger '${pool.indyNamespace}' to retrieve revocation registry definition '${revocationRegistryDefinitionId}'` ) - const legacyRevocationRegistryId = getUnqualifiedRevocationRegistryId( + const legacyRevocationRegistryId = getUnqualifiedRevocationRegistryDefinitionId( namespaceIdentifier, schemaSeqNo, credentialDefinitionTag, @@ -483,7 +514,7 @@ export class IndyVdrAnonCredsRegistry implements AnonCredsRegistry { return { resolutionMetadata: { error: 'notFound', - message: `unable to resolve revocation registry definition`, + message: 'unable to resolve revocation registry definition', }, revocationRegistryDefinitionId, revocationRegistryDefinitionMetadata: {}, @@ -552,48 +583,178 @@ export class IndyVdrAnonCredsRegistry implements AnonCredsRegistry { } } - public async registerRevocationRegistryDefinition(): Promise { - throw new AriesFrameworkError('Not implemented!') - } - - public async getRevocationStatusList( + public async registerRevocationRegistryDefinition( agentContext: AgentContext, - revocationRegistryId: string, - timestamp: number - ): Promise { + { options, revocationRegistryDefinition }: IndyVdrRegisterRevocationRegistryDefinition + ): Promise { try { - const indySdkPoolService = agentContext.dependencyManager.resolve(IndyVdrPoolService) - - const { did, namespaceIdentifier, schemaSeqNo, credentialDefinitionTag, revocationRegistryTag } = - parseIndyRevocationRegistryId(revocationRegistryId) - const { pool } = await indySdkPoolService.getPoolForDid(agentContext, did) + // This will throw an error if trying to register a credential definition with a legacy indy identifier. We only support did:indy + // identifiers for registering, that will allow us to extract the namespace and means all stored records will use did:indy identifiers. + const { namespaceIdentifier, namespace } = parseIndyDid(revocationRegistryDefinition.issuerId) + const indyVdrPoolService = agentContext.dependencyManager.resolve(IndyVdrPoolService) + const pool = indyVdrPoolService.getPoolForNamespace(namespace) agentContext.config.logger.debug( - `Using ledger '${pool.indyNamespace}' to retrieve revocation registry deltas with revocation registry definition id '${revocationRegistryId}' until ${timestamp}` + `Registering revocation registry definition on ledger '${namespace}' with did '${revocationRegistryDefinition.issuerId}'`, + revocationRegistryDefinition ) - const legacyRevocationRegistryId = getUnqualifiedRevocationRegistryId( - namespaceIdentifier, - schemaSeqNo, - credentialDefinitionTag, - revocationRegistryTag - ) - const request = new GetRevocationRegistryDeltaRequest({ - revocationRegistryId: legacyRevocationRegistryId, - toTs: timestamp, - }) + let writeRequest: CustomRequest + let didIndyRevocationRegistryDefinitionId: string - agentContext.config.logger.trace( - `Submitting get revocation registry delta request for revocation registry '${revocationRegistryId}' to ledger` + const { schemaSeqNo, tag: credentialDefinitionTag } = parseIndyCredentialDefinitionId( + revocationRegistryDefinition.credDefId ) - const response = await pool.submitRequest(request) + const { endorsedTransaction, endorserDid, endorserMode } = options + if (endorsedTransaction) { + agentContext.config.logger.debug( + `Preparing endorsed tx '${endorsedTransaction}' for submission on ledger '${namespace}' with did '${revocationRegistryDefinition.issuerId}'`, + revocationRegistryDefinition + ) + writeRequest = new CustomRequest({ customRequest: endorsedTransaction }) + didIndyRevocationRegistryDefinitionId = getDidIndyRevocationRegistryDefinitionId( + namespace, + namespaceIdentifier, + schemaSeqNo, + credentialDefinitionTag, + revocationRegistryDefinition.tag + ) + } else { + const legacyRevocationRegistryDefinitionId = getUnqualifiedRevocationRegistryDefinitionId( + namespaceIdentifier, + schemaSeqNo, + credentialDefinitionTag, + revocationRegistryDefinition.tag + ) + + didIndyRevocationRegistryDefinitionId = getDidIndyRevocationRegistryDefinitionId( + namespace, + namespaceIdentifier, + schemaSeqNo, + credentialDefinitionTag, + revocationRegistryDefinition.tag + ) + + const legacyCredentialDefinitionId = getUnqualifiedCredentialDefinitionId( + namespaceIdentifier, + schemaSeqNo, + credentialDefinitionTag + ) + + const revocationRegistryDefinitionRequest = new RevocationRegistryDefinitionRequest({ + submitterDid: namespaceIdentifier, + revocationRegistryDefinitionV1: { + id: legacyRevocationRegistryDefinitionId, + ver: '1.0', + credDefId: legacyCredentialDefinitionId, + tag: revocationRegistryDefinition.tag, + revocDefType: revocationRegistryDefinition.revocDefType, + value: { + issuanceType: 'ISSUANCE_BY_DEFAULT', + ...revocationRegistryDefinition.value, + }, + }, + }) + + const submitterKey = await verificationKeyForIndyDid(agentContext, revocationRegistryDefinition.issuerId) + writeRequest = await pool.prepareWriteRequest( + agentContext, + revocationRegistryDefinitionRequest, + submitterKey, + endorserDid !== revocationRegistryDefinition.issuerId ? endorserDid : undefined + ) + + if (endorserMode === 'external') { + return { + jobId: didIndyRevocationRegistryDefinitionId, + revocationRegistryDefinitionState: { + state: 'action', + action: 'endorseIndyTransaction', + revocationRegistryDefinition, + revocationRegistryDefinitionId: didIndyRevocationRegistryDefinitionId, + revocationRegistryDefinitionRequest: writeRequest.body, + }, + registrationMetadata: {}, + revocationRegistryDefinitionMetadata: {}, + } + } + + if (endorserMode === 'internal' && endorserDid !== revocationRegistryDefinition.issuerId) { + const endorserKey = await verificationKeyForIndyDid(agentContext, endorserDid as string) + await multiSignRequest(agentContext, writeRequest, endorserKey, parseIndyDid(endorserDid).namespaceIdentifier) + } + } + + const response = await pool.submitRequest(writeRequest) agentContext.config.logger.debug( - `Got revocation registry deltas '${revocationRegistryId}' until timestamp ${timestamp} from ledger` + `Registered revocation registry definition '${didIndyRevocationRegistryDefinitionId}' on ledger '${pool.indyNamespace}'`, + { + response, + revocationRegistryDefinition, + } ) + return { + revocationRegistryDefinitionMetadata: {}, + revocationRegistryDefinitionState: { + revocationRegistryDefinition, + revocationRegistryDefinitionId: didIndyRevocationRegistryDefinitionId, + state: 'finished', + }, + registrationMetadata: {}, + } + } catch (error) { + agentContext.config.logger.error( + `Error registering revocation registry definition for credential definition '${revocationRegistryDefinition.credDefId}'`, + { + error, + did: revocationRegistryDefinition.issuerId, + revocationRegistryDefinition, + } + ) + + return { + revocationRegistryDefinitionMetadata: {}, + registrationMetadata: {}, + revocationRegistryDefinitionState: { + revocationRegistryDefinition, + state: 'failed', + reason: `unknownError: ${error.message}`, + }, + } + } + } + + public async getRevocationStatusList( + agentContext: AgentContext, + revocationRegistryDefinitionId: string, + timestamp: number + ): Promise { + try { + const indyVdrPoolService = agentContext.dependencyManager.resolve(IndyVdrPoolService) + + const { did } = parseIndyRevocationRegistryId(revocationRegistryDefinitionId) + const { pool } = await indyVdrPoolService.getPoolForDid(agentContext, did) + + const revocationDelta = await this.fetchIndyRevocationDelta( + agentContext, + revocationRegistryDefinitionId, + timestamp + ) + + if (!revocationDelta) { + return { + resolutionMetadata: { + error: 'notFound', + message: `Error retrieving revocation registry delta '${revocationRegistryDefinitionId}' from ledger, potentially revocation interval ends before revocation registry creation`, + }, + revocationStatusListMetadata: {}, + } + } + const { revocationRegistryDefinition, resolutionMetadata, revocationRegistryDefinitionMetadata } = - await this.getRevocationRegistryDefinition(agentContext, revocationRegistryId) + await this.getRevocationRegistryDefinition(agentContext, revocationRegistryDefinitionId) if ( !revocationRegistryDefinition || @@ -602,7 +763,7 @@ export class IndyVdrAnonCredsRegistry implements AnonCredsRegistry { ) { return { resolutionMetadata: { - error: `error resolving revocation registry definition with id ${revocationRegistryId}: ${resolutionMetadata.error} ${resolutionMetadata.message}`, + error: `error resolving revocation registry definition with id ${revocationRegistryDefinitionId}: ${resolutionMetadata.error} ${resolutionMetadata.message}`, }, revocationStatusListMetadata: { didIndyNamespace: pool.indyNamespace, @@ -612,29 +773,12 @@ export class IndyVdrAnonCredsRegistry implements AnonCredsRegistry { const isIssuanceByDefault = revocationRegistryDefinitionMetadata.issuanceType === 'ISSUANCE_BY_DEFAULT' - if (!response.result.data) { - return { - resolutionMetadata: { - error: 'notFound', - message: `Error retrieving revocation registry delta '${revocationRegistryId}' from ledger, potentially revocation interval ends before revocation registry creation`, - }, - revocationStatusListMetadata: {}, - } - } - - const revocationRegistryDelta = { - accum: response.result.data.value.accum_to.value.accum, - issued: response.result.data.value.issued, - revoked: response.result.data.value.revoked, - } - return { resolutionMetadata: {}, revocationStatusList: anonCredsRevocationStatusListFromIndyVdr( - revocationRegistryId, + revocationRegistryDefinitionId, revocationRegistryDefinition, - revocationRegistryDelta, - response.result.data.value.accum_to.txnTime, + revocationDelta, isIssuanceByDefault ), revocationStatusListMetadata: { @@ -643,25 +787,178 @@ export class IndyVdrAnonCredsRegistry implements AnonCredsRegistry { } } catch (error) { agentContext.config.logger.error( - `Error retrieving revocation registry delta '${revocationRegistryId}' from ledger, potentially revocation interval ends before revocation registry creation?"`, + `Error retrieving revocation registry delta '${revocationRegistryDefinitionId}' from ledger, potentially revocation interval ends before revocation registry creation?"`, { error, - revocationRegistryId: revocationRegistryId, + revocationRegistryId: revocationRegistryDefinitionId, } ) return { resolutionMetadata: { error: 'notFound', - message: `Error retrieving revocation registry delta '${revocationRegistryId}' from ledger, potentially revocation interval ends before revocation registry creation: ${error.message}`, + message: `Error retrieving revocation registry delta '${revocationRegistryDefinitionId}' from ledger, potentially revocation interval ends before revocation registry creation: ${error.message}`, }, revocationStatusListMetadata: {}, } } } - public async registerRevocationStatusList(): Promise { - throw new AriesFrameworkError('Not implemented!') + public async registerRevocationStatusList( + agentContext: AgentContext, + { options, revocationStatusList }: IndyVdrRegisterRevocationStatusList + ): Promise { + try { + // This will throw an error if trying to register a revocation status list with a legacy indy identifier. We only support did:indy + // identifiers for registering, that will allow us to extract the namespace and means all stored records will use did:indy identifiers. + const { endorsedTransaction, endorserDid, endorserMode } = options + const { namespaceIdentifier, namespace } = parseIndyDid(revocationStatusList.issuerId) + const indyVdrPoolService = agentContext.dependencyManager.resolve(IndyVdrPoolService) + const pool = indyVdrPoolService.getPoolForNamespace(namespace) + + agentContext.config.logger.debug( + `Registering revocation status list on ledger '${namespace}' with did '${revocationStatusList.issuerId}'`, + revocationStatusList + ) + + let writeRequest: CustomRequest + + // Parse the revocation registry id + const { + schemaSeqNo, + credentialDefinitionTag, + namespaceIdentifier: revocationRegistryNamespaceIdentifier, + revocationRegistryTag, + namespace: revocationRegistryNamespace, + } = parseIndyRevocationRegistryId(revocationStatusList.revRegDefId) + + const legacyRevocationRegistryDefinitionId = getUnqualifiedRevocationRegistryDefinitionId( + namespaceIdentifier, + schemaSeqNo, + credentialDefinitionTag, + revocationRegistryTag + ) + + const didIndyRevocationRegistryEntryId = getDidIndyRevocationRegistryEntryId( + namespace, + namespaceIdentifier, + schemaSeqNo, + credentialDefinitionTag, + revocationRegistryTag + ) + + if (revocationRegistryNamespace && revocationRegistryNamespace !== namespace) { + throw new AriesFrameworkError( + `Issued id '${revocationStatusList.issuerId}' does not have the same namespace (${namespace}) as the revocation registry definition '${revocationRegistryNamespace}'` + ) + } + + if (revocationRegistryNamespaceIdentifier !== namespaceIdentifier) { + throw new AriesFrameworkError( + `Cannot register revocation registry definition using a different DID. Revocation registry definition contains '${revocationRegistryNamespaceIdentifier}', but DID used was '${namespaceIdentifier}'` + ) + } + + if (endorsedTransaction) { + agentContext.config.logger.debug( + `Preparing endorsed tx '${endorsedTransaction}' for submission on ledger '${namespace}' with did '${revocationStatusList.issuerId}'`, + revocationStatusList + ) + + writeRequest = new CustomRequest({ customRequest: endorsedTransaction }) + } else { + const previousDelta = await this.fetchIndyRevocationDelta( + agentContext, + legacyRevocationRegistryDefinitionId, + // Fetch revocation delta for current timestamp + dateToTimestamp(new Date()) + ) + + const revocationRegistryDefinitionEntryValue = indyVdrCreateLatestRevocationDelta( + revocationStatusList.currentAccumulator, + revocationStatusList.revocationList, + previousDelta ?? undefined + ) + + const revocationRegistryDefinitionRequest = new RevocationRegistryEntryRequest({ + submitterDid: namespaceIdentifier, + revocationRegistryEntry: { + ver: '1.0', + value: revocationRegistryDefinitionEntryValue, + }, + revocationRegistryDefinitionType: 'CL_ACCUM', + revocationRegistryDefinitionId: legacyRevocationRegistryDefinitionId, + }) + + const submitterKey = await verificationKeyForIndyDid(agentContext, revocationStatusList.issuerId) + writeRequest = await pool.prepareWriteRequest( + agentContext, + revocationRegistryDefinitionRequest, + submitterKey, + endorserDid !== revocationStatusList.issuerId ? endorserDid : undefined + ) + + if (endorserMode === 'external') { + return { + jobId: didIndyRevocationRegistryEntryId, + revocationStatusListState: { + state: 'action', + action: 'endorseIndyTransaction', + revocationStatusList, + revocationStatusListRequest: writeRequest.body, + }, + registrationMetadata: {}, + revocationStatusListMetadata: {}, + } + } + + if (endorserMode === 'internal' && endorserDid !== revocationStatusList.issuerId) { + const endorserKey = await verificationKeyForIndyDid(agentContext, endorserDid as string) + await multiSignRequest(agentContext, writeRequest, endorserKey, parseIndyDid(endorserDid).namespaceIdentifier) + } + } + + const response = await pool.submitRequest( + writeRequest as RevocationRegistryEntryRequest + ) + agentContext.config.logger.debug( + `Registered revocation status list '${didIndyRevocationRegistryEntryId}' on ledger '${pool.indyNamespace}'`, + { + response, + revocationStatusList, + } + ) + + return { + revocationStatusListMetadata: {}, + revocationStatusListState: { + revocationStatusList: { + ...revocationStatusList, + timestamp: response.result.txnMetadata.txnTime, + }, + state: 'finished', + }, + registrationMetadata: {}, + } + } catch (error) { + agentContext.config.logger.error( + `Error registering revocation status list for revocation registry definition '${revocationStatusList.revRegDefId}}'`, + { + error, + did: revocationStatusList.issuerId, + } + ) + + return { + registrationMetadata: {}, + revocationStatusListMetadata: {}, + revocationStatusListState: { + revocationStatusList, + state: 'failed', + reason: `unknownError: ${error.message}`, + }, + } + } } private async fetchIndySchemaWithSeqNo(agentContext: AgentContext, seqNo: number, did: string) { @@ -697,6 +994,56 @@ export class IndyVdrAnonCredsRegistry implements AnonCredsRegistry { indyNamespace: pool.indyNamespace, } } + + private async fetchIndyRevocationDelta( + agentContext: AgentContext, + revocationRegistryDefinitionId: string, + toTs: number + ): Promise { + const indyVdrPoolService = agentContext.dependencyManager.resolve(IndyVdrPoolService) + + const { did, namespaceIdentifier, schemaSeqNo, credentialDefinitionTag, revocationRegistryTag } = + parseIndyRevocationRegistryId(revocationRegistryDefinitionId) + const { pool } = await indyVdrPoolService.getPoolForDid(agentContext, did) + + agentContext.config.logger.debug( + `Using ledger '${pool.indyNamespace}' to retrieve revocation registry deltas with revocation registry definition id '${revocationRegistryDefinitionId}' until ${toTs}` + ) + + const legacyRevocationRegistryDefinitionId = getUnqualifiedRevocationRegistryDefinitionId( + namespaceIdentifier, + schemaSeqNo, + credentialDefinitionTag, + revocationRegistryTag + ) + + const deltaRequest = new GetRevocationRegistryDeltaRequest({ + toTs, + submitterDid: namespaceIdentifier, + revocationRegistryId: legacyRevocationRegistryDefinitionId, + }) + + agentContext.config.logger.trace(`Submitting get transaction request to ledger '${pool.indyNamespace}'`) + const response = await pool.submitRequest(deltaRequest) + const { + result: { data, type, txnTime }, + } = response + + // Indicating there are no deltas + if (type !== '117' || data === null || !txnTime) { + agentContext.config.logger.warn( + `Could not get any deltas from ledger for revocation registry definition '${revocationRegistryDefinitionId}' from ledger '${pool.indyNamespace}'` + ) + return null + } + + return { + revoked: data.value.revoked, + issued: data.value.issued, + accum: data.value.accum_to.value.accum, + txnTime, + } + } } interface SchemaType { @@ -781,3 +1128,72 @@ export type IndyVdrRegisterCredentialDefinition = | IndyVdrRegisterCredentialDefinitionExternalSubmitOptions export type IndyVdrRegisterCredentialDefinitionOptions = IndyVdrRegisterCredentialDefinition['options'] + +export interface IndyVdrRegisterRevocationRegistryDefinitionInternalOptions { + revocationRegistryDefinition: AnonCredsRevocationRegistryDefinition + options: InternalEndorsement +} + +export interface IndyVdrRegisterRevocationRegistryDefinitionExternalCreateOptions { + revocationRegistryDefinition: AnonCredsRevocationRegistryDefinition + options: ExternalEndorsementCreate +} + +export interface IndyVdrRegisterRevocationRegistryDefinitionExternalSubmitOptions { + revocationRegistryDefinition: AnonCredsRevocationRegistryDefinition + options: ExternalEndorsementSubmit +} + +export interface IndyVdrRegisterRevocationRegistryDefinitionReturnStateAction + extends RegisterRevocationRegistryDefinitionReturnStateAction { + action: 'endorseIndyTransaction' + revocationRegistryDefinitionRequest: string +} + +export interface IndyVdrRegisterRevocationRegistryDefinitionReturn extends RegisterRevocationRegistryDefinitionReturn { + revocationRegistryDefinitionState: + | IndyVdrRegisterRevocationRegistryDefinitionReturnStateAction + | RegisterRevocationRegistryDefinitionReturnStateWait + | RegisterRevocationRegistryDefinitionReturnStateFinished + | RegisterRevocationRegistryDefinitionReturnStateFailed +} + +export type IndyVdrRegisterRevocationRegistryDefinition = + | IndyVdrRegisterRevocationRegistryDefinitionInternalOptions + | IndyVdrRegisterRevocationRegistryDefinitionExternalCreateOptions + | IndyVdrRegisterRevocationRegistryDefinitionExternalSubmitOptions + +export type IndyVdrRegisterRevocationRegistryDefinitionOptions = IndyVdrRegisterRevocationRegistryDefinition['options'] + +export interface IndyVdrRegisterRevocationStatusListInternalOptions extends RegisterRevocationStatusListOptions { + options: InternalEndorsement +} + +export interface IndyVdrRegisterRevocationStatusListExternalCreateOptions extends RegisterRevocationStatusListOptions { + options: ExternalEndorsementCreate +} + +export interface IndyVdrRegisterRevocationStatusListExternalSubmitOptions extends RegisterRevocationStatusListOptions { + options: ExternalEndorsementSubmit +} + +export interface IndyVdrRegisterRevocationStatusListReturnStateAction + extends RegisterRevocationStatusListReturnStateAction { + action: 'endorseIndyTransaction' + revocationStatusListRequest: string +} + +export interface IndyVdrRegisterRevocationStatusListReturn extends RegisterRevocationStatusListReturn { + revocationStatusListState: + | IndyVdrRegisterRevocationStatusListReturnStateAction + | RegisterRevocationStatusListReturnStateWait + | RegisterRevocationStatusListReturnStateFinished + | RegisterRevocationStatusListReturnStateFailed +} + +export type IndyVdrRegisterRevocationStatusList = + | IndyVdrRegisterRevocationStatusListInternalOptions + | IndyVdrRegisterRevocationStatusListExternalCreateOptions + | IndyVdrRegisterRevocationStatusListExternalSubmitOptions + +export type IndyVdrRegisterRevocationStatusListOptions = IndyVdrRegisterRevocationStatusList['options'] diff --git a/packages/indy-vdr/src/anoncreds/utils/__tests__/identifiers.test.ts b/packages/indy-vdr/src/anoncreds/utils/__tests__/identifiers.test.ts index b96720611b..0028a3bfa2 100644 --- a/packages/indy-vdr/src/anoncreds/utils/__tests__/identifiers.test.ts +++ b/packages/indy-vdr/src/anoncreds/utils/__tests__/identifiers.test.ts @@ -1,6 +1,6 @@ import { getDidIndyCredentialDefinitionId, - getDidIndyRevocationRegistryId, + getDidIndyRevocationRegistryDefinitionId, getDidIndySchemaId, indyVdrAnonCredsRegistryIdentifierRegex, } from '../identifiers' @@ -72,7 +72,7 @@ describe('identifiers', () => { const credentialDefinitionTag = 'someTag' const tag = 'anotherTag' - expect(getDidIndyRevocationRegistryId(namespace, did, seqNo, credentialDefinitionTag, tag)).toEqual( + expect(getDidIndyRevocationRegistryDefinitionId(namespace, did, seqNo, credentialDefinitionTag, tag)).toEqual( 'did:indy:sovrin:test:12345/anoncreds/v0/REV_REG_DEF/420/someTag/anotherTag' ) }) diff --git a/packages/indy-vdr/src/anoncreds/utils/__tests__/transform.test.ts b/packages/indy-vdr/src/anoncreds/utils/__tests__/transform.test.ts new file mode 100644 index 0000000000..c5f71e8868 --- /dev/null +++ b/packages/indy-vdr/src/anoncreds/utils/__tests__/transform.test.ts @@ -0,0 +1,164 @@ +import type { RevocationRegistryDelta } from '../transform' +import type { AnonCredsRevocationRegistryDefinition } from '@aries-framework/anoncreds' + +import { indyVdrCreateLatestRevocationDelta, anonCredsRevocationStatusListFromIndyVdr } from '../transform' + +const createRevocationRegistryDefinition = (maxCreds: number): AnonCredsRevocationRegistryDefinition => ({ + value: { + tailsHash: 'hash', + maxCredNum: maxCreds, + publicKeys: { + accumKey: { + z: 'key', + }, + }, + tailsLocation: 'nowhere', + }, + revocDefType: 'CL_ACCUM', + tag: 'REV_TAG', + issuerId: 'does:not:matter', + credDefId: 'does:not:matter', +}) + +describe('transform', () => { + const accum = 'does not matter' + const revocationRegistryDefinitionId = 'does:not:matter' + + describe('indy vdr delta to anoncreds revocation status list', () => { + test('issued and revoked are filled', () => { + const delta: RevocationRegistryDelta = { + accum, + issued: [0, 1, 2, 3, 4], + revoked: [5, 6, 7, 8, 9], + txnTime: 1, + } + + const revocationRegistryDefinition = createRevocationRegistryDefinition(10) + + const statusList = anonCredsRevocationStatusListFromIndyVdr( + revocationRegistryDefinitionId, + revocationRegistryDefinition, + delta, + true + ) + + expect(statusList.revocationList).toStrictEqual([0, 0, 0, 0, 0, 1, 1, 1, 1, 1]) + }) + + test('issued and revoked are empty (issuance by default)', () => { + const delta: RevocationRegistryDelta = { + accum, + issued: [], + revoked: [], + txnTime: 1, + } + + const revocationRegistryDefinition = createRevocationRegistryDefinition(10) + + const statusList = anonCredsRevocationStatusListFromIndyVdr( + revocationRegistryDefinitionId, + revocationRegistryDefinition, + delta, + true + ) + + expect(statusList.revocationList).toStrictEqual([0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) + }) + + test('issued and revoked are empty (issuance on demand)', () => { + const delta: RevocationRegistryDelta = { + accum, + issued: [], + revoked: [], + txnTime: 1, + } + + const revocationRegistryDefinition = createRevocationRegistryDefinition(10) + + const statusList = anonCredsRevocationStatusListFromIndyVdr( + revocationRegistryDefinitionId, + revocationRegistryDefinition, + delta, + false + ) + + expect(statusList.revocationList).toStrictEqual([1, 1, 1, 1, 1, 1, 1, 1, 1, 1]) + }) + + test('issued index is too high', () => { + const delta: RevocationRegistryDelta = { + accum, + issued: [200], + revoked: [5, 6, 7, 8, 9], + txnTime: 1, + } + + const revocationRegistryDefinition = createRevocationRegistryDefinition(10) + + expect(() => + anonCredsRevocationStatusListFromIndyVdr( + revocationRegistryDefinitionId, + revocationRegistryDefinition, + delta, + true + ) + ).toThrowError() + }) + }) + + describe('create latest indy vdr delta from status list and previous delta', () => { + test('delta and status list are equal', () => { + const delta: RevocationRegistryDelta = { + accum, + issued: [0, 1, 2, 3, 4], + revoked: [5, 6, 7, 8, 9], + txnTime: 1, + } + + const revocationStatusList = [0, 0, 0, 0, 0, 1, 1, 1, 1, 1] + + const { revoked, issued } = indyVdrCreateLatestRevocationDelta(accum, revocationStatusList, delta) + + expect(revoked).toStrictEqual([]) + expect(issued).toStrictEqual([]) + }) + + test('no previous delta', () => { + const revocationStatusList = [0, 0, 0, 0, 0, 1, 1, 1, 1, 1] + + const { revoked, issued } = indyVdrCreateLatestRevocationDelta(accum, revocationStatusList) + + expect(issued).toStrictEqual([0, 1, 2, 3, 4]) + expect(revoked).toStrictEqual([5, 6, 7, 8, 9]) + }) + + test('status list and previous delta are out of sync', () => { + const delta: RevocationRegistryDelta = { + accum, + issued: [0], + revoked: [5], + txnTime: 1, + } + + const revocationStatusList = [0, 0, 0, 0, 0, 1, 1, 1, 1, 1] + + const { revoked, issued } = indyVdrCreateLatestRevocationDelta(accum, revocationStatusList, delta) + + expect(issued).toStrictEqual([1, 2, 3, 4]) + expect(revoked).toStrictEqual([6, 7, 8, 9]) + }) + + test('previous delta index exceeds length of revocation status list', () => { + const delta: RevocationRegistryDelta = { + accum, + issued: [200], + revoked: [5], + txnTime: 1, + } + + const revocationStatusList = [0, 0, 0, 0, 0, 1, 1, 1, 1, 1] + + expect(() => indyVdrCreateLatestRevocationDelta(accum, revocationStatusList, delta)).toThrowError() + }) + }) +}) diff --git a/packages/indy-vdr/src/anoncreds/utils/identifiers.ts b/packages/indy-vdr/src/anoncreds/utils/identifiers.ts index 1e9d6a8fd3..46bebaadf7 100644 --- a/packages/indy-vdr/src/anoncreds/utils/identifiers.ts +++ b/packages/indy-vdr/src/anoncreds/utils/identifiers.ts @@ -46,18 +46,28 @@ export function getDidIndySchemaId(namespace: string, unqualifiedDid: string, na export function getDidIndyCredentialDefinitionId( namespace: string, unqualifiedDid: string, - seqNo: string | number, + schemaSeqNo: string | number, tag: string ) { - return `did:indy:${namespace}:${unqualifiedDid}/anoncreds/v0/CLAIM_DEF/${seqNo}/${tag}` + return `did:indy:${namespace}:${unqualifiedDid}/anoncreds/v0/CLAIM_DEF/${schemaSeqNo}/${tag}` } -export function getDidIndyRevocationRegistryId( +export function getDidIndyRevocationRegistryDefinitionId( namespace: string, unqualifiedDid: string, - seqNo: string | number, + schemaSeqNo: string | number, credentialDefinitionTag: string, revocationRegistryTag: string ) { - return `did:indy:${namespace}:${unqualifiedDid}/anoncreds/v0/REV_REG_DEF/${seqNo}/${credentialDefinitionTag}/${revocationRegistryTag}` + return `did:indy:${namespace}:${unqualifiedDid}/anoncreds/v0/REV_REG_DEF/${schemaSeqNo}/${credentialDefinitionTag}/${revocationRegistryTag}` +} + +export function getDidIndyRevocationRegistryEntryId( + namespace: string, + unqualifiedDid: string, + schemaSeqNo: string | number, + credentialDefinitionTag: string, + revocationRegistryTag: string +) { + return `did:indy:${namespace}:${unqualifiedDid}/anoncreds/v0/REV_REG_ENTRY/${schemaSeqNo}/${credentialDefinitionTag}/${revocationRegistryTag}` } diff --git a/packages/indy-vdr/src/anoncreds/utils/transform.ts b/packages/indy-vdr/src/anoncreds/utils/transform.ts index 36f4628bb4..87180e50d3 100644 --- a/packages/indy-vdr/src/anoncreds/utils/transform.ts +++ b/packages/indy-vdr/src/anoncreds/utils/transform.ts @@ -1,26 +1,51 @@ import type { AnonCredsRevocationStatusList, AnonCredsRevocationRegistryDefinition } from '@aries-framework/anoncreds' +import { AriesFrameworkError } from '@aries-framework/core' + +export type RevocationRegistryDelta = { + accum: string + issued: number[] + revoked: number[] + txnTime: number +} + +enum RevocationState { + Active, + Revoked, +} + export function anonCredsRevocationStatusListFromIndyVdr( revocationRegistryDefinitionId: string, revocationRegistryDefinition: AnonCredsRevocationRegistryDefinition, - delta: RevocRegDelta, - timestamp: number, + delta: RevocationRegistryDelta, isIssuanceByDefault: boolean ): AnonCredsRevocationStatusList { + // Check whether the highest delta index is supported in the `maxCredNum` field of the + // revocation registry definition. This will likely also be checked on other levels as well + // by the ledger or the indy-vdr library itself + if (Math.max(...delta.issued, ...delta.revoked) >= revocationRegistryDefinition.value.maxCredNum) { + throw new AriesFrameworkError( + `Highest delta index '${Math.max( + ...delta.issued, + ...delta.revoked + )}' is too large for the Revocation registry maxCredNum '${revocationRegistryDefinition.value.maxCredNum}' ` + ) + } + // 0 means unrevoked, 1 means revoked - const defaultState = isIssuanceByDefault ? 0 : 1 + const defaultState = isIssuanceByDefault ? RevocationState.Active : RevocationState.Revoked // Fill with default value const revocationList = new Array(revocationRegistryDefinition.value.maxCredNum).fill(defaultState) // Set all `issuer` indexes to 0 (not revoked) for (const issued of delta.issued ?? []) { - revocationList[issued] = 0 + revocationList[issued] = RevocationState.Active } // Set all `revoked` indexes to 1 (revoked) for (const revoked of delta.revoked ?? []) { - revocationList[revoked] = 1 + revocationList[revoked] = RevocationState.Revoked } return { @@ -28,12 +53,83 @@ export function anonCredsRevocationStatusListFromIndyVdr( currentAccumulator: delta.accum, revRegDefId: revocationRegistryDefinitionId, revocationList, - timestamp, + timestamp: delta.txnTime, } } -interface RevocRegDelta { - accum: string - issued: number[] - revoked: number[] +/** + * + * Transforms the previous deltas and the full revocation status list into the latest delta + * + * ## Example + * + * input: + * + * revocationStatusList: [0, 1, 1, 1, 0, 0, 0, 1, 1, 0] + * previousDelta: + * - issued: [1, 2, 5, 8, 9] + * - revoked: [0, 3, 4, 6, 7] + * + * output: + * - issued: [5, 9] + * - revoked: [3, 7] + * + */ +export function indyVdrCreateLatestRevocationDelta( + currentAccumulator: string, + revocationStatusList: Array, + previousDelta?: RevocationRegistryDelta +) { + if (previousDelta && Math.max(...previousDelta.issued, ...previousDelta.revoked) > revocationStatusList.length - 1) { + throw new AriesFrameworkError( + `Indy Vdr delta contains an index '${Math.max( + ...previousDelta.revoked, + ...previousDelta.issued + )}' that exceeds the length of the revocation status list '${revocationStatusList.length}'` + ) + } + + const issued: Array = [] + const revoked: Array = [] + + if (previousDelta) { + for (const issuedIdx of previousDelta.issued) { + // Check whether the revocationStatusList has a different state compared to the delta + if (revocationStatusList[issuedIdx] !== RevocationState.Active) { + issued.push(issuedIdx) + } + } + + for (const revokedIdx of previousDelta.revoked) { + // Check whether the revocationStatusList has a different state compared to the delta + if (revocationStatusList[revokedIdx] !== RevocationState.Revoked) { + revoked.push(revokedIdx) + } + } + + revocationStatusList.forEach((revocationStatus, idx) => { + // Check whether the revocationStatusList entry is not included in the previous delta issued indices + if (revocationStatus === RevocationState.Active && !previousDelta.issued.includes(idx)) { + issued.push(idx) + } + + // Check whether the revocationStatusList entry is not included in the previous delta revoked indices + if (revocationStatus === RevocationState.Revoked && !previousDelta.revoked.includes(idx)) { + revoked.push(idx) + } + }) + } else { + // No delta is provided, initial state, so the entire revocation status list is converted to two list of indices + revocationStatusList.forEach((revocationStatus, idx) => { + if (revocationStatus === RevocationState.Active) issued.push(idx) + if (revocationStatus === RevocationState.Revoked) revoked.push(idx) + }) + } + + return { + issued, + revoked, + accum: currentAccumulator, + prevAccum: previousDelta?.accum, + } } diff --git a/packages/indy-vdr/tests/__fixtures__/anoncreds.ts b/packages/indy-vdr/tests/__fixtures__/anoncreds.ts index fea36d5fcb..f4b57c21f0 100644 --- a/packages/indy-vdr/tests/__fixtures__/anoncreds.ts +++ b/packages/indy-vdr/tests/__fixtures__/anoncreds.ts @@ -28,3 +28,14 @@ export const credentialDefinitionValue = { y: '1 1BF97F07270EC21A89E43BCA645D86A755F846B547238F1DA379E088CDD9B40D 1 146BB00F56FFC0DEF6541CEB484C718559B398DB1547B52850E46B23144161F1 1 079A1BEF8DFFA4E6352F701D476664340E7FBE5D3F46B897412BD2B5F10E33D7 1 02FDC508AEF90FB11961AF332BE4037973C76B954FFA48848F7E0588E93FCA8C 2 095E45DDF417D05FB10933FFC63D474548B7FFFF7888802F07FFFFFF7D07A8A8 1 0000000000000000000000000000000000000000000000000000000000000000', }, } + +export const revocationRegistryDefinitionValue = { + maxCredNum: 11, + publicKeys: { + accumKey: { + z: '1 1812B206EB395D3AEBD4BBF53EBB0FFC3371D8BD6175316AB32C1C5F65452051 1 22A079D49C5351EFDC1410C81A1F6D8B2E3B79CFF20A30690C118FE2050F72CB 1 0FFC28B923A4654E261DB4CB5B9BABEFCB4DB189B20F52412B0CC9CCCBB8A3B2 1 1EE967C43EF1A3F487061D21B07076A26C126AAF7712E7B5CF5A53688DDD5CC0 1 009ED4D65879CA81DA8227D34CEA3B759B4627E1E2FFB273E9645CD4F3B10F19 1 1CF070212E1E213AEB472F56EDFC9D48009796C77B2D8CC16F2836E37B8715C2 1 04954F0B7B468781BAAE3291DD0E6FFA7F1AF66CAA4094D37B24363CC34606FB 1 115367CB755E9DB18781B3825CB1AEE2C334558B2C038E13DF57BB57CE1CF847 1 110D37EC05862EE2757A7DF39E814876FC97376FF8105D2D29619CB575537BDE 1 13C559A9563FCE083B3B39AE7E8FCA4099BEF3A4C8C6672E543D521F9DA88F96 1 137D87CC22ACC1B6B8C20EABE59F6ED456A58FE4CBEEFDFC4FA9B87E3EF32D17 1 00A2A9711737AAF0404F35AE502887AC6172B2B57D236BD4A40B45F659BFC696', + }, + }, + tailsHash: 'HLKresYcDSZYSKogq8wive4zyXNY84669MygftLFBG1i', + tailsLocation: '/var/folders/l3/xy8jzyvj4p5_d9g1123rt4bw0000gn/T/HLKresYcDSZYSKogq8wive4zyXNY84669MygftLFBG1i', +} diff --git a/packages/indy-vdr/tests/indy-vdr-anoncreds-registry.e2e.test.ts b/packages/indy-vdr/tests/indy-vdr-anoncreds-registry.e2e.test.ts index c98a807b91..f55b43fd8d 100644 --- a/packages/indy-vdr/tests/indy-vdr-anoncreds-registry.e2e.test.ts +++ b/packages/indy-vdr/tests/indy-vdr-anoncreds-registry.e2e.test.ts @@ -1,14 +1,12 @@ import type { IndyVdrDidCreateOptions, IndyVdrDidCreateResult } from '../src/dids/IndyVdrIndyDidRegistrar' -import type { RevocationRegistryEntryResponse } from '@hyperledger/indy-vdr-shared' -import { parseIndyDid } from '@aries-framework/anoncreds' +import { + getUnqualifiedRevocationRegistryDefinitionId, + parseIndyDid, + parseIndyRevocationRegistryId, +} from '@aries-framework/anoncreds' import { Agent, DidsModule, TypedArrayEncoder } from '@aries-framework/core' import { indyVdr } from '@hyperledger/indy-vdr-nodejs' -import { - CustomRequest, - RevocationRegistryDefinitionRequest, - RevocationRegistryEntryRequest, -} from '@hyperledger/indy-vdr-shared' import { agentDependencies, getAgentConfig, importExistingIndyDidFromPrivateKey } from '../../core/tests/helpers' import { IndySdkModule } from '../../indy-sdk/src' @@ -16,10 +14,9 @@ import { indySdk } from '../../indy-sdk/tests/setupIndySdkModule' import { IndyVdrIndyDidResolver, IndyVdrModule, IndyVdrSovDidResolver } from '../src' import { IndyVdrAnonCredsRegistry } from '../src/anoncreds/IndyVdrAnonCredsRegistry' import { IndyVdrIndyDidRegistrar } from '../src/dids/IndyVdrIndyDidRegistrar' -import { verificationKeyForIndyDid } from '../src/dids/didIndyUtil' import { IndyVdrPoolService } from '../src/pool' -import { credentialDefinitionValue } from './__fixtures__/anoncreds' +import { credentialDefinitionValue, revocationRegistryDefinitionValue } from './__fixtures__/anoncreds' import { indyVdrModuleConfig } from './helpers' const endorserConfig = getAgentConfig('IndyVdrAnonCredsRegistryEndorser') @@ -64,7 +61,6 @@ const agent = new Agent({ }) const indyVdrPoolService = endorser.dependencyManager.resolve(IndyVdrPoolService) -const pool = indyVdrPoolService.getPoolForNamespace('pool:localtest') describe('IndyVdrAnonCredsRegistry', () => { let endorserDid: string @@ -104,7 +100,6 @@ describe('IndyVdrAnonCredsRegistry', () => { const didIndyIssuerId = didCreateResult.didState.did const { namespaceIdentifier: legacyIssuerId } = parseIndyDid(didIndyIssuerId) const dynamicVersion = `1.${Math.random() * 100}` - const signingKey = await verificationKeyForIndyDid(endorser.context, didIndyIssuerId) const legacySchemaId = `${legacyIssuerId}:2:test:${dynamicVersion}` const didIndySchemaId = `did:indy:pool:localtest:${legacyIssuerId}/anoncreds/v0/SCHEMA/test/${dynamicVersion}` @@ -120,6 +115,7 @@ describe('IndyVdrAnonCredsRegistry', () => { version: dynamicVersion, }, }) + const schemaSeqNo = schemaResult.schemaMetadata.indyLedgerSeqNo as number expect(schemaResult).toMatchObject({ schemaState: { @@ -174,8 +170,8 @@ describe('IndyVdrAnonCredsRegistry', () => { }, }) - const legacyCredentialDefinitionId = `${legacyIssuerId}:3:CL:${schemaResult.schemaMetadata.indyLedgerSeqNo}:TAG` - const didIndyCredentialDefinitionId = `did:indy:pool:localtest:${legacyIssuerId}/anoncreds/v0/CLAIM_DEF/${schemaResult.schemaMetadata.indyLedgerSeqNo}/TAG` + const legacyCredentialDefinitionId = `${legacyIssuerId}:3:CL:${schemaSeqNo}:TAG` + const didIndyCredentialDefinitionId = `did:indy:pool:localtest:${legacyIssuerId}/anoncreds/v0/CLAIM_DEF/${schemaSeqNo}/TAG` const credentialDefinitionResult = await indyVdrAnonCredsRegistry.registerCredentialDefinition(endorser.context, { credentialDefinition: { issuerId: didIndyIssuerId, @@ -250,18 +246,15 @@ describe('IndyVdrAnonCredsRegistry', () => { resolutionMetadata: {}, }) - // We don't support creating a revocation registry using AFJ yet, so we directly use indy-vdr to create the revocation registry - const legacyRevocationRegistryId = `${legacyIssuerId}:4:${legacyIssuerId}:3:CL:${schemaResult.schemaMetadata.indyLedgerSeqNo}:TAG:CL_ACCUM:tag` - const didIndyRevocationRegistryId = `did:indy:pool:localtest:${legacyIssuerId}/anoncreds/v0/REV_REG_DEF/${schemaResult.schemaMetadata.indyLedgerSeqNo}/TAG/tag` - const revocationRegistryRequest = new RevocationRegistryDefinitionRequest({ - submitterDid: legacyIssuerId, - revocationRegistryDefinitionV1: { - credDefId: legacyCredentialDefinitionId, - id: legacyRevocationRegistryId, + const { + revocationRegistryDefinitionState: { revocationRegistryDefinitionId: didIndyRevocationRegistryDefinitionId }, + } = await indyVdrAnonCredsRegistry.registerRevocationRegistryDefinition(endorser.context, { + revocationRegistryDefinition: { + tag: 'REV_TAG', + issuerId: didIndyIssuerId, + credDefId: didIndyCredentialDefinitionId, revocDefType: 'CL_ACCUM', - tag: 'tag', value: { - issuanceType: 'ISSUANCE_BY_DEFAULT', maxCredNum: 100, publicKeys: { accumKey: { @@ -272,55 +265,37 @@ describe('IndyVdrAnonCredsRegistry', () => { tailsLocation: '/var/folders/l3/xy8jzyvj4p5_d9g1123rt4bw0000gn/T/HLKresYcDSZYSKogq8wive4zyXNY84669MygftLFBG1i', }, - ver: '1.0', }, - }) - - // After this call, the revocation registry should now be resolvable - const writeRequest = await pool.prepareWriteRequest( - endorser.context, - revocationRegistryRequest, - signingKey, - endorserDid - ) - const endorsedRequest = await endorser.modules.indyVdr.endorseTransaction(writeRequest.body, endorserDid) - await pool.submitRequest(new CustomRequest({ customRequest: endorsedRequest })) - - // Also create a revocation registry entry - const revocationEntryRequest = new RevocationRegistryEntryRequest({ - revocationRegistryDefinitionId: legacyRevocationRegistryId, - revocationRegistryDefinitionType: 'CL_ACCUM', - revocationRegistryEntry: { - ver: '1.0', - value: { - accum: '1', - }, + options: { + endorserMode: 'internal', + endorserDid: endorserDid, }, - submitterDid: legacyIssuerId, }) - // After this call we can query the revocation registry entries (using timestamp now) + if (!didIndyRevocationRegistryDefinitionId) { + throw Error('revocation registry definition was not created correctly') + } - const revocationEntryWriteRequest = await pool.prepareWriteRequest( - endorser.context, - revocationEntryRequest, - signingKey, - endorserDid + const { credentialDefinitionTag, revocationRegistryTag } = parseIndyRevocationRegistryId( + didIndyRevocationRegistryDefinitionId ) - const endorsedRevocationEntryWriteRequest = await endorser.modules.indyVdr.endorseTransaction( - revocationEntryWriteRequest.body, - endorserDid + const legacyRevocationRegistryDefinitionId = getUnqualifiedRevocationRegistryDefinitionId( + legacyIssuerId, + schemaSeqNo, + credentialDefinitionTag, + revocationRegistryTag ) - const entryResponse = (await pool.submitRequest( - new CustomRequest({ customRequest: endorsedRevocationEntryWriteRequest }) - )) as RevocationRegistryEntryResponse + + // Wait some time before resolving revocation registry definition object + await new Promise((res) => setTimeout(res, 1000)) const legacyRevocationRegistryDefinition = await indyVdrAnonCredsRegistry.getRevocationRegistryDefinition( endorser.context, - legacyRevocationRegistryId + legacyRevocationRegistryDefinitionId ) + expect(legacyRevocationRegistryDefinition).toMatchObject({ - revocationRegistryDefinitionId: legacyRevocationRegistryId, + revocationRegistryDefinitionId: legacyRevocationRegistryDefinitionId, revocationRegistryDefinition: { issuerId: legacyIssuerId, revocDefType: 'CL_ACCUM', @@ -335,7 +310,7 @@ describe('IndyVdrAnonCredsRegistry', () => { }, }, }, - tag: 'tag', + tag: 'REV_TAG', credDefId: legacyCredentialDefinitionId, }, revocationRegistryDefinitionMetadata: { @@ -347,10 +322,11 @@ describe('IndyVdrAnonCredsRegistry', () => { const didIndyRevocationRegistryDefinition = await indyVdrAnonCredsRegistry.getRevocationRegistryDefinition( endorser.context, - didIndyRevocationRegistryId + didIndyRevocationRegistryDefinitionId ) + expect(didIndyRevocationRegistryDefinition).toMatchObject({ - revocationRegistryDefinitionId: didIndyRevocationRegistryId, + revocationRegistryDefinitionId: didIndyRevocationRegistryDefinitionId, revocationRegistryDefinition: { issuerId: didIndyIssuerId, revocDefType: 'CL_ACCUM', @@ -365,7 +341,7 @@ describe('IndyVdrAnonCredsRegistry', () => { }, }, }, - tag: 'tag', + tag: 'REV_TAG', credDefId: didIndyCredentialDefinitionId, }, revocationRegistryDefinitionMetadata: { @@ -375,10 +351,30 @@ describe('IndyVdrAnonCredsRegistry', () => { resolutionMetadata: {}, }) + const registerStatusListResult = await indyVdrAnonCredsRegistry.registerRevocationStatusList(endorser.context, { + revocationStatusList: { + issuerId: didIndyIssuerId, + revRegDefId: didIndyRevocationRegistryDefinitionId, + revocationList: new Array(100).fill(0), + currentAccumulator: '1', + }, + options: { + endorserMode: 'internal', + endorserDid: endorserDid, + }, + }) + + if (registerStatusListResult.revocationStatusListState.state !== 'finished') { + throw new Error(`Unable to register status list: ${JSON.stringify(registerStatusListResult)}`) + } + + // Wait some time before resolving revocation status list object + await new Promise((res) => setTimeout(res, 1000)) + const legacyRevocationStatusList = await indyVdrAnonCredsRegistry.getRevocationStatusList( endorser.context, - legacyRevocationRegistryId, - entryResponse.result.txnMetadata.txnTime + legacyRevocationRegistryDefinitionId, + registerStatusListResult.revocationStatusListState.revocationStatusList.timestamp ) expect(legacyRevocationStatusList).toMatchObject({ @@ -386,13 +382,13 @@ describe('IndyVdrAnonCredsRegistry', () => { revocationStatusList: { issuerId: legacyIssuerId, currentAccumulator: '1', - revRegDefId: legacyRevocationRegistryId, + revRegDefId: legacyRevocationRegistryDefinitionId, revocationList: [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ], - timestamp: entryResponse.result.txnMetadata.txnTime, + timestamp: registerStatusListResult.revocationStatusListState.revocationStatusList.timestamp, }, revocationStatusListMetadata: { didIndyNamespace: 'pool:localtest', @@ -401,8 +397,8 @@ describe('IndyVdrAnonCredsRegistry', () => { const didIndyRevocationStatusList = await indyVdrAnonCredsRegistry.getRevocationStatusList( endorser.context, - didIndyRevocationRegistryId, - entryResponse.result.txnMetadata.txnTime + didIndyRevocationRegistryDefinitionId, + registerStatusListResult.revocationStatusListState.revocationStatusList.timestamp ) expect(didIndyRevocationStatusList).toMatchObject({ @@ -410,13 +406,13 @@ describe('IndyVdrAnonCredsRegistry', () => { revocationStatusList: { issuerId: didIndyIssuerId, currentAccumulator: '1', - revRegDefId: didIndyRevocationRegistryId, + revRegDefId: didIndyRevocationRegistryDefinitionId, revocationList: [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ], - timestamp: entryResponse.result.txnMetadata.txnTime, + timestamp: registerStatusListResult.revocationStatusListState.revocationStatusList.timestamp, }, revocationStatusListMetadata: { didIndyNamespace: 'pool:localtest', @@ -429,8 +425,6 @@ describe('IndyVdrAnonCredsRegistry', () => { const legacyIssuerId = 'DJKobikPAaYWAu9vfhEEo5' const didIndyIssuerId = 'did:indy:pool:localtest:DJKobikPAaYWAu9vfhEEo5' - const signingKey = await verificationKeyForIndyDid(agent.context, didIndyIssuerId) - const legacySchemaId = `DJKobikPAaYWAu9vfhEEo5:2:test:${dynamicVersion}` const didIndySchemaId = `did:indy:pool:localtest:DJKobikPAaYWAu9vfhEEo5/anoncreds/v0/SCHEMA/test/${dynamicVersion}` @@ -447,6 +441,8 @@ describe('IndyVdrAnonCredsRegistry', () => { }, }) + const schemaSeqNo = schemaResult.schemaMetadata.indyLedgerSeqNo as number + expect(schemaResult).toMatchObject({ schemaState: { state: 'finished', @@ -500,8 +496,8 @@ describe('IndyVdrAnonCredsRegistry', () => { }, }) - const legacyCredentialDefinitionId = `DJKobikPAaYWAu9vfhEEo5:3:CL:${schemaResult.schemaMetadata.indyLedgerSeqNo}:TAG` - const didIndyCredentialDefinitionId = `did:indy:pool:localtest:DJKobikPAaYWAu9vfhEEo5/anoncreds/v0/CLAIM_DEF/${schemaResult.schemaMetadata.indyLedgerSeqNo}/TAG` + const legacyCredentialDefinitionId = `DJKobikPAaYWAu9vfhEEo5:3:CL:${schemaSeqNo}:TAG` + const didIndyCredentialDefinitionId = `did:indy:pool:localtest:DJKobikPAaYWAu9vfhEEo5/anoncreds/v0/CLAIM_DEF/${schemaSeqNo}/TAG` const credentialDefinitionResult = await indyVdrAnonCredsRegistry.registerCredentialDefinition(endorser.context, { credentialDefinition: { issuerId: didIndyIssuerId, @@ -576,18 +572,15 @@ describe('IndyVdrAnonCredsRegistry', () => { resolutionMetadata: {}, }) - // We don't support creating a revocation registry using AFJ yet, so we directly use indy-vdr to create the revocation registry - const legacyRevocationRegistryId = `DJKobikPAaYWAu9vfhEEo5:4:DJKobikPAaYWAu9vfhEEo5:3:CL:${schemaResult.schemaMetadata.indyLedgerSeqNo}:TAG:CL_ACCUM:tag` - const didIndyRevocationRegistryId = `did:indy:pool:localtest:DJKobikPAaYWAu9vfhEEo5/anoncreds/v0/REV_REG_DEF/${schemaResult.schemaMetadata.indyLedgerSeqNo}/TAG/tag` - const revocationRegistryRequest = new RevocationRegistryDefinitionRequest({ - submitterDid: 'DJKobikPAaYWAu9vfhEEo5', - revocationRegistryDefinitionV1: { - credDefId: legacyCredentialDefinitionId, - id: legacyRevocationRegistryId, + const { + revocationRegistryDefinitionState: { revocationRegistryDefinitionId: didIndyRevocationRegistryDefinitionId }, + } = await indyVdrAnonCredsRegistry.registerRevocationRegistryDefinition(endorser.context, { + revocationRegistryDefinition: { + tag: 'REV_TAG', + issuerId: didIndyIssuerId, + credDefId: didIndyCredentialDefinitionId, revocDefType: 'CL_ACCUM', - tag: 'tag', value: { - issuanceType: 'ISSUANCE_BY_DEFAULT', maxCredNum: 100, publicKeys: { accumKey: { @@ -598,42 +591,37 @@ describe('IndyVdrAnonCredsRegistry', () => { tailsLocation: '/var/folders/l3/xy8jzyvj4p5_d9g1123rt4bw0000gn/T/HLKresYcDSZYSKogq8wive4zyXNY84669MygftLFBG1i', }, - ver: '1.0', }, - }) - - // After this call, the revocation registry should now be resolvable - const writeRequest = await pool.prepareWriteRequest(endorser.context, revocationRegistryRequest, signingKey) - await pool.submitRequest(writeRequest) - - // Also create a revocation registry entry - const revocationEntryRequest = new RevocationRegistryEntryRequest({ - revocationRegistryDefinitionId: legacyRevocationRegistryId, - revocationRegistryDefinitionType: 'CL_ACCUM', - revocationRegistryEntry: { - ver: '1.0', - value: { - accum: '1', - }, + options: { + endorserMode: 'internal', + endorserDid: endorserDid, }, - submitterDid: legacyIssuerId, }) - // After this call we can query the revocation registry entries (using timestamp now) + if (!didIndyRevocationRegistryDefinitionId) { + throw Error('revocation registry definition was not created correctly') + } - const revocationEntryWriteRequest = await pool.prepareWriteRequest( - endorser.context, - revocationEntryRequest, - signingKey + const { credentialDefinitionTag, revocationRegistryTag } = parseIndyRevocationRegistryId( + didIndyRevocationRegistryDefinitionId + ) + const legacyRevocationRegistryDefinitionId = getUnqualifiedRevocationRegistryDefinitionId( + legacyIssuerId, + schemaSeqNo, + credentialDefinitionTag, + revocationRegistryTag ) - const entryResponse = await pool.submitRequest(revocationEntryWriteRequest) + + // Wait some time before resolving revocation registry definition object + await new Promise((res) => setTimeout(res, 1000)) const legacyRevocationRegistryDefinition = await indyVdrAnonCredsRegistry.getRevocationRegistryDefinition( endorser.context, - legacyRevocationRegistryId + legacyRevocationRegistryDefinitionId ) + expect(legacyRevocationRegistryDefinition).toMatchObject({ - revocationRegistryDefinitionId: legacyRevocationRegistryId, + revocationRegistryDefinitionId: legacyRevocationRegistryDefinitionId, revocationRegistryDefinition: { issuerId: legacyIssuerId, revocDefType: 'CL_ACCUM', @@ -648,7 +636,7 @@ describe('IndyVdrAnonCredsRegistry', () => { }, }, }, - tag: 'tag', + tag: 'REV_TAG', credDefId: legacyCredentialDefinitionId, }, revocationRegistryDefinitionMetadata: { @@ -660,10 +648,11 @@ describe('IndyVdrAnonCredsRegistry', () => { const didIndyRevocationRegistryDefinition = await indyVdrAnonCredsRegistry.getRevocationRegistryDefinition( endorser.context, - didIndyRevocationRegistryId + didIndyRevocationRegistryDefinitionId ) + expect(didIndyRevocationRegistryDefinition).toMatchObject({ - revocationRegistryDefinitionId: didIndyRevocationRegistryId, + revocationRegistryDefinitionId: didIndyRevocationRegistryDefinitionId, revocationRegistryDefinition: { issuerId: didIndyIssuerId, revocDefType: 'CL_ACCUM', @@ -678,7 +667,7 @@ describe('IndyVdrAnonCredsRegistry', () => { }, }, }, - tag: 'tag', + tag: 'REV_TAG', credDefId: didIndyCredentialDefinitionId, }, revocationRegistryDefinitionMetadata: { @@ -688,10 +677,30 @@ describe('IndyVdrAnonCredsRegistry', () => { resolutionMetadata: {}, }) + const registerStatusListResult = await indyVdrAnonCredsRegistry.registerRevocationStatusList(endorser.context, { + revocationStatusList: { + issuerId: didIndyIssuerId, + revRegDefId: didIndyRevocationRegistryDefinitionId, + revocationList: new Array(100).fill(0), + currentAccumulator: '1', + }, + options: { + endorserMode: 'internal', + endorserDid: endorserDid, + }, + }) + + if (registerStatusListResult.revocationStatusListState.state !== 'finished') { + throw new Error(`Unable to register status list: ${JSON.stringify(registerStatusListResult)}`) + } + + // Wait some time before resolving revocation status list object + await new Promise((res) => setTimeout(res, 1000)) + const legacyRevocationStatusList = await indyVdrAnonCredsRegistry.getRevocationStatusList( endorser.context, - legacyRevocationRegistryId, - entryResponse.result.txnMetadata.txnTime + legacyRevocationRegistryDefinitionId, + registerStatusListResult.revocationStatusListState.revocationStatusList.timestamp ) expect(legacyRevocationStatusList).toMatchObject({ @@ -699,13 +708,13 @@ describe('IndyVdrAnonCredsRegistry', () => { revocationStatusList: { issuerId: legacyIssuerId, currentAccumulator: '1', - revRegDefId: legacyRevocationRegistryId, + revRegDefId: legacyRevocationRegistryDefinitionId, revocationList: [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ], - timestamp: entryResponse.result.txnMetadata.txnTime, + timestamp: registerStatusListResult.revocationStatusListState.revocationStatusList.timestamp, }, revocationStatusListMetadata: { didIndyNamespace: 'pool:localtest', @@ -714,8 +723,8 @@ describe('IndyVdrAnonCredsRegistry', () => { const didIndyRevocationStatusList = await indyVdrAnonCredsRegistry.getRevocationStatusList( endorser.context, - didIndyRevocationRegistryId, - entryResponse.result.txnMetadata.txnTime + didIndyRevocationRegistryDefinitionId, + registerStatusListResult.revocationStatusListState.revocationStatusList.timestamp ) expect(didIndyRevocationStatusList).toMatchObject({ @@ -723,13 +732,13 @@ describe('IndyVdrAnonCredsRegistry', () => { revocationStatusList: { issuerId: didIndyIssuerId, currentAccumulator: '1', - revRegDefId: didIndyRevocationRegistryId, + revRegDefId: didIndyRevocationRegistryDefinitionId, revocationList: [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ], - timestamp: entryResponse.result.txnMetadata.txnTime, + timestamp: registerStatusListResult.revocationStatusListState.revocationStatusList.timestamp, }, revocationStatusListMetadata: { didIndyNamespace: 'pool:localtest', @@ -737,7 +746,10 @@ describe('IndyVdrAnonCredsRegistry', () => { }) }) - test('register and resolve a schema and credential definition (external)', async () => { + test('register and resolve a schema, credential definition, revocation registry and status list (external)', async () => { + // ---- + // CREATE DID + // ---- const didCreateTxResult = (await agent.dids.create({ method: 'indy', options: { @@ -773,8 +785,10 @@ describe('IndyVdrAnonCredsRegistry', () => { const legacyIssuerId = namespaceIdentifier const didIndyIssuerId = agentDid - const signingKey = await verificationKeyForIndyDid(agent.context, didIndyIssuerId) + // ---- + // CREATE SCHEMA + // ---- const legacySchemaId = `${namespaceIdentifier}:2:test:${dynamicVersion}` const didIndySchemaId = `did:indy:pool:localtest:${namespaceIdentifier}/anoncreds/v0/SCHEMA/test/${dynamicVersion}` @@ -859,6 +873,9 @@ describe('IndyVdrAnonCredsRegistry', () => { }, }) + // ---- + // CREATE CREDENTIAL DEFINITION + // ---- const legacyCredentialDefinitionId = `${namespaceIdentifier}:3:CL:${submitSchemaTxResult.schemaMetadata.indyLedgerSeqNo}:TAG` const didIndyCredentialDefinitionId = `did:indy:pool:localtest:${namespaceIdentifier}/anoncreds/v0/CLAIM_DEF/${submitSchemaTxResult.schemaMetadata.indyLedgerSeqNo}/TAG` @@ -879,13 +896,13 @@ describe('IndyVdrAnonCredsRegistry', () => { const { credentialDefinitionState } = createCredDefTxResult if (credentialDefinitionState.state !== 'action' || credentialDefinitionState.action !== 'endorseIndyTransaction') - throw Error('unexpected schema state') + throw Error('unexpected credential definition state') const endorsedCredDefTx = await endorser.modules.indyVdr.endorseTransaction( credentialDefinitionState.credentialDefinitionRequest, endorserDid ) - const SubmitCredDefTxResult = await indyVdrAnonCredsRegistry.registerCredentialDefinition(agent.context, { + const submitCredDefTxResult = await indyVdrAnonCredsRegistry.registerCredentialDefinition(agent.context, { credentialDefinition: credentialDefinitionState.credentialDefinition, options: { endorserMode: 'external', @@ -893,7 +910,7 @@ describe('IndyVdrAnonCredsRegistry', () => { }, }) - expect(SubmitCredDefTxResult).toMatchObject({ + expect(submitCredDefTxResult).toMatchObject({ credentialDefinitionMetadata: {}, credentialDefinitionState: { credentialDefinition: { @@ -953,92 +970,76 @@ describe('IndyVdrAnonCredsRegistry', () => { resolutionMetadata: {}, }) - // We don't support creating a revocation registry using AFJ yet, so we directly use indy-vdr to create the revocation registry - const legacyRevocationRegistryId = `${namespaceIdentifier}:4:${namespaceIdentifier}:3:CL:${submitSchemaTxResult.schemaMetadata.indyLedgerSeqNo}:TAG:CL_ACCUM:tag` - const didIndyRevocationRegistryId = `did:indy:pool:localtest:${namespaceIdentifier}/anoncreds/v0/REV_REG_DEF/${submitSchemaTxResult.schemaMetadata.indyLedgerSeqNo}/TAG/tag` - const revocationRegistryRequest = new RevocationRegistryDefinitionRequest({ - submitterDid: namespaceIdentifier, - revocationRegistryDefinitionV1: { - credDefId: legacyCredentialDefinitionId, - id: legacyRevocationRegistryId, + // ---- + // CREATE REVOCATION REGISTRY + // ---- + const legacyRevocationRegistryDefinitionId = `${namespaceIdentifier}:4:${namespaceIdentifier}:3:CL:${submitSchemaTxResult.schemaMetadata.indyLedgerSeqNo}:TAG:CL_ACCUM:REV_TAG` + const didIndyRevocationRegistryDefinitionId = `did:indy:pool:localtest:${namespaceIdentifier}/anoncreds/v0/REV_REG_DEF/${submitSchemaTxResult.schemaMetadata.indyLedgerSeqNo}/TAG/REV_TAG` + + const createRevRegDefTxResult = await indyVdrAnonCredsRegistry.registerRevocationRegistryDefinition(agent.context, { + revocationRegistryDefinition: { + tag: 'REV_TAG', revocDefType: 'CL_ACCUM', - tag: 'tag', - value: { - issuanceType: 'ISSUANCE_BY_DEFAULT', - maxCredNum: 100, - publicKeys: { - accumKey: { - z: '1 1812B206EB395D3AEBD4BBF53EBB0FFC3371D8BD6175316AB32C1C5F65452051 1 22A079D49C5351EFDC1410C81A1F6D8B2E3B79CFF20A30690C118FE2050F72CB 1 0FFC28B923A4654E261DB4CB5B9BABEFCB4DB189B20F52412B0CC9CCCBB8A3B2 1 1EE967C43EF1A3F487061D21B07076A26C126AAF7712E7B5CF5A53688DDD5CC0 1 009ED4D65879CA81DA8227D34CEA3B759B4627E1E2FFB273E9645CD4F3B10F19 1 1CF070212E1E213AEB472F56EDFC9D48009796C77B2D8CC16F2836E37B8715C2 1 04954F0B7B468781BAAE3291DD0E6FFA7F1AF66CAA4094D37B24363CC34606FB 1 115367CB755E9DB18781B3825CB1AEE2C334558B2C038E13DF57BB57CE1CF847 1 110D37EC05862EE2757A7DF39E814876FC97376FF8105D2D29619CB575537BDE 1 13C559A9563FCE083B3B39AE7E8FCA4099BEF3A4C8C6672E543D521F9DA88F96 1 137D87CC22ACC1B6B8C20EABE59F6ED456A58FE4CBEEFDFC4FA9B87E3EF32D17 1 00A2A9711737AAF0404F35AE502887AC6172B2B57D236BD4A40B45F659BFC696', - }, - }, - tailsHash: 'HLKresYcDSZYSKogq8wive4zyXNY84669MygftLFBG1i', - tailsLocation: - '/var/folders/l3/xy8jzyvj4p5_d9g1123rt4bw0000gn/T/HLKresYcDSZYSKogq8wive4zyXNY84669MygftLFBG1i', - }, - ver: '1.0', + credDefId: didIndyCredentialDefinitionId, + issuerId: didIndyIssuerId, + value: revocationRegistryDefinitionValue, + }, + options: { + endorserMode: 'external', + endorserDid, }, }) - // After this call, the revocation registry should now be resolvable - const writeRequest = await pool.prepareWriteRequest( - agent.context, - revocationRegistryRequest, - signingKey, + const { revocationRegistryDefinitionState } = createRevRegDefTxResult + + if ( + revocationRegistryDefinitionState.state !== 'action' || + revocationRegistryDefinitionState.action !== 'endorseIndyTransaction' + ) { + throw Error('unexpected revocation registry definition state') + } + + const endorsedRevRegDefTx = await endorser.modules.indyVdr.endorseTransaction( + revocationRegistryDefinitionState.revocationRegistryDefinitionRequest, endorserDid ) - const endorsedRequest = await endorser.modules.indyVdr.endorseTransaction(writeRequest.body, endorserDid) - await pool.submitRequest(new CustomRequest({ customRequest: endorsedRequest })) - - // Also create a revocation registry entry - const revocationEntryRequest = new RevocationRegistryEntryRequest({ - revocationRegistryDefinitionId: legacyRevocationRegistryId, - revocationRegistryDefinitionType: 'CL_ACCUM', - revocationRegistryEntry: { - ver: '1.0', - value: { - accum: '1', - }, + const submitRevRegDefTxResult = await indyVdrAnonCredsRegistry.registerRevocationRegistryDefinition(agent.context, { + revocationRegistryDefinition: revocationRegistryDefinitionState.revocationRegistryDefinition, + options: { + endorserMode: 'external', + endorsedTransaction: endorsedRevRegDefTx, }, - submitterDid: legacyIssuerId, }) - // After this call we can query the revocation registry entries (using timestamp now) + expect(submitRevRegDefTxResult).toMatchObject({ + revocationRegistryDefinitionMetadata: {}, + revocationRegistryDefinitionState: { + revocationRegistryDefinition: { + credDefId: didIndyCredentialDefinitionId, + issuerId: didIndyIssuerId, + tag: 'REV_TAG', + value: revocationRegistryDefinitionValue, + }, + revocationRegistryDefinitionId: didIndyRevocationRegistryDefinitionId, + state: 'finished', + }, + registrationMetadata: {}, + }) - const revocationEntryWriteRequest = await pool.prepareWriteRequest( - agent.context, - revocationEntryRequest, - signingKey, - endorserDid - ) - const endorsedRevEntryWriteRequest = await endorser.modules.indyVdr.endorseTransaction( - revocationEntryWriteRequest.body, - endorserDid - ) - const entryResponse = (await pool.submitRequest( - new CustomRequest({ customRequest: endorsedRevEntryWriteRequest }) - )) as RevocationRegistryEntryResponse + await new Promise((res) => setTimeout(res, 1000)) const legacyRevocationRegistryDefinition = await indyVdrAnonCredsRegistry.getRevocationRegistryDefinition( agent.context, - legacyRevocationRegistryId + legacyRevocationRegistryDefinitionId ) + expect(legacyRevocationRegistryDefinition).toMatchObject({ - revocationRegistryDefinitionId: legacyRevocationRegistryId, + revocationRegistryDefinitionId: legacyRevocationRegistryDefinitionId, revocationRegistryDefinition: { issuerId: legacyIssuerId, revocDefType: 'CL_ACCUM', - value: { - maxCredNum: 100, - tailsHash: 'HLKresYcDSZYSKogq8wive4zyXNY84669MygftLFBG1i', - tailsLocation: - '/var/folders/l3/xy8jzyvj4p5_d9g1123rt4bw0000gn/T/HLKresYcDSZYSKogq8wive4zyXNY84669MygftLFBG1i', - publicKeys: { - accumKey: { - z: '1 1812B206EB395D3AEBD4BBF53EBB0FFC3371D8BD6175316AB32C1C5F65452051 1 22A079D49C5351EFDC1410C81A1F6D8B2E3B79CFF20A30690C118FE2050F72CB 1 0FFC28B923A4654E261DB4CB5B9BABEFCB4DB189B20F52412B0CC9CCCBB8A3B2 1 1EE967C43EF1A3F487061D21B07076A26C126AAF7712E7B5CF5A53688DDD5CC0 1 009ED4D65879CA81DA8227D34CEA3B759B4627E1E2FFB273E9645CD4F3B10F19 1 1CF070212E1E213AEB472F56EDFC9D48009796C77B2D8CC16F2836E37B8715C2 1 04954F0B7B468781BAAE3291DD0E6FFA7F1AF66CAA4094D37B24363CC34606FB 1 115367CB755E9DB18781B3825CB1AEE2C334558B2C038E13DF57BB57CE1CF847 1 110D37EC05862EE2757A7DF39E814876FC97376FF8105D2D29619CB575537BDE 1 13C559A9563FCE083B3B39AE7E8FCA4099BEF3A4C8C6672E543D521F9DA88F96 1 137D87CC22ACC1B6B8C20EABE59F6ED456A58FE4CBEEFDFC4FA9B87E3EF32D17 1 00A2A9711737AAF0404F35AE502887AC6172B2B57D236BD4A40B45F659BFC696', - }, - }, - }, - tag: 'tag', + value: revocationRegistryDefinitionValue, + tag: 'REV_TAG', credDefId: legacyCredentialDefinitionId, }, revocationRegistryDefinitionMetadata: { @@ -1050,25 +1051,16 @@ describe('IndyVdrAnonCredsRegistry', () => { const didIndyRevocationRegistryDefinition = await indyVdrAnonCredsRegistry.getRevocationRegistryDefinition( agent.context, - didIndyRevocationRegistryId + didIndyRevocationRegistryDefinitionId ) + expect(didIndyRevocationRegistryDefinition).toMatchObject({ - revocationRegistryDefinitionId: didIndyRevocationRegistryId, + revocationRegistryDefinitionId: didIndyRevocationRegistryDefinitionId, revocationRegistryDefinition: { issuerId: didIndyIssuerId, revocDefType: 'CL_ACCUM', - value: { - maxCredNum: 100, - tailsHash: 'HLKresYcDSZYSKogq8wive4zyXNY84669MygftLFBG1i', - tailsLocation: - '/var/folders/l3/xy8jzyvj4p5_d9g1123rt4bw0000gn/T/HLKresYcDSZYSKogq8wive4zyXNY84669MygftLFBG1i', - publicKeys: { - accumKey: { - z: '1 1812B206EB395D3AEBD4BBF53EBB0FFC3371D8BD6175316AB32C1C5F65452051 1 22A079D49C5351EFDC1410C81A1F6D8B2E3B79CFF20A30690C118FE2050F72CB 1 0FFC28B923A4654E261DB4CB5B9BABEFCB4DB189B20F52412B0CC9CCCBB8A3B2 1 1EE967C43EF1A3F487061D21B07076A26C126AAF7712E7B5CF5A53688DDD5CC0 1 009ED4D65879CA81DA8227D34CEA3B759B4627E1E2FFB273E9645CD4F3B10F19 1 1CF070212E1E213AEB472F56EDFC9D48009796C77B2D8CC16F2836E37B8715C2 1 04954F0B7B468781BAAE3291DD0E6FFA7F1AF66CAA4094D37B24363CC34606FB 1 115367CB755E9DB18781B3825CB1AEE2C334558B2C038E13DF57BB57CE1CF847 1 110D37EC05862EE2757A7DF39E814876FC97376FF8105D2D29619CB575537BDE 1 13C559A9563FCE083B3B39AE7E8FCA4099BEF3A4C8C6672E543D521F9DA88F96 1 137D87CC22ACC1B6B8C20EABE59F6ED456A58FE4CBEEFDFC4FA9B87E3EF32D17 1 00A2A9711737AAF0404F35AE502887AC6172B2B57D236BD4A40B45F659BFC696', - }, - }, - }, - tag: 'tag', + value: revocationRegistryDefinitionValue, + tag: 'REV_TAG', credDefId: didIndyCredentialDefinitionId, }, revocationRegistryDefinitionMetadata: { @@ -1078,10 +1070,59 @@ describe('IndyVdrAnonCredsRegistry', () => { resolutionMetadata: {}, }) + // ---- + // CREATE REVOCATION STATUS LIST + // ---- + const revocationStatusListValue = { + issuerId: didIndyIssuerId, + revRegDefId: didIndyRevocationRegistryDefinitionId, + revocationList: [1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 0], + currentAccumulator: '1', + } + + const createRevStatusListTxResult = await indyVdrAnonCredsRegistry.registerRevocationStatusList(agent.context, { + options: { + endorserMode: 'external', + endorserDid, + }, + revocationStatusList: revocationStatusListValue, + }) + + const { revocationStatusListState } = createRevStatusListTxResult + + if (revocationStatusListState.state !== 'action' || revocationStatusListState.action !== 'endorseIndyTransaction') { + throw Error('unexpected revocation status list state') + } + + const endorsedRevStatusListTx = await endorser.modules.indyVdr.endorseTransaction( + revocationStatusListState.revocationStatusListRequest, + endorserDid + ) + + const submitRevStatusListTxResult = await indyVdrAnonCredsRegistry.registerRevocationStatusList(agent.context, { + revocationStatusList: revocationStatusListState.revocationStatusList, + options: { + endorserMode: 'external', + endorsedTransaction: endorsedRevStatusListTx, + }, + }) + + expect(submitRevStatusListTxResult).toMatchObject({ + revocationStatusListMetadata: {}, + revocationStatusListState: { + revocationStatusList: { ...revocationStatusListValue, timestamp: expect.any(Number) }, + state: 'finished', + }, + registrationMetadata: {}, + }) + + // Wait some time before resolving status list + await new Promise((res) => setTimeout(res, 1000)) + const legacyRevocationStatusList = await indyVdrAnonCredsRegistry.getRevocationStatusList( agent.context, - legacyRevocationRegistryId, - entryResponse.result.txnMetadata.txnTime + legacyRevocationRegistryDefinitionId, + submitRevStatusListTxResult.revocationStatusListState.revocationStatusList?.timestamp as number ) expect(legacyRevocationStatusList).toMatchObject({ @@ -1089,13 +1130,9 @@ describe('IndyVdrAnonCredsRegistry', () => { revocationStatusList: { issuerId: legacyIssuerId, currentAccumulator: '1', - revRegDefId: legacyRevocationRegistryId, - revocationList: [ - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - ], - timestamp: entryResponse.result.txnMetadata.txnTime, + revRegDefId: legacyRevocationRegistryDefinitionId, + revocationList: revocationStatusListValue.revocationList, + timestamp: submitRevStatusListTxResult.revocationStatusListState.revocationStatusList?.timestamp, }, revocationStatusListMetadata: { didIndyNamespace: 'pool:localtest', @@ -1104,8 +1141,8 @@ describe('IndyVdrAnonCredsRegistry', () => { const didIndyRevocationStatusList = await indyVdrAnonCredsRegistry.getRevocationStatusList( agent.context, - didIndyRevocationRegistryId, - entryResponse.result.txnMetadata.txnTime + didIndyRevocationRegistryDefinitionId, + submitRevStatusListTxResult.revocationStatusListState.revocationStatusList?.timestamp as number ) expect(didIndyRevocationStatusList).toMatchObject({ @@ -1113,13 +1150,9 @@ describe('IndyVdrAnonCredsRegistry', () => { revocationStatusList: { issuerId: didIndyIssuerId, currentAccumulator: '1', - revRegDefId: didIndyRevocationRegistryId, - revocationList: [ - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - ], - timestamp: entryResponse.result.txnMetadata.txnTime, + revRegDefId: didIndyRevocationRegistryDefinitionId, + revocationList: revocationStatusListValue.revocationList, + timestamp: submitRevStatusListTxResult.revocationStatusListState.revocationStatusList?.timestamp, }, revocationStatusListMetadata: { didIndyNamespace: 'pool:localtest', diff --git a/yarn.lock b/yarn.lock index e6a6a40e34..2c4567fc9f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1249,22 +1249,22 @@ dependencies: buffer "^6.0.3" -"@hyperledger/indy-vdr-nodejs@^0.2.0-dev.5": - version "0.2.0-dev.5" - resolved "https://registry.yarnpkg.com/@hyperledger/indy-vdr-nodejs/-/indy-vdr-nodejs-0.2.0-dev.5.tgz#ea40095116e0abdd4c28214122f01669367f1db5" - integrity sha512-TeeapuZBRS7+Tbn8QQ3RoMpGaI/QHjeU7TlDU5UHNoEFuZcBdDcdH6V9QAoJ1RNxc6k7tiUYKFir8LMQ+hXrXQ== +"@hyperledger/indy-vdr-nodejs@^0.2.0-dev.6": + version "0.2.0-dev.6" + resolved "https://registry.yarnpkg.com/@hyperledger/indy-vdr-nodejs/-/indy-vdr-nodejs-0.2.0-dev.6.tgz#c21916600e17cf6ee46fc78a054cb904f9156594" + integrity sha512-yOmfOqJJJapJRWdKSJQG7q/frKGUrntoae4fiYnwdQEWy4rdRiyZPo0ht9R6uuZ/AQwxtNMMRylvQZBfHA+vKA== dependencies: "@2060.io/ffi-napi" "4.0.8" "@2060.io/ref-napi" "3.0.6" - "@hyperledger/indy-vdr-shared" "0.2.0-dev.5" + "@hyperledger/indy-vdr-shared" "0.2.0-dev.6" "@mapbox/node-pre-gyp" "^1.0.10" ref-array-di "^1.2.2" ref-struct-di "^1.1.1" -"@hyperledger/indy-vdr-shared@0.2.0-dev.5", "@hyperledger/indy-vdr-shared@^0.2.0-dev.5": - version "0.2.0-dev.5" - resolved "https://registry.yarnpkg.com/@hyperledger/indy-vdr-shared/-/indy-vdr-shared-0.2.0-dev.5.tgz#ab33feda90dcbf457f3ff59da266ab32968ab48c" - integrity sha512-oPvNG5ePvtuz3H+KxWdCdxWXeo3Jxs8AFAAuG8qLPSlicEHpWchbT1amun8ccp1lk7pIBx9J0aLf08yrM5H8iw== +"@hyperledger/indy-vdr-shared@0.2.0-dev.6", "@hyperledger/indy-vdr-shared@^0.2.0-dev.6": + version "0.2.0-dev.6" + resolved "https://registry.yarnpkg.com/@hyperledger/indy-vdr-shared/-/indy-vdr-shared-0.2.0-dev.6.tgz#4954ee06fa8a2e4545b35cd525b7b86e0f10b6fe" + integrity sha512-pNLq0zkqv5rFCpU9tzyJ5DPvED5YE+UFP8iKwVD7fe+mAD6/VpweOunYNKgIBT4K1DYI21q7bs3SzxQZ0hLlKw== "@isaacs/string-locale-compare@^1.1.0": version "1.1.0"