diff --git a/acapy_agent/admin/decorators/auth.py b/acapy_agent/admin/decorators/auth.py index 818f297d40..3d1f209893 100644 --- a/acapy_agent/admin/decorators/auth.py +++ b/acapy_agent/admin/decorators/auth.py @@ -1,6 +1,8 @@ """Authentication decorators for the admin API.""" import functools +import re +from typing import Optional, Pattern from aiohttp import web @@ -48,6 +50,8 @@ def tenant_authentication(handler): - check for a valid bearer token in the Autorization header if running in multi-tenant mode - check for a valid x-api-key header if running in single-tenant mode + - check if the base wallet has access to the requested path if running + in multi-tenant mode """ @functools.wraps(handler) @@ -61,11 +65,15 @@ async def tenant_auth(request): ) insecure_mode = bool(profile.settings.get("admin.admin_insecure_mode")) multitenant_enabled = profile.settings.get("multitenant.enabled") + base_wallet_allowed_route = _base_wallet_route_access( + profile.settings.get("multitenant.base_wallet_routes"), request.path + ) # CORS fix: allow OPTIONS method access to paths without a token if ( (multitenant_enabled and authorization_header) or (not multitenant_enabled and valid_key) + or (multitenant_enabled and valid_key and base_wallet_allowed_route) or insecure_mode or request.method == "OPTIONS" ): @@ -78,3 +86,25 @@ async def tenant_auth(request): ) return tenant_auth + + +def _base_wallet_route_access(additional_routes: str, request_path: str) -> bool: + """Check if request path matches additional routes.""" + additional_routes_pattern = _build_additional_routes_pattern(additional_routes) + return _matches_additional_routes(additional_routes_pattern, request_path) + + +def _build_additional_routes_pattern(pattern_string: str) -> Optional[Pattern]: + """Build pattern from space delimited list of paths.""" + # create array and add word boundary to avoid false positives + if pattern_string: + paths = pattern_string.split(" ") + return re.compile("^((?:)" + "|".join(paths) + ")$") + return None + + +def _matches_additional_routes(pattern: Pattern, path: str) -> bool: + """Matches request path to provided pattern.""" + if pattern and path: + return bool(pattern.match(path)) + return False diff --git a/acapy_agent/admin/server.py b/acapy_agent/admin/server.py index 84650ed11b..111d2f2a52 100644 --- a/acapy_agent/admin/server.py +++ b/acapy_agent/admin/server.py @@ -5,7 +5,7 @@ import re import warnings import weakref -from typing import Callable, Coroutine, Optional, Pattern, Sequence, cast +from typing import Callable, Coroutine, Optional import aiohttp_cors import jwt @@ -280,29 +280,6 @@ def __init__( self.websocket_queues = {} self.site = None self.multitenant_manager = context.inject_or(BaseMultitenantManager) - self._additional_route_pattern: Optional[Pattern] = None - - @property - def additional_routes_pattern(self) -> Optional[Pattern]: - """Pattern for configured additional routes to permit base wallet to access.""" - if self._additional_route_pattern: - return self._additional_route_pattern - - base_wallet_routes = self.context.settings.get("multitenant.base_wallet_routes") - base_wallet_routes = cast(Sequence[str], base_wallet_routes) - if base_wallet_routes: - self._additional_route_pattern = re.compile( - "^(?:" + "|".join(base_wallet_routes) + ")" - ) - return None - - def _matches_additional_routes(self, path: str) -> bool: - """Path matches additional_routes_pattern.""" - pattern = self.additional_routes_pattern - if pattern: - return bool(pattern.match(path)) - - return False async def make_application(self) -> web.Application: """Get the aiohttp application instance.""" diff --git a/acapy_agent/admin/tests/test_auth.py b/acapy_agent/admin/tests/test_auth.py index 1ebd0cd171..73680a1b00 100644 --- a/acapy_agent/admin/tests/test_auth.py +++ b/acapy_agent/admin/tests/test_auth.py @@ -133,3 +133,27 @@ async def test_multi_tenant_valid_auth_header(self): decor_func = tenant_authentication(self.decorated_handler) await decor_func(self.request) self.decorated_handler.assert_called_once_with(self.request) + + async def test_base_wallet_additional_route_allowed(self): + self.profile.settings["multitenant.base_wallet_routes"] = "/extra-route" + self.request = mock.MagicMock( + __getitem__=lambda _, k: self.request_dict[k], + headers={"x-api-key": "admin_api_key"}, + method="POST", + path="/extra-route", + ) + decor_func = tenant_authentication(self.decorated_handler) + await decor_func(self.request) + self.decorated_handler.assert_called_once_with(self.request) + + async def test_base_wallet_additional_route_denied(self): + self.profile.settings["multitenant.base_wallet_routes"] = "/extra-route" + self.request = mock.MagicMock( + __getitem__=lambda _, k: self.request_dict[k], + headers={"x-api-key": "admin_api_key"}, + method="POST", + path="/extra-route-wrong", + ) + decor_func = tenant_authentication(self.decorated_handler) + with self.assertRaises(web.HTTPUnauthorized): + await decor_func(self.request) diff --git a/acapy_agent/anoncreds/base.py b/acapy_agent/anoncreds/base.py index 6cb5940bb2..32c0a1c1f1 100644 --- a/acapy_agent/anoncreds/base.py +++ b/acapy_agent/anoncreds/base.py @@ -6,8 +6,8 @@ from ..config.injection_context import InjectionContext from ..core.error import BaseError from ..core.profile import Profile -from .models.anoncreds_cred_def import CredDef, CredDefResult, GetCredDefResult -from .models.anoncreds_revocation import ( +from .models.credential_definition import CredDef, CredDefResult, GetCredDefResult +from .models.revocation import ( GetRevListResult, GetRevRegDefResult, RevList, @@ -15,7 +15,7 @@ RevRegDef, RevRegDefResult, ) -from .models.anoncreds_schema import AnonCredsSchema, GetSchemaResult, SchemaResult +from .models.schema import AnonCredsSchema, GetSchemaResult, SchemaResult T = TypeVar("T") diff --git a/acapy_agent/anoncreds/default/did_indy/registry.py b/acapy_agent/anoncreds/default/did_indy/registry.py index 5bef819f82..388dfe5022 100644 --- a/acapy_agent/anoncreds/default/did_indy/registry.py +++ b/acapy_agent/anoncreds/default/did_indy/registry.py @@ -7,8 +7,8 @@ from ....config.injection_context import InjectionContext from ....core.profile import Profile from ...base import BaseAnonCredsRegistrar, BaseAnonCredsResolver -from ...models.anoncreds_cred_def import CredDef, CredDefResult, GetCredDefResult -from ...models.anoncreds_revocation import ( +from ...models.credential_definition import CredDef, CredDefResult, GetCredDefResult +from ...models.revocation import ( GetRevListResult, GetRevRegDefResult, RevList, @@ -16,7 +16,7 @@ RevRegDef, RevRegDefResult, ) -from ...models.anoncreds_schema import AnonCredsSchema, GetSchemaResult, SchemaResult +from ...models.schema import AnonCredsSchema, GetSchemaResult, SchemaResult LOGGER = logging.getLogger(__name__) diff --git a/acapy_agent/anoncreds/default/did_web/registry.py b/acapy_agent/anoncreds/default/did_web/registry.py index 0585e2b924..710412aa22 100644 --- a/acapy_agent/anoncreds/default/did_web/registry.py +++ b/acapy_agent/anoncreds/default/did_web/registry.py @@ -7,8 +7,8 @@ from ....config.injection_context import InjectionContext from ....core.profile import Profile from ...base import BaseAnonCredsRegistrar, BaseAnonCredsResolver -from ...models.anoncreds_cred_def import CredDef, CredDefResult, GetCredDefResult -from ...models.anoncreds_revocation import ( +from ...models.credential_definition import CredDef, CredDefResult, GetCredDefResult +from ...models.revocation import ( GetRevListResult, GetRevRegDefResult, RevList, @@ -16,7 +16,9 @@ RevRegDef, RevRegDefResult, ) -from ...models.anoncreds_schema import AnonCredsSchema, GetSchemaResult, SchemaResult +from ...models.schema import AnonCredsSchema, GetSchemaResult, SchemaResult + +LOGGER = logging.getLogger(__name__) LOGGER = logging.getLogger(__name__) diff --git a/acapy_agent/anoncreds/default/legacy_indy/recover.py b/acapy_agent/anoncreds/default/legacy_indy/recover.py index cd183adf15..4c3eaf0517 100644 --- a/acapy_agent/anoncreds/default/legacy_indy/recover.py +++ b/acapy_agent/anoncreds/default/legacy_indy/recover.py @@ -9,7 +9,7 @@ import indy_vdr from anoncreds import RevocationRegistry, RevocationRegistryDefinition -from ...models.anoncreds_revocation import RevList +from ...models.revocation import RevList LOGGER = logging.getLogger(__name__) diff --git a/acapy_agent/anoncreds/default/legacy_indy/registry.py b/acapy_agent/anoncreds/default/legacy_indy/registry.py index 892a9d17fe..2f91937580 100644 --- a/acapy_agent/anoncreds/default/legacy_indy/registry.py +++ b/acapy_agent/anoncreds/default/legacy_indy/registry.py @@ -56,14 +56,14 @@ ) from ...events import RevListFinishedEvent from ...issuer import CATEGORY_CRED_DEF, AnonCredsIssuer, AnonCredsIssuerError -from ...models.anoncreds_cred_def import ( +from ...models.credential_definition import ( CredDef, CredDefResult, CredDefState, CredDefValue, GetCredDefResult, ) -from ...models.anoncreds_revocation import ( +from ...models.revocation import ( GetRevListResult, GetRevRegDefResult, RevList, @@ -74,7 +74,7 @@ RevRegDefState, RevRegDefValue, ) -from ...models.anoncreds_schema import ( +from ...models.schema import ( AnonCredsSchema, GetSchemaResult, SchemaResult, diff --git a/acapy_agent/anoncreds/default/legacy_indy/tests/test_recover.py b/acapy_agent/anoncreds/default/legacy_indy/tests/test_recover.py index 61c0a90f40..b504690c5e 100644 --- a/acapy_agent/anoncreds/default/legacy_indy/tests/test_recover.py +++ b/acapy_agent/anoncreds/default/legacy_indy/tests/test_recover.py @@ -9,9 +9,8 @@ import pytest from anoncreds import RevocationRegistryDefinition -from acapy_agent.tests import mock - -from ....models.anoncreds_revocation import RevList, RevRegDef, RevRegDefValue +from .....tests import mock +from ....models.revocation import RevList, RevRegDef, RevRegDefValue from ..recover import ( RevocRecoveryException, _check_tails_hash_for_inconsistency, diff --git a/acapy_agent/anoncreds/default/legacy_indy/tests/test_registry.py b/acapy_agent/anoncreds/default/legacy_indy/tests/test_registry.py index 2b6b6c0b76..c41eb80797 100644 --- a/acapy_agent/anoncreds/default/legacy_indy/tests/test_registry.py +++ b/acapy_agent/anoncreds/default/legacy_indy/tests/test_registry.py @@ -16,25 +16,6 @@ from .....anoncreds.base import AnonCredsSchemaAlreadyExists from .....anoncreds.default.legacy_indy import registry as test_module from .....anoncreds.issuer import AnonCredsIssuer -from .....anoncreds.models.anoncreds_cred_def import ( - CredDef, - CredDefResult, - CredDefValue, - CredDefValuePrimary, -) -from .....anoncreds.models.anoncreds_revocation import ( - RevList, - RevListResult, - RevRegDef, - RevRegDefResult, - RevRegDefState, - RevRegDefValue, -) -from .....anoncreds.models.anoncreds_schema import ( - AnonCredsSchema, - GetSchemaResult, - SchemaResult, -) from .....askar.profile_anon import ( AskarAnoncredsProfileSession, ) @@ -55,6 +36,21 @@ ) from .....tests import mock from .....utils.testing import create_test_profile +from ....models.credential_definition import ( + CredDef, + CredDefResult, + CredDefValue, + CredDefValuePrimary, +) +from ....models.revocation import ( + RevList, + RevListResult, + RevRegDef, + RevRegDefResult, + RevRegDefState, + RevRegDefValue, +) +from ....models.schema import AnonCredsSchema, GetSchemaResult, SchemaResult B58 = alphabet if isinstance(alphabet, str) else alphabet.decode("ascii") INDY_DID = rf"^(did:sov:)?[{B58}]{{21,22}}$" diff --git a/acapy_agent/anoncreds/events.py b/acapy_agent/anoncreds/events.py index 4511b546f7..98477bf4e1 100644 --- a/acapy_agent/anoncreds/events.py +++ b/acapy_agent/anoncreds/events.py @@ -4,7 +4,7 @@ from typing import NamedTuple, Optional from ..core.event_bus import Event -from .models.anoncreds_revocation import RevRegDef +from .models.revocation import RevRegDef CRED_DEF_FINISHED_EVENT = "anoncreds::credential-definition::finished" REV_REG_DEF_FINISHED_EVENT = "anoncreds::revocation-registry-definition::finished" diff --git a/acapy_agent/anoncreds/holder.py b/acapy_agent/anoncreds/holder.py index ae14e4be5d..6da4456c72 100644 --- a/acapy_agent/anoncreds/holder.py +++ b/acapy_agent/anoncreds/holder.py @@ -23,7 +23,6 @@ from pyld.jsonld import JsonLdProcessor from uuid_utils import uuid4 -from ..anoncreds.models.anoncreds_schema import AnonCredsSchema from ..askar.profile_anon import AskarAnoncredsProfile from ..core.error import BaseError from ..core.profile import Profile @@ -33,7 +32,8 @@ from ..vc.vc_ld import VerifiableCredential from ..wallet.error import WalletNotFoundError from .error_messages import ANONCREDS_PROFILE_REQUIRED_MSG -from .models.anoncreds_cred_def import CredDef +from .models.credential_definition import CredDef +from .models.schema import AnonCredsSchema from .registry import AnonCredsRegistry LOGGER = logging.getLogger(__name__) diff --git a/acapy_agent/anoncreds/issuer.py b/acapy_agent/anoncreds/issuer.py index ad79173e16..6dc3fababa 100644 --- a/acapy_agent/anoncreds/issuer.py +++ b/acapy_agent/anoncreds/issuer.py @@ -24,8 +24,8 @@ from .base import AnonCredsSchemaAlreadyExists, BaseAnonCredsError from .error_messages import ANONCREDS_PROFILE_REQUIRED_MSG from .events import CredDefFinishedEvent -from .models.anoncreds_cred_def import CredDef, CredDefResult -from .models.anoncreds_schema import AnonCredsSchema, SchemaResult, SchemaState +from .models.credential_definition import CredDef, CredDefResult +from .models.schema import AnonCredsSchema, SchemaResult, SchemaState from .registry import AnonCredsRegistry LOGGER = logging.getLogger(__name__) diff --git a/acapy_agent/anoncreds/models/credential.py b/acapy_agent/anoncreds/models/credential.py new file mode 100644 index 0000000000..c008fc16c7 --- /dev/null +++ b/acapy_agent/anoncreds/models/credential.py @@ -0,0 +1,167 @@ +"""Credential artifacts.""" + +from typing import Mapping, Optional + +from marshmallow import EXCLUDE, ValidationError, fields + +from ...messaging.models.base import BaseModel, BaseModelSchema +from ...messaging.valid import ( + ANONCREDS_CRED_DEF_ID_EXAMPLE, + ANONCREDS_CRED_DEF_ID_VALIDATE, + ANONCREDS_REV_REG_ID_EXAMPLE, + ANONCREDS_REV_REG_ID_VALIDATE, + ANONCREDS_SCHEMA_ID_EXAMPLE, + ANONCREDS_SCHEMA_ID_VALIDATE, + NUM_STR_ANY_EXAMPLE, + NUM_STR_ANY_VALIDATE, +) + + +class AnoncredsAttrValue(BaseModel): + """Anoncreds attribute value.""" + + class Meta: + """Anoncreds attribute value.""" + + schema_class = "AnoncredsAttrValueSchema" + + def __init__( + self, raw: Optional[str] = None, encoded: Optional[str] = None, **kwargs + ): + """Initialize anoncreds (credential) attribute value.""" + super().__init__(**kwargs) + self.raw = raw + self.encoded = encoded + + +class AnoncredsAttrValueSchema(BaseModelSchema): + """Anoncreds attribute value schema.""" + + class Meta: + """Anoncreds attribute value schema metadata.""" + + model_class = AnoncredsAttrValue + unknown = EXCLUDE + + raw = fields.Str(required=True, metadata={"description": "Attribute raw value"}) + encoded = fields.Str( + required=True, + validate=NUM_STR_ANY_VALIDATE, + metadata={ + "description": "Attribute encoded value", + "example": NUM_STR_ANY_EXAMPLE, + }, + ) + + +class DictWithAnoncredsAttrValueSchema(fields.Dict): + """Dict with anoncreds attribute value schema.""" + + def __init__(self, **kwargs): + """Initialize the custom schema for a dictionary with AnoncredsAttrValue.""" + super().__init__( + keys=fields.Str(metadata={"description": "Attribute name"}), + values=fields.Nested(AnoncredsAttrValueSchema()), + **kwargs, + ) + + def _deserialize(self, value, attr, data, **kwargs): + """Deserialize dict with anoncreds attribute value.""" + if not isinstance(value, dict): + raise ValidationError("Value must be a dict.") + + errors = {} + anoncreds_attr_value_schema = AnoncredsAttrValueSchema() + + for k, v in value.items(): + if isinstance(v, dict): + validation_errors = anoncreds_attr_value_schema.validate(v) + if validation_errors: + errors[k] = validation_errors + + if errors: + raise ValidationError(errors) + + return value + + +class AnoncredsCredential(BaseModel): + """Anoncreds credential.""" + + class Meta: + """Anoncreds credential metadata.""" + + schema_class = "AnoncredsCredentialSchema" + + def __init__( + self, + schema_id: Optional[str] = None, + cred_def_id: Optional[str] = None, + rev_reg_id: Optional[str] = None, + values: Mapping[str, AnoncredsAttrValue] = None, + signature: Optional[Mapping] = None, + signature_correctness_proof: Optional[Mapping] = None, + rev_reg: Optional[Mapping] = None, + witness: Optional[Mapping] = None, + ): + """Initialize anoncreds credential.""" + self.schema_id = schema_id + self.cred_def_id = cred_def_id + self.rev_reg_id = rev_reg_id + self.values = values + self.signature = signature + self.signature_correctness_proof = signature_correctness_proof + self.rev_reg = rev_reg + self.witness = witness + + +class AnoncredsCredentialSchema(BaseModelSchema): + """Anoncreds credential schema.""" + + class Meta: + """Anoncreds credential schemametadata.""" + + model_class = AnoncredsCredential + unknown = EXCLUDE + + schema_id = fields.Str( + required=True, + validate=ANONCREDS_SCHEMA_ID_VALIDATE, + metadata={ + "description": "Schema identifier", + "example": ANONCREDS_SCHEMA_ID_EXAMPLE, + }, + ) + cred_def_id = fields.Str( + required=True, + validate=ANONCREDS_CRED_DEF_ID_VALIDATE, + metadata={ + "description": "Credential definition identifier", + "example": ANONCREDS_CRED_DEF_ID_EXAMPLE, + }, + ) + rev_reg_id = fields.Str( + allow_none=True, + validate=ANONCREDS_REV_REG_ID_VALIDATE, + metadata={ + "description": "Revocation registry identifier", + "example": ANONCREDS_REV_REG_ID_EXAMPLE, + }, + ) + values = DictWithAnoncredsAttrValueSchema( + required=True, + metadata={"description": "Credential attributes"}, + ) + signature = fields.Dict( + required=True, metadata={"description": "Credential signature"} + ) + signature_correctness_proof = fields.Dict( + required=True, + metadata={"description": "Credential signature correctness proof"}, + ) + rev_reg = fields.Dict( + allow_none=True, metadata={"description": "Revocation registry state"} + ) + witness = fields.Dict( + allow_none=True, metadata={"description": "Witness for revocation proof"} + ) diff --git a/acapy_agent/anoncreds/models/anoncreds_cred_def.py b/acapy_agent/anoncreds/models/credential_definition.py similarity index 97% rename from acapy_agent/anoncreds/models/anoncreds_cred_def.py rename to acapy_agent/anoncreds/models/credential_definition.py index 17d41098e7..4e543eaf57 100644 --- a/acapy_agent/anoncreds/models/anoncreds_cred_def.py +++ b/acapy_agent/anoncreds/models/credential_definition.py @@ -9,9 +9,9 @@ from ...messaging.models.base import BaseModel, BaseModelSchema from ...messaging.valid import ( - INDY_CRED_DEF_ID_EXAMPLE, - INDY_OR_KEY_DID_EXAMPLE, - INDY_SCHEMA_ID_EXAMPLE, + ANONCREDS_CRED_DEF_ID_EXAMPLE, + ANONCREDS_DID_EXAMPLE, + ANONCREDS_SCHEMA_ID_EXAMPLE, NUM_STR_WHOLE_EXAMPLE, NUM_STR_WHOLE_VALIDATE, ) @@ -256,7 +256,7 @@ class Meta: issuer_id = fields.Str( metadata={ "description": "Issuer Identifier of the credential definition or schema", - "example": INDY_OR_KEY_DID_EXAMPLE, + "example": ANONCREDS_DID_EXAMPLE, }, data_key="issuerId", ) @@ -264,7 +264,7 @@ class Meta: data_key="schemaId", metadata={ "description": "Schema identifier", - "example": INDY_SCHEMA_ID_EXAMPLE, + "example": ANONCREDS_SCHEMA_ID_EXAMPLE, }, ) type = fields.Str(validate=OneOf(["CL"])) @@ -333,7 +333,7 @@ class Meta: credential_definition_id = fields.Str( metadata={ "description": "credential definition id", - "example": INDY_CRED_DEF_ID_EXAMPLE, + "example": ANONCREDS_CRED_DEF_ID_EXAMPLE, }, allow_none=True, ) @@ -434,7 +434,7 @@ class Meta: credential_definition_id = fields.Str( metadata={ "description": "credential definition id", - "example": INDY_CRED_DEF_ID_EXAMPLE, + "example": ANONCREDS_CRED_DEF_ID_EXAMPLE, }, ) credential_definition = fields.Nested( diff --git a/acapy_agent/anoncreds/models/credential_offer.py b/acapy_agent/anoncreds/models/credential_offer.py new file mode 100644 index 0000000000..c77419eba3 --- /dev/null +++ b/acapy_agent/anoncreds/models/credential_offer.py @@ -0,0 +1,149 @@ +"""Anoncreds Credential Offer format for v2.0 of the issue-credential protocol.""" + +from typing import Optional, Sequence + +from marshmallow import EXCLUDE, fields + +from ...messaging.models.base import BaseModel, BaseModelSchema +from ...messaging.valid import ( + ANONCREDS_CRED_DEF_ID_EXAMPLE, + ANONCREDS_CRED_DEF_ID_VALIDATE, + ANONCREDS_SCHEMA_ID_EXAMPLE, + ANONCREDS_SCHEMA_ID_VALIDATE, + NUM_STR_WHOLE_EXAMPLE, + NUM_STR_WHOLE_VALIDATE, +) + + +class AnoncredsKeyCorrectnessProof(BaseModel): + """Anoncreds key correctness proof.""" + + class Meta: + """AnoncredsKeyCorrectnessProof metadata.""" + + schema_class = "AnoncredsKeyCorrectnessProofSchema" + + def __init__( + self, + c: Optional[str] = None, + xz_cap: Optional[str] = None, + xr_cap: Sequence[Sequence[str]] = None, + **kwargs, + ): + """Initialize XR cap for anoncreds key correctness proof.""" + super().__init__(**kwargs) + + self.c = c + self.xz_cap = xz_cap + self.xr_cap = xr_cap + + +class AnoncredsCorrectnessProofSchema(BaseModelSchema): + """Anoncreds key correctness proof schema.""" + + class Meta: + """Anoncreds key correctness proof schema metadata.""" + + model_class = AnoncredsKeyCorrectnessProof + unknown = EXCLUDE + + c = fields.Str( + required=True, + validate=NUM_STR_WHOLE_VALIDATE, + metadata={ + "description": "c in key correctness proof", + "example": NUM_STR_WHOLE_EXAMPLE, + }, + ) + xz_cap = fields.Str( + required=True, + validate=NUM_STR_WHOLE_VALIDATE, + metadata={ + "description": "xz_cap in key correctness proof", + "example": NUM_STR_WHOLE_EXAMPLE, + }, + ) + xr_cap = fields.List( + fields.List( + fields.Str( + required=True, + metadata={ + "description": "xr_cap component values in key correctness proof" + }, + ), + required=True, + metadata={ + "description": "xr_cap components in key correctness proof", + "many": True, + }, + ), + required=True, + metadata={"description": "xr_cap in key correctness proof", "many": True}, + ) + + +class AnoncredsCredentialOffer(BaseModel): + """Anoncreds Credential Offer.""" + + class Meta: + """AnoncredsCredentialOffer metadata.""" + + schema_class = "AnoncredsCredentialOfferSchema" + + def __init__( + self, + schema_id: Optional[str] = None, + cred_def_id: Optional[str] = None, + nonce: Optional[str] = None, + key_correctness_proof: Optional[str] = None, + **kwargs, + ): + """Initialize values .""" + super().__init__(**kwargs) + self.schema_id = schema_id + self.cred_def_id = cred_def_id + self.nonce = nonce + self.key_correctness_proof = key_correctness_proof + + +class AnoncredsCredentialOfferSchema(BaseModelSchema): + """Anoncreds Credential Offer Schema.""" + + class Meta: + """AnoncredsCredentialOffer schema metadata.""" + + model_class = AnoncredsCredentialOffer + unknown = EXCLUDE + + schema_id = fields.Str( + required=True, + validate=ANONCREDS_SCHEMA_ID_VALIDATE, + metadata={ + "description": "Schema identifier", + "example": ANONCREDS_SCHEMA_ID_EXAMPLE, + }, + ) + + cred_def_id = fields.Str( + required=True, + validate=ANONCREDS_CRED_DEF_ID_VALIDATE, + metadata={ + "description": "Credential definition identifier", + "example": ANONCREDS_CRED_DEF_ID_EXAMPLE, + }, + ) + + nonce = fields.Str( + required=True, + validate=NUM_STR_WHOLE_VALIDATE, + metadata={ + "description": "Nonce in credential abstract", + "example": NUM_STR_WHOLE_EXAMPLE, + }, + ) + + key_correctness_proof = fields.Nested( + AnoncredsCorrectnessProofSchema(), + required=True, + metadata={"description": "Key correctness proof"}, + ) diff --git a/acapy_agent/anoncreds/models/credential_proposal.py b/acapy_agent/anoncreds/models/credential_proposal.py new file mode 100644 index 0000000000..3d41d08747 --- /dev/null +++ b/acapy_agent/anoncreds/models/credential_proposal.py @@ -0,0 +1,58 @@ +"""Anoncreds credential definition proposal.""" + +import re + +from marshmallow import fields + +from ...core.profile import Profile +from ...messaging.models.openapi import OpenAPISchema +from ...messaging.valid import ( + ANONCREDS_CRED_DEF_ID_EXAMPLE, + ANONCREDS_CRED_DEF_ID_VALIDATE, + ANONCREDS_DID_EXAMPLE, + ANONCREDS_DID_VALIDATE, + ANONCREDS_SCHEMA_ID_EXAMPLE, + ANONCREDS_SCHEMA_ID_VALIDATE, +) + + +class AnoncredsCredentialDefinitionProposal(OpenAPISchema): + """Query string parameters for credential definition searches.""" + + schema_id = fields.Str( + required=False, + validate=ANONCREDS_SCHEMA_ID_VALIDATE, + metadata={ + "description": "Schema identifier", + "example": ANONCREDS_SCHEMA_ID_EXAMPLE, + }, + ) + issuer_id = fields.Str( + required=False, + validate=ANONCREDS_DID_VALIDATE, + metadata={"description": "Issuer DID", "example": ANONCREDS_DID_EXAMPLE}, + ) + cred_def_id = fields.Str( + required=False, + validate=ANONCREDS_CRED_DEF_ID_VALIDATE, + metadata={ + "description": "Credential definition id", + "example": ANONCREDS_CRED_DEF_ID_EXAMPLE, + }, + ) + + +CRED_DEF_TAGS = list( + vars(AnoncredsCredentialDefinitionProposal).get("_declared_fields", []) +) + +CRED_DEF_EVENT_PREFIX = "acapy::CRED_DEF::" +EVENT_LISTENER_PATTERN = re.compile(f"^{CRED_DEF_EVENT_PREFIX}(.*)?$") + + +async def notify_cred_def_event(profile: Profile, cred_def_id: str, meta_data: dict): + """Send notification for a cred def post-process event.""" + await profile.notify( + CRED_DEF_EVENT_PREFIX + cred_def_id, + meta_data, + ) diff --git a/acapy_agent/anoncreds/models/credential_request.py b/acapy_agent/anoncreds/models/credential_request.py new file mode 100644 index 0000000000..49fd58e996 --- /dev/null +++ b/acapy_agent/anoncreds/models/credential_request.py @@ -0,0 +1,81 @@ +"""Cred request artifacts to attach to RFC 453 messages.""" + +from typing import Mapping, Optional + +from marshmallow import EXCLUDE, fields + +from ...messaging.models.base import BaseModel, BaseModelSchema +from ...messaging.valid import ( + ANONCREDS_CRED_DEF_ID_EXAMPLE, + ANONCREDS_CRED_DEF_ID_VALIDATE, + NUM_STR_WHOLE_EXAMPLE, + NUM_STR_WHOLE_VALIDATE, + UUID4_EXAMPLE, +) + + +class AnoncredsCredRequest(BaseModel): + """Anoncreds credential request.""" + + class Meta: + """Anoncreds credential request metadata.""" + + schema_class = "AnoncredsCredRequestSchema" + + def __init__( + self, + prover_did: Optional[str] = None, + cred_def_id: Optional[str] = None, + blinded_ms: Optional[Mapping] = None, + blinded_ms_correctness_proof: Optional[Mapping] = None, + nonce: Optional[str] = None, + **kwargs, + ): + """Initialize anoncreds credential request.""" + super().__init__(**kwargs) + self.prover_did = prover_did + self.cred_def_id = cred_def_id + self.blinded_ms = blinded_ms + self.blinded_ms_correctness_proof = blinded_ms_correctness_proof + self.nonce = nonce + + +class AnoncredsCredRequestSchema(BaseModelSchema): + """Anoncreds credential request schema.""" + + class Meta: + """Anoncreds credential request schema metadata.""" + + model_class = AnoncredsCredRequest + unknown = EXCLUDE + + prover_did = fields.Str( + required=True, + metadata={ + "description": "Prover DID/Random String/UUID", + "example": UUID4_EXAMPLE, + }, + ) + cred_def_id = fields.Str( + required=True, + validate=ANONCREDS_CRED_DEF_ID_VALIDATE, + metadata={ + "description": "Credential definition identifier", + "example": ANONCREDS_CRED_DEF_ID_EXAMPLE, + }, + ) + blinded_ms = fields.Dict( + required=True, metadata={"description": "Blinded master secret"} + ) + blinded_ms_correctness_proof = fields.Dict( + required=True, + metadata={"description": "Blinded master secret correctness proof"}, + ) + nonce = fields.Str( + required=True, + validate=NUM_STR_WHOLE_VALIDATE, + metadata={ + "description": "Nonce in credential request", + "example": NUM_STR_WHOLE_EXAMPLE, + }, + ) diff --git a/acapy_agent/anoncreds/models/non_rev_interval.py b/acapy_agent/anoncreds/models/non_rev_interval.py new file mode 100644 index 0000000000..a224891189 --- /dev/null +++ b/acapy_agent/anoncreds/models/non_rev_interval.py @@ -0,0 +1,78 @@ +"""Anoncreds non-revocation interval.""" + +from time import time +from typing import Optional + +from marshmallow import EXCLUDE, fields + +from ...messaging.models.base import BaseModel, BaseModelSchema +from ...messaging.valid import INT_EPOCH_EXAMPLE, INT_EPOCH_VALIDATE + + +class AnoncredsNonRevocationInterval(BaseModel): + """Anoncreds non-revocation interval.""" + + class Meta: + """NonRevocationInterval metadata.""" + + schema_class = "AnoncredsNonRevocationIntervalSchema" + + def __init__(self, fro: Optional[int] = None, to: Optional[int] = None, **kwargs): + """Initialize non-revocation interval. + + Args: + fro: earliest time of interest + to: latest time of interest + kwargs: additional attributes + + """ + super().__init__(**kwargs) + self.fro = fro + self.to = to + + def covers(self, timestamp: Optional[int] = None) -> bool: + """Whether input timestamp (default now) lies within non-revocation interval. + + Args: + timestamp: time of interest + + Returns: + whether input time satisfies non-revocation interval + + """ + timestamp = timestamp or int(time()) + return (self.fro or 0) <= timestamp <= (self.to or timestamp) + + def timestamp(self) -> bool: + """Return a timestamp that the non-revocation interval covers.""" + return self.to or self.fro or int(time()) + + +class AnoncredsNonRevocationIntervalSchema(BaseModelSchema): + """Schema to allow serialization/deserialization of non-revocation intervals.""" + + class Meta: + """AnoncredsNonRevocationIntervalSchema metadata.""" + + model_class = AnoncredsNonRevocationInterval + unknown = EXCLUDE + + fro = fields.Int( + required=False, + data_key="from", + validate=INT_EPOCH_VALIDATE, + metadata={ + "description": "Earliest time of interest in non-revocation interval", + "strict": True, + "example": INT_EPOCH_EXAMPLE, + }, + ) + to = fields.Int( + required=False, + validate=INT_EPOCH_VALIDATE, + metadata={ + "description": "Latest time of interest in non-revocation interval", + "strict": True, + "example": INT_EPOCH_EXAMPLE, + }, + ) diff --git a/acapy_agent/anoncreds/models/predicate.py b/acapy_agent/anoncreds/models/predicate.py new file mode 100644 index 0000000000..9810a7bb78 --- /dev/null +++ b/acapy_agent/anoncreds/models/predicate.py @@ -0,0 +1,82 @@ +"""Utilities for dealing with predicates.""" + +from collections import namedtuple +from enum import Enum +from typing import Any + +Relation = namedtuple("Relation", "fortran wql math yes no") + + +class Predicate(Enum): + """Enum for predicate types that anoncreds-sdk supports.""" + + LT = Relation( + "LT", + "$lt", + "<", + lambda x, y: Predicate.to_int(x) < Predicate.to_int(y), + lambda x, y: Predicate.to_int(x) >= Predicate.to_int(y), + ) + LE = Relation( + "LE", + "$lte", + "<=", + lambda x, y: Predicate.to_int(x) <= Predicate.to_int(y), + lambda x, y: Predicate.to_int(x) > Predicate.to_int(y), + ) + GE = Relation( + "GE", + "$gte", + ">=", + lambda x, y: Predicate.to_int(x) >= Predicate.to_int(y), + lambda x, y: Predicate.to_int(x) < Predicate.to_int(y), + ) + GT = Relation( + "GT", + "$gt", + ">", + lambda x, y: Predicate.to_int(x) > Predicate.to_int(y), + lambda x, y: Predicate.to_int(x) <= Predicate.to_int(y), + ) + + @property + def fortran(self) -> str: + """Fortran nomenclature.""" + return self.value.fortran + + @property + def wql(self) -> str: + """WQL nomenclature.""" + return self.value.wql + + @property + def math(self) -> str: + """Mathematical nomenclature.""" + return self.value.math + + @staticmethod + def get(relation: str) -> "Predicate": + """Return enum instance corresponding to input relation string.""" + + for pred in Predicate: + if relation.upper() in ( + pred.value.fortran, + pred.value.wql.upper(), + pred.value.math, + ): + return pred + return None + + @staticmethod + def to_int(value: Any) -> int: + """Cast a value as its equivalent int for anoncreds predicate argument. + + Raise ValueError for any input but int, stringified int, or boolean. + + Args: + value: value to coerce + """ + + if isinstance(value, (bool, int)): + return int(value) + return int(str(value)) # kick out floats diff --git a/acapy_agent/anoncreds/models/presentation_request.py b/acapy_agent/anoncreds/models/presentation_request.py new file mode 100644 index 0000000000..05881dfdc8 --- /dev/null +++ b/acapy_agent/anoncreds/models/presentation_request.py @@ -0,0 +1,306 @@ +"""Classes to represent anoncreds presentation request.""" + +from typing import Mapping, Optional + +from marshmallow import ( + EXCLUDE, + Schema, + ValidationError, + fields, + validate, + validates_schema, +) + +from ...messaging.models.base import BaseModel, BaseModelSchema +from ...messaging.models.openapi import OpenAPISchema +from ...messaging.valid import ( + ANONCREDS_CRED_DEF_ID_EXAMPLE, + INT_EPOCH_EXAMPLE, + INT_EPOCH_VALIDATE, + MAJOR_MINOR_VERSION_EXAMPLE, + MAJOR_MINOR_VERSION_VALIDATE, + NUM_STR_NATURAL_EXAMPLE, + NUM_STR_NATURAL_VALIDATE, + PREDICATE_EXAMPLE, + PREDICATE_VALIDATE, +) + + +class AnoncredsPresentationReqPredSpecSchema(OpenAPISchema): + """Schema for predicate specification in anoncreds proof request.""" + + name = fields.Str( + required=True, metadata={"example": "index", "description": "Attribute name"} + ) + p_type = fields.Str( + required=True, + validate=PREDICATE_VALIDATE, + metadata={ + "description": "Predicate type ('<', '<=', '>=', or '>')", + "example": PREDICATE_EXAMPLE, + }, + ) + p_value = fields.Int( + required=True, metadata={"description": "Threshold value", "strict": True} + ) + restrictions = fields.List( + fields.Dict( + keys=fields.Str( + validate=validate.Regexp( + "^schema_id|schema_issuer_did|schema_name|schema_version|issuer_did|" + "cred_def_id|attr::.+::value$" + ), + metadata={"example": "cred_def_id"}, + ), + values=fields.Str(metadata={"example": ANONCREDS_CRED_DEF_ID_EXAMPLE}), + ), + required=False, + metadata={ + "description": ( + "If present, credential must satisfy one of given restrictions: specify" + " schema_id, schema_issuer_did, schema_name, schema_version," + " issuer_did, cred_def_id, and/or attr::::value where" + " represents a credential attribute name" + ) + }, + ) + non_revoked = fields.Nested( + Schema.from_dict( + { + "from": fields.Int( + required=False, + validate=INT_EPOCH_VALIDATE, + metadata={ + "description": ( + "Earliest time of interest in non-revocation interval" + ), + "strict": True, + "example": INT_EPOCH_EXAMPLE, + }, + ), + "to": fields.Int( + required=False, + validate=INT_EPOCH_VALIDATE, + metadata={ + "description": ( + "Latest time of interest in non-revocation interval" + ), + "strict": True, + "example": INT_EPOCH_EXAMPLE, + }, + ), + }, + name="AnoncredsPresentationReqPredSpecNonRevokedSchema", + ), + allow_none=True, + required=False, + ) + + +class AnoncredsPresentationReqAttrSpecSchema(OpenAPISchema): + """Schema for attribute specification in anoncreds proof request.""" + + name = fields.Str( + required=False, + metadata={"example": "favouriteDrink", "description": "Attribute name"}, + ) + names = fields.List( + fields.Str(metadata={"example": "age"}), + required=False, + metadata={"description": "Attribute name group"}, + ) + restrictions = fields.List( + fields.Dict( + keys=fields.Str( + validate=validate.Regexp( + "^schema_id|schema_issuer_did|schema_name|schema_version|issuer_did|" + "cred_def_id|attr::.+::value$" + ), + metadata={"example": "cred_def_id"}, + ), + values=fields.Str(metadata={"example": ANONCREDS_CRED_DEF_ID_EXAMPLE}), + ), + required=False, + metadata={ + "description": ( + "If present, credential must satisfy one of given restrictions: specify" + " schema_id, schema_issuer_did, schema_name, schema_version," + " issuer_did, cred_def_id, and/or attr::::value where" + " represents a credential attribute name" + ) + }, + ) + non_revoked = fields.Nested( + Schema.from_dict( + { + "from": fields.Int( + required=False, + validate=INT_EPOCH_VALIDATE, + metadata={ + "description": ( + "Earliest time of interest in non-revocation interval" + ), + "strict": True, + "example": INT_EPOCH_EXAMPLE, + }, + ), + "to": fields.Int( + required=False, + validate=INT_EPOCH_VALIDATE, + metadata={ + "description": ( + "Latest time of interest in non-revocation interval" + ), + "strict": True, + "example": INT_EPOCH_EXAMPLE, + }, + ), + }, + name="AnoncredsPresentationReqAttrSpecNonRevokedSchema", + ), + allow_none=True, + required=False, + ) + + @validates_schema + def validate_fields(self, data, **kwargs): + """Validate schema fields. + + Data must have exactly one of name or names; if names then restrictions are + mandatory. + + Args: + data: The data to validate + kwargs: Additional keyword arguments + + Raises: + ValidationError: if data has both or neither of name and names + + """ + if ("name" in data) == ("names" in data): + raise ValidationError( + "Attribute specification must have either name or names but not both" + ) + restrictions = data.get("restrictions") + if ("names" in data) and (not restrictions or all(not r for r in restrictions)): + raise ValidationError( + "Attribute specification on 'names' must have non-empty restrictions" + ) + + +class AnoncredsPresentationRequest(BaseModel): + """anoncreds proof request.""" + + class Meta: + """Anoncreds proof request metadata.""" + + schema_class = "AnoncredsPresentationRequestSchema" + + def __init__( + self, + nonce: Optional[str] = None, + name: Optional[str] = None, + version: Optional[str] = None, + requested_attributes: Optional[Mapping] = None, + requested_predicates: Optional[Mapping] = None, + non_revoked: Optional[Mapping] = None, + **kwargs, + ): + """Initialize anoncreds cred abstract object. + + Args: + nonce (str): The nonce value. + name (str): The name of the proof request. + version (str): The version of the proof request. + requested_attributes (Mapping): A mapping of attribute names to attribute + constraints. + requested_predicates (Mapping): A mapping of predicate names to predicate + constraints. + non_revoked (Mapping): A mapping of non-revocation timestamps. + kwargs: Keyword arguments for BaseModel + + """ + super().__init__(**kwargs) + self.nonce = nonce + self.name = name + self.version = version + self.requested_attributes = requested_attributes + self.requested_predicates = requested_predicates + self.non_revoked = non_revoked + + +class AnoncredsPresentationRequestSchema(BaseModelSchema): + """Schema for anoncreds proof request.""" + + class Meta: + """Anoncreds proof request schema metadata.""" + + model_class = AnoncredsPresentationRequest + unknown = EXCLUDE + + nonce = fields.Str( + required=False, + validate=NUM_STR_NATURAL_VALIDATE, + metadata={"description": "Nonce", "example": NUM_STR_NATURAL_EXAMPLE}, + ) + name = fields.Str( + required=False, + dump_default="Proof request", + metadata={"description": "Proof request name", "example": "Proof request"}, + ) + version = fields.Str( + required=False, + dump_default="1.0", + validate=MAJOR_MINOR_VERSION_VALIDATE, + metadata={ + "description": "Proof request version", + "example": MAJOR_MINOR_VERSION_EXAMPLE, + }, + ) + requested_attributes = fields.Dict( + required=True, + keys=fields.Str( + metadata={"decription": "Attribute referent", "example": "0_legalname_uuid"} + ), + values=fields.Nested(AnoncredsPresentationReqAttrSpecSchema()), + metadata={"description": "Requested attribute specifications of proof request"}, + ) + requested_predicates = fields.Dict( + required=True, + keys=fields.Str( + metadata={"description": "Predicate referent", "example": "0_age_GE_uuid"} + ), + values=fields.Nested(AnoncredsPresentationReqPredSpecSchema()), + metadata={"description": "Requested predicate specifications of proof request"}, + ) + non_revoked = fields.Nested( + Schema.from_dict( + { + "from": fields.Int( + required=False, + validate=INT_EPOCH_VALIDATE, + metadata={ + "description": ( + "Earliest time of interest in non-revocation interval" + ), + "strict": True, + "example": INT_EPOCH_EXAMPLE, + }, + ), + "to": fields.Int( + required=False, + validate=INT_EPOCH_VALIDATE, + metadata={ + "description": ( + "Latest time of interest in non-revocation interval" + ), + "strict": True, + "example": INT_EPOCH_EXAMPLE, + }, + ), + }, + name="AnoncredPresentationRequestNonRevokedSchema", + ), + allow_none=True, + required=False, + ) diff --git a/acapy_agent/anoncreds/models/proof.py b/acapy_agent/anoncreds/models/proof.py new file mode 100644 index 0000000000..817b947cc5 --- /dev/null +++ b/acapy_agent/anoncreds/models/proof.py @@ -0,0 +1,749 @@ +"""Marshmallow bindings for anoncreds proofs.""" + +from typing import Mapping, Optional, Sequence + +from marshmallow import EXCLUDE, fields, validate + +from ...messaging.models.base import BaseModel, BaseModelSchema +from ...messaging.valid import ( + ANONCREDS_CRED_DEF_ID_EXAMPLE, + ANONCREDS_CRED_DEF_ID_VALIDATE, + ANONCREDS_REV_REG_ID_EXAMPLE, + ANONCREDS_REV_REG_ID_VALIDATE, + ANONCREDS_SCHEMA_ID_EXAMPLE, + ANONCREDS_SCHEMA_ID_VALIDATE, + INT_EPOCH_EXAMPLE, + INT_EPOCH_VALIDATE, + NUM_STR_ANY_EXAMPLE, + NUM_STR_ANY_VALIDATE, + NUM_STR_WHOLE_EXAMPLE, + NUM_STR_WHOLE_VALIDATE, +) +from ...utils.tracing import AdminAPIMessageTracingSchema +from .predicate import Predicate +from .requested_credentials import ( + AnoncredsRequestedCredsRequestedAttrSchema, + AnoncredsRequestedCredsRequestedPredSchema, +) + + +class AnoncredsEQProof(BaseModel): + """Equality proof for anoncreds primary proof.""" + + class Meta: + """Equality proof metadata.""" + + schema_class = "AnoncredsEQProofMeta" + + def __init__( + self, + revealed_attrs: Mapping[str, str] = None, + a_prime: Optional[str] = None, + e: Optional[str] = None, + v: Optional[str] = None, + m: Mapping[str, str] = None, + m2: Optional[str] = None, + **kwargs, + ): + """Initialize equality proof object.""" + super().__init__(**kwargs) + self.revealed_attrs = revealed_attrs + self.a_prime = a_prime + self.e = e + self.v = v + self.m = m + self.m2 = m2 + + +class AnoncredsEQProofSchema(BaseModelSchema): + """Anoncreds equality proof schema.""" + + class Meta: + """Anoncreds equality proof metadata.""" + + model_class = AnoncredsEQProof + unknown = EXCLUDE + + revealed_attrs = fields.Dict( + keys=fields.Str(metadata={"example": "preference"}), + values=fields.Str( + validate=NUM_STR_ANY_VALIDATE, metadata={"example": NUM_STR_ANY_EXAMPLE} + ), + ) + a_prime = fields.Str( + validate=NUM_STR_WHOLE_VALIDATE, metadata={"example": NUM_STR_WHOLE_EXAMPLE} + ) + e = fields.Str( + validate=NUM_STR_WHOLE_VALIDATE, metadata={"example": NUM_STR_WHOLE_EXAMPLE} + ) + v = fields.Str( + validate=NUM_STR_WHOLE_VALIDATE, metadata={"example": NUM_STR_WHOLE_EXAMPLE} + ) + m = fields.Dict( + keys=fields.Str(metadata={"example": "master_secret"}), + values=fields.Str( + validate=NUM_STR_WHOLE_VALIDATE, metadata={"example": NUM_STR_WHOLE_EXAMPLE} + ), + ) + m2 = fields.Str( + validate=NUM_STR_WHOLE_VALIDATE, metadata={"example": NUM_STR_WHOLE_EXAMPLE} + ) + + +class AnoncredsGEProofPred(BaseModel): + """Anoncreds GE proof predicate.""" + + class Meta: + """Anoncreds GE proof predicate metadata.""" + + schema_class = "AnoncredsGEProofPredSchema" + + def __init__( + self, + attr_name: Optional[str] = None, + p_type: Optional[str] = None, + value: Optional[int] = None, + **kwargs, + ): + """Initialize anoncreds GE proof predicate.""" + super().__init__(**kwargs) + self.attr_name = attr_name + self.p_type = p_type + self.value = value + + +class AnoncredsGEProofPredSchema(BaseModelSchema): + """Anoncreds GE proof predicate schema.""" + + class Meta: + """Anoncreds GE proof predicate metadata.""" + + model_class = AnoncredsGEProofPred + unknown = EXCLUDE + + attr_name = fields.Str( + metadata={"description": "Attribute name, anoncreds-canonicalized"} + ) + p_type = fields.Str( + validate=validate.OneOf([p.fortran for p in Predicate]), + metadata={"description": "Predicate type"}, + ) + value = fields.Integer( + metadata={"strict": True, "description": "Predicate threshold value"} + ) + + +class AnoncredsGEProof(BaseModel): + """Greater-than-or-equal-to proof for anoncreds primary proof.""" + + class Meta: + """GE proof metadata.""" + + schema_class = "AnoncredsGEProofMeta" + + def __init__( + self, + u: Mapping[str, str] = None, + r: Mapping[str, str] = None, + mj: Optional[str] = None, + alpha: Optional[str] = None, + t: Mapping[str, str] = None, + predicate: Optional[AnoncredsGEProofPred] = None, + **kwargs, + ): + """Initialize GE proof object.""" + super().__init__(**kwargs) + self.u = u + self.r = r + self.mj = mj + self.alpha = alpha + self.t = t + self.predicate = predicate + + +class AnoncredsGEProofSchema(BaseModelSchema): + """Anoncreds GE proof schema.""" + + class Meta: + """Anoncreds GE proof schema metadata.""" + + model_class = AnoncredsGEProof + unknown = EXCLUDE + + u = fields.Dict( + keys=fields.Str(), + values=fields.Str( + validate=NUM_STR_WHOLE_VALIDATE, metadata={"example": NUM_STR_WHOLE_EXAMPLE} + ), + ) + r = fields.Dict( + keys=fields.Str(), + values=fields.Str( + validate=NUM_STR_WHOLE_VALIDATE, metadata={"example": NUM_STR_WHOLE_EXAMPLE} + ), + ) + mj = fields.Str( + validate=NUM_STR_WHOLE_VALIDATE, metadata={"example": NUM_STR_WHOLE_EXAMPLE} + ) + alpha = fields.Str( + validate=NUM_STR_WHOLE_VALIDATE, metadata={"example": NUM_STR_WHOLE_EXAMPLE} + ) + t = fields.Dict( + keys=fields.Str(), + values=fields.Str( + validate=NUM_STR_WHOLE_VALIDATE, metadata={"example": NUM_STR_WHOLE_EXAMPLE} + ), + ) + predicate = fields.Nested(AnoncredsGEProofPredSchema) + + +class AnoncredsPrimaryProof(BaseModel): + """Anoncreds primary proof.""" + + class Meta: + """Anoncreds primary proof metadata.""" + + schema_class = "AnoncredsPrimaryProofSchema" + + def __init__( + self, + eq_proof: Optional[AnoncredsEQProof] = None, + ge_proofs: Sequence[AnoncredsGEProof] = None, + **kwargs, + ): + """Initialize anoncreds primary proof.""" + super().__init__(**kwargs) + self.eq_proof = eq_proof + self.ge_proofs = ge_proofs + + +class AnoncredsPrimaryProofSchema(BaseModelSchema): + """Anoncreds primary proof schema.""" + + class Meta: + """Anoncreds primary proof schema metadata.""" + + model_class = AnoncredsPrimaryProof + unknown = EXCLUDE + + eq_proof = fields.Nested( + AnoncredsEQProofSchema, + allow_none=True, + metadata={"description": "Anoncreds equality proof"}, + ) + ge_proofs = fields.Nested( + AnoncredsGEProofSchema, + many=True, + allow_none=True, + metadata={"description": "Anoncreds GE proofs"}, + ) + + +class AnoncredsNonRevocProof(BaseModel): + """Anoncreds non-revocation proof.""" + + class Meta: + """Anoncreds non-revocation proof metadata.""" + + schema_class = "AnoncredsNonRevocProofSchema" + + def __init__( + self, + x_list: Optional[Mapping] = None, + c_list: Optional[Mapping] = None, + **kwargs, + ): + """Initialize anoncreds non-revocation proof.""" + super().__init__(**kwargs) + self.x_list = x_list + self.c_list = c_list + + +class AnoncredsNonRevocProofSchema(BaseModelSchema): + """Anoncreds non-revocation proof schema.""" + + class Meta: + """Anoncreds non-revocation proof schema metadata.""" + + model_class = AnoncredsNonRevocProof + unknown = EXCLUDE + + x_list = fields.Dict(keys=fields.Str(), values=fields.Str()) + c_list = fields.Dict(keys=fields.Str(), values=fields.Str()) + + +class AnoncredsProofProofProofsProof(BaseModel): + """Anoncreds proof.proof.proofs constituent proof.""" + + class Meta: + """Anoncreds proof.proof.proofs constituent proof schema.""" + + schema_class = "AnoncredsProofProofProofsProofSchema" + + def __init__( + self, + primary_proof: Optional[AnoncredsPrimaryProof] = None, + non_revoc_proof: Optional[AnoncredsNonRevocProof] = None, + **kwargs, + ): + """Initialize proof.proof.proofs constituent proof.""" + super().__init__(**kwargs) + self.primary_proof = primary_proof + self.non_revoc_proof = non_revoc_proof + + +class AnoncredsProofProofProofsProofSchema(BaseModelSchema): + """Anoncreds proof.proof.proofs constituent proof schema.""" + + class Meta: + """Anoncreds proof.proof.proofs constituent proof schema metadata.""" + + model_class = AnoncredsProofProofProofsProof + unknown = EXCLUDE + + primary_proof = fields.Nested( + AnoncredsPrimaryProofSchema, metadata={"description": "Anoncreds primary proof"} + ) + non_revoc_proof = fields.Nested( + AnoncredsNonRevocProofSchema, + allow_none=True, + metadata={"description": "Anoncreds non-revocation proof"}, + ) + + +class AnoncredsProofProofAggregatedProof(BaseModel): + """Anoncreds proof.proof aggregated proof.""" + + class Meta: + """Anoncreds proof.proof aggregated proof metadata.""" + + schema_class = "AnoncredsProofProofAggregatedProofSchema" + + def __init__( + self, + c_hash: Optional[str] = None, + c_list: Sequence[Sequence[int]] = None, + **kwargs, + ): + """Initialize anoncreds proof.proof agreggated proof.""" + super().__init__(**kwargs) + self.c_hash = c_hash + self.c_list = c_list + + +class AnoncredsProofProofAggregatedProofSchema(BaseModelSchema): + """Anoncreds proof.proof aggregated proof schema.""" + + class Meta: + """Anoncreds proof.proof aggregated proof schema metadata.""" + + model_class = AnoncredsProofProofAggregatedProof + unknown = EXCLUDE + + c_hash = fields.Str(metadata={"description": "c_hash value"}) + c_list = fields.List( + fields.List(fields.Int(metadata={"strict": True})), + metadata={"description": "c_list value"}, + ) + + +class AnoncredsProofProof(BaseModel): + """Anoncreds proof.proof content.""" + + class Meta: + """Anoncreds proof.proof content metadata.""" + + schema_class = "AnoncredsProofProofSchema" + + def __init__( + self, + proofs: Sequence[AnoncredsProofProofProofsProof] = None, + aggregated_proof: Optional[AnoncredsProofProofAggregatedProof] = None, + **kwargs, + ): + """Initialize anoncreds proof.proof content.""" + super().__init__(**kwargs) + self.proofs = proofs + self.aggregated_proof = aggregated_proof + + +class AnoncredsProofProofSchema(BaseModelSchema): + """Anoncreds proof.proof content schema.""" + + class Meta: + """Anoncreds proof.proof content schema metadata.""" + + model_class = AnoncredsProofProof + unknown = EXCLUDE + + proofs = fields.Nested( + AnoncredsProofProofProofsProofSchema, + many=True, + metadata={"description": "Anoncreds proof proofs"}, + ) + aggregated_proof = fields.Nested( + AnoncredsProofProofAggregatedProofSchema, + metadata={"description": "Anoncreds proof aggregated proof"}, + ) + + +class RawEncoded(BaseModel): + """Raw and encoded attribute values.""" + + class Meta: + """Raw and encoded attribute values metadata.""" + + schema_class = "RawEncodedSchema" + + def __init__( + self, + raw: Optional[str] = None, + encoded: Optional[str] = None, + **kwargs, + ): + """Initialize raw and encoded attribute values.""" + super().__init__(**kwargs) + self.raw = raw + self.encoded = encoded + + +class RawEncodedSchema(BaseModelSchema): + """Raw and encoded attribute values schema.""" + + class Meta: + """Raw and encoded attribute values schema metadata.""" + + model_class = RawEncoded + unknown = EXCLUDE + + raw = fields.Str(metadata={"description": "Raw value"}) + encoded = fields.Str( + validate=NUM_STR_ANY_VALIDATE, + metadata={"description": "Encoded value", "example": NUM_STR_ANY_EXAMPLE}, + ) + + +class AnoncredsProofRequestedProofRevealedAttr(RawEncoded): + """Anoncreds proof requested proof revealed attr.""" + + class Meta: + """Anoncreds proof requested proof revealed attr metadata.""" + + schema_class = "AnoncredsProofRequestedProofRevealedAttrSchema" + + def __init__( + self, + sub_proof_index: Optional[int] = None, + **kwargs, + ): + """Initialize anoncreds proof requested proof revealed attr.""" + super().__init__(**kwargs) + self.sub_proof_index = sub_proof_index + + +class AnoncredsProofRequestedProofRevealedAttrSchema(RawEncodedSchema): + """Anoncreds proof requested proof revealed attr schema.""" + + class Meta: + """Anoncreds proof requested proof revealed attr schema metadata.""" + + model_class = AnoncredsProofRequestedProofRevealedAttr + unknown = EXCLUDE + + sub_proof_index = fields.Int( + metadata={"strict": True, "description": "Sub-proof index"} + ) + + +class AnoncredsProofRequestedProofRevealedAttrGroup(BaseModel): + """Anoncreds proof requested proof revealed attr group.""" + + class Meta: + """Anoncreds proof requested proof revealed attr group metadata.""" + + schema_class = "AnoncredsProofRequestedProofRevealedAttrGroupSchema" + + def __init__( + self, + sub_proof_index: Optional[int] = None, + values: Mapping[str, RawEncoded] = None, + **kwargs, + ): + """Initialize anoncreds proof requested proof revealed attr.""" + super().__init__(**kwargs) + self.sub_proof_index = sub_proof_index + self.values = values + + +class AnoncredsProofRequestedProofRevealedAttrGroupSchema(BaseModelSchema): + """Anoncreds proof requested proof revealed attr group schema.""" + + class Meta: + """Anoncreds proof requested proof revealed attr group schema metadata.""" + + model_class = AnoncredsProofRequestedProofRevealedAttrGroup + unknown = EXCLUDE + + sub_proof_index = fields.Int( + metadata={"strict": True, "description": "Sub-proof index"} + ) + values = fields.Dict( + keys=fields.Str(), + values=fields.Nested(RawEncodedSchema), + metadata={ + "description": "Anoncreds proof requested proof revealed attr groups group value" # noqa: E501 + }, + ) + + +class AnoncredsProofRequestedProofPredicate(BaseModel): + """Anoncreds proof requested proof predicate.""" + + class Meta: + """Anoncreds proof requested proof requested proof predicate metadata.""" + + schema_class = "AnoncredsProofRequestedProofPredicateSchema" + + def __init__( + self, + sub_proof_index: Optional[int] = None, + **kwargs, + ): + """Initialize anoncreds proof requested proof predicate.""" + super().__init__(**kwargs) + self.sub_proof_index = sub_proof_index + + +class AnoncredsProofRequestedProofPredicateSchema(BaseModelSchema): + """Anoncreds proof requested prrof predicate schema.""" + + class Meta: + """Anoncreds proof requested proof requested proof predicate schema metadata.""" + + model_class = AnoncredsProofRequestedProofPredicate + unknown = EXCLUDE + + sub_proof_index = fields.Int( + metadata={"strict": True, "description": "Sub-proof index"} + ) + + +class AnoncredsProofRequestedProof(BaseModel): + """Anoncreds proof.requested_proof content.""" + + class Meta: + """Anoncreds proof.requested_proof content metadata.""" + + schema_class = "AnoncredsProofRequestedProofSchema" + + def __init__( + self, + revealed_attrs: Mapping[str, AnoncredsProofRequestedProofRevealedAttr] = None, + revealed_attr_groups: Mapping[ + str, + AnoncredsProofRequestedProofRevealedAttrGroup, + ] = None, + self_attested_attrs: Optional[Mapping] = None, + unrevealed_attrs: Optional[Mapping] = None, + predicates: Mapping[str, AnoncredsProofRequestedProofPredicate] = None, + **kwargs, + ): + """Initialize anoncreds proof requested proof.""" + super().__init__(**kwargs) + self.revealed_attrs = revealed_attrs + self.revealed_attr_groups = revealed_attr_groups + self.self_attested_attrs = self_attested_attrs + self.unrevealed_attrs = unrevealed_attrs + self.predicates = predicates + + +class AnoncredsProofRequestedProofSchema(BaseModelSchema): + """Anoncreds proof requested proof schema.""" + + class Meta: + """Anoncreds proof requested proof schema metadata.""" + + model_class = AnoncredsProofRequestedProof + unknown = EXCLUDE + + revealed_attrs = fields.Dict( + keys=fields.Str(), + values=fields.Nested(AnoncredsProofRequestedProofRevealedAttrSchema), + allow_none=True, + metadata={"description": "Proof requested proof revealed attributes"}, + ) + revealed_attr_groups = fields.Dict( + keys=fields.Str(), + values=fields.Nested(AnoncredsProofRequestedProofRevealedAttrGroupSchema), + allow_none=True, + metadata={"description": "Proof requested proof revealed attribute groups"}, + ) + self_attested_attrs = fields.Dict( + metadata={"description": "Proof requested proof self-attested attributes"} + ) + unrevealed_attrs = fields.Dict(metadata={"description": "Unrevealed attributes"}) + predicates = fields.Dict( + keys=fields.Str(), + values=fields.Nested(AnoncredsProofRequestedProofPredicateSchema), + metadata={"description": "Proof requested proof predicates."}, + ) + + +class AnoncredsProofIdentifier(BaseModel): + """Anoncreds proof identifier.""" + + class Meta: + """Anoncreds proof identifier metadata.""" + + schema_class = "AnoncredsProofIdentifierSchema" + + def __init__( + self, + schema_id: Optional[str] = None, + cred_def_id: Optional[str] = None, + rev_reg_id: Optional[str] = None, + timestamp: Optional[int] = None, + **kwargs, + ): + """Initialize anoncreds proof identifier.""" + super().__init__(**kwargs) + self.schema_id = schema_id + self.cred_def_id = cred_def_id + self.rev_reg_id = rev_reg_id + self.timestamp = timestamp + + +class AnoncredsProofIdentifierSchema(BaseModelSchema): + """Anoncreds proof identifier schema.""" + + class Meta: + """Anoncreds proof identifier schema metadata.""" + + model_class = AnoncredsProofIdentifier + unknown = EXCLUDE + + schema_id = fields.Str( + validate=ANONCREDS_SCHEMA_ID_VALIDATE, + metadata={ + "description": "Schema identifier", + "example": ANONCREDS_SCHEMA_ID_EXAMPLE, + }, + ) + cred_def_id = fields.Str( + validate=ANONCREDS_CRED_DEF_ID_VALIDATE, + metadata={ + "description": "Credential definition identifier", + "example": ANONCREDS_CRED_DEF_ID_EXAMPLE, + }, + ) + rev_reg_id = fields.Str( + allow_none=True, + validate=ANONCREDS_REV_REG_ID_VALIDATE, + metadata={ + "description": "Revocation registry identifier", + "example": ANONCREDS_REV_REG_ID_EXAMPLE, + }, + ) + timestamp = fields.Int( + allow_none=True, + validate=INT_EPOCH_VALIDATE, + metadata={ + "strict": True, + "description": "Timestamp epoch", + "example": INT_EPOCH_EXAMPLE, + }, + ) + + +class AnoncredsProof(BaseModel): + """Anoncreds proof.""" + + class Meta: + """Anoncreds proof metadata.""" + + schema_class = "AnoncredsProofSchema" + + def __init__( + self, + proof: Optional[AnoncredsProofProof] = None, + requested_proof: Optional[AnoncredsProofRequestedProof] = None, + identifiers: Sequence[AnoncredsProofIdentifier] = None, + **kwargs, + ): + """Initialize anoncreds proof.""" + super().__init__(**kwargs) + self.proof = proof + self.requested_proof = requested_proof + self.identifiers = identifiers + + +class AnoncredsProofSchema(BaseModelSchema): + """Anoncreds proof schema.""" + + class Meta: + """Anoncreds proof schema metadata.""" + + model_class = AnoncredsProof + unknown = EXCLUDE + + proof = fields.Nested( + AnoncredsProofProofSchema, + metadata={"description": "Anoncreds proof.proof content"}, + ) + requested_proof = fields.Nested( + AnoncredsProofRequestedProofSchema, + metadata={"description": "Anoncreds proof.requested_proof content"}, + ) + identifiers = fields.Nested( + AnoncredsProofIdentifierSchema, + many=True, + metadata={"description": "Anoncreds proof.identifiers content"}, + ) + + +class AnoncredsPresSpecSchema(AdminAPIMessageTracingSchema): + """Request schema for anoncreds proof specification to send as presentation.""" + + self_attested_attributes = fields.Dict( + required=True, + keys=fields.Str(metadata={"example": "attr_name"}), + values=fields.Str( + metadata={ + "example": "self_attested_value", + "description": ( + "Self-attested attribute values to use in requested-credentials" + " structure for proof construction" + ), + } + ), + metadata={"description": "Self-attested attributes to build into proof"}, + ) + requested_attributes = fields.Dict( + required=True, + keys=fields.Str(metadata={"example": "attr_referent"}), + values=fields.Nested(AnoncredsRequestedCredsRequestedAttrSchema), + metadata={ + "description": ( + "Nested object mapping proof request attribute referents to" + " requested-attribute specifiers" + ) + }, + ) + requested_predicates = fields.Dict( + required=True, + keys=fields.Str(metadata={"example": "pred_referent"}), + values=fields.Nested(AnoncredsRequestedCredsRequestedPredSchema), + metadata={ + "description": ( + "Nested object mapping proof request predicate referents to" + " requested-predicate specifiers" + ) + }, + ) + trace = fields.Bool( + required=False, + metadata={ + "description": "Whether to trace event (default false)", + "example": False, + }, + ) diff --git a/acapy_agent/anoncreds/models/requested_credentials.py b/acapy_agent/anoncreds/models/requested_credentials.py new file mode 100644 index 0000000000..ac0fcf3d33 --- /dev/null +++ b/acapy_agent/anoncreds/models/requested_credentials.py @@ -0,0 +1,47 @@ +"""Admin routes for presentations.""" + +from marshmallow import fields + +from ...messaging.models.openapi import OpenAPISchema +from ...messaging.valid import INT_EPOCH_EXAMPLE, INT_EPOCH_VALIDATE + + +class AnoncredsRequestedCredsRequestedAttrSchema(OpenAPISchema): + """Schema for requested attributes within anoncreds requested creds structure.""" + + cred_id = fields.Str( + required=True, + metadata={ + "example": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "description": ( + "Wallet credential identifier (typically but not necessarily a UUID)" + ), + }, + ) + revealed = fields.Bool( + dump_default=True, + metadata={"description": "Whether to reveal attribute in proof (default true)"}, + ) + + +class AnoncredsRequestedCredsRequestedPredSchema(OpenAPISchema): + """Schema for requested predicates within anoncreds requested creds structure.""" + + cred_id = fields.Str( + required=True, + metadata={ + "description": ( + "Wallet credential identifier (typically but not necessarily a UUID)" + ), + "example": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + }, + ) + timestamp = fields.Int( + required=False, + validate=INT_EPOCH_VALIDATE, + metadata={ + "description": "Epoch timestamp of interest for non-revocation proof", + "strict": True, + "example": INT_EPOCH_EXAMPLE, + }, + ) diff --git a/acapy_agent/anoncreds/models/anoncreds_revocation.py b/acapy_agent/anoncreds/models/revocation.py similarity index 96% rename from acapy_agent/anoncreds/models/anoncreds_revocation.py rename to acapy_agent/anoncreds/models/revocation.py index cc90469eac..94c75a525a 100644 --- a/acapy_agent/anoncreds/models/anoncreds_revocation.py +++ b/acapy_agent/anoncreds/models/revocation.py @@ -7,15 +7,14 @@ from marshmallow.validate import OneOf from typing_extensions import Literal -from acapy_agent.messaging.valid import ( - INDY_CRED_DEF_ID_EXAMPLE, - INDY_ISO8601_DATETIME_EXAMPLE, - INDY_OR_KEY_DID_EXAMPLE, - INDY_RAW_PUBLIC_KEY_EXAMPLE, - INDY_REV_REG_ID_EXAMPLE, -) - from ...messaging.models.base import BaseModel, BaseModelSchema +from ...messaging.valid import ( + ANONCREDS_CRED_DEF_ID_EXAMPLE, + ANONCREDS_DID_EXAMPLE, + ANONCREDS_REV_REG_ID_EXAMPLE, + ISO8601_DATETIME_EXAMPLE, + RAW_ED25519_2018_PUBLIC_KEY_EXAMPLE, +) class RevRegDefValue(BaseModel): @@ -60,7 +59,7 @@ class Meta: unknown = EXCLUDE public_keys = fields.Dict( - data_key="publicKeys", metadata={"example": INDY_RAW_PUBLIC_KEY_EXAMPLE} + data_key="publicKeys", metadata={"example": RAW_ED25519_2018_PUBLIC_KEY_EXAMPLE} ) max_cred_num = fields.Int(data_key="maxCredNum", metadata={"example": 777}) tails_location = fields.Str( @@ -131,7 +130,7 @@ class Meta: issuer_id = fields.Str( metadata={ "description": "Issuer Identifier of the credential definition or schema", - "example": INDY_OR_KEY_DID_EXAMPLE, + "example": ANONCREDS_DID_EXAMPLE, }, data_key="issuerId", ) @@ -139,7 +138,7 @@ class Meta: cred_def_id = fields.Str( metadata={ "description": "Credential definition identifier", - "example": INDY_CRED_DEF_ID_EXAMPLE, + "example": ANONCREDS_CRED_DEF_ID_EXAMPLE, }, data_key="credDefId", ) @@ -209,7 +208,7 @@ class Meta: revocation_registry_definition_id = fields.Str( metadata={ "description": "revocation registry definition id", - "example": INDY_REV_REG_ID_EXAMPLE, + "example": ANONCREDS_REV_REG_ID_EXAMPLE, } ) revocation_registry_definition = fields.Nested( @@ -380,14 +379,14 @@ class Meta: issuer_id = fields.Str( metadata={ "description": "Issuer Identifier of the credential definition or schema", - "example": INDY_OR_KEY_DID_EXAMPLE, + "example": ANONCREDS_DID_EXAMPLE, }, data_key="issuerId", ) rev_reg_def_id = fields.Str( metadata={ "description": "The ID of the revocation registry definition", - "example": INDY_REV_REG_ID_EXAMPLE, + "example": ANONCREDS_REV_REG_ID_EXAMPLE, }, data_key="revRegDefId", ) @@ -409,7 +408,7 @@ class Meta: timestamp = fields.Int( metadata={ "description": "Timestamp at which revocation list is applicable", - "example": INDY_ISO8601_DATETIME_EXAMPLE, + "example": ISO8601_DATETIME_EXAMPLE, }, required=False, ) diff --git a/acapy_agent/anoncreds/models/anoncreds_schema.py b/acapy_agent/anoncreds/models/schema.py similarity index 95% rename from acapy_agent/anoncreds/models/anoncreds_schema.py rename to acapy_agent/anoncreds/models/schema.py index b2383ff60f..6a3f56bc03 100644 --- a/acapy_agent/anoncreds/models/anoncreds_schema.py +++ b/acapy_agent/anoncreds/models/schema.py @@ -7,7 +7,10 @@ from marshmallow.validate import OneOf from ...messaging.models.base import BaseModel, BaseModelSchema -from ...messaging.valid import INDY_OR_KEY_DID_EXAMPLE, INDY_SCHEMA_ID_EXAMPLE +from ...messaging.valid import ( + ANONCREDS_DID_EXAMPLE, + ANONCREDS_SCHEMA_ID_EXAMPLE, +) class AnonCredsSchema(BaseModel): @@ -58,7 +61,7 @@ class Meta: issuer_id = fields.Str( metadata={ "description": "Issuer Identifier of the credential definition or schema", - "example": INDY_OR_KEY_DID_EXAMPLE, + "example": ANONCREDS_DID_EXAMPLE, }, data_key="issuerId", ) @@ -129,7 +132,10 @@ class Meta: schema_value = fields.Nested(AnonCredsSchemaSchema(), data_key="schema") schema_id = fields.Str( - metadata={"description": "Schema identifier", "example": INDY_SCHEMA_ID_EXAMPLE} + metadata={ + "description": "Schema identifier", + "example": ANONCREDS_SCHEMA_ID_EXAMPLE, + } ) resolution_metadata = fields.Dict() schema_metadata = fields.Dict() @@ -185,7 +191,7 @@ class Meta: schema_id = fields.Str( metadata={ "description": "Schema identifier", - "example": INDY_SCHEMA_ID_EXAMPLE, + "example": ANONCREDS_SCHEMA_ID_EXAMPLE, } ) schema_value = fields.Nested(AnonCredsSchemaSchema(), data_key="schema") diff --git a/acapy_agent/anoncreds/models/utils.py b/acapy_agent/anoncreds/models/utils.py new file mode 100644 index 0000000000..51f140844f --- /dev/null +++ b/acapy_agent/anoncreds/models/utils.py @@ -0,0 +1,99 @@ +"""Utilities to deal with anoncreds objects.""" + +from ..holder import AnonCredsHolder + + +def _get_value_error_msg(proof_request: dict, referent: str) -> str: + return ( + "Could not automatically construct presentation for " + + f"presentation request {proof_request['name']}" + + f":{proof_request['version']} because referent " + + f"{referent} did not produce any credentials." + ) + + +async def get_requested_creds_from_proof_request_preview( + proof_request: dict, + *, + holder: AnonCredsHolder, +): + """Build anoncreds requested-credentials structure. + + Given input proof request and presentation preview, use credentials in + holder's wallet to build anoncreds requested credentials structure for input + to proof creation. + + Args: + proof_request: anoncreds proof request + preview: preview from presentation proposal, if applicable + holder: holder injected into current context + + """ + req_creds = { + "self_attested_attributes": {}, + "requested_attributes": {}, + "requested_predicates": {}, + } + + for referent, _ in proof_request["requested_attributes"].items(): + credentials = await holder.get_credentials_for_presentation_request_by_referent( + presentation_request=proof_request, + referents=(referent,), + start=0, + count=100, + ) + if not credentials: + raise ValueError(_get_value_error_msg(proof_request, referent)) + + cred_match = credentials[0] # holder sorts + + if "restrictions" in proof_request["requested_attributes"][referent]: + req_creds["requested_attributes"][referent] = { + "cred_id": cred_match["cred_info"]["referent"], + "revealed": True, + } + else: + req_creds["self_attested_attributes"][referent] = cred_match["cred_info"][ + "attrs" + ][proof_request["requested_attributes"][referent]["name"]] + + for referent in proof_request["requested_predicates"]: + credentials = await holder.get_credentials_for_presentation_request_by_referent( + presentation_request=proof_request, + referents=(referent,), + start=0, + count=100, + ) + if not credentials: + raise ValueError(_get_value_error_msg(proof_request, referent)) + + cred_match = credentials[0] # holder sorts + if "restrictions" in proof_request["requested_predicates"][referent]: + req_creds["requested_predicates"][referent] = { + "cred_id": cred_match["cred_info"]["referent"], + "revealed": True, + } + else: + req_creds["self_attested_attributes"][referent] = cred_match["cred_info"][ + "attrs" + ][proof_request["requested_predicates"][referent]["name"]] + + return req_creds + + +def extract_non_revocation_intervals_from_proof_request(proof_req: dict): + """Return non-revocation intervals by requested item referent in proof request.""" + non_revoc_intervals = {} + for req_item_type in ("requested_attributes", "requested_predicates"): + for reft, req_item in proof_req[req_item_type].items(): + interval = req_item.get( + "non_revoked", + proof_req.get("non_revoked"), + ) + if interval: + timestamp_from = interval.get("from") + timestamp_to = interval.get("to") + if (timestamp_to is not None) and timestamp_from == timestamp_to: + interval["from"] = 0 # accommodate verify=False if from=to + non_revoc_intervals[reft] = interval + return non_revoc_intervals diff --git a/acapy_agent/anoncreds/registry.py b/acapy_agent/anoncreds/registry.py index 79aba036cc..c355b447cd 100644 --- a/acapy_agent/anoncreds/registry.py +++ b/acapy_agent/anoncreds/registry.py @@ -11,8 +11,8 @@ BaseAnonCredsRegistrar, BaseAnonCredsResolver, ) -from .models.anoncreds_cred_def import CredDef, CredDefResult, GetCredDefResult -from .models.anoncreds_revocation import ( +from .models.credential_definition import CredDef, CredDefResult, GetCredDefResult +from .models.revocation import ( GetRevListResult, GetRevRegDefResult, RevList, @@ -20,7 +20,7 @@ RevRegDef, RevRegDefResult, ) -from .models.anoncreds_schema import AnonCredsSchema, GetSchemaResult, SchemaResult +from .models.schema import AnonCredsSchema, GetSchemaResult, SchemaResult LOGGER = logging.getLogger(__name__) diff --git a/acapy_agent/anoncreds/revocation.py b/acapy_agent/anoncreds/revocation.py index 56b19d222d..e9cc88293f 100644 --- a/acapy_agent/anoncreds/revocation.py +++ b/acapy_agent/anoncreds/revocation.py @@ -26,12 +26,12 @@ from requests import RequestException, Session from uuid_utils import uuid4 -from acapy_agent.anoncreds.models.anoncreds_cred_def import CredDef - from ..askar.profile_anon import AskarAnoncredsProfile, AskarAnoncredsProfileSession from ..core.error import BaseError from ..core.event_bus import Event, EventBus from ..core.profile import Profile, ProfileSession +from ..multitenant.base import BaseMultitenantManager +from ..tails.anoncreds_tails_server import AnonCredsTailsServer from ..tails.base import BaseTailsServer from .error_messages import ANONCREDS_PROFILE_REQUIRED_MSG from .events import RevListFinishedEvent, RevRegDefFinishedEvent @@ -41,7 +41,8 @@ STATE_FINISHED, AnonCredsIssuer, ) -from .models.anoncreds_revocation import ( +from .models.credential_definition import CredDef +from .models.revocation import ( RevList, RevListResult, RevListState, @@ -694,7 +695,12 @@ def get_local_tails_path(self, rev_reg_def: RevRegDef) -> str: async def upload_tails_file(self, rev_reg_def: RevRegDef): """Upload the local tails file to the tails server.""" - tails_server = self.profile.inject_or(BaseTailsServer) + multitenant_mgr = self.profile.inject_or(BaseMultitenantManager) + if multitenant_mgr: + tails_server = AnonCredsTailsServer() + else: + tails_server = self.profile.inject_or(BaseTailsServer) + if not tails_server: raise AnonCredsRevocationError("Tails server not configured") if not Path(self.get_local_tails_path(rev_reg_def)).is_file(): diff --git a/acapy_agent/anoncreds/routes.py b/acapy_agent/anoncreds/routes.py index 9cf1d6ad98..9a8fe04669 100644 --- a/acapy_agent/anoncreds/routes.py +++ b/acapy_agent/anoncreds/routes.py @@ -16,13 +16,12 @@ from ..admin.decorators.auth import tenant_authentication from ..admin.request_context import AdminRequestContext from ..core.event_bus import EventBus -from ..ledger.error import LedgerError from ..messaging.models.openapi import OpenAPISchema from ..messaging.valid import ( - INDY_CRED_DEF_ID_EXAMPLE, - INDY_OR_KEY_DID_EXAMPLE, - INDY_REV_REG_ID_EXAMPLE, - INDY_SCHEMA_ID_EXAMPLE, + ANONCREDS_CRED_DEF_ID_EXAMPLE, + ANONCREDS_DID_EXAMPLE, + ANONCREDS_REV_REG_ID_EXAMPLE, + ANONCREDS_SCHEMA_ID_EXAMPLE, UUIDFour, ) from ..revocation.error import RevocationNotSupportedError @@ -35,9 +34,9 @@ AnonCredsResolutionError, ) from .issuer import AnonCredsIssuer, AnonCredsIssuerError -from .models.anoncreds_cred_def import CredDefResultSchema, GetCredDefResultSchema -from .models.anoncreds_revocation import RevListResultSchema, RevRegDefResultSchema -from .models.anoncreds_schema import ( +from .models.credential_definition import CredDefResultSchema, GetCredDefResultSchema +from .models.revocation import RevListResultSchema, RevRegDefResultSchema +from .models.schema import ( AnonCredsSchemaSchema, GetSchemaResultSchema, SchemaResultSchema, @@ -69,7 +68,7 @@ class SchemaIdMatchInfo(OpenAPISchema): schema_id = fields.Str( metadata={ "description": "Schema identifier", - "example": INDY_SCHEMA_ID_EXAMPLE, + "example": ANONCREDS_SCHEMA_ID_EXAMPLE, } ) @@ -112,7 +111,7 @@ class SchemasQueryStringSchema(OpenAPISchema): schema_issuer_id = fields.Str( metadata={ "description": "Schema issuer identifier", - "example": INDY_OR_KEY_DID_EXAMPLE, + "example": ANONCREDS_DID_EXAMPLE, } ) @@ -124,7 +123,7 @@ class GetSchemasResponseSchema(OpenAPISchema): fields.Str( metadata={ "description": "Schema identifiers", - "example": INDY_SCHEMA_ID_EXAMPLE, + "example": ANONCREDS_SCHEMA_ID_EXAMPLE, } ) ) @@ -137,7 +136,7 @@ class SchemaPostRequestSchema(OpenAPISchema): options = fields.Nested(SchemaPostOptionSchema()) -@docs(tags=["anoncreds - schemas"], summary="Create a schema on the connected ledger") +@docs(tags=["anoncreds - schemas"], summary="Create a schema on the connected datastore") @request_schema(SchemaPostRequestSchema()) @response_schema(SchemaResultSchema(), 200, description="") @tenant_authentication @@ -278,7 +277,7 @@ class CredIdMatchInfo(OpenAPISchema): cred_def_id = fields.Str( metadata={ "description": "Credential definition identifier", - "example": INDY_CRED_DEF_ID_EXAMPLE, + "example": ANONCREDS_CRED_DEF_ID_EXAMPLE, }, required=True, ) @@ -297,7 +296,7 @@ class InnerCredDefSchema(OpenAPISchema): schema_id = fields.Str( metadata={ "description": "Schema identifier", - "example": INDY_SCHEMA_ID_EXAMPLE, + "example": ANONCREDS_SCHEMA_ID_EXAMPLE, }, required=True, data_key="schemaId", @@ -305,7 +304,7 @@ class InnerCredDefSchema(OpenAPISchema): issuer_id = fields.Str( metadata={ "description": "Issuer Identifier of the credential definition", - "example": INDY_OR_KEY_DID_EXAMPLE, + "example": ANONCREDS_DID_EXAMPLE, }, required=True, data_key="issuerId", @@ -357,13 +356,13 @@ class CredDefsQueryStringSchema(OpenAPISchema): issuer_id = fields.Str( metadata={ "description": "Issuer Identifier of the credential definition", - "example": INDY_OR_KEY_DID_EXAMPLE, + "example": ANONCREDS_DID_EXAMPLE, } ) schema_id = fields.Str( metadata={ "description": "Schema identifier", - "example": INDY_SCHEMA_ID_EXAMPLE, + "example": ANONCREDS_SCHEMA_ID_EXAMPLE, } ) schema_name = fields.Str( @@ -382,7 +381,7 @@ class CredDefsQueryStringSchema(OpenAPISchema): @docs( tags=["anoncreds - credential definitions"], - summary="Create a credential definition on the connected ledger", + summary="Create a credential definition on the connected datastore", ) @request_schema(CredDefPostRequestSchema()) @response_schema(CredDefResultSchema(), 200, description="") @@ -522,14 +521,14 @@ class InnerRevRegDefSchema(OpenAPISchema): issuer_id = fields.Str( metadata={ "description": "Issuer Identifier of the credential definition or schema", - "example": INDY_OR_KEY_DID_EXAMPLE, + "example": ANONCREDS_DID_EXAMPLE, }, data_key="issuerId", ) cred_def_id = fields.Str( metadata={ "description": "Credential definition identifier", - "example": INDY_SCHEMA_ID_EXAMPLE, + "example": ANONCREDS_SCHEMA_ID_EXAMPLE, }, data_key="credDefId", ) @@ -573,7 +572,7 @@ class RevRegCreateRequestSchemaAnoncreds(OpenAPISchema): @docs( tags=["anoncreds - revocation"], - summary="Create and publish a registration revocation on the connected ledger", + summary="Create and publish a registration revocation on the connected datastore", ) @request_schema(RevRegCreateRequestSchemaAnoncreds()) @response_schema(RevRegDefResultSchema(), 200, description="") @@ -649,7 +648,7 @@ class RevListCreateRequestSchema(OpenAPISchema): rev_reg_def_id = fields.Str( metadata={ "description": "Revocation registry definition identifier", - "example": INDY_REV_REG_ID_EXAMPLE, + "example": ANONCREDS_REV_REG_ID_EXAMPLE, } ) options = fields.Nested(RevListOptionsSchema) @@ -657,7 +656,7 @@ class RevListCreateRequestSchema(OpenAPISchema): @docs( tags=["anoncreds - revocation"], - summary="Create and publish a revocation status list on the connected ledger", + summary="Create and publish a revocation status list on the connected datastore", ) @request_schema(RevListCreateRequestSchema()) @response_schema(RevListResultSchema(), 200, description="") @@ -687,7 +686,7 @@ async def rev_list_post(request: web.BaseRequest): handle_value_error(e) except StorageNotFoundError as err: raise web.HTTPNotFound(reason=err.roll_up) from err - except (AnonCredsRevocationError, LedgerError) as err: + except AnonCredsRevocationError as err: raise web.HTTPBadRequest(reason=err.roll_up) from err diff --git a/acapy_agent/anoncreds/tests/test_holder.py b/acapy_agent/anoncreds/tests/test_holder.py index 6f8dae681d..c894b21e3f 100644 --- a/acapy_agent/anoncreds/tests/test_holder.py +++ b/acapy_agent/anoncreds/tests/test_holder.py @@ -45,8 +45,8 @@ from ...wallet.error import WalletNotFoundError from .. import holder as test_module from ..holder import CATEGORY_CREDENTIAL, AnonCredsHolder, AnonCredsHolderError -from ..models.anoncreds_cred_def import CredDef, CredDefValue, CredDefValuePrimary -from ..models.anoncreds_revocation import GetRevListResult, RevList +from ..models.credential_definition import CredDef, CredDefValue, CredDefValuePrimary +from ..models.revocation import GetRevListResult, RevList from ..registry import AnonCredsRegistry diff --git a/acapy_agent/anoncreds/tests/test_issuer.py b/acapy_agent/anoncreds/tests/test_issuer.py index f165f178d6..7442ffd2ae 100644 --- a/acapy_agent/anoncreds/tests/test_issuer.py +++ b/acapy_agent/anoncreds/tests/test_issuer.py @@ -10,7 +10,7 @@ AnonCredsObjectAlreadyExists, AnonCredsSchemaAlreadyExists, ) -from ...anoncreds.models.anoncreds_cred_def import ( +from ...anoncreds.models.credential_definition import ( CredDef, CredDefResult, CredDefState, @@ -19,7 +19,7 @@ CredDefValueRevocation, GetCredDefResult, ) -from ...anoncreds.models.anoncreds_schema import ( +from ...anoncreds.models.schema import ( AnonCredsSchema, GetSchemaResult, SchemaResult, diff --git a/acapy_agent/anoncreds/tests/test_revocation.py b/acapy_agent/anoncreds/tests/test_revocation.py index 5de4ef368e..2d9bc62e15 100644 --- a/acapy_agent/anoncreds/tests/test_revocation.py +++ b/acapy_agent/anoncreds/tests/test_revocation.py @@ -16,8 +16,8 @@ from requests import RequestException, Session from ...anoncreds.issuer import AnonCredsIssuer -from ...anoncreds.models.anoncreds_cred_def import CredDef -from ...anoncreds.models.anoncreds_revocation import ( +from ...anoncreds.models.credential_definition import CredDef +from ...anoncreds.models.revocation import ( RevList, RevListResult, RevListState, @@ -26,7 +26,7 @@ RevRegDefState, RevRegDefValue, ) -from ...anoncreds.models.anoncreds_schema import ( +from ...anoncreds.models.schema import ( AnonCredsSchema, GetSchemaResult, ) @@ -48,7 +48,7 @@ "accum_key": {"z": "1 0BB...386"}, }, tails_hash="58NNWYnVxVFzAfUztwGSNBL4551XNq6nXk56pCiKJxxt", - tails_location="http://tails-server.com", + tails_location="https://tails-server.com", ), issuer_id="CsQY9MGeD3CQP4EyuVFo5m", type="CL_ACCUM", @@ -836,21 +836,40 @@ def test_generate_public_tails_uri(self): async def test_upload_tails_file(self): self.profile.inject_or = mock.Mock( - return_value=mock.MagicMock( - upload_tails_file=mock.CoroutineMock( - side_effect=[ - (True, "http://tails-server.com"), - (None, "http://tails-server.com"), - (True, "not-http://tails-server.com"), - ] - ) - ) + side_effect=[ + None, + mock.MagicMock( + upload_tails_file=mock.CoroutineMock( + return_value=(True, "https://tails-server.com") + ) + ), + ] ) # valid await self.revocation.upload_tails_file(rev_reg_def) # upload fails + self.profile.inject_or = mock.Mock( + side_effect=[ + None, + mock.MagicMock( + upload_tails_file=mock.CoroutineMock( + return_value=(None, "https://tails-server.com"), + ) + ), + ] + ) with self.assertRaises(test_module.AnonCredsRevocationError): await self.revocation.upload_tails_file(rev_reg_def) + self.profile.inject_or = mock.Mock( + side_effect=[ + None, + mock.MagicMock( + upload_tails_file=mock.CoroutineMock( + return_value=(True, "not-http://tails-server.com"), + ) + ), + ] + ) # tails location does not match with self.assertRaises(test_module.AnonCredsRevocationError): await self.revocation.upload_tails_file(rev_reg_def) diff --git a/acapy_agent/anoncreds/tests/test_revocation_setup.py b/acapy_agent/anoncreds/tests/test_revocation_setup.py index e56291efeb..357899f4c9 100644 --- a/acapy_agent/anoncreds/tests/test_revocation_setup.py +++ b/acapy_agent/anoncreds/tests/test_revocation_setup.py @@ -11,7 +11,7 @@ RevRegDefFinishedEvent, RevRegDefFinishedPayload, ) -from ..models.anoncreds_revocation import RevRegDef, RevRegDefValue +from ..models.revocation import RevRegDef, RevRegDefValue from ..revocation import AnonCredsRevocation diff --git a/acapy_agent/anoncreds/tests/test_routes.py b/acapy_agent/anoncreds/tests/test_routes.py index 17c4d15bee..607b81f4ae 100644 --- a/acapy_agent/anoncreds/tests/test_routes.py +++ b/acapy_agent/anoncreds/tests/test_routes.py @@ -7,7 +7,7 @@ from ...admin.request_context import AdminRequestContext from ...anoncreds.base import AnonCredsObjectNotFound from ...anoncreds.issuer import AnonCredsIssuer -from ...anoncreds.models.anoncreds_schema import ( +from ...anoncreds.models.schema import ( AnonCredsSchema, SchemaResult, SchemaState, diff --git a/acapy_agent/anoncreds/tests/test_verifier.py b/acapy_agent/anoncreds/tests/test_verifier.py index 8f912a3f0f..0ff11fcb95 100644 --- a/acapy_agent/anoncreds/tests/test_verifier.py +++ b/acapy_agent/anoncreds/tests/test_verifier.py @@ -3,21 +3,21 @@ import pytest -from ...anoncreds.models.anoncreds_cred_def import ( +from ...anoncreds.models.credential_definition import ( CredDef, CredDefValue, CredDefValuePrimary, CredDefValueRevocation, GetCredDefResult, ) -from ...anoncreds.models.anoncreds_revocation import ( +from ...anoncreds.models.revocation import ( GetRevListResult, GetRevRegDefResult, RevList, RevRegDef, RevRegDefValue, ) -from ...anoncreds.models.anoncreds_schema import ( +from ...anoncreds.models.schema import ( AnonCredsSchema, GetSchemaResult, ) diff --git a/acapy_agent/anoncreds/verifier.py b/acapy_agent/anoncreds/verifier.py index 4f7972371d..849c44ab5b 100644 --- a/acapy_agent/anoncreds/verifier.py +++ b/acapy_agent/anoncreds/verifier.py @@ -1,4 +1,4 @@ -"""Indy-Credx verifier implementation.""" +"""Anoncreds verifier implementation.""" import asyncio import logging @@ -9,10 +9,10 @@ from anoncreds import AnoncredsError, Presentation, W3cPresentation from ..core.profile import Profile -from ..indy.models.xform import indy_proof_req2non_revoc_intervals from ..messaging.util import canon, encode from ..vc.vc_ld.validation_result import PresentationVerificationResult -from .models.anoncreds_cred_def import GetCredDefResult +from .models.credential_definition import GetCredDefResult +from .models.utils import extract_non_revocation_intervals_from_proof_request from .registry import AnonCredsRegistry LOGGER = logging.getLogger(__name__) @@ -45,7 +45,7 @@ def non_revoc_intervals(self, pres_req: dict, pres: dict, cred_defs: dict) -> li """Remove superfluous non-revocation intervals in presentation request. Irrevocable credentials constitute proof of non-revocation, but - indy rejects proof requests with non-revocation intervals lining up + anoncreds rejects proof requests with non-revocation intervals lining up with non-revocable credentials in proof: seek and remove. Args: @@ -116,13 +116,15 @@ async def check_timestamps( Args: profile: relevant profile - pres_req: indy proof request - pres: indy proof request + pres_req: anoncreds proof request + pres: anoncreds proof request rev_reg_defs: rev reg defs by rev reg id, augmented with transaction times """ msgs = [] now = int(time()) - non_revoc_intervals = indy_proof_req2non_revoc_intervals(pres_req) + non_revoc_intervals = extract_non_revocation_intervals_from_proof_request( + pres_req + ) LOGGER.debug(f">>> got non-revoc intervals: {non_revoc_intervals}") # timestamp for irrevocable credential diff --git a/acapy_agent/config/default_context.py b/acapy_agent/config/default_context.py index 35ee02a184..32750b31be 100644 --- a/acapy_agent/config/default_context.py +++ b/acapy_agent/config/default_context.py @@ -29,23 +29,17 @@ from .injection_context import InjectionContext from .provider import CachedProvider, ClassProvider -LOGGER = logging.getLogger(__name__) -add_trace_level() # Allow trace logs from this module - class DefaultContextBuilder(ContextBuilder): """Default context builder.""" async def build_context(self) -> InjectionContext: """Build the base injection context; set DIDComm prefix to emit.""" - LOGGER.trace("Building new injection context with settings: %s", self.settings) - context = InjectionContext(settings=self.settings) context.settings.set_default("default_label", "Aries Cloud Agent") if context.settings.get("timing.enabled"): timing_log = context.settings.get("timing.log_file") - LOGGER.trace("Enabling timing collector with log file: %s", timing_log) collector = Collector(log_path=timing_log) context.injector.bind_instance(Collector, collector) @@ -87,13 +81,11 @@ async def build_context(self) -> InjectionContext: async def bind_providers(self, context: InjectionContext): """Bind various class providers.""" - LOGGER.trace("Begin binding providers to context") context.injector.bind_provider(ProfileManager, ProfileManagerProvider()) wallet_type = self.settings.get("wallet.type") if wallet_type == "askar-anoncreds": - LOGGER.trace("Using AnonCreds tails server") context.injector.bind_provider( BaseTailsServer, ClassProvider( @@ -101,7 +93,6 @@ async def bind_providers(self, context: InjectionContext): ), ) else: - LOGGER.trace("Using Indy tails server") context.injector.bind_provider( BaseTailsServer, ClassProvider( @@ -124,7 +115,6 @@ async def bind_providers(self, context: InjectionContext): async def load_plugins(self, context: InjectionContext): """Set up plugin registry and load plugins.""" - LOGGER.trace("Initializing plugin registry") plugin_registry = PluginRegistry( blocklist=self.settings.get("blocked_plugins", []) ) @@ -162,10 +152,8 @@ async def load_plugins(self, context: InjectionContext): "acapy_agent.revocation", ] - def register_plugins(plugins: list[str], plugin_type: str): - """Register a group of plugins with logging.""" - LOGGER.trace("Registering %s plugins", plugin_type) - for plugin in plugins: + def register_askar_plugins(): + for plugin in askar_plugins: plugin_registry.register_plugin(plugin) def register_askar_plugins(): @@ -177,7 +165,6 @@ def register_anoncreds_plugins(): register_plugins(default_plugins, "default") if context.settings.get("multitenant.admin_enabled"): - LOGGER.trace("Multitenant admin enabled - registering additional plugins") plugin_registry.register_plugin("acapy_agent.multitenant.admin") register_askar_plugins() register_anoncreds_plugins() @@ -189,7 +176,6 @@ def register_anoncreds_plugins(): # Register external plugins for plugin_path in self.settings.get("external_plugins", []): - LOGGER.trace("Registering external plugin: %s", plugin_path) plugin_registry.register_plugin(plugin_path) # Register message protocols diff --git a/acapy_agent/config/logging/configurator.py b/acapy_agent/config/logging/configurator.py index fb9e2cb347..5f7c67eee5 100644 --- a/acapy_agent/config/logging/configurator.py +++ b/acapy_agent/config/logging/configurator.py @@ -39,6 +39,8 @@ # Add TRACE level to logging before any configuration add_trace_level() +LOGGER = logging.getLogger(__name__) + def load_resource(path: str, encoding: Optional[str] = None): """Open a resource file located in a python package or the local filesystem. diff --git a/acapy_agent/config/tests/test_logging.py b/acapy_agent/config/tests/test_logging.py index 446395003d..747816b352 100644 --- a/acapy_agent/config/tests/test_logging.py +++ b/acapy_agent/config/tests/test_logging.py @@ -1,5 +1,4 @@ import contextlib -import logging from io import BufferedReader, StringIO, TextIOWrapper from tempfile import NamedTemporaryFile from unittest import IsolatedAsyncioTestCase, mock @@ -164,131 +163,3 @@ def test_load_resource(self): mock_files.return_value.joinpath.assert_called_once_with("def") mock_resource_path.open.assert_called_once_with("rb") assert result == mock_resource_handle # Verify the returned binary stream - - -class TestLoggingUtils(IsolatedAsyncioTestCase): - def setUp(self): - """Set up test environment by backing up logging states and resetting TRACE level.""" - # Backup existing logging attributes (e.g., DEBUG, INFO) - self.original_levels = { - attr: getattr(logging, attr) for attr in dir(logging) if attr.isupper() - } - - # Backup existing logger class methods (e.g., debug, info) - self.original_logger_methods = { - attr: getattr(logging.getLoggerClass(), attr, None) - for attr in dir(logging.getLoggerClass()) - if not attr.startswith("_") - } - - # Remove TRACE level and 'trace' method if they exist - if hasattr(logging, "TRACE"): - delattr(logging, "TRACE") - if hasattr(logging.getLoggerClass(), "trace"): - delattr(logging.getLoggerClass(), "trace") - - # Patch the TRACE_LEVEL_ADDED flag to False before each test - self.trace_level_added_patcher = mock.patch( - "acapy_agent.config.logging.utils._TRACE_LEVEL_ADDED", False - ) - self.mock_trace_level_added = self.trace_level_added_patcher.start() - - def tearDown(self): - """Restore original logging states after each test.""" - # Stop patching TRACE_LEVEL_ADDED - self.trace_level_added_patcher.stop() - - # Restore original logging level attributes - for attr, value in self.original_levels.items(): - setattr(logging, attr, value) - - # Identify and remove any new uppercase attributes added during tests (e.g., TRACE) - current_levels = {attr for attr in dir(logging) if attr.isupper()} - for attr in current_levels - set(self.original_levels.keys()): - delattr(logging, attr) - - # Restore original logger class methods - LoggerClass = logging.getLoggerClass() - for attr, value in self.original_logger_methods.items(): - if value is not None: - setattr(LoggerClass, attr, value) - else: - if hasattr(LoggerClass, attr): - delattr(LoggerClass, attr) - - # Identify and remove any new logger methods added during tests (e.g., trace) - current_methods = {attr for attr in dir(LoggerClass) if not attr.startswith("_")} - for attr in current_methods - set(self.original_logger_methods.keys()): - delattr(LoggerClass, attr) - - @mock.patch("acapy_agent.config.logging.utils.LOGGER") - @mock.patch("acapy_agent.config.logging.utils.logging.addLevelName") - def test_add_logging_level_success(self, mock_add_level_name, mock_logger): - utils.add_logging_level("CUSTOM", 2) - - mock_add_level_name.assert_called_once_with(2, "CUSTOM") - self.assertTrue(hasattr(logging, "CUSTOM")) - self.assertEqual(logging.CUSTOM, 2) - - logger = logging.getLogger(__name__) - self.assertTrue(hasattr(logger, "custom")) - self.assertTrue(callable(logger.custom)) - - self.assertTrue(hasattr(logging, "custom")) - self.assertTrue(callable(logging.custom)) - - def test_add_logging_level_existing_level_name(self): - # Add a level named 'DEBUG' which already exists - with self.assertRaises(AttributeError) as context: - utils.add_logging_level("DEBUG", 15) - self.assertIn("DEBUG already defined in logging module", str(context.exception)) - - def test_add_logging_level_existing_method_name(self): - # Add a logging method that already exists ('debug') - with self.assertRaises(AttributeError) as context: - utils.add_logging_level("CUSTOM", 25, method_name="debug") - self.assertIn("debug already defined in logging module", str(context.exception)) - - @mock.patch("acapy_agent.config.logging.utils.add_logging_level") - @mock.patch("acapy_agent.config.logging.utils.LOGGER") - def test_add_trace_level_new(self, mock_logger, mock_add_logging_level): - # Ensure _TRACE_LEVEL_ADDED is False - utils.add_trace_level() - - mock_add_logging_level.assert_called_once_with( - "TRACE", logging.DEBUG - 5, "trace" - ) - - # Verify logger.debug was called - mock_logger.debug.assert_called_with("%s level added to logging module.", "TRACE") - - # Check that _TRACE_LEVEL_ADDED is now True - self.assertTrue(utils._TRACE_LEVEL_ADDED) - - @mock.patch("acapy_agent.config.logging.utils.LOGGER") - @mock.patch( - "acapy_agent.config.logging.utils.add_logging_level", - side_effect=AttributeError("TRACE already exists"), - ) - def test_add_trace_level_already_exists_exception( - self, mock_add_logging_level, mock_logger - ): - utils.add_trace_level() - - # Verify logger.warning was called - mock_logger.warning.assert_called_with( - "%s level already exists: %s", "TRACE", mock_add_logging_level.side_effect - ) - - @mock.patch("acapy_agent.config.logging.utils.LOGGER") - @mock.patch("acapy_agent.config.logging.utils.add_logging_level") - def test_add_trace_level_already_present(self, mock_add_logging_level, mock_logger): - # Manually set _TRACE_LEVEL_ADDED to True to simulate already added TRACE level - with mock.patch("acapy_agent.config.logging.utils._TRACE_LEVEL_ADDED", True): - utils.add_trace_level() - - # add_logging_level should not be called since TRACE level is already added - mock_add_logging_level.assert_not_called() - - # Verify logger.debug was not called - mock_logger.debug.assert_not_called() diff --git a/acapy_agent/connections/models/conn_record.py b/acapy_agent/connections/models/conn_record.py index 874d8c3d7f..e27be92c4f 100644 --- a/acapy_agent/connections/models/conn_record.py +++ b/acapy_agent/connections/models/conn_record.py @@ -11,8 +11,8 @@ from ...messaging.valid import ( GENERIC_DID_EXAMPLE, GENERIC_DID_VALIDATE, - INDY_RAW_PUBLIC_KEY_EXAMPLE, - INDY_RAW_PUBLIC_KEY_VALIDATE, + RAW_ED25519_2018_PUBLIC_KEY_EXAMPLE, + RAW_ED25519_2018_PUBLIC_KEY_VALIDATE, UUID4_EXAMPLE, ) from ...protocols.connections.v1_0.message_types import ARIES_PROTOCOL as CONN_PROTO @@ -725,10 +725,10 @@ class Meta: ) invitation_key = fields.Str( required=False, - validate=INDY_RAW_PUBLIC_KEY_VALIDATE, + validate=RAW_ED25519_2018_PUBLIC_KEY_VALIDATE, metadata={ "description": "Public key for connection", - "example": INDY_RAW_PUBLIC_KEY_EXAMPLE, + "example": RAW_ED25519_2018_PUBLIC_KEY_EXAMPLE, }, ) invitation_msg_id = fields.Str( diff --git a/acapy_agent/connections/models/connection_target.py b/acapy_agent/connections/models/connection_target.py index ad7219db0a..bc81adc42e 100644 --- a/acapy_agent/connections/models/connection_target.py +++ b/acapy_agent/connections/models/connection_target.py @@ -8,8 +8,8 @@ from ...messaging.valid import ( GENERIC_DID_EXAMPLE, GENERIC_DID_VALIDATE, - INDY_RAW_PUBLIC_KEY_EXAMPLE, - INDY_RAW_PUBLIC_KEY_VALIDATE, + RAW_ED25519_2018_PUBLIC_KEY_EXAMPLE, + RAW_ED25519_2018_PUBLIC_KEY_VALIDATE, ) @@ -75,10 +75,10 @@ class Meta: ) recipient_keys = fields.List( fields.Str( - validate=INDY_RAW_PUBLIC_KEY_VALIDATE, + validate=RAW_ED25519_2018_PUBLIC_KEY_VALIDATE, metadata={ "description": "Recipient public key", - "example": INDY_RAW_PUBLIC_KEY_EXAMPLE, + "example": RAW_ED25519_2018_PUBLIC_KEY_EXAMPLE, }, ), required=False, @@ -86,10 +86,10 @@ class Meta: ) routing_keys = fields.List( fields.Str( - validate=INDY_RAW_PUBLIC_KEY_VALIDATE, + validate=RAW_ED25519_2018_PUBLIC_KEY_VALIDATE, metadata={ "description": "Routing key", - "example": INDY_RAW_PUBLIC_KEY_EXAMPLE, + "example": RAW_ED25519_2018_PUBLIC_KEY_EXAMPLE, }, ), data_key="routingKeys", @@ -98,9 +98,9 @@ class Meta: ) sender_key = fields.Str( required=False, - validate=INDY_RAW_PUBLIC_KEY_VALIDATE, + validate=RAW_ED25519_2018_PUBLIC_KEY_VALIDATE, metadata={ "description": "Sender public key", - "example": INDY_RAW_PUBLIC_KEY_EXAMPLE, + "example": RAW_ED25519_2018_PUBLIC_KEY_EXAMPLE, }, ) diff --git a/acapy_agent/core/conductor.py b/acapy_agent/core/conductor.py index af5a7d1bbc..346dea1c9b 100644 --- a/acapy_agent/core/conductor.py +++ b/acapy_agent/core/conductor.py @@ -127,7 +127,7 @@ async def setup(self): LOGGER.debug("Starting setup of the Conductor") context = await self.context_builder.build_context() - LOGGER.trace("Context built successfully") + LOGGER.debug("Context built successfully") if self.force_agent_anoncreds: LOGGER.debug( @@ -153,15 +153,15 @@ async def setup(self): await get_genesis_transactions(context.settings) # Configure the root profile - LOGGER.trace("Configuring the root profile and setting up public DID") + LOGGER.debug("Configuring the root profile and setting up public DID") self.root_profile, self.setup_public_did = await wallet_config(context) context = self.root_profile.context - LOGGER.trace("Root profile configured successfully") + LOGGER.debug("Root profile configured successfully") # Multiledger Setup ledger_config_list = context.settings.get("ledger.ledger_config_list") if ledger_config_list and len(ledger_config_list) > 0: - LOGGER.trace("Setting up multiledger manager") + LOGGER.debug("Setting up multiledger manager") context.injector.bind_provider( BaseMultipleLedgerManager, MultiIndyLedgerManagerProvider(self.root_profile), @@ -177,7 +177,7 @@ async def setup(self): self.root_profile.BACKEND_NAME == "askar" and ledger.BACKEND_NAME == "indy-vdr" ): - LOGGER.trace("Binding IndyCredxVerifier for 'askar' backend.") + LOGGER.debug("Binding IndyCredxVerifier for 'askar' backend.") context.injector.bind_provider( IndyVerifier, ClassProvider( @@ -189,7 +189,7 @@ async def setup(self): self.root_profile.BACKEND_NAME == "askar-anoncreds" and ledger.BACKEND_NAME == "indy-vdr" ): - LOGGER.trace( + LOGGER.debug( "Binding IndyCredxVerifier for 'askar-anoncreds' backend." ) context.injector.bind_provider( @@ -228,7 +228,7 @@ async def setup(self): context.injector.bind_instance( InboundTransportManager, self.inbound_transport_manager ) - LOGGER.trace("Inbound transports registered successfully.") + LOGGER.debug("Inbound transports registered successfully.") # Register all outbound transports LOGGER.debug("Setting up outbound transports.") @@ -236,46 +236,46 @@ async def setup(self): self.root_profile, self.handle_not_delivered ) await self.outbound_transport_manager.setup() - LOGGER.trace("Outbound transports registered successfully.") + LOGGER.debug("Outbound transports registered successfully.") # Initialize dispatcher - LOGGER.trace("Initializing dispatcher.") + LOGGER.debug("Initializing dispatcher.") self.dispatcher = Dispatcher(self.root_profile) await self.dispatcher.setup() - LOGGER.trace("Dispatcher initialized successfully.") + LOGGER.debug("Dispatcher initialized successfully.") wire_format = context.inject_or(BaseWireFormat) if wire_format and hasattr(wire_format, "task_queue"): wire_format.task_queue = self.dispatcher.task_queue - LOGGER.trace("Wire format task queue bound to dispatcher.") + LOGGER.debug("Wire format task queue bound to dispatcher.") # Bind manager for multitenancy related tasks if context.settings.get("multitenant.enabled"): - LOGGER.trace("Multitenant is enabled. Binding MultitenantManagerProvider.") + LOGGER.debug("Multitenant is enabled. Binding MultitenantManagerProvider.") context.injector.bind_provider( BaseMultitenantManager, MultitenantManagerProvider(self.root_profile) ) # Bind route manager provider - LOGGER.trace("Binding RouteManagerProvider.") + LOGGER.debug("Binding RouteManagerProvider.") context.injector.bind_provider( RouteManager, RouteManagerProvider(self.root_profile) ) # Bind OobMessageProcessor to be able to receive and process unencrypted messages - LOGGER.trace("Binding OobMessageProcessor.") + LOGGER.debug("Binding OobMessageProcessor.") context.injector.bind_instance( OobMessageProcessor, OobMessageProcessor(inbound_message_router=self.inbound_message_router), ) # Bind default PyLD document loader - LOGGER.trace("Binding default DocumentLoader.") + LOGGER.debug("Binding default DocumentLoader.") context.injector.bind_instance(DocumentLoader, DocumentLoader(self.root_profile)) # Admin API if context.settings.get("admin.enabled"): - LOGGER.trace("Admin API is enabled. Attempting to register admin server.") + LOGGER.debug("Admin API is enabled. Attempting to register admin server.") try: admin_host = context.settings.get("admin.host", "0.0.0.0") admin_port = context.settings.get("admin.port", "80") @@ -299,7 +299,7 @@ async def setup(self): # Fetch stats collector, if any collector = context.inject_or(Collector) if collector: - LOGGER.trace("Stats collector found. Wrapping methods for collection.") + LOGGER.debug("Stats collector found. Wrapping methods for collection.") # add stats to our own methods collector.wrap( self, @@ -318,35 +318,35 @@ async def setup(self): "find_inbound_connection", ), ) - LOGGER.trace("Methods wrapped with stats collector.") + LOGGER.debug("Methods wrapped with stats collector.") async def start(self) -> None: """Start the agent.""" LOGGER.debug("Starting the Conductor agent.") context = self.root_profile.context await self.check_for_valid_wallet_type(self.root_profile) - LOGGER.trace("Wallet type validated.") + LOGGER.debug("Wallet type validated.") if not context.settings.get("transport.disabled"): # Start up transports if enabled try: - LOGGER.trace("Transport not disabled. Starting inbound transports.") + LOGGER.debug("Transport not disabled. Starting inbound transports.") await self.inbound_transport_manager.start() - LOGGER.trace("Inbound transports started successfully.") + LOGGER.debug("Inbound transports started successfully.") except Exception: LOGGER.exception("Unable to start inbound transports.") raise try: - LOGGER.trace("Starting outbound transports.") + LOGGER.debug("Starting outbound transports.") await self.outbound_transport_manager.start() - LOGGER.trace("Outbound transports started successfully.") + LOGGER.debug("Outbound transports started successfully.") except Exception: LOGGER.exception("Unable to start outbound transports.") raise # Start up Admin server if self.admin_server: - LOGGER.trace("Admin server present. Starting admin server.") + LOGGER.debug("Admin server present. Starting admin server.") try: await self.admin_server.start() LOGGER.debug("Admin server started successfully.") @@ -360,11 +360,10 @@ async def start(self) -> None: self.admin_server.outbound_message_router, ) context.injector.bind_instance(BaseResponder, responder) - LOGGER.trace("Admin responder bound to injector.") + LOGGER.debug("Admin responder bound to injector.") # Get agent label default_label = context.settings.get("default_label") - public_did = self.setup_public_did and self.setup_public_did.did LOGGER.debug("Agent label: %s", default_label) if context.settings.get("log.banner", True): @@ -400,7 +399,7 @@ async def start(self) -> None: from_version_storage = None from_version = None agent_version = f"v{__version__}" - LOGGER.trace("Recording ACA-Py version in wallet if needed.") + LOGGER.debug("Recording ACA-Py version in wallet if needed.") async with self.root_profile.session() as session: storage: BaseStorage = session.context.inject(BaseStorage) try: @@ -420,7 +419,7 @@ async def start(self) -> None: force_upgrade_flag = ( self.root_profile.settings.get("upgrade.force_upgrade") or False ) - LOGGER.trace( + LOGGER.debug( "Force upgrade flag: %s, From version config: %s", force_upgrade_flag, from_version_config, @@ -436,12 +435,12 @@ async def start(self) -> None: from_version = from_version_storage else: from_version = from_version_config - LOGGER.trace( + LOGGER.debug( "Determined from_version based on force_upgrade: %s", from_version ) else: from_version = from_version_storage or from_version_config - LOGGER.trace("Determined from_version: %s", from_version) + LOGGER.debug("Determined from_version: %s", from_version) if not from_version: LOGGER.warning( @@ -453,13 +452,13 @@ async def start(self) -> None: ) from_version = DEFAULT_ACAPY_VERSION self.root_profile.settings.set_value("upgrade.from_version", from_version) - LOGGER.trace("Set upgrade.from_version to default: %s", from_version) + LOGGER.debug("Set upgrade.from_version to default: %s", from_version) config_available_list = get_upgrade_version_list( config_path=self.root_profile.settings.get("upgrade.config_path"), from_version=from_version, ) - LOGGER.trace("Available upgrade versions: %s", config_available_list) + LOGGER.debug("Available upgrade versions: %s", config_available_list) if len(config_available_list) >= 1: LOGGER.info("Upgrade configurations available. Initiating upgrade.") @@ -590,7 +589,7 @@ async def start(self) -> None: mediation_connections_invite = context.settings.get( "mediation.connections_invite", False ) - LOGGER.trace( + LOGGER.debug( "Mediation connections invite flag: %s", mediation_connections_invite ) invitation_handler = ( @@ -601,7 +600,7 @@ async def start(self) -> None: if not mediation_invite_record.used: # clear previous mediator configuration before establishing a new one - LOGGER.trace( + LOGGER.debug( "Mediation invite not used. " "Clearing default mediator before accepting new invite." ) @@ -623,7 +622,7 @@ async def start(self) -> None: await MediationInviteStore( session.context.inject(BaseStorage) ).mark_default_invite_as_used() - LOGGER.trace("Marked mediation invite as used.") + LOGGER.debug("Marked mediation invite as used.") await record.metadata_set( session, MediationManager.SEND_REQ_AFTER_CONNECTION, True @@ -631,11 +630,11 @@ async def start(self) -> None: await record.metadata_set( session, MediationManager.SET_TO_DEFAULT_ON_GRANTED, True ) - LOGGER.trace("Set mediation metadata after connection.") + LOGGER.debug("Set mediation metadata after connection.") LOGGER.info("Attempting to connect to mediator...") del mgr - LOGGER.trace("Mediation manager deleted after setting up mediator.") + LOGGER.debug("Mediation manager deleted after setting up mediator.") except Exception: LOGGER.exception("Error accepting mediation invitation.") @@ -649,7 +648,7 @@ async def start(self) -> None: ) # notify protocols of startup status - LOGGER.trace("Notifying protocols of startup status.") + LOGGER.debug("Notifying protocols of startup status.") await self.root_profile.notify(STARTUP_EVENT_TOPIC, {}) LOGGER.debug("Startup notification sent.") @@ -664,16 +663,16 @@ async def stop(self, timeout=1.0): shutdown = TaskQueue() if self.dispatcher: - LOGGER.trace("Initiating shutdown of dispatcher.") + LOGGER.debug("Initiating shutdown of dispatcher.") shutdown.run(self.dispatcher.complete()) if self.admin_server: - LOGGER.trace("Initiating shutdown of admin server.") + LOGGER.debug("Initiating shutdown of admin server.") shutdown.run(self.admin_server.stop()) if self.inbound_transport_manager: - LOGGER.trace("Initiating shutdown of inbound transport manager.") + LOGGER.debug("Initiating shutdown of inbound transport manager.") shutdown.run(self.inbound_transport_manager.stop()) if self.outbound_transport_manager: - LOGGER.trace("Initiating shutdown of outbound transport manager.") + LOGGER.debug("Initiating shutdown of outbound transport manager.") shutdown.run(self.outbound_transport_manager.stop()) if self.root_profile: @@ -682,12 +681,12 @@ async def stop(self, timeout=1.0): if multitenant_mgr: LOGGER.debug("Closing multitenant profiles.") for profile in multitenant_mgr.open_profiles: - LOGGER.trace("Closing profile: %s", profile.name) + LOGGER.debug("Closing profile: %s", profile.name) shutdown.run(profile.close()) LOGGER.debug("Closing root profile.") shutdown.run(self.root_profile.close()) - LOGGER.trace("Waiting for shutdown tasks to complete with timeout=%f.", timeout) + LOGGER.debug("Waiting for shutdown tasks to complete with timeout=%f.", timeout) await shutdown.complete(timeout) LOGGER.info("Conductor agent stopped successfully.") @@ -980,19 +979,7 @@ async def check_for_valid_wallet_type(self, profile): async def check_for_wallet_upgrades_in_progress(self): """Check for upgrade and upgrade if needed.""" - - # We need to use the correct multitenant manager for single vs multiple wallets - # here because the multitenant provider hasn't been initialized yet. - manager_type = self.context.settings.get_value( - "multitenant.wallet_type", default="basic" - ).lower() - - manager_class = MultitenantManagerProvider.MANAGER_TYPES.get( - manager_type, manager_type - ) - - multitenant_mgr = self.context.inject_or(manager_class) - if multitenant_mgr: + if self.context.settings.get_value("multitenant.enabled"): subwallet_profiles = await get_subwallet_profiles_from_storage( self.root_profile ) diff --git a/acapy_agent/core/plugin_registry.py b/acapy_agent/core/plugin_registry.py index 542203c819..59cebdd0bd 100644 --- a/acapy_agent/core/plugin_registry.py +++ b/acapy_agent/core/plugin_registry.py @@ -132,7 +132,7 @@ def register_plugin(self, module_name: str) -> Optional[ModuleType]: if self._is_valid_plugin(mod, module_name): self._plugins[module_name] = mod - LOGGER.trace("Registered plugin: %s", module_name) + LOGGER.debug("Registered plugin: %s", module_name) return mod LOGGER.debug("Failed to register plugin: %s", module_name) @@ -141,7 +141,7 @@ def register_plugin(self, module_name: str) -> Optional[ModuleType]: def _is_already_registered(self, module_name: str) -> bool: """Check if the plugin is already registered.""" if module_name in self._plugins: - LOGGER.trace("Plugin %s is already registered.", module_name) + LOGGER.debug("Plugin %s is already registered.", module_name) return True return False @@ -165,7 +165,7 @@ def _is_valid_plugin(self, mod: ModuleType, module_name: str) -> bool: """Validate the plugin based on various criteria.""" # Check if the plugin has a 'setup' method if hasattr(mod, "setup"): - LOGGER.trace("Plugin %s has a 'setup' method.", module_name) + LOGGER.debug("Plugin %s has a 'setup' method.", module_name) return True # Check for 'routes' or 'message_types' modules @@ -174,7 +174,7 @@ def _is_valid_plugin(self, mod: ModuleType, module_name: str) -> bool: routes = ClassLoader.load_module("routes", module_name) message_types = ClassLoader.load_module("message_types", module_name) if routes or message_types: - LOGGER.trace("Plugin %s has 'routes' or 'message_types'.", module_name) + LOGGER.debug("Plugin %s has 'routes' or 'message_types'.", module_name) return True # Check for 'definition' module with 'versions' attribute @@ -196,7 +196,7 @@ def _is_valid_plugin(self, mod: ModuleType, module_name: str) -> bool: # Validate the 'versions' attribute try: self.validate_version(definition.versions, module_name) - LOGGER.trace("Plugin %s has valid versions.", module_name) + LOGGER.debug("Plugin %s has valid versions.", module_name) return True except ProtocolDefinitionValidationError as e: LOGGER.error( @@ -208,7 +208,7 @@ def _is_valid_plugin(self, mod: ModuleType, module_name: str) -> bool: def register_package(self, package_name: str) -> Sequence[ModuleType]: """Register all modules (sub-packages) under a given package name.""" - LOGGER.trace("Registering package: %s", package_name) + LOGGER.debug("Registering package: %s", package_name) try: module_names = ClassLoader.scan_subpackages(package_name) except ModuleLoadError: @@ -219,28 +219,28 @@ def register_package(self, package_name: str) -> Sequence[ModuleType]: for module_name in module_names: # Skip any module whose last segment is 'tests' if module_name.split(".")[-1] == "tests": - LOGGER.trace("Skipping test module: %s", module_name) + LOGGER.debug("Skipping test module: %s", module_name) continue plugin = self.register_plugin(module_name) if plugin: registered_plugins.append(plugin) else: - LOGGER.trace("Failed to register %s under %s", module_name, package_name) + LOGGER.debug("Failed to register %s under %s", module_name, package_name) return registered_plugins async def init_context(self, context: InjectionContext) -> None: """Call plugin setup methods on the current context.""" - LOGGER.trace("Initializing plugin context for %d plugins", len(self._plugins)) + LOGGER.debug("Initializing plugin context for %d plugins", len(self._plugins)) for plugin in self._plugins.values(): plugin_name = plugin.__name__ if hasattr(plugin, "setup"): - LOGGER.trace("Running setup for plugin: %s", plugin_name) + LOGGER.debug("Running setup for plugin: %s", plugin_name) await plugin.setup(context) else: - LOGGER.trace( + LOGGER.debug( "Loading protocols for plugin without setup: %s", plugin_name ) await self.load_protocols(context, plugin) @@ -259,55 +259,55 @@ async def load_protocol_version( goal_code_registry = context.inject(GoalCodeRegistry) module_name = mod.__name__ - LOGGER.trace("Loading protocol version for module: %s", module_name) + LOGGER.debug("Loading protocol version for module: %s", module_name) if hasattr(mod, "MESSAGE_TYPES"): - LOGGER.trace("Registering message types for: %s", module_name) + LOGGER.debug("Registering message types for: %s", module_name) protocol_registry.register_message_types( mod.MESSAGE_TYPES, version_definition=version_definition ) if hasattr(mod, "CONTROLLERS"): - LOGGER.trace("Registering controllers for: %s", module_name) + LOGGER.debug("Registering controllers for: %s", module_name) protocol_registry.register_controllers(mod.CONTROLLERS) goal_code_registry.register_controllers(mod.CONTROLLERS) async def load_protocols(self, context: InjectionContext, plugin: ModuleType) -> None: """For modules that don't implement setup, register protocols manually.""" plugin_name = plugin.__name__ - LOGGER.trace("Loading protocols for plugin: %s", plugin_name) + LOGGER.debug("Loading protocols for plugin: %s", plugin_name) # If this module contains message_types, then assume that # this is a valid module of the old style (not versioned) try: message_types_path = f"{plugin_name}.message_types" - LOGGER.trace("Attempting to load message types from: %s", message_types_path) + LOGGER.debug("Attempting to load message types from: %s", message_types_path) mod = ClassLoader.load_module(message_types_path) except ModuleLoadError as e: LOGGER.error("Error loading plugin module message types: %s", e) return if mod: - LOGGER.trace("Found non-versioned message types for: %s", plugin_name) + LOGGER.debug("Found non-versioned message types for: %s", plugin_name) await self.load_protocol_version(context, mod) else: # Otherwise, try check for definition.py for versioned protocol packages try: definition_path = f"{plugin_name}.definition" - LOGGER.trace("Attempting to load definition from: %s", definition_path) + LOGGER.debug("Attempting to load definition from: %s", definition_path) definition = ClassLoader.load_module(definition_path) except ModuleLoadError as e: LOGGER.error("Error loading plugin definition module: %s", e) return if definition: - LOGGER.trace("Loading versioned protocols for: %s", plugin_name) + LOGGER.debug("Loading versioned protocols for: %s", plugin_name) for protocol_version in definition.versions: version_path = ( f"{plugin_name}.{protocol_version['path']}.message_types" ) try: - LOGGER.trace("Loading message types from: %s", version_path) + LOGGER.debug("Loading message types from: %s", version_path) mod = ClassLoader.load_module(version_path) except ModuleLoadError as e: LOGGER.error( @@ -324,20 +324,20 @@ async def load_protocols(self, context: InjectionContext, plugin: ModuleType) -> async def register_admin_routes(self, app) -> None: """Call route registration methods on the current context.""" - LOGGER.trace("Registering admin routes for %d plugins", len(self._plugins)) + LOGGER.debug("Registering admin routes for %d plugins", len(self._plugins)) for plugin in self._plugins.values(): plugin_name = plugin.__name__ - LOGGER.trace("Processing routes for plugin: %s", plugin_name) + LOGGER.debug("Processing routes for plugin: %s", plugin_name) mod = None definition = ClassLoader.load_module("definition", plugin_name) if definition: # Load plugin routes that are in a versioned package. - LOGGER.trace("Loading versioned routes for: %s", plugin_name) + LOGGER.debug("Loading versioned routes for: %s", plugin_name) for plugin_version in definition.versions: version_path = f"{plugin_name}.{plugin_version['path']}.routes" try: - LOGGER.trace("Loading routes from: %s", version_path) + LOGGER.debug("Loading routes from: %s", version_path) mod = ClassLoader.load_module(version_path) except ModuleLoadError as e: LOGGER.error( @@ -346,25 +346,25 @@ async def register_admin_routes(self, app) -> None: continue if mod and hasattr(mod, "register"): - LOGGER.trace("Registering routes for: %s", plugin_name) + LOGGER.debug("Registering routes for: %s", plugin_name) await mod.register(app) else: # Load plugin routes that aren't in a versioned package. routes_path = f"{plugin_name}.routes" try: - LOGGER.trace("Loading non-versioned routes from: %s", routes_path) + LOGGER.debug("Loading non-versioned routes from: %s", routes_path) mod = ClassLoader.load_module(routes_path) except ModuleLoadError as e: LOGGER.error("Error loading admin routes from %s: %s", routes_path, e) continue if mod and hasattr(mod, "register"): - LOGGER.trace("Registering routes for: %s", plugin_name) + LOGGER.debug("Registering routes for: %s", plugin_name) await mod.register(app) def register_protocol_events(self, context: InjectionContext) -> None: """Call route register_events methods on the current context.""" - LOGGER.trace("Registering protocol events for %d plugins", len(self._plugins)) + LOGGER.debug("Registering protocol events for %d plugins", len(self._plugins)) event_bus = context.inject_or(EventBus) if not event_bus: @@ -373,74 +373,74 @@ def register_protocol_events(self, context: InjectionContext) -> None: for plugin in self._plugins.values(): plugin_name = plugin.__name__ - LOGGER.trace("Processing events for plugin: %s", plugin_name) + LOGGER.debug("Processing events for plugin: %s", plugin_name) mod = None definition = ClassLoader.load_module("definition", plugin_name) if definition: # Load plugin routes that are in a versioned package. - LOGGER.trace("Loading versioned events for: %s", plugin_name) + LOGGER.debug("Loading versioned events for: %s", plugin_name) for plugin_version in definition.versions: version_path = f"{plugin_name}.{plugin_version['path']}.routes" try: - LOGGER.trace("Loading events from: %s", version_path) + LOGGER.debug("Loading events from: %s", version_path) mod = ClassLoader.load_module(version_path) except ModuleLoadError as e: LOGGER.error("Error loading events from %s: %s", version_path, e) continue if mod and hasattr(mod, "register_events"): - LOGGER.trace("Registering events from: %s", version_path) + LOGGER.debug("Registering events from: %s", version_path) mod.register_events(event_bus) else: # Load plugin routes that aren't in a versioned package. routes_path = f"{plugin_name}.routes" try: - LOGGER.trace("Loading non-versioned events from: %s", routes_path) + LOGGER.debug("Loading non-versioned events from: %s", routes_path) mod = ClassLoader.load_module(routes_path) except ModuleLoadError as e: LOGGER.error("Error loading events from %s: %s", routes_path, e) continue if mod and hasattr(mod, "register_events"): - LOGGER.trace("Registering events from: %s", version_path) + LOGGER.debug("Registering events from: %s", version_path) mod.register_events(event_bus) def post_process_routes(self, app) -> None: """Call route binary file response OpenAPI fixups if applicable.""" - LOGGER.trace("Post-processing routes for %d plugins", len(self._plugins)) + LOGGER.debug("Post-processing routes for %d plugins", len(self._plugins)) for plugin in self._plugins.values(): plugin_name = plugin.__name__ - LOGGER.trace("Post-processing routes for plugin: %s", plugin_name) + LOGGER.debug("Post-processing routes for plugin: %s", plugin_name) mod = None definition = ClassLoader.load_module("definition", plugin_name) if definition: # Set binary file responses for routes that are in a versioned package. - LOGGER.trace("Processing versioned routes for: %s", plugin_name) + LOGGER.debug("Processing versioned routes for: %s", plugin_name) for plugin_version in definition.versions: version_path = f"{plugin_name}.{plugin_version['path']}.routes" try: - LOGGER.trace("Loading routes from: %s", version_path) + LOGGER.debug("Loading routes from: %s", version_path) mod = ClassLoader.load_module(version_path) except ModuleLoadError as e: LOGGER.error("Error loading routes from %s: %s", version_path, e) continue if mod and hasattr(mod, "post_process_routes"): - LOGGER.trace("Post-processing routes for %s", plugin_name) + LOGGER.debug("Post-processing routes for %s", plugin_name) mod.post_process_routes(app) else: # Set binary file responses for routes not in a versioned package. routes_path = f"{plugin_name}.routes" try: - LOGGER.trace("Loading non-versioned routes from: %s", routes_path) + LOGGER.debug("Loading non-versioned routes from: %s", routes_path) mod = ClassLoader.load_module(routes_path) except ModuleLoadError as e: LOGGER.error("Error loading routes from %s: %s", routes_path, e) continue if mod and hasattr(mod, "post_process_routes"): - LOGGER.trace("Post-processing routes for %s", plugin_name) + LOGGER.debug("Post-processing routes for %s", plugin_name) mod.post_process_routes(app) def __repr__(self) -> str: diff --git a/acapy_agent/indy/models/cred_def.py b/acapy_agent/indy/models/cred_def.py index dbdaea2b2d..4c5547f96e 100644 --- a/acapy_agent/indy/models/cred_def.py +++ b/acapy_agent/indy/models/cred_def.py @@ -6,8 +6,8 @@ from ...messaging.valid import ( INDY_CRED_DEF_ID_EXAMPLE, INDY_CRED_DEF_ID_VALIDATE, - INDY_VERSION_EXAMPLE, - INDY_VERSION_VALIDATE, + MAJOR_MINOR_VERSION_EXAMPLE, + MAJOR_MINOR_VERSION_VALIDATE, NUM_STR_WHOLE_EXAMPLE, NUM_STR_WHOLE_VALIDATE, ) @@ -92,10 +92,10 @@ class CredentialDefinitionSchema(OpenAPISchema): """Marshmallow schema for indy cred def.""" ver = fields.Str( - validate=INDY_VERSION_VALIDATE, + validate=MAJOR_MINOR_VERSION_VALIDATE, metadata={ "description": "Node protocol version", - "example": INDY_VERSION_EXAMPLE, + "example": MAJOR_MINOR_VERSION_EXAMPLE, }, ) ident = fields.Str( diff --git a/acapy_agent/indy/models/pres_preview.py b/acapy_agent/indy/models/pres_preview.py index 496c1e93cd..d9e740193e 100644 --- a/acapy_agent/indy/models/pres_preview.py +++ b/acapy_agent/indy/models/pres_preview.py @@ -16,8 +16,8 @@ from ...messaging.valid import ( INDY_CRED_DEF_ID_EXAMPLE, INDY_CRED_DEF_ID_VALIDATE, - INDY_PREDICATE_EXAMPLE, - INDY_PREDICATE_VALIDATE, + PREDICATE_EXAMPLE, + PREDICATE_VALIDATE, ) from ...multitenant.base import BaseMultitenantManager from ...protocols.didcomm_prefix import DIDCommPrefix @@ -100,10 +100,10 @@ class Meta: ) predicate = fields.Str( required=True, - validate=INDY_PREDICATE_VALIDATE, + validate=PREDICATE_VALIDATE, metadata={ "description": "Predicate type ('<', '<=', '>=', or '>')", - "example": INDY_PREDICATE_EXAMPLE, + "example": PREDICATE_EXAMPLE, }, ) threshold = fields.Int( diff --git a/acapy_agent/indy/models/proof_request.py b/acapy_agent/indy/models/proof_request.py index 1c87ecf454..f27ac5a8f6 100644 --- a/acapy_agent/indy/models/proof_request.py +++ b/acapy_agent/indy/models/proof_request.py @@ -15,14 +15,14 @@ from ...messaging.models.openapi import OpenAPISchema from ...messaging.valid import ( INDY_CRED_DEF_ID_EXAMPLE, - INDY_PREDICATE_EXAMPLE, - INDY_PREDICATE_VALIDATE, - INDY_VERSION_EXAMPLE, - INDY_VERSION_VALIDATE, INT_EPOCH_EXAMPLE, INT_EPOCH_VALIDATE, + MAJOR_MINOR_VERSION_EXAMPLE, + MAJOR_MINOR_VERSION_VALIDATE, NUM_STR_NATURAL_EXAMPLE, NUM_STR_NATURAL_VALIDATE, + PREDICATE_EXAMPLE, + PREDICATE_VALIDATE, ) @@ -125,10 +125,10 @@ class IndyProofReqPredSpecSchema(OpenAPISchema): ) p_type = fields.Str( required=True, - validate=INDY_PREDICATE_VALIDATE, + validate=PREDICATE_VALIDATE, metadata={ "description": "Predicate type ('<', '<=', '>=', or '>')", - "example": INDY_PREDICATE_EXAMPLE, + "example": PREDICATE_EXAMPLE, }, ) p_value = fields.Int( @@ -251,10 +251,10 @@ class Meta: version = fields.Str( required=False, dump_default="1.0", - validate=INDY_VERSION_VALIDATE, + validate=MAJOR_MINOR_VERSION_VALIDATE, metadata={ "description": "Proof request version", - "example": INDY_VERSION_EXAMPLE, + "example": MAJOR_MINOR_VERSION_EXAMPLE, }, ) requested_attributes = fields.Dict( diff --git a/acapy_agent/indy/models/revocation.py b/acapy_agent/indy/models/revocation.py index a7a2795fbd..14c5617709 100644 --- a/acapy_agent/indy/models/revocation.py +++ b/acapy_agent/indy/models/revocation.py @@ -12,8 +12,8 @@ INDY_CRED_DEF_ID_VALIDATE, INDY_REV_REG_ID_EXAMPLE, INDY_REV_REG_ID_VALIDATE, - INDY_VERSION_EXAMPLE, - INDY_VERSION_VALIDATE, + MAJOR_MINOR_VERSION_EXAMPLE, + MAJOR_MINOR_VERSION_VALIDATE, NATURAL_NUM_EXAMPLE, NATURAL_NUM_VALIDATE, ) @@ -180,10 +180,10 @@ class Meta: unknown = EXCLUDE ver = fields.Str( - validate=INDY_VERSION_VALIDATE, + validate=MAJOR_MINOR_VERSION_VALIDATE, metadata={ "description": "Version of revocation registry definition", - "example": INDY_VERSION_EXAMPLE, + "example": MAJOR_MINOR_VERSION_EXAMPLE, }, ) id_ = fields.Str( @@ -294,10 +294,10 @@ class Meta: unknown = EXCLUDE ver = fields.Str( - validate=INDY_VERSION_VALIDATE, + validate=MAJOR_MINOR_VERSION_VALIDATE, metadata={ "description": "Version of revocation registry entry", - "example": INDY_VERSION_EXAMPLE, + "example": MAJOR_MINOR_VERSION_EXAMPLE, }, ) value = fields.Nested( diff --git a/acapy_agent/indy/models/schema.py b/acapy_agent/indy/models/schema.py index 5dff944356..df91802bc7 100644 --- a/acapy_agent/indy/models/schema.py +++ b/acapy_agent/indy/models/schema.py @@ -6,8 +6,8 @@ from ...messaging.valid import ( INDY_SCHEMA_ID_EXAMPLE, INDY_SCHEMA_ID_VALIDATE, - INDY_VERSION_EXAMPLE, - INDY_VERSION_VALIDATE, + MAJOR_MINOR_VERSION_EXAMPLE, + MAJOR_MINOR_VERSION_VALIDATE, NATURAL_NUM_EXAMPLE, NATURAL_NUM_VALIDATE, ) @@ -17,10 +17,10 @@ class SchemaSchema(OpenAPISchema): """Marshmallow schema for indy schema.""" ver = fields.Str( - validate=INDY_VERSION_VALIDATE, + validate=MAJOR_MINOR_VERSION_VALIDATE, metadata={ "description": "Node protocol version", - "example": INDY_VERSION_EXAMPLE, + "example": MAJOR_MINOR_VERSION_EXAMPLE, }, ) ident = fields.Str( @@ -38,8 +38,11 @@ class SchemaSchema(OpenAPISchema): } ) version = fields.Str( - validate=INDY_VERSION_VALIDATE, - metadata={"description": "Schema version", "example": INDY_VERSION_EXAMPLE}, + validate=MAJOR_MINOR_VERSION_VALIDATE, + metadata={ + "description": "Schema version", + "example": MAJOR_MINOR_VERSION_EXAMPLE, + }, ) attr_names = fields.List( fields.Str(metadata={"description": "Attribute name", "example": "score"}), diff --git a/acapy_agent/ledger/routes.py b/acapy_agent/ledger/routes.py index 4d63eb5af3..fee5e15261 100644 --- a/acapy_agent/ledger/routes.py +++ b/acapy_agent/ledger/routes.py @@ -25,10 +25,10 @@ ENDPOINT_VALIDATE, INDY_DID_EXAMPLE, INDY_DID_VALIDATE, - INDY_RAW_PUBLIC_KEY_EXAMPLE, - INDY_RAW_PUBLIC_KEY_VALIDATE, INT_EPOCH_EXAMPLE, INT_EPOCH_VALIDATE, + RAW_ED25519_2018_PUBLIC_KEY_EXAMPLE, + RAW_ED25519_2018_PUBLIC_KEY_VALIDATE, UUID4_EXAMPLE, ) from ..multitenant.base import BaseMultitenantManager @@ -133,10 +133,10 @@ class RegisterLedgerNymQueryStringSchema(OpenAPISchema): ) verkey = fields.Str( required=True, - validate=INDY_RAW_PUBLIC_KEY_VALIDATE, + validate=RAW_ED25519_2018_PUBLIC_KEY_VALIDATE, metadata={ "description": "Verification key", - "example": INDY_RAW_PUBLIC_KEY_EXAMPLE, + "example": RAW_ED25519_2018_PUBLIC_KEY_EXAMPLE, }, ) alias = fields.Str( @@ -230,10 +230,10 @@ class GetDIDVerkeyResponseSchema(OpenAPISchema): verkey = fields.Str( allow_none=True, - validate=INDY_RAW_PUBLIC_KEY_VALIDATE, + validate=RAW_ED25519_2018_PUBLIC_KEY_VALIDATE, metadata={ "description": "Full verification key", - "example": INDY_RAW_PUBLIC_KEY_EXAMPLE, + "example": RAW_ED25519_2018_PUBLIC_KEY_EXAMPLE, }, ) diff --git a/acapy_agent/messaging/credential_definitions/util.py b/acapy_agent/messaging/credential_definitions/util.py index fb732ecd44..1cca544747 100644 --- a/acapy_agent/messaging/credential_definitions/util.py +++ b/acapy_agent/messaging/credential_definitions/util.py @@ -13,8 +13,8 @@ INDY_DID_VALIDATE, INDY_SCHEMA_ID_EXAMPLE, INDY_SCHEMA_ID_VALIDATE, - INDY_VERSION_EXAMPLE, - INDY_VERSION_VALIDATE, + MAJOR_MINOR_VERSION_EXAMPLE, + MAJOR_MINOR_VERSION_VALIDATE, ) CRED_DEF_SENT_RECORD_TYPE = "cred_def_sent" @@ -41,8 +41,11 @@ class CredDefQueryStringSchema(OpenAPISchema): ) schema_version = fields.Str( required=False, - validate=INDY_VERSION_VALIDATE, - metadata={"description": "Schema version", "example": INDY_VERSION_EXAMPLE}, + validate=MAJOR_MINOR_VERSION_VALIDATE, + metadata={ + "description": "Schema version", + "example": MAJOR_MINOR_VERSION_EXAMPLE, + }, ) issuer_did = fields.Str( required=False, diff --git a/acapy_agent/messaging/decorators/attach_decorator.py b/acapy_agent/messaging/decorators/attach_decorator.py index 98fc177996..7b17c6f670 100644 --- a/acapy_agent/messaging/decorators/attach_decorator.py +++ b/acapy_agent/messaging/decorators/attach_decorator.py @@ -29,8 +29,8 @@ BASE64_VALIDATE, BASE64URL_NO_PAD_EXAMPLE, BASE64URL_NO_PAD_VALIDATE, - INDY_ISO8601_DATETIME_EXAMPLE, - INDY_ISO8601_DATETIME_VALIDATE, + ISO8601_DATETIME_EXAMPLE, + ISO8601_DATETIME_VALIDATE, JWS_HEADER_KID_EXAMPLE, JWS_HEADER_KID_VALIDATE, SHA256_EXAMPLE, @@ -778,12 +778,12 @@ class Meta: ) lastmod_time = fields.Str( required=False, - validate=INDY_ISO8601_DATETIME_VALIDATE, + validate=ISO8601_DATETIME_VALIDATE, metadata={ "description": ( "Hint regarding last modification datetime, in ISO-8601 format" ), - "example": INDY_ISO8601_DATETIME_EXAMPLE, + "example": ISO8601_DATETIME_EXAMPLE, }, ) description = fields.Str( diff --git a/acapy_agent/messaging/decorators/service_decorator.py b/acapy_agent/messaging/decorators/service_decorator.py index 0a4695a01c..e4f94b90e3 100644 --- a/acapy_agent/messaging/decorators/service_decorator.py +++ b/acapy_agent/messaging/decorators/service_decorator.py @@ -9,7 +9,10 @@ from marshmallow import EXCLUDE, fields from ..models.base import BaseModel, BaseModelSchema -from ..valid import INDY_RAW_PUBLIC_KEY_EXAMPLE, INDY_RAW_PUBLIC_KEY_VALIDATE +from ..valid import ( + RAW_ED25519_2018_PUBLIC_KEY_EXAMPLE, + RAW_ED25519_2018_PUBLIC_KEY_VALIDATE, +) class ServiceDecorator(BaseModel): @@ -82,10 +85,10 @@ class Meta: recipient_keys = fields.List( fields.Str( - validate=INDY_RAW_PUBLIC_KEY_VALIDATE, + validate=RAW_ED25519_2018_PUBLIC_KEY_VALIDATE, metadata={ "description": "Recipient public key", - "example": INDY_RAW_PUBLIC_KEY_EXAMPLE, + "example": RAW_ED25519_2018_PUBLIC_KEY_EXAMPLE, }, ), data_key="recipientKeys", @@ -102,10 +105,10 @@ class Meta: ) routing_keys = fields.List( fields.Str( - validate=INDY_RAW_PUBLIC_KEY_VALIDATE, + validate=RAW_ED25519_2018_PUBLIC_KEY_VALIDATE, metadata={ "description": "Routing key", - "example": INDY_RAW_PUBLIC_KEY_EXAMPLE, + "example": RAW_ED25519_2018_PUBLIC_KEY_EXAMPLE, }, ), data_key="routingKeys", diff --git a/acapy_agent/messaging/decorators/signature_decorator.py b/acapy_agent/messaging/decorators/signature_decorator.py index 22ec1b38c1..ed7a217611 100644 --- a/acapy_agent/messaging/decorators/signature_decorator.py +++ b/acapy_agent/messaging/decorators/signature_decorator.py @@ -3,7 +3,7 @@ import json import struct import time -from typing import Optional +from typing import Optional, Tuple from marshmallow import EXCLUDE, fields @@ -15,8 +15,8 @@ from ..valid import ( BASE64URL_EXAMPLE, BASE64URL_VALIDATE, - INDY_RAW_PUBLIC_KEY_EXAMPLE, - INDY_RAW_PUBLIC_KEY_VALIDATE, + RAW_ED25519_2018_PUBLIC_KEY_EXAMPLE, + RAW_ED25519_2018_PUBLIC_KEY_VALIDATE, ) @@ -86,7 +86,7 @@ async def create( signer=signer, ) - def decode(self) -> (object, int): + def decode(self) -> Tuple[object, int]: """Decode the signature to its timestamp and value. Returns: @@ -164,9 +164,9 @@ class Meta: ) signer = fields.Str( required=True, - validate=INDY_RAW_PUBLIC_KEY_VALIDATE, + validate=RAW_ED25519_2018_PUBLIC_KEY_VALIDATE, metadata={ "description": "Signer verification key", - "example": INDY_RAW_PUBLIC_KEY_EXAMPLE, + "example": RAW_ED25519_2018_PUBLIC_KEY_EXAMPLE, }, ) diff --git a/acapy_agent/messaging/decorators/timing_decorator.py b/acapy_agent/messaging/decorators/timing_decorator.py index 7ad8fc8c86..cd8dd829d7 100644 --- a/acapy_agent/messaging/decorators/timing_decorator.py +++ b/acapy_agent/messaging/decorators/timing_decorator.py @@ -11,7 +11,7 @@ from ..models.base import BaseModel, BaseModelSchema from ..util import datetime_to_str -from ..valid import INDY_ISO8601_DATETIME_EXAMPLE, INDY_ISO8601_DATETIME_VALIDATE +from ..valid import ISO8601_DATETIME_EXAMPLE, ISO8601_DATETIME_VALIDATE class TimingDecorator(BaseModel): @@ -62,34 +62,34 @@ class Meta: in_time = fields.Str( required=False, - validate=INDY_ISO8601_DATETIME_VALIDATE, + validate=ISO8601_DATETIME_VALIDATE, metadata={ "description": "Time of message receipt", - "example": INDY_ISO8601_DATETIME_EXAMPLE, + "example": ISO8601_DATETIME_EXAMPLE, }, ) out_time = fields.Str( required=False, - validate=INDY_ISO8601_DATETIME_VALIDATE, + validate=ISO8601_DATETIME_VALIDATE, metadata={ "description": "Time of message dispatch", - "example": INDY_ISO8601_DATETIME_EXAMPLE, + "example": ISO8601_DATETIME_EXAMPLE, }, ) stale_time = fields.Str( required=False, - validate=INDY_ISO8601_DATETIME_VALIDATE, + validate=ISO8601_DATETIME_VALIDATE, metadata={ "description": "Time when message should be considered stale", - "example": INDY_ISO8601_DATETIME_EXAMPLE, + "example": ISO8601_DATETIME_EXAMPLE, }, ) expires_time = fields.Str( required=False, - validate=INDY_ISO8601_DATETIME_VALIDATE, + validate=ISO8601_DATETIME_VALIDATE, metadata={ "description": "Time when message should be considered expired", - "example": INDY_ISO8601_DATETIME_EXAMPLE, + "example": ISO8601_DATETIME_EXAMPLE, }, ) delay_milli = fields.Int( @@ -102,9 +102,9 @@ class Meta: ) wait_until_time = fields.Str( required=False, - validate=INDY_ISO8601_DATETIME_VALIDATE, + validate=ISO8601_DATETIME_VALIDATE, metadata={ "description": "Earliest time at which to perform processing", - "example": INDY_ISO8601_DATETIME_EXAMPLE, + "example": ISO8601_DATETIME_EXAMPLE, }, ) diff --git a/acapy_agent/messaging/models/base_record.py b/acapy_agent/messaging/models/base_record.py index 100135486c..f32a8e0c4e 100644 --- a/acapy_agent/messaging/models/base_record.py +++ b/acapy_agent/messaging/models/base_record.py @@ -20,7 +20,7 @@ ) from ...storage.record import StorageRecord from ..util import datetime_to_str, time_now -from ..valid import INDY_ISO8601_DATETIME_EXAMPLE, INDY_ISO8601_DATETIME_VALIDATE +from ..valid import ISO8601_DATETIME_EXAMPLE, ISO8601_DATETIME_VALIDATE from .base import BaseModel, BaseModelError, BaseModelSchema LOGGER = logging.getLogger(__name__) @@ -591,18 +591,18 @@ class Meta: ) created_at = fields.Str( required=False, - validate=INDY_ISO8601_DATETIME_VALIDATE, + validate=ISO8601_DATETIME_VALIDATE, metadata={ "description": "Time of record creation", - "example": INDY_ISO8601_DATETIME_EXAMPLE, + "example": ISO8601_DATETIME_EXAMPLE, }, ) updated_at = fields.Str( required=False, - validate=INDY_ISO8601_DATETIME_VALIDATE, + validate=ISO8601_DATETIME_VALIDATE, metadata={ "description": "Time of last record update", - "example": INDY_ISO8601_DATETIME_EXAMPLE, + "example": ISO8601_DATETIME_EXAMPLE, }, ) diff --git a/acapy_agent/messaging/schemas/routes.py b/acapy_agent/messaging/schemas/routes.py index d8128a5398..c31983d264 100644 --- a/acapy_agent/messaging/schemas/routes.py +++ b/acapy_agent/messaging/schemas/routes.py @@ -49,8 +49,8 @@ B58, INDY_SCHEMA_ID_EXAMPLE, INDY_SCHEMA_ID_VALIDATE, - INDY_VERSION_EXAMPLE, - INDY_VERSION_VALIDATE, + MAJOR_MINOR_VERSION_EXAMPLE, + MAJOR_MINOR_VERSION_VALIDATE, UUID4_EXAMPLE, ) from .util import ( @@ -70,8 +70,11 @@ class SchemaSendRequestSchema(OpenAPISchema): ) schema_version = fields.Str( required=True, - validate=INDY_VERSION_VALIDATE, - metadata={"description": "Schema version", "example": INDY_VERSION_EXAMPLE}, + validate=MAJOR_MINOR_VERSION_VALIDATE, + metadata={ + "description": "Schema version", + "example": MAJOR_MINOR_VERSION_EXAMPLE, + }, ) attributes = fields.List( fields.Str(metadata={"description": "attribute name", "example": "score"}), diff --git a/acapy_agent/messaging/schemas/util.py b/acapy_agent/messaging/schemas/util.py index 35f8ce5bae..9a3e0cf989 100644 --- a/acapy_agent/messaging/schemas/util.py +++ b/acapy_agent/messaging/schemas/util.py @@ -11,8 +11,8 @@ INDY_DID_VALIDATE, INDY_SCHEMA_ID_EXAMPLE, INDY_SCHEMA_ID_VALIDATE, - INDY_VERSION_EXAMPLE, - INDY_VERSION_VALIDATE, + MAJOR_MINOR_VERSION_EXAMPLE, + MAJOR_MINOR_VERSION_VALIDATE, ) @@ -37,8 +37,11 @@ class SchemaQueryStringSchema(OpenAPISchema): ) schema_version = fields.Str( required=False, - validate=INDY_VERSION_VALIDATE, - metadata={"description": "Schema version", "example": INDY_VERSION_EXAMPLE}, + validate=MAJOR_MINOR_VERSION_VALIDATE, + metadata={ + "description": "Schema version", + "example": MAJOR_MINOR_VERSION_EXAMPLE, + }, ) diff --git a/acapy_agent/messaging/tests/test_valid.py b/acapy_agent/messaging/tests/test_valid.py index 0f3b3d3363..b99737c4a8 100644 --- a/acapy_agent/messaging/tests/test_valid.py +++ b/acapy_agent/messaging/tests/test_valid.py @@ -19,20 +19,20 @@ INDY_CRED_REV_ID_VALIDATE, INDY_DID_VALIDATE, INDY_EXTRA_WQL_VALIDATE, - INDY_ISO8601_DATETIME_VALIDATE, - INDY_PREDICATE_VALIDATE, - INDY_RAW_PUBLIC_KEY_VALIDATE, INDY_REV_REG_ID_VALIDATE, INDY_REV_REG_SIZE_VALIDATE, INDY_SCHEMA_ID_VALIDATE, - INDY_VERSION_VALIDATE, INDY_WQL_VALIDATE, INT_EPOCH_VALIDATE, + ISO8601_DATETIME_VALIDATE, JWS_HEADER_KID_VALIDATE, JWT_VALIDATE, + MAJOR_MINOR_VERSION_VALIDATE, NATURAL_NUM_VALIDATE, NUM_STR_NATURAL_VALIDATE, NUM_STR_WHOLE_VALIDATE, + PREDICATE_VALIDATE, + RAW_ED25519_2018_PUBLIC_KEY_VALIDATE, SHA256_VALIDATE, UUID4_VALIDATE, WHOLE_NUM_VALIDATE, @@ -141,9 +141,11 @@ def test_indy_raw_public_key(self): ] for non_indy_raw_public_key in non_indy_raw_public_keys: with self.assertRaises(ValidationError): - INDY_RAW_PUBLIC_KEY_VALIDATE(non_indy_raw_public_key) + RAW_ED25519_2018_PUBLIC_KEY_VALIDATE(non_indy_raw_public_key) - INDY_RAW_PUBLIC_KEY_VALIDATE("Q4zqM7aXqm7gDQkUVLng9hQ4zqM7aXqm7gDQkUVLng9h") + RAW_ED25519_2018_PUBLIC_KEY_VALIDATE( + "Q4zqM7aXqm7gDQkUVLng9hQ4zqM7aXqm7gDQkUVLng9h" + ) def test_jws_header_kid(self): non_kids = [ @@ -283,12 +285,12 @@ def test_version(self): non_versions = ["-1", "", "3_5", "3.5a"] for non_version in non_versions: with self.assertRaises(ValidationError): - INDY_VERSION_VALIDATE(non_version) + MAJOR_MINOR_VERSION_VALIDATE(non_version) - INDY_VERSION_VALIDATE("1.0") - INDY_VERSION_VALIDATE(".05") - INDY_VERSION_VALIDATE("1.2.3") - INDY_VERSION_VALIDATE("..") # perverse but technically OK + MAJOR_MINOR_VERSION_VALIDATE("1.0") + MAJOR_MINOR_VERSION_VALIDATE(".05") + MAJOR_MINOR_VERSION_VALIDATE("1.2.3") + MAJOR_MINOR_VERSION_VALIDATE("..") # perverse but technically OK def test_schema_id(self): non_schema_ids = [ @@ -311,12 +313,12 @@ def test_predicate(self): non_predicates = [">>", "", " >= ", "<<<=", "==", "=", "!="] for non_predicate in non_predicates: with self.assertRaises(ValidationError): - INDY_PREDICATE_VALIDATE(non_predicate) + PREDICATE_VALIDATE(non_predicate) - INDY_PREDICATE_VALIDATE("<") - INDY_PREDICATE_VALIDATE("<=") - INDY_PREDICATE_VALIDATE(">=") - INDY_PREDICATE_VALIDATE(">") + PREDICATE_VALIDATE("<") + PREDICATE_VALIDATE("<=") + PREDICATE_VALIDATE(">=") + PREDICATE_VALIDATE(">") def test_indy_date(self): non_datetimes = [ @@ -329,17 +331,17 @@ def test_indy_date(self): ] for non_datetime in non_datetimes: with self.assertRaises(ValidationError): - INDY_ISO8601_DATETIME_VALIDATE(non_datetime) - - INDY_ISO8601_DATETIME_VALIDATE("2020-01-01 00:00:00Z") - INDY_ISO8601_DATETIME_VALIDATE("2020-01-01T00:00:00Z") - INDY_ISO8601_DATETIME_VALIDATE("2020-01-01T00:00:00") - INDY_ISO8601_DATETIME_VALIDATE("2020-01-01 00:00:00") - INDY_ISO8601_DATETIME_VALIDATE("2020-01-01 00:00:00+00:00") - INDY_ISO8601_DATETIME_VALIDATE("2020-01-01 00:00:00-00:00") - INDY_ISO8601_DATETIME_VALIDATE("2020-01-01 00:00-00:00") - INDY_ISO8601_DATETIME_VALIDATE("2020-01-01 00:00:00.1-00:00") - INDY_ISO8601_DATETIME_VALIDATE("2020-01-01 00:00:00.123456-00:00") + ISO8601_DATETIME_VALIDATE(non_datetime) + + ISO8601_DATETIME_VALIDATE("2020-01-01 00:00:00Z") + ISO8601_DATETIME_VALIDATE("2020-01-01T00:00:00Z") + ISO8601_DATETIME_VALIDATE("2020-01-01T00:00:00") + ISO8601_DATETIME_VALIDATE("2020-01-01 00:00:00") + ISO8601_DATETIME_VALIDATE("2020-01-01 00:00:00+00:00") + ISO8601_DATETIME_VALIDATE("2020-01-01 00:00:00-00:00") + ISO8601_DATETIME_VALIDATE("2020-01-01 00:00-00:00") + ISO8601_DATETIME_VALIDATE("2020-01-01 00:00:00.1-00:00") + ISO8601_DATETIME_VALIDATE("2020-01-01 00:00:00.123456-00:00") def test_indy_wql(self): non_wqls = [ diff --git a/acapy_agent/messaging/valid.py b/acapy_agent/messaging/valid.py index fc1749d600..c5026ca382 100644 --- a/acapy_agent/messaging/valid.py +++ b/acapy_agent/messaging/valid.py @@ -362,6 +362,21 @@ def __init__(self): ) +class AnoncredsDID(Regexp): + """Validate value against indy DID.""" + + EXAMPLE = "did:(method):WgWxqztrNooG92RXvxSTWv" + PATTERN = re.compile("^(did:[a-z]:.+$)?$") + + def __init__(self): + """Initialize the instance.""" + + super().__init__( + IndyDID.PATTERN, + error="Value {input} is not an decentralized identifier (DID)", + ) + + class DIDValidation(Regexp): """Validate value against any valid DID spec.""" @@ -400,8 +415,8 @@ def __init__(self): ) -class IndyRawPublicKey(Regexp): - """Validate value against indy (Ed25519VerificationKey2018) raw public key.""" +class RawPublicEd25519VerificationKey2018(Regexp): + """Validate value against (Ed25519VerificationKey2018) raw public key.""" EXAMPLE = "H3C2AVvLMv6gmMNam3uVAjZpfkcJCwDwnZn6z3wXmqPV" PATTERN = rf"^[{B58}]{{43,44}}$" @@ -410,7 +425,7 @@ def __init__(self): """Initialize the instance.""" super().__init__( - IndyRawPublicKey.PATTERN, + RawPublicEd25519VerificationKey2018.PATTERN, error="Value {input} is not a raw Ed25519VerificationKey2018 key", ) @@ -423,7 +438,9 @@ class RoutingKey(Regexp): """ EXAMPLE = DIDKey.EXAMPLE - PATTERN = re.compile(DIDKey.PATTERN.pattern + "|" + IndyRawPublicKey.PATTERN) + PATTERN = re.compile( + DIDKey.PATTERN.pattern + "|" + RawPublicEd25519VerificationKey2018.PATTERN + ) def __init__(self): """Initialize the instance.""" @@ -458,8 +475,23 @@ def __init__(self): ) -class IndyVersion(Regexp): - """Validate value against indy version specification.""" +class AnoncredsCredDefId(Regexp): + """Validate value against anoncreds credential definition identifier specification.""" + + EXAMPLE = "did:(method):3:CL:20:tag" + PATTERN = r"^(.+$)" + + def __init__(self): + """Initialize the instance.""" + + super().__init__( + IndyCredDefId.PATTERN, + error="Value {input} is not an anoncreds credential definition identifier", + ) + + +class MajorMinorVersion(Regexp): + """Validate value against major minor version specification.""" EXAMPLE = "1.0" PATTERN = r"^[0-9.]+$" @@ -468,8 +500,8 @@ def __init__(self): """Initialize the instance.""" super().__init__( - IndyVersion.PATTERN, - error="Value {input} is not an indy version (use only digits and '.')", + MajorMinorVersion.PATTERN, + error="Value {input} is not a valid version major minor version (use only digits and '.')", # noqa: E501 ) @@ -488,6 +520,21 @@ def __init__(self): ) +class AnoncredsSchemaId(Regexp): + """Validate value against indy schema identifier specification.""" + + EXAMPLE = "did:(method):2:schema_name:1.0" + PATTERN = r"^(.+$)" + + def __init__(self): + """Initialize the instance.""" + + super().__init__( + IndySchemaId.PATTERN, + error="Value {input} is not an anoncreds schema identifier", + ) + + class IndyRevRegId(Regexp): """Validate value against indy revocation registry identifier specification.""" @@ -508,6 +555,21 @@ def __init__(self): ) +class AnoncredsRevRegId(Regexp): + """Validate value against anoncreds revocation registry identifier specification.""" + + EXAMPLE = "did:(method):4:did::3:CL:20:tag:CL_ACCUM:0" + PATTERN = r"^(.+$)" + + def __init__(self): + """Initialize the instance.""" + + super().__init__( + AnoncredsRevRegId.PATTERN, + error="Value {input} is not an anoncreds revocation registry identifier", + ) + + class IndyCredRevId(Regexp): """Validate value against indy credential revocation identifier specification.""" @@ -523,8 +585,8 @@ def __init__(self): ) -class IndyPredicate(OneOf): - """Validate value against indy predicate.""" +class Predicate(OneOf): + """Validate value against predicate.""" EXAMPLE = ">=" @@ -537,8 +599,8 @@ def __init__(self): ) -class IndyISO8601DateTime(Regexp): - """Validate value against ISO 8601 datetime format, indy profile.""" +class ISO8601DateTime(Regexp): + """Validate value against ISO 8601 datetime format.""" EXAMPLE = epoch_to_str(EXAMPLE_TIMESTAMP) PATTERN = ( @@ -550,7 +612,7 @@ def __init__(self): """Initialize the instance.""" super().__init__( - IndyISO8601DateTime.PATTERN, + ISO8601DateTime.PATTERN, error="Value {input} is not a date in valid format", ) @@ -960,29 +1022,38 @@ def __init__( GENERIC_DID_VALIDATE = MaybeIndyDID() GENERIC_DID_EXAMPLE = MaybeIndyDID.EXAMPLE -INDY_RAW_PUBLIC_KEY_VALIDATE = IndyRawPublicKey() -INDY_RAW_PUBLIC_KEY_EXAMPLE = IndyRawPublicKey.EXAMPLE +RAW_ED25519_2018_PUBLIC_KEY_VALIDATE = RawPublicEd25519VerificationKey2018() +RAW_ED25519_2018_PUBLIC_KEY_EXAMPLE = RawPublicEd25519VerificationKey2018.EXAMPLE INDY_SCHEMA_ID_VALIDATE = IndySchemaId() INDY_SCHEMA_ID_EXAMPLE = IndySchemaId.EXAMPLE +ANONCREDS_SCHEMA_ID_VALIDATE = AnoncredsSchemaId() +ANONCREDS_SCHEMA_ID_EXAMPLE = AnoncredsSchemaId.EXAMPLE + INDY_CRED_DEF_ID_VALIDATE = IndyCredDefId() INDY_CRED_DEF_ID_EXAMPLE = IndyCredDefId.EXAMPLE +ANONCREDS_CRED_DEF_ID_VALIDATE = AnoncredsCredDefId() +ANONCREDS_CRED_DEF_ID_EXAMPLE = AnoncredsCredDefId.EXAMPLE + INDY_REV_REG_ID_VALIDATE = IndyRevRegId() INDY_REV_REG_ID_EXAMPLE = IndyRevRegId.EXAMPLE +ANONCREDS_REV_REG_ID_VALIDATE = AnoncredsRevRegId() +ANONCREDS_REV_REG_ID_EXAMPLE = AnoncredsRevRegId.EXAMPLE + INDY_CRED_REV_ID_VALIDATE = IndyCredRevId() INDY_CRED_REV_ID_EXAMPLE = IndyCredRevId.EXAMPLE -INDY_VERSION_VALIDATE = IndyVersion() -INDY_VERSION_EXAMPLE = IndyVersion.EXAMPLE +MAJOR_MINOR_VERSION_VALIDATE = MajorMinorVersion() +MAJOR_MINOR_VERSION_EXAMPLE = MajorMinorVersion.EXAMPLE -INDY_PREDICATE_VALIDATE = IndyPredicate() -INDY_PREDICATE_EXAMPLE = IndyPredicate.EXAMPLE +PREDICATE_VALIDATE = Predicate() +PREDICATE_EXAMPLE = Predicate.EXAMPLE -INDY_ISO8601_DATETIME_VALIDATE = IndyISO8601DateTime() -INDY_ISO8601_DATETIME_EXAMPLE = IndyISO8601DateTime.EXAMPLE +ISO8601_DATETIME_VALIDATE = ISO8601DateTime() +ISO8601_DATETIME_EXAMPLE = ISO8601DateTime.EXAMPLE RFC3339_DATETIME_VALIDATE = RFC3339DateTime() RFC3339_DATETIME_EXAMPLE = RFC3339DateTime.EXAMPLE @@ -1037,3 +1108,6 @@ def __init__( INDY_OR_KEY_DID_VALIDATE = IndyOrKeyDID() INDY_OR_KEY_DID_EXAMPLE = IndyOrKeyDID.EXAMPLE + +ANONCREDS_DID_VALIDATE = AnoncredsDID() +ANONCREDS_DID_EXAMPLE = AnoncredsDID.EXAMPLE diff --git a/acapy_agent/protocols/basicmessage/v1_0/messages/basicmessage.py b/acapy_agent/protocols/basicmessage/v1_0/messages/basicmessage.py index 840be506b9..1a8940a904 100644 --- a/acapy_agent/protocols/basicmessage/v1_0/messages/basicmessage.py +++ b/acapy_agent/protocols/basicmessage/v1_0/messages/basicmessage.py @@ -8,8 +8,8 @@ from .....messaging.agent_message import AgentMessage, AgentMessageSchema from .....messaging.util import datetime_now, datetime_to_str from .....messaging.valid import ( - INDY_ISO8601_DATETIME_EXAMPLE, - INDY_ISO8601_DATETIME_VALIDATE, + ISO8601_DATETIME_EXAMPLE, + ISO8601_DATETIME_VALIDATE, ) from ..message_types import BASIC_MESSAGE, PROTOCOL_PACKAGE @@ -63,12 +63,12 @@ class Meta: sent_time = fields.Str( required=False, - validate=INDY_ISO8601_DATETIME_VALIDATE, + validate=ISO8601_DATETIME_VALIDATE, metadata={ "description": ( "Time message was sent, ISO8601 with space date/time separator" ), - "example": INDY_ISO8601_DATETIME_EXAMPLE, + "example": ISO8601_DATETIME_EXAMPLE, }, ) content = fields.Str( diff --git a/acapy_agent/protocols/connections/v1_0/messages/connection_invitation.py b/acapy_agent/protocols/connections/v1_0/messages/connection_invitation.py index d983eb0ede..51e9f5f3c2 100644 --- a/acapy_agent/protocols/connections/v1_0/messages/connection_invitation.py +++ b/acapy_agent/protocols/connections/v1_0/messages/connection_invitation.py @@ -10,8 +10,8 @@ from .....messaging.valid import ( GENERIC_DID_EXAMPLE, GENERIC_DID_VALIDATE, - INDY_RAW_PUBLIC_KEY_EXAMPLE, - INDY_RAW_PUBLIC_KEY_VALIDATE, + RAW_ED25519_2018_PUBLIC_KEY_EXAMPLE, + RAW_ED25519_2018_PUBLIC_KEY_VALIDATE, ) from .....wallet.util import b64_to_bytes, bytes_to_b64 from ..message_types import CONNECTION_INVITATION, PROTOCOL_PACKAGE @@ -131,10 +131,10 @@ class Meta: ) recipient_keys = fields.List( fields.Str( - validate=INDY_RAW_PUBLIC_KEY_VALIDATE, + validate=RAW_ED25519_2018_PUBLIC_KEY_VALIDATE, metadata={ "description": "Recipient public key", - "example": INDY_RAW_PUBLIC_KEY_EXAMPLE, + "example": RAW_ED25519_2018_PUBLIC_KEY_EXAMPLE, }, ), data_key="recipientKeys", @@ -151,10 +151,10 @@ class Meta: ) routing_keys = fields.List( fields.Str( - validate=INDY_RAW_PUBLIC_KEY_VALIDATE, + validate=RAW_ED25519_2018_PUBLIC_KEY_VALIDATE, metadata={ "description": "Routing key", - "example": INDY_RAW_PUBLIC_KEY_EXAMPLE, + "example": RAW_ED25519_2018_PUBLIC_KEY_EXAMPLE, }, ), data_key="routingKeys", diff --git a/acapy_agent/protocols/connections/v1_0/routes.py b/acapy_agent/protocols/connections/v1_0/routes.py index 8277484e40..0e415ceabc 100644 --- a/acapy_agent/protocols/connections/v1_0/routes.py +++ b/acapy_agent/protocols/connections/v1_0/routes.py @@ -29,8 +29,8 @@ GENERIC_DID_VALIDATE, INDY_DID_EXAMPLE, INDY_DID_VALIDATE, - INDY_RAW_PUBLIC_KEY_EXAMPLE, - INDY_RAW_PUBLIC_KEY_VALIDATE, + RAW_ED25519_2018_PUBLIC_KEY_EXAMPLE, + RAW_ED25519_2018_PUBLIC_KEY_VALIDATE, UUID4_EXAMPLE, UUID4_VALIDATE, ) @@ -94,10 +94,10 @@ class CreateInvitationRequestSchema(OpenAPISchema): recipient_keys = fields.List( fields.Str( - validate=INDY_RAW_PUBLIC_KEY_VALIDATE, + validate=RAW_ED25519_2018_PUBLIC_KEY_VALIDATE, metadata={ "description": "Recipient public key", - "example": INDY_RAW_PUBLIC_KEY_EXAMPLE, + "example": RAW_ED25519_2018_PUBLIC_KEY_EXAMPLE, }, ), required=False, @@ -112,10 +112,10 @@ class CreateInvitationRequestSchema(OpenAPISchema): ) routing_keys = fields.List( fields.Str( - validate=INDY_RAW_PUBLIC_KEY_VALIDATE, + validate=RAW_ED25519_2018_PUBLIC_KEY_VALIDATE, metadata={ "description": "Routing key", - "example": INDY_RAW_PUBLIC_KEY_EXAMPLE, + "example": RAW_ED25519_2018_PUBLIC_KEY_EXAMPLE, }, ), required=False, @@ -213,10 +213,10 @@ class ConnectionStaticResultSchema(OpenAPISchema): ) my_verkey = fields.Str( required=True, - validate=INDY_RAW_PUBLIC_KEY_VALIDATE, + validate=RAW_ED25519_2018_PUBLIC_KEY_VALIDATE, metadata={ "description": "My verification key", - "example": INDY_RAW_PUBLIC_KEY_EXAMPLE, + "example": RAW_ED25519_2018_PUBLIC_KEY_EXAMPLE, }, ) my_endpoint = fields.Str( @@ -231,10 +231,10 @@ class ConnectionStaticResultSchema(OpenAPISchema): ) their_verkey = fields.Str( required=True, - validate=INDY_RAW_PUBLIC_KEY_VALIDATE, + validate=RAW_ED25519_2018_PUBLIC_KEY_VALIDATE, metadata={ "description": "Remote verification key", - "example": INDY_RAW_PUBLIC_KEY_EXAMPLE, + "example": RAW_ED25519_2018_PUBLIC_KEY_EXAMPLE, }, ) record = fields.Nested(ConnRecordSchema(), required=True) @@ -248,10 +248,10 @@ class ConnectionsListQueryStringSchema(PaginatedQuerySchema): ) invitation_key = fields.Str( required=False, - validate=INDY_RAW_PUBLIC_KEY_VALIDATE, + validate=RAW_ED25519_2018_PUBLIC_KEY_VALIDATE, metadata={ "description": "invitation key", - "example": INDY_RAW_PUBLIC_KEY_EXAMPLE, + "example": RAW_ED25519_2018_PUBLIC_KEY_EXAMPLE, }, ) my_did = fields.Str( diff --git a/acapy_agent/protocols/issue_credential/v1_0/messages/credential_proposal.py b/acapy_agent/protocols/issue_credential/v1_0/messages/credential_proposal.py index 05416c54b1..d53ce0e549 100644 --- a/acapy_agent/protocols/issue_credential/v1_0/messages/credential_proposal.py +++ b/acapy_agent/protocols/issue_credential/v1_0/messages/credential_proposal.py @@ -12,8 +12,8 @@ INDY_DID_VALIDATE, INDY_SCHEMA_ID_EXAMPLE, INDY_SCHEMA_ID_VALIDATE, - INDY_VERSION_EXAMPLE, - INDY_VERSION_VALIDATE, + MAJOR_MINOR_VERSION_EXAMPLE, + MAJOR_MINOR_VERSION_VALIDATE, ) from ..message_types import CREDENTIAL_PROPOSAL, PROTOCOL_PACKAGE from .inner.credential_preview import CredentialPreview, CredentialPreviewSchema @@ -104,8 +104,8 @@ class Meta: schema_version = fields.Str( required=False, allow_none=False, - validate=INDY_VERSION_VALIDATE, - metadata={"example": INDY_VERSION_EXAMPLE}, + validate=MAJOR_MINOR_VERSION_VALIDATE, + metadata={"example": MAJOR_MINOR_VERSION_EXAMPLE}, ) cred_def_id = fields.Str( required=False, diff --git a/acapy_agent/protocols/issue_credential/v1_0/routes.py b/acapy_agent/protocols/issue_credential/v1_0/routes.py index 898f335b01..95bb4eeedc 100644 --- a/acapy_agent/protocols/issue_credential/v1_0/routes.py +++ b/acapy_agent/protocols/issue_credential/v1_0/routes.py @@ -34,8 +34,8 @@ INDY_DID_VALIDATE, INDY_SCHEMA_ID_EXAMPLE, INDY_SCHEMA_ID_VALIDATE, - INDY_VERSION_EXAMPLE, - INDY_VERSION_VALIDATE, + MAJOR_MINOR_VERSION_EXAMPLE, + MAJOR_MINOR_VERSION_VALIDATE, UUID4_EXAMPLE, UUID4_VALIDATE, ) @@ -142,8 +142,11 @@ class V10CredentialCreateSchema(AdminAPIMessageTracingSchema): ) schema_version = fields.Str( required=False, - validate=INDY_VERSION_VALIDATE, - metadata={"description": "Schema version", "example": INDY_VERSION_EXAMPLE}, + validate=MAJOR_MINOR_VERSION_VALIDATE, + metadata={ + "description": "Schema version", + "example": MAJOR_MINOR_VERSION_EXAMPLE, + }, ) issuer_did = fields.Str( required=False, @@ -201,8 +204,11 @@ class V10CredentialProposalRequestSchemaBase(AdminAPIMessageTracingSchema): ) schema_version = fields.Str( required=False, - validate=INDY_VERSION_VALIDATE, - metadata={"description": "Schema version", "example": INDY_VERSION_EXAMPLE}, + validate=MAJOR_MINOR_VERSION_VALIDATE, + metadata={ + "description": "Schema version", + "example": MAJOR_MINOR_VERSION_EXAMPLE, + }, ) issuer_did = fields.Str( required=False, diff --git a/acapy_agent/protocols/issue_credential/v2_0/formats/anoncreds/handler.py b/acapy_agent/protocols/issue_credential/v2_0/formats/anoncreds/handler.py index e72614437a..a25e4bb62c 100644 --- a/acapy_agent/protocols/issue_credential/v2_0/formats/anoncreds/handler.py +++ b/acapy_agent/protocols/issue_credential/v2_0/formats/anoncreds/handler.py @@ -1,30 +1,28 @@ -"""V2.0 issue-credential indy credential format handler.""" +"""V2.0 issue-credential anoncreds credential format handler.""" import json import logging from typing import Mapping, Optional, Tuple +from anoncreds import CredentialDefinition, Schema from marshmallow import RAISE +from ......anoncreds.base import AnonCredsResolutionError from ......anoncreds.holder import AnonCredsHolder, AnonCredsHolderError -from ......anoncreds.issuer import AnonCredsIssuer +from ......anoncreds.issuer import CATEGORY_CRED_DEF, CATEGORY_SCHEMA, AnonCredsIssuer +from ......anoncreds.models.credential import AnoncredsCredentialSchema +from ......anoncreds.models.credential_offer import AnoncredsCredentialOfferSchema +from ......anoncreds.models.credential_proposal import ( + AnoncredsCredentialDefinitionProposal, +) +from ......anoncreds.models.credential_request import AnoncredsCredRequestSchema from ......anoncreds.registry import AnonCredsRegistry from ......anoncreds.revocation import AnonCredsRevocation from ......cache.base import BaseCache -from ......indy.models.cred import IndyCredentialSchema -from ......indy.models.cred_abstract import IndyCredAbstractSchema -from ......indy.models.cred_request import IndyCredRequestSchema -from ......ledger.base import BaseLedger -from ......ledger.multiple_ledger.ledger_requests_executor import ( - GET_CRED_DEF, - IndyLedgerRequestsExecutor, -) from ......messaging.credential_definitions.util import ( CRED_DEF_SENT_RECORD_TYPE, - CredDefQueryStringSchema, ) from ......messaging.decorators.attach_decorator import AttachDecorator -from ......multitenant.base import BaseMultitenantManager from ......revocation_anoncreds.models.issuer_cred_rev_record import IssuerCredRevRecord from ......storage.base import BaseStorage from ...message_types import ( @@ -40,16 +38,16 @@ from ...messages.cred_proposal import V20CredProposal from ...messages.cred_request import V20CredRequest from ...models.cred_ex_record import V20CredExRecord -from ...models.detail.indy import V20CredExRecordIndy +from ...models.detail.anoncreds import V20CredExRecordAnoncreds from ..handler import CredFormatAttachment, V20CredFormatError, V20CredFormatHandler LOGGER = logging.getLogger(__name__) class AnonCredsCredFormatHandler(V20CredFormatHandler): - """Indy credential format handler.""" + """Anoncreds credential format handler.""" - format = V20CredFormat.Format.INDY + format = V20CredFormat.Format.ANONCREDS @classmethod def validate_fields(cls, message_type: str, attachment_data: Mapping): @@ -71,10 +69,10 @@ def validate_fields(cls, message_type: str, attachment_data: Mapping): """ mapping = { - CRED_20_PROPOSAL: CredDefQueryStringSchema, - CRED_20_OFFER: IndyCredAbstractSchema, - CRED_20_REQUEST: IndyCredRequestSchema, - CRED_20_ISSUE: IndyCredentialSchema, + CRED_20_PROPOSAL: AnoncredsCredentialDefinitionProposal, + CRED_20_OFFER: AnoncredsCredentialOfferSchema, + CRED_20_REQUEST: AnoncredsCredRequestSchema, + CRED_20_ISSUE: AnoncredsCredentialSchema, } # Get schema class @@ -83,7 +81,7 @@ def validate_fields(cls, message_type: str, attachment_data: Mapping): # Validate, throw if not valid Schema(unknown=RAISE).load(attachment_data) - async def get_detail_record(self, cred_ex_id: str) -> V20CredExRecordIndy: + async def get_detail_record(self, cred_ex_id: str) -> V20CredExRecordAnoncreds: """Retrieve credential exchange detail record by cred_ex_id.""" async with self.profile.session() as session: @@ -167,7 +165,7 @@ async def _match_sent_cred_def_id(self, tag_query: Mapping[str, str]) -> str: async def create_proposal( self, cred_ex_record: V20CredExRecord, proposal_data: Mapping[str, str] ) -> Tuple[V20CredFormat, AttachDecorator]: - """Create indy credential proposal.""" + """Create anoncreds credential proposal.""" if proposal_data is None: proposal_data = {} @@ -176,7 +174,7 @@ async def create_proposal( async def receive_proposal( self, cred_ex_record: V20CredExRecord, cred_proposal_message: V20CredProposal ) -> None: - """Receive indy credential proposal. + """Receive anoncreds credential proposal. No custom handling is required for this step. """ @@ -184,35 +182,37 @@ async def receive_proposal( async def create_offer( self, cred_proposal_message: V20CredProposal ) -> CredFormatAttachment: - """Create indy credential offer.""" + """Create anoncreds credential offer.""" issuer = AnonCredsIssuer(self.profile) - ledger = self.profile.inject(BaseLedger) cache = self.profile.inject_or(BaseCache) + anoncreds_attachment = cred_proposal_message.attachment( + AnonCredsCredFormatHandler.format + ) + + if not anoncreds_attachment: + anoncreds_attachment = cred_proposal_message.attachment( + V20CredFormat.Format.INDY.api + ) + cred_def_id = await issuer.match_created_credential_definitions( - **cred_proposal_message.attachment(AnonCredsCredFormatHandler.format) + **anoncreds_attachment ) async def _create(): offer_json = await issuer.create_credential_offer(cred_def_id) return json.loads(offer_json) - multitenant_mgr = self.profile.inject_or(BaseMultitenantManager) - if multitenant_mgr: - ledger_exec_inst = IndyLedgerRequestsExecutor(self.profile) - else: - ledger_exec_inst = self.profile.inject(IndyLedgerRequestsExecutor) - ledger = ( - await ledger_exec_inst.get_ledger_for_identifier( - cred_def_id, - txn_record_type=GET_CRED_DEF, + async with self.profile.session() as session: + cred_def_entry = await session.handle.fetch(CATEGORY_CRED_DEF, cred_def_id) + cred_def_dict = CredentialDefinition.load(cred_def_entry.value).to_dict() + schema_entry = await session.handle.fetch( + CATEGORY_SCHEMA, cred_def_dict["schemaId"] ) - )[1] - async with ledger: - schema_id = await ledger.credential_definition_id2schema_id(cred_def_id) - schema = await ledger.get_schema(schema_id) - schema_attrs = set(schema["attrNames"]) + schema_dict = Schema.load(schema_entry.value).to_dict() + + schema_attrs = set(schema_dict["attrNames"]) preview_attrs = set(cred_proposal_message.credential_preview.attr_dict()) if preview_attrs != schema_attrs: raise V20CredFormatError( @@ -238,23 +238,26 @@ async def _create(): async def receive_offer( self, cred_ex_record: V20CredExRecord, cred_offer_message: V20CredOffer ) -> None: - """Receive indy credential offer.""" + """Receive anoncreds credential offer.""" async def create_request( self, cred_ex_record: V20CredExRecord, request_data: Optional[Mapping] = None ) -> CredFormatAttachment: - """Create indy credential request.""" + """Create anoncreds credential request.""" if cred_ex_record.state != V20CredExRecord.STATE_OFFER_RECEIVED: raise V20CredFormatError( - "Indy issue credential format cannot start from credential request" + "Anoncreds issue credential format cannot start from credential request" ) await self._check_uniqueness(cred_ex_record.cred_ex_id) - holder_did = request_data.get("holder_did") if request_data else None + + # For backwards compatibility, remove indy backup when indy format is retired + from ..indy.handler import IndyCredFormatHandler + cred_offer = cred_ex_record.cred_offer.attachment( AnonCredsCredFormatHandler.format - ) + ) or cred_ex_record.cred_offer.attachment(IndyCredFormatHandler.format) if "nonce" not in cred_offer: raise V20CredFormatError("Missing nonce in credential offer") @@ -265,19 +268,24 @@ async def create_request( async def _create(): anoncreds_registry = self.profile.inject(AnonCredsRegistry) - cred_def_result = await anoncreds_registry.get_credential_definition( - self.profile, cred_def_id - ) - - holder = AnonCredsHolder(self.profile) - request_json, metadata_json = await holder.create_credential_request( - cred_offer, cred_def_result.credential_definition, holder_did - ) + try: + cred_def_result = await anoncreds_registry.get_credential_definition( + self.profile, cred_def_id + ) + holder = AnonCredsHolder(self.profile) + request_json, metadata_json = await holder.create_credential_request( + cred_offer, cred_def_result.credential_definition, holder_did + ) - return { - "request": json.loads(request_json), - "metadata": json.loads(metadata_json), - } + return { + "request": json.loads(request_json), + "metadata": json.loads(metadata_json), + } + # This is for compatability with a holder that isn't anoncreds capable + except AnonCredsResolutionError: + return await IndyCredFormatHandler.create_cred_request_result( + self, cred_offer, holder_did, cred_def_id + ) cache_key = f"credential_request::{cred_def_id}::{holder_did}::{nonce}" cred_req_result = None @@ -292,7 +300,7 @@ async def _create(): if not cred_req_result: cred_req_result = await _create() - detail_record = V20CredExRecordIndy( + detail_record = V20CredExRecordAnoncreds( cred_ex_id=cred_ex_record.cred_ex_id, cred_request_metadata=cred_req_result["metadata"], ) @@ -305,18 +313,25 @@ async def _create(): async def receive_request( self, cred_ex_record: V20CredExRecord, cred_request_message: V20CredRequest ) -> None: - """Receive indy credential request.""" + """Receive anoncreds credential request.""" if not cred_ex_record.cred_offer: raise V20CredFormatError( - "Indy issue credential format cannot start from credential request" + "Anoncreds issue credential format cannot start from credential request" ) async def issue_credential( self, cred_ex_record: V20CredExRecord, retries: int = 5 ) -> CredFormatAttachment: - """Issue indy credential.""" + """Issue anoncreds credential.""" await self._check_uniqueness(cred_ex_record.cred_ex_id) + # For backwards compatibility, remove indy backup when indy format is retired + from ..indy.handler import IndyCredFormatHandler + + if cred_ex_record.cred_offer.attachment(IndyCredFormatHandler.format): + indy_handler = IndyCredFormatHandler(self.profile) + return await indy_handler.issue_credential(cred_ex_record, retries) + cred_offer = cred_ex_record.cred_offer.attachment( AnonCredsCredFormatHandler.format ) @@ -342,7 +357,7 @@ async def issue_credential( result = self.get_format_data(CRED_20_ISSUE, json.loads(cred_json)) async with self._profile.transaction() as txn: - detail_record = V20CredExRecordIndy( + detail_record = V20CredExRecordAnoncreds( cred_ex_id=cred_ex_record.cred_ex_id, rev_reg_id=rev_reg_def_id, cred_rev_id=cred_rev_id, @@ -371,7 +386,7 @@ async def issue_credential( async def receive_credential( self, cred_ex_record: V20CredExRecord, cred_issue_message: V20CredIssue ) -> None: - """Receive indy credential. + """Receive anoncreds credential. Validation is done in the store credential step. """ @@ -379,21 +394,33 @@ async def receive_credential( async def store_credential( self, cred_ex_record: V20CredExRecord, cred_id: Optional[str] = None ) -> None: - """Store indy credential.""" - cred = cred_ex_record.cred_issue.attachment(AnonCredsCredFormatHandler.format) + """Store anoncreds credential.""" + + # For backwards compatibility, remove indy backup when indy format is retired + from ..indy.handler import IndyCredFormatHandler + + cred = cred_ex_record.cred_issue.attachment( + AnonCredsCredFormatHandler.format + ) or cred_ex_record.cred_issue.attachment(IndyCredFormatHandler.format) rev_reg_def = None anoncreds_registry = self.profile.inject(AnonCredsRegistry) - cred_def_result = await anoncreds_registry.get_credential_definition( - self.profile, cred["cred_def_id"] - ) - if cred.get("rev_reg_id"): - rev_reg_def_result = ( - await anoncreds_registry.get_revocation_registry_definition( - self.profile, cred["rev_reg_id"] + try: + cred_def_result = await anoncreds_registry.get_credential_definition( + self.profile, cred["cred_def_id"] + ) + if cred.get("rev_reg_id"): + rev_reg_def_result = ( + await anoncreds_registry.get_revocation_registry_definition( + self.profile, cred["rev_reg_id"] + ) ) + rev_reg_def = rev_reg_def_result.revocation_registry + + except AnonCredsResolutionError: + return await IndyCredFormatHandler.store_credential( + self, cred_ex_record, cred_id ) - rev_reg_def = rev_reg_def_result.revocation_registry holder = AnonCredsHolder(self.profile) cred_offer_message = cred_ex_record.cred_offer diff --git a/acapy_agent/protocols/issue_credential/v2_0/formats/anoncreds/tests/test_handler.py b/acapy_agent/protocols/issue_credential/v2_0/formats/anoncreds/tests/test_handler.py index 46606417fb..fed47beaf3 100644 --- a/acapy_agent/protocols/issue_credential/v2_0/formats/anoncreds/tests/test_handler.py +++ b/acapy_agent/protocols/issue_credential/v2_0/formats/anoncreds/tests/test_handler.py @@ -36,10 +36,10 @@ from ....messages.cred_request import V20CredRequest from ....messages.inner.cred_preview import V20CredAttrSpec, V20CredPreview from ....models.cred_ex_record import V20CredExRecord -from ....models.detail.indy import V20CredExRecordIndy +from ....models.detail.anoncreds import V20CredExRecordAnoncreds from ...handler import V20CredFormatError from .. import handler as test_module -from ..handler import LOGGER as INDY_LOGGER +from ..handler import LOGGER as ANONCREDS_LOGGER from ..handler import AnonCredsCredFormatHandler TEST_DID = "LjgpST2rjsoxYegQDRm7EL" @@ -108,7 +108,7 @@ "tailsLocation": TAILS_LOCAL, }, } -INDY_OFFER = { +ANONCREDS_OFFER = { "schema_id": SCHEMA_ID, "cred_def_id": CRED_DEF_ID, "key_correctness_proof": { @@ -131,7 +131,7 @@ }, "nonce": "1234567890", } -INDY_CRED_REQ = { +ANONCREDS_CRED_REQ = { "prover_did": TEST_DID, "cred_def_id": CRED_DEF_ID, "blinded_ms": { @@ -148,7 +148,7 @@ }, "nonce": "9876543210", } -INDY_CRED = { +ANONCREDS_CRED = { "schema_id": SCHEMA_ID, "cred_def_id": CRED_DEF_ID, "rev_reg_id": REV_REG_ID, @@ -232,9 +232,9 @@ async def asyncSetUp(self): async def test_validate_fields(self): # Test correct data self.handler.validate_fields(CRED_20_PROPOSAL, {"cred_def_id": CRED_DEF_ID}) - self.handler.validate_fields(CRED_20_OFFER, INDY_OFFER) - self.handler.validate_fields(CRED_20_REQUEST, INDY_CRED_REQ) - self.handler.validate_fields(CRED_20_ISSUE, INDY_CRED) + self.handler.validate_fields(CRED_20_OFFER, ANONCREDS_OFFER) + self.handler.validate_fields(CRED_20_REQUEST, ANONCREDS_CRED_REQ) + self.handler.validate_fields(CRED_20_ISSUE, ANONCREDS_CRED) # test incorrect proposal with self.assertRaises(ValidationError): @@ -244,31 +244,31 @@ async def test_validate_fields(self): # test incorrect offer with self.assertRaises(ValidationError): - offer = INDY_OFFER.copy() + offer = ANONCREDS_OFFER.copy() offer.pop("nonce") self.handler.validate_fields(CRED_20_OFFER, offer) # test incorrect request with self.assertRaises(ValidationError): - req = INDY_CRED_REQ.copy() + req = ANONCREDS_CRED_REQ.copy() req.pop("nonce") self.handler.validate_fields(CRED_20_REQUEST, req) # test incorrect cred with self.assertRaises(ValidationError): - cred = INDY_CRED.copy() + cred = ANONCREDS_CRED.copy() cred.pop("schema_id") self.handler.validate_fields(CRED_20_ISSUE, cred) async def test_get_indy_detail_record(self): cred_ex_id = "dummy" details_indy = [ - V20CredExRecordIndy( + V20CredExRecordAnoncreds( cred_ex_id=cred_ex_id, rev_reg_id="rr-id", cred_rev_id="0", ), - V20CredExRecordIndy( + V20CredExRecordAnoncreds( cred_ex_id=cred_ex_id, rev_reg_id="rr-id", cred_rev_id="1", @@ -278,7 +278,9 @@ async def test_get_indy_detail_record(self): await details_indy[0].save(session) await details_indy[1].save(session) # exercise logger warning on get() - with mock.patch.object(INDY_LOGGER, "warning", mock.MagicMock()) as mock_warning: + with mock.patch.object( + ANONCREDS_LOGGER, "warning", mock.MagicMock() + ) as mock_warning: assert await self.handler.get_detail_record(cred_ex_id) in details_indy mock_warning.assert_called_once() @@ -354,7 +356,7 @@ async def test_create_offer(self): V20CredFormat( attach_id="0", format_=ATTACHMENT_FORMAT[CRED_20_PROPOSAL][ - V20CredFormat.Format.INDY.api + V20CredFormat.Format.ANONCREDS.api ], ) ], @@ -379,7 +381,7 @@ async def test_create_offer(self): await self.session.storage.add_record(cred_def_record) self.issuer.create_credential_offer = mock.CoroutineMock( - return_value=json.dumps(INDY_OFFER) + return_value=json.dumps(ANONCREDS_OFFER) ) (cred_format, attachment) = await self.handler.create_offer(cred_proposal) @@ -390,7 +392,7 @@ async def test_create_offer(self): assert cred_format.attach_id == self.handler.format.api == attachment.ident # assert content of attachment is proposal data - assert attachment.content == INDY_OFFER + assert attachment.content == ANONCREDS_OFFER # assert data is encoded as base64 assert attachment.data.base64 @@ -417,7 +419,7 @@ async def test_create_offer_no_cache(self): V20CredFormat( attach_id="0", format_=ATTACHMENT_FORMAT[CRED_20_PROPOSAL][ - V20CredFormat.Format.INDY.api + V20CredFormat.Format.ANONCREDS.api ], ) ], @@ -446,7 +448,7 @@ async def test_create_offer_no_cache(self): await self.session.storage.add_record(cred_def_record) self.issuer.create_credential_offer = mock.CoroutineMock( - return_value=json.dumps(INDY_OFFER) + return_value=json.dumps(ANONCREDS_OFFER) ) (cred_format, attachment) = await self.handler.create_offer(cred_proposal) @@ -457,7 +459,7 @@ async def test_create_offer_no_cache(self): assert cred_format.attach_id == self.handler.format.api == attachment.ident # assert content of attachment is proposal data - assert attachment.content == INDY_OFFER + assert attachment.content == ANONCREDS_OFFER # assert data is encoded as base64 assert attachment.data.base64 @@ -480,7 +482,7 @@ async def test_create_offer_attr_mismatch(self): V20CredFormat( attach_id="0", format_=ATTACHMENT_FORMAT[CRED_20_PROPOSAL][ - V20CredFormat.Format.INDY.api + V20CredFormat.Format.ANONCREDS.api ], ) ], @@ -509,7 +511,7 @@ async def test_create_offer_attr_mismatch(self): await self.session.storage.add_record(cred_def_record) self.issuer.create_credential_offer = mock.CoroutineMock( - return_value=json.dumps(INDY_OFFER) + return_value=json.dumps(ANONCREDS_OFFER) ) with mock.patch.object( IndyLedgerRequestsExecutor, @@ -526,7 +528,7 @@ async def test_create_offer_no_matching_sent_cred_def(self): V20CredFormat( attach_id="0", format_=ATTACHMENT_FORMAT[CRED_20_PROPOSAL][ - V20CredFormat.Format.INDY.api + V20CredFormat.Format.ANONCREDS.api ], ) ], @@ -534,7 +536,7 @@ async def test_create_offer_no_matching_sent_cred_def(self): ) self.issuer.create_credential_offer = mock.CoroutineMock( - return_value=json.dumps(INDY_OFFER) + return_value=json.dumps(ANONCREDS_OFFER) ) with self.assertRaises(V20CredFormatError) as context: @@ -557,11 +559,11 @@ async def test_create_request(self): V20CredFormat( attach_id="0", format_=ATTACHMENT_FORMAT[CRED_20_OFFER][ - V20CredFormat.Format.INDY.api + V20CredFormat.Format.ANONCREDS.api ], ) ], - offers_attach=[AttachDecorator.data_base64(INDY_OFFER, ident="0")], + offers_attach=[AttachDecorator.data_base64(ANONCREDS_OFFER, ident="0")], ) cred_ex_record = V20CredExRecord( cred_ex_id="dummy-id", @@ -574,7 +576,7 @@ async def test_create_request(self): cred_req_meta = {} self.holder.create_credential_request = mock.CoroutineMock( - return_value=(json.dumps(INDY_CRED_REQ), json.dumps(cred_req_meta)) + return_value=(json.dumps(ANONCREDS_CRED_REQ), json.dumps(cred_req_meta)) ) (cred_format, attachment) = await self.handler.create_request( @@ -582,14 +584,14 @@ async def test_create_request(self): ) self.holder.create_credential_request.assert_called_once_with( - INDY_OFFER, cred_def, holder_did + ANONCREDS_OFFER, cred_def, holder_did ) # assert identifier match assert cred_format.attach_id == self.handler.format.api == attachment.ident # assert content of attachment is proposal data - assert attachment.content == INDY_CRED_REQ + assert attachment.content == ANONCREDS_CRED_REQ # assert data is encoded as base64 assert attachment.data.base64 @@ -617,16 +619,18 @@ async def test_create_request_bad_state(self): with self.assertRaises(V20CredFormatError) as context: await self.handler.create_request(cred_ex_record) - assert "Indy issue credential format cannot start from credential request" in str( - context.exception + assert ( + "Anoncreds issue credential format cannot start from credential request" + in str(context.exception) ) cred_ex_record.state = None with self.assertRaises(V20CredFormatError) as context: await self.handler.create_request(cred_ex_record) - assert "Indy issue credential format cannot start from credential request" in str( - context.exception + assert ( + "Anoncreds issue credential format cannot start from credential request" + in str(context.exception) ) async def test_create_request_not_unique_x(self): @@ -658,8 +662,9 @@ async def test_receive_request_no_offer(self): with self.assertRaises(V20CredFormatError) as context: await self.handler.receive_request(cred_ex_record, cred_request_message) - assert "Indy issue credential format cannot start from credential request" in str( - context.exception + assert ( + "Anoncreds issue credential format cannot start from credential request" + in str(context.exception) ) @pytest.mark.skip(reason="Anoncreds-break") @@ -680,22 +685,22 @@ async def test_issue_credential_revocable(self): V20CredFormat( attach_id="0", format_=ATTACHMENT_FORMAT[CRED_20_OFFER][ - V20CredFormat.Format.INDY.api + V20CredFormat.Format.ANONCREDS.api ], ) ], - offers_attach=[AttachDecorator.data_base64(INDY_OFFER, ident="0")], + offers_attach=[AttachDecorator.data_base64(ANONCREDS_OFFER, ident="0")], ) cred_request = V20CredRequest( formats=[ V20CredFormat( attach_id="0", format_=ATTACHMENT_FORMAT[CRED_20_REQUEST][ - V20CredFormat.Format.INDY.api + V20CredFormat.Format.ANONCREDS.api ], ) ], - requests_attach=[AttachDecorator.data_base64(INDY_CRED_REQ, ident="0")], + requests_attach=[AttachDecorator.data_base64(ANONCREDS_CRED_REQ, ident="0")], ) cred_ex_record = V20CredExRecord( @@ -709,7 +714,7 @@ async def test_issue_credential_revocable(self): cred_rev_id = "1000" self.issuer.create_credential = mock.CoroutineMock( - return_value=(json.dumps(INDY_CRED), cred_rev_id) + return_value=(json.dumps(ANONCREDS_CRED), cred_rev_id) ) with mock.patch.object(test_module, "IndyRevocation", autospec=True) as revoc: @@ -732,8 +737,8 @@ async def test_issue_credential_revocable(self): self.issuer.create_credential.assert_called_once_with( SCHEMA, - INDY_OFFER, - INDY_CRED_REQ, + ANONCREDS_OFFER, + ANONCREDS_CRED_REQ, attr_values, REV_REG_ID, "dummy-path", @@ -743,7 +748,7 @@ async def test_issue_credential_revocable(self): assert cred_format.attach_id == self.handler.format.api == attachment.ident # assert content of attachment is proposal data - assert attachment.content == INDY_CRED + assert attachment.content == ANONCREDS_CRED # assert data is encoded as base64 assert attachment.data.base64 @@ -768,22 +773,22 @@ async def test_issue_credential_non_revocable(self): V20CredFormat( attach_id="0", format_=ATTACHMENT_FORMAT[CRED_20_OFFER][ - V20CredFormat.Format.INDY.api + V20CredFormat.Format.ANONCREDS.api ], ) ], - offers_attach=[AttachDecorator.data_base64(INDY_OFFER, ident="0")], + offers_attach=[AttachDecorator.data_base64(ANONCREDS_OFFER, ident="0")], ) cred_request = V20CredRequest( formats=[ V20CredFormat( attach_id="0", format_=ATTACHMENT_FORMAT[CRED_20_REQUEST][ - V20CredFormat.Format.INDY.api + V20CredFormat.Format.ANONCREDS.api ], ) ], - requests_attach=[AttachDecorator.data_base64(INDY_CRED_REQ, ident="0")], + requests_attach=[AttachDecorator.data_base64(ANONCREDS_CRED_REQ, ident="0")], ) cred_ex_record = V20CredExRecord( @@ -796,7 +801,7 @@ async def test_issue_credential_non_revocable(self): ) self.issuer.create_credential = mock.CoroutineMock( - return_value=(json.dumps(INDY_CRED), None) + return_value=(json.dumps(ANONCREDS_CRED), None) ) self.ledger.get_credential_definition = mock.CoroutineMock( return_value=CRED_DEF_NR @@ -816,8 +821,8 @@ async def test_issue_credential_non_revocable(self): self.issuer.create_credential.assert_called_once_with( SCHEMA, - INDY_OFFER, - INDY_CRED_REQ, + ANONCREDS_OFFER, + ANONCREDS_CRED_REQ, attr_values, None, None, @@ -827,7 +832,7 @@ async def test_issue_credential_non_revocable(self): assert cred_format.attach_id == self.handler.format.api == attachment.ident # assert content of attachment is proposal data - assert attachment.content == INDY_CRED + assert attachment.content == ANONCREDS_CRED # assert data is encoded as base64 assert attachment.data.base64 @@ -867,22 +872,22 @@ async def test_issue_credential_no_active_rr_no_retries(self): V20CredFormat( attach_id="0", format_=ATTACHMENT_FORMAT[CRED_20_OFFER][ - V20CredFormat.Format.INDY.api + V20CredFormat.Format.ANONCREDS.api ], ) ], - offers_attach=[AttachDecorator.data_base64(INDY_OFFER, ident="0")], + offers_attach=[AttachDecorator.data_base64(ANONCREDS_OFFER, ident="0")], ) cred_request = V20CredRequest( formats=[ V20CredFormat( attach_id="0", format_=ATTACHMENT_FORMAT[CRED_20_REQUEST][ - V20CredFormat.Format.INDY.api + V20CredFormat.Format.ANONCREDS.api ], ) ], - requests_attach=[AttachDecorator.data_base64(INDY_CRED_REQ, ident="0")], + requests_attach=[AttachDecorator.data_base64(ANONCREDS_CRED_REQ, ident="0")], ) cred_ex_record = V20CredExRecord( @@ -895,7 +900,7 @@ async def test_issue_credential_no_active_rr_no_retries(self): ) self.issuer.create_credential = mock.CoroutineMock( - return_value=(json.dumps(INDY_CRED), cred_rev_id) + return_value=(json.dumps(ANONCREDS_CRED), cred_rev_id) ) with mock.patch.object(test_module, "IndyRevocation", autospec=True) as revoc: @@ -926,22 +931,22 @@ async def test_issue_credential_no_active_rr_retry(self): V20CredFormat( attach_id="0", format_=ATTACHMENT_FORMAT[CRED_20_OFFER][ - V20CredFormat.Format.INDY.api + V20CredFormat.Format.ANONCREDS.api ], ) ], - offers_attach=[AttachDecorator.data_base64(INDY_OFFER, ident="0")], + offers_attach=[AttachDecorator.data_base64(ANONCREDS_OFFER, ident="0")], ) cred_request = V20CredRequest( formats=[ V20CredFormat( attach_id="0", format_=ATTACHMENT_FORMAT[CRED_20_REQUEST][ - V20CredFormat.Format.INDY.api + V20CredFormat.Format.ANONCREDS.api ], ) ], - requests_attach=[AttachDecorator.data_base64(INDY_CRED_REQ, ident="0")], + requests_attach=[AttachDecorator.data_base64(ANONCREDS_CRED_REQ, ident="0")], ) cred_ex_record = V20CredExRecord( @@ -954,7 +959,7 @@ async def test_issue_credential_no_active_rr_retry(self): ) self.issuer.create_credential = mock.CoroutineMock( - return_value=(json.dumps(INDY_CRED), cred_rev_id) + return_value=(json.dumps(ANONCREDS_CRED), cred_rev_id) ) with mock.patch.object(test_module, "IndyRevocation", autospec=True) as revoc: @@ -996,22 +1001,22 @@ async def test_issue_credential_rr_full(self): V20CredFormat( attach_id="0", format_=ATTACHMENT_FORMAT[CRED_20_OFFER][ - V20CredFormat.Format.INDY.api + V20CredFormat.Format.ANONCREDS.api ], ) ], - offers_attach=[AttachDecorator.data_base64(INDY_OFFER, ident="0")], + offers_attach=[AttachDecorator.data_base64(ANONCREDS_OFFER, ident="0")], ) cred_request = V20CredRequest( formats=[ V20CredFormat( attach_id="0", format_=ATTACHMENT_FORMAT[CRED_20_REQUEST][ - V20CredFormat.Format.INDY.api + V20CredFormat.Format.ANONCREDS.api ], ) ], - requests_attach=[AttachDecorator.data_base64(INDY_CRED_REQ, ident="0")], + requests_attach=[AttachDecorator.data_base64(ANONCREDS_CRED_REQ, ident="0")], ) cred_ex_record = V20CredExRecord( @@ -1075,11 +1080,11 @@ async def test_store_credential(self): V20CredFormat( attach_id="0", format_=ATTACHMENT_FORMAT[CRED_20_OFFER][ - V20CredFormat.Format.INDY.api + V20CredFormat.Format.ANONCREDS.api ], ) ], - offers_attach=[AttachDecorator.data_base64(INDY_OFFER, ident="0")], + offers_attach=[AttachDecorator.data_base64(ANONCREDS_OFFER, ident="0")], ) cred_offer.assign_thread_id(thread_id) cred_request = V20CredRequest( @@ -1087,22 +1092,22 @@ async def test_store_credential(self): V20CredFormat( attach_id="0", format_=ATTACHMENT_FORMAT[CRED_20_REQUEST][ - V20CredFormat.Format.INDY.api + V20CredFormat.Format.ANONCREDS.api ], ) ], - requests_attach=[AttachDecorator.data_base64(INDY_CRED_REQ, ident="0")], + requests_attach=[AttachDecorator.data_base64(ANONCREDS_CRED_REQ, ident="0")], ) cred_issue = V20CredIssue( formats=[ V20CredFormat( attach_id="0", format_=ATTACHMENT_FORMAT[CRED_20_ISSUE][ - V20CredFormat.Format.INDY.api + V20CredFormat.Format.ANONCREDS.api ], ) ], - credentials_attach=[AttachDecorator.data_base64(INDY_CRED, ident="0")], + credentials_attach=[AttachDecorator.data_base64(ANONCREDS_CRED, ident="0")], ) stored_cx_rec = V20CredExRecord( @@ -1167,7 +1172,7 @@ async def test_store_credential(self): self.holder.store_credential.assert_called_once_with( CRED_DEF, - INDY_CRED, + ANONCREDS_CRED, cred_req_meta, {"pic": "image/jpeg"}, credential_id=cred_id, @@ -1198,11 +1203,11 @@ async def test_store_credential_holder_store_indy_error(self): V20CredFormat( attach_id="0", format_=ATTACHMENT_FORMAT[CRED_20_OFFER][ - V20CredFormat.Format.INDY.api + V20CredFormat.Format.ANONCREDS.api ], ) ], - offers_attach=[AttachDecorator.data_base64(INDY_OFFER, ident="0")], + offers_attach=[AttachDecorator.data_base64(ANONCREDS_OFFER, ident="0")], ) cred_offer.assign_thread_id(thread_id) cred_request = V20CredRequest( @@ -1210,22 +1215,22 @@ async def test_store_credential_holder_store_indy_error(self): V20CredFormat( attach_id="0", format_=ATTACHMENT_FORMAT[CRED_20_REQUEST][ - V20CredFormat.Format.INDY.api + V20CredFormat.Format.ANONCREDS.api ], ) ], - requests_attach=[AttachDecorator.data_base64(INDY_CRED_REQ, ident="0")], + requests_attach=[AttachDecorator.data_base64(ANONCREDS_CRED_REQ, ident="0")], ) cred_issue = V20CredIssue( formats=[ V20CredFormat( attach_id="0", format_=ATTACHMENT_FORMAT[CRED_20_ISSUE][ - V20CredFormat.Format.INDY.api + V20CredFormat.Format.ANONCREDS.api ], ) ], - credentials_attach=[AttachDecorator.data_base64(INDY_CRED, ident="0")], + credentials_attach=[AttachDecorator.data_base64(ANONCREDS_CRED, ident="0")], ) stored_cx_rec = V20CredExRecord( diff --git a/acapy_agent/protocols/issue_credential/v2_0/formats/indy/handler.py b/acapy_agent/protocols/issue_credential/v2_0/formats/indy/handler.py index ecadd98539..19a2857cf3 100644 --- a/acapy_agent/protocols/issue_credential/v2_0/formats/indy/handler.py +++ b/acapy_agent/protocols/issue_credential/v2_0/formats/indy/handler.py @@ -7,6 +7,7 @@ from marshmallow import RAISE +from ......askar.profile_anon import AskarAnoncredsProfile from ......cache.base import BaseCache from ......core.profile import Profile from ......indy.holder import IndyHolder, IndyHolderError @@ -14,7 +15,6 @@ from ......indy.models.cred import IndyCredentialSchema from ......indy.models.cred_abstract import IndyCredAbstractSchema from ......indy.models.cred_request import IndyCredRequestSchema -from ......ledger.base import BaseLedger from ......ledger.multiple_ledger.ledger_requests_executor import ( GET_CRED_DEF, GET_SCHEMA, @@ -105,10 +105,6 @@ async def get_detail_record(self, cred_ex_id: str) -> V20CredExRecordIndy: session, cred_ex_id ) - # Temporary shim while the new anoncreds library integration is in progress - if self.anoncreds_handler: - return await self.anoncreds_handler.get_detail_record(cred_ex_id) - if len(records) > 1: LOGGER.warning( "Cred ex id %s has %d %s detail records: should be 1", @@ -140,9 +136,6 @@ def get_format_identifier(self, message_type: str) -> str: str: Issue credential attachment format identifier """ - # Temporary shim while the new anoncreds library integration is in progress - if self.anoncreds_handler: - return self.anoncreds_handler.get_format_identifier(message_type) return ATTACHMENT_FORMAT[message_type][IndyCredFormatHandler.format.api] @@ -162,9 +155,6 @@ def get_format_data(self, message_type: str, data: dict) -> CredFormatAttachment CredFormatAttachment: Credential format and attachment data objects """ - # Temporary shim while the new anoncreds library integration is in progress - if self.anoncreds_handler: - return self.anoncreds_handler.get_format_data(message_type, data) return ( V20CredFormat( @@ -192,7 +182,7 @@ async def create_proposal( self, cred_ex_record: V20CredExRecord, proposal_data: Mapping[str, str] ) -> Tuple[V20CredFormat, AttachDecorator]: """Create indy credential proposal.""" - # Temporary shim while the new anoncreds library integration is in progress + # Create the proposal with the anoncreds handler if agent is anoncreds capable if self.anoncreds_handler: return await self.anoncreds_handler.create_proposal( cred_ex_record, @@ -217,12 +207,12 @@ async def create_offer( ) -> CredFormatAttachment: """Create indy credential offer.""" - # Temporary shim while the new anoncreds library integration is in progress - if self.anoncreds_handler: - return await self.anoncreds_handler.create_offer(cred_proposal_message) + if isinstance(self.profile, AskarAnoncredsProfile): + raise V20CredFormatError( + "This issuer is anoncreds capable. Please use the anonreds format." + ) issuer = self.profile.inject(IndyIssuer) - ledger = self.profile.inject(BaseLedger) cache = self.profile.inject_or(BaseCache) cred_def_id = await self._match_sent_cred_def_id( @@ -275,11 +265,38 @@ async def receive_offer( ) -> None: """Receive indy credential offer.""" + async def create_cred_request_result(self, cred_offer, holder_did, cred_def_id): + """Create credential request result.""" + multitenant_mgr = self.profile.inject_or(BaseMultitenantManager) + if multitenant_mgr: + ledger_exec_inst = IndyLedgerRequestsExecutor(self.profile) + else: + ledger_exec_inst = self.profile.inject(IndyLedgerRequestsExecutor) + ledger = ( + await ledger_exec_inst.get_ledger_for_identifier( + cred_def_id, + txn_record_type=GET_CRED_DEF, + ) + )[1] + async with ledger: + cred_def = await ledger.get_credential_definition(cred_def_id) + + holder = self.profile.inject(IndyHolder) + request_json, metadata_json = await holder.create_credential_request( + cred_offer, cred_def, holder_did + ) + + return { + "request": json.loads(request_json), + "metadata": json.loads(metadata_json), + } + async def create_request( self, cred_ex_record: V20CredExRecord, request_data: Optional[Mapping] = None ) -> CredFormatAttachment: """Create indy credential request.""" - # Temporary shim while the new anoncreds library integration is in progress + + # Create the request with the anoncreds handler if agent is anoncreds capable if self.anoncreds_handler: return await self.anoncreds_handler.create_request( cred_ex_record, @@ -302,31 +319,6 @@ async def create_request( nonce = cred_offer["nonce"] cred_def_id = cred_offer["cred_def_id"] - async def _create(): - multitenant_mgr = self.profile.inject_or(BaseMultitenantManager) - if multitenant_mgr: - ledger_exec_inst = IndyLedgerRequestsExecutor(self.profile) - else: - ledger_exec_inst = self.profile.inject(IndyLedgerRequestsExecutor) - ledger = ( - await ledger_exec_inst.get_ledger_for_identifier( - cred_def_id, - txn_record_type=GET_CRED_DEF, - ) - )[1] - async with ledger: - cred_def = await ledger.get_credential_definition(cred_def_id) - - holder = self.profile.inject(IndyHolder) - request_json, metadata_json = await holder.create_credential_request( - cred_offer, cred_def, holder_did - ) - - return { - "request": json.loads(request_json), - "metadata": json.loads(metadata_json), - } - cache_key = f"credential_request::{cred_def_id}::{holder_did}::{nonce}" cred_req_result = None cache = self.profile.inject_or(BaseCache) @@ -335,10 +327,14 @@ async def _create(): if entry.result: cred_req_result = entry.result else: - cred_req_result = await _create() + cred_req_result = await self.create_cred_request_result( + cred_offer, holder_did, cred_def_id + ) await entry.set_result(cred_req_result, 3600) if not cred_req_result: - cred_req_result = await _create() + cred_req_result = await self.create_cred_request_result( + cred_offer, holder_did, cred_def_id + ) detail_record = V20CredExRecordIndy( cred_ex_id=cred_ex_record.cred_ex_id, @@ -354,7 +350,7 @@ async def receive_request( self, cred_ex_record: V20CredExRecord, cred_request_message: V20CredRequest ) -> None: """Receive indy credential request.""" - # Temporary shim while the new anoncreds library integration is in progress + # Receive the request with the anoncreds handler if agent is anoncreds capable if self.anoncreds_handler: return await self.anoncreds_handler.receive_request( cred_ex_record, @@ -445,9 +441,11 @@ async def issue_credential( await self._check_uniqueness(cred_ex_record.cred_ex_id) cred_offer = cred_ex_record.cred_offer.attachment(IndyCredFormatHandler.format) + from ..anoncreds.handler import AnonCredsCredFormatHandler + cred_request = cred_ex_record.cred_request.attachment( IndyCredFormatHandler.format - ) + ) or cred_ex_record.cred_request.attachment(AnonCredsCredFormatHandler.format) cred_values = cred_ex_record.cred_offer.credential_preview.attr_dict(decode=False) schema_id = cred_offer["schema_id"] cred_def_id = cred_offer["cred_def_id"] @@ -517,10 +515,14 @@ async def store_credential( ) -> None: """Store indy credential.""" # Temporary shim while the new anoncreds library integration is in progress - if self.anoncreds_handler: + if hasattr(self, "anoncreds_handler") and self.anoncreds_handler: return await self.anoncreds_handler.store_credential(cred_ex_record, cred_id) - cred = cred_ex_record.cred_issue.attachment(IndyCredFormatHandler.format) + from ..anoncreds.handler import AnonCredsCredFormatHandler + + cred = cred_ex_record.cred_issue.attachment( + IndyCredFormatHandler.format + ) or cred_ex_record.cred_issue.attachment(AnonCredsCredFormatHandler.format) rev_reg_def = None multitenant_mgr = self.profile.inject_or(BaseMultitenantManager) diff --git a/acapy_agent/protocols/issue_credential/v2_0/formats/ld_proof/models/cred_detail_options.py b/acapy_agent/protocols/issue_credential/v2_0/formats/ld_proof/models/cred_detail_options.py index 9d74382d8d..7a9b1ca549 100644 --- a/acapy_agent/protocols/issue_credential/v2_0/formats/ld_proof/models/cred_detail_options.py +++ b/acapy_agent/protocols/issue_credential/v2_0/formats/ld_proof/models/cred_detail_options.py @@ -6,8 +6,8 @@ from .......messaging.models.base import BaseModel, BaseModelSchema from .......messaging.valid import ( - INDY_ISO8601_DATETIME_EXAMPLE, - INDY_ISO8601_DATETIME_VALIDATE, + ISO8601_DATETIME_EXAMPLE, + ISO8601_DATETIME_VALIDATE, UUID4_EXAMPLE, ) @@ -109,13 +109,13 @@ class Meta: created = fields.Str( required=False, - validate=INDY_ISO8601_DATETIME_VALIDATE, + validate=ISO8601_DATETIME_VALIDATE, metadata={ "description": ( "The date and time of the proof (with a maximum accuracy in seconds)." " Defaults to current system time" ), - "example": INDY_ISO8601_DATETIME_EXAMPLE, + "example": ISO8601_DATETIME_EXAMPLE, }, ) diff --git a/acapy_agent/protocols/issue_credential/v2_0/formats/vc_di/tests/test_handler.py b/acapy_agent/protocols/issue_credential/v2_0/formats/vc_di/tests/test_handler.py index ae8f23fa69..3ff75a5119 100644 --- a/acapy_agent/protocols/issue_credential/v2_0/formats/vc_di/tests/test_handler.py +++ b/acapy_agent/protocols/issue_credential/v2_0/formats/vc_di/tests/test_handler.py @@ -9,11 +9,11 @@ from .......anoncreds.holder import AnonCredsHolder, AnonCredsHolderError from .......anoncreds.issuer import AnonCredsIssuer -from .......anoncreds.models.anoncreds_cred_def import ( +from .......anoncreds.models.credential_definition import ( CredDef, GetCredDefResult, ) -from .......anoncreds.models.anoncreds_revocation import ( +from .......anoncreds.models.revocation import ( GetRevRegDefResult, RevRegDef, ) diff --git a/acapy_agent/protocols/issue_credential/v2_0/manager.py b/acapy_agent/protocols/issue_credential/v2_0/manager.py index 068f94a4bd..124e832c47 100644 --- a/acapy_agent/protocols/issue_credential/v2_0/manager.py +++ b/acapy_agent/protocols/issue_credential/v2_0/manager.py @@ -591,8 +591,14 @@ async def receive_credential( ] handled_formats = [] - # check that we didn't receive any formats not present in the request - if set(issue_formats) - set(req_formats): + def _check_formats(): + """Allow indy issue fomat and anoncreds req format or matching formats.""" + return ( + issue_formats == [V20CredFormat.Format.INDY] + and req_formats == [V20CredFormat.Format.ANONCREDS] + ) or len(set(issue_formats) - set(req_formats)) == 0 + + if not _check_formats(): raise V20CredManagerError( "Received issue credential format(s) not present in credential " f"request: {set(issue_formats) - set(req_formats)}" diff --git a/acapy_agent/protocols/issue_credential/v2_0/message_types.py b/acapy_agent/protocols/issue_credential/v2_0/message_types.py index 668d2b7332..e34ce0385d 100644 --- a/acapy_agent/protocols/issue_credential/v2_0/message_types.py +++ b/acapy_agent/protocols/issue_credential/v2_0/message_types.py @@ -37,21 +37,25 @@ # Format specifications ATTACHMENT_FORMAT = { CRED_20_PROPOSAL: { + V20CredFormat.Format.ANONCREDS.api: "anoncreds/cred-filter@v2.0", V20CredFormat.Format.INDY.api: "hlindy/cred-filter@v2.0", V20CredFormat.Format.LD_PROOF.api: "aries/ld-proof-vc-detail@v1.0", V20CredFormat.Format.VC_DI.api: "didcomm/w3c-di-vc@v0.1", }, CRED_20_OFFER: { + V20CredFormat.Format.ANONCREDS.api: "anoncreds/cred-abstract@v2.0", V20CredFormat.Format.INDY.api: "hlindy/cred-abstract@v2.0", V20CredFormat.Format.LD_PROOF.api: "aries/ld-proof-vc-detail@v1.0", V20CredFormat.Format.VC_DI.api: "didcomm/w3c-di-vc-offer@v0.1", }, CRED_20_REQUEST: { + V20CredFormat.Format.ANONCREDS.api: "anoncreds/cred-req@v2.0", V20CredFormat.Format.INDY.api: "hlindy/cred-req@v2.0", V20CredFormat.Format.LD_PROOF.api: "aries/ld-proof-vc-detail@v1.0", V20CredFormat.Format.VC_DI.api: "didcomm/w3c-di-vc-request@v0.1", }, CRED_20_ISSUE: { + V20CredFormat.Format.ANONCREDS.api: "anoncreds/cred@v2.0", V20CredFormat.Format.INDY.api: "hlindy/cred@v2.0", V20CredFormat.Format.LD_PROOF.api: "aries/ld-proof-vc@v1.0", V20CredFormat.Format.VC_DI.api: "didcomm/w3c-di-vc@v0.1", diff --git a/acapy_agent/protocols/issue_credential/v2_0/messages/cred_format.py b/acapy_agent/protocols/issue_credential/v2_0/messages/cred_format.py index 0574d4736d..589dfcf579 100644 --- a/acapy_agent/protocols/issue_credential/v2_0/messages/cred_format.py +++ b/acapy_agent/protocols/issue_credential/v2_0/messages/cred_format.py @@ -11,6 +11,7 @@ from .....messaging.models.base import BaseModel, BaseModelSchema from .....messaging.valid import UUID4_EXAMPLE from .....utils.classloader import DeferLoad +from ..models.detail.anoncreds import V20CredExRecordAnoncreds from ..models.detail.indy import V20CredExRecordIndy from ..models.detail.ld_proof import V20CredExRecordLDProof @@ -31,6 +32,14 @@ class Meta: class Format(Enum): """Attachment format.""" + ANONCREDS = FormatSpec( + "anoncreds/", + V20CredExRecordAnoncreds, + DeferLoad( + "acapy_agent.protocols.issue_credential.v2_0" + ".formats.anoncreds.handler.AnonCredsCredFormatHandler" + ), + ) INDY = FormatSpec( "hlindy/", V20CredExRecordIndy, @@ -39,23 +48,6 @@ class Format(Enum): ".formats.indy.handler.IndyCredFormatHandler" ), ) - """ - Once we switch to anoncreds this will replace the above INDY definition. - - In the meantime there are some hardcoded references in the - "...formats.indy.handler.IndyCredFormatHandler" class. - - :: - - INDY = FormatSpec( - "hlindy/", - V20CredExRecordIndy, - DeferLoad( - "acapy_agent.protocols.issue_credential.v2_0" - ".formats.anoncreds.handler.AnonCredsCredFormatHandler" - ), - ) - """ LD_PROOF = FormatSpec( "aries/", V20CredExRecordLDProof, @@ -97,7 +89,9 @@ def aries(self) -> str: return self.value.aries @property - def detail(self) -> Union[V20CredExRecordIndy, V20CredExRecordLDProof]: + def detail( + self, + ) -> Union[V20CredExRecordIndy, V20CredExRecordLDProof, V20CredExRecordAnoncreds]: """Accessor for credential exchange detail class.""" return self.value.detail diff --git a/acapy_agent/protocols/issue_credential/v2_0/models/detail/anoncreds.py b/acapy_agent/protocols/issue_credential/v2_0/models/detail/anoncreds.py new file mode 100644 index 0000000000..f683ff68ca --- /dev/null +++ b/acapy_agent/protocols/issue_credential/v2_0/models/detail/anoncreds.py @@ -0,0 +1,131 @@ +"""Anoncreds specific credential exchange information with non-secrets storage.""" + +from typing import Any, Mapping, Optional, Sequence + +from marshmallow import EXCLUDE, fields + +from ......core.profile import ProfileSession +from ......messaging.models.base_record import BaseRecord, BaseRecordSchema +from ......messaging.valid import ( + ANONCREDS_CRED_DEF_ID_EXAMPLE, + ANONCREDS_REV_REG_ID_EXAMPLE, + UUID4_EXAMPLE, +) +from .. import UNENCRYPTED_TAGS + + +class V20CredExRecordAnoncreds(BaseRecord): + """Credential exchange anoncreds detail record.""" + + class Meta: + """V20CredExRecordAnoncreds metadata.""" + + schema_class = "V20CredExRecordAnoncredsSchema" + + RECORD_ID_NAME = "cred_ex_anoncreds_id" + RECORD_TYPE = "anoncreds_cred_ex_v20" + TAG_NAMES = {"~cred_ex_id"} if UNENCRYPTED_TAGS else {"cred_ex_id"} + RECORD_TOPIC = "issue_credential_v2_0_anoncreds" + + def __init__( + self, + cred_ex_anoncreds_id: Optional[str] = None, + *, + cred_ex_id: Optional[str] = None, + cred_id_stored: Optional[str] = None, + cred_request_metadata: Optional[Mapping] = None, + rev_reg_id: Optional[str] = None, + cred_rev_id: Optional[str] = None, + **kwargs, + ): + """Initialize anoncreds credential exchange record details.""" + super().__init__(cred_ex_anoncreds_id, **kwargs) + + self.cred_ex_id = cred_ex_id + self.cred_id_stored = cred_id_stored + self.cred_request_metadata = cred_request_metadata + self.rev_reg_id = rev_reg_id + self.cred_rev_id = cred_rev_id + + @property + def cred_ex_anoncreds_id(self) -> str: + """Accessor for the ID associated with this exchange.""" + return self._id + + @property + def record_value(self) -> dict: + """Accessor for the JSON record value generated for this credential exchange.""" + return { + prop: getattr(self, prop) + for prop in ( + "cred_id_stored", + "cred_request_metadata", + "rev_reg_id", + "cred_rev_id", + ) + } + + @classmethod + async def query_by_cred_ex_id( + cls, + session: ProfileSession, + cred_ex_id: str, + ) -> Sequence["V20CredExRecordAnoncreds"]: + """Retrieve credential exchange anoncreds detail record(s) by its cred ex id.""" + return await cls.query( + session=session, + tag_filter={"cred_ex_id": cred_ex_id}, + ) + + def __eq__(self, other: Any) -> bool: + """Comparison between records.""" + return super().__eq__(other) + + +class V20CredExRecordAnoncredsSchema(BaseRecordSchema): + """Credential exchange anoncreds detail record detail schema.""" + + class Meta: + """Credential exchange anoncreds detail record schema metadata.""" + + model_class = V20CredExRecordAnoncreds + unknown = EXCLUDE + + cred_ex_anoncreds_id = fields.Str( + required=False, + metadata={"description": "Record identifier", "example": UUID4_EXAMPLE}, + ) + cred_ex_id = fields.Str( + required=False, + metadata={ + "description": "Corresponding v2.0 credential exchange record identifier", + "example": UUID4_EXAMPLE, + }, + ) + cred_id_stored = fields.Str( + required=False, + metadata={ + "description": "Credential identifier stored in wallet", + "example": UUID4_EXAMPLE, + }, + ) + cred_request_metadata = fields.Dict( + required=False, + metadata={"description": "Credential request metadata for anoncreds holder"}, + ) + rev_reg_id = fields.Str( + required=False, + metadata={ + "description": "Revocation registry identifier", + "example": ANONCREDS_REV_REG_ID_EXAMPLE, + }, + ) + cred_rev_id = fields.Str( + required=False, + metadata={ + "description": ( + "Credential revocation identifier within revocation registry" + ), + "example": ANONCREDS_CRED_DEF_ID_EXAMPLE, + }, + ) diff --git a/acapy_agent/protocols/issue_credential/v2_0/routes.py b/acapy_agent/protocols/issue_credential/v2_0/routes.py index 7e3c2f20fb..1c06a4e572 100644 --- a/acapy_agent/protocols/issue_credential/v2_0/routes.py +++ b/acapy_agent/protocols/issue_credential/v2_0/routes.py @@ -31,14 +31,16 @@ get_paginated_query_params, ) from ....messaging.valid import ( + ANONCREDS_DID_EXAMPLE, + ANONCREDS_SCHEMA_ID_EXAMPLE, INDY_CRED_DEF_ID_EXAMPLE, INDY_CRED_DEF_ID_VALIDATE, INDY_DID_EXAMPLE, INDY_DID_VALIDATE, INDY_SCHEMA_ID_EXAMPLE, INDY_SCHEMA_ID_VALIDATE, - INDY_VERSION_EXAMPLE, - INDY_VERSION_VALIDATE, + MAJOR_MINOR_VERSION_EXAMPLE, + MAJOR_MINOR_VERSION_VALIDATE, UUID4_EXAMPLE, UUID4_VALIDATE, ) @@ -135,6 +137,36 @@ class V20CredStoreRequestSchema(OpenAPISchema): credential_id = fields.Str(required=False) +class V20CredFilterAnoncredsSchema(OpenAPISchema): + """Anoncreds credential filtration criteria.""" + + cred_def_id = fields.Str( + required=False, + metadata={ + "description": "Credential definition identifier", + "example": ANONCREDS_DID_EXAMPLE, + }, + ) + schema_id = fields.Str( + required=False, + metadata={ + "description": "Schema identifier", + "example": ANONCREDS_SCHEMA_ID_EXAMPLE, + }, + ) + issuer_id = fields.Str( + required=False, + metadata={ + "description": "Credential issuer DID", + "example": ANONCREDS_DID_EXAMPLE, + }, + ) + epoch = fields.Str( + required=False, + metadata={"description": "Credential epoch time", "example": "2021-08-24"}, + ) + + class V20CredFilterIndySchema(OpenAPISchema): """Indy credential filtration criteria.""" @@ -165,8 +197,11 @@ class V20CredFilterIndySchema(OpenAPISchema): ) schema_version = fields.Str( required=False, - validate=INDY_VERSION_VALIDATE, - metadata={"description": "Schema version", "example": INDY_VERSION_EXAMPLE}, + validate=MAJOR_MINOR_VERSION_VALIDATE, + metadata={ + "description": "Schema version", + "example": MAJOR_MINOR_VERSION_EXAMPLE, + }, ) issuer_did = fields.Str( required=False, @@ -205,8 +240,11 @@ class V20CredFilterVCDISchema(OpenAPISchema): ) schema_version = fields.Str( required=False, - validate=INDY_VERSION_VALIDATE, - metadata={"description": "Schema version", "example": INDY_VERSION_EXAMPLE}, + validate=MAJOR_MINOR_VERSION_VALIDATE, + metadata={ + "description": "Schema version", + "example": MAJOR_MINOR_VERSION_EXAMPLE, + }, ) issuer_did = fields.Str( required=False, @@ -218,6 +256,11 @@ class V20CredFilterVCDISchema(OpenAPISchema): class V20CredFilterSchema(OpenAPISchema): """Credential filtration criteria.""" + anoncreds = fields.Nested( + V20CredFilterAnoncredsSchema, + required=False, + metadata={"description": "Credential filter for anoncreds"}, + ) indy = fields.Nested( V20CredFilterIndySchema, required=False, @@ -951,11 +994,17 @@ async def _create_free_offer( ) cred_manager = V20CredManager(profile) - (cred_ex_record, cred_offer_message) = await cred_manager.create_offer( - cred_ex_record, - comment=comment, - replacement_id=replacement_id, - ) + try: + (cred_ex_record, cred_offer_message) = await cred_manager.create_offer( + cred_ex_record, + comment=comment, + replacement_id=replacement_id, + ) + except ValueError as err: + LOGGER.exception(f"Error creating credential offer: {err}") + async with profile.session() as session: + await cred_ex_record.save_error_state(session, reason=err) + raise web.HTTPBadRequest(reason=err) return (cred_ex_record, cred_offer_message) diff --git a/acapy_agent/protocols/issue_credential/v2_0/tests/test_routes.py b/acapy_agent/protocols/issue_credential/v2_0/tests/test_routes.py index 5ca0854086..1359057136 100644 --- a/acapy_agent/protocols/issue_credential/v2_0/tests/test_routes.py +++ b/acapy_agent/protocols/issue_credential/v2_0/tests/test_routes.py @@ -149,9 +149,10 @@ async def test_credential_exchange_retrieve(self): mock_handler.return_value.get_detail_record = mock.CoroutineMock( side_effect=[ - mock.MagicMock( # indy + mock.MagicMock( # anoncreds serialize=mock.MagicMock(return_value={"...": "..."}) ), + None, # indy None, # ld_proof None, # vc_di ] @@ -162,7 +163,8 @@ async def test_credential_exchange_retrieve(self): mock_response.assert_called_once_with( { "cred_ex_record": mock_cx_rec.serialize.return_value, - "indy": {"...": "..."}, + "anoncreds": {"...": "..."}, + "indy": None, "ld_proof": None, "vc_di": None, } @@ -185,6 +187,9 @@ async def test_credential_exchange_retrieve_indy_ld_proof(self): mock_handler.return_value.get_detail_record = mock.CoroutineMock( side_effect=[ + mock.MagicMock( # anoncreds + serialize=mock.MagicMock(return_value={"anon": "creds"}) + ), mock.MagicMock( # indy serialize=mock.MagicMock(return_value={"in": "dy"}) ), @@ -202,6 +207,7 @@ async def test_credential_exchange_retrieve_indy_ld_proof(self): mock_response.assert_called_once_with( { "cred_ex_record": mock_cx_rec.serialize.return_value, + "anoncreds": {"anon": "creds"}, "indy": {"in": "dy"}, "ld_proof": {"ld": "proof"}, "vc_di": {"vc": "di"}, @@ -1230,9 +1236,10 @@ async def test_credential_exchange_issue(self): mock_handler.return_value.get_detail_record = mock.CoroutineMock( side_effect=[ - mock.MagicMock( # indy + mock.MagicMock( # anoncreds serialize=mock.MagicMock(return_value={"...": "..."}) ), + None, None, # ld_proof None, # vc_di ] @@ -1248,7 +1255,8 @@ async def test_credential_exchange_issue(self): mock_response.assert_called_once_with( { "cred_ex_record": mock_cx_rec.serialize.return_value, - "indy": {"...": "..."}, + "anoncreds": {"...": "..."}, + "indy": None, "ld_proof": None, "vc_di": None, } @@ -1277,7 +1285,8 @@ async def test_credential_exchange_issue_vcdi(self): mock_handler.return_value.get_detail_record = mock.CoroutineMock( side_effect=[ - None, + None, # anoncreds + None, # indy None, # ld_proof mock.MagicMock( # indy serialize=mock.MagicMock(return_value={"...": "..."}) @@ -1295,6 +1304,7 @@ async def test_credential_exchange_issue_vcdi(self): mock_response.assert_called_once_with( { "cred_ex_record": mock_cx_rec.serialize.return_value, + "anoncreds": None, "indy": None, "ld_proof": None, "vc_di": {"...": "..."}, @@ -1498,6 +1508,7 @@ async def test_credential_exchange_store(self): ) mock_handler.return_value.get_detail_record = mock.CoroutineMock( side_effect=[ + None, # anoncreds mock.MagicMock( # indy serialize=mock.MagicMock(return_value={"...": "..."}) ), @@ -1519,6 +1530,7 @@ async def test_credential_exchange_store(self): mock_response.assert_called_once_with( { "cred_ex_record": mock_cx_rec.serialize.return_value, + "anoncreds": None, "indy": {"...": "..."}, "ld_proof": None, "vc_di": None, @@ -1575,6 +1587,7 @@ async def test_credential_exchange_store_bad_cred_id_json(self): mock_response.assert_called_once_with( { "cred_ex_record": mock_cx_rec.serialize.return_value, + "anoncreds": None, "indy": {"...": "..."}, "ld_proof": None, "vc_di": None, @@ -1678,6 +1691,7 @@ async def test_credential_exchange_store_x(self): ) mock_handler.return_value.get_detail_record = mock.CoroutineMock( side_effect=[ + None, # anoncreds mock.MagicMock( # indy serialize=mock.MagicMock(return_value={"...": "..."}) ), diff --git a/acapy_agent/protocols/present_proof/anoncreds/pres_exch_handler.py b/acapy_agent/protocols/present_proof/anoncreds/pres_exch_handler.py index 0052a78873..d1d73f8a72 100644 --- a/acapy_agent/protocols/present_proof/anoncreds/pres_exch_handler.py +++ b/acapy_agent/protocols/present_proof/anoncreds/pres_exch_handler.py @@ -1,4 +1,4 @@ -"""Utilities for dif presentation exchange attachment.""" +"""Utilities for anoncreds presentation exchange attachment.""" import json import logging @@ -6,14 +6,15 @@ from typing import Dict, Optional, Tuple, Union from ....anoncreds.holder import AnonCredsHolder, AnonCredsHolderError -from ....anoncreds.models.anoncreds_cred_def import CredDef -from ....anoncreds.models.anoncreds_revocation import RevRegDef -from ....anoncreds.models.anoncreds_schema import AnonCredsSchema +from ....anoncreds.models.credential_definition import CredDef +from ....anoncreds.models.revocation import RevRegDef +from ....anoncreds.models.schema import AnonCredsSchema +from ....anoncreds.models.utils import extract_non_revocation_intervals_from_proof_request from ....anoncreds.registry import AnonCredsRegistry from ....anoncreds.revocation import AnonCredsRevocation +from ....askar.profile_anon import AskarAnoncredsProfile from ....core.error import BaseError from ....core.profile import Profile -from ....indy.models.xform import indy_proof_req2non_revoc_intervals from ..v1_0.models.presentation_exchange import V10PresentationExchange from ..v2_0.messages.pres_format import V20PresFormat from ..v2_0.models.pres_exchange import V20PresExRecord @@ -22,7 +23,7 @@ class AnonCredsPresExchHandlerError(BaseError): - """Base class for Indy Presentation Exchange related errors.""" + """Base class for Anoncreds Presentation Exchange related errors.""" class AnonCredsPresExchHandler: @@ -39,7 +40,9 @@ def __init__( def _extract_proof_request(self, pres_ex_record): if isinstance(pres_ex_record, V20PresExRecord): - return pres_ex_record.pres_request.attachment(V20PresFormat.Format.INDY) + return pres_ex_record.pres_request.attachment( + V20PresFormat.Format.ANONCREDS + ) or pres_ex_record.pres_request.attachment(V20PresFormat.Format.INDY) elif isinstance(pres_ex_record, V10PresentationExchange): return pres_ex_record._presentation_request.ser @@ -229,10 +232,23 @@ async def return_presentation( pres_ex_record: Union[V10PresentationExchange, V20PresExRecord], requested_credentials: Optional[dict] = None, ) -> dict: - """Return Indy proof request as dict.""" + """Return Anoncreds proof request as dict.""" + + # If not anoncreds capable, try to use indy handler. This should be removed when + # indy filter is completely retired + if not isinstance(self._profile, AskarAnoncredsProfile): + from ..indy.pres_exch_handler import IndyPresExchHandler + + handler = IndyPresExchHandler(self._profile) + return await handler.return_presentation( + pres_ex_record, requested_credentials + ) + requested_credentials = requested_credentials or {} proof_request = self._extract_proof_request(pres_ex_record) - non_revoc_intervals = indy_proof_req2non_revoc_intervals(proof_request) + non_revoc_intervals = extract_non_revocation_intervals_from_proof_request( + proof_request + ) requested_referents = self._get_requested_referents( proof_request, requested_credentials, non_revoc_intervals @@ -253,12 +269,12 @@ async def return_presentation( self._set_timestamps(requested_credentials, requested_referents) - indy_proof_json = await self.holder.create_presentation( + proof_json = await self.holder.create_presentation( proof_request, requested_credentials, schemas, cred_defs, revocation_states, ) - indy_proof = json.loads(indy_proof_json) - return indy_proof + proof = json.loads(proof_json) + return proof diff --git a/acapy_agent/protocols/present_proof/indy/pres_exch_handler.py b/acapy_agent/protocols/present_proof/indy/pres_exch_handler.py index 9c510afc9a..b62701ced1 100644 --- a/acapy_agent/protocols/present_proof/indy/pres_exch_handler.py +++ b/acapy_agent/protocols/present_proof/indy/pres_exch_handler.py @@ -55,6 +55,13 @@ async def return_presentation( proof_request = pres_ex_record.pres_request.attachment( V20PresFormat.Format.INDY ) + # If indy filter fails try anoncreds filter format. This is for a + # non-anoncreds agent that gets a anoncreds format proof request and + # should removed when indy format is fully retired. + if not proof_request: + proof_request = pres_ex_record.pres_request.attachment( + V20PresFormat.Format.ANONCREDS + ) elif isinstance(pres_ex_record, V10PresentationExchange): proof_request = pres_ex_record._presentation_request.ser non_revoc_intervals = indy_proof_req2non_revoc_intervals(proof_request) diff --git a/acapy_agent/protocols/present_proof/v1_0/tests/test_routes.py b/acapy_agent/protocols/present_proof/v1_0/tests/test_routes.py index afe923e8be..5c55474765 100644 --- a/acapy_agent/protocols/present_proof/v1_0/tests/test_routes.py +++ b/acapy_agent/protocols/present_proof/v1_0/tests/test_routes.py @@ -4,8 +4,10 @@ from marshmallow import ValidationError from .....admin.request_context import AdminRequestContext +from .....anoncreds.models.presentation_request import ( + AnoncredsPresentationReqAttrSpecSchema, +) from .....indy.holder import IndyHolder -from .....indy.models.proof_request import IndyProofReqAttrSpecSchema from .....indy.verifier import IndyVerifier from .....ledger.base import BaseLedger from .....storage.error import StorageNotFoundError @@ -35,7 +37,7 @@ async def asyncSetUp(self): ) async def test_validate_proof_req_attr_spec(self): - aspec = IndyProofReqAttrSpecSchema() + aspec = AnoncredsPresentationReqAttrSpecSchema() aspec.validate_fields({"name": "attr0"}) aspec.validate_fields( { diff --git a/acapy_agent/protocols/present_proof/v2_0/formats/anoncreds/handler.py b/acapy_agent/protocols/present_proof/v2_0/formats/anoncreds/handler.py index d5618f4a50..2c19d13443 100644 --- a/acapy_agent/protocols/present_proof/v2_0/formats/anoncreds/handler.py +++ b/acapy_agent/protocols/present_proof/v2_0/formats/anoncreds/handler.py @@ -1,4 +1,4 @@ -"""V2.0 present-proof indy presentation-exchange format handler.""" +"""V2.0 present-proof anoncreds presentation-exchange format handler.""" import json import logging @@ -7,12 +7,12 @@ from marshmallow import RAISE from ......anoncreds.holder import AnonCredsHolder +from ......anoncreds.models.predicate import Predicate +from ......anoncreds.models.presentation_request import AnoncredsPresentationRequestSchema +from ......anoncreds.models.proof import AnoncredsProofSchema +from ......anoncreds.models.utils import get_requested_creds_from_proof_request_preview from ......anoncreds.util import generate_pr_nonce from ......anoncreds.verifier import AnonCredsVerifier -from ......indy.models.predicate import Predicate -from ......indy.models.proof import IndyProofSchema -from ......indy.models.proof_request import IndyProofRequestSchema -from ......indy.models.xform import indy_proof_req_preview2indy_requested_creds from ......messaging.decorators.attach_decorator import AttachDecorator from ......messaging.util import canon from ....anoncreds.pres_exch_handler import AnonCredsPresExchHandler @@ -33,7 +33,7 @@ class AnonCredsPresExchangeHandler(V20PresFormatHandler): """Anoncreds presentation format handler.""" - format = V20PresFormat.Format.INDY + format = V20PresFormat.Format.ANONCREDS @classmethod def validate_fields(cls, message_type: str, attachment_data: Mapping): @@ -55,9 +55,9 @@ def validate_fields(cls, message_type: str, attachment_data: Mapping): """ mapping = { - PRES_20_REQUEST: IndyProofRequestSchema, - PRES_20_PROPOSAL: IndyProofRequestSchema, - PRES_20: IndyProofSchema, + PRES_20_REQUEST: AnoncredsPresentationRequestSchema, + PRES_20_PROPOSAL: AnoncredsPresentationRequestSchema, + PRES_20: AnoncredsProofSchema, } # Get schema class @@ -109,20 +109,20 @@ async def create_bound_request( A tuple (updated presentation exchange record, presentation request message) """ - indy_proof_request = pres_ex_record.pres_proposal.attachment( + proof_request = pres_ex_record.pres_proposal.attachment( AnonCredsPresExchangeHandler.format ) if request_data: - indy_proof_request["name"] = request_data.get("name", "proof-request") - indy_proof_request["version"] = request_data.get("version", "1.0") - indy_proof_request["nonce"] = ( + proof_request["name"] = request_data.get("name", "proof-request") + proof_request["version"] = request_data.get("version", "1.0") + proof_request["nonce"] = ( request_data.get("nonce") or await generate_pr_nonce() ) else: - indy_proof_request["name"] = "proof-request" - indy_proof_request["version"] = "1.0" - indy_proof_request["nonce"] = await generate_pr_nonce() - return self.get_format_data(PRES_20_REQUEST, indy_proof_request) + proof_request["name"] = "proof-request" + proof_request["version"] = "1.0" + proof_request["nonce"] = await generate_pr_nonce() + return self.get_format_data(PRES_20_REQUEST, proof_request) async def create_pres( self, @@ -131,45 +131,58 @@ async def create_pres( ) -> Tuple[V20PresFormat, AttachDecorator]: """Create a presentation.""" requested_credentials = {} + + # This is used for the fallback to indy format + from ..indy.handler import IndyPresExchangeHandler + if not request_data: try: proof_request = pres_ex_record.pres_request - indy_proof_request = proof_request.attachment( + # Fall back to indy format should be removed when indy format retired + proof_request = proof_request.attachment( AnonCredsPresExchangeHandler.format - ) - requested_credentials = await indy_proof_req_preview2indy_requested_creds( - indy_proof_request, - preview=None, - holder=AnonCredsHolder(self._profile), + ) or proof_request.attachment(IndyPresExchangeHandler.format) + requested_credentials = ( + await get_requested_creds_from_proof_request_preview( + proof_request, + holder=AnonCredsHolder(self._profile), + ) ) except ValueError as err: LOGGER.warning(f"{err}") - raise V20PresFormatHandlerError( - f"No matching Indy credentials found: {err}" - ) + raise V20PresFormatHandlerError(f"No matching credentials found: {err}") else: - if AnonCredsPresExchangeHandler.format.api in request_data: - indy_spec = request_data.get(AnonCredsPresExchangeHandler.format.api) + # Fall back to indy format should be removed when indy format retired + if ( + AnonCredsPresExchangeHandler.format.api in request_data + or IndyPresExchangeHandler.format.api in request_data + ): + spec = request_data.get( + AnonCredsPresExchangeHandler.format.api + ) or request_data.get(IndyPresExchangeHandler.format.api) requested_credentials = { - "self_attested_attributes": indy_spec["self_attested_attributes"], - "requested_attributes": indy_spec["requested_attributes"], - "requested_predicates": indy_spec["requested_predicates"], + "self_attested_attributes": spec["self_attested_attributes"], + "requested_attributes": spec["requested_attributes"], + "requested_predicates": spec["requested_predicates"], } - indy_handler = AnonCredsPresExchHandler(self._profile) - indy_proof = await indy_handler.return_presentation( + handler = AnonCredsPresExchHandler(self._profile) + presentation_proof = await handler.return_presentation( pres_ex_record=pres_ex_record, requested_credentials=requested_credentials, ) - return self.get_format_data(PRES_20, indy_proof) + return self.get_format_data(PRES_20, presentation_proof) async def receive_pres(self, message: V20Pres, pres_ex_record: V20PresExRecord): """Receive a presentation and check for presented values vs. proposal request.""" def _check_proof_vs_proposal(): """Check for bait and switch in presented values vs. proposal request.""" + from ..indy.handler import IndyPresExchangeHandler + + # Fall back to indy format should be removed when indy format retired proof_req = pres_ex_record.pres_request.attachment( AnonCredsPresExchangeHandler.format - ) + ) or pres_ex_record.pres_request.attachment(IndyPresExchangeHandler.format) # revealed attrs for reft, attr_spec in proof["requested_proof"]["revealed_attrs"].items(): @@ -256,7 +269,8 @@ def _check_proof_vs_proposal(): for req_restriction in req_restrictions: for k in list(req_restriction): # cannot modify en passant if k.startswith("attr::"): - req_restriction.pop(k) # let indy-sdk reject mismatch here + # let anoncreds-sdk reject mismatch here + req_restriction.pop(k) sub_proof_index = pred_spec["sub_proof_index"] for ge_proof in proof["proof"]["proofs"][sub_proof_index][ "primary_proof" @@ -313,10 +327,8 @@ async def verify_pres(self, pres_ex_record: V20PresExRecord) -> V20PresExRecord: """ pres_request_msg = pres_ex_record.pres_request - indy_proof_request = pres_request_msg.attachment( - AnonCredsPresExchangeHandler.format - ) - indy_proof = pres_ex_record.pres.attachment(AnonCredsPresExchangeHandler.format) + proof_request = pres_request_msg.attachment(AnonCredsPresExchangeHandler.format) + proof = pres_ex_record.pres.attachment(AnonCredsPresExchangeHandler.format) verifier = AnonCredsVerifier(self._profile) ( @@ -324,13 +336,13 @@ async def verify_pres(self, pres_ex_record: V20PresExRecord) -> V20PresExRecord: cred_defs, rev_reg_defs, rev_lists, - ) = await verifier.process_pres_identifiers(indy_proof["identifiers"]) + ) = await verifier.process_pres_identifiers(proof["identifiers"]) verifier = AnonCredsVerifier(self._profile) (verified, verified_msgs) = await verifier.verify_presentation( - indy_proof_request, - indy_proof, + proof_request, + proof, schemas, cred_defs, rev_reg_defs, diff --git a/acapy_agent/protocols/present_proof/v2_0/formats/handler.py b/acapy_agent/protocols/present_proof/v2_0/formats/handler.py index c4abf9d4c2..7e4e421cb6 100644 --- a/acapy_agent/protocols/present_proof/v2_0/formats/handler.py +++ b/acapy_agent/protocols/present_proof/v2_0/formats/handler.py @@ -1,4 +1,4 @@ -"""present-proof-v2 format handler - supports DIF and INDY.""" +"""present-proof-v2 format handler - supports ANONCREDS, DIF and INDY.""" import logging from abc import ABC, abstractclassmethod, abstractmethod diff --git a/acapy_agent/protocols/present_proof/v2_0/formats/indy/handler.py b/acapy_agent/protocols/present_proof/v2_0/formats/indy/handler.py index 306ed3b1af..bac3933919 100644 --- a/acapy_agent/protocols/present_proof/v2_0/formats/indy/handler.py +++ b/acapy_agent/protocols/present_proof/v2_0/formats/indy/handler.py @@ -88,9 +88,6 @@ def get_format_identifier(self, message_type: str) -> str: str: Issue credential attachment format identifier """ - # Temporary shim while the new anoncreds library integration is in progress - if self.anoncreds_handler: - return self.anoncreds_handler.get_format_identifier(message_type) return ATTACHMENT_FORMAT[message_type][IndyPresExchangeHandler.format.api] @@ -98,9 +95,6 @@ def get_format_data( self, message_type: str, data: dict ) -> Tuple[V20PresFormat, AttachDecorator]: """Get presentation format and attach objects for use in pres_ex messages.""" - # Temporary shim while the new anoncreds library integration is in progress - if self.anoncreds_handler: - return self.anoncreds_handler.get_format_data(message_type, data) return ( V20PresFormat( @@ -154,9 +148,12 @@ async def create_pres( request_data: Optional[dict] = None, ) -> Tuple[V20PresFormat, AttachDecorator]: """Create a presentation.""" - # Temporary shim while the new anoncreds library integration is in progress + if self.anoncreds_handler: - return await self.anoncreds_handler.create_pres(pres_ex_record, request_data) + return await self.anoncreds_handler.create_pres( + pres_ex_record, + request_data, + ) requested_credentials = {} if not request_data: @@ -349,8 +346,14 @@ async def verify_pres(self, pres_ex_record: V20PresExRecord) -> V20PresExRecord: return await self.anoncreds_handler.verify_pres(pres_ex_record) pres_request_msg = pres_ex_record.pres_request - indy_proof_request = pres_request_msg.attachment(IndyPresExchangeHandler.format) - indy_proof = pres_ex_record.pres.attachment(IndyPresExchangeHandler.format) + + # The `or` anoncreds format is for the indy <--> anoncreds compatibility + indy_proof_request = pres_request_msg.attachment( + IndyPresExchangeHandler.format + ) or pres_request_msg.attachment(AnonCredsPresExchangeHandler.format) + indy_proof = pres_ex_record.pres.attachment( + IndyPresExchangeHandler.format + ) or pres_ex_record.pres.attachment(AnonCredsPresExchangeHandler.format) indy_handler = IndyPresExchHandler(self._profile) ( schemas, diff --git a/acapy_agent/protocols/present_proof/v2_0/message_types.py b/acapy_agent/protocols/present_proof/v2_0/message_types.py index 97b8f063fc..5654ef7d42 100644 --- a/acapy_agent/protocols/present_proof/v2_0/message_types.py +++ b/acapy_agent/protocols/present_proof/v2_0/message_types.py @@ -32,14 +32,17 @@ # Format specifications ATTACHMENT_FORMAT = { PRES_20_PROPOSAL: { + V20PresFormat.Format.ANONCREDS.api: "anoncreds/proof-req@v2.0", V20PresFormat.Format.INDY.api: "hlindy/proof-req@v2.0", V20PresFormat.Format.DIF.api: "dif/presentation-exchange/definitions@v1.0", }, PRES_20_REQUEST: { + V20PresFormat.Format.ANONCREDS.api: "anoncreds/proof-req@v2.0", V20PresFormat.Format.INDY.api: "hlindy/proof-req@v2.0", V20PresFormat.Format.DIF.api: "dif/presentation-exchange/definitions@v1.0", }, PRES_20: { + V20PresFormat.Format.ANONCREDS.api: "anoncreds/proof@v2.0", V20PresFormat.Format.INDY.api: "hlindy/proof@v2.0", V20PresFormat.Format.DIF.api: "dif/presentation-exchange/submission@v1.0", }, diff --git a/acapy_agent/protocols/present_proof/v2_0/messages/pres_format.py b/acapy_agent/protocols/present_proof/v2_0/messages/pres_format.py index 5c55384c86..e65e98644f 100644 --- a/acapy_agent/protocols/present_proof/v2_0/messages/pres_format.py +++ b/acapy_agent/protocols/present_proof/v2_0/messages/pres_format.py @@ -30,6 +30,13 @@ class Meta: class Format(Enum): """Attachment format.""" + ANONCREDS = FormatSpec( + "anoncreds/", + DeferLoad( + "acapy_agent.protocols.present_proof.v2_0" + ".formats.anoncreds.handler.AnonCredsPresExchangeHandler" + ), + ) INDY = FormatSpec( "hlindy/", DeferLoad( @@ -37,19 +44,6 @@ class Format(Enum): ".formats.indy.handler.IndyPresExchangeHandler" ), ) - """ - To make the switch from indy to anoncreds replace the above with the following. - - :: - - INDY = FormatSpec( - "hlindy/", - DeferLoad( - "acapy_agent.protocols.present_proof.v2_0" - ".formats.anoncreds.handler.AnonCredsPresExchangeHandler" - ), - ) - """ DIF = FormatSpec( "dif/", DeferLoad( diff --git a/acapy_agent/protocols/present_proof/v2_0/routes.py b/acapy_agent/protocols/present_proof/v2_0/routes.py index 76fe68c1f6..b428226aa5 100644 --- a/acapy_agent/protocols/present_proof/v2_0/routes.py +++ b/acapy_agent/protocols/present_proof/v2_0/routes.py @@ -16,6 +16,7 @@ from ....admin.decorators.auth import tenant_authentication from ....admin.request_context import AdminRequestContext from ....anoncreds.holder import AnonCredsHolder, AnonCredsHolderError +from ....anoncreds.models.presentation_request import AnoncredsPresentationRequestSchema from ....connections.models.conn_record import ConnRecord from ....indy.holder import IndyHolder, IndyHolderError from ....indy.models.cred_precis import IndyCredPrecisSchema @@ -116,6 +117,11 @@ class V20PresExRecordListSchema(OpenAPISchema): class V20PresProposalByFormatSchema(OpenAPISchema): """Schema for presentation proposal per format.""" + anoncreds = fields.Nested( + AnoncredsPresentationRequestSchema, + required=False, + metadata={"description": "Presentation proposal for anoncreds"}, + ) indy = fields.Nested( IndyProofRequestSchema, required=False, @@ -190,6 +196,11 @@ class V20PresProposalRequestSchema(AdminAPIMessageTracingSchema): class V20PresRequestByFormatSchema(OpenAPISchema): """Presentation request per format.""" + anoncreds = fields.Nested( + AnoncredsPresentationRequestSchema, + required=False, + metadata={"description": "Presentation proposal for anoncreds"}, + ) indy = fields.Nested( IndyProofRequestSchema, required=False, @@ -291,6 +302,11 @@ class V20PresentationSendRequestToProposalSchema(AdminAPIMessageTracingSchema): class V20PresSpecByFormatRequestSchema(AdminAPIMessageTracingSchema): """Presentation specification schema by format, for send-presentation request.""" + anoncreds = fields.Nested( + IndyPresSpecSchema, + required=False, + metadata={"description": "Presentation specification for anoncreds"}, + ) indy = fields.Nested( IndyPresSpecSchema, required=False, @@ -425,7 +441,10 @@ def _formats_attach(by_format: Mapping, msg_type: str, spec: str) -> Mapping: """Break out formats and proposals/requests/presentations for v2.0 messages.""" attach = [] for fmt_api, item_by_fmt in by_format.items(): - if fmt_api == V20PresFormat.Format.INDY.api: + if ( + fmt_api == V20PresFormat.Format.ANONCREDS.api + or fmt_api == V20PresFormat.Format.INDY.api + ): attach.append(AttachDecorator.data_base64(mapping=item_by_fmt, ident=fmt_api)) elif fmt_api == V20PresFormat.Format.DIF.api: attach.append(AttachDecorator.data_json(mapping=item_by_fmt, ident=fmt_api)) @@ -582,19 +601,24 @@ async def present_proof_credentials_list(request: web.BaseRequest): wallet_type = profile.settings.get_value("wallet.type") if wallet_type == "askar-anoncreds": - indy_holder = AnonCredsHolder(profile) + holder = AnonCredsHolder(profile) else: - indy_holder = profile.inject(IndyHolder) - indy_credentials = [] - # INDY + holder = profile.inject(IndyHolder) + credentials = [] + # ANONCREDS or INDY try: - indy_pres_request = pres_ex_record.by_format["pres_request"].get( - V20PresFormat.Format.INDY.api + # try anoncreds and fallback to indy + pres_request = pres_ex_record.by_format["pres_request"].get( + V20PresFormat.Format.ANONCREDS.api ) - if indy_pres_request: - indy_credentials = ( - await indy_holder.get_credentials_for_presentation_request_by_referent( - indy_pres_request, + if not pres_request: + pres_request = pres_ex_record.by_format["pres_request"].get( + V20PresFormat.Format.INDY.api + ) + if pres_request: + credentials = ( + await holder.get_credentials_for_presentation_request_by_referent( + pres_request, pres_referents, offset=offset, limit=limit, @@ -796,7 +820,7 @@ async def present_proof_credentials_list(request: web.BaseRequest): pres_ex_record, outbound_handler, ) - credentials = list(indy_credentials) + dif_cred_value_list + credentials = list(credentials) + dif_cred_value_list return web.json_response(credentials) @@ -936,8 +960,11 @@ async def present_proof_create_request(request: web.BaseRequest): comment = body.get("comment") pres_request_spec = body.get("presentation_request") - if pres_request_spec and V20PresFormat.Format.INDY.api in pres_request_spec: - await _add_nonce(pres_request_spec[V20PresFormat.Format.INDY.api]) + if pres_request_spec: + if V20PresFormat.Format.INDY.api in pres_request_spec: + await _add_nonce(pres_request_spec[V20PresFormat.Format.INDY.api]) + if V20PresFormat.Format.ANONCREDS.api in pres_request_spec: + await _add_nonce(pres_request_spec[V20PresFormat.Format.ANONCREDS.api]) pres_request_message = V20PresRequest( comment=comment, @@ -1020,8 +1047,11 @@ async def present_proof_send_free_request(request: web.BaseRequest): comment = body.get("comment") pres_request_spec = body.get("presentation_request") - if pres_request_spec and V20PresFormat.Format.INDY.api in pres_request_spec: - await _add_nonce(pres_request_spec[V20PresFormat.Format.INDY.api]) + if pres_request_spec: + if V20PresFormat.Format.INDY.api in pres_request_spec: + await _add_nonce(pres_request_spec[V20PresFormat.Format.INDY.api]) + if V20PresFormat.Format.ANONCREDS.api in pres_request_spec: + await _add_nonce(pres_request_spec[V20PresFormat.Format.ANONCREDS.api]) pres_request_message = V20PresRequest( comment=comment, will_confirm=True, @@ -1182,7 +1212,7 @@ async def present_proof_send_presentation(request: web.BaseRequest): outbound_handler = request["outbound_message_router"] pres_ex_id = request.match_info["pres_ex_id"] body = await request.json() - supported_formats = ["dif", "indy"] + supported_formats = ["anoncreds", "dif", "indy"] if not any(x in body for x in supported_formats): raise web.HTTPBadRequest( reason=( diff --git a/acapy_agent/protocols/present_proof/v2_0/tests/test_manager_anoncreds.py b/acapy_agent/protocols/present_proof/v2_0/tests/test_manager_anoncreds.py index 0ee4f1ad38..78d93e01a7 100644 --- a/acapy_agent/protocols/present_proof/v2_0/tests/test_manager_anoncreds.py +++ b/acapy_agent/protocols/present_proof/v2_0/tests/test_manager_anoncreds.py @@ -74,7 +74,7 @@ ) ], ) -INDY_PROOF_REQ_NAME = { +ANONCREDS_PROOF_REQ_NAME = { "name": PROOF_REQ_NAME, "version": PROOF_REQ_VERSION, "nonce": PROOF_REQ_NONCE, @@ -100,7 +100,7 @@ } }, } -INDY_PROOF_REQ_NAMES = { +ANONCREDS_PROOF_REQ_NAMES = { "name": PROOF_REQ_NAME, "version": PROOF_REQ_VERSION, "nonce": PROOF_REQ_NONCE, @@ -121,7 +121,7 @@ } }, } -INDY_PROOF_REQ_SELFIE = { +ANONCREDS_PROOF_REQ_SELFIE = { "name": PROOF_REQ_NAME, "version": PROOF_REQ_VERSION, "nonce": PROOF_REQ_NONCE, @@ -133,7 +133,7 @@ "0_highscore_GE_uuid": {"name": "highScore", "p_type": ">=", "p_value": 1000000} }, } -INDY_PROOF = { +ANONCREDS_PROOF = { "proof": { "proofs": [ { @@ -252,7 +252,7 @@ } ], } -INDY_PROOF_NAMES = { +ANONCREDS_PROOF_NAMES = { "proof": { "proofs": [ { @@ -512,7 +512,9 @@ async def test_record_eq(self): async def test_create_exchange_for_proposal(self): proposal = V20PresProposal( formats=[ - V20PresFormat(attach_id="indy", format_=V20PresFormat.Format.INDY.aries) + V20PresFormat( + attach_id="anoncreds", format_=V20PresFormat.Format.ANONCREDS.aries + ) ] ) @@ -537,7 +539,9 @@ async def test_receive_proposal(self): connection_record = mock.MagicMock(connection_id=CONN_ID) proposal = V20PresProposal( formats=[ - V20PresFormat(attach_id="indy", format_=V20PresFormat.Format.INDY.aries) + V20PresFormat( + attach_id="anoncreds", format_=V20PresFormat.Format.ANONCREDS.aries + ) ] ) with mock.patch.object(V20PresExRecord, "save", autospec=True) as save_ex: @@ -555,14 +559,14 @@ async def test_create_bound_request_a(self): proposal = V20PresProposal( formats=[ V20PresFormat( - attach_id="indy", + attach_id="anoncreds", format_=ATTACHMENT_FORMAT[PRES_20_PROPOSAL][ - V20PresFormat.Format.INDY.api + V20PresFormat.Format.ANONCREDS.api ], ) ], proposals_attach=[ - AttachDecorator.data_base64(INDY_PROOF_REQ_NAME, ident="indy") + AttachDecorator.data_base64(ANONCREDS_PROOF_REQ_NAME, ident="anoncreds") ], ) px_rec = V20PresExRecord( @@ -589,14 +593,14 @@ async def test_create_bound_request_b(self): proposal = V20PresProposal( formats=[ V20PresFormat( - attach_id="indy", + attach_id="anoncreds", format_=ATTACHMENT_FORMAT[PRES_20_PROPOSAL][ - V20PresFormat.Format.INDY.api + V20PresFormat.Format.ANONCREDS.api ], ) ], proposals_attach=[ - AttachDecorator.data_base64(INDY_PROOF_REQ_NAME, ident="indy") + AttachDecorator.data_base64(ANONCREDS_PROOF_REQ_NAME, ident="anoncreds") ], ) px_rec = V20PresExRecord( @@ -725,14 +729,16 @@ async def test_create_exchange_for_request(self): will_confirm=True, formats=[ V20PresFormat( - attach_id="indy", + attach_id="anoncreds", format_=ATTACHMENT_FORMAT[PRES_20_REQUEST][ - V20PresFormat.Format.INDY.api + V20PresFormat.Format.ANONCREDS.api ], ) ], request_presentations_attach=[ - AttachDecorator.data_base64(mapping=INDY_PROOF_REQ_NAME, ident="indy") + AttachDecorator.data_base64( + mapping=ANONCREDS_PROOF_REQ_NAME, ident="anoncreds" + ) ], ) pres_req.assign_thread_id("dummy") @@ -765,14 +771,14 @@ async def test_create_pres_indy(self): pres_request = V20PresRequest( formats=[ V20PresFormat( - attach_id="indy", + attach_id="anoncreds", format_=ATTACHMENT_FORMAT[PRES_20_REQUEST][ - V20PresFormat.Format.INDY.api + V20PresFormat.Format.ANONCREDS.api ], ) ], request_presentations_attach=[ - AttachDecorator.data_base64(INDY_PROOF_REQ_NAME, ident="indy") + AttachDecorator.data_base64(ANONCREDS_PROOF_REQ_NAME, ident="anoncreds") ], ) px_rec_in = V20PresExRecord(pres_request=pres_request.serialize()) @@ -795,9 +801,9 @@ async def test_create_pres_indy(self): ) req_creds = await indy_proof_req_preview2indy_requested_creds( - INDY_PROOF_REQ_NAME, preview=None, holder=self.holder + ANONCREDS_PROOF_REQ_NAME, preview=None, holder=self.holder ) - request_data = {"indy": req_creds} + request_data = {"anoncreds": req_creds} assert not req_creds["self_attested_attributes"] assert len(req_creds["requested_attributes"]) == 2 assert len(req_creds["requested_predicates"]) == 1 @@ -813,9 +819,9 @@ async def test_create_pres_indy_and_dif(self): pres_request = V20PresRequest( formats=[ V20PresFormat( - attach_id="indy", + attach_id="anoncreds", format_=ATTACHMENT_FORMAT[PRES_20_REQUEST][ - V20PresFormat.Format.INDY.api + V20PresFormat.Format.ANONCREDS.api ], ), V20PresFormat( @@ -826,7 +832,7 @@ async def test_create_pres_indy_and_dif(self): ), ], request_presentations_attach=[ - AttachDecorator.data_base64(INDY_PROOF_REQ_NAME, ident="indy"), + AttachDecorator.data_base64(ANONCREDS_PROOF_REQ_NAME, ident="anoncreds"), AttachDecorator.data_json(DIF_PRES_REQ, ident="dif"), ], ) @@ -857,9 +863,9 @@ async def test_create_pres_indy_and_dif(self): ) req_creds = await indy_proof_req_preview2indy_requested_creds( - INDY_PROOF_REQ_NAME, preview=None, holder=self.holder + ANONCREDS_PROOF_REQ_NAME, preview=None, holder=self.holder ) - request_data = {"indy": req_creds, "dif": DIF_PRES_REQ} + request_data = {"anoncreds": req_creds, "dif": DIF_PRES_REQ} assert not req_creds["self_attested_attributes"] assert len(req_creds["requested_attributes"]) == 2 assert len(req_creds["requested_predicates"]) == 1 @@ -872,19 +878,19 @@ async def test_create_pres_indy_and_dif(self): @pytest.mark.skip(reason="Anoncreds-break") async def test_create_pres_proof_req_non_revoc_interval_none(self): - indy_proof_req_vcx = deepcopy(INDY_PROOF_REQ_NAME) + indy_proof_req_vcx = deepcopy(ANONCREDS_PROOF_REQ_NAME) indy_proof_req_vcx["non_revoked"] = None # simulate interop with indy-vcx pres_request = V20PresRequest( formats=[ V20PresFormat( - attach_id="indy", + attach_id="anoncreds", format_=ATTACHMENT_FORMAT[PRES_20_REQUEST][ - V20PresFormat.Format.INDY.api + V20PresFormat.Format.ANONCREDS.api ], ) ], request_presentations_attach=[ - AttachDecorator.data_base64(indy_proof_req_vcx, ident="indy") + AttachDecorator.data_base64(indy_proof_req_vcx, ident="anoncreds") ], ) px_rec_in = V20PresExRecord(pres_request=pres_request.serialize()) @@ -918,7 +924,7 @@ async def test_create_pres_proof_req_non_revoc_interval_none(self): req_creds = await indy_proof_req_preview2indy_requested_creds( indy_proof_req_vcx, preview=None, holder=self.holder ) - request_data = {"indy": req_creds} + request_data = {"anoncreds": req_creds} assert not req_creds["self_attested_attributes"] assert len(req_creds["requested_attributes"]) == 2 assert len(req_creds["requested_predicates"]) == 1 @@ -934,14 +940,14 @@ async def test_create_pres_self_asserted(self): pres_request = V20PresRequest( formats=[ V20PresFormat( - attach_id="indy", + attach_id="anoncreds", format_=ATTACHMENT_FORMAT[PRES_20_REQUEST][ - V20PresFormat.Format.INDY.api + V20PresFormat.Format.ANONCREDS.api ], ) ], request_presentations_attach=[ - AttachDecorator.data_base64(INDY_PROOF_REQ_SELFIE, ident="indy") + AttachDecorator.data_base64(ANONCREDS_PROOF_REQ_SELFIE, ident="anoncreds") ], ) px_rec_in = V20PresExRecord(pres_request=pres_request.serialize()) @@ -965,9 +971,9 @@ async def test_create_pres_self_asserted(self): ) req_creds = await indy_proof_req_preview2indy_requested_creds( - INDY_PROOF_REQ_SELFIE, preview=None, holder=self.holder + ANONCREDS_PROOF_REQ_SELFIE, preview=None, holder=self.holder ) - request_data = {"indy": req_creds} + request_data = {"anoncreds": req_creds} assert len(req_creds["self_attested_attributes"]) == 3 assert not req_creds["requested_attributes"] @@ -991,14 +997,14 @@ async def test_create_pres_no_revocation(self): pres_request = V20PresRequest( formats=[ V20PresFormat( - attach_id="indy", + attach_id="anoncreds", format_=ATTACHMENT_FORMAT[PRES_20_REQUEST][ - V20PresFormat.Format.INDY.api + V20PresFormat.Format.ANONCREDS.api ], ) ], request_presentations_attach=[ - AttachDecorator.data_base64(INDY_PROOF_REQ_NAME, ident="indy") + AttachDecorator.data_base64(ANONCREDS_PROOF_REQ_NAME, ident="anoncreds") ], ) px_rec_in = V20PresExRecord(pres_request=pres_request.serialize()) @@ -1042,10 +1048,10 @@ async def test_create_pres_no_revocation(self): ) req_creds = await indy_proof_req_preview2indy_requested_creds( - INDY_PROOF_REQ_NAME, preview=None, holder=self.holder + ANONCREDS_PROOF_REQ_NAME, preview=None, holder=self.holder ) request_data = { - "indy": { + "anoncreds": { "self_attested_attributes": req_creds["self_attested_attributes"], "requested_attributes": req_creds["requested_attributes"], "requested_predicates": req_creds["requested_predicates"], @@ -1062,7 +1068,7 @@ async def test_create_pres_no_revocation(self): for pred_reft_spec in req_creds["requested_predicates"].values(): pred_reft_spec["timestamp"] = 1234567890 request_data = { - "indy": { + "anoncreds": { "self_attested_attributes": req_creds["self_attested_attributes"], "requested_attributes": req_creds["requested_attributes"], "requested_predicates": req_creds["requested_predicates"], @@ -1076,14 +1082,14 @@ async def test_create_pres_bad_revoc_state(self): pres_request = V20PresRequest( formats=[ V20PresFormat( - attach_id="indy", + attach_id="anoncreds", format_=ATTACHMENT_FORMAT[PRES_20_REQUEST][ - V20PresFormat.Format.INDY.api + V20PresFormat.Format.ANONCREDS.api ], ) ], request_presentations_attach=[ - AttachDecorator.data_base64(INDY_PROOF_REQ_NAME, ident="indy") + AttachDecorator.data_base64(ANONCREDS_PROOF_REQ_NAME, ident="anoncreds") ], ) px_rec_in = V20PresExRecord(pres_request=pres_request.serialize()) @@ -1147,14 +1153,14 @@ async def test_create_pres_multi_matching_proposal_creds_names(self): pres_request = V20PresRequest( formats=[ V20PresFormat( - attach_id="indy", + attach_id="anoncreds", format_=ATTACHMENT_FORMAT[PRES_20_REQUEST][ - V20PresFormat.Format.INDY.api + V20PresFormat.Format.ANONCREDS.api ], ) ], request_presentations_attach=[ - AttachDecorator.data_base64(INDY_PROOF_REQ_NAMES, ident="indy") + AttachDecorator.data_base64(ANONCREDS_PROOF_REQ_NAMES, ident="anoncreds") ], ) px_rec_in = V20PresExRecord(pres_request=pres_request.serialize()) @@ -1228,12 +1234,12 @@ async def test_create_pres_multi_matching_proposal_creds_names(self): ) req_creds = await indy_proof_req_preview2indy_requested_creds( - INDY_PROOF_REQ_NAMES, preview=None, holder=self.holder + ANONCREDS_PROOF_REQ_NAMES, preview=None, holder=self.holder ) assert not req_creds["self_attested_attributes"] assert len(req_creds["requested_attributes"]) == 1 assert len(req_creds["requested_predicates"]) == 1 - request_data = {"indy": req_creds} + request_data = {"anoncreds": req_creds} (px_rec_out, pres_msg) = await self.manager.create_pres( px_rec_in, request_data ) @@ -1244,14 +1250,14 @@ async def test_no_matching_creds_for_proof_req(self): pres_request = V20PresRequest( formats=[ V20PresFormat( - attach_id="indy", + attach_id="anoncreds", format_=ATTACHMENT_FORMAT[PRES_20_REQUEST][ - V20PresFormat.Format.INDY.api + V20PresFormat.Format.ANONCREDS.api ], ) ], request_presentations_attach=[ - AttachDecorator.data_base64(INDY_PROOF_REQ_NAMES, ident="indy") + AttachDecorator.data_base64(ANONCREDS_PROOF_REQ_NAMES, ident="anoncreds") ], ) V20PresExRecord(pres_request=pres_request.serialize()) @@ -1260,7 +1266,7 @@ async def test_no_matching_creds_for_proof_req(self): with self.assertRaises(ValueError): await indy_proof_req_preview2indy_requested_creds( - INDY_PROOF_REQ_NAMES, preview=None, holder=self.holder + ANONCREDS_PROOF_REQ_NAMES, preview=None, holder=self.holder ) get_creds = mock.CoroutineMock( @@ -1277,21 +1283,21 @@ async def test_no_matching_creds_for_proof_req(self): ) self.holder.get_credentials_for_presentation_request_by_referent = get_creds await indy_proof_req_preview2indy_requested_creds( - INDY_PROOF_REQ_NAMES, preview=None, holder=self.holder + ANONCREDS_PROOF_REQ_NAMES, preview=None, holder=self.holder ) async def test_no_matching_creds_indy_handler(self): pres_request = V20PresRequest( formats=[ V20PresFormat( - attach_id="indy", + attach_id="anoncreds", format_=ATTACHMENT_FORMAT[PRES_20_REQUEST][ - V20PresFormat.Format.INDY.api + V20PresFormat.Format.ANONCREDS.api ], ) ], request_presentations_attach=[ - AttachDecorator.data_base64(INDY_PROOF_REQ_NAMES, ident="indy") + AttachDecorator.data_base64(ANONCREDS_PROOF_REQ_NAMES, ident="anoncreds") ], ) px_rec_in = V20PresExRecord(pres_request=pres_request.serialize()) @@ -1311,44 +1317,50 @@ async def test_no_matching_creds_indy_handler(self): (px_rec_out, pres_msg) = await self.manager.create_pres( px_rec_in, request_data ) - assert "No matching Indy" in str(context.exception) + assert "AnonCreds interface requires AskarAnoncreds profile" in str( + context.exception + ) async def test_receive_pres(self): connection_record = mock.MagicMock(connection_id=CONN_ID) pres_proposal = V20PresProposal( formats=[ V20PresFormat( - attach_id="indy", + attach_id="anoncreds", format_=ATTACHMENT_FORMAT[PRES_20_PROPOSAL][ - V20PresFormat.Format.INDY.api + V20PresFormat.Format.ANONCREDS.api ], ) ], proposals_attach=[ - AttachDecorator.data_base64(INDY_PROOF_REQ_NAME, ident="indy") + AttachDecorator.data_base64(ANONCREDS_PROOF_REQ_NAME, ident="anoncreds") ], ) pres_request = V20PresRequest( formats=[ V20PresFormat( - attach_id="indy", + attach_id="anoncreds", format_=ATTACHMENT_FORMAT[PRES_20_REQUEST][ - V20PresFormat.Format.INDY.api + V20PresFormat.Format.ANONCREDS.api ], ) ], request_presentations_attach=[ - AttachDecorator.data_base64(INDY_PROOF_REQ_NAME, ident="indy") + AttachDecorator.data_base64(ANONCREDS_PROOF_REQ_NAME, ident="anoncreds") ], ) pres = V20Pres( formats=[ V20PresFormat( - attach_id="indy", - format_=ATTACHMENT_FORMAT[PRES_20][V20PresFormat.Format.INDY.api], + attach_id="anoncreds", + format_=ATTACHMENT_FORMAT[PRES_20][ + V20PresFormat.Format.ANONCREDS.api + ], ) ], - presentations_attach=[AttachDecorator.data_base64(INDY_PROOF, ident="indy")], + presentations_attach=[ + AttachDecorator.data_base64(ANONCREDS_PROOF, ident="anoncreds") + ], ) pres.assign_thread_id("thread-id") @@ -1360,8 +1372,8 @@ async def test_receive_pres(self): # cover by_format property by_format = px_rec_dummy.by_format - assert by_format.get("pres_proposal").get("indy") == INDY_PROOF_REQ_NAME - assert by_format.get("pres_request").get("indy") == INDY_PROOF_REQ_NAME + assert by_format.get("pres_proposal").get("anoncreds") == ANONCREDS_PROOF_REQ_NAME + assert by_format.get("pres_request").get("anoncreds") == ANONCREDS_PROOF_REQ_NAME with mock.patch.object( V20PresExRecord, "save", autospec=True @@ -1383,41 +1395,45 @@ async def test_receive_pres_receive_pred_value_mismatch_punt_to_indy(self): pres_proposal = V20PresProposal( formats=[ V20PresFormat( - attach_id="indy", + attach_id="anoncreds", format_=ATTACHMENT_FORMAT[PRES_20_PROPOSAL][ - V20PresFormat.Format.INDY.api + V20PresFormat.Format.ANONCREDS.api ], ) ], proposals_attach=[ - AttachDecorator.data_base64(INDY_PROOF_REQ_NAME, ident="indy") + AttachDecorator.data_base64(ANONCREDS_PROOF_REQ_NAME, ident="anoncreds") ], ) - indy_proof_req = deepcopy(INDY_PROOF_REQ_NAME) + indy_proof_req = deepcopy(ANONCREDS_PROOF_REQ_NAME) indy_proof_req["requested_predicates"]["0_highscore_GE_uuid"]["restrictions"][0][ "attr::player::value" ] = "impostor" pres_request = V20PresRequest( formats=[ V20PresFormat( - attach_id="indy", + attach_id="anoncreds", format_=ATTACHMENT_FORMAT[PRES_20_REQUEST][ - V20PresFormat.Format.INDY.api + V20PresFormat.Format.ANONCREDS.api ], ) ], request_presentations_attach=[ - AttachDecorator.data_base64(indy_proof_req, ident="indy") + AttachDecorator.data_base64(indy_proof_req, ident="anoncreds") ], ) pres = V20Pres( formats=[ V20PresFormat( - attach_id="indy", - format_=ATTACHMENT_FORMAT[PRES_20][V20PresFormat.Format.INDY.api], + attach_id="anoncreds", + format_=ATTACHMENT_FORMAT[PRES_20][ + V20PresFormat.Format.ANONCREDS.api + ], ) ], - presentations_attach=[AttachDecorator.data_base64(INDY_PROOF, ident="indy")], + presentations_attach=[ + AttachDecorator.data_base64(ANONCREDS_PROOF, ident="anoncreds") + ], ) pres.assign_thread_id("thread-id") @@ -1429,8 +1445,8 @@ async def test_receive_pres_receive_pred_value_mismatch_punt_to_indy(self): # cover by_format property by_format = px_rec_dummy.by_format - assert by_format.get("pres_proposal").get("indy") == INDY_PROOF_REQ_NAME - assert by_format.get("pres_request").get("indy") == indy_proof_req + assert by_format.get("pres_proposal").get("anoncreds") == ANONCREDS_PROOF_REQ_NAME + assert by_format.get("pres_request").get("anoncreds") == indy_proof_req with mock.patch.object( V20PresExRecord, "save", autospec=True @@ -1478,24 +1494,28 @@ async def test_receive_pres_indy_no_predicate_restrictions(self): pres_request = V20PresRequest( formats=[ V20PresFormat( - attach_id="indy", + attach_id="anoncreds", format_=ATTACHMENT_FORMAT[PRES_20_REQUEST][ - V20PresFormat.Format.INDY.api + V20PresFormat.Format.ANONCREDS.api ], ) ], request_presentations_attach=[ - AttachDecorator.data_base64(indy_proof_req, ident="indy") + AttachDecorator.data_base64(indy_proof_req, ident="anoncreds") ], ) pres = V20Pres( formats=[ V20PresFormat( - attach_id="indy", - format_=ATTACHMENT_FORMAT[PRES_20][V20PresFormat.Format.INDY.api], + attach_id="anoncreds", + format_=ATTACHMENT_FORMAT[PRES_20][ + V20PresFormat.Format.ANONCREDS.api + ], ) ], - presentations_attach=[AttachDecorator.data_base64(INDY_PROOF, ident="indy")], + presentations_attach=[ + AttachDecorator.data_base64(ANONCREDS_PROOF, ident="anoncreds") + ], ) pres.assign_thread_id("thread-id") @@ -1506,7 +1526,7 @@ async def test_receive_pres_indy_no_predicate_restrictions(self): # cover by_format property by_format = px_rec_dummy.by_format - assert by_format.get("pres_request").get("indy") == indy_proof_req + assert by_format.get("pres_request").get("anoncreds") == indy_proof_req with mock.patch.object( V20PresExRecord, "save", autospec=True @@ -1538,7 +1558,7 @@ async def test_receive_pres_indy_no_attr_restrictions(self): }, "requested_predicates": {}, } - proof = deepcopy(INDY_PROOF) + proof = deepcopy(ANONCREDS_PROOF) proof["requested_proof"]["revealed_attrs"] = { "0_player_uuid": { "sub_proof_index": 0, @@ -1550,24 +1570,26 @@ async def test_receive_pres_indy_no_attr_restrictions(self): pres_request = V20PresRequest( formats=[ V20PresFormat( - attach_id="indy", + attach_id="anoncreds", format_=ATTACHMENT_FORMAT[PRES_20_REQUEST][ - V20PresFormat.Format.INDY.api + V20PresFormat.Format.ANONCREDS.api ], ) ], request_presentations_attach=[ - AttachDecorator.data_base64(indy_proof_req, ident="indy") + AttachDecorator.data_base64(indy_proof_req, ident="anoncreds") ], ) pres = V20Pres( formats=[ V20PresFormat( - attach_id="indy", - format_=ATTACHMENT_FORMAT[PRES_20][V20PresFormat.Format.INDY.api], + attach_id="anoncreds", + format_=ATTACHMENT_FORMAT[PRES_20][ + V20PresFormat.Format.ANONCREDS.api + ], ) ], - presentations_attach=[AttachDecorator.data_base64(proof, ident="indy")], + presentations_attach=[AttachDecorator.data_base64(proof, ident="anoncreds")], ) pres.assign_thread_id("thread-id") @@ -1578,7 +1600,7 @@ async def test_receive_pres_indy_no_attr_restrictions(self): # cover by_format property by_format = px_rec_dummy.by_format - assert by_format.get("pres_request").get("indy") == indy_proof_req + assert by_format.get("pres_request").get("anoncreds") == indy_proof_req with mock.patch.object( V20PresExRecord, "save", autospec=True @@ -1597,44 +1619,48 @@ async def test_receive_pres_indy_no_attr_restrictions(self): async def test_receive_pres_bait_and_switch_attr_name(self): connection_record = mock.MagicMock(connection_id=CONN_ID) - indy_proof_req = deepcopy(INDY_PROOF_REQ_NAME) + indy_proof_req = deepcopy(ANONCREDS_PROOF_REQ_NAME) indy_proof_req["requested_attributes"]["0_screencapture_uuid"]["restrictions"][0][ "attr::screenCapture::value" ] = "c2NyZWVuIGNhcHR1cmUgc2hvd2luZyBzY29yZSBpbiB0aGUgbWlsbGlvbnM=" pres_proposal = V20PresProposal( formats=[ V20PresFormat( - attach_id="indy", + attach_id="anoncreds", format_=ATTACHMENT_FORMAT[PRES_20_PROPOSAL][ - V20PresFormat.Format.INDY.api + V20PresFormat.Format.ANONCREDS.api ], ) ], proposals_attach=[ - AttachDecorator.data_base64(INDY_PROOF_REQ_NAME, ident="indy") + AttachDecorator.data_base64(ANONCREDS_PROOF_REQ_NAME, ident="anoncreds") ], ) pres_request = V20PresRequest( formats=[ V20PresFormat( - attach_id="indy", + attach_id="anoncreds", format_=ATTACHMENT_FORMAT[PRES_20_REQUEST][ - V20PresFormat.Format.INDY.api + V20PresFormat.Format.ANONCREDS.api ], ) ], request_presentations_attach=[ - AttachDecorator.data_base64(indy_proof_req, ident="indy") + AttachDecorator.data_base64(indy_proof_req, ident="anoncreds") ], ) pres_x = V20Pres( formats=[ V20PresFormat( - attach_id="indy", - format_=ATTACHMENT_FORMAT[PRES_20][V20PresFormat.Format.INDY.api], + attach_id="anoncreds", + format_=ATTACHMENT_FORMAT[PRES_20][ + V20PresFormat.Format.ANONCREDS.api + ], ) ], - presentations_attach=[AttachDecorator.data_base64(INDY_PROOF, ident="indy")], + presentations_attach=[ + AttachDecorator.data_base64(ANONCREDS_PROOF, ident="anoncreds") + ], ) px_rec_dummy = V20PresExRecord( pres_proposal=pres_proposal.serialize(), @@ -1655,33 +1681,41 @@ async def test_receive_pres_bait_and_switch_attr_name(self): pres_proposal = V20PresProposal( formats=[ V20PresFormat( - attach_id="indy", + attach_id="anoncreds", format_=ATTACHMENT_FORMAT[PRES_20_PROPOSAL][ - V20PresFormat.Format.INDY.api + V20PresFormat.Format.ANONCREDS.api ], ) ], - proposals_attach=[AttachDecorator.data_base64(indy_proof_req, ident="indy")], + proposals_attach=[ + AttachDecorator.data_base64(indy_proof_req, ident="anoncreds") + ], ) pres_request = V20PresRequest( formats=[ V20PresFormat( - attach_id="indy", - format_=ATTACHMENT_FORMAT[PRES_20][V20PresFormat.Format.INDY.api], + attach_id="anoncreds", + format_=ATTACHMENT_FORMAT[PRES_20][ + V20PresFormat.Format.ANONCREDS.api + ], ) ], request_presentations_attach=[ - AttachDecorator.data_base64(indy_proof_req, ident="indy") + AttachDecorator.data_base64(indy_proof_req, ident="anoncreds") ], ) pres_x = V20Pres( formats=[ V20PresFormat( - attach_id="indy", - format_=ATTACHMENT_FORMAT[PRES_20][V20PresFormat.Format.INDY.api], + attach_id="anoncreds", + format_=ATTACHMENT_FORMAT[PRES_20][ + V20PresFormat.Format.ANONCREDS.api + ], ) ], - presentations_attach=[AttachDecorator.data_base64(INDY_PROOF, ident="indy")], + presentations_attach=[ + AttachDecorator.data_base64(ANONCREDS_PROOF, ident="anoncreds") + ], ) px_rec_dummy = V20PresExRecord( @@ -1699,43 +1733,47 @@ async def test_receive_pres_bait_and_switch_attr_name(self): async def test_receive_pres_bait_and_switch_attr_names(self): connection_record = mock.MagicMock(connection_id=CONN_ID) - indy_proof_req = deepcopy(INDY_PROOF_REQ_NAMES) + indy_proof_req = deepcopy(ANONCREDS_PROOF_REQ_NAMES) indy_proof_req["requested_attributes"]["0_player_uuid"]["restrictions"][0][ "attr::screenCapture::value" ] = "c2NyZWVuIGNhcHR1cmUgc2hvd2luZyBzY29yZSBpbiB0aGUgbWlsbGlvbnM=" pres_proposal = V20PresProposal( formats=[ V20PresFormat( - attach_id="indy", + attach_id="anoncreds", format_=ATTACHMENT_FORMAT[PRES_20_PROPOSAL][ - V20PresFormat.Format.INDY.api + V20PresFormat.Format.ANONCREDS.api ], ) ], - proposals_attach=[AttachDecorator.data_base64(indy_proof_req, ident="indy")], + proposals_attach=[ + AttachDecorator.data_base64(indy_proof_req, ident="anoncreds") + ], ) pres_request = V20PresRequest( formats=[ V20PresFormat( - attach_id="indy", + attach_id="anoncreds", format_=ATTACHMENT_FORMAT[PRES_20_REQUEST][ - V20PresFormat.Format.INDY.api + V20PresFormat.Format.ANONCREDS.api ], ) ], request_presentations_attach=[ - AttachDecorator.data_base64(indy_proof_req, ident="indy") + AttachDecorator.data_base64(indy_proof_req, ident="anoncreds") ], ) pres_x = V20Pres( formats=[ V20PresFormat( - attach_id="indy", - format_=ATTACHMENT_FORMAT[PRES_20][V20PresFormat.Format.INDY.api], + attach_id="anoncreds", + format_=ATTACHMENT_FORMAT[PRES_20][ + V20PresFormat.Format.ANONCREDS.api + ], ) ], presentations_attach=[ - AttachDecorator.data_base64(INDY_PROOF_NAMES, ident="indy") + AttachDecorator.data_base64(ANONCREDS_PROOF_NAMES, ident="anoncreds") ], ) @@ -1760,34 +1798,40 @@ async def test_receive_pres_bait_and_switch_attr_names(self): pres_proposal = V20PresProposal( formats=[ V20PresFormat( - attach_id="indy", + attach_id="anoncreds", format_=ATTACHMENT_FORMAT[PRES_20_PROPOSAL][ - V20PresFormat.Format.INDY.api + V20PresFormat.Format.ANONCREDS.api ], ) ], - proposals_attach=[AttachDecorator.data_base64(indy_proof_req, ident="indy")], + proposals_attach=[ + AttachDecorator.data_base64(indy_proof_req, ident="anoncreds") + ], ) pres_request = V20PresRequest( formats=[ V20PresFormat( - attach_id="indy", - format_=ATTACHMENT_FORMAT[PRES_20][V20PresFormat.Format.INDY.api], + attach_id="anoncreds", + format_=ATTACHMENT_FORMAT[PRES_20][ + V20PresFormat.Format.ANONCREDS.api + ], ) ], request_presentations_attach=[ - AttachDecorator.data_base64(indy_proof_req, ident="indy") + AttachDecorator.data_base64(indy_proof_req, ident="anoncreds") ], ) pres_x = V20Pres( formats=[ V20PresFormat( - attach_id="indy", - format_=ATTACHMENT_FORMAT[PRES_20][V20PresFormat.Format.INDY.api], + attach_id="anoncreds", + format_=ATTACHMENT_FORMAT[PRES_20][ + V20PresFormat.Format.ANONCREDS.api + ], ) ], presentations_attach=[ - AttachDecorator.data_base64(INDY_PROOF_NAMES, ident="indy") + AttachDecorator.data_base64(ANONCREDS_PROOF_NAMES, ident="anoncreds") ], ) @@ -1806,42 +1850,46 @@ async def test_receive_pres_bait_and_switch_attr_names(self): async def test_receive_pres_bait_and_switch_pred(self): connection_record = mock.MagicMock(connection_id=CONN_ID) - indy_proof_req = deepcopy(INDY_PROOF_REQ_NAME) + indy_proof_req = deepcopy(ANONCREDS_PROOF_REQ_NAME) indy_proof_req["requested_predicates"] = {} pres_proposal = V20PresProposal( formats=[ V20PresFormat( - attach_id="indy", + attach_id="anoncreds", format_=ATTACHMENT_FORMAT[PRES_20_PROPOSAL][ - V20PresFormat.Format.INDY.api + V20PresFormat.Format.ANONCREDS.api ], ) ], proposals_attach=[ - AttachDecorator.data_base64(INDY_PROOF_REQ_NAME, ident="indy") + AttachDecorator.data_base64(ANONCREDS_PROOF_REQ_NAME, ident="anoncreds") ], ) pres_request = V20PresRequest( formats=[ V20PresFormat( - attach_id="indy", + attach_id="anoncreds", format_=ATTACHMENT_FORMAT[PRES_20_REQUEST][ - V20PresFormat.Format.INDY.api + V20PresFormat.Format.ANONCREDS.api ], ) ], request_presentations_attach=[ - AttachDecorator.data_base64(indy_proof_req, ident="indy") + AttachDecorator.data_base64(indy_proof_req, ident="anoncreds") ], ) pres_x = V20Pres( formats=[ V20PresFormat( - attach_id="indy", - format_=ATTACHMENT_FORMAT[PRES_20][V20PresFormat.Format.INDY.api], + attach_id="anoncreds", + format_=ATTACHMENT_FORMAT[PRES_20][ + V20PresFormat.Format.ANONCREDS.api + ], ) ], - presentations_attach=[AttachDecorator.data_base64(INDY_PROOF, ident="indy")], + presentations_attach=[ + AttachDecorator.data_base64(ANONCREDS_PROOF, ident="anoncreds") + ], ) px_rec_dummy = V20PresExRecord( @@ -1867,33 +1915,41 @@ async def test_receive_pres_bait_and_switch_pred(self): pres_proposal = V20PresProposal( formats=[ V20PresFormat( - attach_id="indy", + attach_id="anoncreds", format_=ATTACHMENT_FORMAT[PRES_20_PROPOSAL][ - V20PresFormat.Format.INDY.api + V20PresFormat.Format.ANONCREDS.api ], ) ], - proposals_attach=[AttachDecorator.data_base64(indy_proof_req, ident="indy")], + proposals_attach=[ + AttachDecorator.data_base64(indy_proof_req, ident="anoncreds") + ], ) pres_request = V20PresRequest( formats=[ V20PresFormat( - attach_id="indy", - format_=ATTACHMENT_FORMAT[PRES_20][V20PresFormat.Format.INDY.api], + attach_id="anoncreds", + format_=ATTACHMENT_FORMAT[PRES_20][ + V20PresFormat.Format.ANONCREDS.api + ], ) ], request_presentations_attach=[ - AttachDecorator.data_base64(indy_proof_req, ident="indy") + AttachDecorator.data_base64(indy_proof_req, ident="anoncreds") ], ) pres_x = V20Pres( formats=[ V20PresFormat( - attach_id="indy", - format_=ATTACHMENT_FORMAT[PRES_20][V20PresFormat.Format.INDY.api], + attach_id="anoncreds", + format_=ATTACHMENT_FORMAT[PRES_20][ + V20PresFormat.Format.ANONCREDS.api + ], ) ], - presentations_attach=[AttachDecorator.data_base64(INDY_PROOF, ident="indy")], + presentations_attach=[ + AttachDecorator.data_base64(ANONCREDS_PROOF, ident="anoncreds") + ], ) px_rec_dummy = V20PresExRecord( @@ -1919,33 +1975,41 @@ async def test_receive_pres_bait_and_switch_pred(self): pres_proposal = V20PresProposal( formats=[ V20PresFormat( - attach_id="indy", + attach_id="anoncreds", format_=ATTACHMENT_FORMAT[PRES_20_PROPOSAL][ - V20PresFormat.Format.INDY.api + V20PresFormat.Format.ANONCREDS.api ], ) ], - proposals_attach=[AttachDecorator.data_base64(indy_proof_req, ident="indy")], + proposals_attach=[ + AttachDecorator.data_base64(indy_proof_req, ident="anoncreds") + ], ) pres_request = V20PresRequest( formats=[ V20PresFormat( - attach_id="indy", - format_=ATTACHMENT_FORMAT[PRES_20][V20PresFormat.Format.INDY.api], + attach_id="anoncreds", + format_=ATTACHMENT_FORMAT[PRES_20][ + V20PresFormat.Format.ANONCREDS.api + ], ) ], request_presentations_attach=[ - AttachDecorator.data_base64(indy_proof_req, ident="indy") + AttachDecorator.data_base64(indy_proof_req, ident="anoncreds") ], ) pres_x = V20Pres( formats=[ V20PresFormat( - attach_id="indy", - format_=ATTACHMENT_FORMAT[PRES_20][V20PresFormat.Format.INDY.api], + attach_id="anoncreds", + format_=ATTACHMENT_FORMAT[PRES_20][ + V20PresFormat.Format.ANONCREDS.api + ], ) ], - presentations_attach=[AttachDecorator.data_base64(INDY_PROOF, ident="indy")], + presentations_attach=[ + AttachDecorator.data_base64(ANONCREDS_PROOF, ident="anoncreds") + ], ) px_rec_dummy = V20PresExRecord( @@ -1971,33 +2035,41 @@ async def test_receive_pres_bait_and_switch_pred(self): pres_proposal = V20PresProposal( formats=[ V20PresFormat( - attach_id="indy", + attach_id="anoncreds", format_=ATTACHMENT_FORMAT[PRES_20_PROPOSAL][ - V20PresFormat.Format.INDY.api + V20PresFormat.Format.ANONCREDS.api ], ) ], - proposals_attach=[AttachDecorator.data_base64(indy_proof_req, ident="indy")], + proposals_attach=[ + AttachDecorator.data_base64(indy_proof_req, ident="anoncreds") + ], ) pres_request = V20PresRequest( formats=[ V20PresFormat( - attach_id="indy", - format_=ATTACHMENT_FORMAT[PRES_20][V20PresFormat.Format.INDY.api], + attach_id="anoncreds", + format_=ATTACHMENT_FORMAT[PRES_20][ + V20PresFormat.Format.ANONCREDS.api + ], ) ], request_presentations_attach=[ - AttachDecorator.data_base64(indy_proof_req, ident="indy") + AttachDecorator.data_base64(indy_proof_req, ident="anoncreds") ], ) pres_x = V20Pres( formats=[ V20PresFormat( - attach_id="indy", - format_=ATTACHMENT_FORMAT[PRES_20][V20PresFormat.Format.INDY.api], + attach_id="anoncreds", + format_=ATTACHMENT_FORMAT[PRES_20][ + V20PresFormat.Format.ANONCREDS.api + ], ) ], - presentations_attach=[AttachDecorator.data_base64(INDY_PROOF, ident="indy")], + presentations_attach=[ + AttachDecorator.data_base64(ANONCREDS_PROOF, ident="anoncreds") + ], ) px_rec_dummy = V20PresExRecord( @@ -2020,25 +2092,29 @@ async def test_verify_pres(self): pres_request = V20PresRequest( formats=[ V20PresFormat( - attach_id="indy", + attach_id="anoncreds", format_=ATTACHMENT_FORMAT[PRES_20_REQUEST][ - V20PresFormat.Format.INDY.api + V20PresFormat.Format.ANONCREDS.api ], ) ], will_confirm=True, request_presentations_attach=[ - AttachDecorator.data_base64(INDY_PROOF_REQ_NAME, ident="indy") + AttachDecorator.data_base64(ANONCREDS_PROOF_REQ_NAME, ident="anoncreds") ], ) pres = V20Pres( formats=[ V20PresFormat( - attach_id="indy", - format_=ATTACHMENT_FORMAT[PRES_20][V20PresFormat.Format.INDY.api], + attach_id="anoncreds", + format_=ATTACHMENT_FORMAT[PRES_20][ + V20PresFormat.Format.ANONCREDS.api + ], ) ], - presentations_attach=[AttachDecorator.data_base64(INDY_PROOF, ident="indy")], + presentations_attach=[ + AttachDecorator.data_base64(ANONCREDS_PROOF, ident="anoncreds") + ], ) px_rec_in = V20PresExRecord( pres_request=pres_request, @@ -2063,9 +2139,9 @@ async def test_verify_pres_indy_and_dif(self): pres_request = V20PresRequest( formats=[ V20PresFormat( - attach_id="indy", + attach_id="anoncreds", format_=ATTACHMENT_FORMAT[PRES_20_REQUEST][ - V20PresFormat.Format.INDY.api + V20PresFormat.Format.ANONCREDS.api ], ), V20PresFormat( @@ -2077,15 +2153,17 @@ async def test_verify_pres_indy_and_dif(self): ], will_confirm=True, request_presentations_attach=[ - AttachDecorator.data_base64(INDY_PROOF_REQ_NAME, ident="indy"), + AttachDecorator.data_base64(ANONCREDS_PROOF_REQ_NAME, ident="anoncreds"), AttachDecorator.data_json(DIF_PRES_REQ, ident="dif"), ], ) pres = V20Pres( formats=[ V20PresFormat( - attach_id="indy", - format_=ATTACHMENT_FORMAT[PRES_20][V20PresFormat.Format.INDY.api], + attach_id="anoncreds", + format_=ATTACHMENT_FORMAT[PRES_20][ + V20PresFormat.Format.ANONCREDS.api + ], ), V20PresFormat( attach_id="dif", @@ -2093,7 +2171,7 @@ async def test_verify_pres_indy_and_dif(self): ), ], presentations_attach=[ - AttachDecorator.data_base64(INDY_PROOF, ident="indy"), + AttachDecorator.data_base64(ANONCREDS_PROOF, ident="anoncreds"), AttachDecorator.data_json(DIF_PRES, ident="dif"), ], ) diff --git a/acapy_agent/protocols/present_proof/v2_0/tests/test_routes.py b/acapy_agent/protocols/present_proof/v2_0/tests/test_routes.py index af70cd2d0b..b1fb15ef7f 100644 --- a/acapy_agent/protocols/present_proof/v2_0/tests/test_routes.py +++ b/acapy_agent/protocols/present_proof/v2_0/tests/test_routes.py @@ -6,8 +6,10 @@ from marshmallow import ValidationError from .....admin.request_context import AdminRequestContext +from .....anoncreds.models.presentation_request import ( + AnoncredsPresentationReqAttrSpecSchema, +) from .....indy.holder import IndyHolder -from .....indy.models.proof_request import IndyProofReqAttrSpecSchema from .....indy.verifier import IndyVerifier from .....ledger.base import BaseLedger from .....storage.error import StorageNotFoundError @@ -221,7 +223,7 @@ async def test_validate(self): schema.validate_fields({"veres-one": {"no": "support"}}) async def test_validate_proof_req_attr_spec(self): - aspec = IndyProofReqAttrSpecSchema() + aspec = AnoncredsPresentationReqAttrSpecSchema() aspec.validate_fields({"name": "attr0"}) aspec.validate_fields( { diff --git a/acapy_agent/protocols/present_proof/v2_0/tests/test_routes_anoncreds.py b/acapy_agent/protocols/present_proof/v2_0/tests/test_routes_anoncreds.py index 96c3da744e..faa4f0f278 100644 --- a/acapy_agent/protocols/present_proof/v2_0/tests/test_routes_anoncreds.py +++ b/acapy_agent/protocols/present_proof/v2_0/tests/test_routes_anoncreds.py @@ -8,8 +8,10 @@ from .....admin.request_context import AdminRequestContext from .....anoncreds.holder import AnonCredsHolder +from .....anoncreds.models.presentation_request import ( + AnoncredsPresentationReqAttrSpecSchema, +) from .....anoncreds.verifier import AnonCredsVerifier -from .....indy.models.proof_request import IndyProofReqAttrSpecSchema from .....ledger.base import BaseLedger from .....storage.error import StorageNotFoundError from .....storage.vc_holder.base import VCHolder @@ -223,7 +225,7 @@ async def test_validate(self): schema.validate_fields({"veres-one": {"no": "support"}}) async def test_validate_proof_req_attr_spec(self): - aspec = IndyProofReqAttrSpecSchema() + aspec = AnoncredsPresentationReqAttrSpecSchema() aspec.validate_fields({"name": "attr0"}) aspec.validate_fields( { diff --git a/acapy_agent/resolver/default/tests/test_universal.py b/acapy_agent/resolver/default/tests/test_universal.py index 36b2cc73db..c516ac7068 100644 --- a/acapy_agent/resolver/default/tests/test_universal.py +++ b/acapy_agent/resolver/default/tests/test_universal.py @@ -119,6 +119,7 @@ async def test_fetch_resolver_props(mock_client_session: MockClientSession): @pytest.mark.asyncio async def test_get_supported_did_regex(): + # Old response format props = {"example": {"http": {"pattern": "match a test string"}}} with mock.patch.object( UniversalResolver, @@ -128,6 +129,29 @@ async def test_get_supported_did_regex(): pattern = await UniversalResolver()._get_supported_did_regex() assert pattern.fullmatch("match a test string") + # Example response from dev universal resolver 1.0 + props = { + "^(did:sov:(?:(?:\\w[-\\w]*(?::\\w[-\\w]*)*):)?(?:[1-9A-HJ-NP-Za-km-z]{21,22}))$": { + "libIndyPath": "", + "openParallel": "false", + "poolVersions": "_;2;test;2;builder;2;danube;2;idunion;2;idunion:test;2;indicio;2;indicio:test;2;indicio:demo;2;nxd;2;findy:test;2;bcovrin;2;bcovrin:test;2;bcovrin:dev;2;candy;2;candy:test;2;candy:dev;2", + "submitterDidSeeds": "_;_;test;_;builder;_;danube;_;idunion;_;idunion:test;_;indicio;_;indicio:test;_;indicio:demo;_;nxd;_;findy:test;_;bcovrin;_;bcovrin:test;_;bcovrin:dev;_;candy;_;candy:test;_;candy:dev;_", + "http": { + "resolveUri": "http://driver-did-sov:8080/1.0/identifiers/", + "propertiesUri": "http://driver-did-sov:8080/1.0/properties", + }, + "walletNames": "_;w1;test;w2;builder;w3;danube;w4;idunion;w5;idunion:test;w6;indicio;w7;indicio:test;w8;indicio:demo;w9;nxd;w11;findy:test;w12;bcovrin;w13;bcovrin:test;w14;bcovrin:dev;w15;candy;w16;candy:test;w17;candy:dev;w18", + "poolConfigs": "_;./sovrin/_.txn;test;./sovrin/test.txn;builder;./sovrin/builder.txn;danube;./sovrin/danube.txn;idunion;./sovrin/idunion.txn;idunion:test;./sovrin/idunion-test.txn;indicio;./sovrin/indicio.txn;indicio:test;./sovrin/indicio-test.txn;indicio:demo;./sovrin/indicio-demo.txn;nxd;./sovrin/nxd.txn;bcovrin:test;./sovrin/bcovrin-test.txn;candy;./sovrin/candy.txn;candy:test;./sovrin/candy-test.txn;candy:dev;./sovrin/candy-dev.txn", + } + } + with mock.patch.object( + UniversalResolver, + "_fetch_resolver_props", + mock.CoroutineMock(return_value=props), + ): + pattern = await UniversalResolver()._get_supported_did_regex() + assert pattern.match("did:sov:WRfXPg8dantKVubE3HX8pw") + def test_compile_supported_did_regex(): patterns = ["one", "two", "three"] diff --git a/acapy_agent/resolver/default/universal.py b/acapy_agent/resolver/default/universal.py index 4d359a9a6e..8d4b1eb1d8 100644 --- a/acapy_agent/resolver/default/universal.py +++ b/acapy_agent/resolver/default/universal.py @@ -110,8 +110,16 @@ async def _fetch_resolver_props(self) -> dict: "Failed to retrieve resolver properties: " + await resp.text() ) - async def _get_supported_did_regex(self) -> Pattern: + async def _get_supported_did_regex(self): props = await self._fetch_resolver_props() - return _compile_supported_did_regex( - driver["http"]["pattern"] for driver in props.values() - ) + + def _get_patterns(): + """Handle both old and new properties responses.""" + patterns = list(props.values())[0].get("http", {}).get("pattern") + if not patterns: + return props.keys() + else: + return [driver["http"]["pattern"] for driver in props.values()] + + patterns = _get_patterns() + return _compile_supported_did_regex(patterns) diff --git a/acapy_agent/revocation_anoncreds/routes.py b/acapy_agent/revocation_anoncreds/routes.py index 928ef0ebd8..4c5b3bd8b0 100644 --- a/acapy_agent/revocation_anoncreds/routes.py +++ b/acapy_agent/revocation_anoncreds/routes.py @@ -24,7 +24,7 @@ ) from ..anoncreds.default.legacy_indy.registry import LegacyIndyRegistry from ..anoncreds.issuer import AnonCredsIssuerError -from ..anoncreds.models.anoncreds_revocation import RevRegDefState +from ..anoncreds.models.revocation import RevRegDefState from ..anoncreds.revocation import AnonCredsRevocation, AnonCredsRevocationError from ..anoncreds.routes import ( create_transaction_for_endorser_description, diff --git a/acapy_agent/revocation_anoncreds/tests/test_routes.py b/acapy_agent/revocation_anoncreds/tests/test_routes.py index 11d4ea25e3..4fef93761e 100644 --- a/acapy_agent/revocation_anoncreds/tests/test_routes.py +++ b/acapy_agent/revocation_anoncreds/tests/test_routes.py @@ -6,7 +6,7 @@ from aiohttp.web import HTTPNotFound from ...admin.request_context import AdminRequestContext -from ...anoncreds.models.anoncreds_revocation import RevRegDef, RevRegDefValue +from ...anoncreds.models.revocation import RevRegDef, RevRegDefValue from ...tests import mock from ...utils.testing import create_test_profile from .. import routes as test_module diff --git a/acapy_agent/utils/classloader.py b/acapy_agent/utils/classloader.py index e257abdb3a..31f0b1a39a 100644 --- a/acapy_agent/utils/classloader.py +++ b/acapy_agent/utils/classloader.py @@ -3,6 +3,7 @@ import inspect import logging import sys +from functools import lru_cache from importlib import import_module, resources from importlib.util import find_spec, resolve_name from types import ModuleType @@ -12,7 +13,6 @@ from ..core.error import BaseError LOGGER = logging.getLogger(__name__) -add_trace_level() # Allow trace logs from this module class ModuleLoadError(BaseError): @@ -27,6 +27,7 @@ class ClassLoader: """Class used to load classes from modules dynamically.""" @classmethod + @lru_cache def load_module( cls, mod_path: str, package: Optional[str] = None ) -> Optional[ModuleType]: @@ -43,49 +44,53 @@ def load_module( ModuleLoadError: If there was an error loading the module """ - LOGGER.trace("Attempting to load module: %s with package: %s", mod_path, package) + LOGGER.debug( + "Attempting to load module: %s%s", + mod_path, + f" with package: {package}" if package else "", + ) if package: - LOGGER.trace("Preloading parent package: %s", package) + LOGGER.debug("Preloading parent package: %s", package) # preload parent package if not cls.load_module(package): - LOGGER.trace("Failed to preload parent package: %s", package) + LOGGER.debug("Failed to preload parent package: %s", package) return None # must treat as a relative import if not mod_path.startswith("."): mod_path = f".{mod_path}" - LOGGER.trace("Adjusted mod_path for relative import: %s", mod_path) + LOGGER.debug("Adjusted mod_path for relative import: %s", mod_path) full_path = resolve_name(mod_path, package) - LOGGER.trace("Resolved full module path: %s", full_path) + LOGGER.debug("Resolved full module path: %s", full_path) if full_path in sys.modules: - LOGGER.trace("Module %s is already loaded", full_path) + LOGGER.debug("Module %s is already loaded", full_path) return sys.modules[full_path] if "." in mod_path: parent_mod_path, mod_name = mod_path.rsplit(".", 1) - LOGGER.trace( + LOGGER.debug( "Parent module path: %s, Module name: %s", parent_mod_path, mod_name ) if parent_mod_path and parent_mod_path[-1] != ".": parent_mod = cls.load_module(parent_mod_path, package) if not parent_mod: - LOGGER.trace("Failed to load parent module: %s", parent_mod_path) + LOGGER.debug("Failed to load parent module: %s", parent_mod_path) return None package = parent_mod.__name__ mod_path = f".{mod_name}" - LOGGER.trace("Adjusted mod_path after loading parent: %s", mod_path) + LOGGER.debug("Adjusted mod_path after loading parent: %s", mod_path) # Load the module spec first - LOGGER.trace("Finding spec for module: %s with package: %s", mod_path, package) + LOGGER.debug("Finding spec for module: %s with package: %s", mod_path, package) spec = find_spec(mod_path, package) if not spec: - LOGGER.trace("Module spec not found for: %s", mod_path) + LOGGER.debug("Module spec not found for: %s", mod_path) return None try: - LOGGER.trace("Importing module: %s with package: %s", mod_path, package) + LOGGER.debug("Importing module: %s with package: %s", mod_path, package) return import_module(mod_path, package) except ModuleNotFoundError as e: LOGGER.warning("Module %s not found during import", full_path) @@ -114,24 +119,24 @@ def load_class( """ - LOGGER.trace( - "Attempting to load class: %s with default_module: %s and package: %s", + LOGGER.debug( + "Attempting to load class: %s%s%s", class_name, - default_module, - package, + f", with default_module: {default_module}" if default_module else "", + f", with package: {package}" if package else "", ) if "." in class_name: # import module and find class mod_path, class_name = class_name.rsplit(".", 1) - LOGGER.trace( + LOGGER.debug( "Extracted module path: %s, class name: %s from full class path", mod_path, class_name, ) elif default_module: mod_path = default_module - LOGGER.trace("No module in class name, using default_module: %s", mod_path) + LOGGER.debug("No module in class name, using default_module: %s", mod_path) else: LOGGER.warning( "Cannot resolve class name %s with no default module", class_name @@ -160,7 +165,7 @@ def load_class( raise ClassNotFoundError( f"Resolved value is not a class: {mod_path}.{class_name}" ) - LOGGER.trace("Successfully loaded class %s from module %s", class_name, mod_path) + LOGGER.debug("Successfully loaded class %s from module %s", class_name, mod_path) return resolved @classmethod @@ -183,7 +188,7 @@ def load_subclass_of( """ - LOGGER.trace( + LOGGER.debug( "Attempting to load subclass of %s from module %s with package %s", base_class.__name__, mod_path, @@ -201,7 +206,7 @@ def load_subclass_of( # Find the first declared class that inherits from the base_class try: - LOGGER.trace( + LOGGER.debug( "Inspecting classes in module %s for subclasses of %s", mod_path, base_class.__name__, @@ -211,13 +216,13 @@ def load_subclass_of( for name, obj in inspect.getmembers(mod, inspect.isclass) if issubclass(obj, base_class) and obj is not base_class ) - LOGGER.trace( + LOGGER.debug( "Found subclass %s of base class %s", imported_class.__name__, base_class.__name__, ) except StopIteration: - LOGGER.trace( + LOGGER.debug( "No subclass of %s found in module %s", base_class.__name__, mod_path, @@ -233,14 +238,14 @@ def scan_subpackages(cls, package: str) -> Sequence[str]: LOGGER.debug("Scanning subpackages under package %s", package) if "." in package: package, sub_pkg = package.split(".", 1) - LOGGER.trace("Extracted main package: %s, sub-package: %s", package, sub_pkg) + LOGGER.debug("Extracted main package: %s, sub-package: %s", package, sub_pkg) else: sub_pkg = "." - LOGGER.trace("No sub-package provided, defaulting to %s", sub_pkg) + LOGGER.debug("No sub-package provided, defaulting to %s", sub_pkg) try: package_path = resources.files(package) - LOGGER.trace("Found package path: %s", package_path) + LOGGER.debug("Found package path: %s", package_path) except FileNotFoundError: LOGGER.warning("Package %s not found during subpackage scan", package) raise ModuleLoadError(f"Undefined package {package}") @@ -257,7 +262,6 @@ def scan_subpackages(cls, package: str) -> Sequence[str]: if (item / "__init__.py").exists(): subpackage = f"{package}.{joiner}{item.name}" found.append(subpackage) - LOGGER.trace("Found sub-package: %s", subpackage) LOGGER.debug("Total sub-packages found under %s: %s", package, found) return found diff --git a/acapy_agent/vc/vc_ld/models/linked_data_proof.py b/acapy_agent/vc/vc_ld/models/linked_data_proof.py index c441ff5911..57853000c6 100644 --- a/acapy_agent/vc/vc_ld/models/linked_data_proof.py +++ b/acapy_agent/vc/vc_ld/models/linked_data_proof.py @@ -6,8 +6,8 @@ from ....messaging.models.base import BaseModel, BaseModelSchema from ....messaging.valid import ( - INDY_ISO8601_DATETIME_EXAMPLE, - INDY_ISO8601_DATETIME_VALIDATE, + ISO8601_DATETIME_EXAMPLE, + ISO8601_DATETIME_VALIDATE, UUID4_EXAMPLE, Uri, ) @@ -93,13 +93,13 @@ class Meta: created = fields.Str( required=False, - validate=INDY_ISO8601_DATETIME_VALIDATE, + validate=ISO8601_DATETIME_VALIDATE, metadata={ "description": ( "The string value of an ISO8601 combined date and time string generated" " by the Signature Algorithm" ), - "example": INDY_ISO8601_DATETIME_EXAMPLE, + "example": ISO8601_DATETIME_EXAMPLE, }, ) diff --git a/acapy_agent/vc/vc_ld/models/options.py b/acapy_agent/vc/vc_ld/models/options.py index 2d7adbe4e2..a939df56ac 100644 --- a/acapy_agent/vc/vc_ld/models/options.py +++ b/acapy_agent/vc/vc_ld/models/options.py @@ -5,8 +5,8 @@ from marshmallow import INCLUDE, Schema, fields from acapy_agent.messaging.valid import ( - INDY_ISO8601_DATETIME_EXAMPLE, - INDY_ISO8601_DATETIME_VALIDATE, + ISO8601_DATETIME_EXAMPLE, + ISO8601_DATETIME_VALIDATE, UUID4_EXAMPLE, ) @@ -124,13 +124,13 @@ class Meta: created = fields.Str( required=False, - validate=INDY_ISO8601_DATETIME_VALIDATE, + validate=ISO8601_DATETIME_VALIDATE, metadata={ "description": ( "The date and time of the proof (with a maximum accuracy in seconds)." " Defaults to current system time" ), - "example": INDY_ISO8601_DATETIME_EXAMPLE, + "example": ISO8601_DATETIME_EXAMPLE, }, ) diff --git a/acapy_agent/wallet/anoncreds_upgrade.py b/acapy_agent/wallet/anoncreds_upgrade.py index 12f2cb2dd3..4261acf0eb 100644 --- a/acapy_agent/wallet/anoncreds_upgrade.py +++ b/acapy_agent/wallet/anoncreds_upgrade.py @@ -21,15 +21,15 @@ CATEGORY_CRED_DEF_PRIVATE, CATEGORY_SCHEMA, ) -from ..anoncreds.models.anoncreds_cred_def import CredDef, CredDefState -from ..anoncreds.models.anoncreds_revocation import ( +from ..anoncreds.models.credential_definition import CredDef, CredDefState +from ..anoncreds.models.revocation import ( RevList, RevListState, RevRegDef, RevRegDefState, RevRegDefValue, ) -from ..anoncreds.models.anoncreds_schema import SchemaState +from ..anoncreds.models.schema import SchemaState from ..anoncreds.revocation import ( CATEGORY_REV_LIST, CATEGORY_REV_REG_DEF, @@ -699,6 +699,7 @@ async def check_upgrade_completion_loop(profile: Profile, is_subwallet=False): UpgradeInProgressSingleton().remove_wallet(profile.name) if is_subwallet: await upgrade_subwallet(profile) + await finish_upgrade(profile) LOGGER.info( f"""Upgrade of subwallet {profile.settings.get('wallet.name')} has completed. Profile is now askar-anoncreds""" # noqa: E501 ) diff --git a/acapy_agent/wallet/routes.py b/acapy_agent/wallet/routes.py index 00bc48cfb1..cf539d61ac 100644 --- a/acapy_agent/wallet/routes.py +++ b/acapy_agent/wallet/routes.py @@ -9,11 +9,10 @@ from aiohttp_apispec import docs, querystring_schema, request_schema, response_schema from marshmallow import fields, validate -from acapy_agent.connections.base_manager import BaseConnectionManager - from ..admin.decorators.auth import tenant_authentication from ..admin.request_context import AdminRequestContext from ..config.injection_context import InjectionContext +from ..connections.base_manager import BaseConnectionManager from ..connections.models.conn_record import ConnRecord from ..core.event_bus import Event, EventBus from ..core.profile import Profile @@ -35,12 +34,12 @@ GENERIC_DID_VALIDATE, INDY_DID_EXAMPLE, INDY_DID_VALIDATE, - INDY_RAW_PUBLIC_KEY_EXAMPLE, - INDY_RAW_PUBLIC_KEY_VALIDATE, JWT_EXAMPLE, JWT_VALIDATE, NON_SD_LIST_EXAMPLE, NON_SD_LIST_VALIDATE, + RAW_ED25519_2018_PUBLIC_KEY_EXAMPLE, + RAW_ED25519_2018_PUBLIC_KEY_VALIDATE, SD_JWT_EXAMPLE, SD_JWT_VALIDATE, UUID4_EXAMPLE, @@ -95,10 +94,10 @@ class DIDSchema(OpenAPISchema): ) verkey = fields.Str( required=True, - validate=INDY_RAW_PUBLIC_KEY_VALIDATE, + validate=RAW_ED25519_2018_PUBLIC_KEY_VALIDATE, metadata={ "description": "Public verification key", - "example": INDY_RAW_PUBLIC_KEY_EXAMPLE, + "example": RAW_ED25519_2018_PUBLIC_KEY_EXAMPLE, }, ) posture = fields.Str( @@ -293,10 +292,10 @@ class DIDListQueryStringSchema(OpenAPISchema): ) verkey = fields.Str( required=False, - validate=INDY_RAW_PUBLIC_KEY_VALIDATE, + validate=RAW_ED25519_2018_PUBLIC_KEY_VALIDATE, metadata={ "description": "Verification key of interest", - "example": INDY_RAW_PUBLIC_KEY_EXAMPLE, + "example": RAW_ED25519_2018_PUBLIC_KEY_EXAMPLE, }, ) posture = fields.Str( diff --git a/demo/bdd_support/agent_backchannel_client.py b/demo/bdd_support/agent_backchannel_client.py index 5d116e2fea..fa6db011c7 100644 --- a/demo/bdd_support/agent_backchannel_client.py +++ b/demo/bdd_support/agent_backchannel_client.py @@ -143,11 +143,13 @@ def aries_container_issue_credential( the_container: AgentContainer, cred_def_id: str, cred_attrs: list, + filter_type: str = "indy", ): return run_coroutine( the_container.issue_credential, cred_def_id, cred_attrs, + filter_type=filter_type, ) @@ -167,11 +169,13 @@ def aries_container_request_proof( the_container: AgentContainer, proof_request: dict, explicit_revoc_required: bool = False, + is_anoncreds: bool = False, ): return run_coroutine( the_container.request_proof, proof_request, explicit_revoc_required=explicit_revoc_required, + is_anoncreds=is_anoncreds, ) diff --git a/demo/features/0453-issue-credential.feature b/demo/features/0453-issue-credential.feature index 3927bbf4d9..4f9b47e46d 100644 --- a/demo/features/0453-issue-credential.feature +++ b/demo/features/0453-issue-credential.feature @@ -30,7 +30,7 @@ Feature: RFC 0453 Aries agent issue credential | --public-did --wallet-type askar-anoncreds | --wallet-type askar-anoncreds | driverslicense | Data_DL_NormalizedValues | | | | --public-did --wallet-type askar-anoncreds --cred-type vc_di | --wallet-type askar-anoncreds | driverslicense | Data_DL_NormalizedValues | | | - @Release @WalletType_Askar_AnonCreds @AltTests + @PR @Release @WalletType_Askar_AnonCreds Examples: | Acme_capabilities | Bob_capabilities | Schema_name | Credential_data | Acme_extra | Bob_extra | | --public-did --wallet-type askar-anoncreds | | driverslicense | Data_DL_NormalizedValues | | | diff --git a/demo/features/steps/0453-issue-credential.py b/demo/features/steps/0453-issue-credential.py index 97106d3e1d..d96fdae10a 100644 --- a/demo/features/steps/0453-issue-credential.py +++ b/demo/features/steps/0453-issue-credential.py @@ -62,10 +62,9 @@ def step_impl(context, issuer, credential_data): agent = context.active_agents[issuer] cred_attrs = read_credential_data(context.schema_name, credential_data) + filter_type = "indy" if not is_anoncreds(agent) else "anoncreds" cred_exchange = aries_container_issue_credential( - agent["agent"], - context.cred_def_id, - cred_attrs, + agent["agent"], context.cred_def_id, cred_attrs, filter_type ) context.cred_attrs = cred_attrs diff --git a/demo/features/steps/0454-present-proof.py b/demo/features/steps/0454-present-proof.py index 984ee72e63..31e91930f5 100644 --- a/demo/features/steps/0454-present-proof.py +++ b/demo/features/steps/0454-present-proof.py @@ -34,7 +34,11 @@ def step_impl(context, verifier, request_for_proof, prover): "restrictions" ] = cred_def_restrictions - proof_exchange = aries_container_request_proof(agent["agent"], proof_request_info) + proof_exchange = aries_container_request_proof( + agent["agent"], + proof_request_info, + agent["agent"].wallet_type == "askar-anoncreds", + ) context.proof_request = proof_request_info context.proof_exchange = proof_exchange @@ -49,7 +53,10 @@ def step_impl(context, verifier, request_for_proof, prover): proof_request_info = read_proof_req_data(request_for_proof) proof_exchange = aries_container_request_proof( - agent["agent"], proof_request_info, explicit_revoc_required=True + agent["agent"], + proof_request_info, + explicit_revoc_required=True, + is_anoncreds=agent["agent"].wallet_type == "askar-anoncreds", ) context.proof_request = proof_request_info diff --git a/demo/features/steps/0586-sign-transaction.py b/demo/features/steps/0586-sign-transaction.py index bf5a8a0c3c..f8ade72f98 100644 --- a/demo/features/steps/0586-sign-transaction.py +++ b/demo/features/steps/0586-sign-transaction.py @@ -593,14 +593,15 @@ def step_impl(context, agent_name): agent["agent"], "/issue-credential-2.0/records/" + cred_exchange["cred_ex_id"] ) context.cred_exchange = cred_exchange + cred_exchange_format = cred_exchange.get("indy") or cred_exchange.get("anoncreds") agent_container_POST( agent["agent"], endpoint, data={ - "cred_rev_id": cred_exchange["indy"]["cred_rev_id"], + "cred_rev_id": cred_exchange_format["cred_rev_id"], "publish": False, - "rev_reg_id": cred_exchange["indy"]["rev_reg_id"], + "rev_reg_id": cred_exchange_format["rev_reg_id"], "connection_id": cred_exchange["cred_ex_record"]["connection_id"], }, ) @@ -627,11 +628,13 @@ def step_impl(context, agent_name): context.cred_exchange = cred_exchange connection_id = agent["agent"].agent.connection_id + cred_exchange_format = cred_exchange.get("indy") or cred_exchange.get("anoncreds") + # revoke the credential if not is_anoncreds(agent): data = { - "rev_reg_id": cred_exchange["indy"]["rev_reg_id"], - "cred_rev_id": cred_exchange["indy"]["cred_rev_id"], + "rev_reg_id": cred_exchange_format["rev_reg_id"], + "cred_rev_id": cred_exchange_format["cred_rev_id"], "publish": False, "connection_id": cred_exchange["cred_ex_record"]["connection_id"], } @@ -642,9 +645,9 @@ def step_impl(context, agent_name): endpoint = "/revocation/revoke" else: data = { - "cred_rev_id": cred_exchange["indy"]["cred_rev_id"], + "cred_rev_id": cred_exchange_format["cred_rev_id"], "publish": False, - "rev_reg_id": cred_exchange["indy"]["rev_reg_id"], + "rev_reg_id": cred_exchange_format["rev_reg_id"], "connection_id": cred_exchange["cred_ex_record"]["connection_id"], "options": { "endorser_connection_id": connection_id, @@ -676,15 +679,17 @@ def step_impl(context, agent_name): else: endpoint = "/anoncreds/revocation/publish-revocations" + cred_exchange_format = context.cred_exchange.get("indy") or context.cred_exchange.get( + "anoncreds" + ) + # create rev_reg entry transaction created_rev_reg = agent_container_POST( agent["agent"], endpoint, data={ "rrid2crid": { - context.cred_exchange["indy"]["rev_reg_id"]: [ - context.cred_exchange["indy"]["cred_rev_id"] - ] + cred_exchange_format["rev_reg_id"]: [cred_exchange_format["cred_rev_id"]] } }, params={}, @@ -703,14 +708,15 @@ def step_impl(context, agent_name): agent = context.active_agents[agent_name] connection_id = agent["agent"].agent.connection_id + cred_exchange_format = context.cred_exchange.get("indy") or context.cred_exchange.get( + "anoncreds" + ) # create rev_reg entry transaction if not is_anoncreds(agent): data = { "rrid2crid": { - context.cred_exchange["indy"]["rev_reg_id"]: [ - context.cred_exchange["indy"]["cred_rev_id"] - ] + cred_exchange_format["rev_reg_id"]: [cred_exchange_format["cred_rev_id"]] } } params = { @@ -721,9 +727,7 @@ def step_impl(context, agent_name): else: data = { "rrid2crid": { - context.cred_exchange["indy"]["rev_reg_id"]: [ - context.cred_exchange["indy"]["cred_rev_id"] - ] + cred_exchange_format["rev_reg_id"]: [cred_exchange_format["cred_rev_id"]] }, "options": { "endorser_connection_id": connection_id, diff --git a/demo/features/upgrade.feature b/demo/features/upgrade.feature index 259c7a485a..557dd75646 100644 --- a/demo/features/upgrade.feature +++ b/demo/features/upgrade.feature @@ -19,10 +19,7 @@ Feature: ACA-Py Anoncreds Upgrade Then "Faber" has the proof verification fail Then "Bob" can verify the credential from "" was revoked And "" upgrades the wallet to anoncreds - And "Bob" has an issued credential from "" And "Bob" upgrades the wallet to anoncreds - And "Bob" has an issued credential from "" - When "Faber" sends a request for proof presentation to "Bob" Examples: | issuer | Acme_capabilities | Bob_capabilities | Schema_name | Credential_data | Proof_request | diff --git a/demo/runners/agent_container.py b/demo/runners/agent_container.py index c2ad39fe5f..61b07f4ef7 100644 --- a/demo/runners/agent_container.py +++ b/demo/runners/agent_container.py @@ -36,6 +36,8 @@ log_timer, ) +from .support.agent import CRED_FORMAT_ANONCREDS + CRED_PREVIEW_TYPE = "https://didcomm.org/issue-credential/2.0/credential-preview" SELF_ATTESTED = os.getenv("SELF_ATTESTED") TAILS_FILE_COUNT = int(os.getenv("TAILS_FILE_COUNT", 100)) @@ -272,16 +274,25 @@ async def handle_issue_credential_v2_0(self, message): elif state == "offer-received": log_status("#15 After receiving credential offer, send credential request") + + def _should_send_request_without_data(message): + """Formats that do not require credential request data.""" + cred_offer_by_format = message["by_format"].get("cred_offer") + + return ( + not message.get("by_format") + or cred_offer_by_format.get("anoncreds") + or cred_offer_by_format.get("indy") + or cred_offer_by_format.get("vc_di") + ) + # Should wait for a tiny bit for the delete tests await asyncio.sleep(0.2) if not message.get("by_format"): # this should not happen, something hinky when running in IDE... # this will work if using indy payloads self.log(f"No 'by_format' in message: {message}") - await self.admin_POST( - f"/issue-credential-2.0/records/{cred_ex_id}/send-request" - ) - elif message["by_format"]["cred_offer"].get("indy"): + elif _should_send_request_without_data(message): await self.admin_POST( f"/issue-credential-2.0/records/{cred_ex_id}/send-request" ) @@ -294,10 +305,6 @@ async def handle_issue_credential_v2_0(self, message): await self.admin_POST( f"/issue-credential-2.0/records/{cred_ex_id}/send-request", data ) - elif message["by_format"]["cred_offer"].get("vc_di"): - await self.admin_POST( - f"/issue-credential-2.0/records/{cred_ex_id}/send-request" - ) elif state == "done": pass @@ -327,6 +334,26 @@ async def handle_issue_credential_v2_0_indy(self, message): self.log(f"Revocation registry ID: {rev_reg_id}") self.log(f"Credential revocation ID: {cred_rev_id}") + async def handle_issue_credential_v2_0_anoncreds(self, message): + rev_reg_id = message.get("rev_reg_id") + cred_rev_id = message.get("cred_rev_id") + cred_id_stored = message.get("cred_id_stored") + + if cred_id_stored: + cred_id = message["cred_id_stored"] + log_status(f"#18.1 Stored credential {cred_id} in wallet") + cred = await self.admin_GET(f"/credential/{cred_id}") + log_json(cred, label="Credential details:") + self.log("credential_id", cred_id) + self.log("cred_def_id", cred["cred_def_id"]) + self.log("schema_id", cred["schema_id"]) + # track last successfully received credential + self.last_credential_received = cred + + if rev_reg_id and cred_rev_id: + self.log(f"Revocation registry ID: {rev_reg_id}") + self.log(f"Credential revocation ID: {cred_rev_id}") + async def handle_issue_credential_v2_0_vc_di(self, message): self.log(f"Handle VC_DI Credential: message = {message}") @@ -442,16 +469,18 @@ async def handle_present_proof_v2_0(self, message): # this should not happen, something hinky when running in IDE... self.log(f"No 'by_format' in message: {message}") else: - pres_request_indy = ( - message["by_format"].get("pres_request", {}).get("indy") - ) - pres_request_dif = message["by_format"].get("pres_request", {}).get("dif") + pres_request_by_format = message["by_format"].get("pres_request", {}) + pres_request = pres_request_by_format.get( + "indy" + ) or pres_request_by_format.get("anoncreds") + + pres_request_dif = pres_request_by_format.get("dif") request = {} - if not pres_request_dif and not pres_request_indy: + if not pres_request_dif and not pres_request: raise Exception("Invalid presentation request received") - if pres_request_indy: + if pres_request: # include self-attested attributes (not included in credentials) creds_by_reft = {} revealed = {} @@ -463,7 +492,6 @@ async def handle_present_proof_v2_0(self, message): creds = await self.admin_GET( f"/present-proof-2.0/records/{pres_ex_id}/credentials" ) - # print(">>> creds:", creds) if creds: # select only indy credentials creds = [x for x in creds if "cred_info" in x] @@ -484,7 +512,7 @@ async def handle_present_proof_v2_0(self, message): # submit the proof wit one unrevealed revealed attribute revealed_flag = False - for referent in pres_request_indy["requested_attributes"]: + for referent in pres_request["requested_attributes"]: if referent in creds_by_reft: revealed[referent] = { "cred_id": creds_by_reft[referent]["cred_info"][ @@ -496,7 +524,7 @@ async def handle_present_proof_v2_0(self, message): else: self_attested[referent] = "my self-attested value" - for referent in pres_request_indy["requested_predicates"]: + for referent in pres_request["requested_predicates"]: if referent in creds_by_reft: predicates[referent] = { "cred_id": creds_by_reft[referent]["cred_info"][ @@ -504,15 +532,15 @@ async def handle_present_proof_v2_0(self, message): ] } - log_status("#25 Generate the indy proof") - indy_request = { - "indy": { + log_status("#25 Generate the proof") + request = { + "indy" if "indy" in pres_request_by_format else "anoncreds": { "requested_predicates": predicates, "requested_attributes": revealed, "self_attested_attributes": self_attested, } } - request.update(indy_request) + request.update(request) except ClientError: pass @@ -779,7 +807,7 @@ def __init__( # endorsers and authors need public DIDs (assume cred_type is Indy) if endorser_role == "author" or endorser_role == "endorser": self.public_did = True - self.cred_type = CRED_FORMAT_INDY + # self.cred_type = CRED_FORMAT_INDY self.reuse_connections = reuse_connections self.multi_use_invitations = multi_use_invitations @@ -938,7 +966,7 @@ async def create_schema_and_cred_def( ): if not self.public_did: raise Exception("Can't create a schema/cred def without a public DID :-(") - if self.cred_type in [CRED_FORMAT_INDY, CRED_FORMAT_VC_DI]: + if self.cred_type in [CRED_FORMAT_ANONCREDS, CRED_FORMAT_INDY, CRED_FORMAT_VC_DI]: # need to redister schema and cred def on the ledger self.cred_def_id = await self.agent.create_schema_and_cred_def( schema_name, @@ -981,20 +1009,26 @@ async def issue_credential( self, cred_def_id: str, cred_attrs: list, + filter_type: str = "indy", ): log_status("#13 Issue credential offer to X") - if self.cred_type in [CRED_FORMAT_INDY, CRED_FORMAT_VC_DI]: + if self.cred_type in [CRED_FORMAT_ANONCREDS, CRED_FORMAT_INDY, CRED_FORMAT_VC_DI]: cred_preview = { "@type": CRED_PREVIEW_TYPE, "attributes": cred_attrs, } + if filter_type == "indy": + _filter = {"indy": {"cred_def_id": cred_def_id}} + else: + _filter = {"anoncreds": {"cred_def_id": cred_def_id}} + offer_request = { "connection_id": self.agent.connection_id, "comment": f"Offer on cred def id {cred_def_id}", "auto_remove": False, "credential_preview": cred_preview, - "filter": {"indy": {"cred_def_id": cred_def_id}}, + "filter": _filter, "trace": self.exchange_tracing, } cred_exchange = await self.agent.admin_POST( @@ -1041,11 +1075,13 @@ async def receive_credential( return matched - async def request_proof(self, proof_request, explicit_revoc_required: bool = False): + async def request_proof( + self, proof_request, explicit_revoc_required: bool = False, is_anoncreds=False + ): log_status("#20 Request proof of degree from alice") - if self.cred_type in [CRED_FORMAT_INDY, CRED_FORMAT_VC_DI]: - indy_proof_request = { + if self.cred_type in [CRED_FORMAT_ANONCREDS, CRED_FORMAT_INDY, CRED_FORMAT_VC_DI]: + proof_request = { "name": ( proof_request["name"] if "name" in proof_request else "Proof of stuff" ), @@ -1061,24 +1097,24 @@ async def request_proof(self, proof_request, explicit_revoc_required: bool = Fal # plug in revocation where requested in the supplied proof request non_revoked = {"to": int(time.time())} if "non_revoked" in proof_request: - indy_proof_request["non_revoked"] = non_revoked + proof_request["non_revoked"] = non_revoked non_revoked_supplied = True for attr in proof_request["requested_attributes"]: if "non_revoked" in proof_request["requested_attributes"][attr]: - indy_proof_request["requested_attributes"][attr][ - "non_revoked" - ] = non_revoked + proof_request["requested_attributes"][attr]["non_revoked"] = ( + non_revoked + ) non_revoked_supplied = True for pred in proof_request["requested_predicates"]: if "non_revoked" in proof_request["requested_predicates"][pred]: - indy_proof_request["requested_predicates"][pred][ - "non_revoked" - ] = non_revoked + proof_request["requested_predicates"][pred]["non_revoked"] = ( + non_revoked + ) non_revoked_supplied = True if not non_revoked_supplied and not explicit_revoc_required: # else just make it global - indy_proof_request["non_revoked"] = non_revoked + proof_request["non_revoked"] = non_revoked else: # make sure we are not leaking non-revoc requests @@ -1091,13 +1127,16 @@ async def request_proof(self, proof_request, explicit_revoc_required: bool = Fal if "non_revoked" in proof_request["requested_predicates"][pred]: del proof_request["requested_predicates"][pred]["non_revoked"] - log_status(f" >>> asking for proof for request: {indy_proof_request}") + log_status(f" >>> asking for proof for request: {proof_request}") + + if is_anoncreds: + presentation_request = {"anoncreds": proof_request} + else: + presentation_request = {"indy": proof_request} proof_request_web_request = { "connection_id": self.agent.connection_id, - "presentation_request": { - "indy": indy_proof_request, - }, + "presentation_request": presentation_request, "trace": self.exchange_tracing, } proof_exchange = await self.agent.admin_POST( @@ -1108,7 +1147,6 @@ async def request_proof(self, proof_request, explicit_revoc_required: bool = Fal elif self.cred_type == CRED_FORMAT_JSON_LD: # TODO create and send the json-ld proof request - pass return None else: @@ -1125,7 +1163,7 @@ async def verify_proof(self, proof_request): # log_status(f">>> last proof received: {self.agent.last_proof_received}") - if self.cred_type in [CRED_FORMAT_INDY, CRED_FORMAT_VC_DI]: + if self.cred_type in [CRED_FORMAT_ANONCREDS, CRED_FORMAT_INDY, CRED_FORMAT_VC_DI]: # return verified status return self.agent.last_proof_received["verified"] @@ -1514,12 +1552,14 @@ async def create_agent_with_args(args, ident: str = None, extra_args: list = Non aip = 20 if "cred_type" in args and args.cred_type not in [ + CRED_FORMAT_ANONCREDS, CRED_FORMAT_INDY, CRED_FORMAT_VC_DI, ]: public_did = None aip = 20 elif "cred_type" in args and args.cred_type in [ + CRED_FORMAT_ANONCREDS, CRED_FORMAT_INDY, CRED_FORMAT_VC_DI, ]: @@ -1528,6 +1568,12 @@ async def create_agent_with_args(args, ident: str = None, extra_args: list = Non public_did = args.public_did if "public_did" in args else None cred_type = args.cred_type if "cred_type" in args else None + + # Set anoncreds agent to use anoncreds credential format + wallet_type = arg_file_dict.get("wallet-type") or args.wallet_type + if wallet_type == "askar-anoncreds": + cred_type = CRED_FORMAT_ANONCREDS + log_msg( f"Initializing demo agent {agent_ident} with AIP {aip} and credential type {cred_type}" ) @@ -1564,7 +1610,7 @@ async def create_agent_with_args(args, ident: str = None, extra_args: list = Non mediation=args.mediation, cred_type=cred_type, use_did_exchange=(aip == 20) if ("aip" in args) else args.did_exchange, - wallet_type=arg_file_dict.get("wallet-type") or args.wallet_type, + wallet_type=wallet_type, public_did=public_did, seed="random" if public_did else None, arg_file=arg_file, diff --git a/demo/runners/faber.py b/demo/runners/faber.py index 5cecf47d37..00cc9a2e98 100644 --- a/demo/runners/faber.py +++ b/demo/runners/faber.py @@ -17,6 +17,7 @@ create_agent_with_args, ) from runners.support.agent import ( # noqa:E402 + CRED_FORMAT_ANONCREDS, CRED_FORMAT_INDY, CRED_FORMAT_JSON_LD, CRED_FORMAT_VC_DI, @@ -111,7 +112,7 @@ def generate_credential_offer(self, aip, cred_type, cred_def_id, exchange_tracin return offer_request elif aip == 20: - if cred_type == CRED_FORMAT_INDY: + if cred_type == CRED_FORMAT_ANONCREDS or cred_type == CRED_FORMAT_INDY: self.cred_attrs[cred_def_id] = { "name": "Alice Smith", "date": "2018-05-28", @@ -127,12 +128,16 @@ def generate_credential_offer(self, aip, cred_type, cred_def_id, exchange_tracin for (n, v) in self.cred_attrs[cred_def_id].items() ], } + if cred_type == CRED_FORMAT_ANONCREDS: + _filter = {"anoncreds": {"cred_def_id": cred_def_id}} + else: + _filter = {"indy": {"cred_def_id": cred_def_id}} offer_request = { "connection_id": self.connection_id, "comment": f"Offer on cred def id {cred_def_id}", "auto_remove": False, "credential_preview": cred_preview, - "filter": {"indy": {"cred_def_id": cred_def_id}}, + "filter": _filter, "trace": exchange_tracing, } return offer_request @@ -249,7 +254,7 @@ def generate_proof_request_web_request( "restrictions": [{"schema_name": "degree schema"}], } ] - indy_proof_request = { + proof_request = { "name": "Proof of Education", "version": "1.0", "requested_attributes": { @@ -261,10 +266,10 @@ def generate_proof_request_web_request( } if revocation: - indy_proof_request["non_revoked"] = {"to": int(time.time())} + proof_request["non_revoked"] = {"to": int(time.time())} proof_request_web_request = { - "proof_request": indy_proof_request, + "proof_request": proof_request, "trace": exchange_tracing, } if not connectionless: @@ -272,7 +277,7 @@ def generate_proof_request_web_request( return proof_request_web_request elif aip == 20: - if cred_type == CRED_FORMAT_INDY: + if cred_type == CRED_FORMAT_ANONCREDS or cred_type == CRED_FORMAT_INDY: req_attrs = [ { "name": "name", @@ -312,7 +317,7 @@ def generate_proof_request_web_request( "restrictions": [{"schema_name": "degree schema"}], } ] - indy_proof_request = { + proof_request = { "name": "Proof of Education", "version": "1.0", "requested_attributes": { @@ -325,14 +330,19 @@ def generate_proof_request_web_request( } if revocation: - indy_proof_request["non_revoked"] = {"to": int(time.time())} + proof_request["non_revoked"] = {"to": int(time.time())} + if cred_type == CRED_FORMAT_ANONCREDS: + presentation_request = {"anoncreds": proof_request} + else: + presentation_request = {"indy": proof_request} proof_request_web_request = { - "presentation_request": {"indy": indy_proof_request}, + "presentation_request": presentation_request, "trace": exchange_tracing, } if not connectionless: proof_request_web_request["connection_id"] = self.connection_id + return proof_request_web_request elif cred_type == CRED_FORMAT_VC_DI: @@ -537,7 +547,11 @@ async def main(args): "birthdate_dateint", "timestamp", ] - if faber_agent.cred_type in [CRED_FORMAT_INDY, CRED_FORMAT_VC_DI]: + if faber_agent.cred_type in [ + CRED_FORMAT_ANONCREDS, + CRED_FORMAT_INDY, + CRED_FORMAT_VC_DI, + ]: faber_agent.public_did = True await faber_agent.initialize( the_agent=agent, @@ -569,6 +583,7 @@ async def main(args): exchange_tracing = False options = " (1) Issue Credential\n" if faber_agent.cred_type in [ + CRED_FORMAT_ANONCREDS, CRED_FORMAT_INDY, CRED_FORMAT_VC_DI, ]: @@ -664,12 +679,14 @@ async def main(args): elif option == "1a": new_cred_type = await prompt( - "Enter credential type ({}, {}): ".format( + "Enter credential type ({}, {}, {}): ".format( + CRED_FORMAT_ANONCREDS, CRED_FORMAT_INDY, CRED_FORMAT_VC_DI, ) ) if new_cred_type in [ + CRED_FORMAT_ANONCREDS, CRED_FORMAT_INDY, CRED_FORMAT_VC_DI, ]: @@ -689,7 +706,11 @@ async def main(args): ) elif faber_agent.aip == 20: - if faber_agent.cred_type == CRED_FORMAT_INDY: + if faber_agent.cred_type in [ + CRED_FORMAT_ANONCREDS, + CRED_FORMAT_INDY, + CRED_FORMAT_VC_DI, + ]: offer_request = faber_agent.agent.generate_credential_offer( faber_agent.aip, faber_agent.cred_type, @@ -705,14 +726,6 @@ async def main(args): exchange_tracing, ) - elif faber_agent.cred_type == CRED_FORMAT_VC_DI: - offer_request = faber_agent.agent.generate_credential_offer( - faber_agent.aip, - faber_agent.cred_type, - faber_agent.cred_def_id, - exchange_tracing, - ) - else: raise Exception( f"Error invalid credential type: {faber_agent.cred_type}" @@ -742,27 +755,12 @@ async def main(args): pass elif faber_agent.aip == 20: - if faber_agent.cred_type == CRED_FORMAT_INDY: - proof_request_web_request = ( - faber_agent.agent.generate_proof_request_web_request( - faber_agent.aip, - faber_agent.cred_type, - faber_agent.revocation, - exchange_tracing, - ) - ) - - elif faber_agent.cred_type == CRED_FORMAT_JSON_LD: - proof_request_web_request = ( - faber_agent.agent.generate_proof_request_web_request( - faber_agent.aip, - faber_agent.cred_type, - faber_agent.revocation, - exchange_tracing, - ) - ) - - elif faber_agent.cred_type == CRED_FORMAT_VC_DI: + if faber_agent.cred_type in [ + CRED_FORMAT_ANONCREDS, + CRED_FORMAT_INDY, + CRED_FORMAT_VC_DI, + CRED_FORMAT_JSON_LD, + ]: proof_request_web_request = ( faber_agent.agent.generate_proof_request_web_request( faber_agent.aip, @@ -771,7 +769,6 @@ async def main(args): exchange_tracing, ) ) - else: raise Exception( "Error invalid credential type:" + faber_agent.cred_type @@ -819,28 +816,12 @@ async def main(args): qr.print_ascii(invert=True) elif faber_agent.aip == 20: - if faber_agent.cred_type == CRED_FORMAT_INDY: - proof_request_web_request = ( - faber_agent.agent.generate_proof_request_web_request( - faber_agent.aip, - faber_agent.cred_type, - faber_agent.revocation, - exchange_tracing, - connectionless=True, - ) - ) - elif faber_agent.cred_type == CRED_FORMAT_JSON_LD: - proof_request_web_request = ( - faber_agent.agent.generate_proof_request_web_request( - faber_agent.aip, - faber_agent.cred_type, - faber_agent.revocation, - exchange_tracing, - connectionless=True, - ) - ) - - elif faber_agent.cred_type == CRED_FORMAT_VC_DI: + if faber_agent.cred_type in [ + CRED_FORMAT_ANONCREDS, + CRED_FORMAT_INDY, + CRED_FORMAT_VC_DI, + CRED_FORMAT_JSON_LD, + ]: proof_request_web_request = ( faber_agent.agent.generate_proof_request_web_request( faber_agent.aip, diff --git a/demo/runners/support/agent.py b/demo/runners/support/agent.py index 2905ad4912..5d1e10ccd6 100644 --- a/demo/runners/support/agent.py +++ b/demo/runners/support/agent.py @@ -70,6 +70,7 @@ WALLET_TYPE_ASKAR = "askar" WALLET_TYPE_ANONCREDS = "askar-anoncreds" +CRED_FORMAT_ANONCREDS = "anoncreds" CRED_FORMAT_INDY = "indy" CRED_FORMAT_JSON_LD = "json-ld" CRED_FORMAT_VC_DI = "vc_di" @@ -672,7 +673,7 @@ async def register_did( role: str = "TRUST_ANCHOR", cred_type: str = CRED_FORMAT_INDY, ): - if cred_type in [CRED_FORMAT_INDY, CRED_FORMAT_VC_DI]: + if cred_type in [CRED_FORMAT_ANONCREDS, CRED_FORMAT_INDY, CRED_FORMAT_VC_DI]: # if registering a did for issuing indy credentials, publish the did on the ledger self.log(f"Registering {self.ident} ...") if not ledger_url: diff --git a/mkdocs-requirements.txt b/mkdocs-requirements.txt index eb1ee40600..a6d710f6af 100644 --- a/mkdocs-requirements.txt +++ b/mkdocs-requirements.txt @@ -1,3 +1,3 @@ -mkdocs-material==9.5.44 +mkdocs-material==9.5.46 mike==2.1.3 diff --git a/poetry.lock b/poetry.lock index 381bfd21f8..37ddba7f6c 100644 --- a/poetry.lock +++ b/poetry.lock @@ -2514,29 +2514,29 @@ test = ["hypothesis (==5.19.0)", "pytest (>=7.0.0)", "pytest-xdist (>=2.4.0)"] [[package]] name = "ruff" -version = "0.7.4" +version = "0.8.0" description = "An extremely fast Python linter and code formatter, written in Rust." optional = false python-versions = ">=3.7" files = [ - {file = "ruff-0.7.4-py3-none-linux_armv6l.whl", hash = "sha256:a4919925e7684a3f18e18243cd6bea7cfb8e968a6eaa8437971f681b7ec51478"}, - {file = "ruff-0.7.4-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:cfb365c135b830778dda8c04fb7d4280ed0b984e1aec27f574445231e20d6c63"}, - {file = "ruff-0.7.4-py3-none-macosx_11_0_arm64.whl", hash = "sha256:63a569b36bc66fbadec5beaa539dd81e0527cb258b94e29e0531ce41bacc1f20"}, - {file = "ruff-0.7.4-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0d06218747d361d06fd2fdac734e7fa92df36df93035db3dc2ad7aa9852cb109"}, - {file = "ruff-0.7.4-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e0cea28d0944f74ebc33e9f934238f15c758841f9f5edd180b5315c203293452"}, - {file = "ruff-0.7.4-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:80094ecd4793c68b2571b128f91754d60f692d64bc0d7272ec9197fdd09bf9ea"}, - {file = "ruff-0.7.4-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:997512325c6620d1c4c2b15db49ef59543ef9cd0f4aa8065ec2ae5103cedc7e7"}, - {file = "ruff-0.7.4-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:00b4cf3a6b5fad6d1a66e7574d78956bbd09abfd6c8a997798f01f5da3d46a05"}, - {file = "ruff-0.7.4-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7dbdc7d8274e1422722933d1edddfdc65b4336abf0b16dfcb9dedd6e6a517d06"}, - {file = "ruff-0.7.4-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0e92dfb5f00eaedb1501b2f906ccabfd67b2355bdf117fea9719fc99ac2145bc"}, - {file = "ruff-0.7.4-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:3bd726099f277d735dc38900b6a8d6cf070f80828877941983a57bca1cd92172"}, - {file = "ruff-0.7.4-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:2e32829c429dd081ee5ba39aef436603e5b22335c3d3fff013cd585806a6486a"}, - {file = "ruff-0.7.4-py3-none-musllinux_1_2_i686.whl", hash = "sha256:662a63b4971807623f6f90c1fb664613f67cc182dc4d991471c23c541fee62dd"}, - {file = "ruff-0.7.4-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:876f5e09eaae3eb76814c1d3b68879891d6fde4824c015d48e7a7da4cf066a3a"}, - {file = "ruff-0.7.4-py3-none-win32.whl", hash = "sha256:75c53f54904be42dd52a548728a5b572344b50d9b2873d13a3f8c5e3b91f5cac"}, - {file = "ruff-0.7.4-py3-none-win_amd64.whl", hash = "sha256:745775c7b39f914238ed1f1b0bebed0b9155a17cd8bc0b08d3c87e4703b990d6"}, - {file = "ruff-0.7.4-py3-none-win_arm64.whl", hash = "sha256:11bff065102c3ae9d3ea4dc9ecdfe5a5171349cdd0787c1fc64761212fc9cf1f"}, - {file = "ruff-0.7.4.tar.gz", hash = "sha256:cd12e35031f5af6b9b93715d8c4f40360070b2041f81273d0527683d5708fce2"}, + {file = "ruff-0.8.0-py3-none-linux_armv6l.whl", hash = "sha256:fcb1bf2cc6706adae9d79c8d86478677e3bbd4ced796ccad106fd4776d395fea"}, + {file = "ruff-0.8.0-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:295bb4c02d58ff2ef4378a1870c20af30723013f441c9d1637a008baaf928c8b"}, + {file = "ruff-0.8.0-py3-none-macosx_11_0_arm64.whl", hash = "sha256:7b1f1c76b47c18fa92ee78b60d2d20d7e866c55ee603e7d19c1e991fad933a9a"}, + {file = "ruff-0.8.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eb0d4f250a7711b67ad513fde67e8870109e5ce590a801c3722580fe98c33a99"}, + {file = "ruff-0.8.0-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0e55cce9aa93c5d0d4e3937e47b169035c7e91c8655b0974e61bb79cf398d49c"}, + {file = "ruff-0.8.0-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3f4cd64916d8e732ce6b87f3f5296a8942d285bbbc161acee7fe561134af64f9"}, + {file = "ruff-0.8.0-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:c5c1466be2a2ebdf7c5450dd5d980cc87c8ba6976fb82582fea18823da6fa362"}, + {file = "ruff-0.8.0-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2dabfd05b96b7b8f2da00d53c514eea842bff83e41e1cceb08ae1966254a51df"}, + {file = "ruff-0.8.0-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:facebdfe5a5af6b1588a1d26d170635ead6892d0e314477e80256ef4a8470cf3"}, + {file = "ruff-0.8.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:87a8e86bae0dbd749c815211ca11e3a7bd559b9710746c559ed63106d382bd9c"}, + {file = "ruff-0.8.0-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:85e654f0ded7befe2d61eeaf3d3b1e4ef3894469cd664ffa85006c7720f1e4a2"}, + {file = "ruff-0.8.0-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:83a55679c4cb449fa527b8497cadf54f076603cc36779b2170b24f704171ce70"}, + {file = "ruff-0.8.0-py3-none-musllinux_1_2_i686.whl", hash = "sha256:812e2052121634cf13cd6fddf0c1871d0ead1aad40a1a258753c04c18bb71bbd"}, + {file = "ruff-0.8.0-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:780d5d8523c04202184405e60c98d7595bdb498c3c6abba3b6d4cdf2ca2af426"}, + {file = "ruff-0.8.0-py3-none-win32.whl", hash = "sha256:5fdb6efecc3eb60bba5819679466471fd7d13c53487df7248d6e27146e985468"}, + {file = "ruff-0.8.0-py3-none-win_amd64.whl", hash = "sha256:582891c57b96228d146725975fbb942e1f30a0c4ba19722e692ca3eb25cc9b4f"}, + {file = "ruff-0.8.0-py3-none-win_arm64.whl", hash = "sha256:ba93e6294e9a737cd726b74b09a6972e36bb511f9a102f1d9a7e1ce94dd206a6"}, + {file = "ruff-0.8.0.tar.gz", hash = "sha256:a7ccfe6331bf8c8dad715753e157457faf7351c2b69f62f32c165c2dbcbacd44"}, ] [[package]] @@ -2801,87 +2801,38 @@ files = [ [[package]] name = "uuid-utils" -version = "0.9.0" +version = "0.10.0" description = "Drop-in replacement for Python UUID in Rust" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" files = [ - {file = "uuid_utils-0.9.0-cp310-cp310-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:28da15c275ef06a759efe0fcba7c58eab8ae2217f7a7f66289dee6ae332605a0"}, - {file = "uuid_utils-0.9.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:81144165068e84a1ac8b92f23bfc961e5b5dbb89ddcff70c5e7d096ae07a439c"}, - {file = "uuid_utils-0.9.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:362695ed7756b1b89d9a2dd193fe2523ca1d4a31235606f7cb516a322789ffc4"}, - {file = "uuid_utils-0.9.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:36cb70e29f5749b554ae06f1a88926c9b47d5b48004b6903fb7ea47e8b398080"}, - {file = "uuid_utils-0.9.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fa39a95eb67e2b98a61d4e64ddf622ee902fe9142b579921ab3e9027472f5080"}, - {file = "uuid_utils-0.9.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d9b7029044d40f66366cebb6a94ecb04d8c0a745029561552d527ac52dc0deb9"}, - {file = "uuid_utils-0.9.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c883c6f815d2af344dab585d7b16a77410fcb1f6c097e6ad0384bd12cd84e16e"}, - {file = "uuid_utils-0.9.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:be5f83e9002a43aac868fa9cbc73f28cfb0b4e8b14f5a449fef04fea31bf2904"}, - {file = "uuid_utils-0.9.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:8f84b0fd38df8a211230b8bbd02d5e7e631f1d35c1ba67efb895f80c7d9215aa"}, - {file = "uuid_utils-0.9.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:89cc342b061d8e3f669b8aaeb8d9e2973407c93aaa675dc6a59e985feda2805c"}, - {file = "uuid_utils-0.9.0-cp310-none-win32.whl", hash = "sha256:3d5554f44aeab9706770169316897f79089b51a061800684b7f29bccf663ad0f"}, - {file = "uuid_utils-0.9.0-cp310-none-win_amd64.whl", hash = "sha256:db35df169f17617129cc070e3a8fba00598ee55adcbe22c79b2c2f306eeb3b9e"}, - {file = "uuid_utils-0.9.0-cp311-cp311-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:cdb7224fb8b1f18edcd4357f83fcbe33b9f82ac392095e5d9e0e05336d65d587"}, - {file = "uuid_utils-0.9.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:5312449ea5427a94edab3aebf2cff8f99a3112f273dd9d5919e332bc4e6a031f"}, - {file = "uuid_utils-0.9.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1d6837677217b3d0aa375f8aa7b89935c3ebf2ee5e1667be50b2090741684baa"}, - {file = "uuid_utils-0.9.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:402e522f62ceb8344f579e37f9a25c9274d484675afd71ad725ddd61ced7cd36"}, - {file = "uuid_utils-0.9.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e24a27724b2289d66d75358ffecf698ae36816458897cfdec972e6ede1ead328"}, - {file = "uuid_utils-0.9.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db04420c0b1a99f3160ddc90ba52986484ce8304b54586ea161133f755a4605b"}, - {file = "uuid_utils-0.9.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:152f3c1a0634558e329b64ff4a9592ee83b722e3fdc214f648f851fa14163c36"}, - {file = "uuid_utils-0.9.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:5710b6ca95966f80125f48dad9e3d70ef99f48dd892bec3cb38673d087a7d71a"}, - {file = "uuid_utils-0.9.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e3b9b9cf0cc544ef552c955bc2caaecf5e38cc8ed65e23f208b0dcdb6d37719e"}, - {file = "uuid_utils-0.9.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:6d715b6321d4728a1aa0d5e26a338c97aaba2bf337f3ef9804ef5027f07de61d"}, - {file = "uuid_utils-0.9.0-cp311-none-win32.whl", hash = "sha256:3b61b0543404c99f7f23fb556b69090bc58358e8bd9d570381722221281f3706"}, - {file = "uuid_utils-0.9.0-cp311-none-win_amd64.whl", hash = "sha256:b4bdd9e80c92b1f253e66392e1d6ad0ec4926d5c7e2de3d852ee6fed1939f58b"}, - {file = "uuid_utils-0.9.0-cp312-cp312-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:9cfcfb222f62612bea58bdec32b6842d9636fca53ca4579f88c1b620b1c4cbf7"}, - {file = "uuid_utils-0.9.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:7253039ecfa534b3177b3225764cede9c6dae5db572e17d8582dd7cc5b3af484"}, - {file = "uuid_utils-0.9.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6fb6e792ae1e3a7bf9d357f87dcf628923d0881f1d13a9cba826b848ca1774b1"}, - {file = "uuid_utils-0.9.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fbea3bb9ad319de7f69d6a60b6921acaa97d577aaeceb247b1ff4a8a92b3aa6a"}, - {file = "uuid_utils-0.9.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:09043b3f48e20d631c7a81030be8c4f6ea9a6d44cccda8cbbdc1ad18da301c89"}, - {file = "uuid_utils-0.9.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d9ba2681f5f177f800e40f5a0f09ed03c49f4290a245e4273d21cb48a7e88969"}, - {file = "uuid_utils-0.9.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:866811473f3d9dc38c02dd08e076e333b9e4d41ce952353941b49d0724d2327b"}, - {file = "uuid_utils-0.9.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:14aa9917ef11bd0ee1648237dddbf33aa0459e609ebe2f9202ffb259ca80f763"}, - {file = "uuid_utils-0.9.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:22cae83bc486a6c12b38239eb0546f2f81c81c34fcf833f84c2c7cd0cab1ad8f"}, - {file = "uuid_utils-0.9.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c9513399045169435563c284caba0f1802748b02f27c95769426b7a00ed246e1"}, - {file = "uuid_utils-0.9.0-cp312-none-win_amd64.whl", hash = "sha256:284e6d24ba659d003bd47fcfc404d374c7807983407d4d61f26c80a3b7f4cb41"}, - {file = "uuid_utils-0.9.0-cp38-cp38-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:fd3d09c53d5a96c78c77211786fa25bbdbe031566b927383c10ab81a27c7e431"}, - {file = "uuid_utils-0.9.0-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:2b052d01176d2d1afa476ebe158eae052e265c92110d987d5801f0c3c1c8852a"}, - {file = "uuid_utils-0.9.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:85e636a9603dc04e15b8305129619b2b45843f883e58355c91dc6cc22d51dde8"}, - {file = "uuid_utils-0.9.0-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:37d2f9ec3a99caf29531fe257b7906f8a5946a3ee7000c744d73e5187a2542a1"}, - {file = "uuid_utils-0.9.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4ace932d10a83141d22c42287a0c707cfa41522ecb797f17e63820f09688c6d7"}, - {file = "uuid_utils-0.9.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:843ec54337233d5f99a99b156ba5be5659f5ea193db8287035bf85d6a6a00c58"}, - {file = "uuid_utils-0.9.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f16a60ef4ddf9fe16fcd4793f83932d2b5198af6873d0a0478acfe0bd19d8e2f"}, - {file = "uuid_utils-0.9.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:901da31d61f78ec8df96741326541ae7af156a2628ca82ccf2806bcee257de1a"}, - {file = "uuid_utils-0.9.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:fad20ba28a11f2b33f06b2d7e0e77be73a8c3a41de833a123c6d93025d463b4b"}, - {file = "uuid_utils-0.9.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:c5c30716a0dcffd1a7a66853f5a8372a276d454510f48fc7e4c39c13212bb1f6"}, - {file = "uuid_utils-0.9.0-cp38-none-win32.whl", hash = "sha256:2912c9a810e74cb3abaebd0fdfa5b1f202cf60a8f0fec9cc76117dab6c42cb77"}, - {file = "uuid_utils-0.9.0-cp38-none-win_amd64.whl", hash = "sha256:b55bcffc260356c84a09ac3a391e2442ad4df0beaaf71670d4e8ffd8b7328e75"}, - {file = "uuid_utils-0.9.0-cp39-cp39-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:08aec826a8ed4d3baab29d144ad634091e5accabcb88dfa0a834b9ba9e74930f"}, - {file = "uuid_utils-0.9.0-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:e7de8fcf3d22e299582ab61c67390abdf37cc7d5b330066c3f792a082167c419"}, - {file = "uuid_utils-0.9.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8de8da71bc5a6a527952648357acca8d6d1717bbbc044304c5a9cf53eb06568c"}, - {file = "uuid_utils-0.9.0-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ce3ef96aa6ba760e1d0481ed076fe9e8f0386e27c7039bc6ffa377c98979fae7"}, - {file = "uuid_utils-0.9.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:69dd8c0d622e4e40b1b993a2e452bbed21127b97f8e4a3fdbde6e5f556253265"}, - {file = "uuid_utils-0.9.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4600d5dcd9d0759be4bc580daf6d521da99a825a25db3d492abcbb12f6dc98f6"}, - {file = "uuid_utils-0.9.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:067d5f36ccc70896c76a9dafc8220a942db0e42ab58b84c43208e0ff84c1bdf0"}, - {file = "uuid_utils-0.9.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:7a23d20f08636899a1b24394a12a2c469262af56d5279dbac1c6b14b059e5d4b"}, - {file = "uuid_utils-0.9.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:6a2cbc5da89f0e701d2b80df0e6b30da16a0adcd2883da3d3e1f7aac1f046c1c"}, - {file = "uuid_utils-0.9.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:bbf93bdd3c8a3e3b2b2f82ad4dfafcd2aaecc1d6ec924fd5da2b712ab324ee51"}, - {file = "uuid_utils-0.9.0-cp39-none-win32.whl", hash = "sha256:9d9b3ba5be11a017c568e904eac76f6cd6f82f956ca65ffbaa73ee8d7cdbe13b"}, - {file = "uuid_utils-0.9.0-cp39-none-win_amd64.whl", hash = "sha256:b3e494547cfef124f0c51831a303bfc0278e3c33b9ab9d9db90d94728d9871c2"}, - {file = "uuid_utils-0.9.0-pp38-pypy38_pp73-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:98c7c7b414b405b547c9bc1f972513b3a0bfb3dca248c89c623ab60efdd36ad8"}, - {file = "uuid_utils-0.9.0-pp38-pypy38_pp73-macosx_10_12_x86_64.whl", hash = "sha256:7b75241666cae4bc5786e6c68e862ccf4e15b2f98c81e47a6b0a85c907b7b6fa"}, - {file = "uuid_utils-0.9.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bf6b43723a2d4af21c07fec98385a97e2023a808fd6eaf4296a163bffca76cf5"}, - {file = "uuid_utils-0.9.0-pp38-pypy38_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4c260bb9a9fdd484cadc437cdb26ad07d66c8049a365c4ef49c9f09d7ed9ac2e"}, - {file = "uuid_utils-0.9.0-pp38-pypy38_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f4605c6326a7914d1b59c548f0ae4ef119619fe09c3ab4f07d99f3fbec39d470"}, - {file = "uuid_utils-0.9.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d498673249826ce7c47aa481709a5dd647bbff764fb64370bc691c7e43c7a2a1"}, - {file = "uuid_utils-0.9.0-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a37c48cba2f4eb6182a3857d045aa1420fa9878bfd268f6855fce7293a006677"}, - {file = "uuid_utils-0.9.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:eeb9c56d401e4a422ae7fd818ef1376bda384ae0bed86e339c9a256c6b10d252"}, - {file = "uuid_utils-0.9.0-pp39-pypy39_pp73-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:33dd97e9c59891fbfb4e4a11596c42cc3d10774b843daaf75316d036a0a07799"}, - {file = "uuid_utils-0.9.0-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:b5e67fc3c6def9d62de4caf19529bd8044e821eb2f5c39821cfd011940f96394"}, - {file = "uuid_utils-0.9.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:241ab8ddb76ef8d4294c09cd6045dd62a1aa46acf8ff0a017796a54a530a87ae"}, - {file = "uuid_utils-0.9.0-pp39-pypy39_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5a9a16d2af4b4ad59d4cef7f43ba33644353b12769fafabf6e1663aa230cca38"}, - {file = "uuid_utils-0.9.0-pp39-pypy39_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:897a167659d30a8431a094533bcb58d2b16005f7456408bd40f3e5872e1b2f46"}, - {file = "uuid_utils-0.9.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0b4f56ab5ce19697552df9689c24c742054e8fd8e2ef8c26f9b8393f6d7ae893"}, - {file = "uuid_utils-0.9.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:7b219d9341de117e2e608f3fe08e31480ad60fb6128f8fffd2151ba88e53508c"}, - {file = "uuid_utils-0.9.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:b5c08de8a94181c0ba209a8b8275993499f424b57958bea6c39739311526fd18"}, - {file = "uuid_utils-0.9.0.tar.gz", hash = "sha256:239f45b7e71d0ea61e3a4957aa4b8f3b49bfb009cf768d11c3f464787dbb737f"}, + {file = "uuid_utils-0.10.0-cp39-abi3-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:8d5a4508feefec62456cd6a41bcdde458d56827d908f226803b886d22a3d5e63"}, + {file = "uuid_utils-0.10.0-cp39-abi3-macosx_10_12_x86_64.whl", hash = "sha256:dbefc2b9113f9dfe56bdae58301a2b3c53792221410d422826f3d1e3e6555fe7"}, + {file = "uuid_utils-0.10.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ffc49c33edf87d1ec8112a9b43e4cf55326877716f929c165a2cc307d31c73d5"}, + {file = "uuid_utils-0.10.0-cp39-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0636b6208f69d5a4e629707ad2a89a04dfa8d1023e1999181f6830646ca048a1"}, + {file = "uuid_utils-0.10.0-cp39-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7bc06452856b724df9dedfc161c3582199547da54aeb81915ec2ed54f92d19b0"}, + {file = "uuid_utils-0.10.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:263b2589111c61decdd74a762e8f850c9e4386fb78d2cf7cb4dfc537054cda1b"}, + {file = "uuid_utils-0.10.0-cp39-abi3-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a558db48b7096de6b4d2d2210d82bba8586a6d55f99106b03bb7d01dc5c5bcd6"}, + {file = "uuid_utils-0.10.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:807465067f3c892514230326ac71a79b28a8dfe2c88ecd2d5675fc844f3c76b5"}, + {file = "uuid_utils-0.10.0-cp39-abi3-musllinux_1_2_i686.whl", hash = "sha256:57423d4a2b9d7b916de6dbd75ba85465a28f9578a89a97f7d3e098d9aa4e5d4a"}, + {file = "uuid_utils-0.10.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:76d8d660f18ff6b767e319b1b5f927350cd92eafa4831d7ef5b57fdd1d91f974"}, + {file = "uuid_utils-0.10.0-cp39-abi3-win32.whl", hash = "sha256:6c11a71489338837db0b902b75e1ba7618d5d29f05fde4f68b3f909177dbc226"}, + {file = "uuid_utils-0.10.0-cp39-abi3-win_amd64.whl", hash = "sha256:11c55ae64f6c0a7a0c741deae8ca2a4eaa11e9c09dbb7bec2099635696034cf7"}, + {file = "uuid_utils-0.10.0-pp310-pypy310_pp73-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:acea543dfc7b87df749e3e814c54ac739a82ff5e3800d25bd25a3e00599e1554"}, + {file = "uuid_utils-0.10.0-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:0767eefa7b1e96f06cfa9b95758d286240c01bbf19e9d8f1b6043cdbe76cc639"}, + {file = "uuid_utils-0.10.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:973fe4bb5258fd2ccb144d8b40c2d3158f16cc856a20527f8b40d14b2ae1dee9"}, + {file = "uuid_utils-0.10.0-pp310-pypy310_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:71b8505b67a0d77d0fbd765d8463094a8f447677125da7647bec7ea0b99406f0"}, + {file = "uuid_utils-0.10.0-pp310-pypy310_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6bdcb1211bb61476cbef12a87101fa48243e20ed82b2bd324c816b1b5826bd5e"}, + {file = "uuid_utils-0.10.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1c5247f1df040aae71ea313819b563debe69bca7086a2cc6a3ac0eaddd3dadac"}, + {file = "uuid_utils-0.10.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a50bd29ef89660b93aa07ffa95ac691a0e12832375030569a8bd5c9272f3b8e6"}, + {file = "uuid_utils-0.10.0-pp39-pypy39_pp73-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:a778cd9d8f995b94bba6e51f3ebee5b338fd834b0c4ecc8f932bd23e29db3e19"}, + {file = "uuid_utils-0.10.0-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:d3d5b5c5ed66ff923961b9ebb902232cd67f6a7ec6b6f7a58e05e00ff44e3c7f"}, + {file = "uuid_utils-0.10.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:789ed6335225326c66f5d6162649bed978105a85f232be7811387c395c226801"}, + {file = "uuid_utils-0.10.0-pp39-pypy39_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:05d1aa7b944b719eb1ee472435ae5444a3f8a00eb6350e3b1d1217d738477d33"}, + {file = "uuid_utils-0.10.0-pp39-pypy39_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:aa8d8559c2d25d6ac87e0adeee601d2c91ec40b357ab780bcf79061cc23324e6"}, + {file = "uuid_utils-0.10.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e0badcbfe3c72b5b30d59c2b12f120923127abd95a0d2aa64ddc1234e495abc2"}, + {file = "uuid_utils-0.10.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:0a7c1c494012335113748815156c5b6234c59b0fe0d3a8eede1b1a46f7e25a69"}, + {file = "uuid_utils-0.10.0.tar.gz", hash = "sha256:5db0e1890e8f008657ffe6ded4d9459af724ab114cfe82af1557c87545301539"}, ] [[package]] @@ -3041,4 +2992,4 @@ didcommv2 = ["didcomm-messaging"] [metadata] lock-version = "2.0" python-versions = "^3.12" -content-hash = "43c5305bb9f9811e63f129b843936e514bcf0d32c160e723f6213c86fe4fc417" +content-hash = "cb8b649f6c4779cc8e3680d088b22b35c96b912a3c1ed3c17441d82f3a10f7eb" diff --git a/pyproject.toml b/pyproject.toml index c1732b3039..67df9fd78a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -16,7 +16,7 @@ repository = "https://github.com/openwallet-foundation/acapy" [tool.poetry.dependencies] python = "^3.12" -aiohttp = "~3.11.2" +aiohttp = "~3.11.7" aiohttp-apispec-acapy = "~3.0.2" aiohttp-cors = "~0.7.0" apispec = "^6.6.0" @@ -45,7 +45,7 @@ requests = "~2.32.3" rlp = "4.0.1" unflatten = "~0.2" sd-jwt = "^0.10.3" -uuid_utils = "^0.9.0" +uuid_utils = "^0.10.0" # did libraries did-peer-2 = "^0.1.2" @@ -69,7 +69,7 @@ canonicaljson = "^2.0.0" [tool.poetry.group.dev.dependencies] pre-commit = "~3.8.0" # Sync with version in .pre-commit-config.yaml -ruff = "0.7.4" +ruff = "0.8.0" sphinx = "^5.3.0" sphinx-rtd-theme = ">=0.4.3" @@ -84,7 +84,7 @@ pytest-asyncio = "^0.24.0" pytest-cov = "^5.0.0" pytest-ruff = "^0.4.1" pytest-xdist = "^3.6.1" -debugpy = "^1.8.8" +debugpy = "^1.8.9" [tool.poetry.extras] askar = ["aries-askar-ff137", "indy-credx", "indy-vdr", "anoncreds"] diff --git a/scenarios/examples/anoncreds_issuance_and_revocation/docker-compose.yml b/scenarios/examples/anoncreds_issuance_and_revocation/docker-compose.yml index 247a0b7daf..6e0a757fbd 100644 --- a/scenarios/examples/anoncreds_issuance_and_revocation/docker-compose.yml +++ b/scenarios/examples/anoncreds_issuance_and_revocation/docker-compose.yml @@ -1,22 +1,26 @@ services: - alice: + agency: image: acapy-test ports: - "3001:3001" command: > start - --label Alice + --label Agency --inbound-transport http 0.0.0.0 3000 --outbound-transport http - --endpoint http://alice:3000 + --endpoint http://agency:3000 --admin 0.0.0.0 3001 --admin-insecure-mode --tails-server-base-url http://tails:6543 --genesis-url http://test.bcovrin.vonx.io/genesis - --wallet-type askar-anoncreds - --wallet-name alice + --wallet-type askar + --wallet-name agency --wallet-key insecure --auto-provision + --multitenant + --multitenant-admin + --jwt-secret insecure + --multitenancy-config wallet_type=single-wallet-askar key_derivation_method=RAW --log-level info --debug-webhooks --notify-revocation @@ -30,22 +34,50 @@ tails: condition: service_started - bob: + holder_anoncreds: image: acapy-test ports: - "3002:3001" command: > start - --label Bob + --label Holder-Anoncreds --inbound-transport http 0.0.0.0 3000 --outbound-transport http - --endpoint http://bob:3000 + --endpoint http://holder_anoncreds:3000 --admin 0.0.0.0 3001 --admin-insecure-mode --tails-server-base-url http://tails:6543 --genesis-url http://test.bcovrin.vonx.io/genesis --wallet-type askar-anoncreds - --wallet-name bob + --wallet-name holder_anoncreds + --wallet-key insecure + --auto-provision + --log-level info + --debug-webhooks + --monitor-revocation-notification + healthcheck: + test: curl -s -o /dev/null -w '%{http_code}' "http://localhost:3001/status/live" | grep "200" > /dev/null + start_period: 30s + interval: 7s + timeout: 5s + retries: 5 + + holder_indy: + image: acapy-test + ports: + - "3003:3001" + command: > + start + --label Holder-Indy + --inbound-transport http 0.0.0.0 3000 + --outbound-transport http + --endpoint http://holder_indy:3000 + --admin 0.0.0.0 3001 + --admin-insecure-mode + --tails-server-base-url http://tails:6543 + --genesis-url http://test.bcovrin.vonx.io/genesis + --wallet-type askar + --wallet-name holder_indy --wallet-key insecure --auto-provision --log-level info @@ -63,15 +95,18 @@ build: context: ../.. environment: - - ALICE=http://alice:3001 - - BOB=http://bob:3001 + - AGENCY=http://agency:3001 + - HOLDER_ANONCREDS=http://holder_anoncreds:3001 + - HOLDER_INDY=http://holder_indy:3001 volumes: - ./example.py:/usr/src/app/example.py:ro,z command: python -m example depends_on: - alice: + agency: + condition: service_healthy + holder_anoncreds: condition: service_healthy - bob: + holder_indy: condition: service_healthy tails: diff --git a/scenarios/examples/anoncreds_issuance_and_revocation/example.py b/scenarios/examples/anoncreds_issuance_and_revocation/example.py index 6d7d1a1bd2..05a68c395a 100644 --- a/scenarios/examples/anoncreds_issuance_and_revocation/example.py +++ b/scenarios/examples/anoncreds_issuance_and_revocation/example.py @@ -7,23 +7,30 @@ import json from dataclasses import dataclass from os import getenv -from secrets import token_hex +from secrets import randbelow, token_hex +from typing import Any, Dict, List, Mapping, Optional, Tuple, Type, Union +from uuid import uuid4 from acapy_controller import Controller -from acapy_controller.controller import Minimal +from acapy_controller.controller import Minimal, MinType from acapy_controller.logging import logging_to_stdout -from acapy_controller.models import V20PresExRecord, V20PresExRecordList +from acapy_controller.models import ( + CreateWalletResponse, + V20CredExRecordIndy, + V20PresExRecord, + V20PresExRecordList, +) from acapy_controller.protocols import ( DIDResult, didexchange, - indy_issue_credential_v2, - indy_present_proof_v2, + indy_anoncred_credential_artifacts, params, ) from aiohttp import ClientSession -ALICE = getenv("ALICE", "http://alice:3001") -BOB = getenv("BOB", "http://bob:3001") +AGENCY = getenv("AGENCY", "http://agency:3001") +HOLDER_ANONCREDS = getenv("HOLDER_ANONCREDS", "http://holder_anoncreds:3001") +HOLDER_INDY = getenv("HOLDER_INDY", "http://holder_indy:3001") def summary(presentation: V20PresExRecord) -> str: @@ -33,7 +40,9 @@ def summary(presentation: V20PresExRecord) -> str: { "state": presentation.state, "verified": presentation.verified, - "presentation_request": request.dict(by_alias=True) if request else None, + "presentation_request": request.model_dump(by_alias=True) + if request + else None, }, indent=2, sort_keys=True, @@ -41,32 +50,395 @@ def summary(presentation: V20PresExRecord) -> str: @dataclass -class SchemaResult(Minimal): +class SchemaResultAnoncreds(Minimal): """Schema result.""" schema_state: dict @dataclass -class CredDefResult(Minimal): +class CredDefResultAnoncreds(Minimal): """Credential definition result.""" credential_definition_state: dict +@dataclass +class V20CredExRecord(Minimal): + """V2.0 credential exchange record.""" + + state: str + cred_ex_id: str + connection_id: str + thread_id: str + + +@dataclass +class V20CredExRecordFormat(Minimal): + """V2.0 credential exchange record anoncreds.""" + + rev_reg_id: Optional[str] = None + cred_rev_id: Optional[str] = None + + +@dataclass +class V20CredExRecordDetail(Minimal): + """V2.0 credential exchange record detail.""" + + cred_ex_record: V20CredExRecord + details: Optional[V20CredExRecordFormat] = None + + +@dataclass +class ProofRequest(Minimal): + """Proof request.""" + + requested_attributes: Dict[str, Any] + requested_predicates: Dict[str, Any] + + +@dataclass +class PresSpec(Minimal): + """Presentation specification.""" + + requested_attributes: Dict[str, Any] + requested_predicates: Dict[str, Any] + self_attested_attributes: Dict[str, Any] + + +@dataclass +class CredInfo(Minimal): + """Credential information.""" + + referent: str + attrs: Dict[str, Any] + + +@dataclass +class CredPrecis(Minimal): + """Credential precis.""" + + cred_info: CredInfo + presentation_referents: List[str] + + @classmethod + def deserialize(cls: Type[MinType], value: Mapping[str, Any]) -> MinType: + """Deserialize the credential precis.""" + value = dict(value) + if cred_info := value.get("cred_info"): + value["cred_info"] = CredInfo.deserialize(cred_info) + return super().deserialize(value) + + +@dataclass +class Settings(Minimal): + """Settings information.""" + + +def auto_select_credentials_for_presentation_request( + presentation_request: Union[ProofRequest, dict], + relevant_creds: List[CredPrecis], +) -> PresSpec: + """Select credentials to use for presentation automatically.""" + if isinstance(presentation_request, dict): + presentation_request = ProofRequest.deserialize(presentation_request) + + requested_attributes = {} + for pres_referrent in presentation_request.requested_attributes.keys(): + for cred_precis in relevant_creds: + if pres_referrent in cred_precis.presentation_referents: + requested_attributes[pres_referrent] = { + "cred_id": cred_precis.cred_info.referent, + "revealed": True, + } + requested_predicates = {} + for pres_referrent in presentation_request.requested_predicates.keys(): + for cred_precis in relevant_creds: + if pres_referrent in cred_precis.presentation_referents: + requested_predicates[pres_referrent] = { + "cred_id": cred_precis.cred_info.referent, + } + + return PresSpec.deserialize( + { + "requested_attributes": requested_attributes, + "requested_predicates": requested_predicates, + "self_attested_attributes": {}, + } + ) + + +async def issue_credential_v2( + issuer: Controller, + holder: Controller, + issuer_connection_id: str, + holder_connection_id: str, + cred_def_id: str, + attributes: Mapping[str, str], +) -> Tuple[V20CredExRecordDetail, V20CredExRecordDetail]: + """Issue an credential using issue-credential/2.0. + + Issuer and holder should already be connected. + """ + + is_issuer_anoncreds = (await issuer.get("/settings", response=Settings)).get( + "wallet.type" + ) == "askar-anoncreds" + is_holder_anoncreds = (await holder.get("/settings", response=Settings)).get( + "wallet.type" + ) == "askar-anoncreds" + + if is_issuer_anoncreds: + _filter = {"anoncreds": {"cred_def_id": cred_def_id}} + else: + _filter = {"indy": {"cred_def_id": cred_def_id}} + issuer_cred_ex = await issuer.post( + "/issue-credential-2.0/send-offer", + json={ + "auto_issue": False, + "auto_remove": False, + "comment": "Credential from minimal example", + "trace": False, + "connection_id": issuer_connection_id, + "filter": _filter, + "credential_preview": { + "type": "issue-credential-2.0/2.0/credential-preview", # pyright: ignore + "attributes": [ + { + "mime_type": None, + "name": name, + "value": value, + } + for name, value in attributes.items() + ], + }, + }, + response=V20CredExRecord, + ) + issuer_cred_ex_id = issuer_cred_ex.cred_ex_id + + holder_cred_ex = await holder.event_with_values( + topic="issue_credential_v2_0", + event_type=V20CredExRecord, + connection_id=holder_connection_id, + state="offer-received", + ) + holder_cred_ex_id = holder_cred_ex.cred_ex_id + + await holder.post( + f"/issue-credential-2.0/records/{holder_cred_ex_id}/send-request", + response=V20CredExRecord, + ) + + await issuer.event_with_values( + topic="issue_credential_v2_0", + cred_ex_id=issuer_cred_ex_id, + state="request-received", + ) + + await issuer.post( + f"/issue-credential-2.0/records/{issuer_cred_ex_id}/issue", + json={}, + response=V20CredExRecordDetail, + ) + + await holder.event_with_values( + topic="issue_credential_v2_0", + cred_ex_id=holder_cred_ex_id, + state="credential-received", + ) + + await holder.post( + f"/issue-credential-2.0/records/{holder_cred_ex_id}/store", + json={}, + response=V20CredExRecordDetail, + ) + issuer_cred_ex = await issuer.event_with_values( + topic="issue_credential_v2_0", + event_type=V20CredExRecord, + cred_ex_id=issuer_cred_ex_id, + state="done", + ) + issuer_indy_record = await issuer.event_with_values( + topic="issue_credential_v2_0_anoncreds" + if is_issuer_anoncreds + else "issue_credential_v2_0_indy", + event_type=V20CredExRecordIndy, + ) + + holder_cred_ex = await holder.event_with_values( + topic="issue_credential_v2_0", + event_type=V20CredExRecord, + cred_ex_id=holder_cred_ex_id, + state="done", + ) + holder_indy_record = await holder.event_with_values( + topic="issue_credential_v2_0_anoncreds" + if is_holder_anoncreds + else "issue_credential_v2_0_indy", + event_type=V20CredExRecordIndy, + ) + + return ( + V20CredExRecordDetail(cred_ex_record=issuer_cred_ex, details=issuer_indy_record), + V20CredExRecordDetail( + cred_ex_record=holder_cred_ex, + details=holder_indy_record, + ), + ) + + +async def present_proof_v2( + holder: Controller, + verifier: Controller, + holder_connection_id: str, + verifier_connection_id: str, + *, + name: Optional[str] = None, + version: Optional[str] = None, + comment: Optional[str] = None, + requested_attributes: Optional[List[Mapping[str, Any]]] = None, + requested_predicates: Optional[List[Mapping[str, Any]]] = None, + non_revoked: Optional[Mapping[str, int]] = None, +): + """Present an credential using present proof v2.""" + + is_verifier_anoncreds = (await verifier.get("/settings", response=Settings)).get( + "wallet.type" + ) == "askar-anoncreds" + + attrs = { + "name": name or "proof", + "version": version or "0.1.0", + "nonce": str(randbelow(10**10)), + "requested_attributes": { + str(uuid4()): attr for attr in requested_attributes or [] + }, + "requested_predicates": { + str(uuid4()): pred for pred in requested_predicates or [] + }, + "non_revoked": (non_revoked if non_revoked else None), + } + + if is_verifier_anoncreds: + presentation_request = { + "anoncreds": attrs, + } + else: + presentation_request = { + "indy": attrs, + } + verifier_pres_ex = await verifier.post( + "/present-proof-2.0/send-request", + json={ + "auto_verify": False, + "comment": comment or "Presentation request from minimal", + "connection_id": verifier_connection_id, + "presentation_request": presentation_request, + "trace": False, + }, + response=V20PresExRecord, + ) + verifier_pres_ex_id = verifier_pres_ex.pres_ex_id + + holder_pres_ex = await holder.event_with_values( + topic="present_proof_v2_0", + event_type=V20PresExRecord, + connection_id=holder_connection_id, + state="request-received", + ) + assert holder_pres_ex.pres_request + holder_pres_ex_id = holder_pres_ex.pres_ex_id + + relevant_creds = await holder.get( + f"/present-proof-2.0/records/{holder_pres_ex_id}/credentials", + response=List[CredPrecis], + ) + assert holder_pres_ex.by_format.pres_request + proof_request = holder_pres_ex.by_format.pres_request.get( + "anoncreds" + ) or holder_pres_ex.by_format.pres_request.get("indy") + pres_spec = auto_select_credentials_for_presentation_request( + proof_request, relevant_creds + ) + if is_verifier_anoncreds: + proof = {"anoncreds": pres_spec.serialize()} + else: + proof = {"indy": pres_spec.serialize()} + await holder.post( + f"/present-proof-2.0/records/{holder_pres_ex_id}/send-presentation", + json=proof, + response=V20PresExRecord, + ) + + await verifier.event_with_values( + topic="present_proof_v2_0", + event_type=V20PresExRecord, + pres_ex_id=verifier_pres_ex_id, + state="presentation-received", + ) + await verifier.post( + f"/present-proof-2.0/records/{verifier_pres_ex_id}/verify-presentation", + json={}, + response=V20PresExRecord, + ) + verifier_pres_ex = await verifier.event_with_values( + topic="present_proof_v2_0", + event_type=V20PresExRecord, + pres_ex_id=verifier_pres_ex_id, + state="done", + ) + + holder_pres_ex = await holder.event_with_values( + topic="present_proof_v2_0", + event_type=V20PresExRecord, + pres_ex_id=holder_pres_ex_id, + state="done", + ) + + return holder_pres_ex, verifier_pres_ex + + async def main(): """Test Controller protocols.""" - async with Controller(base_url=ALICE) as alice, Controller(base_url=BOB) as bob: + issuer_name = "issuer" + token_hex(8) + async with Controller(base_url=AGENCY) as agency: + issuer = await agency.post( + "/multitenancy/wallet", + json={ + "label": issuer_name, + "wallet_name": issuer_name, + "wallet_type": "askar", + }, + response=CreateWalletResponse, + ) + + async with Controller( + base_url=AGENCY, + wallet_id=issuer.wallet_id, + subwallet_token=issuer.token, + ) as issuer, Controller(base_url=HOLDER_ANONCREDS) as holder_anoncreds, Controller( + base_url=HOLDER_INDY + ) as holder_indy: + """ + This section of the test script demonstrates the issuance, presentation and + revocation of a credential where both the issuer is not anoncreds capable + (wallet type askar) and the holder is anoncreds capable + (wallet type askar-anoncreds). + """ + # Connecting - alice_conn, bob_conn = await didexchange(alice, bob) + issuer_conn_with_anoncreds_holder, holder_anoncreds_conn = await didexchange( + issuer, holder_anoncreds + ) # Issuance prep - config = (await alice.get("/status/config"))["config"] + config = (await issuer.get("/status/config"))["config"] genesis_url = config.get("ledger.genesis_url") - public_did = (await alice.get("/wallet/did/public", response=DIDResult)).result + public_did = (await issuer.get("/wallet/did/public", response=DIDResult)).result if not public_did: public_did = ( - await alice.post( + await issuer.post( "/wallet/did/create", json={"method": "sov", "options": {"key_type": "ed25519"}}, response=DIDResult, @@ -87,21 +459,154 @@ async def main(): ) as resp: assert resp.ok - await alice.post("/wallet/did/public", params=params(did=public_did.did)) + await issuer.post("/wallet/did/public", params=params(did=public_did.did)) + + _, cred_def = await indy_anoncred_credential_artifacts( + issuer, + ["firstname", "lastname"], + support_revocation=True, + ) + + # Issue a credential + issuer_cred_ex, _ = await issue_credential_v2( + issuer, + holder_anoncreds, + issuer_conn_with_anoncreds_holder.connection_id, + holder_anoncreds_conn.connection_id, + cred_def.credential_definition_id, + {"firstname": "Anoncreds", "lastname": "Holder"}, + ) + + # Present the the credential's attributes + await present_proof_v2( + holder_anoncreds, + issuer, + holder_anoncreds_conn.connection_id, + issuer_conn_with_anoncreds_holder.connection_id, + requested_attributes=[{"name": "firstname"}], + ) + + # Revoke credential + await issuer.post( + url="/revocation/revoke", + json={ + "connection_id": issuer_conn_with_anoncreds_holder.connection_id, + "rev_reg_id": issuer_cred_ex.details.rev_reg_id, + "cred_rev_id": issuer_cred_ex.details.cred_rev_id, + "publish": True, + "notify": True, + "notify_version": "v1_0", + }, + ) + + await holder_anoncreds.record(topic="revocation-notification") + + """ + This section of the test script demonstrates the issuance, presentation and + revocation of a credential where the issuer and holder are not anoncreds + capable. Both are askar wallet type. + """ + + # Connecting + issuer_conn_with_indy_holder, holder_indy_conn = await didexchange( + issuer, holder_indy + ) + + # Issue a credential + issuer_cred_ex, _ = await issue_credential_v2( + issuer, + holder_indy, + issuer_conn_with_indy_holder.connection_id, + holder_indy_conn.connection_id, + cred_def.credential_definition_id, + {"firstname": "Indy", "lastname": "Holder"}, + ) + + # Present the the credential's attributes + await present_proof_v2( + holder_indy, + issuer, + holder_indy_conn.connection_id, + issuer_conn_with_indy_holder.connection_id, + requested_attributes=[{"name": "firstname"}], + ) + # Query presentations + presentations = await issuer.get( + "/present-proof-2.0/records", + response=V20PresExRecordList, + ) + + # Presentation summary + for _, pres in enumerate(presentations.results): + print(summary(pres)) + + # Revoke credential + await issuer.post( + url="/revocation/revoke", + json={ + "connection_id": issuer_conn_with_indy_holder.connection_id, + "rev_reg_id": issuer_cred_ex.details.rev_reg_id, + "cred_rev_id": issuer_cred_ex.details.cred_rev_id, + "publish": True, + "notify": True, + "notify_version": "v1_0", + }, + ) + + await holder_indy.record(topic="revocation-notification") - schema = await alice.post( + """ + Upgrade the issuer tenant to anoncreds capable wallet type. When upgrading a + tenant the agent doesn't require a restart. That is why the test is done + with multitenancy + """ + await issuer.post( + "/anoncreds/wallet/upgrade", + params={ + "wallet_name": issuer_name, + }, + ) + + # Wait for the upgrade to complete + await asyncio.sleep(2) + + """ + Do issuance and presentation again after the upgrade. This time the issuer is + an anoncreds capable wallet (wallet type askar-anoncreds). + """ + # Presentation for anoncreds capable holder on existing credential + await present_proof_v2( + holder_anoncreds, + issuer, + holder_anoncreds_conn.connection_id, + issuer_conn_with_anoncreds_holder.connection_id, + requested_attributes=[{"name": "firstname"}], + ) + + # Presentation for indy capable holder on existing credential + await present_proof_v2( + holder_indy, + issuer, + holder_indy_conn.connection_id, + issuer_conn_with_indy_holder.connection_id, + requested_attributes=[{"name": "firstname"}], + ) + + # Create a new schema and cred def with different attributes on new + # anoncreds endpoints + schema = await issuer.post( "/anoncreds/schema", json={ "schema": { "name": "anoncreds-test-" + token_hex(8), "version": "1.0", - "attrNames": ["firstname", "lastname"], + "attrNames": ["middlename"], "issuerId": public_did.did, } }, - response=SchemaResult, + response=SchemaResultAnoncreds, ) - cred_def = await alice.post( + cred_def = await issuer.post( "/anoncreds/credential-definition", json={ "credential_definition": { @@ -109,77 +614,73 @@ async def main(): "schemaId": schema.schema_state["schema_id"], "tag": token_hex(8), }, - "options": { - "revocation_registry_size": 2000, - "support_revocation": True, - }, + "options": {"support_revocation": True, "revocation_registry_size": 10}, }, - response=CredDefResult, + response=CredDefResultAnoncreds, ) - # Issue a credential - alice_cred_ex, _ = await indy_issue_credential_v2( - alice, - bob, - alice_conn.connection_id, - bob_conn.connection_id, + # Issue a new credential to anoncreds holder + issuer_cred_ex, _ = await issue_credential_v2( + issuer, + holder_anoncreds, + issuer_conn_with_anoncreds_holder.connection_id, + holder_anoncreds_conn.connection_id, cred_def.credential_definition_state["credential_definition_id"], - {"firstname": "Bob", "lastname": "Builder"}, + {"middlename": "Anoncreds"}, ) - - # Present the the credential's attributes - await indy_present_proof_v2( - bob, - alice, - bob_conn.connection_id, - alice_conn.connection_id, - requested_attributes=[{"name": "firstname"}], + # Presentation for anoncreds capable holder + await present_proof_v2( + holder_anoncreds, + issuer, + holder_anoncreds_conn.connection_id, + issuer_conn_with_anoncreds_holder.connection_id, + requested_attributes=[{"name": "middlename"}], ) - # Revoke credential - await alice.post( + await issuer.post( url="/anoncreds/revocation/revoke", json={ - "connection_id": alice_conn.connection_id, - "rev_reg_id": alice_cred_ex.indy.rev_reg_id, - "cred_rev_id": alice_cred_ex.indy.cred_rev_id, + "connection_id": issuer_conn_with_anoncreds_holder.connection_id, + "rev_reg_id": issuer_cred_ex.details.rev_reg_id, + "cred_rev_id": issuer_cred_ex.details.cred_rev_id, "publish": True, "notify": True, "notify_version": "v1_0", }, ) + await holder_anoncreds.record(topic="revocation-notification") - await bob.record(topic="revocation-notification") - - # Request proof, no interval - await indy_present_proof_v2( - bob, - alice, - bob_conn.connection_id, - alice_conn.connection_id, - requested_attributes=[ - { - "name": "firstname", - "restrictions": [ - { - "cred_def_id": cred_def.credential_definition_state[ - "credential_definition_id" - ] - } - ], - } - ], + # Issue a new credential to indy holder + issuer_cred_ex, _ = await issue_credential_v2( + issuer, + holder_indy, + issuer_conn_with_indy_holder.connection_id, + holder_indy_conn.connection_id, + cred_def.credential_definition_state["credential_definition_id"], + {"middlename": "Indy"}, ) - - # Query presentations - presentations = await alice.get( - "/present-proof-2.0/records", - response=V20PresExRecordList, + # Presentation for indy holder + await present_proof_v2( + holder_indy, + issuer, + holder_indy_conn.connection_id, + issuer_conn_with_indy_holder.connection_id, + requested_attributes=[{"name": "middlename"}], + ) + # Revoke credential + await issuer.post( + url="/anoncreds/revocation/revoke", + json={ + "connection_id": issuer_conn_with_indy_holder.connection_id, + "rev_reg_id": issuer_cred_ex.details.rev_reg_id, + "cred_rev_id": issuer_cred_ex.details.cred_rev_id, + "publish": True, + "notify": True, + "notify_version": "v1_0", + }, ) - # Presentation summary - for i, pres in enumerate(presentations.results): - print(summary(pres)) + await holder_indy.record(topic="revocation-notification") if __name__ == "__main__":