Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

fix: various anoncreds revocation fixes #1416

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 11 additions & 8 deletions packages/anoncreds-rs/src/services/AnonCredsRsHolderService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,20 +105,23 @@ export class AnonCredsRsHolderService implements AnonCredsHolderService {
throw new AnonCredsRsError(`Revocation Registry ${revocationRegistryDefinitionId} not found`)
}

const { definition, tailsFilePath } = options.revocationRegistries[revocationRegistryDefinitionId]
const { definition, revocationStatusLists, tailsFilePath } =
options.revocationRegistries[revocationRegistryDefinitionId]

// Extract revocation status list for the given timestamp
const revocationStatusList = revocationStatusLists[timestamp]
if (!revocationStatusList) {
throw new AriesFrameworkError(
`Revocation status list for revocation registry ${revocationRegistryDefinitionId} and timestamp ${timestamp} not found in revocation status lists. All revocation status lists must be present.`
)
}

revocationRegistryDefinition = RevocationRegistryDefinition.fromJson(definition as unknown as JsonObject)
revocationState = CredentialRevocationState.create({
revocationRegistryIndex: Number(revocationRegistryIndex),
revocationRegistryDefinition,
tailsPath: tailsFilePath,
revocationStatusList: RevocationStatusList.create({
issuerId: definition.issuerId,
issuanceByDefault: true,
revocationRegistryDefinition,
revocationRegistryDefinitionId,
timestamp,
}),
revocationStatusList: RevocationStatusList.fromJson(revocationStatusList as unknown as JsonObject),
})
}
return {
Expand Down
187 changes: 10 additions & 177 deletions packages/anoncreds/src/formats/AnonCredsProofFormatService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,7 @@ import type {
AnonCredsSelectedCredentials,
AnonCredsProofRequest,
} from '../models'
import type {
AnonCredsHolderService,
AnonCredsVerifierService,
CreateProofOptions,
GetCredentialsForProofRequestReturn,
VerifyProofOptions,
} from '../services'
import type { AnonCredsHolderService, AnonCredsVerifierService, GetCredentialsForProofRequestReturn } from '../services'
import type {
ProofFormatService,
AgentContext,
Expand Down Expand Up @@ -57,11 +51,12 @@ import {
sortRequestedCredentialsMatches,
createRequestFromPreview,
areAnonCredsProofRequestsEqual,
assertRevocationInterval,
downloadTailsFile,
assertBestPracticeRevocationInterval,
checkValidCredentialValueEncoding,
encodeCredentialValue,
assertNoDuplicateGroupsNamesInProofRequest,
getRevocationRegistriesForRequest,
getRevocationRegistriesForProof,
} from '../utils'

const ANONCREDS_PRESENTATION_PROPOSAL = 'anoncreds/[email protected]'
Expand Down Expand Up @@ -240,7 +235,7 @@ export class AnonCredsProofFormatService implements ProofFormatService<AnonCreds
new Set(proofJson.identifiers.map((i) => i.cred_def_id))
)

const revocationRegistries = await this.getRevocationRegistriesForProof(agentContext, proofJson)
const revocationRegistries = await getRevocationRegistriesForProof(agentContext, proofJson)

return await verifierService.verifyProof(agentContext, {
proofRequest: proofRequestJson,
Expand Down Expand Up @@ -538,7 +533,7 @@ export class AnonCredsProofFormatService implements ProofFormatService<AnonCreds
)

// Make sure the revocation interval follows best practices from Aries RFC 0441
assertRevocationInterval(requestNonRevoked)
assertBestPracticeRevocationInterval(requestNonRevoked)

const registryService = agentContext.dependencyManager.resolve(AnonCredsRegistryService)
const registry = registryService.getRegistryForIdentifier(agentContext, revocationRegistryId)
Expand Down Expand Up @@ -596,185 +591,23 @@ export class AnonCredsProofFormatService implements ProofFormatService<AnonCreds
new Set(credentialObjects.map((c) => c.credentialDefinitionId))
)

const revocationRegistries = await this.getRevocationRegistriesForRequest(
// selectedCredentials are overridden with specified timestamps of the revocation status list that
// should be used for the selected credentials.
const { revocationRegistries, updatedSelectedCredentials } = await getRevocationRegistriesForRequest(
agentContext,
proofRequest,
selectedCredentials
)

return await holderService.createProof(agentContext, {
proofRequest,
selectedCredentials,
selectedCredentials: updatedSelectedCredentials,
schemas,
credentialDefinitions,
revocationRegistries,
})
}

private async getRevocationRegistriesForRequest(
agentContext: AgentContext,
proofRequest: AnonCredsProofRequest,
selectedCredentials: AnonCredsSelectedCredentials
) {
const revocationRegistries: CreateProofOptions['revocationRegistries'] = {}

try {
agentContext.config.logger.debug(`Retrieving revocation registries for proof request`, {
proofRequest,
selectedCredentials,
})

const referentCredentials = []

// Retrieve information for referents and push to single array
for (const [referent, selectedCredential] of Object.entries(selectedCredentials.attributes)) {
referentCredentials.push({
referent,
credentialInfo: selectedCredential.credentialInfo,
nonRevoked: proofRequest.requested_attributes[referent].non_revoked ?? proofRequest.non_revoked,
})
}
for (const [referent, selectedCredential] of Object.entries(selectedCredentials.predicates)) {
referentCredentials.push({
referent,
credentialInfo: selectedCredential.credentialInfo,
nonRevoked: proofRequest.requested_predicates[referent].non_revoked ?? proofRequest.non_revoked,
})
}

for (const { referent, credentialInfo, nonRevoked } of referentCredentials) {
if (!credentialInfo) {
throw new AriesFrameworkError(
`Credential for referent '${referent} does not have credential info for revocation state creation`
)
}

// Prefer referent-specific revocation interval over global revocation interval
const credentialRevocationId = credentialInfo.credentialRevocationId
const revocationRegistryId = credentialInfo.revocationRegistryId

// If revocation interval is present and the credential is revocable then create revocation state
if (nonRevoked && credentialRevocationId && revocationRegistryId) {
agentContext.config.logger.trace(
`Presentation is requesting proof of non revocation for referent '${referent}', creating revocation state for credential`,
{
nonRevoked,
credentialRevocationId,
revocationRegistryId,
}
)

// Make sure the revocation interval follows best practices from Aries RFC 0441
assertRevocationInterval(nonRevoked)

const registry = agentContext.dependencyManager
.resolve(AnonCredsRegistryService)
.getRegistryForIdentifier(agentContext, revocationRegistryId)

// Fetch revocation registry definition if not in revocation registries list yet
if (!revocationRegistries[revocationRegistryId]) {
const { revocationRegistryDefinition, resolutionMetadata } = await registry.getRevocationRegistryDefinition(
agentContext,
revocationRegistryId
)
if (!revocationRegistryDefinition) {
throw new AriesFrameworkError(
`Could not retrieve revocation registry definition for revocation registry ${revocationRegistryId}: ${resolutionMetadata.message}`
)
}

const { tailsLocation, tailsHash } = revocationRegistryDefinition.value
const { tailsFilePath } = await downloadTailsFile(agentContext, tailsLocation, tailsHash)

revocationRegistries[revocationRegistryId] = {
definition: revocationRegistryDefinition,
tailsFilePath,
revocationStatusLists: {},
}
}

// TODO: can we check if the revocation status list is already fetched? We don't know which timestamp the query will return. This
// should probably be solved using caching
// Fetch the revocation status list
const { revocationStatusList, resolutionMetadata: statusListResolutionMetadata } =
await registry.getRevocationStatusList(agentContext, revocationRegistryId, nonRevoked.to ?? Date.now())
if (!revocationStatusList) {
throw new AriesFrameworkError(
`Could not retrieve revocation status list for revocation registry ${revocationRegistryId}: ${statusListResolutionMetadata.message}`
)
}

revocationRegistries[revocationRegistryId].revocationStatusLists[revocationStatusList.timestamp] =
revocationStatusList
}
}

agentContext.config.logger.debug(`Retrieved revocation registries for proof request`, {
revocationRegistries,
})

return revocationRegistries
} catch (error) {
agentContext.config.logger.error(`Error retrieving revocation registry for proof request`, {
error,
proofRequest,
selectedCredentials,
})

throw error
}
}

private async getRevocationRegistriesForProof(agentContext: AgentContext, proof: AnonCredsProof) {
const revocationRegistries: VerifyProofOptions['revocationRegistries'] = {}

for (const identifier of proof.identifiers) {
const revocationRegistryId = identifier.rev_reg_id
const timestamp = identifier.timestamp

// Skip if no revocation registry id is present
if (!revocationRegistryId || !timestamp) continue

const registry = agentContext.dependencyManager
.resolve(AnonCredsRegistryService)
.getRegistryForIdentifier(agentContext, revocationRegistryId)

// Fetch revocation registry definition if not already fetched
if (!revocationRegistries[revocationRegistryId]) {
const { revocationRegistryDefinition, resolutionMetadata } = await registry.getRevocationRegistryDefinition(
agentContext,
revocationRegistryId
)
if (!revocationRegistryDefinition) {
throw new AriesFrameworkError(
`Could not retrieve revocation registry definition for revocation registry ${revocationRegistryId}: ${resolutionMetadata.message}`
)
}

revocationRegistries[revocationRegistryId] = {
definition: revocationRegistryDefinition,
revocationStatusLists: {},
}
}

// Fetch revocation status list by timestamp if not already fetched
if (!revocationRegistries[revocationRegistryId].revocationStatusLists[timestamp]) {
const { revocationStatusList, resolutionMetadata: statusListResolutionMetadata } =
await registry.getRevocationStatusList(agentContext, revocationRegistryId, timestamp)

if (!revocationStatusList) {
throw new AriesFrameworkError(
`Could not retrieve revocation status list for revocation registry ${revocationRegistryId}: ${statusListResolutionMetadata.message}`
)
}

revocationRegistries[revocationRegistryId].revocationStatusLists[timestamp] = revocationStatusList
}
}

return revocationRegistries
}

/**
* Returns an object of type {@link Attachment} for use in credential exchange messages.
* It looks up the correct format identifier and encodes the data as a base64 attachment.
Expand Down
Loading