Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

feat(anoncreds): Implement automated setup of revocation #2292

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
104 changes: 104 additions & 0 deletions aries_cloudagent/anoncreds/events.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
"""Events fired by AnonCreds interface."""

import re
from typing import NamedTuple

from ..core.event_bus import Event
from .models.anoncreds_revocation import RevRegDef


CRED_DEF_FINISHED_EVENT = "anoncreds::credential-definition::finished"
REV_REG_DEF_FINISHED_EVENT = "anoncreds::revocation-registry-definition::finished"
REV_LIST_FINISHED_EVENT = "anoncreds::revocation-list::finished"

CRED_DEF_FINISHED_PATTERN = re.compile("anoncreds::credential-definition::finished")
REV_REG_DEF_FINISHED_PATTERN = re.compile(
"anoncreds::revocation-registry-definition::finished"
)
REV_LIST_FINISHED_PATTERN = re.compile("anoncreds::revocation-list::finished")


class CredDefFinishedPayload(NamedTuple):
"""Payload of cred def finished event."""

schema_id: str
cred_def_id: str
issuer_id: str
support_revocation: bool
max_cred_num: int


class CredDefFinishedEvent(Event):
"""Event for cred def finished."""

def __init__(
self,
payload: CredDefFinishedPayload,
):
self._topic = CRED_DEF_FINISHED_EVENT
self._payload = payload

@classmethod
def with_payload(
cls,
schema_id: str,
cred_def_id: str,
issuer_id: str,
support_revocation: bool,
max_cred_num: int,
):
payload = CredDefFinishedPayload(
schema_id, cred_def_id, issuer_id, support_revocation, max_cred_num
)
return cls(payload)

@property
def payload(self) -> CredDefFinishedPayload:
"""Return payload."""
return self._payload


class RevRegDefFinishedPayload(NamedTuple):
"""Payload of rev reg def finished event."""

rev_reg_def_id: str
rev_reg_def: RevRegDef


class RevRegDefFinishedEvent(Event):
"""Event for rev reg def finished."""

def __init__(self, payload: RevRegDefFinishedPayload):
self._topic = REV_REG_DEF_FINISHED_EVENT
self._payload = payload

@classmethod
def with_payload(
cls,
rev_reg_def_id: str,
rev_reg_def: RevRegDef,
):
payload = RevRegDefFinishedPayload(rev_reg_def_id, rev_reg_def)
return cls(payload)

@property
def payload(self) -> RevRegDefFinishedPayload:
"""Return payload."""
return self._payload


class RevListFinishedPayload(NamedTuple):
"""Payload of rev list finished event."""


class RevListFinishedEvent(Event):
"""Event for rev list finished."""

def __init__(self, payload: RevListFinishedPayload):
self._topic = REV_LIST_FINISHED_EVENT
self._payload = payload

@property
def payload(self) -> RevListFinishedPayload:
"""Return payload."""
return self._payload
48 changes: 46 additions & 2 deletions aries_cloudagent/anoncreds/issuer.py
Original file line number Diff line number Diff line change
@@ -1,23 +1,27 @@
"""anoncreds-rs issuer implementation."""

import asyncio
import json
import logging
from time import time
from typing import Optional, Sequence

from aries_askar import AskarError

from anoncreds import (
AnoncredsError,
Credential,
CredentialDefinition,
CredentialOffer,
Schema,
)
from aries_askar import AskarError

from ..askar.profile import AskarProfile, AskarProfileSession
from ..core.error import BaseError
from ..core.event_bus import Event, EventBus
from ..core.profile import Profile
from .base import AnonCredsSchemaAlreadyExists
from .events import CredDefFinishedEvent
from .models.anoncreds_cred_def import CredDef, CredDefResult
from .models.anoncreds_schema import AnonCredsSchema, SchemaResult, SchemaState
from .registry import AnonCredsRegistry
Expand All @@ -26,6 +30,7 @@

DEFAULT_CRED_DEF_TAG = "default"
DEFAULT_SIGNATURE_TYPE = "CL"
DEFAULT_MAX_CRED_NUM = 1000
CATEGORY_SCHEMA = "schema"
CATEGORY_CRED_DEF = "credential_def"
CATEGORY_CRED_DEF_PRIVATE = "credential_def_private"
Expand Down Expand Up @@ -91,6 +96,11 @@ def profile(self) -> AskarProfile:

return self._profile

async def notify(self, event: Event):
"""Accessor for the event bus instance."""
event_bus = self.profile.inject(EventBus)
await event_bus.notify(self._profile, event)

async def _finish_registration(
self, txn: AskarProfileSession, category: str, job_id: str, registered_id: str
):
Expand All @@ -113,6 +123,7 @@ async def _finish_registration(
tags=tags,
)
await txn.handle.remove(category, job_id)
return entry

async def _store_schema(
self,
Expand Down Expand Up @@ -291,6 +302,12 @@ async def create_and_register_credential_definition(

options = options or {}
support_revocation = options.get("support_revocation", False)
if not isinstance(support_revocation, bool):
raise ValueError("support_revocation must be a boolean")

max_cred_num = options.get("max_cred_num", DEFAULT_MAX_CRED_NUM)
if not isinstance(max_cred_num, int):
raise ValueError("max_cred_num must be an integer")

try:
# Create the cred def
Expand Down Expand Up @@ -342,6 +359,11 @@ async def create_and_register_credential_definition(
"schema_version": schema_result.schema.version,
"state": result.credential_definition_state.state,
"epoch": str(int(time())),
# TODO We need to keep track of these but tags probably
# isn't ideal. This suggests that a full record object
# is necessary for non-private values
"support_revocation": json.dumps(support_revocation),
"max_cred_num": str(max_cred_num),
},
)
await txn.handle.insert(
Expand All @@ -353,6 +375,12 @@ async def create_and_register_credential_definition(
CATEGORY_CRED_DEF_KEY_PROOF, ident, key_proof.to_json_buffer()
)
await txn.commit()
if result.credential_definition_state.state == STATE_FINISHED:
await self.notify(
CredDefFinishedEvent.with_payload(
schema_id, ident, issuer_id, support_revocation, max_cred_num
)
)
except AskarError as err:
raise AnonCredsIssuerError("Error storing credential definition") from err

Expand All @@ -361,7 +389,13 @@ async def create_and_register_credential_definition(
async def finish_cred_def(self, job_id: str, cred_def_id: str):
"""Finish a cred def."""
async with self.profile.transaction() as txn:
await self._finish_registration(txn, CATEGORY_CRED_DEF, job_id, cred_def_id)
entry = await self._finish_registration(
txn, CATEGORY_CRED_DEF, job_id, cred_def_id
)
cred_def = CredDef.from_json(entry.value)
support_revocation = json.loads(entry.tags["support_revocation"])
max_cred_num = int(entry.tags["max_cred_num"])

await self._finish_registration(
txn, CATEGORY_CRED_DEF_PRIVATE, job_id, cred_def_id
)
Expand All @@ -370,6 +404,16 @@ async def finish_cred_def(self, job_id: str, cred_def_id: str):
)
await txn.commit()

await self.notify(
CredDefFinishedEvent.with_payload(
schema_id=cred_def.schema_id,
cred_def_id=cred_def_id,
issuer_id=cred_def.issuer_id,
support_revocation=support_revocation,
max_cred_num=max_cred_num,
)
)

async def get_created_credential_definitions(
self,
issuer_id: Optional[str] = None,
Expand Down
28 changes: 22 additions & 6 deletions aries_cloudagent/anoncreds/revocation.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,22 +11,24 @@
from typing import List, NamedTuple, Optional, Sequence, Tuple
from urllib.parse import urlparse

from aries_askar.error import AskarError
import base58
from requests import RequestException, Session

from anoncreds import (
AnoncredsError,
Credential,
CredentialRevocationConfig,
RevocationRegistryDefinition,
RevocationStatusList,
)
from aries_askar.error import AskarError
import base58
from requests import RequestException, Session


from ..askar.profile import AskarProfile, AskarProfileSession
from ..core.error import BaseError
from ..core.event_bus import Event, EventBus
from ..core.profile import Profile, ProfileSession
from ..tails.base import BaseTailsServer
from .events import RevRegDefFinishedEvent
from .issuer import (
AnonCredsIssuer,
CATEGORY_CRED_DEF,
Expand Down Expand Up @@ -89,7 +91,10 @@ def profile(self) -> AskarProfile:

return self._profile

# Revocation artifact management
async def notify(self, event: Event):
"""Emit an event on the event bus."""
event_bus = self.profile.inject(EventBus)
await event_bus.notify(self.profile, event)

async def _finish_registration(
self,
Expand Down Expand Up @@ -123,6 +128,7 @@ async def _finish_registration(
tags=tags,
)
await txn.handle.remove(category, job_id)
return entry

async def create_and_register_revocation_registry_definition(
self,
Expand Down Expand Up @@ -219,6 +225,11 @@ async def create_and_register_revocation_registry_definition(
rev_reg_def_private.to_json_buffer(),
)
await txn.commit()

if result.revocation_registry_definition_state.state == STATE_FINISHED:
await self.notify(
RevRegDefFinishedEvent.with_payload(ident, rev_reg_def)
)
except AskarError as err:
raise AnonCredsRevocationError(
"Error saving new revocation registry"
Expand All @@ -231,9 +242,10 @@ async def finish_revocation_registry_definition(
):
"""Mark a rev reg def as finished."""
async with self.profile.transaction() as txn:
await self._finish_registration(
entry = await self._finish_registration(
txn, CATEGORY_REV_REG_DEF, job_id, rev_reg_def_id, state=STATE_FINISHED
)
rev_reg_def = RevRegDef.from_json(entry.value)
await self._finish_registration(
txn,
CATEGORY_REV_REG_DEF_PRIVATE,
Expand All @@ -242,6 +254,10 @@ async def finish_revocation_registry_definition(
)
await txn.commit()

await self.notify(
RevRegDefFinishedEvent.with_payload(rev_reg_def_id, rev_reg_def)
)

async def get_created_revocation_registry_definitions(
self,
cred_def_id: Optional[str] = None,
Expand Down
Loading