Skip to content

Commit

Permalink
Implement register schema
Browse files Browse the repository at this point in the history
  • Loading branch information
DaevMithran committed Nov 5, 2024
1 parent 1c63da0 commit b900fd6
Show file tree
Hide file tree
Showing 6 changed files with 206 additions and 20 deletions.
8 changes: 8 additions & 0 deletions acapy_agent/anoncreds/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,14 @@ async def setup(context: InjectionContext):
await indy_registry.setup(context)
registry.register(indy_registry)

cheqd_registry = ClassProvider(
"acapy_agent.anoncreds.default.did_cheqd.registry.DIDCheqdRegistry",
# supported_identifiers=[],
# method_name="did:cheqd",
).provide(context.settings, context.injector)
await cheqd_registry.setup(context)
registry.register(cheqd_registry)

web_registry = ClassProvider(
"acapy_agent.anoncreds.default.did_web.registry.DIDWebRegistry",
# supported_identifiers=[],
Expand Down
129 changes: 122 additions & 7 deletions acapy_agent/anoncreds/default/did_cheqd/registry.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,19 @@
"""DID Indy Registry."""

import logging
import re
from typing import Optional, Pattern, Sequence
from aiohttp import web
from pydantic.v1 import UUID4
from uuid_utils.compat import uuid4

from ..legacy_indy.registry import FAILED_TO_STORE_TRANSACTION_RECORD
from ....config.injection_context import InjectionContext
from ....core.profile import Profile
from ...base import BaseAnonCredsRegistrar, BaseAnonCredsResolver
from ...base import (
BaseAnonCredsRegistrar,
BaseAnonCredsResolver,
AnonCredsRegistrationError,
)
from ...models.anoncreds_cred_def import CredDef, CredDefResult, GetCredDefResult
from ...models.anoncreds_revocation import (
GetRevListResult,
Expand All @@ -16,7 +23,16 @@
RevRegDef,
RevRegDefResult,
)
from ...models.anoncreds_schema import AnonCredsSchema, GetSchemaResult, SchemaResult
from ...models.anoncreds_schema import (
AnonCredsSchema,
GetSchemaResult,
SchemaResult,
SchemaState,
)
from ....did.cheqd.cheqd_manager import DidCheqdManager
from ....messaging.valid import CheqdDID
from ....wallet.base import BaseWallet
from ....wallet.util import b64_to_bytes, bytes_to_b64

LOGGER = logging.getLogger(__name__)

Expand All @@ -31,13 +47,16 @@ def __init__(self):
None
"""
self._supported_identifiers_regex = re.compile(r"^did:cheqd:.*$")

@property
def supported_identifiers_regex(self) -> Pattern:
"""Supported Identifiers regex."""
return self._supported_identifiers_regex
# TODO: fix regex (too general)
return CheqdDID.PATTERN

@staticmethod
def make_schema_id(schema: AnonCredsSchema, resource_id: UUID4) -> str:
"""Derive the ID for a schema."""
return f"{schema.issuer_id}/resources/{resource_id}"

async def setup(self, context: InjectionContext):
"""Setup."""
Expand All @@ -54,7 +73,47 @@ async def register_schema(
options: Optional[dict] = None,
) -> SchemaResult:
"""Register a schema on the registry."""
raise NotImplementedError()
resource_id = options.get("resource_id") or uuid4()
resource_type = "anoncredsSchema"
resource_version = schema.version
resource_name = schema.name

schema_id = self.make_schema_id(schema, resource_id)
LOGGER.debug("Registering schema: %s", schema_id)
cheqd_schema = {
"id": resource_id,
"name": resource_name,
"resourceType": resource_type,
"version": resource_version,
"data": {
"name": f"{schema.name}",
"version": f"{schema.version}",
"attrNames": f"{schema.attr_names}",
},
}
LOGGER.debug("schema value: %s", cheqd_schema)
try:
await self._create_and_publish_resource(
profile,
schema.issuer_id,
cheqd_schema,
)
except Exception:
raise AnonCredsRegistrationError(FAILED_TO_STORE_TRANSACTION_RECORD)
return SchemaResult(
job_id=uuid4().hex,
schema_state=SchemaState(
state=SchemaState.STATE_FINISHED,
schema_id=schema_id,
schema=schema,
),
registration_metadata={
"resource_id": resource_id,
"resource_name": resource_name,
"resource_type": resource_type,
"resource_version": resource_version,
},
)

async def get_credential_definition(
self, profile: Profile, credential_definition_id: str
Expand Down Expand Up @@ -118,3 +177,59 @@ async def update_revocation_list(
) -> RevListResult:
"""Update a revocation list on the registry."""
raise NotImplementedError()

@staticmethod
async def _create_and_publish_resource(profile: Profile, did: str, options: dict):
"""Create, Sign and Publish a Resource."""
cheqd_manager = DidCheqdManager(profile)
async with profile.session() as session:
wallet = session.inject_or(BaseWallet)
if not wallet:
raise web.HTTPForbidden(reason="No wallet available")

try:
# validate issuer_id
did_record = await wallet.get_local_did(did)
verkey = did_record.verkey

# request create resource operation
create_request_res = await cheqd_manager.registrar.create_resource(
did, options
)

job_id: str = create_request_res.get("jobId")
resource_state = create_request_res.get("resourceState")
if resource_state.get("state") == "action":
sign_req: dict = resource_state.get("signingRequest")[0]
kid: str = sign_req.get("kid")
payload_to_sign: str = sign_req.get("serializedPayload")
# sign payload
signature_bytes = await wallet.sign_message(
b64_to_bytes(payload_to_sign), verkey
)
# publish resource
publish_resource_res = await cheqd_manager.registrar.create_resource(
did,
{
"jobId": job_id,
"secret": {
"signingResponse": [
{
"kid": kid,
"signature": bytes_to_b64(signature_bytes),
}
],
},
},
)
resource_state = publish_resource_res.get("resourceState")
if resource_state.get("state") != "finished":
raise AnonCredsRegistrationError(
f"Error publishing Resource {resource_state.get("reason")}"
)
else:
raise AnonCredsRegistrationError(
f"Error publishing Resource {resource_state.get("reason")}"
)
except Exception:
raise AnonCredsRegistrationError(FAILED_TO_STORE_TRANSACTION_RECORD)
6 changes: 4 additions & 2 deletions acapy_agent/did/cheqd/cheqd_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,9 +90,11 @@ async def register(self, options: dict) -> dict:
)
publish_did_state = publish_did_res.get("didState")
if publish_did_state.get("state") != "finished":
raise WalletError("Error registering DID")
raise WalletError(
f"Error registering DID {publish_did_state.get("reason")}"
)
else:
raise WalletError("Error registering DID")
raise WalletError(f"Error registering DID {did_state.get("reason")}")
except Exception:
raise
async with self.profile.session() as session:
Expand Down
47 changes: 38 additions & 9 deletions acapy_agent/did/cheqd/registrar.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"""DID Registrar for Cheqd."""

from aiohttp import ClientSession
from aiohttp import ClientSession, web


class DidCheqdRegistrar:
Expand Down Expand Up @@ -30,19 +30,48 @@ async def generate_did_doc(self, network: str, public_key_hex: str) -> dict | No
raise

async def create(self, options: dict) -> dict | None:
"""Request Create and Publish a DID Document."""
"""Create a DID Document."""
async with ClientSession() as session:
try:
async with session.post(
self.DID_REGISTRAR_BASE_URL + "create", json=options
) as response:
if response.status == 200 or response.status == 201:
return await response.json()
return await response.json()
except Exception:
raise

async def update(self, options: dict) -> dict:
"""Update a DID Document."""
async with ClientSession() as session:
try:
async with session.post(
self.DID_REGISTRAR_BASE_URL + "update", json=options
) as response:
return await response.json()
except Exception:
raise

async def deactivate(self, options: dict) -> dict:
"""Deactivate a DID Document."""
async with ClientSession() as session:
try:
async with session.post(
self.DID_REGISTRAR_BASE_URL + "deactivate", json=options
) as response:
return await response.json()
except Exception:
raise

# async def update(self, options: dict) -> dict:
#
# async def deactivate(self, options: dict) -> dict:
#
# async def create_resource(self, options:dict) -> dict
async def create_resource(self, did: str, options: dict) -> dict:
"""Create a DID Linked Resource."""
async with ClientSession() as session:
try:
async with session.post(
self.DID_REGISTRAR_BASE_URL + did + "/create-resource", json=options
) as response:
if response.status == 200:
return await response.json()
else:
raise web.HTTPInternalServerError()
except Exception:
raise
17 changes: 17 additions & 0 deletions acapy_agent/resolver/default/cheqd.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,3 +55,20 @@ async def _resolve(
raise ResolverError(
"Could not find doc for {}: {}".format(did, await response.text())
)

async def resolve_resource(self, did: str) -> dict:
"""Resolve a Cheqd DID Linked Resource."""
async with ClientSession() as session:
async with session.get(
self.DID_RESOLVER_BASE_URL + did,
) as response:
if response.status == 200:
try:
return await response.json()
except Exception as err:
raise ResolverError("Response was incorrectly formatted") from err
if response.status == 404:
raise DIDNotFound(f"No resource found for {did}")
raise ResolverError(
"Could not find doc for {}: {}".format(did, await response.text())
)
19 changes: 17 additions & 2 deletions acapy_agent/wallet/routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,16 @@
)
from .base import BaseWallet
from .did_info import DIDInfo
from .did_method import KEY, PEER2, PEER4, SOV, DIDMethod, DIDMethods, HolderDefinedDid
from .did_method import (
KEY,
PEER2,
PEER4,
SOV,
DIDMethod,
DIDMethods,
HolderDefinedDid,
CHEQD,
)
from .did_posture import DIDPosture
from .error import WalletError, WalletNotFoundError
from .key_type import BLS12381G2, ED25519, KeyTypes
Expand Down Expand Up @@ -313,7 +322,13 @@ class DIDListQueryStringSchema(OpenAPISchema):
method = fields.Str(
required=False,
validate=validate.OneOf(
[KEY.method_name, SOV.method_name, PEER2.method_name, PEER4.method_name]
[
KEY.method_name,
SOV.method_name,
CHEQD.method_name,
PEER2.method_name,
PEER4.method_name,
]
),
metadata={
"example": KEY.method_name,
Expand Down

0 comments on commit b900fd6

Please sign in to comment.