Skip to content

Commit

Permalink
Merge pull request #306 from andrewwhitehead/feature/rev-reg
Browse files Browse the repository at this point in the history
Basic support for credential revocation and revocation registry handling
  • Loading branch information
andrewwhitehead authored Mar 5, 2020
2 parents 3c719eb + 3b95740 commit 6a96cad
Show file tree
Hide file tree
Showing 42 changed files with 2,410 additions and 441 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -185,3 +185,4 @@ $RECYCLE.BIN/

# Docs build
_build/
**/*.iml
5 changes: 3 additions & 2 deletions aries_cloudagent/config/default_context.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,10 +82,10 @@ async def bind_providers(self, context: InjectionContext):
StatsProvider(
LedgerProvider(),
(
"create_and_send_credential_definition",
"create_and_send_schema",
"get_credential_definition",
"get_schema",
"send_credential_definition",
"send_schema",
),
)
),
Expand Down Expand Up @@ -154,6 +154,7 @@ async def load_plugins(self, context: InjectionContext):
"aries_cloudagent.messaging.credential_definitions"
)
plugin_registry.register_plugin("aries_cloudagent.messaging.schemas")
plugin_registry.register_plugin("aries_cloudagent.revocation")
plugin_registry.register_plugin("aries_cloudagent.wallet")

# Register external plugins
Expand Down
103 changes: 101 additions & 2 deletions aries_cloudagent/holder/base.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
"""Base holder class."""

from abc import ABC
from abc import ABC, ABCMeta, abstractmethod
from typing import Union


class BaseHolder(ABC):
class BaseHolder(ABC, metaclass=ABCMeta):
"""Base class for holder."""

def __repr__(self) -> str:
Expand All @@ -15,3 +16,101 @@ def __repr__(self) -> str:
"""
return "<{}>".format(self.__class__.__name__)

@abstractmethod
async def get_credential(self, credential_id: str):
"""
Get a credential stored in the wallet.
Args:
credential_id: Credential id to retrieve
"""

@abstractmethod
async def delete_credential(self, credential_id: str):
"""
Remove a credential stored in the wallet.
Args:
credential_id: Credential id to remove
"""

@abstractmethod
async def get_mime_type(
self, credential_id: str, attr: str = None
) -> Union[dict, str]:
"""
Get MIME type per attribute (or for all attributes).
Args:
credential_id: credential id
attr: attribute of interest or omit for all
Returns: Attribute MIME type or dict mapping attribute names to MIME types
attr_meta_json = all_meta.tags.get(attr)
"""

@abstractmethod
async def create_presentation(
self,
presentation_request: dict,
requested_credentials: dict,
schemas: dict,
credential_definitions: dict,
rev_states_json: dict = None,
):
"""
Get credentials stored in the wallet.
Args:
presentation_request: Valid indy format presentation request
requested_credentials: Indy format requested_credentials
schemas: Indy formatted schemas_json
credential_definitions: Indy formatted schemas_json
rev_states_json: Indy format revocation states
"""

@abstractmethod
async def create_credential_request(
self, credential_offer, credential_definition, holder_did: str
):
"""
Create a credential offer for the given credential definition id.
Args:
credential_offer: The credential offer to create request for
credential_definition: The credential definition to create an offer for
holder_did: the DID of the agent making the request
Returns:
A credential request
"""

@abstractmethod
async def store_credential(
self,
credential_definition,
credential_data,
credential_request_metadata,
credential_attr_mime_types=None,
credential_id=None,
rev_reg_def_json=None,
):
"""
Store a credential in the wallet.
Args:
credential_definition: Credential definition for this credential
credential_data: Credential data generated by the issuer
credential_request_metadata: credential request metadata generated
by the issuer
credential_attr_mime_types: dict mapping attribute names to (optional)
MIME types to store as non-secret record, if specified
credential_id: optionally override the stored credential id
rev_reg_def_json: optional revocation registry definition in json
"""
42 changes: 22 additions & 20 deletions aries_cloudagent/holder/indy.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,14 +35,15 @@ def __init__(self, wallet):
self.wallet = wallet

async def create_credential_request(
self, credential_offer, credential_definition, did
self, credential_offer, credential_definition, holder_did: str
):
"""
Create a credential offer for the given credential definition id.
Args:
credential_offer: The credential offer to create request for
credential_definition: The credential definition to create an offer for
holder_did: the DID of the agent making the request
Returns:
A credential request
Expand All @@ -54,7 +55,7 @@ async def create_credential_request(
credential_request_metadata_json,
) = await indy.anoncreds.prover_create_credential_req(
self.wallet.handle,
did,
holder_did,
json.dumps(credential_offer),
json.dumps(credential_definition),
self.wallet.master_secret_id,
Expand All @@ -77,7 +78,8 @@ async def store_credential(
credential_data,
credential_request_metadata,
credential_attr_mime_types=None,
credential_id=None
credential_id=None,
rev_reg_def_json=None,
):
"""
Store a credential in the wallet.
Expand All @@ -89,15 +91,17 @@ async def store_credential(
by the issuer
credential_attr_mime_types: dict mapping attribute names to (optional)
MIME types to store as non-secret record, if specified
credential_id: optionally override the stored credential id
rev_reg_def_json: revocation registry definition in json
"""
credential_id = await indy.anoncreds.prover_store_credential(
self.wallet.handle,
credential_id,
json.dumps(credential_request_metadata),
json.dumps(credential_data),
json.dumps(credential_definition),
None, # We don't support revocation yet
wallet_handle=self.wallet.handle,
cred_id=credential_id,
cred_req_metadata_json=json.dumps(credential_request_metadata),
cred_json=json.dumps(credential_data),
cred_def_json=json.dumps(credential_definition),
rev_reg_def_json=json.dumps(rev_reg_def_json) if rev_reg_def_json else None,
)

if credential_attr_mime_types:
Expand All @@ -111,7 +115,7 @@ async def store_credential(
type=IndyHolder.RECORD_TYPE_MIME_TYPES,
value=credential_id,
tags=mime_types,
id=f"{IndyHolder.RECORD_TYPE_MIME_TYPES}::{credential_id}"
id=f"{IndyHolder.RECORD_TYPE_MIME_TYPES}::{credential_id}",
)
indy_stor = IndyStorage(self.wallet)
await indy_stor.add_record(record)
Expand All @@ -129,8 +133,7 @@ async def get_credentials(self, start: int, count: int, wql: dict):
"""
search_handle, record_count = await indy.anoncreds.prover_search_credentials(
self.wallet.handle,
json.dumps(wql)
self.wallet.handle, json.dumps(wql)
)

# We need to move the database cursor position manually...
Expand All @@ -139,8 +142,7 @@ async def get_credentials(self, start: int, count: int, wql: dict):
await indy.anoncreds.prover_fetch_credentials(search_handle, start)

credentials_json = await indy.anoncreds.prover_fetch_credentials(
search_handle,
count
search_handle, count
)
await indy.anoncreds.prover_close_credentials_search(search_handle)

Expand Down Expand Up @@ -248,7 +250,7 @@ async def delete_credential(self, credential_id: str):
indy_stor = IndyStorage(self.wallet)
mime_types_record = await indy_stor.get_record(
IndyHolder.RECORD_TYPE_MIME_TYPES,
f"{IndyHolder.RECORD_TYPE_MIME_TYPES}::{credential_id}"
f"{IndyHolder.RECORD_TYPE_MIME_TYPES}::{credential_id}",
)
await indy_stor.delete_record(mime_types_record)
except StorageNotFoundError:
Expand All @@ -267,9 +269,7 @@ async def delete_credential(self, credential_id: str):
raise

async def get_mime_type(
self,
credential_id: str,
attr: str = None
self, credential_id: str, attr: str = None
) -> Union[dict, str]:
"""
Get MIME type per attribute (or for all attributes).
Expand All @@ -285,7 +285,7 @@ async def get_mime_type(
try:
mime_types_record = await IndyStorage(self.wallet).get_record(
IndyHolder.RECORD_TYPE_MIME_TYPES,
f"{IndyHolder.RECORD_TYPE_MIME_TYPES}::{credential_id}"
f"{IndyHolder.RECORD_TYPE_MIME_TYPES}::{credential_id}",
)
except StorageError:
return None # no MIME types: not an error
Expand All @@ -298,6 +298,7 @@ async def create_presentation(
requested_credentials: dict,
schemas: dict,
credential_definitions: dict,
rev_states_json: dict = None,
):
"""
Get credentials stored in the wallet.
Expand All @@ -307,6 +308,7 @@ async def create_presentation(
requested_credentials: Indy format requested_credentials
schemas: Indy formatted schemas_json
credential_definitions: Indy formatted schemas_json
rev_states_json: Indy format revocation states
"""

Expand All @@ -317,7 +319,7 @@ async def create_presentation(
self.wallet.master_secret_id,
json.dumps(schemas),
json.dumps(credential_definitions),
json.dumps({}) # We don't support revocation currently.
json.dumps(rev_states_json) if rev_states_json else "{}",
)

presentation = json.loads(presentation_json)
Expand Down
12 changes: 6 additions & 6 deletions aries_cloudagent/holder/tests/test_indy.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,12 +55,12 @@ async def test_store_credential(self, mock_store_cred):
)

mock_store_cred.assert_called_once_with(
mock_wallet.handle,
None,
json.dumps("credential_request_metadata"),
json.dumps("credential_data"),
json.dumps("credential_definition"),
None,
wallet_handle=mock_wallet.handle,
cred_id=None,
cred_req_metadata_json=json.dumps("credential_request_metadata"),
cred_json=json.dumps("credential_data"),
cred_def_json=json.dumps("credential_definition"),
rev_reg_def_json=None
)

assert cred_id == "cred_id"
Expand Down
58 changes: 56 additions & 2 deletions aries_cloudagent/issuer/base.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
"""Ledger issuer class."""

from abc import ABC
from abc import ABC, ABCMeta, abstractmethod


class BaseIssuer(ABC):
class BaseIssuer(ABC, metaclass=ABCMeta):
"""Base class for issuer."""

def __repr__(self) -> str:
Expand All @@ -15,3 +15,57 @@ def __repr__(self) -> str:
"""
return "<{}>".format(self.__class__.__name__)

@abstractmethod
def create_credential_offer(self, credential_definition_id):
"""
Create a credential offer for the given credential definition id.
Args:
credential_definition_id: The credential definition to create an offer for
Returns:
A credential offer
"""
pass

@abstractmethod
async def create_credential(
self,
schema,
credential_offer,
credential_request,
credential_values,
revoc_reg_id: str = None,
tails_reader_handle: int = None,
):
"""
Create a credential.
Args
schema: Schema to create credential for
credential_offer: Credential Offer to create credential for
credential_request: Credential request to create credential for
credential_values: Values to go in credential
revoc_reg_id: ID of the revocation registry
tails_reader_handle: Handle for the tails file blob reader
Returns:
A tuple of created credential, revocation id
"""
pass

@abstractmethod
def revoke_credential(self, revoc_reg_id, tails_reader_handle, cred_revoc_id):
"""
Revoke a credential.
Args
revoc_reg_id: ID of the revocation registry
tails_reader_handle: handle for the registry tails file
cred_revoc_id: index of the credential in the revocation registry
"""
pass
Loading

0 comments on commit 6a96cad

Please sign in to comment.