# Updating ACA-Py to use new AnonCreds implementation
## Authors
- Adam Burdett <adam@indicio.tech>
- Char Howland <char@indicio.tech>
- Daniel Bluhm <daniel@indicio.tech>
## Introduction
The Government of British Columbia would like to update Aries Cloud Agent Python (ACA-Py) to use the new anoncreds-rs implementation of AnonCreds from the Hyperledger AnonCreds project. In changing to the new AnonCreds implementation, existing ACA-Py deployments (controllers) must continue to work unchanged, and support should be added for using AnonCreds stored in verifiable data registries other than Hyperledger Indy.
This document outlines the design for the replacement of the current AnonCreds implementation in ACA-Py with the Hyperledger AnonCreds implementation that is backwards compatible and enables support for other verifiable data registries.
## Context
The Government of British Columbia issues and verifies privacy-preserving AnonCreds in a number of verifiable credentials use cases, and provides the BC Wallet to hold AnonCreds credentials. BC Gov also supports the recent transition of AnonCreds out of the Hyperledger Indy project and into its own Hyperledger AnonCreds project. With the new implementation of AnonCreds (anoncreds-rs, https://github.com/hyperledger/anoncreds-rs) nearing completion, we would like to transition ACA-Py to use the new implementation as soon as possible, in a way that is as easy as possible for existing deployments to use. In doing that, we would like to enable the use of ACA-Py and AnonCreds with Hyperledger Indy and other verifiable data registries. Notably, the Hyperledger AnonCreds implementation in ACA-Py be made pluggable, such that support for other AnonCreds object identifiers, and hence other verifiable data registries, can be easily added. The format of identifiers we would like to support as a result of this work must include:
- Legacy Indy AnonCreds object identifiers used in our existing applications,
- did:indy AnonCreds object identifiers,
- At least one other kind of AnonCreds object identifier, likely did:web or for another public ledger such as Cheqd or Cardano.
The idea of supporting the third type of AnonCreds objects identifier is to ensure there is a clear interface for adding implementations of other types of AnonCreds object identifiers in the future. If did:web is chosen as the third method, the registration of AnonCreds objects of type did:web can be a “nice to have”, and the resolution component should be a simple addition to ACA-Py.
Some basic information about replacing the current AnonCreds implementation with the one from Hyperledger AnonCreds can be found in this ACA-Py issue 2044 (https://github.com/hyperledger/aries-cloudagent-python/issues/2044). Similar work is happening in Aries Framework JavaScript (https://github.com/hyperledger/aries-framework-javascript), and that work might serve as a model for the ACA-Py work.
### Deliverables
- Phase 1: An accepted design for the replacement of the current AnonCreds implementation in ACA-Py with the Hyperledger AnonCreds implementation that is backwards compatible and enables support for other verifiable data registries. This phase is a gate for the rest of the project. Based on the documented approach either side can decide to terminate the remainder of the project.
- Phase 2: Implementation of the agreed upon design in the ACA-Py repository, including source code, unit tests, and documentation. The existing ACA-Py unit, integration, and Aries Agent Test Harness (https://github.com/hyperledger/aries-agent-test-harness) tests must pass.
### Acceptance Criteria
- A Phase 1 design document that must describe the approach to be used, covering the approach for backwards compatibility and for registration (writing) and resolution of AnonCreds objects in a ledger-agnostic manner, such that AnonCreds can be used with other ledgers in the future.
- Open Source code and unit tests merged into ACA-Py repository and that are accepted following review by the core maintainers.
- The successful execution of the existing ACA-Py unit, integration, and Aries Agent Test Harness tests.
- The substantial completion of the work by March 31, 2023.
## User stories
A holder who has an AnonCreds verifiable credential whose cryptographic information is stored on a Cheqd ledger can create a presentation with their AnonCreds VC using ACA-Py.
For developers, the existing ACA-Py unit, integration, and Aries Agent Test Harness tests pass.
## Goals
- Maintain backwards compatibility
- Unless there is a good reason to do otherwise, we will maintain backward compatibility
- Replace indy-shared-rs with anoncreds-rs
- Add pluggable interface for AnonCreds registries
- Add builtin plugin for legacy indy
- Add builtin plugin for did:indy
- Add builtin plugin for did:web (AnonCreds object resolution only)
- Add/modify Admin API endpoints to support ledger-agnostic AnonCreds
- Ensure unit tests pass
- Ensure integration tests pass
- Create documentation for the new pluggable interfaces and their usage
- Create documentation for any new/modified Admin API endpoints
### Nice to haves
- Simplify the Admin API where possible
- Flag for activating/deactivating legacy/anoncreds
## Non-Goals
- Anoncreds-rs bugs (unless they can be easily fixed)
- Endorser protocol support
- DID Method specific prerequisites for writing to ledger
- E.g. Indy TAA, endorsement, etc.
- These can be handled by Admin API endpoints added by method specific AnonCreds plugins
## Proposed Solution (Technical Architecture)
#### High-level overview
- Replace indy-shared-rs with anoncreds-rs
- Implement pluggable AnonCreds Registry interface
- Implement registries for:
- Legacy Indy (backwards compatibility)
- did:indy
- did:web (at least resolution)
- Integrate AnonCredsRegistry interface into Issue Credential
- Integrate AnonCredsRegistry interface into Present Proof
- Admin API for creating AnonCreds objects
- Leave previous endpoints intact
- Testing and Documentation
- In parallel with development
### Replace indy-shared-rs with anoncreds-rs
> PIP package for `anoncreds-rs` is not yet published; pull from git repo to workaround and pull pre-built binaries from alternate location. There should be a package available soon.
- Rename `aries_cloudagent.indy` to `aries_cloudagent.anoncreds`.
- Rename `IndyIssuer` to `AnoncredsIssuer`.
- Rename `IndyHolder` to `AnoncredsHolder`.
- Rename `IndyVerifier` to `AnoncredsVerifier`.
- Replace `indy-credx` wrapper usage with `anoncreds` wrapper usage.
- Since `anoncreds-rs` is not opinionated about identifiers (as `indy-shared-rs` was), create identifiers for schemas, cred defs, rev reg defs, rev reg entries directly in `AnonCredsIssuer`, `AnonCredsHolder`, `AnonCredsVerifier`, etc.
- Fix tests.
### Pluggable AnonCreds Registries
#### Interface ABC
[Credit to Timo Glastra for outlining this interface originally.](https://hackmd.io/Ailjb-WDTeC48EjF0gjrGA) Dataclasses will likely need to be Marshmallow objects to be reusable from Admin API as appropriate.
```python
from abc import ABC, abstractmethod
from typing import Any, Dict, List, Optional, Literal
from dataclasses import dataclass
@dataclass
class AnonCredsSchema:
issuerId: str
attrNames: List[str]
name: str
version: str
@dataclass
class AnonCredsRegistryGetSchema:
schema: AnonCredsSchema
schema_id: str
resolution_metadata: Dict[str, Any]
schema_metadata: Dict[str, Any]
# TODO: determine types for `primary` and `revocation`
@dataclass
class AnonCredsCredentialDefinitionValue:
primary: Any
revocation: Optional[Any]
@dataclass
class AnonCredsCredentialDefinition:
issuerId: str
schemaId: str
type: Literal["CL"]
tag: str
value: AnonCredsCredentialDefinitionValue
@dataclass
class AnonCredsRegistryGetCredentialDefinition:
credential_definition: AnonCredsCredentialDefinition
credential_definition_id: str
resolution_metadata: Dict[str, Any]
credential_definition_metadata: Dict[str, Any]
@dataclass
class AnonCredsRevocationRegistryDefinition:
issuerId: str
type: Literal["CL_ACCUM"]
credDefId: str
tag: str
# TODO: determine type for `publicKeys`
publicKeys: Any
maxCredNum: int
tailsLocation: str
tailsHash: str
@dataclass
class AnonCredsRegistryGetRevocationRegistryDefinition:
revocation_registry: AnonCredsRevocationRegistryDefinition
revocation_registry_id: str
resolution_metadata: Dict[str, Any]
revocation_registry_metadata: Dict[str, Any]
@dataclass
class AnonCredsRevocationList:
issuerId: str
revRegId: str
revocationList: List[int]
currentAccumulator: str
timestamp: int
@dataclass
class AnonCredsRegistryGetRevocationList:
revocation_list: AnonCredsRevocationList
resolution_metadata: Dict[str, Any]
revocation_registry_metadata: Dict[str, Any]
class AnonCredsRegistry(ABC):
@abstractmethod
async def get_schema(self, schema_id: str) -> AnonCredsRegistryGetSchema:
"""Get a schema from the registry."""
# TODO: determine keyword arguments
@abstractmethod
async def register_schema(self):
"""Register a schema on the registry."""
@abstractmethod
async def get_credential_definition(
self, credential_definition_id: str
) -> AnonCredsRegistryGetCredentialDefinition:
"""Get a credential definition from the registry."""
# TODO: determine keyword arguments
@abstractmethod
async def register_credential_definition(self):
"""Register a credential definition on the registry."""
@abstractmethod
async def get_revocation_registry_definition(
self, revocation_registry_id: str
) -> AnonCredsRegistryGetRevocationRegistryDefinition:
"""Get a revocation registry definition from the registry."""
# TODO: determine keyword arguments
@abstractmethod
async def register_revocation_registry_definition(self):
"""Register a revocation registry definition on the registry."""
@abstractmethod
async def get_revocation_list(
self, revocation_registry_id: str, timestamp: str
) -> AnonCredsRegistryGetRevocationList:
"""Get a revocation list from the registry."""
# TODO: determine keyword arguments
@abstractmethod
async def register_revocation_list(self):
"""Register a revocation list on the registry."""
```
### Plugin implementation registration
Follows the same pattern as the pluggable DID Resolver interface.
- Define an object that stores AnonCredsRegistry instances in ACA-Py's context
- Update the default context to load the default registries
- Plugins are responsible for registering themselves through the plugin `setup` method.
- "Global" AnonCredsRegistry (a registry of registries, meta-registry) that uses the list of AnonCreds registries to select the appropriate registry based on identifier
### Default Registry Implementations
#### Legacy Indy Registry
- Update for consistent behavior between registries
#### did:indy Registry
* Create and retrieve schemas
* Create and retrieve credential definitions
* Create and retrieve revocation registry definitions
* Create and retrieve revocation registry entries
#### did:web Registry
* Create and retrieve schemas
* Create and retrieve credential definitions
* Create and retrieve revocation registry definitions
* Create and retrieve revocation registry entries
### Admin API
* Create anoncreds routes to OpenAPI specifications
* Overview of endpoint definitions
* Schemas
* `POST /anoncreds/schema`
* `GET /anoncreds/schema/<schema_id>`
* `GET /anoncreds/schemas` (filter method in query parameters)
* Credential definitions
* `POST /anoncreds/credential-definition`
* `GET /anoncreds/credential-definition/<cred_def_id>`
* `GET /anoncreds/credential-definitions` (filter method in query parameters)
* Revocation
* Existing Admin API to be modified to use the AnonCredsRegistry implementation
#### Endpoint Definitions
##### Schemas
###### Create Schema
```
POST /anoncreds/schema
```
**Request**
```json
{
"schema": {
"attrNames": ["string"],
"name": "string",
"version": "string",
"issuerId": "string"
},
// options method can be different per method, but it can also include default options for all anoncreds methods (none for schema)
"options": {
// it can also be automatically inferred from the agent startup parameters (default endorser)
// TODO: should we add an endorser did that will return the transaction instead of send?
// Note: endorser protocol support is out of scope for this project, but this is the entrypoint for when it is implemented
"endorser_connection_id": ""
}
}
```
**Response**
```json
{
"job_id": "string",
"schema_state": {
"state": "string",
"schema_id": "string",
"schema": {
"attrNames": ["string"],
"name": "string",
"version": "string",
"issuerId": "string"
}
},
"registration_metadata": {},
// For indy, schema_metadata will contain the seqNo
"schema_metadata": {}
}
```
- `job_id` - job identifier to keep track of the status of the schema creation. MUST be absent or have a null value if the value of the `schema_state.state` response field is either `finished` or `failed`, and MUST NOT have a null value otherwise.
- `schema_state`
- `state` - The state of the schema creation. Possible values are `finished`, `failed`, `action` and `wait`.
- `schema_id` - The id of the schema. If the value of the `schema_state.state` response field is `finished`, this field MUST be present and MUST NOT have a null value.
- `schema` - The schema. If the value of the `schema_state.state` response field is `finished`, this field MUST be present and MUST NOT have a null value.
- `registration_metadata` - This field contains metadata about hte registration process
- `schema_metadata` - This fields contains metadata about the schema.
###### Get Schema by schema_id
```
GET /anoncreds/schema/<schema_id>
```
**Response**
```json
{
"schema": {
"issuerId": "string"
"attrNames": ["string"],
"name": "string",
"version": "string"
},
"schema_id": "string",
"resolution_metadata": {},
"schema_metadata": {}
}
```
- `schema` - The schema.
- `schema_id` - The id of the schema.
- `schema_resolution_metadata` - This field contains metadata about the resolution process
- `schema_metadata` - This fields contains metadata about the schema.
###### Get Schemas
```
GET /anoncreds/schemas
```
**Request**
- `schema_name` - (optional) The name of the schema.
- `schema_version` - (optional) The version of the schema.
- `schema_issuer_did` - (optional) The did of the issuer of the schema
**Response**
Return the schema identifier
```json
{
"schema_ids": ["string"]
}
```
- `schema_ids` - The list of schema_ids that match the query
##### Credential Definitions
###### Create Credential Definition
```
POST /anoncreds/credential-definitions
```
**Request**
```json
{
// FIXME: this is using snake case, while the schema call is using camelCase (schema is model from anoncreds spec, while this is
// input to the credential definition creation.
"credential_definition": {
"tag": "string",
"schemaId": "string",
"issuerId": "string"
// TODO: we could infer this from whether `revocation_registry_size` is defined
"support_revocation": "boolean",
"revocation_registry_size": "integer",
},
// options method can be different per method, but it can also include default options for all anoncreds methods (such as support_revocation)
"options": {
// For indy it can include e.g. an endorser did
// it can also be automatically inferred from the agent startup parameters (default endorser)
// TODO: should we add an endorser did that will return the transaction instead of send it?
"endorser_connection_id": "string",
// "support_revocation" is optional option for _all_ anoncreds methods
"support_revocation" : "boolean",
// revocation_registry_size is optional and will have a default value if support_revocation is `true`
"revocation_registry_size": "integer",
}
}
```
**Response**
```json
{
"job_id": "string",
"credential_definition_state": {
"state": "string",
"credential_definition_id": "string",
"credential_definition": {
"issuerId": "string",
"schemaId": "string",
"type": "CL",
"tag": "string",
"value": {
"primary": {
"n": "779...397",
"r": {
"link_secret": "521...922",
"<key>": "410...200"
},
"rctxt": "774...977",
"s": "750..893",
"z": "632...005"
}
}
}
},
"registration_metadata": {},
"credential_definition_metadata": {}
}
```
- `job_id` - job identifier to keep track of the status of the credential definition creation. MUST be absent or have a null value if the value of the `credential_definition_state.state` response field is either `finished` or `failed`, and MUST NOT have a null value otherwise.
- `credential_definition_state`
- `state` - The state of the credential definition creation. Possible values are `finished`, `failed`, `action` and `wait`.
- `credential_definition_id` - The id of the credential definition. If the value of the `credential_definition.state` response field is `finished`, this field MUST be present and MUST NOT have a null value.
- `credential_definition` - The credential definition. If the value of the `credential_definition.state` response field is `finished`, this field MUST be present and MUST NOT have a null value.
- `registration_metadata` - This field contains metadata about the credential definition registration process
- `credential_definition_metadata` - This fields contains metadata about the credential definition.
###### Get Credential Definition by cred def id
```
GET /anoncreds/credential-definition/<cred_def_id>
```
**Response**
```json
{
"credential_definition_id": "string",
"credential_definition": {
"issuerId": "string",
"schemaId": "string",
"type": "CL",
"tag": "string",
"value": {
"primary": {
"n": "779...397",
"r": {
"link_secret": "521...922",
"<key>": "410...200"
},
"rctxt": "774...977",
"s": "750..893",
"z": "632...005"
}
}
},
"resolution_metadata": {},
"credential_definition_metadata": {}
}
```
- `credential_definition_id` - The id of the credential definition
- `credential_definition` - The credential definition.
- `resolution_metadata` - This field contains metadata about the resolution process
- `credential_definition_metadata` - This fields contains metadata about the schema.
###### Get Credential Definitions
```
GET /anoncreds/credential-definitions
```
**Request**
- `credential_definition_id` - (optional) The id of the credential definition
- `issuer_id` - (optional) The issuer id that created the credential definition.
- `schema_id` - (optional) The id of the schema
- `schema_issuer_id` - (optional) The issuer id that created the schema.
- `schema_name` - (optional) The name of the schema.
- `schema_version` - (optional) The version of the schema.
**Response**
```json
{
"credential_definition_id": ["string"]
}
```
- `credential_definition_id` - The credential definition id(s) that match the query
#### Integrate AnonCredsRegistry into Issue Credential, Present Proof
Most changes will be encapsulated in the `AnonCreds{Issuer,Holder,Verifier}` classes. Below is a summary of interactions that will be impacted by using the AnonCreds Registry.
- In credential issuance, the AnonCreds Registry will be used by the holder to:
- Retrieve Credential Definition to create cred request and check validity of received credential
- In proof presentation, the AnonCreds Registry will be used by the verifier to:
- Retrieve cred def
- Retrieve revocation registry def (passed to anoncreds rs to worry about the rest)
- In proof presentation, the AnonCreds Registry will be used by the holder to:
- Retrieve revocation list to create rev_states (to be passed to anoncreds rs to worry about the rest)
#### Testing and documentation
* Create unit and end-to-end tests
* Make sure existing tests pass to validate backwards compatibility
* Write documentation
### Open Questions